diff --git a/config.yml b/config.yml new file mode 100644 index 00000000..ef9c3910 --- /dev/null +++ b/config.yml @@ -0,0 +1,69 @@ + +# Wearing heavy pieces of armors reduces +# your movement speed by a specific amount, +# relative to your current move speed. +heavy-armors: + + # The movement speed reduction in % for each heavy armor piece worn. + speed-malus: 5.0 + + # The list of heavy armor pieces. By default gold is not + # considered a heavy armor to incite players to use this armor set. + list: + - DIAMOND_HELMET + - DIAMOND_CHESTPLATE + - DIAMOND_LEGGINGS + - DIAMOND_BOOTS + - IRON_HELMET + - IRON_CHESTPLATE + - IRON_LEGGINGS + - IRON_BOOTS + +# The list of all conditions which must be met for the +# BLOCK REGEN and BLOCK RESTRICTIONS to apply. +# Set to 'custom-min-conditions: []' for no condition. +custom-mine-conditions: +- 'world{name="world1,world2,world_nether,world_the_end"}' +- 'region{name="region1,region2,__global__"}' + +# Offset is the distance traveled on X and Y coordinates +# Height is the Y velocity coordinate. Lootsplosions +# only trigger with MythicMobs monsters. +# Requires a SERVER reload when changed. +lootsplosion: + enabled: true + mmoitems-color: true + offset: .2 + height: .6 + +party: + + # Edit party buffs here. You may + # add as many stats as you want. + buff: + health-regeneration: 3% + additional-experience: 5% + + # Prefix you need to put in the chat + # to talk in the party chat. + chat-prefix: '@' + +# Enable this open to override vanilla EXP and display +# level progress on the vanilla experience bar. +# Requires a SERVER reload when changed. +override-vanilla-exp: true + +# Allows to scale health up/down to a specific +# amount so extra health does not fill up the screen. +# Requires a SERVER reload when changed. +health-scale: + enabled: true + scale: 40 + +# Players can swap their hotbar with the 9 inventory slots +# right above it by pressing [swap items] while crouching. +hotbar-swap: true + +# Use this option if you're having issue with Anvil GUIs. +# This replaces anvil inputs by chat inputs. +use-chat-input: true diff --git a/default/attributes.yml b/default/attributes.yml new file mode 100644 index 00000000..cc8f4347 --- /dev/null +++ b/default/attributes.yml @@ -0,0 +1,30 @@ + +# Attribute ID +strength: + + name: Strength + + # Maximum amount of points players + # may spend in this attribute. + max-points: 40 + + # Buffs given every 1 attribute point spent + # in this specific attribute. + buff: + weapon_damage: 2 + max_health: 1% + +dexterity: + name: Dexterity + max-points: 40 + buff: + physical_damage: 1.5 + projectile_damage: 1 + attack_speed: 0.5% + +intelligence: + name: Intelligence + max-points: 40 + buff: + magical_damage: 2 + cooldown_reduction: 1 diff --git a/default/chests.yml b/default/chests.yml new file mode 100644 index 00000000..3826e9f0 --- /dev/null +++ b/default/chests.yml @@ -0,0 +1,24 @@ + +'world 59 71 131': + + # Create directly your drop table here. + drop-table: + items: + - 'vanilla{type=DIAMOND} 1 1-3' + - 'gold{} .9 1-3' + - 'gold{} .9 1-3' + - 'gold{} .9 1-3' + - 'gold{} .9 1-3' + - 'gold{} .9 1-3' + - 'gold{} .9 1-3' + - 'note{min=1;max=10} .9 1-3' + + # Ticks the chest takes to appear again. + regen-time: 40 + + # The particle played every 4sec around the chest. + # Types available: helix|offset|galaxy + # Particle names here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html + effect: + type: helix + particle: FLAME diff --git a/default/classes/arcane-mage.yml b/default/classes/arcane-mage.yml new file mode 100644 index 00000000..ed7caf7e --- /dev/null +++ b/default/classes/arcane-mage.yml @@ -0,0 +1,103 @@ +options: + display: false +mana: + char: ■ + color: BLUE + name: 'Mana' +display: + name: 'Arcane Mage' + lore: + - 'The Mage has mastered the power of the' + - 'Arcanes, taking down any enemy on his path' + - 'using powerful magic & ranged abilities.' + attribute-lore: + - '&a+ &7Mana Regeneration' + - '&a+ &7Health Regeneration' + - '&a+ &7Max Mana' + - '&c- &7Max Health' + - '' + - '&8&lStrength' + - '&7 Attack Damage: &c1 &7(+&c0&7)' + - '&7 Attack Speed: &c4 &7(+&c0&7)' + - '&7 Max Health: &c18 &7(+&c0&7)' + - '' + - '&8&lDexterity' + - '&7 Knockback Resistance: &a0% &7(+&a0%&7)' + - '&7 Movement Speed: &a20 &7(+&a0&7)' + - '&7 Speed Malus Reduction: &a0% &7(+&a0%&7)' + - '' + - '&8&lIntellect' + - '&7 Max Mana: &930 &7(+&91.3&7)' + - '&7 Health Regen: &90.13 &7(+&90&7)' + - '&7 Mana Regen: &90.2 &7(+&90.04&7)' + item: BLAZE_POWDER:1 # Supports custom model data/texture by durability + +# Players cannot go further than Lvl 100 +max-level: 100 + +triggers: + level-up: + - 'command{format="mmocore admin skill-points %player% 1"}' + +attributes: + max-health: + base: 18 + per-level: 0 + max-mana: + base: 30 + per-level: 1.3 + mana-regeneration: + base: .2 + per-level: .04 + health-regeneration: + base: 0.13 + per-level: 0 + +skills: + FIRE_STORM: + level: 1 + max-level: 30 + damage: + base: 8.0 + per-level: 4.0 + ignite: + base: 2.0 + per-level: 0.1 + mana: + base: 15.0 + per-level: 2.0 + cooldown: + base: 5.0 + per-level: -0.1 + max: 5.0 + min: 1.0 + + POWER_MARK: + level: 3 + max-level: 30 + FIREBALL: + level: 5 + max-level: 30 + MINOR_HEALINGS: + level: 6 + max-level: 30 + ICE_SPIKES: + level: 8 + max-level: 30 + AMBERS: + level: 9 + max-level: 30 + WEAKEN: + level: 10 + max-level: 30 + WARP: + level: 13 + max-level: 30 + GREATER_HEALINGS: + level: 15 + max-level: 30 + +# Experience sources for main class experience. +main-exp-sources: +- 'killmob{type=ZOMBIE;amount=1-3}' +- 'killmob{type=ENDERMAN;amount=6-9}' diff --git a/default/classes/human.yml b/default/classes/human.yml new file mode 100644 index 00000000..5b61e11c --- /dev/null +++ b/default/classes/human.yml @@ -0,0 +1,32 @@ +display: + name: 'Human' + +options: + default: true + display: false + + # Health/mana regen scales on missing health/mana + missing-health-regen: false + missing-mana-regen: false + + # Resource regen scales on max mana/health + max-health-regen: false + max-mana-regen: false + + # Only regens when out of combat + off-combat-health-regen: false + off-combat-mana-regen: false + +mana: + char: ■ + color: BLUE + name: 'Mana' + +triggers: + level-up: + - 'command{format="mmocore admin skill-points %player% 1"}' + +# Experience sources for main class experience. +main-exp-sources: +- 'killmob{type=ZOMBIE;amount=1-3}' +- 'killmob{type=ENDERMAN;amount=6-9}' diff --git a/default/classes/mage.yml b/default/classes/mage.yml new file mode 100644 index 00000000..dcf93930 --- /dev/null +++ b/default/classes/mage.yml @@ -0,0 +1,113 @@ +display: + name: 'Mage' + lore: + - 'The Mage has mastered the power of the' + - 'Arcanes, taking down any enemy on his path' + - 'using powerful magic & ranged abilities.' + attribute-lore: + - '&a+ &7Mana Regeneration' + - '&a+ &7Health Regeneration' + - '&a+ &7Max Mana' + - '&c- &7Max Health' + - '' + - '&8&lStrength' + - '&7 Attack Damage: &c1 &7(+&c0&7)' + - '&7 Attack Speed: &c4 &7(+&c0&7)' + - '&7 Max Health: &c18 &7(+&c0&7)' + - '' + - '&8&lDexterity' + - '&7 Knockback Resistance: &a0% &7(+&a0%&7)' + - '&7 Movement Speed: &a20 &7(+&a0&7)' + - '&7 Speed Malus Reduction: &a0% &7(+&a0%&7)' + - '' + - '&8&lIntellect' + - '&7 Max Mana: &927 &7(+&91.2&7)' + - '&7 Health Regen: &90.13 &7(+&90&7)' + - '&7 Mana Regen: &90.2 &7(+&90.04&7)' + item: BLAZE_POWDER + +# Players cannot go further than Lvl 100 +max-level: 100 + +triggers: + level-up: + - 'command{format="mmocore admin skill-points %player% 1"}' + +mana: + char: ■ + color: BLUE + name: 'Mana' + +cast-particle: + particle: SPELL_INSTANT + +options: + off-combat-health-regen: true + +attributes: + max-health: + base: 18 + per-level: 0 + max-mana: + base: 27 + per-level: 1.2 + mana-regeneration: + base: .2 + per-level: .04 + health-regeneration: + base: 0.13 + per-level: 0 + +subclasses: + ARCANE_MAGE: 10 + +skills: + FIRE_STORM: + level: 1 + max-level: 30 + + # Specific skill modifiers based on class. + # Arcane mage's fire storm may deal more damage + # than an apprentice mage's fire storm. + damage: + base: 5.0 + per-level: 3.0 + ignite: + base: 2.0 + per-level: 0.1 + mana: + base: 15.0 + per-level: 2.0 + cooldown: + base: 5.0 + per-level: -0.1 + max: 5.0 + min: 1.0 + POWER_MARK: + level: 3 + max-level: 30 + FIREBALL: + level: 5 + max-level: 30 + MINOR_HEALINGS: + level: 6 + max-level: 30 + ICE_SPIKES: + level: 8 + AMBERS: + level: 9 + max-level: 30 + WEAKEN: + level: 10 + max-level: 30 + WARP: + level: 13 + max-level: 30 + GREATER_HEALINGS: + level: 15 + max-level: 30 + +# Experience sources for main class experience. +main-exp-sources: +- 'killmob{type=ZOMBIE;amount=1-3}' +- 'killmob{type=ENDERMAN;amount=6-9}' diff --git a/default/classes/marksman.yml b/default/classes/marksman.yml new file mode 100644 index 00000000..c6f94439 --- /dev/null +++ b/default/classes/marksman.yml @@ -0,0 +1,74 @@ +display: + name: 'Marksman' + lore: + - 'The marksman is a swift and accurate' + - 'ranged fighter, never missing any arrow.' + attribute-lore: + - '&a+ &7Knockback Resistance' + - '&a+ &7Speed Malus Reduction' + - '&c- &7Max Health' + - '&c- &7Movement Speed' + - '' + - '&8&lStrength' + - '&7 Attack Damage: &c1 &7(+&c0&7)' + - '&7 Attack Speed: &c4 &7(+&c0&7)' + - '&7 Max Health: &c18 &7(+&c0&7)' + - '' + - '&8&lDexterity' + - '&7 Knockback Resistance: &a15% &7(+&a1%&7)' + - '&7 Movement Speed: &a21 &7(+&a0&7)' + - '&7 Speed Malus Reduction: &a10% &7(+&a2%&7)' + - '' + - '&8&lIntellect' + - '&7 Max Mana: &920 &7(+&90&7)' + - '&7 Health Regen: &90.1 &7(+&90&7)' + - '&7 Mana Regen: &90.166 &7(+&90&7)' + item: BOW + +# Players cannot go further than Lvl 100 +max-level: 100 + +triggers: + level-up: + - 'command{format="mmocore admin skill-points %player% 1"}' + +mana: + char: ■ + color: BLUE + name: 'Mana' + +cast-particle: + particle: CRIT + +skills: + WEAKEN: + level: 3 + max-level: 30 + COMBO_ATTACK: + level: 7 + max-level: 30 + EVADE: + level: 9 + max-level: 30 + MINOR_HEALINGS: + level: 10 + max-level: 30 + +attributes: + knockback-resistance: + base: 15 + per-level: 1 + speed-malus-reduction: + base: 10 + per-level: 2 + max-health: + base: 18 + per-level: 0 + movement-speed: + base: .21 + per-level: 0 + +# Experience sources for main class experience. +main-exp-sources: +- 'killmob{type=ZOMBIE;amount=1-3}' +- 'killmob{type=ENDERMAN;amount=6-9}' diff --git a/default/classes/paladin.yml b/default/classes/paladin.yml new file mode 100644 index 00000000..bf99802e --- /dev/null +++ b/default/classes/paladin.yml @@ -0,0 +1,89 @@ +display: + name: 'Paladin' + lore: + - 'The paladin is a heavy tanky fighter' + - 'who uses his mana to heal and protect' + - 'members from his team.' + attribute-lore: + - '&a+ &7Max Health' + - '&a+ &7Knockback Resistance' + - '&a+ &7Speed Malus Reduction' + - '&c- &7Movement Speed' + - '&c- &7Attack Speed' + - '' + - '&8&lStrength' + - '&7 Attack Damage: &c1 &7(+&c0&7)' + - '&7 Attack Speed: &c3.7 &7(+&c0&7)' + - '&7 Max Health: &c22 &7(+&c.3&7)' + - '' + - '&8&lDexterity' + - '&7 Knockback Resistance: &a30% &7(+&a1%&7)' + - '&7 Movement Speed: &a19 &7(+&a0&7)' + - '&7 Speed Malus Reduction: &a30% &7(+&a2%&7)' + - '' + - '&8&lIntellect' + - '&7 Max Mana: &920 &7(+&90&7)' + - '&7 Health Regen: &90.1 &7(+&90&7)' + - '&7 Mana Regen: &90.166 &7(+&90&7)' + item: ENCHANTED_GOLDEN_APPLE:0 # Supports custom model data/texture by durability + +# Players cannot go further than Lvl 100 +max-level: 100 + +mana: + char: ■ + color: BLUE + name: 'Mana' + +triggers: + level-up: + - 'command{format="mmocore admin skill-points %player% 1"}' + + # Get some mana back when dealing magic damage. + magic-damage: + - 'mana{operation=GIVE;amount=1}' + +cast-particle: + particle: VILLAGER_HAPPY + +skills: + DEEP_WOUND: + level: 10 + max-level: 30 + WEAKEN: + level: 5 + max-level: 30 + AMBERS: + level: 15 + max-level: 30 + GREATER_HEALINGS: + level: 7 + max-level: 30 + MINOR_HEALINGS: + level: 3 + max-level: 30 + HUMAN_SHIELD: + level: 13 + max-level: 20 + +attributes: + knockback-resistance: + base: 30 + per-level: 1 + speed-malus-reduction: + base: 30 + per-level: 2 + max-health: + base: 22 + per-level: 0.3 + attack-speed: + base: 3.7 + per-level: 0 + movement-speed: + base: .19 + per-level: 0 + +# Experience sources for main class experience. +main-exp-sources: +- 'killmob{type=ZOMBIE;amount=1-3}' +- 'killmob{type=ENDERMAN;amount=6-9}' diff --git a/default/classes/rogue.yml b/default/classes/rogue.yml new file mode 100644 index 00000000..1b1dd2b6 --- /dev/null +++ b/default/classes/rogue.yml @@ -0,0 +1,88 @@ +display: + name: 'Rogue' + lore: + - 'His important damage burst potential and' + - 'his lack of health forces him to one' + - 'shot his targets before being taken down.' + attribute-lore: + - '&a+ &7Attack Speed' + - '&c- &7Max Health' + - '&c- &7Movement Speed' + - '' + - '&8&lStrength' + - '&7 Attack Damage: &c1 &7(+&c0&7)' + - '&7 Attack Speed: &c4.2 &7(+&c0.05&7)' + - '&7 Max Health: &c18 &7(+&c0&7)' + - '' + - '&8&lDexterity' + - '&7 Knockback Resistance: &a0% &7(+&a0%&7)' + - '&7 Movement Speed: &a21 &7(+&a0&7)' + - '&7 Speed Malus Reduction: &a0% &7(+&a0%&7)' + - '' + - '&8&lIntellect' + - '&7 Max Mana: &920 &7(+&90&7)' + - '&7 Health Regen: &90.1 &7(+&90&7)' + - '&7 Mana Regen: &90.166 &7(+&90&7)' + item: LEATHER_BOOTS + +# Players cannot go further than Lvl 100 +max-level: 100 + +triggers: + level-up: + - 'command{format="mmocore admin skill-points %player% 1"}' + +options: + off-combat-mana-regen: true + off-combat-health-regen: true + missing-health-regen: true + +cast-particle: + particle: SPELL_WITCH + +mana: + char: ■ + color: BLUE + name: 'Mana' + +skills: + DEEP_WOUND: + level: 1 + max-level: 30 + WEAKEN: + level: 3 + max-level: 30 + EMPOWERED_ATTACK: + level: 5 + max-level: 30 + COMBO_ATTACK: + level: 7 + max-level: 30 + EVADE: + level: 9 + max-level: 30 + FURTIVE_STRIKE: + level: 4 + max-level: 30 + MINOR_HEALINGS: + level: 10 + max-level: 30 + SNEAKY_PICKY: + level: 2 + max-level: 20 + +attributes: + attack-speed: + base: 4.2 + per-level: 0.05 + max-health: + base: 18 + per-level: 0 + movement-speed: + base: .21 + per-level: 0 + +# Experience sources for main class experience. +main-exp-sources: +- 'killmob{type=ZOMBIE;amount=1-3}' +- 'killmob{type=ENDERMAN;amount=6-9}' diff --git a/default/classes/warrior.yml b/default/classes/warrior.yml new file mode 100644 index 00000000..1117b99a --- /dev/null +++ b/default/classes/warrior.yml @@ -0,0 +1,111 @@ +display: + name: 'Warrior' + lore: + - 'The warrior has extraordinary fighting and' + - 'evolving capabilities, which grant him much' + - 'higher per-level stats than other classes.' + - '' + - 'The warrior collects rage when casting skills,' + - 'temporarily increasing his base damage.' + attribute-lore: + - '&a+ &7Attack Damage' + - '&a+ &7Attack Speed' + - '&a+ &7Max Health' + - '&a+ &7Speed Malus Reduction' + - '&c- &7Movement Speed' + - '' + - '&8&lStrength' + - '&7 Attack Damage: &c2 &7(+&c0.1&7)' + - '&7 Attack Speed: &c4.2 &7(+&c0.05&7)' + - '&7 Max Health: &c24 &7(+&c0.5&7)' + - '' + - '&8&lDexterity' + - '&7 Knockback Resistance: &a0% &7(+&a0%&7)' + - '&7 Movement Speed: &a19 &7(+&a0&7)' + - '&7 Speed Malus Reduction: &a40% &7(+&a2%&7)' + - '' + - '&8&lWill' + - '&7 Max Rage: &c20 &7(+&c1&7)' + - '&7 Health Regen: &90.1 &7(+&90&7)' + - '&7 Rage Degeneration: &9-0.5&7/s' + item: IRON_SWORD:0 # Supports custom model data/texture by durability + +# Players cannot go further than Lvl 100 +max-level: 100 + +# Warrior has rage which he gains while casting spells +# Rage increase its skill damage. Use +# to get the player's rage (MythicMobs formulas) +mana: + char: ♦ + color: RED + name: 'Rage' + +# Rage charges when dealing weapon and physical damage. +triggers: + weapon-damage: + - 'mana{operation=GIVE;amount=2-3}' + physical-damage: + - 'mana{operation=GIVE;amount=2-3}' + + level-up: + - 'command{format="mmocore admin skill-points %player% 1"}' + +cast-particle: + particle: REDSTONE + color: + red: 255 + green: 0 + blue: 0 + +# Rage only decays when out of combat +options: + off-combat-mana-regen: true + +skills: + DEEP_WOUND: + level: 1 + max-level: 30 + WEAKEN: + level: 3 + max-level: 30 + EMPOWERED_ATTACK: + level: 5 + max-level: 30 + COMBO_ATTACK: + level: 7 + max-level: 30 + MINOR_HEALINGS: + level: 10 + max-level: 30 + FIRE_BERSERKER: + level: 15 + +attributes: + mana-regeneration: + base: -0.5 + per-level: 0 + speed-malus-reduction: + base: 50 + per-level: 2 + max-mana: + base: 20 + per-level: 1 + max-health: + base: 24 + per-level: 0.5 + movement-speed: + base: .19 + per-level: 0 + attack-damage: + base: 2 + per-level: 0.1 + attack-speed: + base: 4.2 + per-level: 0.05 + +# Experience sources for main class experience. +main-exp-sources: +- 'killmob{type=ZOMBIE;amount=1-3}' +- 'killmob{type=ENDERMAN;amount=6-9}' + diff --git a/default/drop-tables/example-drop-tables.yml b/default/drop-tables/example-drop-tables.yml new file mode 100644 index 00000000..25894aaf --- /dev/null +++ b/default/drop-tables/example-drop-tables.yml @@ -0,0 +1,16 @@ + +# You may create as many drop tables as you want. You can also +# make drop tables refer to other drop tables. +# +# DON'T TRY to create recursive drop tables (drop tables +# which call themselves to multiply items dropped). + +diamond-drop-table: + items: + - 'vanilla{type=DIAMOND} 1 1-3' + # - 'mmoitem{type=material;id=RARE_DIAMOND} .1 1-3' + - 'droptable{id=other-drop-table} .1' + +other-drop-table: + items: + - 'vanilla{type=STONE_SLAB} 1 1-3' \ No newline at end of file diff --git a/default/gui/attribute-view.yml b/default/gui/attribute-view.yml new file mode 100644 index 00000000..ff89a5bf --- /dev/null +++ b/default/gui/attribute-view.yml @@ -0,0 +1,71 @@ + +# GUI display name +name: Player Attributes + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 27 + +items: + reallocate: + slots: [26] + function: reallocation + item: CAULDRON + name: '&aReallocate Skill Points' + lore: + - '' + - 'You have spent a total of &6{total}&7 skill points.' + - '&7Right click to reallocate them.' + - '' + - '&eCosts 1 attribute reallocation point.' + - '&e◆ Attribute Reallocation Points: &6{points}' + str: + slots: [11] + function: attribute_strength + name: '&a{name}' + item: GOLDEN_APPLE + lore: # {buffs} returns amount of buffs + - '' + - '&7Points Spent: &6{spent}&7/&6{max}' + - '&7Current {name}: &6&l{current}' + - '' + - '&8When Leveled Up:' + - '&7 +{buff_weapon_damage}% Weapon Damage (&a+{total_weapon_damage}%&7)' + - '&7 +{buff_max_health} Max Health (&a+{total_max_health}&7)' + - '' + - '&eClick to level up for 1 attribute point.' + - '&e◆ Current Attribute Points: {attribute_points}' + dex: + slots: [13] + function: attribute_dexterity + name: '&a{name}' + item: LEATHER_BOOTS + hide-flags: true + lore: + - '' + - '&7Points Spent: &6{spent}&7/&6{max}' + - '&7Current {name}: &6&l{current}' + - '' + - '&8When Leveled Up:' + - '&7 +{buff_physical_damage}% Physical Damage (&a+{total_physical_damage}%&7)' + - '&7 +{buff_projectile_damage}% Projectile Damage (&a+{total_projectile_damage}%&7)' + - '&7 +{buff_attack_speed} Attack Speed (&a+{total_attack_speed}&7)' + - '' + - '&eClick to level up for 1 attribute point.' + - '&e◆ Current Attribute Points: {attribute_points}' + int: + slots: [15] + function: attribute_intelligence + name: '&a{name}' + item: BOOK + lore: + - '' + - '&7Points Spent: &6{spent}&7/&6{max}' + - '&7Current {name}: &6&l{current}' + - '' + - '&8When Leveled Up:' + - '&7 +{buff_magical_damage}% Magical Damage (&a+{total_magical_damage}%&7)' + - '&7 +{buff_cooldown_reduction}% Cooldown Reduction (&a+{total_cooldown_reduction}%&7)' + - '' + - '&eClick to level up for 1 attribute point.' + - '&e◆ Current Attribute Points: {attribute_points}' diff --git a/default/gui/class-confirm.yml b/default/gui/class-confirm.yml new file mode 100644 index 00000000..46aaab4b --- /dev/null +++ b/default/gui/class-confirm.yml @@ -0,0 +1,39 @@ + +# GUI display name +name: Class Confirmation + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 27 + +items: + yes: + slots: [12] + function: 'yes' + + # Displayed when the player had already selected this class + # before (only if class slots are enabled in the config). + unlocked: + item: GREEN_TERRACOTTA + name: '&aSelect {class}' + lore: + - '' + - '&7Class Level: &e{level}' + - '&7Progression: &e{exp} / {next_level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7Skill Points: &6{skill_points}' + - '&7Skills You Unlocked: &6{unlocked_skills}&7/&6{class_skills}' + + # Displayed when the class is being chosen for the first time. + locked: + item: GREEN_TERRACOTTA + name: '&aSelect {class}' + lore: {} + + back: + slots: [14] + item: RED_TERRACOTTA + function: back + name: '&aBack' + lore: {} diff --git a/default/gui/class-select.yml b/default/gui/class-select.yml new file mode 100644 index 00000000..0e6cad40 --- /dev/null +++ b/default/gui/class-select.yml @@ -0,0 +1,18 @@ + +# GUI display name +name: Class Selection + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 27 + +items: + class: + slots: [13,12,14,11,15,10,16] + function: class + name: '&a&lThe {name}' + hide-flags: true + lore: + - '{lore}' + - '' + - '{attribute-lore}' diff --git a/default/gui/friend-list.yml b/default/gui/friend-list.yml new file mode 100644 index 00000000..290a9170 --- /dev/null +++ b/default/gui/friend-list.yml @@ -0,0 +1,42 @@ + +# GUI display name +name: Friends + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 54 + +items: + friend: + slots: [10,11,12,13,14,15,16,19,20,21,22,23,24,25,28,29,30,31,32,33,34] + function: friend + + # No friend + item: GRAY_STAINED_GLASS_PANE + name: '&cNo Friend' + lore: {} + + # Online friend + online: + item: PLAYER_HEAD + name: '&a[Online] &f{name}' + lore: + - '&7Level {level} {class}' + - '&7Online Since: {online_since}' + - '' + - '&cRight click to remove.' + + # Offline friend + offline: + item: PLAYER_HEAD + name: '&c[Offline] &f{name}' + lore: + - '&7Last Seen: {last_seen} ago' + - '' + - '&cRight click to remove.' + request: + slots: [49] + function: request + item: WRITABLE_BOOK + name: '&aNew Friend Request' + lore: {} diff --git a/default/gui/friend-removal.yml b/default/gui/friend-removal.yml new file mode 100644 index 00000000..f5f03ca3 --- /dev/null +++ b/default/gui/friend-removal.yml @@ -0,0 +1,21 @@ + +# GUI display name +name: Friend Removal + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 27 + +items: + yes: + slots: [12] + item: RED_TERRACOTTA + function: 'yes' + name: '&aRemove {name}' + lore: {} + back: + slots: [14] + item: GREEN_TERRACOTTA + function: 'back' + name: '&aKeep {name}' + lore: {} diff --git a/default/gui/party-creation.yml b/default/gui/party-creation.yml new file mode 100644 index 00000000..8e9e10bf --- /dev/null +++ b/default/gui/party-creation.yml @@ -0,0 +1,21 @@ + +# GUI display name +name: Party Creation + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 27 + +items: + create: + slots: [12] + item: GREEN_TERRACOTTA + function: 'create' + name: '&aCreate a party!' + lore: {} + back: + slots: [14] + item: RED_TERRACOTTA + function: back + name: '&aBack' + lore: {} diff --git a/default/gui/party-view.yml b/default/gui/party-view.yml new file mode 100644 index 00000000..dce5e6c3 --- /dev/null +++ b/default/gui/party-view.yml @@ -0,0 +1,33 @@ + +# GUI display name +name: Party ({players}/{max}) + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 54 + +items: + member: + slots: [10,12,14,16,28,30,32,34] + function: member + empty: + item: GRAY_STAINED_GLASS_PANE + name: '&aNo Player' + member: + item: PLAYER_HEAD + name: '&a{name}' + lore: + - '&7Level {level} {class}' + - '&7Online Since: {since}' + leave: + slots: [50] + function: leave + item: REDSTONE + name: '&cLeave Party' + lore: {} + request: + slots: [48] + function: invite + item: WRITABLE_BOOK + name: '&aInvite a player..' + lore: {} \ No newline at end of file diff --git a/default/gui/player-stats.yml b/default/gui/player-stats.yml new file mode 100644 index 00000000..90016848 --- /dev/null +++ b/default/gui/player-stats.yml @@ -0,0 +1,236 @@ + +# GUI display name +name: Your Character + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 54 + +items: + mining-profession: + slots: [10] + function: profession_mining + item: IRON_PICKAXE + name: '&aMining' + hide-flags: true + lore: + - '' + - '&7Current Level: &e{level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7&oMining unlocks rare ores and raw materials.' + - '&7&oThis is vital to your rise in power and strength,' + - '&7&omine frequently for unique and rare drops.' + woodcutting-profession: + slots: [11] + function: profession_woodcutting + item: IRON_AXE + name: '&aWoodcutting' + hide-flags: true + lore: + - '' + - '&7Current Level: &e{level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7&oThough it may seem like a boring task, woodcutting' + - '&7&ois vital to obtaining materials used for crafting and trade,' + - '&7&oand will help give you the upper hand in the arcane ways.' + farming-profession: + slots: [12] + function: profession_farming + item: IRON_HOE + name: '&aFarming' + hide-flags: true + lore: + - '' + - '&7Current Level: &e{level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7&oWith tons of new food and consumable recipes,' + - '&7&oyou will need to stay on top of the crops in order' + - '&7&oto obtain the best food and drinks to keep yourself healthy.' + fishing-profession: + slots: [19] + function: profession_fishing + item: FISHING_ROD + name: '&aFishing' + hide-flags: true + lore: + - '' + - '&7Current Level: &e{level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7&oFishing may give you unique drops you' + - '&7&ocan''t find anywhere else. The more you' + - '&7&ofish, the easier it becomes to find these.' + - '' + - '&7Fishing Strength: &c{fishing_strength}%' + - '&7Crit Fishing Rate: &c{critical_fishing_chance}%' + - '&7Crit Failure Rate: &c{critical_fishing_failure_chance}%' + alchemy-profession: + slots: [20] + function: profession_alchemy + item: BREWING_STAND + name: '&aAlchemy' + lore: + - '' + - '&7Current Level: &e{level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7&oIn a world where you are no longer limited to' + - '&7&osimple potions, try learning tons of new brewing' + - '&7&orecipes to give yourself the edge on the battlefield.' + smithing-profession: + slots: [21] + function: profession_smithing + item: ANVIL + name: '&aSmithing' + lore: + - '' + - '&7Current Level: &e{level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7&oStabbing enemies and having them laugh is the worst,' + - '&7&oPractice makes perfect when it comes to smithing.' + - '&7&o&nWar is won by the man with the pointiest stick.' + enchanting-profession: + slots: [28] + function: profession_enchanting + item: ENCHANTED_BOOK + name: '&aEnchanting' + lore: + - '' + - '&7Current Level: &e{level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7&oLorem ipsum dolor sit amet, consectetur' + - '&7&oadipiscing elit. Proin malesuada maximus massa,' + - '&7&osodales imperdiet sapien fermentum at.' + smelting-profession: + slots: [29] + function: profession_smelting + item: FURNACE + name: '&aSmelting' + lore: + - '' + - '&7Current Level: &e{level}' + - '&8[&e{progress}&8] &e{percent}%' + - '' + - '&7&oSinging your eyebrows will become standard.' + - '&7&oYour long hours over the heat will make you' + - '&7&ofaster and more efficient with your oven.' + boost-display: + slots: [47,48,49,50,51] + function: boost + item: BARRIER + no-boost: + item: GRAY_STAINED_GLASS_PANE + name: '&aNo Booster' + lore: {} + + # Profession experience boosters + profession: + item: EXPERIENCE_BOTTLE + name: '&aEXP Boost' + lore: + - '&7Amount: &6+{value}%' + - '&7Time left: &6{left}' + - '&7Profession: &6{profession}' + - '&7' + - '&eStarted by {author}' + + # Main class experience boosters + main-level: + item: EXPERIENCE_BOTTLE + name: '&aEXP Boost' + lore: + - '&7Amount: &6+{value}%' + - '&7Time left: &6{left}' + - '&7' + - '&eStarted by {author}' + boost-next: + slots: [52] + function: boost-next + item: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTliZjMyOTJlMTI2YTEwNWI1NGViYTcxM2FhMWIxNTJkNTQxYTFkODkzODgyOWM1NjM2NGQxNzhlZDIyYmYifX19 + name: '&aNext' + lore: {} + boost-prev: + slots: [46] + function: boost-previous + item: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmQ2OWUwNmU1ZGFkZmQ4NGU1ZjNkMWMyMTA2M2YyNTUzYjJmYTk0NWVlMWQ0ZDcxNTJmZGM1NDI1YmMxMmE5In19fQ== + name: '&aPrevious' + lore: {} + party: + slots: [16] + function: party + item: CAKE + name: '&aParty Morale' + lore: + - '&7&oPlaying with your friends' + - '&7&ogreatly encourages you!' + - '' + - '&7Party Bonuses ({count}):' + - '&8- +{buff_additional_experience} Experience Earned!' + - '&8- +{buff_health_regeneration} Health Regeneration' + stats: + slots: [15] + function: profile + item: PLAYER_HEAD + name: '&e{player}' + lore: + - '' + - '&7Current Level: &e{level}' + - '&7Progression: &e{exp} / {next_level}' + - '&8[&e{progress}&8] &e{percent}%' + - '&7Skill Points: &6{skill_points}' + - '' + - '&7Current Class: &c{class}' + - '&7Class Points: &c{class_points}' + phys: + slots: [32] + function: stats + name: '&cPhysical' + item: GOLDEN_APPLE + hide-flags: true + lore: + - '' + - 'Current Strength: &c&l{attribute_strength}' + - '' + - '✦ Attack Damage: &c{attack_damage} &7(&c{attack_damage_base}&7+&c{attack_damage_extra}&7)' + - '✦ Attack Speed: &c{attack_speed} &7(&c{attack_speed_base}&7+&c{attack_speed_extra}&7)' + - '' + - '❤ Max Health: &c{max_health} &7(&c{max_health_base}&7+&c{max_health_extra}&7)' + - '❤ Health Regen: &c{attack_speed} &7(&c{attack_speed_base}&7+&c{attack_speed_extra}&7)' + - '' + - '❖ Armor: &c{armor} &7(&c{armor_base}&7+&c{armor_extra}&7)' + - '❖ Armor Toughness: &c{armor_toughness} &7(&c{armor_toughness_base}&7+&c{armor_toughness_extra}&7)' + dex: + slots: [33] + function: stats + name: '&aDexterity' + item: LEATHER_BOOTS + hide-flags: true + lore: + - '' + - 'Current Dexterity: &a&l{attribute_dexterity}' + - '' + - '✤ Knockback Resistance: &f{knockback_resistance} &7(&f{knockback_resistance_base}&7+&f{knockback_resistance_extra}&7)' + - '✤ Movement Speed: &f{movement_speed} &7(&f{movement_speed_base}&7+&f{movement_speed_extra}&7)' + - '✤ Speed Malus Reduction: &f{speed_malus_reduction} &7(&f{speed_malus_reduction_base}&7+&f{speed_malus_reduction_extra}&7)' + int: + slots: [34] + function: stats + name: '&bIntellect' + item: BOOK + hide-flags: true + lore: + - '' + - 'Current Intelligence: &b&l{attribute_intelligence}' + - '' + - '✤ Max Mana: &c{max_mana} &7(&c{max_mana_base}&7+&c{max_mana_extra}&7)' + - '✤ Mana Regen: &c{mana_regeneration} &7(&c{mana_regeneration_base}&7+&c{mana_regeneration_extra}&7)' + - '' + - '❊ Max Stellium: &c{max_stellium} &7(&c{max_stellium_base}&7+&c{max_stellium_extra}&7)' + - '❊ Stellium Regen: &c{stellium_regeneration} &7(&c{stellium_regeneration_base}&7+&c{stellium_regeneration_extra}&7)' diff --git a/default/gui/quest-list.yml b/default/gui/quest-list.yml new file mode 100644 index 00000000..1834a2cb --- /dev/null +++ b/default/gui/quest-list.yml @@ -0,0 +1,67 @@ + +# GUI display name +name: Quests + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 45 + +items: + skill: + slots: [10,11,12,13,14,15,16,19,20,21,22,23,24,25,28,29,30,31,32,33,34] + function: quest + + # When quest is locked + locked: + item: PAPER + name: '&c- Unavailable -' + + # When there is no quest to display + no-quest: + item: GRAY_STAINED_GLASS_PANE + name: '&a' + + level-requirement: + main: + hit: '&a ✔ Requires Level {level}' + not-hit: '&c ✖ Requires Level {level}' + profession: + hit: '&a ✔ Requires {profession} Level {level}' + not-hit: '&c ✖ Requires {profession} Level {level}' + + # Date format used in the {date} placeholder + date-format: 'MMM d yyyy' + + item: BOOK + name: '&a{name}' + lore: + - '{lore}' + - '' + - '{started}&8Quest Started!' + - '{started}&7► Progression: &e{progress}%' + - '{started}&7► &o{objective}' + - '{started}' + - '{level_req}&7Level Requirements ({current_level_req}/{total_level_req}):' + - '{level_req}{level_requirements}' + - '{level_req}' + - '{completed}&8You''ve completed this quest on the {date}.' + - '{completed_cannot_redo}&8You can''t do this quest twice.' + - '{completed_delay}&8You can start the quest in {delay}.' + - '{completed_can_redo}&8You can start this quest.' + - '{completed}' + - '{started}&c► Right click to cancel.' + - '{!started}&e► Left click to start.' + next: + slots: [26] + function: next + item: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTliZjMyOTJlMTI2YTEwNWI1NGViYTcxM2FhMWIxNTJkNTQxYTFkODkzODgyOWM1NjM2NGQxNzhlZDIyYmYifX19 + name: '&aNext' + lore: {} + previous: + slots: [18] + function: previous + item: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmQ2OWUwNmU1ZGFkZmQ4NGU1ZjNkMWMyMTA2M2YyNTUzYjJmYTk0NWVlMWQ0ZDcxNTJmZGM1NDI1YmMxMmE5In19fQ== + name: '&aPrevious' + lore: {} diff --git a/default/gui/skill-list.yml b/default/gui/skill-list.yml new file mode 100644 index 00000000..60993b92 --- /dev/null +++ b/default/gui/skill-list.yml @@ -0,0 +1,97 @@ + +# GUI display name +name: Your Skills + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 45 + +items: + skill: + slots: [11,12,13,14,15] + + # Index from 'slots' of the skill + # currently selected in the GUI + selected-slot: 2 + + function: skill + name: '&a{skill} &6[{level}]' + lore: + - '' + - '{unlocked}&a✔ Requires Level {unlock}' + - '{locked}&c✖ Requires Level {unlock}' + - '{max_level}&e✔ Maximum Level Hit!' + - '' + - '{lore}' + next: + slots: [16] + function: next + item: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTliZjMyOTJlMTI2YTEwNWI1NGViYTcxM2FhMWIxNTJkNTQxYTFkODkzODgyOWM1NjM2NGQxNzhlZDIyYmYifX19 + name: '&aNext' + lore: {} + previous: + slots: [10] + function: previous + item: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmQ2OWUwNmU1ZGFkZmQ4NGU1ZjNkMWMyMTA2M2YyNTUzYjJmYTk0NWVlMWQ0ZDcxNTJmZGM1NDI1YmMxMmE5In19fQ== + name: '&aPrevious' + lore: {} + switch: + slots: [28] + function: switch + item: PLAYER_HEAD + binding: + item: PINK_STAINED_GLASS + name: '&aSwitch to Binding' + lore: {} + upgrading: + item: PINK_STAINED_GLASS + name: '&aSwitch to Upgrading' + lore: {} + skill-slot: + slots: [29,30,31,32,33,34] + function: slot + item: BOOK + + # Material used when the slot is empty + empty-item: GRAY_DYE + + name: '&aSkill Slot {slot}' + no-skill: '&cNone' + lore: + - '&7Current Skill: &6{skill}' + - '' + - '&7&oCast this spell by pressing [F] followed' + - '&7&oby the keybind displayed on the action bar.' + - '' + - '&e► Left click to bind {selected}.' + - '&e► Right click to unbind.' + skill-level: + slots: [29,30,31,32,33,34] + function: level + + # Skill level offset, should be changed + # according to the amount of inventory + # slots the skill-level item occupies. + offset: 2 + + # Item displayed if the skill level is + # too low to display a level item in the GUI + too-low: + item: AIR + + item: LIME_DYE + name: '&a{skill} Level {roman}' + lore: + - '' + - '{lore}' + upgrade: + slots: [31] + function: upgrade + item: GREEN_STAINED_GLASS_PANE + name: '&a&lUPGRADE {skill_caps}' + lore: + - '&7Costs 1 skill point.' + - '' + - '&eCurrent Skill Points: {skill_points}' diff --git a/default/gui/subclass-confirm.yml b/default/gui/subclass-confirm.yml new file mode 100644 index 00000000..772f7f1c --- /dev/null +++ b/default/gui/subclass-confirm.yml @@ -0,0 +1,21 @@ + +# GUI display name +name: Subclass Confirmation + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 27 + +items: + yes: + slots: [12] + item: GREEN_TERRACOTTA + function: 'yes' + name: '&aSelect {class}' + lore: {} + back: + slots: [14] + item: RED_TERRACOTTA + function: back + name: '&aBack' + lore: {} diff --git a/default/gui/subclass-select.yml b/default/gui/subclass-select.yml new file mode 100644 index 00000000..e86d3c5c --- /dev/null +++ b/default/gui/subclass-select.yml @@ -0,0 +1,23 @@ + +# GUI display name +name: Choose your path... + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 27 + +items: + back: + function: back + slots: [26] + item: RED_STAINED_GLASS_PANE + name: '&aBack to Class Selection' + lore: [] + class: + slots: [13,12,14,11,15,10,16] + function: class + name: '&a&lThe {name}' + lore: + - '{lore}' + - '' + - '{attribute-lore}' diff --git a/default/gui/waypoints.yml b/default/gui/waypoints.yml new file mode 100644 index 00000000..6d5a2c58 --- /dev/null +++ b/default/gui/waypoints.yml @@ -0,0 +1,51 @@ + +# GUI display name +name: Waypoints + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 45 + +items: + waypoint: + slots: [10,11,12,13,14,15,16,19,20,21,22,23,24,25,28,29,30,31,32,33,34] + function: waypoint + + # Displayed when there is no waypoint + no-waypoint: + item: GRAY_STAINED_GLASS_PANE + name: '&a' + lore: {} + + # Displayed when the waypoint has not been unlocked yet. + locked: + name: '&c- Locked -' + item: GRAY_DYE + lore: {} + + # Displayed when the waypoint is unlocked. + display: + name: '&a{name}' + item: ENDER_EYE + + # Material displayed when the waypoint is not + # ready (not dynamic, or not enough stellium) + not-ready: ENDER_PEARL + + lore: + - '&7You have unlocked this waypoint.' + - '&7Click to teleport for &b{stellium} &7stellium.' + next: + slots: [26] + function: next + item: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTliZjMyOTJlMTI2YTEwNWI1NGViYTcxM2FhMWIxNTJkNTQxYTFkODkzODgyOWM1NjM2NGQxNzhlZDIyYmYifX19 + name: '&aNext Page' + lore: [] + previous: + slots: [18] + function: previous + item: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmQ2OWUwNmU1ZGFkZmQ4NGU1ZjNkMWMyMTA2M2YyNTUzYjJmYTk0NWVlMWQ0ZDcxNTJmZGM1NDI1YmMxMmE5In19fQ== + name: '&aPrevious Page' + lore: [] \ No newline at end of file diff --git a/default/items.yml b/default/items.yml new file mode 100644 index 00000000..1c98c533 --- /dev/null +++ b/default/items.yml @@ -0,0 +1,29 @@ + +GOLD_COIN: + item: GOLD_NUGGET + name: '&6Gold Coin' + lore: + - '&eWorth: 1g' + +NOTE: + item: PAPER + name: '&6Note' + lore: + - '&eWorth: {worth}g' + +DEPOSIT_ITEM: + item: BOOK + name: '&eDeposit {worth}g' + lore: {} + +GOLD_POUCH: + item: LEATHER + name: '&fGold Pouch' + lore: + - '&7Right-Click to open.' + +MOB_GOLD_POUCH: + item: LEATHER + name: '&fGold Pouch' + lore: + - '&7Right-Click to open.' diff --git a/default/levels.txt b/default/levels.txt new file mode 100644 index 00000000..fa40a4b4 --- /dev/null +++ b/default/levels.txt @@ -0,0 +1,10 @@ +200 +400 +600 +800 +1000 +1200 +1400 +1600 +1800 +2000 \ No newline at end of file diff --git a/default/messages.yml b/default/messages.yml new file mode 100644 index 00000000..87f0773d --- /dev/null +++ b/default/messages.yml @@ -0,0 +1,129 @@ + +# Level & Experience +level-up: +- '' +- '&eCongratulations, you reached level &6{level}&e!' +- '&eUse &6/p &eto see your new statistics!' +- '' +profession-level-up: +- '&eYou are now level &6{level}&e in &6{profession}&e!' +exp-notification: '&f{profession} &e{progress} &e{ratio}%' +exp-hologram: '&e+{exp} EXP!' +class-select: '&eYou are now a &6{class}&e!' +already-on-class: '&cYou are already a {class}.' + +# General +booster-main: +- '&e' +- '&eA &6{multiplier}x&e EXP multiplier is now active!' +- '&e' +booster-skill: +- '&e' +- '&eA &6{multiplier}x&e &6{profession} &eEXP multiplier is now active!' +- '&e' + +# Fishing Profession +caught-fish: '&cYou caught a fish!' +fish-out-water: '&aWell done!' +fish-out-water-crit: '&aCritical Fish!' + +# Player Input +player-input: + anvil: + friend-request: 'Friend name..' + party-invite: 'Player name..' + chat: + friend-request: '&eWrite in the chat the player name.' + party-invite: '&eWrite in the chat the player you want to invite.' + +# Spell Casting +casting: + action-bar: '&6[{index}] &a&l{skill}' + split: '&7 &7 - &7 ' + no-longer: '&cYou cancelled skill casting.' + no-mana: '&cYou do not have enough mana!' + on-cooldown: '&cThis skill is on cooldown.' + +# Combat Log +now-in-combat: '&cYou are now in combat!' +leave-combat: '&aYou left combat.' + +# Waypoints +new-waypoint: '&eYou unlocked the &6{waypoint} &ewaypoint!' +not-enough-stellium: '&cYou don''t have enough stellium: you need {more} more.' +waypoint-cooldown: '&cPlease wait {cooldown} before using a waypoint again.' +not-unlocked-waypoint: '&cYou have not unlocked that waypoint yet.' +not-dynamic-waypoint: '&cYou many only teleport to a non-dynamic waypoint while standing on another waypoint.' +standing-on-waypoint: '&cYou are already standing on this waypoint.' +warping-canceled: '&cWaypoint warping canceled.' +warping-comencing: '&cDO NOT MOVE!&e You will be warped in {left}sec.' + +# Cash +deposit: '&eYou successfully deposited &6{worth}g&e.' +withdrawing: '&eType in the chat the amount of &6gold&e you want to &6withdraw&e.' +withdraw-cancel: '&eWithdrawing canceled.' +withdrew: '&eYou successfully withdrew &6{worth}g&e.' +wrong-number: '&c{arg} is not a valid number.' +not-enough-money: '&cYou don''t have enough money, you need {left} more gold.' +stand-near-enderchest: '&cYou must be standing near a bank to do that.' + +# Blocks +cannot-break: '&cYou do not have the right tool in order to break that block.' + +# Friends +no-longer-friends: '&cYou and {unfriend} are no longer friends.' +not-online-player: '&c{player} is not online.' +sent-friend-request: '&eYou sent a friend request to &6{player}&e.' +now-friends: '&eYou are now friends with &6{player}&e.' +friend-request-cooldown: '&cPlease wait {cooldown}.' +cant-request-to-yourself: '&cYou can''t send a request to yourself.' +already-friends: '&cYou are already friends with {player}.' +friend-request: +- '{"text":""}' +- '{"text":"&6{player} &ejust sent you a friend request!"}' +- '[{"text":" ","hoverEvent":{}},{"text":"&8[&a&lACCEPT&8]","clickEvent":{"action":"run_command","value":"/friends accept {uuid}"},"hoverEvent":{"action":"show_text","value":"&eClick to accept!"}},{"text":"&r ","hoverEvent":{}},{"text":"&8[&c&lDENY&8]","clickEvent":{"action":"run_command","value":"/friends deny {uuid}"},"hoverEvent":{"action":"show_text","value":"&eClick to deny."}}]' +- '{"text":""}' + +# Parties +party-chat: '&5[Party] {player}: {message}' +sent-party-invite: '&eYou sent a party invite to &6{player}&e.' +already-in-party: '&c{player} is already in your party.' +party-invite: +- '{"text":""}' +- '{"text":"&6{player} &ehas invited you to their party!"}' +- '[{"text":" ","hoverEvent":{}},{"text":"&8[&a&lACCEPT&8]","clickEvent":{"action":"run_command","value":"/party accept {uuid}"},"hoverEvent":{"action":"show_text","value":"&eClick to accept!"}},{"text":"&r ","hoverEvent":{}},{"text":"&8[&c&lDENY&8]","clickEvent":{"action":"run_command","value":"/party deny {uuid}"},"hoverEvent":{"action":"show_text","value":"&eClick to deny."}}]' +- '{"text":""}' +party-is-full: '&cSorry, your party is full.' +party-joined: '&eYou successfully joined &6{owner}&e''s party.' +party-joined-other: '&6{player}&e joined your party!' +transfer-party-ownership: '&eYou were transfered the party ownership.' +kick-from-party: '&eYou successfully kicked &6{player}&e.' +party-invite-cooldown: '&cPlease wait {cooldown} before inviting {player}.' + +# Quests +already-on-quest: '&cYou are already on a quest.' +cancel-quest: '&eYou successfully canceled your ongoing quest.' +quest-level-restriction: '&cYou need to be {level} {count}.' +cant-redo-quest: '&cYou can''t start this quest twice.' +quest-cooldown: '&cYou need to wait {delay}.' +start-quest: '&eYou successfully started &6{quest}&e.' + +cant-choose-new-class: +- '&cYou need one class point to perform this action.' + +# Attributes +no-attribute-points-spent: '&cYou have not spent any attribute points.' +not-attribute-reallocation-point: '&cYou do not have 1 reallocation point.' +not-attribute-point: '&cYou do not have 1 attribute point.' +attribute-points-reallocated: '&eYou successfully reset your attributes. You now have &6{points} &eattribute points.' +attribute-max-points-hit: '&cYou cannot level up this attribute anymore.' +attribute-level-up: '&eYou successfully leveled up your &6{attribute}&e.' # {level} + +# Skills +no-class-skill: '&cYour class has no skill.' +not-enough-skill-points: '&cYou need one skill point.' +upgrade-skill: '&eYour &6{skill} &eis now Level &6{level}&e!' +not-unlocked-skill: '&cYou have not unlocked that skill yet.' +no-skill-bound: '&cYou don''t have any skill bound to this slot.' +not-active-skill: '&cThis is not an active skill.' +skill-max-level-hit: '&cYou already hit the max level for that skill.' diff --git a/default/professions/alchemy.yml b/default/professions/alchemy.yml new file mode 100644 index 00000000..cfc5fa93 --- /dev/null +++ b/default/professions/alchemy.yml @@ -0,0 +1,61 @@ + +# Display options +name: Alchemy + +# Experience given to the main level +# when leveling up this profession +experience: + base: 20 + per-level: 3 + +exp-sources: +- 'brewpotion{effect=SPEED}' + +# When brewing a potion, players earn Alchemy experience. +# Experience earned depends on the effect you give to your +# potion, if you upgrade/extend its duration, etc. +alchemy-experience: + + special: + + # When brewing a potion into a splash potion, + # only 40% of the base EXP is earned. + splash: 40 + + # When brewing a potion into a splash potion, + # only 40% of the base EXP is earned. + lingering: 40 + + # When extending a pot duration, + # only 40% of base EXP is earned. + extend: 40 + + # When upgrading a potion level, + # only 40% of base EXP is earned. + upgrade: 40 + + # Base EXP of potions + effects: + + # Water bottles + AWKWARD: 5 + MUNDANE: 5 + THICK: 5 + + # Potion effects + NIGHT_VISION: 10 + INVISIBILITY: 10 + JUMP: 10 + FIRE_RESISTANCE: 10 + SPEED: 10 + SLOWNESS: 10 + WATER_BREATHING: 10 + INSTANT_HEAL: 10 + INSTANT_DAMAGE: 10 + POISON: 10 + REGEN: 10 + STRENGTH: 10 + WEAKNESS: 10 + LUCK: 10 + TURTLE_MASTER: 10 + SLOW_FALLING: 10 diff --git a/default/professions/enchanting.yml b/default/professions/enchanting.yml new file mode 100644 index 00000000..5edf61d8 --- /dev/null +++ b/default/professions/enchanting.yml @@ -0,0 +1,63 @@ + +# Display options +name: Enchanting + +# Experience given to the main level +# when leveling up this profession +experience: + base: 10 + per-level: 2 + +# Remove the 'enchant' parameter from the line config +# to make the profession get EXP with ANY enchant. + +# Specifying enchants like that lets you create different +# enchanting professions, like DESTRUCTION for offense enchants +# (Smite, Bane of Arth., Sharpness, Power, Fire Aspect, Flame) +# and ALTERATION for strategic enchants for e.g + +exp-sources: +- 'enchantitem{enchant=sharpness}' + +# When enchanting an item, players earn Enchanting experience. +# Experience given by an item is the sum of the EXP given by every extra enchantment. +# Enchantment experience depends on the enchant itself + the enchant level. +# Enchant exp = * +base-enchant-exp: + fire_protection: 10 + sharpness: 10 + flame: 10 + aqua_affinity: 10 + punch: 10 + loyalty: 10 + depth_strider: 10 + vanishing_curse: 10 + unbreaking: 10 + knockback: 10 + luck_of_the_sea: 10 + binding_curse: 10 + fortune: 10 + protection: 10 + efficiency: 10 + mending: 10 + frost_walker: 10 + lure: 10 + looting: 10 + piercing: 10 + blast_protection: 10 + smite: 10 + multishot: 10 + fire_aspect: 10 + channeling: 10 + sweeping: 10 + thorns: 10 + bane_of_arthropods: 10 + respiration: 10 + riptide: 10 + silk_touch: 10 + quick_charge: 10 + projectile_protection: 10 + impaling: 10 + feather_falling: 10 + power: 10 + infinity: 10 diff --git a/default/professions/farming.yml b/default/professions/farming.yml new file mode 100644 index 00000000..3ec12fd6 --- /dev/null +++ b/default/professions/farming.yml @@ -0,0 +1,14 @@ + +# Display options +name: Farming + +# Experience given to the main level +# when leveling up this profession +experience: + base: 10 + per-level: 2 + +exp-sources: +- 'mineblock{type=CARROTS;amount=1-3}' +- 'mineblock{type=POTATOES;amount=1-3}' +- 'mineblock{type=WHEAT;amount=1-3}' diff --git a/default/professions/fishing.yml b/default/professions/fishing.yml new file mode 100644 index 00000000..6c271a4a --- /dev/null +++ b/default/professions/fishing.yml @@ -0,0 +1,41 @@ + +# Display options +name: Fishing + +# Experience given to the main level +# when leveling up this profession +experience: + base: 20 + per-level: 3 + +exp-sources: {} + +# Fishing drop tables which override MC default. +# When fishing, the plugin reads through all the drop tables +# and picks THE first one which all conditions are met!! +# You must put at first place the drop tables which +# have the most conditions/which are the most important. +# Number of tugs = number of times you need to click to fish. +on-fish: + overriding-drop-table: + conditions: + - 'region{name=swamp}' + + # When drop table is read, one of these + # items will be selected randomly. + items: + + # Tugs needed: 4 to 5 + # Fishing EXP earned: 1 to 6 + - 'mmoitem{type=CONSUMABLE;id=SUSHI_ROLL;tugs=30-40;experience=1-6}' + + # Tugs needed: 10 to 20 + # Fishing EXP earned: 20 to 30 + - 'mmoitem{type=GEM_STONE;id=SPITEFUL_OPAQUE_DIAMOND;tugs=10-15;experience=20-30}' + + # Default drop table which always apply. + # When removing every drop table, the vanilla + # fishing mecanism is back. + default: + items: + - 'vanilla{type=SALMON;tugs=4-5;experience=1-6}' diff --git a/default/professions/mining.yml b/default/professions/mining.yml new file mode 100644 index 00000000..f5314d64 --- /dev/null +++ b/default/professions/mining.yml @@ -0,0 +1,53 @@ + +# Display options +name: Mining + +# Experience given to the main level +# when leveling up this profession +experience: + base: 20 + per-level: 3 + +on-mine: + EMERALD_ORE: + drop-table: + items: + - 'vanilla{type=EMERALD} 1 1-9' + vanilla-drops: false + regen: + time: 2000 + temp-block: STONE + triggers: + - 'exp{profession=mining;amount=32}' + + DIAMOND_ORE: + + # Refer to drop-tables.yml + # The drop table used by the block. + drop-table: + items: + - 'vanilla{type=DIAMOND} 1 1-3' + # - 'mmoitem{type=material;id=RARE_DIAMOND} .1 1-3' + # - 'droptable{id=other-drop-table} .1' + + # Triggers when destroying the block, can + # be used to give exp to a player! + triggers: + - 'exp{profession=mining;amount=20}' + + # Set to false if you want to disable vanila drops. + vanilla-drops: false + + regen: + + # Ticks the block takes to appear again + time: 2000 + + # The temporary block which shows + # during the block regen time. + # + # !! Warning !! + # When using the temp-block option, make sure you choose + # one temp block and don't use it anywhere else in the + # configuration so that block regens do not affect each other + temp-block: STONE \ No newline at end of file diff --git a/default/professions/smelting.yml b/default/professions/smelting.yml new file mode 100644 index 00000000..348a0ebf --- /dev/null +++ b/default/professions/smelting.yml @@ -0,0 +1,9 @@ + +# Display options +name: Smelting + +# Experience given to the main level +# when leveling up this profession +experience: + base: 20 + per-level: 3 \ No newline at end of file diff --git a/default/professions/smithing.yml b/default/professions/smithing.yml new file mode 100644 index 00000000..8736903c --- /dev/null +++ b/default/professions/smithing.yml @@ -0,0 +1,36 @@ + +# Display options +name: Smithing + +# Experience given to the main level +# when leveling up this profession +experience: + base: 20 + per-level: 3 + +exp-sources: +- 'repairitem{}' + +# Experience given by repairing 100 +# durability points from a specific material. + +# Warning, diamonds/iron ingots/ do +# not repair the same amount of durability! +repair-exp: + + # Swords + DIAMOND_SWORD: 1.923 # Max durability: 1561 + GOLDEN_SWORD: 62.5 # Max durability: 32 + IRON_SWORD: 8 # Md: 250 + STONE_SWORD: 7.634 # Md: 131 + WOODEN_SWORD: 13.56 # Md: 59 + + # Picks + DIAMOND_PICKAXE: 1.923 + GOLDEN_PICKAXE: 62.5 + IRON_PICKAXE: 8 + STONE_PICKAXE: 7.634 + WOODEN_PICKAXE: 13.56 + + # Add as many as you want: bows, shields.. \ No newline at end of file diff --git a/default/professions/woodcutting.yml b/default/professions/woodcutting.yml new file mode 100644 index 00000000..11d592ef --- /dev/null +++ b/default/professions/woodcutting.yml @@ -0,0 +1,18 @@ + +# Display options +name: Woodcutting + +# Experience given to the main level +# when leveling up this profession +experience: + base: 13 + per-level: 2.5 + +exp-sources: +- 'mineblock{type=OAK_LOG;amount=1-3}' +- 'mineblock{type=SPRUCE_LOG;amount=1-3}' +- 'mineblock{type=BIRCH_LOG;amount=1-3}' +- 'mineblock{type=JUNGLE_LOG;amount=1-3}' +- 'mineblock{type=ACACIA_LOG;amount=1-3}' +- 'mineblock{type=BIRCH_LOG;amount=1-3}' +- 'mineblock{type=DARK_OAK_LOG;amount=1-3}' diff --git a/default/quests/adv-begins.yml b/default/quests/adv-begins.yml new file mode 100644 index 00000000..724db33d --- /dev/null +++ b/default/quests/adv-begins.yml @@ -0,0 +1,30 @@ + +# Display options +name: 'The Beginning of an Adventure' +lore: +- 'Yet another quest example.' +- '' +- '&eRewards:' +- '&7► Stone Tools' +- '&7► 30 EXP' + +# Parent quests +parent: +- 'tutorial' + +# Quest objectives +objectives: + 1: + type: 'goto{world="world";x=120;y=46;z=652;range=10}' + lore: 'Head to the camp.' + triggers: + - 'message{format="Good job!"}' + 2: + type: 'talkto{npc=0}' + lore: 'Go talk to the Blacksmith.' + triggers: + - 'message{format="The blacksmith told you to come see me... ?"}' + - 'item{type=STONE_SWORD}' + - 'item{type=STONE_AXE}' + - 'item{type=STONE_PICKAXE}' + - 'experience{amount=30}' diff --git a/default/quests/fetch-mango.yml b/default/quests/fetch-mango.yml new file mode 100644 index 00000000..365379df --- /dev/null +++ b/default/quests/fetch-mango.yml @@ -0,0 +1,39 @@ + +# Display options +name: 'The Exotic Fruit' + +# Quest lore displayed in the quest menu. +lore: +- 'Gimme mangoes please.' +- '' +- '&eRewards:' +- '&7► 10 Gold coins' +- '&7► 30 EXP' + +# Quests the player must finish +# in order to unlock this one. +parent: [] + +# Cooldown in hours. Don't put any +# to make the quest a one-time quest. +# Put it to 0 to make it instantly redoable. +delay: 0 + +# Objectives the player needs to +# complete. Once they're all complete, +# the quest will end. +objectives: + 1: + type: 'mine{type=OAK_LOG;amount=8}' + lore: 'Mangoes only grow in Oak trees. Go break 8 oak logs!' + triggers: + - message{format="&aThank you! Those mangoes are gonna drop from these leaves."} + - sound{sound=ENTITY_EXPERIENCE_ORB_PICKUP;pitch=1;volume=1} + 2: + type: 'getitem{type=APPLE;npc=1;amount=2}' + lore: 'Bring me mangoes.' + triggers: + - message{format="&aThis looked like apple. But owell, thanks! Here are a a few coins'"} + - sound{sound=ENTITY_EXPERIENCE_ORB_PICKUP;pitch=1;volume=1} + - command{format="mmocore coins %player% 10"} + - 'experience{amount=30}' diff --git a/default/quests/tutorial.yml b/default/quests/tutorial.yml new file mode 100644 index 00000000..cbaa256e --- /dev/null +++ b/default/quests/tutorial.yml @@ -0,0 +1,69 @@ + +# Levels players must have in +# order to unlock this quest. +level-req: + main: 10 + mining: 5 + +# Quest name displayed in the quest menu. +name: 'A Whole New World' + +# Quest lore displayed in the quest menu. +lore: +- 'This is the tutorial quest.' +- 'Lore example...' +- '' +- '&eRewards:' +- '&7► Wooden Tools' +- '&7► Leather Armor' +- '&7► 100 EXP' + +# Quests the player must finish +# in order to unlock this one. +parent: [] + +# Cooldown in hours. Don't put any +# to make the quest a one-time quest. +# Put it to 0 to make it instantly redoable. +delay: 12 + +# Objectives the player needs to +# complete. Once they're all complete, +# the quest will end. +objectives: + 1: + type: 'goto{world="world";x=56;y=68;z=115;range=5}' + lore: 'Head to the camp.' + triggers: + - 'message{format="&aBlacksmith> &fHello, %player%! I am currently looking for some help."}' + - 'message{format="&aBlacksmith> &fCould you please get me 3 oak logs?"}' + - 'sound{sound=ENTITY_EXPERIENCE_ORB_PICKUP}' + 2: + type: 'mine{type="OAK_LOG";amount=3}' + lore: 'Get three oak logs!' + triggers: + - 'message{format="&aBlacksmith> &fGood job, now bring these logs back to me!"}' + - 'sound{sound="ENTITY_EXPERIENCE_ORB_PICKUP"}' + 3: + type: 'getitem{type="OAK_LOG";amount=3;npc=0}' + lore: 'Give these oak logs to the blacksmith.' + triggers: + - 'message{format="&aBlacksmith> &fGood job, now talk to the blacksmith again to claim your weapons!"}' + - 'sound{sound=ENTITY_EXPERIENCE_ORB_PICKUP}' + 4: + type: 'talkto{npc=0}' + lore: 'Get your weapons from the blacksmith!' + triggers: + - 'item{type=WOODEN_SWORD}' + - 'item{type=WOODEN_AXE}' + - 'item{type=BOW}' + - 'message{format="&aBlacksmith> &fNow go kill 5 skeletal knights to finish tutorial!"}' + - 'sound{sound=ENTITY_PLAYER_LEVELUP}' + 5: + type: 'killmythicmob{name="SkeletalKnight";amount=5}' + lore: 'Kill 5 skeletal knights!' + triggers: + - 'message{format="&aYou have successfully finished the tutorial!"}' + - 'sound{sound="ENTITY_PLAYER_LEVELUP"}' + - 'mmoitem{type=SWORD;id=CUTLASS}' + - 'experience{amount=100}' diff --git a/default/recipes/brewing.yml b/default/recipes/brewing.yml new file mode 100644 index 00000000..b12a33ad --- /dev/null +++ b/default/recipes/brewing.yml @@ -0,0 +1,15 @@ +# Every brewing recipe requires an empty water bottle. +# Make sure every brewing recipe has a different ID. +# It is only used as reference in the plugin, doesn't really matter. + +# Brewing recipe ID +mana-pot-recipe: + + # Ingredient used to brew the empty water bottles. + ingredient: MATERIAL.MANA_FLOWER + + # Items replacing empty water bottles when brewing is complete. + result: CONSUMABLE.MANA_POTION + + # Time required to brew empty water bottles. + cook-time: 10 \ No newline at end of file diff --git a/default/recipes/furnace.yml b/default/recipes/furnace.yml new file mode 100644 index 00000000..c0dbc394 --- /dev/null +++ b/default/recipes/furnace.yml @@ -0,0 +1,14 @@ +# Make sure every furnace recipe has a different ID. +# It is only used as reference in the plugin, doesn't really matter. + +# Furnace recipe ID +emerald-recipe: + + # Item being smelted. + ingredient: MATERIAL.EMERALD_GEODE + + # Item given as output. + result: MATERIAL.SHINY_EMERALD + + # Time required to smelt the item. + cook-time: 10 diff --git a/default/restrictions.yml b/default/restrictions.yml new file mode 100644 index 00000000..7f0b8744 --- /dev/null +++ b/default/restrictions.yml @@ -0,0 +1,40 @@ +# Mining a block in creative will NOT drop any +# custom drops/will not apply block regen, etc. +# +# Any block specified in this config, wherever it +# is located, MUST be mined using the correct tool +# otherwise nothing will drop! + +# The corresponding tool. It's CASE_SENSITIVE! +WOODEN_PICKAXE: + + # What the tool can mine. + can-mine: + - COAL_ORE + +STONE_PICKAXE: + can-mine: + - IRON_ORE + + # The block break permissions the tool inherits. + # e.g a stone pickaxe can mine iron ores PLUS + # any block that the wooden pickaxe can mine. + # Used to make the config much clearer. + parent: WOODEN_PICKAXE + +IRON_PICKAXE: + parent: STONE_PICKAXE + can-mine: + - GOLD_ORE + +GOLDEN_PICKAXE: + parent: IRON_PICKAXE + can-mine: + - LAPIS_ORE + +DIAMOND_PICKAXE: + parent: GOLDEN_PICKAXE + can-mine: + - DIAMOND_ORE + - EMERALD_ORE + - REDSTONE_ORE diff --git a/default/stats.yml b/default/stats.yml new file mode 100644 index 00000000..7fe5e4c6 --- /dev/null +++ b/default/stats.yml @@ -0,0 +1,112 @@ +default: + ATTACK_DAMAGE: + base: 1 + per-level: 0 + ATTACK_SPEED: + base: 4 + per-level: 0 + MAX_HEALTH: + base: 20 + per-level: 0 + MOVEMENT_SPEED: + base: .2 + per-level: 0 + ARMOR: + base: 0 + per-level: 0 + ARMOR_TOUGHNESS: + base: 0 + per-level: 0 + SPEED_MALUS_REDUCTION: + base: 0 + per-level: 0 + KNOCKBACK_RESISTANCE: + base: 0 + per-level: 0 + HEALTH_REGENERATION: + base: .1 + per-level: 0 + MAX_MANA: + base: 20 + per-level: 0 + MANA_REGENERATION: + base: .166 + per-level: .03 + MAX_STELLIUM: + base: 20 + per-level: 0 + STELLIUM_REGENERATION: + base: .01 + per-level: 0 + + # Increases main class experience earned. + ADDITIONAL_EXPERIENCE: + base: 0 + per-level: 0 + + # Reduces skill cooldowns by X% + COOLDOWN_REDUCTION: + base: 0 + per-level: 0 + + # Dealt by skills + SKILL_DAMAGE: + base: 0 + per-level: 0 + + # Physical skill damage + PHYSICAL_DAMAGE: + base: 0 + per-level: 0 + + # Magical skill damage + MAGICAL_DAMAGE: + base: 0 + per-level: 0 + + # Dealt by any weapon + WEAPON_DAMAGE: + base: 0 + per-level: 0 + + # Dealt by projectile skills or weapons + PROJECTILE_DAMAGE: + base: 0 + per-level: 0 + + # Reduces the amount of tugs needed to fish fishes. + # (When set to 30, 30% of the tugs will be removed). + FISHING_STRENGTH: + base: 0 + per-level: 0.3 + min: 0 + max: 40 + + # Chance of instantly fishing. + CRITICAL_FISHING_CHANCE: + base: 5 + per-level: 0 + min: 0 + max: 70 + + # Chance of being dragged in the waters + # by the fish when trying to catch it, + # once the player has NOT successfully + # fished (conditional probability). + CRITICAL_FISHING_FAILURE_CHANCE: + base: 3 + per-level: -.01 + min: 1 + max: 100 + +# How much decimal places the different stats +# display in the GUIs (e.g in player stats). +# Default is "0.#" when none is specified. +# https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html +decimal-format: + MOVEMENT_SPEED: '0.##' + ARMOR_TOUGHNESS: '0.###' + KNOCKBACK_RESISTANCE: '0.##' + HEALTH_REGENERATION: '0.##' + MANA_REGENERATION: '0.##' + STELLIUM_REGENERATION: '0.##' diff --git a/default/waypoints.yml b/default/waypoints.yml new file mode 100644 index 00000000..30eeb7b7 --- /dev/null +++ b/default/waypoints.yml @@ -0,0 +1,45 @@ + +# Waypoint ID, used as reference. +# Make sure the waypoints have different IDs. +spawn: + + # Name of waypoint displayed in the waypoint GUI. + name: Spawn + + # Location of waypoint: + # Yaw and pitch are where the player will be looking at when teleported. + location: 'world 69 71 136 136 0' + + # Radius of waypoint around the specified location. + radius: 1.5 + + # Stellium cost in order to use the waypoint. + # Stellium is like stamina however it's not used + # by skills and regens much slower than mana. + stellium: 3 + + # When enabled, players can unlock the waypoint + # by sneaking on it, and can open the waypoint menu + # from the specified location (true by default). + sneak: true + + # Should be waypoint be unlocked by default? + default: true + +spawn1: + name: Spawn1 + location: 'world 69 71 136 136 0' + radius: 1.5 + stellium: 3 + default: false + + # Can be teleported to even when not standing + # on any waypoint (waypoint must be unlocked). + dynamic: true + +spawn2: + name: Spawn2 + location: 'world 69 71 136 136 0' + radius: 1.5 + stellium: 3 + sneak: false diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 00000000..5c5d358e --- /dev/null +++ b/plugin.yml @@ -0,0 +1,47 @@ +name: MMOCore +version: 1.0.6 +main: net.Indyuce.mmocore.MMOCore +author: Indyuce +loadbefore: [MMOItems] +softdepend: [Vault,MythicMobs,PlaceholderAPI] +api-version: 1.13 +commands: + player: + description: Show player stats. + aliases: [p] + class: + description: Choose a new class. + attributes: + description: Opens the attribute menu. + waypoints: + description: Open the waypoints menu. + mmocore: + description: Main command. + aliases: [rpg] + deposit: + description: Opens the currency deposit menu. + withdraw: + description: Creates a withdraw request. + friends: + description: Opens the friends menu. + aliases: [f] + party: + description: Opens the party menu. + aliases: [f] + quests: + description: Opens the quests menu. + skills: + description: Opens the skills menu. +permissions: + mmocore.admin: + description: Access to /rpg + default: op + mmocore.waypoints: + description: Access to /waypoints + default: op + mmocore.currency: + description: Access to /deposit and /withdraw + default: op + mmocore.class-select: + description: Access to /class + default: op \ No newline at end of file diff --git a/src/com/codingforcookies/armorequip/ArmorEquipEvent.java b/src/com/codingforcookies/armorequip/ArmorEquipEvent.java new file mode 100644 index 00000000..e9f8fe76 --- /dev/null +++ b/src/com/codingforcookies/armorequip/ArmorEquipEvent.java @@ -0,0 +1,97 @@ +package com.codingforcookies.armorequip; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; + +/** + * @author Arnah + * @since Jul 30, 2015 + */ +public final class ArmorEquipEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + // private boolean cancelled = false; + private final ArmorType type; + private final ItemStack oldArmor, newArmor; + + /** + * Constructor for the ArmorEquipEvent. + * + * @param player + * The player who put on / removed the armor. + * @param type + * The ArmorType of the armor added + * @param oldArmorPiece + * The ItemStack of the armor removed. + * @param newArmorPiece + * The ItemStack of the armor added. + */ + public ArmorEquipEvent(Player player, ArmorType type, ItemStack oldArmor, ItemStack newArmor) { + super(player); + + this.type = type; + this.oldArmor = oldArmor; + this.newArmor = newArmor; + } + + /** + * Gets a list of handlers handling this event. + * + * @return A list of handlers handling this event. + */ + public final static HandlerList getHandlerList() { + return handlers; + } + + /** + * Gets a list of handlers handling this event. + * + * @return A list of handlers handling this event. + */ + @Override + public final HandlerList getHandlers() { + return handlers; + } + + /** + * Sets if this event should be cancelled. + * + * @param cancel + * If this event should be cancelled. + */ + // public final void setCancelled(final boolean cancelled){ + // this.cancelled = cancelled; + // } + + /** + * Gets if this event is cancelled. + * + * @return If this event is cancelled + */ + // public final boolean isCancelled(){ + // return cancelled; + // } + + public final ArmorType getType() { + return type; + } + + /** + * Returns the last equipped armor piece, could be a piece of armor, + * {@link Material#Air}, or null. + */ + public final ItemStack getOldArmor() { + return oldArmor; + } + + /** + * Returns the newly equipped armor, could be a piece of armor, + * {@link Material#Air}, or null. + */ + public final ItemStack getNewArmorPiece() { + return newArmor; + } +} \ No newline at end of file diff --git a/src/com/codingforcookies/armorequip/ArmorListener.java b/src/com/codingforcookies/armorequip/ArmorListener.java new file mode 100644 index 00000000..f8bb375c --- /dev/null +++ b/src/com/codingforcookies/armorequip/ArmorListener.java @@ -0,0 +1,186 @@ +package com.codingforcookies.armorequip; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockDispenseArmorEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemBreakEvent; +import org.bukkit.inventory.ItemStack; + +/** + * @author Arnah + * @since Jul 30, 2015 + */ +public class ArmorListener implements Listener { + + // private final List blockedMaterials; + + // public ArmorListener(List blockedMaterials){ + // this.blockedMaterials = blockedMaterials; + // } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onClick(InventoryClickEvent event) { + if (event.isCancelled() || event.getAction() == InventoryAction.NOTHING) + return; + + final boolean shift = event.getClick().equals(ClickType.SHIFT_LEFT) || event.getClick().equals(ClickType.SHIFT_RIGHT), numberkey = event.getClick().equals(ClickType.NUMBER_KEY); + + if (event.getSlotType() != SlotType.ARMOR && event.getSlotType() != SlotType.QUICKBAR && event.getSlotType() != SlotType.CONTAINER) + return; + if (event.getClickedInventory() != null && !event.getClickedInventory().getType().equals(InventoryType.PLAYER)) + return; + if (!event.getInventory().getType().equals(InventoryType.CRAFTING) && !event.getInventory().getType().equals(InventoryType.PLAYER)) + return; + if (!(event.getWhoClicked() instanceof Player)) + return; + ArmorType newArmorType = ArmorType.matchType(shift ? event.getCurrentItem() : event.getCursor()); + if (!shift && newArmorType != null && event.getRawSlot() != newArmorType.getSlot()) { + // Used for drag and drop checking to make sure you aren't trying to + // place a helmet in the boots slot. + return; + } + + if (shift) { + newArmorType = ArmorType.matchType(event.getCurrentItem()); + if (newArmorType != null) { + final boolean equipping = event.getRawSlot() != newArmorType.getSlot(); + Player player = (Player) event.getWhoClicked(); + + if (equipping ? isAirOrNull(newArmorType.getItem(player)) : !isAirOrNull(newArmorType.getItem(player))) + Bukkit.getServer().getPluginManager().callEvent(new ArmorEquipEvent((Player) event.getWhoClicked(), newArmorType, equipping ? null : event.getCurrentItem(), equipping ? event.getCurrentItem() : null)); + // if(armorEquipEvent.isCancelled()){ + // event.setCancelled(true); + // } + } + + } else { + ItemStack newArmorPiece = event.getCursor(); + ItemStack oldArmorPiece = event.getCurrentItem(); + if (numberkey) { + + // prevents shit in the 2v2 crafting + if (event.getClickedInventory().getType().equals(InventoryType.PLAYER)) { + // event.getClickedInventory() == The players inventory + // event.getHotBarButton() == key people are pressing to + // equip + // or unequip the item to or from. + // event.getRawSlot() == The slot the item is going to. + // event.getSlot() == Armor slot, can't use + // event.getRawSlot() as + // that gives a hotbar slot ;-; + ItemStack hotbarItem = event.getClickedInventory().getItem(event.getHotbarButton()); + if (!isAirOrNull(hotbarItem)) {// Equipping + newArmorType = ArmorType.matchType(hotbarItem); + newArmorPiece = hotbarItem; + oldArmorPiece = event.getClickedInventory().getItem(event.getSlot()); + } else // Unequipping + newArmorType = ArmorType.matchType(!isAirOrNull(event.getCurrentItem()) ? event.getCurrentItem() : event.getCursor()); + } + + // equip with no new item going into the slot + } else if (isAirOrNull(event.getCursor()) && !isAirOrNull(event.getCurrentItem())) + newArmorType = ArmorType.matchType(event.getCurrentItem()); + + // event.getCurrentItem() == Unequip + // event.getCursor() == Equip + // newArmorType = + // ArmorType.matchType(!isAirOrNull(event.getCurrentItem()) ? + // event.getCurrentItem() : event.getCursor()); + if (newArmorType != null && event.getRawSlot() == newArmorType.getSlot()) + Bukkit.getServer().getPluginManager().callEvent(new ArmorEquipEvent((Player) event.getWhoClicked(), newArmorType, oldArmorPiece, newArmorPiece)); + // if(armorEquipEvent.isCancelled()){ + // event.setCancelled(true); + // } + } + } + + @EventHandler + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() == Action.PHYSICAL || (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK)) + return; + + ArmorType type = ArmorType.matchType(event.getItem()); + if (type != null && isAirOrNull(type.getItem(event.getPlayer()))) + Bukkit.getServer().getPluginManager().callEvent(new ArmorEquipEvent(event.getPlayer(), ArmorType.matchType(event.getItem()), null, event.getItem())); + // if(armorEquipEvent.isCancelled()){ + // event.setCancelled(true); + // player.updateInventory(); + // } + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent event) { + // getType() seems to always be even. + // Old Cursor gives the item you are equipping + // Raw slot is the ArmorType slot + // Can't replace armor using this method making getCursor() useless. + ArmorType type = ArmorType.matchType(event.getOldCursor()); + if (event.getRawSlots().isEmpty()) + return;// Idk if this will ever happen + if (type != null && type.getSlot() == event.getRawSlots().stream().findFirst().orElse(0)) + Bukkit.getServer().getPluginManager().callEvent(new ArmorEquipEvent((Player) event.getWhoClicked(), type, null, event.getOldCursor())); + // if(armorEquipEvent.isCancelled()){ + // event.setResult(Result.DENY); + // event.setCancelled(true); + // } + } + + @EventHandler + public void onItemBreak(PlayerItemBreakEvent event) { + ArmorType type = ArmorType.matchType(event.getBrokenItem()); + if (type != null) + Bukkit.getServer().getPluginManager().callEvent(new ArmorEquipEvent(event.getPlayer(), type, event.getBrokenItem(), null)); + // if(armorEquipEvent.isCancelled()){ + // ItemStack i = event.getBrokenItem().clone(); + // i.setAmount(1); + // i.setDurability((short) (i.getDurability() - 1)); + // if(type.equals(ArmorType.HELMET)){ + // p.getInventory().setHelmet(i); + // }else if(type.equals(ArmorType.CHESTPLATE)){ + // p.getInventory().setChestplate(i); + // }else if(type.equals(ArmorType.LEGGINGS)){ + // p.getInventory().setLeggings(i); + // }else if(type.equals(ArmorType.BOOTS)){ + // p.getInventory().setBoots(i); + // } + // } + } + + @EventHandler + public void onDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + for (ItemStack item : player.getInventory().getArmorContents()) + if (!isAirOrNull(item)) + Bukkit.getServer().getPluginManager().callEvent(new ArmorEquipEvent(player, ArmorType.matchType(item), item, null)); + } + + @EventHandler + public void onDispense(BlockDispenseArmorEvent event) { + ArmorType type = ArmorType.matchType(event.getItem()); + if (type != null && event.getTargetEntity() instanceof Player) + Bukkit.getServer().getPluginManager().callEvent(new ArmorEquipEvent((Player) event.getTargetEntity(), type, null, event.getItem())); + // if(armorEquipEvent.isCancelled()){ + // event.setCancelled(true); + // } + } + + /** + * A utility method to support versions that use null or air ItemStacks. + */ + private boolean isAirOrNull(ItemStack item) { + return item == null || item.getType().equals(Material.AIR); + } +} diff --git a/src/com/codingforcookies/armorequip/ArmorType.java b/src/com/codingforcookies/armorequip/ArmorType.java new file mode 100644 index 00000000..0dc43ea5 --- /dev/null +++ b/src/com/codingforcookies/armorequip/ArmorType.java @@ -0,0 +1,64 @@ +package com.codingforcookies.armorequip; + +import java.util.function.Function; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +/** + * @author Arnah + * @since Jul 30, 2015 + */ +public enum ArmorType { + HELMET(5, (inv) -> inv.getHelmet()), + CHESTPLATE(6, (inv) -> inv.getChestplate()), + LEGGINGS(7, (inv) -> inv.getLeggings()), + BOOTS(8, (inv) -> inv.getBoots()); + + private final int slot; + private final Function handler; + + private ArmorType(int slot, Function handler) { + this.slot = slot; + this.handler = handler; + } + + public int getSlot() { + return slot; + } + + public ItemStack getItem(Player player) { + return handler.apply(player.getInventory()); + } + + /** + * Attempts to match the ArmorType for the specified ItemStack. + * + * @param itemStack + * The ItemStack to parse the type of. + * @return The parsed ArmorType. (null if none were found.) + */ + public static ArmorType matchType(ItemStack item) { + if (item == null || item.getType().equals(Material.AIR)) + return null; + + Material type = item.getType(); + String name = type.name(); + if (name.endsWith("HELMET") || name.endsWith("SKULL") || name.endsWith("HEAD") || type == Material.PLAYER_HEAD || type == Material.PUMPKIN) + return HELMET; + + else if (name.endsWith("CHESTPLATE")) + return CHESTPLATE; + + else if (name.endsWith("LEGGINGS")) + return LEGGINGS; + + else if (name.endsWith("BOOTS")) + return BOOTS; + + else + return null; + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/MMOCore.java b/src/net/Indyuce/mmocore/MMOCore.java new file mode 100644 index 00000000..2e07cc61 --- /dev/null +++ b/src/net/Indyuce/mmocore/MMOCore.java @@ -0,0 +1,356 @@ +package net.Indyuce.mmocore; + +import java.io.File; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import com.codingforcookies.armorequip.ArmorListener; + +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.resource.PlayerResource; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.command.AttributesCommand; +import net.Indyuce.mmocore.command.ClassCommand; +import net.Indyuce.mmocore.command.DepositCommand; +import net.Indyuce.mmocore.command.FriendsCommand; +import net.Indyuce.mmocore.command.MMOCoreCommand; +import net.Indyuce.mmocore.command.PartyCommand; +import net.Indyuce.mmocore.command.PlayerStatsCommand; +import net.Indyuce.mmocore.command.QuestsCommand; +import net.Indyuce.mmocore.command.SkillsCommand; +import net.Indyuce.mmocore.command.WaypointsCommand; +import net.Indyuce.mmocore.command.WithdrawCommand; +import net.Indyuce.mmocore.comp.Metrics; +import net.Indyuce.mmocore.comp.ShopKeepersEntityHandler; +import net.Indyuce.mmocore.comp.citizens.CitizenInteractEventListener; +import net.Indyuce.mmocore.comp.citizens.CitizensMMOLoader; +import net.Indyuce.mmocore.comp.entity.MyPetEntityHandler; +import net.Indyuce.mmocore.comp.holograms.CMIPlugin; +import net.Indyuce.mmocore.comp.holograms.HologramSupport; +import net.Indyuce.mmocore.comp.holograms.HologramsPlugin; +import net.Indyuce.mmocore.comp.holograms.HolographicDisplaysPlugin; +import net.Indyuce.mmocore.comp.mythicmobs.MythicMobsDrops; +import net.Indyuce.mmocore.comp.mythicmobs.MythicMobsMMOLoader; +import net.Indyuce.mmocore.comp.placeholder.DefaultParser; +import net.Indyuce.mmocore.comp.placeholder.PlaceholderAPIParser; +import net.Indyuce.mmocore.comp.placeholder.PlaceholderParser; +import net.Indyuce.mmocore.comp.rpg.DefaultRPGUtilHandler; +import net.Indyuce.mmocore.comp.rpg.RPGUtilHandler; +import net.Indyuce.mmocore.comp.vault.VaultEconomy; +import net.Indyuce.mmocore.comp.vault.VaultMMOLoader; +import net.Indyuce.mmocore.comp.worldguard.DefaultRegionHandler; +import net.Indyuce.mmocore.comp.worldguard.RegionHandler; +import net.Indyuce.mmocore.comp.worldguard.WorldGuardMMOLoader; +import net.Indyuce.mmocore.comp.worldguard.WorldGuardRegionHandler; +import net.Indyuce.mmocore.listener.BlockListener; +import net.Indyuce.mmocore.listener.GoldPouchesListener; +import net.Indyuce.mmocore.listener.LootableChestsListener; +import net.Indyuce.mmocore.listener.PartyListener; +import net.Indyuce.mmocore.listener.PlayerListener; +import net.Indyuce.mmocore.listener.SpellCast; +import net.Indyuce.mmocore.listener.WaypointsListener; +import net.Indyuce.mmocore.listener.event.PlayerAttackEventListener; +import net.Indyuce.mmocore.listener.profession.FishingListener; +import net.Indyuce.mmocore.listener.profession.PlayerCollectStats; +import net.Indyuce.mmocore.manager.AttributeManager; +import net.Indyuce.mmocore.manager.ClassManager; +import net.Indyuce.mmocore.manager.ConfigItemManager; +import net.Indyuce.mmocore.manager.ConfigManager; +import net.Indyuce.mmocore.manager.CustomBlockManager; +import net.Indyuce.mmocore.manager.DamageManager; +import net.Indyuce.mmocore.manager.DropTableManager; +import net.Indyuce.mmocore.manager.EntityManager; +import net.Indyuce.mmocore.manager.InventoryManager; +import net.Indyuce.mmocore.manager.LootableChestManager; +import net.Indyuce.mmocore.manager.MMOLoadManager; +import net.Indyuce.mmocore.manager.QuestManager; +import net.Indyuce.mmocore.manager.RestrictionManager; +import net.Indyuce.mmocore.manager.SkillManager; +import net.Indyuce.mmocore.manager.WaypointManager; +import net.Indyuce.mmocore.manager.profession.AlchemyManager; +import net.Indyuce.mmocore.manager.profession.EnchantManager; +import net.Indyuce.mmocore.manager.profession.FishingManager; +import net.Indyuce.mmocore.manager.profession.ProfessionManager; +import net.Indyuce.mmocore.manager.profession.SmithingManager; +import net.Indyuce.mmocore.manager.social.BoosterManager; +import net.Indyuce.mmocore.manager.social.PartyManager; +import net.Indyuce.mmocore.manager.social.RequestManager; +import net.Indyuce.mmocore.version.ServerVersion; +import net.Indyuce.mmocore.version.nms.NMSHandler; + +public class MMOCore extends JavaPlugin { + public static MMOCore plugin; + + public final ClassManager classManager = new ClassManager(); + public ConfigManager configManager; + public WaypointManager waypointManager; + public RestrictionManager restrictionManager; + public final DropTableManager dropTableManager = new DropTableManager(); + public final CustomBlockManager mineManager = new CustomBlockManager(); + public final BoosterManager boosterManager = new BoosterManager(); + public LootableChestManager chestManager; + public RequestManager requestManager; + public final AttributeManager attributeManager = new AttributeManager(); + public final PartyManager partyManager = new PartyManager(); + public final QuestManager questManager = new QuestManager(); + public ConfigItemManager configItems; + public SkillManager skillManager; + public final ProfessionManager professionManager = new ProfessionManager(); + public VaultEconomy economy; + public HologramSupport hologramSupport; + public PlaceholderParser placeholderParser = new DefaultParser(); + public final DamageManager damage = new DamageManager(); + public final EntityManager entities = new EntityManager(); + public NMSHandler nms; + public ServerVersion version; + public InventoryManager inventoryManager; + public RegionHandler regionHandler; + + /* + * professions + */ + public final FishingManager fishingManager = new FishingManager(); + public final AlchemyManager alchemyManager = new AlchemyManager(); + public final EnchantManager enchantManager = new EnchantManager(); + public final SmithingManager smithingManager = new SmithingManager(); + + public final MMOLoadManager loadManager = new MMOLoadManager(); + public RPGUtilHandler rpgUtilHandler = new DefaultRPGUtilHandler(); + + public static final DecimalFormat digit = new DecimalFormat("0.#"), digit2 = new DecimalFormat("0.##"), digit3 = new DecimalFormat("0.###"); + public static final SimpleDateFormat smoothDateFormat = new SimpleDateFormat(""); + + public void onLoad() { + plugin = this; + version = new ServerVersion(Bukkit.getServer().getClass()); + + /* + * register extra objective, drop items... + */ + if (Bukkit.getPluginManager().getPlugin("WorldGuard") != null) + loadManager.registerLoader(new WorldGuardMMOLoader()); + + if (Bukkit.getPluginManager().getPlugin("Citizens") != null) + loadManager.registerLoader(new CitizensMMOLoader()); + + if (Bukkit.getPluginManager().getPlugin("Vault") != null) + loadManager.registerLoader(new VaultMMOLoader()); + + if (Bukkit.getPluginManager().getPlugin("MythicMobs") != null) + loadManager.registerLoader(new MythicMobsMMOLoader()); + } + + public void onEnable() { + + /* + * decimal format + */ + DecimalFormatSymbols symbols = digit.getDecimalFormatSymbols(); + symbols.setDecimalSeparator('.'); + digit.setDecimalFormatSymbols(symbols); + digit2.setDecimalFormatSymbols(symbols); + digit3.setDecimalFormatSymbols(symbols); + + try { + getLogger().log(Level.INFO, "Detected Bukkit Version: " + version.toString()); + nms = (NMSHandler) Class.forName("net.Indyuce.mmocore.version.nms.NMSHandler_" + version.toString().substring(1)).newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException exception) { + getLogger().log(Level.INFO, "Your server version is not compatible."); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + new Metrics(this); + + if (Bukkit.getPluginManager().getPlugin("Vault") != null) + economy = new VaultEconomy(); + + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { + placeholderParser = new PlaceholderAPIParser(); + getLogger().log(Level.INFO, "Hooked onto PlaceholderAPI"); + } + + if (Bukkit.getPluginManager().getPlugin("Citizens") != null) { + Bukkit.getPluginManager().registerEvents(new CitizenInteractEventListener(), this); + getLogger().log(Level.INFO, "Hooked onto Citizens"); + } + + if (Bukkit.getPluginManager().getPlugin("WorldGuard") != null) { + regionHandler = new WorldGuardRegionHandler(); + getLogger().log(Level.INFO, "Hooked onto WorldGuard"); + } else + regionHandler = new DefaultRegionHandler(); + + if (Bukkit.getPluginManager().getPlugin("HolographicDisplays") != null) { + hologramSupport = new HolographicDisplaysPlugin(); + getLogger().log(Level.INFO, "Hooked onto HolographicDisplays"); + } else if (Bukkit.getPluginManager().getPlugin("CMI") != null) { + hologramSupport = new CMIPlugin(); + getLogger().log(Level.INFO, "Hooked onto CMI Holograms"); + } else if (Bukkit.getPluginManager().getPlugin("Holograms") != null) { + hologramSupport = new HologramsPlugin(); + getLogger().log(Level.INFO, "Hooked onto Holograms"); + } + + if (Bukkit.getPluginManager().getPlugin("ShopKeepers") != null) { + entities.registerHandler(new ShopKeepersEntityHandler()); + getLogger().log(Level.INFO, "Hooked onto ShopKeepers"); + } + + if (Bukkit.getPluginManager().getPlugin("MyPet") != null) { + entities.registerHandler(new MyPetEntityHandler()); + getLogger().log(Level.INFO, "Hooked onto MyPet"); + } + + if (Bukkit.getPluginManager().getPlugin("MythicMobs") != null) { + Bukkit.getServer().getPluginManager().registerEvents(new MythicMobsDrops(), this); + getLogger().log(Level.INFO, "Hooked onto MythicMobs"); + } + + /* + * resource regeneration. must check if entity is dead otherwise regen + * will make the 'respawn' button glitched plus HURT entity effect bug + */ + new BukkitRunnable() { + public void run() { + for (PlayerData data : PlayerData.getAll()) + if (data.isOnline() && !data.getPlayer().isDead()) { + + data.giveStellium(data.getStats().getStat(StatType.STELLIUM_REGENERATION)); + + if (data.canRegen(PlayerResource.HEALTH)) + data.heal(data.calculateRegen(PlayerResource.HEALTH)); + + if (data.canRegen(PlayerResource.MANA)) + data.giveMana(data.calculateRegen(PlayerResource.MANA)); + + if (data.canRegen(PlayerResource.STAMINA)) + data.giveMana(data.calculateRegen(PlayerResource.STAMINA)); + } + } + }.runTaskTimerAsynchronously(MMOCore.plugin, 100, 20); + + saveDefaultConfig(); + reloadPlugin(); + + Bukkit.getPluginManager().registerEvents(new PlayerAttackEventListener(), this); + + Bukkit.getPluginManager().registerEvents(new DamageManager(), this); + Bukkit.getPluginManager().registerEvents(new WaypointsListener(), this); + Bukkit.getPluginManager().registerEvents(new PlayerListener(), this); + Bukkit.getPluginManager().registerEvents(new GoldPouchesListener(), this); + Bukkit.getPluginManager().registerEvents(new BlockListener(), this); + Bukkit.getPluginManager().registerEvents(new LootableChestsListener(), this); + Bukkit.getPluginManager().registerEvents(new SpellCast(), this); + Bukkit.getPluginManager().registerEvents(new PartyListener(), this); + Bukkit.getPluginManager().registerEvents(new FishingListener(), this); + Bukkit.getPluginManager().registerEvents(new PlayerCollectStats(), this); + + Bukkit.getPluginManager().registerEvents(new ArmorListener(), this); + + /* + * initialize player data from all online players. this is very + * important to do that after registering all the professses otherwise + * the player datas can't recognize what profess the player has and + * professes will be lost + */ + Bukkit.getOnlinePlayers().forEach(player -> PlayerData.setup(player).setPlayer(player)); + + // commands + getCommand("player").setExecutor(new PlayerStatsCommand()); + getCommand("attributes").setExecutor(new AttributesCommand()); + getCommand("class").setExecutor(new ClassCommand()); + getCommand("waypoints").setExecutor(new WaypointsCommand()); + getCommand("quests").setExecutor(new QuestsCommand()); + getCommand("skills").setExecutor(new SkillsCommand()); + getCommand("friends").setExecutor(new FriendsCommand()); + getCommand("party").setExecutor(new PartyCommand()); + + if (hasEconomy() && economy.isValid()) { + getCommand("withdraw").setExecutor(new WithdrawCommand()); + getCommand("deposit").setExecutor(new DepositCommand()); + } + + MMOCoreCommand mmoCoreCommand = new MMOCoreCommand(); + getCommand("mmocore").setExecutor(mmoCoreCommand); + getCommand("mmocore").setTabCompleter(mmoCoreCommand); + } + + public void onDisable() { + for (PlayerData playerData : PlayerData.getAll()) { + ConfigFile config = new ConfigFile(playerData.getUniqueId()); + playerData.getQuestData().resetBossBar(); + playerData.saveInConfig(config.getConfig()); + config.save(); + } + + mineManager.resetRemainingBlocks(); + } + + public void reloadPlugin() { + configManager = new ConfigManager(); + skillManager = new SkillManager(); + + mineManager.clear(); + mineManager.reload(); + + fishingManager.clear(); + alchemyManager.clear(); + smithingManager.clear(); + + partyManager.clear(); + partyManager.reload(); + + attributeManager.clear(); + attributeManager.reload(); + + professionManager.clear(); + professionManager.reload(); + + classManager.clear(); + classManager.reload(); + + inventoryManager = new InventoryManager(); + + dropTableManager.clear(); + dropTableManager.reload(); + + questManager.clear(); + questManager.reload(); + + chestManager = new LootableChestManager(new ConfigFile("chests").getConfig()); + waypointManager = new WaypointManager(new ConfigFile("waypoints").getConfig()); + restrictionManager = new RestrictionManager(new ConfigFile("restrictions").getConfig()); + requestManager = new RequestManager(); + configItems = new ConfigItemManager(new ConfigFile("items").getConfig()); + + StatType.load(); + } + + public static void log(String message) { + log(Level.INFO, message); + } + + public static void log(Level level, String message) { + plugin.getLogger().log(level, message); + } + + public File getJarFile() { + return getFile(); + } + + public boolean hasHolograms() { + return hologramSupport != null; + } + + public boolean hasEconomy() { + return economy != null && economy.isValid(); + } +} diff --git a/src/net/Indyuce/mmocore/MMOCoreUtils.java b/src/net/Indyuce/mmocore/MMOCoreUtils.java new file mode 100644 index 00000000..f387623a --- /dev/null +++ b/src/net/Indyuce/mmocore/MMOCoreUtils.java @@ -0,0 +1,186 @@ +package net.Indyuce.mmocore; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.bukkit.util.io.BukkitObjectOutputStream; +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; + +public class MMOCoreUtils { + public static boolean pluginItem(ItemStack item) { + return item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName(); + } + + public static String displayName(ItemStack item) { + return item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : caseOnWords(item.getType().name().replace("_", " ")); + } + + public static String caseOnWords(String s) { + StringBuilder builder = new StringBuilder(s); + boolean isLastSpace = true; + for (int item = 0; item < builder.length(); item++) { + char ch = builder.charAt(item); + if (isLastSpace && ch >= 'a' && ch <= 'z') { + builder.setCharAt(item, (char) (ch + ('A' - 'a'))); + isLastSpace = false; + } else if (ch != ' ') + isLastSpace = false; + else + isLastSpace = true; + } + return builder.toString(); + } + + public static ItemStack readIcon(String string) { + try { + Validate.notNull(string, "String cannot be null"); + String[] split = string.split("\\:"); + Material material = Material.valueOf(split[0].toUpperCase().replace("-", "_").replace(" ", "_")); + return split.length > 1 ? MMOCore.plugin.version.getTextureHandler().textureItem(material, Integer.parseInt(split[1])) : new ItemStack(material); + } catch (IllegalArgumentException exception) { + return new ItemStack(Material.BARRIER); + } + } + + public static int getWorth(ItemStack[] items) { + int t = 0; + for (ItemStack item : items) + if (item != null && item.getType() != Material.AIR) + t += MMOCore.plugin.nms.getNBTItem(item).getInt("RpgWorth") * item.getAmount(); + return t; + } + + public static String toBase64(ItemStack[] items) { + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream); + dataOutput.writeInt(items.length); + for (int i = 0; i < items.length; i++) + dataOutput.writeObject(items[i]); + dataOutput.close(); + return Base64Coder.encodeLines(outputStream.toByteArray()); + } catch (Exception e) { + return null; + } + } + + public static ItemStack[] itemStackArrayFromBase64(String data) { + try { + ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data)); + BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream); + ItemStack[] items = new ItemStack[dataInput.readInt()]; + for (int i = 0; i < items.length; i++) + items[i] = (ItemStack) dataInput.readObject(); + dataInput.close(); + return items; + } catch (Exception e) { + return null; + } + } + + public static String intToRoman(int input) { + if (input < 1 || input > 499) + return ">499"; + + String s = ""; + while (input >= 400) { + s += "CD"; + input -= 400; + } + while (input >= 100) { + s += "C"; + input -= 100; + } + while (input >= 90) { + s += "XC"; + input -= 90; + } + while (input >= 50) { + s += "L"; + input -= 50; + } + while (input >= 40) { + s += "XL"; + input -= 40; + } + while (input >= 10) { + s += "X"; + input -= 10; + } + while (input >= 9) { + s += "IX"; + input -= 9; + } + while (input >= 5) { + s += "V"; + input -= 5; + } + while (input >= 4) { + s += "IV"; + input -= 4; + } + while (input >= 1) { + s += "I"; + input -= 1; + } + return s; + } + + /* + * method to get all entities surrounding a location. this method does not + * take every entity in the world but rather takes all the entities from the + * 9 chunks around the entity, so even if the location is at the border of a + * chunk (worst case border of 4 chunks), the entity will still be included + */ + public static List getNearbyChunkEntities(Location loc) { + + /* + * another method to save performance is if an entity bounding box + * calculation is made twice in the same tick then the method does not + * need to be called twice, it can utilize the same entity list since + * the entities have not moved (e.g fireball which does 2+ calculations + * per tick) + */ + List entities = new ArrayList<>(); + + int cx = loc.getChunk().getX(); + int cz = loc.getChunk().getZ(); + + for (int x = -1; x < 2; x++) + for (int z = -1; z < 2; z++) + for (Entity entity : loc.getWorld().getChunkAt(cx + x, cz + z).getEntities()) + entities.add(entity); + + return entities; + } + + // TODO worldguard flags support for no target + public static boolean canTarget(Player player, Entity target) { + return !player.equals(target) && target instanceof LivingEntity && !target.isDead() && !MMOCore.plugin.entities.findCustom(target); + } + + public static void heal(LivingEntity target, double value) { + + double max = target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); + double gain = Math.min(max, target.getHealth() + value) - target.getHealth(); + + EntityRegainHealthEvent event = new EntityRegainHealthEvent(target, gain, RegainReason.CUSTOM); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) + target.setHealth(target.getHealth() + gain); + } +} diff --git a/src/net/Indyuce/mmocore/api/AltChar.java b/src/net/Indyuce/mmocore/api/AltChar.java new file mode 100644 index 00000000..51285be0 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/AltChar.java @@ -0,0 +1,27 @@ +package net.Indyuce.mmocore.api; + +public class AltChar { + public static final String gemSymbol = "۞"; + public static final String square = "█"; + public static final String star = "✦"; + public static final String rightArrow = "⇨"; + public static final String fourEdgedClub = "✤"; + + public static final String club = "♣"; + public static final String diamond = "◆"; + public static final String spade = "♤"; + public static final String heart = "♥"; + + public static final String note1 = "♩"; + public static final String note2 = "♪"; + public static final String doubleNote1 = "♫"; + public static final String doubleNote2 = "♬"; + + public static final String listDash = "►"; + public static final String smallListDash = "▸"; + + public static final String listSquare = "■"; + + public static final String ok = "✔"; + public static final String no = "✖"; +} diff --git a/src/net/Indyuce/mmocore/api/ConfigFile.java b/src/net/Indyuce/mmocore/api/ConfigFile.java new file mode 100644 index 00000000..90ecba37 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/ConfigFile.java @@ -0,0 +1,51 @@ +package net.Indyuce.mmocore.api; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; +import java.util.logging.Level; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import net.Indyuce.mmocore.MMOCore; + +public class ConfigFile { + private final File file; + private final String name; + private final FileConfiguration config; + + public ConfigFile(Player player) { + this(player.getUniqueId()); + } + + public ConfigFile(UUID uuid) { + this(MMOCore.plugin, "/userdata", uuid.toString()); + } + + public ConfigFile(String name) { + this(MMOCore.plugin, "", name); + } + + public ConfigFile(String folder, String name) { + this(MMOCore.plugin, folder, name); + } + + public ConfigFile(Plugin plugin, String folder, String name) { + config = YamlConfiguration.loadConfiguration(file = new File(plugin.getDataFolder() + folder, (this.name = name) + ".yml")); + } + + public FileConfiguration getConfig() { + return config; + } + + public void save() { + try { + config.save(file); + } catch (IOException e2) { + MMOCore.plugin.getLogger().log(Level.SEVERE, "Could not save " + name + ".yml!"); + } + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/api/ConfigMessage.java b/src/net/Indyuce/mmocore/api/ConfigMessage.java new file mode 100644 index 00000000..74644df9 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/ConfigMessage.java @@ -0,0 +1,43 @@ +package net.Indyuce.mmocore.api; + +import java.util.Collection; +import java.util.List; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; + +public class ConfigMessage { + private final List messages; + + public ConfigMessage(String key) { + messages = MMOCore.plugin.configManager.getMessage(key); + } + + public ConfigMessage addPlaceholders(String... placeholders) { + for (int n = 0; n < messages.size(); n++) { + String line = messages.get(n); + for (int j = 0; j < placeholders.length - 1; j += 2) { + String placeholder = placeholders[j]; + if (line.contains("{" + placeholder + "}")) + line = line.replace("{" + placeholder + "}", placeholders[j + 1]); + } + messages.set(n, line); + } + return this; + } + + public void send(CommandSender sender) { + messages.forEach(line -> sender.sendMessage(ChatColor.translateAlternateColorCodes('&', line))); + } + + public void send(Collection players) { + players.forEach(player -> messages.forEach(line -> player.sendMessage(ChatColor.translateAlternateColorCodes('&', line)))); + } + + public void sendAsJSon(Player player) { + messages.forEach(line -> MMOCore.plugin.nms.sendJson(player, ChatColor.translateAlternateColorCodes('&', line))); + } +} diff --git a/src/net/Indyuce/mmocore/api/Waypoint.java b/src/net/Indyuce/mmocore/api/Waypoint.java new file mode 100644 index 00000000..a0d7f53e --- /dev/null +++ b/src/net/Indyuce/mmocore/api/Waypoint.java @@ -0,0 +1,91 @@ +package net.Indyuce.mmocore.api; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; + +public class Waypoint { + private final String id, name; + private final Location loc; + private final double radiusSquared, stellium; + private final boolean def, sneak, dynamic; + + public Waypoint(ConfigurationSection section) { + Validate.notNull(section, "Could not load config section"); + + id = section.getName(); + + name = section.getString("name"); + Validate.notNull(name, "Could not load waypoint name"); + + String format = section.getString("location"); + Validate.notNull(format, "Could not read location"); + loc = readLocation(format); + + stellium = section.getDouble("stellium"); + radiusSquared = Math.pow(section.getDouble("radius"), 2); + def = section.getBoolean("default"); + sneak = section.contains("sneak") ? section.getBoolean("sneak") : true; + dynamic = section.getBoolean("dynamic"); + } + + public Location getLocation() { + return loc; + } + + public String getName() { + return name; + } + + public double getStelliumCost() { + return stellium; + } + + public boolean hasSneakEnabled() { + return sneak; + } + + public String getId() { + return id; + } + + public boolean isDefault() { + return def; + } + + public boolean isDynamic() { + return dynamic; + } + + public boolean isOnWaypoint(Player player) { + return player.getWorld().equals(loc.getWorld()) && player.getLocation().distanceSquared(loc) < radiusSquared; + } + + @Override + public String toString() { + return id; + } + + @Override + public boolean equals(Object object) { + return object instanceof Waypoint && ((Waypoint) object).id.equals(id); + } + + private Location readLocation(String string) { + String[] split = string.split("\\ "); + + World world = Bukkit.getWorld(split[0]); + Validate.notNull(world, "Could not find world " + world); + + double x = Double.parseDouble(split[1]); + double y = Double.parseDouble(split[2]); + double z = Double.parseDouble(split[3]); + float yaw = split.length > 4 ? (float) Double.parseDouble(split[4]) : 0; + float pitch = split.length > 5 ? (float) Double.parseDouble(split[5]) : 0; + + return new Location(world, x, y, z, yaw, pitch); + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/api/droptable/DropTable.java b/src/net/Indyuce/mmocore/api/droptable/DropTable.java new file mode 100644 index 00000000..a632f091 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/DropTable.java @@ -0,0 +1,64 @@ +package net.Indyuce.mmocore.api.droptable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; + +public class DropTable { + private final String id; + private final Set drops = new HashSet<>(); + + /* + * cached in order to load other items. + */ + private ConfigurationSection loaded; + + public DropTable(ConfigurationSection section) { + id = section.getName(); + loaded = section; + } + + /* + * must be loaded after since drop tables must be initialized first + * otherwise no reference for drop table drop items. + */ + public DropTable load() { + + List list = loaded.getStringList("items"); + Validate.notNull(list, "Could not find drop item list"); + + for (String key : list) + try { + drops.add(MMOCore.plugin.loadManager.loadDropItem(new MMOLineConfig(key))); + } catch (MMOLoadException exception) { + exception.printConsole("DropTables", "drop item"); + } + + loaded = null; + return this; + } + + public String getId() { + return id; + } + + public List collect() { + List total = new ArrayList<>(); + + for (DropItem item : drops) + if (item.rollChance()) + item.collect(total); + + return total; + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/api/droptable/condition/Condition.java b/src/net/Indyuce/mmocore/api/droptable/condition/Condition.java new file mode 100644 index 00000000..d1238e37 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/condition/Condition.java @@ -0,0 +1,17 @@ +package net.Indyuce.mmocore.api.droptable.condition; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; + +public abstract class Condition { + private final String id; + + public Condition(MMOLineConfig config) { + this.id = config.getKey(); + } + + public String getId() { + return id; + } + + public abstract boolean isMet(ConditionInstance entity); +} diff --git a/src/net/Indyuce/mmocore/api/droptable/condition/ConditionInstance.java b/src/net/Indyuce/mmocore/api/droptable/condition/ConditionInstance.java new file mode 100644 index 00000000..92ed9373 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/condition/ConditionInstance.java @@ -0,0 +1,42 @@ +package net.Indyuce.mmocore.api.droptable.condition; + +import java.util.List; +import java.util.stream.Stream; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +import net.Indyuce.mmocore.MMOCore; + +public class ConditionInstance { + private final Entity entity; + private final Location applied; + private final List regions; + + public ConditionInstance(Entity entity) { + this(entity, entity.getLocation()); + } + + public ConditionInstance(Entity entity, Location applied) { + this.entity = entity; + this.regions = MMOCore.plugin.regionHandler.getRegions(this.applied = applied); + + regions.add("__global__"); + } + + public boolean isInRegion(String name) { + return regions.contains(name); + } + + public Location getAppliedLocation() { + return applied; + } + + public Entity getEntity() { + return entity; + } + + public Stream getRegionStream() { + return regions.stream(); + } +} diff --git a/src/net/Indyuce/mmocore/api/droptable/condition/WorldCondition.java b/src/net/Indyuce/mmocore/api/droptable/condition/WorldCondition.java new file mode 100644 index 00000000..6c7a0ae7 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/condition/WorldCondition.java @@ -0,0 +1,22 @@ +package net.Indyuce.mmocore.api.droptable.condition; + +import java.util.Arrays; +import java.util.List; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; + +public class WorldCondition extends Condition { + private final List names; + + public WorldCondition(MMOLineConfig config) { + super(config); + + config.validate("name"); + names = Arrays.asList(config.getString("name").split("\\,")); + } + + @Override + public boolean isMet(ConditionInstance entity) { + return names.contains(entity.getEntity().getWorld().getName()); + } +} diff --git a/src/net/Indyuce/mmocore/api/droptable/dropitem/DropItem.java b/src/net/Indyuce/mmocore/api/droptable/dropitem/DropItem.java new file mode 100644 index 00000000..44bdb94d --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/dropitem/DropItem.java @@ -0,0 +1,39 @@ +package net.Indyuce.mmocore.api.droptable.dropitem; + +import java.util.List; +import java.util.Random; + +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.math.formula.RandomAmount; + +public abstract class DropItem { + protected static final Random random = new Random(); + + private double chance; + private RandomAmount amount; + + public DropItem(MMOLineConfig config) { + chance = config.args().length > 0 ? Double.parseDouble(config.args()[0]) : 1; + amount = config.args().length > 1 ? new RandomAmount(config.args()[1]) : new RandomAmount(1, 0); + } + + public RandomAmount getAmount() { + return amount; + } + + public double getChance() { + return chance; + } + + public int rollAmount() { + return amount.calculateInt(); + } + + public boolean rollChance() { + return random.nextDouble() < chance; + } + + public abstract void collect(List total); +} diff --git a/src/net/Indyuce/mmocore/api/droptable/dropitem/DropTableDropItem.java b/src/net/Indyuce/mmocore/api/droptable/dropitem/DropTableDropItem.java new file mode 100644 index 00000000..cef0734c --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/dropitem/DropTableDropItem.java @@ -0,0 +1,30 @@ +package net.Indyuce.mmocore.api.droptable.dropitem; + +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.DropTable; +import net.Indyuce.mmocore.api.load.MMOLineConfig; + +public class DropTableDropItem extends DropItem { + private DropTable dropTable; + + public DropTableDropItem(MMOLineConfig config) { + super(config); + + config.validate("id"); + String id = config.getString("id"); + + Validate.isTrue(MMOCore.plugin.dropTableManager.has(id), "Could not find drop table " + id); + this.dropTable = MMOCore.plugin.dropTableManager.get(id); + } + + @Override + public void collect(List total) { + for (int j = 0; j < rollAmount(); j++) + total.addAll(dropTable.collect()); + } +} diff --git a/src/net/Indyuce/mmocore/api/droptable/dropitem/GoldDropItem.java b/src/net/Indyuce/mmocore/api/droptable/dropitem/GoldDropItem.java new file mode 100644 index 00000000..ef46812c --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/dropitem/GoldDropItem.java @@ -0,0 +1,19 @@ +package net.Indyuce.mmocore.api.droptable.dropitem; + +import java.util.List; + +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.item.CurrencyItem; +import net.Indyuce.mmocore.api.load.MMOLineConfig; + +public class GoldDropItem extends DropItem { + public GoldDropItem(MMOLineConfig config) { + super(config); + } + + @Override + public void collect(List total) { + total.add(new CurrencyItem("GOLD_COIN", 1, rollAmount()).build()); + } +} diff --git a/src/net/Indyuce/mmocore/api/droptable/dropitem/NoteDropItem.java b/src/net/Indyuce/mmocore/api/droptable/dropitem/NoteDropItem.java new file mode 100644 index 00000000..2c07acd6 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/dropitem/NoteDropItem.java @@ -0,0 +1,26 @@ +package net.Indyuce.mmocore.api.droptable.dropitem; + +import java.util.List; + +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.item.CurrencyItem; +import net.Indyuce.mmocore.api.load.MMOLineConfig; + +public class NoteDropItem extends DropItem { + private int min, max; + + public NoteDropItem(MMOLineConfig config) { + super(config); + + config.validate("max", "min"); + + min = (int) config.getDouble("min"); + max = (int) config.getDouble("max"); + } + + @Override + public void collect(List total) { + total.add(new CurrencyItem("NOTE", random.nextInt(max - min + 1) + min, rollAmount()).build()); + } +} diff --git a/src/net/Indyuce/mmocore/api/droptable/dropitem/VanillaDropItem.java b/src/net/Indyuce/mmocore/api/droptable/dropitem/VanillaDropItem.java new file mode 100644 index 00000000..9a652704 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/dropitem/VanillaDropItem.java @@ -0,0 +1,28 @@ +package net.Indyuce.mmocore.api.droptable.dropitem; + +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; + +public class VanillaDropItem extends DropItem { + private final Material material; + + public VanillaDropItem(MMOLineConfig config) { + super(config); + + config.validate("type"); + this.material = Material.valueOf(config.getString("type")); + } + + public Material getMaterial() { + return material; + } + + @Override + public void collect(List total) { + total.add(new ItemStack(material, rollAmount())); + } +} diff --git a/src/net/Indyuce/mmocore/api/droptable/dropitem/fishing/FishingDropItem.java b/src/net/Indyuce/mmocore/api/droptable/dropitem/fishing/FishingDropItem.java new file mode 100644 index 00000000..d9005099 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/droptable/dropitem/fishing/FishingDropItem.java @@ -0,0 +1,74 @@ +package net.Indyuce.mmocore.api.droptable.dropitem.fishing; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.math.formula.RandomAmount; + +public class FishingDropItem { + private final RandomAmount experience, tugs; + private final DropItem dropItem; + + private final int minCoef, maxCoef; + + public FishingDropItem(int minCoef, String value) { + MMOLineConfig config = new MMOLineConfig(value); + + config.validate("tugs", "experience"); + + tugs = new RandomAmount(config.getString("tugs")); + experience = new RandomAmount(config.getString("experience")); + + this.minCoef = minCoef; + maxCoef = minCoef + (config.contains("coef") ? Math.min(1, config.getInt("coef")) : 1); + + dropItem = MMOCore.plugin.loadManager.loadDropItem(config); + } + + public int getMinCoefficient() { + return minCoef; + } + + public int getMaxCoefficient() { + return maxCoef; + } + + public boolean matchesCoefficient(int n) { + return n >= minCoef && n < maxCoef; + } + + public DropItem getItem() { + return dropItem; + } + + public RandomAmount getExperience() { + return experience; + } + + public RandomAmount getTugs() { + return tugs; + } + + public int rollExperience() { + return experience.calculateInt(); + } + + public int rollTugs() { + return tugs.calculateInt(); + } + + public DropItem getDropItem() { + return dropItem; + } + + public ItemStack collect() { + List collect = new ArrayList<>(); + dropItem.collect(collect); + return collect.stream().findAny().get(); + } +} diff --git a/src/net/Indyuce/mmocore/api/eco/Withdraw.java b/src/net/Indyuce/mmocore/api/eco/Withdraw.java new file mode 100644 index 00000000..2930d273 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/eco/Withdraw.java @@ -0,0 +1,119 @@ +package net.Indyuce.mmocore.api.eco; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.item.CurrencyItem; +import net.Indyuce.mmocore.api.item.SmartGive; + +public class Withdraw implements Listener { + private static final Set withdrawing = new HashSet<>(); + + private final Player player; + + public Withdraw(Player player) { + this.player = player; + } + + public Player getPlayer() { + return player; + } + + public void open() { + if (isWithdrawing()) + return; + + withdrawing.add(player.getUniqueId()); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("withdrawing")); + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + Bukkit.getScheduler().runTaskLater(MMOCore.plugin, () -> close(), 20 * 20); + } + + public void close() { + HandlerList.unregisterAll(this); + withdrawing.remove(player.getUniqueId()); + } + + public boolean isWithdrawing() { + return withdrawing.contains(player.getUniqueId()); + } + + @EventHandler + public void a(PlayerMoveEvent event) { + if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockY() == event.getTo().getBlockY() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) + return; + + if (!event.getPlayer().equals(player)) + return; + + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("withdraw-cancel")); + close(); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void b(AsyncPlayerChatEvent event) { + if (!event.getPlayer().equals(player)) + return; + + event.setCancelled(true); + + final int worth; + try { + worth = Integer.parseInt(event.getMessage()); + } catch (Exception e) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("wrong-number", "arg", event.getMessage())); + return; + } + + int left = (int) (MMOCore.plugin.economy.getEconomy().getBalance(player) - worth); + if (left < 0) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-enough-money", "left", "" + -left)); + return; + } + + close(); + + Bukkit.getScheduler().runTask(MMOCore.plugin, () -> { + MMOCore.plugin.economy.getEconomy().withdrawPlayer(player, worth); + withdrawAlgorythm(worth); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("withdrew", "worth", "" + worth)); + }); + } + + public void withdrawAlgorythm(int worth) { + int note = worth / 10 * 10; + int coins = worth - note; + + SmartGive smart = new SmartGive(player); + if (note > 0) + smart.give(new CurrencyItem("NOTE", note).build()); + + ItemStack coinsItem = new CurrencyItem("GOLD_COIN", 1).build(); + coinsItem.setAmount(coins); + smart.give(coinsItem); + } + + /* + * extra safety + */ + @EventHandler + public void c(PlayerQuitEvent event) { + if (event.getPlayer().equals(player)) + close(); + } +} diff --git a/src/net/Indyuce/mmocore/api/event/CustomBlockMineEvent.java b/src/net/Indyuce/mmocore/api/event/CustomBlockMineEvent.java new file mode 100644 index 00000000..68a18d4d --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/CustomBlockMineEvent.java @@ -0,0 +1,65 @@ +package net.Indyuce.mmocore.api.event; + +import java.util.List; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.experience.ExperienceInfo; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.CustomBlockManager.BlockInfo; + +public class CustomBlockMineEvent extends PlayerDataEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final Block block; + private final List drops; + private final ExperienceInfo experience; + + private boolean cancelled = false; + + public CustomBlockMineEvent(PlayerData player, Block block, BlockInfo info) { + super(player); + + this.block = block; + this.drops = info.collectDrops(); + this.experience = info.hasExperience() ? info.getExperience().newInfo() : null; + } + + public boolean hasGainedExperience() { + return experience != null; + } + + public ExperienceInfo getGainedExperience() { + return experience; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean value) { + cancelled = value; + } + + public Block getBlock() { + return block; + } + + public List getDrops() { + return drops; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/api/event/CustomPlayerFishEvent.java b/src/net/Indyuce/mmocore/api/event/CustomPlayerFishEvent.java new file mode 100644 index 00000000..c82a65c3 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/CustomPlayerFishEvent.java @@ -0,0 +1,44 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class CustomPlayerFishEvent extends PlayerDataEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final DropItem caught; + + private boolean cancelled = false; + + public CustomPlayerFishEvent(PlayerData player, DropItem caught) { + super(player); + + this.caught = caught; + } + + public DropItem getCaught() { + return caught; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean value) { + cancelled = value; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/api/event/EntityKillEntityEvent.java b/src/net/Indyuce/mmocore/api/event/EntityKillEntityEvent.java new file mode 100644 index 00000000..2ff91e1e --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/EntityKillEntityEvent.java @@ -0,0 +1,31 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +public class EntityKillEntityEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + + private final Entity target; + + public EntityKillEntityEvent(Entity what, Entity target) { + super(what); + + this.target = target; + } + + public Entity getTarget() { + return target; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/net/Indyuce/mmocore/api/event/PlayerAttackEvent.java b/src/net/Indyuce/mmocore/api/event/PlayerAttackEvent.java new file mode 100644 index 00000000..9b544df6 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/PlayerAttackEvent.java @@ -0,0 +1,64 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class PlayerAttackEvent extends PlayerDataEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final EntityDamageByEntityEvent event; + private final DamageInfo info; + + public PlayerAttackEvent(PlayerData data, EntityDamageByEntityEvent event, DamageInfo info) { + super(data); + + this.event = event; + this.info = info; + } + + @Override + public boolean isCancelled() { + return event.isCancelled(); + } + + @Override + public void setCancelled(boolean value) { + event.setCancelled(value); + } + + public DamageInfo getDamageInfo() { + return info; + } + + // @Deprecated + public boolean isWeapon() { + return info.getTypes().contains(DamageType.WEAPON); + } + + public Entity getEntity() { + return event.getEntity(); + } + + public double getDamage() { + return event.getDamage(); + } + + public void setDamage(double value) { + event.setDamage(value); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/api/event/PlayerCastSkillEvent.java b/src/net/Indyuce/mmocore/api/event/PlayerCastSkillEvent.java new file mode 100644 index 00000000..f4265b95 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/PlayerCastSkillEvent.java @@ -0,0 +1,44 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill.SkillInfo; + +public class PlayerCastSkillEvent extends PlayerDataEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final SkillInfo skill; + + private boolean cancelled; + + public PlayerCastSkillEvent(PlayerData playerData, SkillInfo skill) { + super(playerData); + + this.skill = skill; + } + + public SkillInfo getCast() { + return skill; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean value) { + cancelled = value; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/api/event/PlayerChangeClassEvent.java b/src/net/Indyuce/mmocore/api/event/PlayerChangeClassEvent.java new file mode 100644 index 00000000..7b2b5eea --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/PlayerChangeClassEvent.java @@ -0,0 +1,48 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; + +public class PlayerChangeClassEvent extends PlayerDataEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final PlayerClass newClass; + + private boolean cancelled = false; + + public PlayerChangeClassEvent(PlayerData player, PlayerClass newClass) { + super(player); + + this.newClass = newClass; + } + + public PlayerClass getNewClass() { + return newClass; + } + + public boolean isSubclass() { + return getData().getProfess().getSubclasses().stream().filter(sub -> sub.getProfess().equals(newClass)).count() > 0; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean value) { + cancelled = value; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/api/event/PlayerCombatEvent.java b/src/net/Indyuce/mmocore/api/event/PlayerCombatEvent.java new file mode 100644 index 00000000..630647b4 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/PlayerCombatEvent.java @@ -0,0 +1,30 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.api.player.PlayerData; + +public class PlayerCombatEvent extends PlayerDataEvent { + private static final HandlerList handlers = new HandlerList(); + + private final boolean enter; + + public PlayerCombatEvent(PlayerData playerData, boolean enter) { + super(playerData); + + this.enter = enter; + } + + public boolean entersCombat() { + return enter; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/api/event/PlayerDataEvent.java b/src/net/Indyuce/mmocore/api/event/PlayerDataEvent.java new file mode 100644 index 00000000..af5ea96c --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/PlayerDataEvent.java @@ -0,0 +1,19 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.event.player.PlayerEvent; + +import net.Indyuce.mmocore.api.player.PlayerData; + +public abstract class PlayerDataEvent extends PlayerEvent { + private final PlayerData playerData; + + public PlayerDataEvent(PlayerData playerData) { + super(playerData.getPlayer()); + + this.playerData = playerData; + } + + public PlayerData getData() { + return playerData; + } +} diff --git a/src/net/Indyuce/mmocore/api/event/PlayerLevelUpEvent.java b/src/net/Indyuce/mmocore/api/event/PlayerLevelUpEvent.java new file mode 100644 index 00000000..bf6deda0 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/PlayerLevelUpEvent.java @@ -0,0 +1,46 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class PlayerLevelUpEvent extends PlayerDataEvent { + private static final HandlerList handlers = new HandlerList(); + + // if null, this is main level + private final Profession profession; + private final int level; + + public PlayerLevelUpEvent(PlayerData player, int level) { + this(player, null, level); + } + + public PlayerLevelUpEvent(PlayerData player, Profession profession, int level) { + super(player); + + this.profession = profession; + this.level = level; + } + + public int getNewLevel() { + return level; + } + + public boolean hasProfession() { + return profession != null; + } + + public Profession getProfession() { + return profession; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/api/event/social/PartyChatEvent.java b/src/net/Indyuce/mmocore/api/event/social/PartyChatEvent.java new file mode 100644 index 00000000..c6c02bb7 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/event/social/PartyChatEvent.java @@ -0,0 +1,54 @@ +package net.Indyuce.mmocore.api.event.social; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.api.event.PlayerDataEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.social.Party; + +public class PartyChatEvent extends PlayerDataEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final Party party; + + private boolean cancelled; + private String message; + + public PartyChatEvent(PlayerData playerData, String message) { + super(playerData); + this.party = playerData.getParty(); + this.message = message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public Party getParty() { + return party; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/Booster.java b/src/net/Indyuce/mmocore/api/experience/Booster.java new file mode 100644 index 00000000..69670310 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/Booster.java @@ -0,0 +1,70 @@ +package net.Indyuce.mmocore.api.experience; + +import java.util.UUID; + +public class Booster { + private final UUID uuid = UUID.randomUUID(); + private final long date = System.currentTimeMillis(), length; + private final Profession profession; + private final double extra; + private final String author; + + public Booster(double extra, long length) { + this(null, null, extra, length); + } + + public Booster(String author, double extra, long length) { + this(author, null, extra, length); + } + + public Booster(String author, Profession profession, double extra, long length) { + this.author = author; + this.length = length * 1000; + this.profession = profession; + this.extra = extra; + } + + public boolean isTimedOut() { + return date + length < System.currentTimeMillis(); + } + + public long getLeft() { + return Math.max(0, date + length - System.currentTimeMillis()); + } + + public long getCreationDate() { + return date; + } + + public long getLength() { + return length; + } + + public boolean hasProfession() { + return profession != null; + } + + public Profession getProfession() { + return profession; + } + + public UUID getUniqueId() { + return uuid; + } + + public double calculateExp(double exp) { + return exp * (1 + extra); + } + + public double getExtra() { + return extra; + } + + public boolean hasAuthor() { + return author != null; + } + + public String getAuthor() { + return author; + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/ExperienceInfo.java b/src/net/Indyuce/mmocore/api/experience/ExperienceInfo.java new file mode 100644 index 00000000..51cc80b5 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/ExperienceInfo.java @@ -0,0 +1,19 @@ +package net.Indyuce.mmocore.api.experience; + +public class ExperienceInfo { + private final Profession profess; + private final int value; + + public ExperienceInfo(int value, Profession profess) { + this.value = value; + this.profess = profess; + } + + public Profession getProfession() { + return profess; + } + + public int getValue() { + return value; + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/Profession.java b/src/net/Indyuce/mmocore/api/experience/Profession.java new file mode 100644 index 00000000..27e5bf9e --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/Profession.java @@ -0,0 +1,119 @@ +package net.Indyuce.mmocore.api.experience; + +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.potion.PotionType; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.math.formula.LinearValue; + +public class Profession { + private final String id, name; + + private final LinearValue experience; + + /* + * removed when loaded + */ + private FileConfiguration config; + + public Profession(String id, FileConfiguration config) { + this.id = id.toLowerCase().replace("_", "-").replace(" ", "-"); + this.config = config; + + this.name = config.getString("name"); + Validate.notNull(name, "Could not load name"); + + experience = new LinearValue(config.getConfigurationSection("experience")); + + if (config.contains("exp-sources")) + for (String key : config.getStringList("exp-sources")) + try { + MMOCore.plugin.professionManager.registerExpSource(MMOCore.plugin.loadManager.loadExperienceSource(new MMOLineConfig(key), this)); + } catch (MMOLoadException exception) { + exception.printConsole("Professions", "exp source"); + } + } + + /* + * drop tables must be loaded after professions are initialized + */ + public void loadOptions() { + + if (config.contains("on-fish")) + MMOCore.plugin.fishingManager.loadDropTables(config.getConfigurationSection("on-fish")); + + if (config.contains("on-mine")) + MMOCore.plugin.mineManager.loadDropTables(config.getConfigurationSection("on-mine")); + + if (config.contains("alchemy-experience")) { + + MMOCore.plugin.alchemyManager.splash = 1 + config.getDouble("alchemy-experience.special.splash") / 100; + MMOCore.plugin.alchemyManager.lingering = 1 + config.getDouble("alchemy-experience.special.lingering") / 100; + MMOCore.plugin.alchemyManager.extend = 1 + config.getDouble("alchemy-experience.special.extend") / 100; + MMOCore.plugin.alchemyManager.upgrade = 1 + config.getDouble("alchemy-experience.special.upgrade") / 100; + + for (String key : config.getConfigurationSection("alchemy-experience.effects").getKeys(false)) + try { + PotionType type = PotionType.valueOf(key.toUpperCase().replace("-", "_").replace(" ", "_")); + MMOCore.plugin.alchemyManager.registerBaseExperience(type, config.getDouble("alchemy-experience.effects." + key)); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[Professions:" + id + "] Could not read potion type from " + key); + } + } + + if (config.contains("base-enchant-exp")) + for (String key : config.getConfigurationSection("base-enchant-exp").getKeys(false)) + try { + Enchantment enchant = Enchantment.getByKey(NamespacedKey.minecraft(key.toLowerCase().replace("-", "_"))); + MMOCore.plugin.enchantManager.registerBaseExperience(enchant, config.getDouble("base-enchant-exp." + key)); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[Professions:" + id + "] Could not read enchant from " + key); + } + + if (config.contains("repair-exp")) + for (String key : config.getConfigurationSection("repair-exp").getKeys(false)) + try { + Material material = Material.valueOf(key.toUpperCase().replace("-", "_").replace(" ", "_")); + MMOCore.plugin.smithingManager.registerBaseExperience(material, config.getDouble("repair-exp." + key)); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[Professions:" + id + "] Could not read material from " + key); + } + + // if (config.contains("effect-weight")) + // for (String key : + // config.getConfigurationSection("effect-weight").getKeys(false)) + // try { + // MMOCore.plugin.alchemyManager.registerEffectWeight(PotionEffectType.getByName(key.toUpperCase().replace("-", + // "_").replace(" ", "_")), config.getDouble("effect-weight." + key)); + // } catch (IllegalArgumentException exception) { + // MMOCore.log(Level.WARNING, "[Professions:" + id + "] Could not read + // potion effect type from " + key); + // } + + config = null; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public int calculateExperience(int x) { + return (int) experience.calculate(x); + } + + public LinearValue getExperience() { + return experience; + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/BrewPotionExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/BrewPotionExperienceSource.java new file mode 100644 index 00000000..7e47aa64 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/BrewPotionExperienceSource.java @@ -0,0 +1,175 @@ +package net.Indyuce.mmocore.api.experience.source; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionType; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public class BrewPotionExperienceSource extends ExperienceSource { + private final List types = new ArrayList<>(); + + public BrewPotionExperienceSource(Profession profession, MMOLineConfig config) { + super(profession); + + if (config.contains("effect")) + for (String key : config.getString("effect").split("\\,")) + types.add(PotionType.valueOf(key.toUpperCase().replace("-", "_"))); + } + + @Override + public boolean matches(PlayerData player, PotionMeta meta) { + return hasRightClass(player) && (types.isEmpty() || new ArrayList<>(types).stream().filter(type -> meta.getBasePotionData().getType() == type).count() > 0); + } + + @Override + public ExperienceManager newManager() { + return new ExperienceManager() { + + @EventHandler(priority = EventPriority.HIGH) + public void a(BrewEvent event) { + if (event.isCancelled()) + return; + + Optional playerOpt = getNearbyPlayer(event.getBlock().getLocation(), 10); + if (!playerOpt.isPresent()) + return; + + final ItemStack found = findPotion(event.getContents()); + if (found != null) + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> { + ItemStack brewn = findPotion(event.getContents()); + if (brewn == null) + return; + + PlayerData data = PlayerData.get(playerOpt.get()); + for (BrewPotionExperienceSource source : getSources()) + if (source.matches(data, (PotionMeta) brewn.getItemMeta())) + new PotionUpgrade(found, brewn).process(data.getPlayer()); + }); + } + }; + } + + private ItemStack findPotion(BrewerInventory inv) { + for (int j = 0; j < 3; j++) { + ItemStack item = inv.getItem(j); + if (item != null && item.hasItemMeta() && item.getItemMeta() instanceof PotionMeta) + return item; + } + return null; + } + + private Optional getNearbyPlayer(Location loc, double d) { + double d2 = d * d; + return loc.getWorld().getPlayers().stream().filter(player -> player.getLocation().distanceSquared(loc) < d2).findAny(); + } + + public class PotionUpgrade { + + /* + * if the potion was extended using redstone or upgraded using + * glowstone. PREPARE corresponds to when a water bottle is prepared for + * later recipes using NETHER STALK + */ + private double exp; + + // private final PotionMeta old, brewn; + + public PotionUpgrade(ItemStack old, ItemStack brewn) { + this(old.getType(), (PotionMeta) old.getItemMeta(), brewn.getType(), (PotionMeta) brewn.getItemMeta()); + } + + public PotionUpgrade(Material oldPot, PotionMeta old, Material brewnPot, PotionMeta brewn) { + + // this.old = old; + // this.brewn = brewn; + + /* + * calculate base exp + */ + + exp += MMOCore.plugin.alchemyManager.getBaseExperience(brewn.getBasePotionData().getType()); + + // !old.getBasePotionData().getType().isUpgradeable() && + // brewn.getBasePotionData().getType().isUpgradeable(), + // + // !old.getBasePotionData().isExtended() && + // brewn.getBasePotionData().isExtended(), + // + // !old.getBasePotionData().isUpgraded() && + // brewn.getBasePotionData().isUpgraded()); + + /* + * EXP modifiers based on brewing conditions + */ + if (oldPot == Material.POTION && brewnPot == Material.SPLASH_POTION) + exp *= MMOCore.plugin.alchemyManager.splash; + if (oldPot == Material.POTION && brewnPot == Material.LINGERING_POTION) + exp *= MMOCore.plugin.alchemyManager.lingering; + if (!old.getBasePotionData().isExtended() && brewn.getBasePotionData().isExtended()) + exp *= MMOCore.plugin.alchemyManager.extend; + if (!old.getBasePotionData().isUpgraded() && brewn.getBasePotionData().isUpgraded()) + exp *= MMOCore.plugin.alchemyManager.upgrade; + } + + // private Map mapEffectDurations() { + // Map map = new HashMap<>(); + // + // /* + // * potion level, plus the potion gained duration (max 0 so it does + // * not give negative EXP), multiplied by the potion effect weight. + // */ + // brewn.getCustomEffects().forEach(effect -> map.put(effect.getType(), + // + // (effect.getAmplifier() + 1 + (double) Math.max(0, + // effect.getDuration() - getPotionEffect(old, + // effect.getType()).orElseGet(() -> new + // PotionEffect(PotionEffectType.SPEED, 0, 0)).getDuration()) / 60.) + // + // * MMOCore.plugin.alchemyManager.getWeight(effect.getType()))); + // + // return map; + // } + + // private int getTotal(Map map) { + // double t = 0; + // for (double d : map.values()) + // t += d; + // return (int) t; + // } + + // private Optional getPotionEffect(PotionMeta meta, + // PotionEffectType type) { + // return meta.getCustomEffects().stream().filter(effect -> + // effect.getType() == type).findFirst(); + // } + + public void process(Player player) { + + /* + * calculate extra exp due to extra effects + */ + // exp += getTotal(mapEffectDurations()); + + giveExperience(PlayerData.get(player), (int) exp); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/EnchantItemExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/EnchantItemExperienceSource.java new file mode 100644 index 00000000..cab67f94 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/EnchantItemExperienceSource.java @@ -0,0 +1,69 @@ +package net.Indyuce.mmocore.api.experience.source; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.NamespacedKey; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.enchantment.EnchantItemEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public class EnchantItemExperienceSource extends ExperienceSource { + private final List enchants = new ArrayList<>(); + + public EnchantItemExperienceSource(Profession profession, MMOLineConfig config) { + super(profession); + + if (config.contains("enchant")) + for (String key : config.getString("enchant").split("\\,")) + enchants.add(Enchantment.getByKey(NamespacedKey.minecraft(key.toLowerCase().replace("-", "_")))); + } + + @Override + public boolean matches(PlayerData player, Void v) { + return hasRightClass(player); + } + + @Override + public ExperienceManager newManager() { + return new ExperienceManager() { + + @EventHandler(priority = EventPriority.HIGH) + public void a(EnchantItemEvent event) { + if (event.isCancelled()) + return; + + PlayerData player = PlayerData.get(event.getEnchanter()); + for (EnchantItemExperienceSource source : getSources()) + if (source.matches(player, null)) { + Map ench = new HashMap<>(event.getEnchantsToAdd()); + + if (!enchants.isEmpty()) + for (Iterator iterator = ench.keySet().iterator(); iterator.hasNext();) + if (!enchants.contains(iterator.next())) + iterator.remove(); + + if (ench.isEmpty()) + continue; + + double exp = 0; + for (Entry entry : ench.entrySet()) + exp += MMOCore.plugin.enchantManager.getBaseExperience(entry.getKey()) * entry.getValue(); + giveExperience(player, (int) exp); + } + } + }; + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/FishItemExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/FishItemExperienceSource.java new file mode 100644 index 00000000..bb1c62aa --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/FishItemExperienceSource.java @@ -0,0 +1,51 @@ +package net.Indyuce.mmocore.api.experience.source; + +import org.bukkit.Material; +import org.bukkit.entity.Item; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.event.player.PlayerFishEvent.State; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.SpecificExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public class FishItemExperienceSource extends SpecificExperienceSource { + private final Material material; + + public FishItemExperienceSource(Profession profession, MMOLineConfig config) { + super(profession, config); + + config.validate("type"); + material = Material.valueOf(config.getString("type").toUpperCase().replace("-", "_").replace(" ", "_")); + } + + @Override + public ExperienceManager newManager() { + return new ExperienceManager() { + + @EventHandler(priority = EventPriority.HIGH) + public void a(PlayerFishEvent event) { + if (!event.isCancelled() && event.getState() == State.CAUGHT_FISH) { + ItemStack caught = ((Item) event.getCaught()).getItemStack(); + if (caught.hasItemMeta()) + return; + + PlayerData data = PlayerData.get(event.getPlayer()); + for (FishItemExperienceSource source : getSources()) + if (source.matches(data, caught)) + source.giveExperience(data); + } + } + }; + } + + @Override + public boolean matches(PlayerData player, ItemStack obj) { + return hasRightClass(player) && obj.getType() == material; + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/KillMobExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/KillMobExperienceSource.java new file mode 100644 index 00000000..a5031f7a --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/KillMobExperienceSource.java @@ -0,0 +1,45 @@ +package net.Indyuce.mmocore.api.experience.source; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; + +import net.Indyuce.mmocore.api.event.EntityKillEntityEvent; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.SpecificExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public class KillMobExperienceSource extends SpecificExperienceSource { + private final EntityType type; + + public KillMobExperienceSource(Profession profession, MMOLineConfig config) { + super(profession, config); + + config.validate("type"); + type = EntityType.valueOf(config.getString("type").toUpperCase().replace("-", "_").replace(" ", "_")); + } + + @Override + public ExperienceManager newManager() { + return new ExperienceManager() { + + @EventHandler + public void a(EntityKillEntityEvent event) { + if (event.getEntity() instanceof Player) { + PlayerData data = PlayerData.get((Player) event.getEntity()); + for (KillMobExperienceSource source : getSources()) + if (source.matches(data, event.getTarget())) + source.giveExperience(data); + } + } + }; + } + + @Override + public boolean matches(PlayerData player, Entity obj) { + return hasRightClass(player) && obj.getType() == type; + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/MineBlockExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/MineBlockExperienceSource.java new file mode 100644 index 00000000..2415b36f --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/MineBlockExperienceSource.java @@ -0,0 +1,59 @@ +package net.Indyuce.mmocore.api.experience.source; + +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.SpecificExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public class MineBlockExperienceSource extends SpecificExperienceSource { + private final Material material; + private final boolean silkTouch; + + public MineBlockExperienceSource(Profession profession, MMOLineConfig config) { + super(profession, config); + + config.validate("type"); + material = Material.valueOf(config.getString("type").toUpperCase().replace("-", "_").replace(" ", "_")); + silkTouch = config.getBoolean("silk-touch", true); + } + + @Override + public ExperienceManager newManager() { + return new ExperienceManager() { + + @EventHandler(priority = EventPriority.HIGHEST) + public void a(BlockBreakEvent event) { + if (event.isCancelled() || event.getPlayer().getGameMode() != GameMode.SURVIVAL) + return; + + if (silkTouch && hasSilkTouch(event.getPlayer().getInventory().getItemInMainHand())) + return; + + Material broken = event.getBlock().getType(); + + PlayerData data = PlayerData.get(event.getPlayer()); + for (MineBlockExperienceSource source : getSources()) + if (source.matches(data, broken)) + source.giveExperience(data); + } + }; + } + + private boolean hasSilkTouch(ItemStack item) { + return item != null && item.hasItemMeta() && item.getItemMeta().hasEnchant(Enchantment.SILK_TOUCH); + } + + @Override + public boolean matches(PlayerData player, Material obj) { + return material == obj && hasRightClass(player); + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/RepairItemExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/RepairItemExperienceSource.java new file mode 100644 index 00000000..bae98566 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/RepairItemExperienceSource.java @@ -0,0 +1,73 @@ +package net.Indyuce.mmocore.api.experience.source; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public class RepairItemExperienceSource extends ExperienceSource { + private final Material material; + + public RepairItemExperienceSource(Profession profession, MMOLineConfig config) { + super(profession); + + /* + * if material is null, the player can repair ANY material in order to + * get experience. + */ + material = config.contains("type") ? Material.valueOf(config.getString("type").toUpperCase().replace("-", "_").replace(" ", "_")) : null; + } + + @Override + public boolean matches(PlayerData player, ItemStack item) { + return (material == null || item.getType() == material) && hasRightClass(player); + } + + @Override + public ExperienceManager newManager() { + return new ExperienceManager() { + + @EventHandler(priority = EventPriority.HIGH) + public void a(InventoryClickEvent event) { + if (!event.isCancelled() && event.getInventory() != null && event.getInventory().getType() == InventoryType.ANVIL && event.getSlot() == 2) { + + ItemStack item = event.getCurrentItem(); + PlayerData data = PlayerData.get((Player) event.getWhoClicked()); + + for (RepairItemExperienceSource source : getSources()) + if (source.matches(data, item)) { + /* + * make sure the items can actually be repaired + * before getting the amount of durability repaired + */ + ItemStack old = event.getInventory().getItem(0); + if (old.getType().getMaxDurability() < 30 || item.getType().getMaxDurability() < 10) + return; + + if (!MMOCore.plugin.smithingManager.hasExperience(item.getType())) + continue; + + /* + * calculate exp based on amount of durability which + * was repaired, substract damage from old item + * durability. + */ + double exp = MMOCore.plugin.smithingManager.getBaseExperience(item.getType()) * Math.max(0, ((Damageable) old.getItemMeta()).getDamage() - ((Damageable) item.getItemMeta()).getDamage()) / 100; + giveExperience(data, (int) exp); + } + } + } + }; + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/SmeltItemExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/SmeltItemExperienceSource.java new file mode 100644 index 00000000..9023b50f --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/SmeltItemExperienceSource.java @@ -0,0 +1,62 @@ +package net.Indyuce.mmocore.api.experience.source; + +import java.util.Optional; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockCookEvent; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.SpecificExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public class SmeltItemExperienceSource extends SpecificExperienceSource { + private final Material material; + + public SmeltItemExperienceSource(Profession profession, MMOLineConfig config) { + super(profession, config); + + config.validate("type"); + material = Material.valueOf(config.getString("type").toUpperCase().replace("-", "_").replace(" ", "_")); + } + + @Override + public ExperienceManager newManager() { + return new ExperienceManager() { + + @EventHandler(priority = EventPriority.HIGH) + public void a(BlockCookEvent event) { + if (!event.isCancelled()) { + Optional player = getNearbyPlayer(event.getBlock().getLocation(), 10); + if (!player.isPresent()) + return; + + ItemStack caught = event.getResult(); + if (caught.hasItemMeta()) + return; + + PlayerData data = PlayerData.get(player.get()); + for (SmeltItemExperienceSource source : getSources()) + if (source.matches(data, caught)) + source.giveExperience(data); + } + } + }; + } + + private Optional getNearbyPlayer(Location loc, double d) { + double d2 = d * d; + return loc.getWorld().getPlayers().stream().filter(player -> player.getLocation().distanceSquared(loc) < d2).findAny(); + } + + @Override + public boolean matches(PlayerData player, ItemStack obj) { + return obj.getType() == material && hasRightClass(player); + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/type/ExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/type/ExperienceSource.java new file mode 100644 index 00000000..8d686ea3 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/type/ExperienceSource.java @@ -0,0 +1,51 @@ +package net.Indyuce.mmocore.api.experience.source.type; + +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public abstract class ExperienceSource { + private final Profession profession; + private PlayerClass profess; + + public ExperienceSource(Profession profession) { + this(profession, null); + } + + public ExperienceSource(PlayerClass profess) { + this(null, profess); + } + + public ExperienceSource(Profession profession, PlayerClass profess) { + this.profession = profession; + this.profess = profess; + } + + public void setClass(PlayerClass profess) { + this.profess = profess; + } + + public boolean hasRightClass(PlayerData data) { + return profess == null || profess.equals(data.getProfess()); + } + + public boolean hasProfession() { + return profession != null; + } + + public boolean hasClass() { + return profess != null; + } + + public abstract ExperienceManager newManager(); + + public abstract boolean matches(PlayerData player, T obj); + + public void giveExperience(PlayerData player, int amount) { + if (hasProfession()) + player.getCollectionSkills().giveExperience(profession, amount); + else + player.giveExperience(amount); + } +} diff --git a/src/net/Indyuce/mmocore/api/experience/source/type/SpecificExperienceSource.java b/src/net/Indyuce/mmocore/api/experience/source/type/SpecificExperienceSource.java new file mode 100644 index 00000000..fc1b3139 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/experience/source/type/SpecificExperienceSource.java @@ -0,0 +1,33 @@ +package net.Indyuce.mmocore.api.experience.source.type; + +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.math.formula.RandomAmount; +import net.Indyuce.mmocore.api.player.PlayerData; + +public abstract class SpecificExperienceSource extends ExperienceSource { + private final RandomAmount amount; + + /* + * not all experience sources have to specify a random experience amount e.g + * ENCHANT and ALCHEMY experience depend on the enchanted item. + */ + public SpecificExperienceSource(Profession profession, MMOLineConfig config) { + super(profession); + + config.validate("amount"); + amount = new RandomAmount(config.getString("amount")); + } + + public RandomAmount getAmount() { + return amount; + } + + public int rollAmount() { + return amount.calculateInt(); + } + + public void giveExperience(PlayerData player) { + giveExperience(player, rollAmount()); + } +} diff --git a/src/net/Indyuce/mmocore/api/input/AnvilGUI.java b/src/net/Indyuce/mmocore/api/input/AnvilGUI.java new file mode 100644 index 00000000..5e78909a --- /dev/null +++ b/src/net/Indyuce/mmocore/api/input/AnvilGUI.java @@ -0,0 +1,70 @@ +package net.Indyuce.mmocore.api.input; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.util.Consumer; + +import net.Indyuce.mmocore.MMOCore; + +public class AnvilGUI extends PlayerInput { + private final int containerId; + private final Inventory inventory; + + public AnvilGUI(Player player, InputType type, Consumer output) { + super(player, output); + + ItemStack paper = new ItemStack(Material.PAPER); + ItemMeta paperMeta = paper.getItemMeta(); + paperMeta.setDisplayName(MMOCore.plugin.configManager.getSimpleMessage("player-input.anvil." + type.getLowerCaseName())); + paper.setItemMeta(paperMeta); + + MMOCore.plugin.nms.handleInventoryCloseEvent(player); + MMOCore.plugin.nms.setActiveContainerDefault(player); + + final Object container = MMOCore.plugin.nms.newContainerAnvil(player); + + inventory = MMOCore.plugin.nms.toBukkitInventory(container); + inventory.setItem(0, paper); + + containerId = MMOCore.plugin.nms.getNextContainerId(player); + MMOCore.plugin.nms.sendPacketOpenWindow(player, containerId); + MMOCore.plugin.nms.setActiveContainer(player, container); + MMOCore.plugin.nms.setActiveContainerId(container, containerId); + MMOCore.plugin.nms.addActiveContainerSlotListener(container, player); + } + + public void close() { + MMOCore.plugin.nms.handleInventoryCloseEvent(getPlayer()); + MMOCore.plugin.nms.setActiveContainerDefault(getPlayer()); + MMOCore.plugin.nms.sendPacketCloseWindow(getPlayer(), containerId); + + InventoryClickEvent.getHandlerList().unregister(this); + InventoryCloseEvent.getHandlerList().unregister(this); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void a(InventoryClickEvent event) { + if (event.getInventory().equals(inventory)) { + event.setCancelled(true); + + if (event.getRawSlot() == 2) { + ItemStack clicked = inventory.getItem(event.getRawSlot()); + if (clicked != null && clicked.getType() != Material.AIR) + getOutput().accept(clicked.hasItemMeta() ? clicked.getItemMeta().getDisplayName() : clicked.getType().toString()); + } + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void b(InventoryCloseEvent event) { + if (event.getInventory().equals(inventory)) + close(); + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/api/input/ChatInput.java b/src/net/Indyuce/mmocore/api/input/ChatInput.java new file mode 100644 index 00000000..15489e30 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/input/ChatInput.java @@ -0,0 +1,40 @@ +package net.Indyuce.mmocore.api.input; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.util.Consumer; + +import net.Indyuce.mmocore.MMOCore; + +public class ChatInput extends PlayerInput { + public ChatInput(Player player, InputType type, Consumer output) { + super(player, output); + + player.closeInventory(); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("player-input.chat." + type.getLowerCaseName())); + } + + @Override + public void close() { + AsyncPlayerChatEvent.getHandlerList().unregister(this); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void a(AsyncPlayerChatEvent event) { + if (event.getPlayer().equals(getPlayer())) { + close(); + event.setCancelled(true); + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> getOutput().accept(event.getMessage())); + } + } + + @EventHandler + public void b(InventoryOpenEvent event) { + if (event.getPlayer().equals(getPlayer())) + close(); + } +} diff --git a/src/net/Indyuce/mmocore/api/input/PlayerInput.java b/src/net/Indyuce/mmocore/api/input/PlayerInput.java new file mode 100644 index 00000000..8c65cb9d --- /dev/null +++ b/src/net/Indyuce/mmocore/api/input/PlayerInput.java @@ -0,0 +1,40 @@ +package net.Indyuce.mmocore.api.input; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.util.Consumer; + +import net.Indyuce.mmocore.MMOCore; + +public abstract class PlayerInput implements Listener { + private final Player player; + private final Consumer output; + + public PlayerInput(Player player, Consumer output) { + this.player = player; + this.output = output; + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + public Consumer getOutput() { + return output; + } + + public Player getPlayer() { + return player; + } + + public abstract void close(); + + public enum InputType { + FRIEND_REQUEST, + + PARTY_INVITE; + + public String getLowerCaseName() { + return name().toLowerCase().replace("_", "-"); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/item/ConfigItem.java b/src/net/Indyuce/mmocore/api/item/ConfigItem.java new file mode 100644 index 00000000..2bc23a0e --- /dev/null +++ b/src/net/Indyuce/mmocore/api/item/ConfigItem.java @@ -0,0 +1,133 @@ +package net.Indyuce.mmocore.api.item; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class ConfigItem { + private final String name, id, texture; + private final ItemStack item; + private final List lore; + private final int damage; + + private boolean unbreakable; + private Map placeholders = new HashMap<>(); + + public ConfigItem(ConfigurationSection config) { + id = config.getName(); + name = config.getString("name"); + lore = config.getStringList("lore"); + item = new ItemStack(Material.valueOf(config.getString("item"))); + + Validate.notNull(name, "Name cannot be null"); + Validate.notNull(lore, "Lore can be empty but not null"); + + /* + * extra options + */ + damage = config.getInt("damage"); + texture = config.getString("texture"); + } + + public ConfigItem(String id) { + this(MMOCore.plugin.configItems.get(id)); + } + + public ConfigItem(ConfigItem cache) { + this.id = cache.id; + name = cache.name; + lore = cache.lore; + item = cache.item; + damage = cache.damage; + texture = cache.texture; + } + + public ItemStack getItem(int amount) { + ItemStack item = this.item.clone(); + item.setAmount(amount); + return item; + } + + public List getLore() { + return lore; + } + + public String getName() { + return name; + } + + public String getId() { + return id; + } + + public ConfigItem setUnbreakable() { + unbreakable = true; + return this; + } + + public ConfigItem addPlaceholders(String... placeholders) { + for (int j = 0; j < placeholders.length - 1; j += 2) + this.placeholders.put(placeholders[j], placeholders[j + 1]); + return this; + } + + public ItemStack build() { + return build(1); + } + + public ItemStack build(int amount) { + ItemStack item = getItem(amount); + ItemMeta meta = item.getItemMeta(); + + if (meta instanceof Damageable) + ((Damageable) meta).setDamage(damage); + + if (item.getType() == Material.PLAYER_HEAD && texture != null) { + try { + Field profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + GameProfile profile = new GameProfile(UUID.randomUUID(), null); + profile.getProperties().put("textures", new Property("textures", texture)); + profileField.set(meta, profile); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { + MMOCore.log(Level.WARNING, "Could not load config item texture of " + id); + } + } + + meta.addItemFlags(ItemFlag.values()); + meta.setDisplayName(format(name)); + + List lore = new ArrayList<>(); + getLore().forEach(line -> lore.add(format(line))); + meta.setLore(lore); + + item.setItemMeta(meta); + return unbreakable ? NBTItem.get(item).add(new ItemTag("Unbreakable", true)).toItem() : item; + } + + protected String format(String string) { + for (String placeholder : placeholders.keySet()) + if (string.contains("{" + placeholder + "}")) + string = string.replace("{" + placeholder + "}", "" + placeholders.get(placeholder)); + return ChatColor.translateAlternateColorCodes('&', string); + } +} diff --git a/src/net/Indyuce/mmocore/api/item/CurrencyItem.java b/src/net/Indyuce/mmocore/api/item/CurrencyItem.java new file mode 100644 index 00000000..db088eae --- /dev/null +++ b/src/net/Indyuce/mmocore/api/item/CurrencyItem.java @@ -0,0 +1,40 @@ +package net.Indyuce.mmocore.api.item; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class CurrencyItem extends ConfigItem { + private final int worth, amount; + + public CurrencyItem(String key, int worth) { + this(key, worth, 1); + } + + public CurrencyItem(String key, int worth, int amount) { + super(key); + addPlaceholders("worth", "" + (this.worth = worth)); + this.amount = amount; + } + + @Override + public ItemStack build() { + ItemStack item = getItem(amount); + ItemMeta meta = item.getItemMeta(); + + meta.addItemFlags(ItemFlag.values()); + meta.setDisplayName(format(getName())); + + List lore = new ArrayList<>(); + getLore().forEach(line -> lore.add(format(line))); + meta.setLore(lore); + + item.setItemMeta(meta); + return NBTItem.get(item).add(new ItemTag("RpgWorth", worth)).toItem(); + } +} diff --git a/src/net/Indyuce/mmocore/api/item/NBTItem.java b/src/net/Indyuce/mmocore/api/item/NBTItem.java new file mode 100644 index 00000000..f796c26c --- /dev/null +++ b/src/net/Indyuce/mmocore/api/item/NBTItem.java @@ -0,0 +1,48 @@ +package net.Indyuce.mmocore.api.item; + +import java.util.List; +import java.util.Set; + +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public abstract class NBTItem { + protected ItemStack item; + + public NBTItem(ItemStack item) { + this.item = item; + } + + public ItemStack getItem() { + return item; + } + + public abstract String getString(String path); + + public abstract boolean has(String path); + + public abstract boolean getBoolean(String path); + + public abstract double getDouble(String path); + + public abstract int getInt(String path); + + public abstract NBTItem add(ItemTag... tags); + + public abstract NBTItem remove(String... paths); + + public abstract Set getTags(); + + public abstract ItemStack toItem(); + + public void add(List tags) { + for (ItemTag tag : tags) + add(tag); + } + + public static NBTItem get(ItemStack item) { + return MMOCore.plugin.nms.getNBTItem(item); + } +} diff --git a/src/net/Indyuce/mmocore/api/item/NamedItemStack.java b/src/net/Indyuce/mmocore/api/item/NamedItemStack.java new file mode 100644 index 00000000..55a903a1 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/item/NamedItemStack.java @@ -0,0 +1,16 @@ +package net.Indyuce.mmocore.api.item; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public class NamedItemStack extends ItemStack { + public NamedItemStack(Material material, String name) { + super(material); + + ItemMeta meta = getItemMeta(); + meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name)); + setItemMeta(meta); + } +} diff --git a/src/net/Indyuce/mmocore/api/item/SmartGive.java b/src/net/Indyuce/mmocore/api/item/SmartGive.java new file mode 100644 index 00000000..3f4be946 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/item/SmartGive.java @@ -0,0 +1,32 @@ +package net.Indyuce.mmocore.api.item; + +import java.util.List; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +public class SmartGive { + private final Inventory inv; + private final Location loc; + + public SmartGive(Player player) { + inv = player.getInventory(); + loc = player.getLocation(); + } + + /* + * either give directly the item to the player or drops it on the ground if + * there is not enough space in the player inventory. + */ + public void give(ItemStack... item) { + for (ItemStack drop : inv.addItem(item).values()) + loc.getWorld().dropItem(loc, drop); + } + + public void give(List item) { + for (ItemStack drop : inv.addItem(item.toArray(new ItemStack[item.size()])).values()) + loc.getWorld().dropItem(loc, drop); + } +} diff --git a/src/net/Indyuce/mmocore/api/load/DefaultMMOLoader.java b/src/net/Indyuce/mmocore/api/load/DefaultMMOLoader.java new file mode 100644 index 00000000..fabc2213 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/load/DefaultMMOLoader.java @@ -0,0 +1,130 @@ +package net.Indyuce.mmocore.api.load; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.condition.WorldCondition; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.droptable.dropitem.DropTableDropItem; +import net.Indyuce.mmocore.api.droptable.dropitem.GoldDropItem; +import net.Indyuce.mmocore.api.droptable.dropitem.NoteDropItem; +import net.Indyuce.mmocore.api.droptable.dropitem.VanillaDropItem; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.BrewPotionExperienceSource; +import net.Indyuce.mmocore.api.experience.source.EnchantItemExperienceSource; +import net.Indyuce.mmocore.api.experience.source.FishItemExperienceSource; +import net.Indyuce.mmocore.api.experience.source.KillMobExperienceSource; +import net.Indyuce.mmocore.api.experience.source.MineBlockExperienceSource; +import net.Indyuce.mmocore.api.experience.source.RepairItemExperienceSource; +import net.Indyuce.mmocore.api.experience.source.SmeltItemExperienceSource; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.quest.objective.ClickonObjective; +import net.Indyuce.mmocore.api.quest.objective.GoToObjective; +import net.Indyuce.mmocore.api.quest.objective.KillMobObjective; +import net.Indyuce.mmocore.api.quest.objective.MineBlockObjective; +import net.Indyuce.mmocore.api.quest.objective.Objective; +import net.Indyuce.mmocore.api.quest.trigger.CommandTrigger; +import net.Indyuce.mmocore.api.quest.trigger.ExperienceTrigger; +import net.Indyuce.mmocore.api.quest.trigger.ItemTrigger; +import net.Indyuce.mmocore.api.quest.trigger.ManaTrigger; +import net.Indyuce.mmocore.api.quest.trigger.MessageTrigger; +import net.Indyuce.mmocore.api.quest.trigger.SoundTrigger; +import net.Indyuce.mmocore.api.quest.trigger.StelliumTrigger; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class DefaultMMOLoader implements MMOLoader { + + @Override + public Trigger loadTrigger(MMOLineConfig config) { + if (config.getKey().equals("message")) + return new MessageTrigger(config); + + if (config.getKey().equals("sound") || config.getKey().equals("playsound")) + return new SoundTrigger(config); + + if (config.getKey().equals("mana")) + return new ManaTrigger(config); + + if (config.getKey().equals("stellium")) + return new StelliumTrigger(config); + + if (config.getKey().equals("command")) + return new CommandTrigger(config); + + if (config.getKey().equals("item") || config.getKey().equals("vanilla")) + return new ItemTrigger(config); + + if (config.getKey().equals("exp") || config.getKey().equals("experience")) + return new ExperienceTrigger(config); + + return null; + } + + @Override + public DropItem loadDropItem(MMOLineConfig config) { + if (config.getKey().equals("droptable")) + return new DropTableDropItem(config); + + if (config.getKey().equals("vanilla")) + return new VanillaDropItem(config); + + if (config.getKey().equals("note")) + return new NoteDropItem(config); + + if (config.getKey().equals("gold") || config.getKey().equals("coin")) + return new GoldDropItem(config); + + return null; + } + + @Override + public Objective loadObjective(MMOLineConfig config, ConfigurationSection section) { + if (config.getKey().equals("goto")) + return new GoToObjective(section, config); + + if (config.getKey().equals("mine")) + return new MineBlockObjective(section, config); + + if (config.getKey().equals("kill")) + return new KillMobObjective(section, config); + + if (config.getKey().equals("clickon")) + return new ClickonObjective(section, config); + + return null; + } + + @Override + public Condition loadCondition(MMOLineConfig config) { + if (config.getKey().equals("world")) + return new WorldCondition(config); + + return null; + } + + @Override + public ExperienceSource loadExperienceSource(MMOLineConfig config, Profession profession) { + if (config.getKey().equals("fishitem")) + return new FishItemExperienceSource(profession, config); + + if (config.getKey().equals("killmob")) + return new KillMobExperienceSource(profession, config); + + if (config.getKey().equals("mineblock")) + return new MineBlockExperienceSource(profession, config); + + if (config.getKey().equals("brewpotion")) + return new BrewPotionExperienceSource(profession, config); + + if (config.getKey().equals("smeltitem")) + return new SmeltItemExperienceSource(profession, config); + + if (config.getKey().equals("enchantitem")) + return new EnchantItemExperienceSource(profession, config); + + if (config.getKey().equals("repairitem")) + return new RepairItemExperienceSource(profession, config); + + return null; + } +} diff --git a/src/net/Indyuce/mmocore/api/load/MMOLineConfig.java b/src/net/Indyuce/mmocore/api/load/MMOLineConfig.java new file mode 100644 index 00000000..25ca1596 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/load/MMOLineConfig.java @@ -0,0 +1,96 @@ +package net.Indyuce.mmocore.api.load; + +import org.apache.commons.lang.Validate; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; + +public class MMOLineConfig { + private final String key, value; + private final String[] args; + private JsonObject json; + + public MMOLineConfig(String value) { + this.value = value; + + /* + * if there is no config, no need to parse the json object. split, + * define key and find arg + */ + if (!value.contains("{") || !value.contains("}")) { + String[] split = value.split("\\ "); + key = split[0]; + args = split.length > 1 ? value.replace(key + " ", "").split("\\ ") : new String[0]; + return; + } + + /* + * load json and extra args + */ + int begin = value.indexOf("{"), end = value.lastIndexOf("}") + 1; + key = value.substring(0, begin); + + try { + json = new JsonParser().parse(value.substring(begin, end)).getAsJsonObject(); + } catch (JsonParseException exception) { + throw new IllegalArgumentException("Could not load config"); + } + + String format = value.substring(Math.min(value.length(), end + 1)); + args = format.isEmpty() ? new String[0] : format.split("\\ "); + } + + /* + * extra arguments outside the config brackets + */ + public String[] args() { + return args; + } + + public String getKey() { + return key; + } + + public String getString(String path) { + return json.get(path).getAsString(); + } + + public double getDouble(String path) { + return json.get(path).getAsDouble(); + } + + public int getInt(String path) { + return json.get(path).getAsInt(); + } + + public long getLong(String path) { + return json.get(path).getAsLong(); + } + + public boolean getBoolean(String path) { + return json.get(path).getAsBoolean(); + } + + public boolean getBoolean(String path, boolean def) { + return json.has(path) ? getBoolean(path) : def; + } + + public boolean contains(String path) { + return json.has(path); + } + + public void validate(String... paths) { + for (String path : paths) + Validate.isTrue(contains(path), "Config is missing parameter '" + path+"'"); + } + + public void validateArgs(int count) { + Validate.isTrue(args.length >= count, "Config must have at least " + count + " parameters"); + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/net/Indyuce/mmocore/api/load/MMOLoadException.java b/src/net/Indyuce/mmocore/api/load/MMOLoadException.java new file mode 100644 index 00000000..b9d9a9e4 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/load/MMOLoadException.java @@ -0,0 +1,29 @@ +package net.Indyuce.mmocore.api.load; + +import java.util.logging.Level; + +import net.Indyuce.mmocore.MMOCore; + +public class MMOLoadException extends IllegalArgumentException { + private static final long serialVersionUID = -8839506644440697800L; + + private MMOLineConfig config; + + public MMOLoadException(String message) { + super(message); + } + + public MMOLoadException(MMOLineConfig config, Exception exception) { + super(exception.getMessage()); + + this.config = config; + } + + public boolean hasConfig() { + return config != null; + } + + public void printConsole(String prefix, String obj) { + MMOCore.plugin.getLogger().log(Level.WARNING, "[" + prefix + "] " + (hasConfig() ? "Could not load " + obj + " '" + config.toString() + "': " : "") + getMessage()); + } +} diff --git a/src/net/Indyuce/mmocore/api/load/MMOLoader.java b/src/net/Indyuce/mmocore/api/load/MMOLoader.java new file mode 100644 index 00000000..4d981802 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/load/MMOLoader.java @@ -0,0 +1,22 @@ +package net.Indyuce.mmocore.api.load; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.quest.objective.Objective; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public interface MMOLoader { + public Condition loadCondition(MMOLineConfig config); + + public Trigger loadTrigger(MMOLineConfig config); + + public DropItem loadDropItem(MMOLineConfig config); + + public Objective loadObjective(MMOLineConfig config, ConfigurationSection section); + + public ExperienceSource loadExperienceSource(MMOLineConfig config, Profession profession); +} diff --git a/src/net/Indyuce/mmocore/api/math/Line3D.java b/src/net/Indyuce/mmocore/api/math/Line3D.java new file mode 100644 index 00000000..3275b2b4 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/Line3D.java @@ -0,0 +1,63 @@ +package net.Indyuce.mmocore.api.math; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.util.Vector; + +public class Line3D { + + private final Vector dir; + private final Vector pt; + + public Line3D(Vector loc1, Vector loc2) { + pt = loc1.clone(); + dir = loc1.clone().subtract(loc2); + } + + public Line3D(Location pt, Vector dir) { + this.dir = dir.clone(); + this.pt = pt.toVector(); + } + + public Line3D(double a, double b, double c, double e, double f, double g) { + pt = new Vector(a, b, c); + dir = new Vector(e, f, g); + } + + public double distanceSquared(Entity entity) { + return distanceSquared(entity.getLocation().add(0, entity.getHeight() / 2, 0).toVector()); + } + + public double distanceSquared(Vector loc) { + + // dir = (alpha ; beta ; gamma) + // LINE : (x ; y ; z) = (a ; b ; c) + t * dir + // loc = (e ; f ; g) + + // therefore, distance vector is: + // ( a + t * alpha - e ; b + t * beta - f ; c + t * gamma - g ) + + // length of vector is: + // d = sqrt( (a-e) + (b-f) + (c-g) + 2t( alpha(a-e) + beta(b-f) + + // gamma(c-g) ) + t(alpha + beta + gamma) ) + + // analysis: we find the value of t for which the distance d is the + // smallest. (canonical form) axis of symetry is min = -b/2a therefore + + // we don't care about calculating the 0 degree constant of polynomial + + double a = dir.lengthSquared(); + double b = 2 * (dir.getX() * (pt.getX() - loc.getX()) + dir.getY() * (pt.getY() - loc.getY()) + dir.getZ() * (pt.getZ() - loc.getZ())); + double min = -b / (2 * a); + + return loc.distanceSquared(getPoint(min)); + } + + public double distance(Vector loc) { + return Math.sqrt(distanceSquared(loc)); + } + + public Vector getPoint(double t) { + return pt.clone().add(dir.clone().multiply(t)); + } +} diff --git a/src/net/Indyuce/mmocore/api/math/VectorRotation.java b/src/net/Indyuce/mmocore/api/math/VectorRotation.java new file mode 100644 index 00000000..e919714c --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/VectorRotation.java @@ -0,0 +1,37 @@ +package net.Indyuce.mmocore.api.math; + +import org.bukkit.Location; +import org.bukkit.util.Vector; + +public class VectorRotation { + private final float yaw, pitch; + + public VectorRotation(Location source) { + this.yaw = source.getYaw(); + this.pitch = source.getPitch(); + } + + public Vector rotate(Vector vec) { + vec = rotateX(vec, this.pitch / 180 * Math.PI); + vec = rotateY(vec, -this.yaw / 180 * Math.PI); + return vec; + } + + private Vector rotateX(Vector vec, double a) { + double y = vec.getY() * Math.cos(a) - vec.getZ() * Math.sin(a); + double z = vec.getY() * Math.sin(a) + vec.getZ() * Math.cos(a); + return vec.setY(y).setZ(z); + } + + private Vector rotateY(Vector vec, double b) { + double x = vec.getX() * Math.cos(b) + vec.getZ() * Math.sin(b); + double z = vec.getX() * -Math.sin(b) + vec.getZ() * Math.cos(b); + return vec.setX(x).setZ(z); + } + + // private Vector rotateZ(Vector vec, double c) { + // double x = vec.getX() * Math.cos(c) - vec.getY() * Math.sin(c); + // double y = vec.getX() * Math.sin(c) + vec.getY() * Math.cos(c); + // return vec.setX(x).setY(y); + // } +} diff --git a/src/net/Indyuce/mmocore/api/math/format/DelayFormat.java b/src/net/Indyuce/mmocore/api/math/format/DelayFormat.java new file mode 100644 index 00000000..ae1a3e52 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/format/DelayFormat.java @@ -0,0 +1,29 @@ +package net.Indyuce.mmocore.api.math.format; + +public class DelayFormat { + private int display = charArray.length; + + private static final long[] millisArray = { 31557081600l, 2629756800l, 86400000l, 3600000l, 60000l, 1000l }; + private static final String[] charArray = { "y", "M", "d", "h", "m", "s" }; + + public DelayFormat() { + this(charArray.length); + } + + public DelayFormat(int display) { + this.display = Math.min(display, charArray.length); + } + + public String format(long ms) { + String format = ""; + + for (int j = 0; j < charArray.length && display > 0; j++) + if (ms > millisArray[j]) { + format += (ms / millisArray[j]) + charArray[j] + " "; + ms = ms % millisArray[j]; + display--; + } + + return format.equals("") ? "Now!" : format.substring(0, format.length() - 1); + } +} diff --git a/src/net/Indyuce/mmocore/api/math/format/RomanFormat.java b/src/net/Indyuce/mmocore/api/math/format/RomanFormat.java new file mode 100644 index 00000000..29c28676 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/format/RomanFormat.java @@ -0,0 +1,9 @@ +package net.Indyuce.mmocore.api.math.format; + +public class RomanFormat { + + public RomanFormat() { + // TODO Auto-generated constructor stub + } + +} diff --git a/src/net/Indyuce/mmocore/api/math/formula/IntegerLinearValue.java b/src/net/Indyuce/mmocore/api/math/formula/IntegerLinearValue.java new file mode 100644 index 00000000..ebb28d62 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/formula/IntegerLinearValue.java @@ -0,0 +1,26 @@ +package net.Indyuce.mmocore.api.math.formula; + +import org.bukkit.configuration.ConfigurationSection; + +public class IntegerLinearValue extends LinearValue { + public IntegerLinearValue(double base, double perLevel) { + super(base, perLevel); + } + + public IntegerLinearValue(double base, double perLevel, double min, double max) { + super(base, perLevel, min, max); + } + + public IntegerLinearValue(IntegerLinearValue value) { + super(value); + } + + public IntegerLinearValue(ConfigurationSection config) { + super(config); + } + + @Override + public String getDisplay(int level) { + return "" + (int) calculate(level); + } +} diff --git a/src/net/Indyuce/mmocore/api/math/formula/LinearValue.java b/src/net/Indyuce/mmocore/api/math/formula/LinearValue.java new file mode 100644 index 00000000..7a4c47e9 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/formula/LinearValue.java @@ -0,0 +1,93 @@ +package net.Indyuce.mmocore.api.math.formula; + +import java.text.DecimalFormat; + +import org.bukkit.configuration.ConfigurationSection; + +public class LinearValue { + private final double base, perLevel, min, max; + private final boolean hasmin, hasmax; + + private static final DecimalFormat format = new DecimalFormat("0.###"); + + /* + * a number which depends on the player level. it can be used as a skill + * modifier to make the ability better depending on the player level or as + * an attrribute value to make attributes increase with the player level + */ + public LinearValue(double base, double perLevel) { + this.base = base; + this.perLevel = perLevel; + hasmin = false; + hasmax = false; + min = 0; + max = 0; + } + + public LinearValue(double base, double perLevel, double min, double max) { + this.base = base; + this.perLevel = perLevel; + hasmin = true; + hasmax = true; + this.min = min; + this.max = max; + } + + public LinearValue(LinearValue value) { + base = value.base; + perLevel = value.perLevel; + hasmin = value.hasmin; + hasmax = value.hasmax; + min = value.min; + max = value.max; + } + + public LinearValue(ConfigurationSection config) { + base = config.getDouble("base"); + perLevel = config.getDouble("per-level"); + hasmin = config.contains("min"); + hasmax = config.contains("max"); + min = hasmin ? config.getDouble("min") : 0; + max = hasmax ? config.getDouble("max") : 0; + } + + public double getBaseValue() { + return base; + } + + public double getPerLevel() { + return perLevel; + } + + public double getMax() { + return max; + } + + public double getMin() { + return min; + } + + public boolean hasMax() { + return hasmax; + } + + public boolean hasMin() { + return hasmin; + } + + public String getDisplay(int level) { + return format.format(calculate(level)); + } + + public double calculate(int level) { + double value = base + perLevel * (level - 1); + + if (hasmin) + value = Math.max(min, value); + + if (hasmax) + value = Math.min(max, value); + + return value; + } +} diff --git a/src/net/Indyuce/mmocore/api/math/formula/RandomAmount.java b/src/net/Indyuce/mmocore/api/math/formula/RandomAmount.java new file mode 100644 index 00000000..b7c4ea3c --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/formula/RandomAmount.java @@ -0,0 +1,37 @@ +package net.Indyuce.mmocore.api.math.formula; + +import java.util.Random; + +public class RandomAmount { + private double min, max; + + private static final Random random = new Random(); + + public RandomAmount(double min, double max) { + this.min = min; + this.max = max; + } + + public RandomAmount(String value) { + String[] split = value.split("\\-"); + min = Double.parseDouble(split[0]); + if (split.length > 1) + max = Double.parseDouble(split[1]); + } + + public double getMax() { + return max; + } + + public double getMin() { + return min; + } + + public double calculate() { + return max > 0 ? random.nextDouble() * Math.abs((max - min)) + Math.min(max, min) : min; + } + + public int calculateInt() { + return (int) (max > 0 ? (random.nextInt((int) (max - min + 1)) + min) : min); + } +} diff --git a/src/net/Indyuce/mmocore/api/math/particle/CastingParticle.java b/src/net/Indyuce/mmocore/api/math/particle/CastingParticle.java new file mode 100644 index 00000000..cba91d49 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/particle/CastingParticle.java @@ -0,0 +1,49 @@ +package net.Indyuce.mmocore.api.math.particle; + +import java.util.function.Consumer; + +import org.apache.commons.lang.Validate; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.configuration.ConfigurationSection; + +public class CastingParticle { + private final Consumer display; + + public CastingParticle(ConfigurationSection config) { + Validate.notNull(config, "Casting particle config cannot be null"); + + String format = config.getString("particle"); + Validate.notNull(format, "Could not read particle name"); + Particle particle = Particle.valueOf(format.toUpperCase().replace("-", "_").replace(" ", "_")); + + if (config.contains("color")) { + final float size = (float) config.getDouble("size") == 0 ? 1 : (float) Math.max(config.getDouble("size"), 0); + Color color = Color.fromRGB(config.getInt("color.red"), config.getInt("color.green"), config.getInt("color.blue")); + + display = (loc) -> loc.getWorld().spawnParticle(particle, loc, 0, new Particle.DustOptions(color, size)); + return; + } + + if (config.contains("material")) { + format = config.getString("material"); + Validate.notNull(format, "Could not read material name"); + Material material = Material.valueOf(format.toUpperCase().replace("-", "_").replace(" ", "_")); + + display = (loc) -> loc.getWorld().spawnParticle(particle, loc, 0, material.createBlockData()); + return; + } + + display = (loc) -> loc.getWorld().spawnParticle(particle, loc, 0); + } + + public CastingParticle(Particle particle) { + display = (loc) -> loc.getWorld().spawnParticle(particle, loc, 0); + } + + public void display(Location loc) { + display.accept(loc); + } +} diff --git a/src/net/Indyuce/mmocore/api/math/particle/ChestParticleEffect.java b/src/net/Indyuce/mmocore/api/math/particle/ChestParticleEffect.java new file mode 100644 index 00000000..cb96aa61 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/particle/ChestParticleEffect.java @@ -0,0 +1,61 @@ +package net.Indyuce.mmocore.api.math.particle; + +import java.util.function.BiConsumer; + +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; + +public enum ChestParticleEffect { + + HELIX((loc, particle) -> { + new BukkitRunnable() { + double ti = 0; + + public void run() { + if ((ti += Math.PI / 16) > Math.PI * 2) + cancel(); + for (double j = 0; j < Math.PI * 2; j += Math.PI * 2 / 5) + loc.getWorld().spawnParticle(particle, loc.clone().add(Math.cos(j + ti / 2), -.5 + ti / Math.PI / 2, Math.sin(j + ti / 2)), 0); + } + }.runTaskTimer(MMOCore.plugin, 0, 1); + }), + + OFFSET((loc, particle) -> { + new BukkitRunnable() { + int ti = 0; + + public void run() { + if (ti++ > 20) + cancel(); + for (double j = 0; j < Math.PI * 2; j += Math.PI * 2 / 5) + loc.getWorld().spawnParticle(particle, loc.clone(), 1, .5, .5, .5, 0); + } + }.runTaskTimer(MMOCore.plugin, 0, 1); + }), + + GALAXY((loc, particle) -> { + new BukkitRunnable() { + double ti = 0; + + public void run() { + if ((ti += Math.PI / 16) > Math.PI * 2) + cancel(); + for (double j = 0; j < Math.PI * 2; j += Math.PI * 2 / 5) + loc.getWorld().spawnParticle(particle, loc.clone().add(0, -.1, 0), 0, Math.cos(j + ti / 2), 0, Math.sin(j + ti / 2), .13); + } + }.runTaskTimer(MMOCore.plugin, 0, 1); + }); + + private BiConsumer func; + + private ChestParticleEffect(BiConsumer func) { + this.func = func; + } + + public void play(Location loc, Particle particle) { + func.accept(loc, particle); + } +} diff --git a/src/net/Indyuce/mmocore/api/math/particle/ParabolicProjectile.java b/src/net/Indyuce/mmocore/api/math/particle/ParabolicProjectile.java new file mode 100644 index 00000000..76d5cbb8 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/particle/ParabolicProjectile.java @@ -0,0 +1,75 @@ +package net.Indyuce.mmocore.api.math.particle; + +import java.util.function.Consumer; + +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; + +public class ParabolicProjectile extends BukkitRunnable { + private final Location target; + private final Consumer display; + private final Vector vec; + private final Runnable end; + private final int speed; + + // calculation + private Location loc; + private int j; + + // private static final Random random = new Random(); + + public ParabolicProjectile(Location source, Location target, Color color) { + this(source, target, Particle.REDSTONE, () -> { + }, 1, color, 1); + } + + public ParabolicProjectile(Location source, Location target, Particle particle, Runnable end, int speed, Color color, float size) { + this(source, target, target.clone().subtract(source).toVector().multiply(.1).setY(6).normalize().multiply(.3), end, speed, (loc) -> loc.getWorld().spawnParticle(particle, loc, 1, new Particle.DustOptions(color, size))); + } + + public ParabolicProjectile(Location source, Location target, Runnable end, Color color) { + this(source, target, target.clone().subtract(source).toVector().multiply(.1).setY(6).normalize().multiply(.3), end, 1, (loc) -> loc.getWorld().spawnParticle(Particle.REDSTONE, loc, 1, new Particle.DustOptions(color, 1))); + } + + public ParabolicProjectile(Location source, Location target, Particle particle) { + this(source, target, target.clone().subtract(source).toVector().multiply(.1).setY(6).normalize().multiply(.3), () -> { + }, 1, (loc) -> loc.getWorld().spawnParticle(particle, loc, 0)); + } + + public ParabolicProjectile(Location source, Location target, Runnable end, int speed, Particle particle) { + this(source, target, target.clone().subtract(source).toVector().multiply(.1).setY(6).normalize().multiply(.3), end, speed, (loc) -> loc.getWorld().spawnParticle(particle, loc, 0)); + } + + public ParabolicProjectile(Location source, Location target, Vector vec, Runnable end, int speed, Particle particle) { + this(source, target, vec, end, speed, (loc) -> loc.getWorld().spawnParticle(particle, loc, 0)); + } + + private ParabolicProjectile(Location source, Location target, Vector vec, Runnable end, int speed, Consumer display) { + loc = source.clone(); + this.target = target; + this.display = display; + this.end = end; + this.vec = vec; + this.speed = Math.max(1, speed); + + runTaskTimer(MMOCore.plugin, 0, 1); + } + + @Override + public void run() { + for (int k = 0; k < speed; k++) { + if (j++ > 100 || loc.distanceSquared(target) < .8) { + end.run(); + cancel(); + } + + double c = Math.min(1, (double) j / 40); + display.accept(loc.add(target.clone().subtract(loc).toVector().normalize().multiply(c).add(vec.clone().multiply(1 - c)))); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/math/particle/PotionParticles.java b/src/net/Indyuce/mmocore/api/math/particle/PotionParticles.java new file mode 100644 index 00000000..8d049b62 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/particle/PotionParticles.java @@ -0,0 +1,49 @@ +package net.Indyuce.mmocore.api.math.particle; + +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; + +public class PotionParticles extends BukkitRunnable { + private double r, g, b; + private ThrownPotion potion; + private boolean valid = true; + + public PotionParticles(ThrownPotion potion) { + this.potion = potion; + + Color color = ((PotionMeta) potion.getItem().getItemMeta()).getColor(); + if (color == null) { + valid = false; + return; + } + + r = Math.max((double) 1 / 255, ratio(color.getRed())); + g = ratio(color.getGreen()); + b = ratio(color.getBlue()); + + } + + public void start() { + if (valid) + runTaskTimer(MMOCore.plugin, 0, 1); + } + + private double ratio(int l) { + return (double) l / 255.; + } + + @Override + public void run() { + if (potion == null || potion.isDead()) { + cancel(); + return; + } + + potion.getWorld().spawnParticle(Particle.SPELL_MOB, potion.getLocation(), 0, r, g, b); + } +} diff --git a/src/net/Indyuce/mmocore/api/math/particle/SmallParticleEffect.java b/src/net/Indyuce/mmocore/api/math/particle/SmallParticleEffect.java new file mode 100644 index 00000000..77b85ed7 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/math/particle/SmallParticleEffect.java @@ -0,0 +1,38 @@ +package net.Indyuce.mmocore.api.math.particle; + +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Entity; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; + +public class SmallParticleEffect extends BukkitRunnable { + private final Location loc; + private final Particle particle; + private final double r; + + private double t; + + public SmallParticleEffect(Entity entity, Particle particle) { + this(entity, particle, .7); + } + + public SmallParticleEffect(Entity entity, Particle particle, double r) { + this.loc = entity.getLocation().add(0, .5, 0); + this.particle = particle; + this.r = r; + + runTaskTimer(MMOCore.plugin, 0, 1); + } + + public void run() { + if (t > Math.PI * 2) + cancel(); + + for (int k = 0; k < 3; k++) { + t += Math.PI / 10; + loc.getWorld().spawnParticle(particle, loc.clone().add(r * Math.cos(t), t / Math.PI / 2, r * Math.sin(t)), 0); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/player/CombatRunnable.java b/src/net/Indyuce/mmocore/api/player/CombatRunnable.java new file mode 100644 index 00000000..89368845 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/CombatRunnable.java @@ -0,0 +1,39 @@ +package net.Indyuce.mmocore.api.player; + +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.PlayerCombatEvent; + +public class CombatRunnable extends BukkitRunnable { + private final PlayerData player; + + private long lastHit = System.currentTimeMillis(); + + public CombatRunnable(PlayerData player) { + this.player = player; + + player.getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("now-in-combat")); + Bukkit.getPluginManager().callEvent(new PlayerCombatEvent(player, true)); + runTaskTimer(MMOCore.plugin, 20, 20); + } + + public void update() { + lastHit = System.currentTimeMillis(); + } + + @Override + public void run() { + if (lastHit + 10000 < System.currentTimeMillis()) { + Bukkit.getPluginManager().callEvent(new PlayerCombatEvent(player, false)); + player.getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("leave-combat")); + close(); + } + } + + private void close() { + player.combat = null; + cancel(); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/OfflinePlayerData.java b/src/net/Indyuce/mmocore/api/player/OfflinePlayerData.java new file mode 100644 index 00000000..d880a673 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/OfflinePlayerData.java @@ -0,0 +1,40 @@ +package net.Indyuce.mmocore.api.player; + +import java.util.List; +import java.util.UUID; + +import net.Indyuce.mmocore.api.ConfigFile; + +public class OfflinePlayerData { + private final UUID uuid; + private final PlayerData data; + + /* + * supports offline player data operations like friend removals which can't + * be handled when their player data is not loaded in the data map. + */ + public OfflinePlayerData(UUID uuid) { + data = PlayerData.isLoaded(this.uuid = uuid) ? PlayerData.get(uuid) : null; + } + + public boolean isLoaded() { + return data != null; + } + + public void removeFriend(UUID uuid) { + if (isLoaded()) { + data.removeFriend(uuid); + return; + } + + ConfigFile config = new ConfigFile(this.uuid); + List friends = config.getConfig().getStringList("friends"); + friends.remove(uuid.toString()); + config.getConfig().set("friends", friends); + config.save(); + } + + public boolean hasFriend(UUID uuid) { + return isLoaded() ? data.hasFriend(uuid) : new ConfigFile(this.uuid).getConfig().getStringList("friends").contains(uuid.toString()); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/PlayerData.java b/src/net/Indyuce/mmocore/api/player/PlayerData.java new file mode 100644 index 00000000..940195cd --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/PlayerData.java @@ -0,0 +1,712 @@ +package net.Indyuce.mmocore.api.player; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.attribute.Attribute; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.api.ConfigMessage; +import net.Indyuce.mmocore.api.Waypoint; +import net.Indyuce.mmocore.api.event.PlayerCastSkillEvent; +import net.Indyuce.mmocore.api.event.PlayerLevelUpEvent; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.attribute.PlayerAttributes; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.api.player.profess.PlayerClass.Subclass; +import net.Indyuce.mmocore.api.player.profess.SavedClassInformation; +import net.Indyuce.mmocore.api.player.profess.resource.PlayerResource; +import net.Indyuce.mmocore.api.player.social.FriendRequest; +import net.Indyuce.mmocore.api.player.social.Party; +import net.Indyuce.mmocore.api.player.stats.PlayerStats; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.Skill.SkillInfo; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.SkillResult.CancelReason; +import net.Indyuce.mmocore.listener.SpellCast.SkillCasting; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.TextComponent; + +public class PlayerData { + + private final UUID uuid; + + /* + * is updated everytime the player joins the server. it is kept when the + * player is offline so the plugin can use #isOnline to check if the player + * is online + */ + private Player player; + + private PlayerClass profess; + private final Map classSlots = new HashMap<>(); + private int level, experience, classPoints, skillPoints, attributePoints, attributeReallocationPoints;// skillReallocationPoints, + private double mana, stamina, stellium; + private final Professions collectSkills = new Professions(this); + private final PlayerQuests questData; + private final Set waypoints = new HashSet<>(); + private List friends; + private Party party; + private final List boundSkills = new ArrayList<>(); + private final PlayerAttributes attributes = new PlayerAttributes(this); + + private final PlayerStats playerStats = new PlayerStats(this); + private long lastWaypoint, lastLogin, lastFriendRequest, lastActionbarUpdate; + + private final Map skills = new HashMap<>(); + private final PlayerSkillData skillData = new PlayerSkillData(this); + + /* + * NON-FINAL player data stuff made public to facilitate field change + */ + public int skillGuiDisplayOffset; + public SkillCasting skillCasting; + public boolean nocd; + public CombatRunnable combat; + + private static Map playerData = new HashMap<>(); + + private PlayerData(Player player) { + uuid = player.getUniqueId(); + setPlayer(player); + + questData = new PlayerQuests(this); + } + + public PlayerData load(FileConfiguration config) { + this.classPoints = config.getInt("class-points"); + this.skillPoints = config.getInt("skill-points"); + this.attributePoints = config.getInt("attribute-points"); + // this.skillReallocationPoints = config.getInt("skill-realloc-points"); + this.attributeReallocationPoints = config.getInt("attribute-realloc-points"); + this.level = config.getInt("level"); + this.experience = config.getInt("experience"); + this.profess = config.contains("class") ? MMOCore.plugin.classManager.get(config.getString("class")) : null; + this.mana = getStats().getStat(StatType.MAX_MANA); + this.stamina = getStats().getStat(StatType.MAX_STAMINA); + this.stellium = getStats().getStat(StatType.MAX_STELLIUM); + if (config.contains("attribute")) + attributes.load(config.getConfigurationSection("attribute")); + if (config.contains("profession")) + collectSkills.load(config.getConfigurationSection("profession")); + if (config.contains("quest")) + questData.load(config.getConfigurationSection("quest")); + questData.updateBossBar(); + if (config.contains("waypoints")) + waypoints.addAll(config.getStringList("waypoints")); + MMOCore.plugin.waypointManager.getDefault().forEach(waypoint -> waypoints.add(waypoint.getId())); + this.friends = config.contains("friends") ? config.getStringList("friends").stream().map((str) -> UUID.fromString(str)).collect(Collectors.toList()) : new ArrayList<>(); + if (config.contains("skill")) + config.getConfigurationSection("skill").getKeys(false).forEach(id -> skills.put(id, config.getInt("skill." + id))); + if (config.contains("bound-skills")) + for (String id : config.getStringList("bound-skills")) + if (MMOCore.plugin.skillManager.has(id)) + boundSkills.add(getProfess().getSkill(id)); + + /* + * load class slots, use try so the player can log in. + */ + if (config.contains("class-info")) + for (String key : config.getConfigurationSection("class-info").getKeys(false)) + try { + PlayerClass profess = MMOCore.plugin.classManager.get(key); + Validate.notNull(profess, "Could not find class '" + key + "'"); + applyClassInfo(profess, new SavedClassInformation(config.getConfigurationSection("class-info." + key))); + } catch (IllegalArgumentException exception) { + log(Level.SEVERE, "Could not load class info " + key + ": " + exception.getMessage()); + } + + return this; + } + + public void saveInConfig(FileConfiguration config) { + config.set("class-points", classPoints); + config.set("skill-points", skillPoints); + config.set("attribute-points", attributePoints); + // config.set("skill-realloc-points", skillReallocationPoints); + config.set("attribute-realloc-points", attributeReallocationPoints); + config.set("level", getLevel()); + config.set("experience", experience); + config.set("class", profess == null ? null : profess.getId()); + config.set("waypoints", new ArrayList<>(waypoints)); + config.set("friends", toStringList(friends)); + + config.set("skill", null); + skills.entrySet().forEach(entry -> config.set("skill." + entry.getKey(), entry.getValue())); + + List boundSkills = new ArrayList<>(); + this.boundSkills.forEach(skill -> boundSkills.add(skill.getSkill().getId())); + config.set("bound-skills", boundSkills); + + config.set("attribute", null); + config.createSection("attribute"); + attributes.save(config.getConfigurationSection("attribute")); + + config.set("profession", null); + config.createSection("profession"); + collectSkills.save(config.getConfigurationSection("profession")); + + config.set("quest", null); + config.createSection("quest"); + questData.save(config.getConfigurationSection("quest")); + + config.set("class-info", null); + for (String key : classSlots.keySet()) { + SavedClassInformation info = classSlots.get(key); + config.set("class-info." + key + ".level", info.getLevel()); + config.set("class-info." + key + ".experience", info.getExperience()); + config.set("class-info." + key + ".skill-points", info.getSkillPoints()); + info.getSkillKeys().forEach(skill -> config.set("class-info." + key + ".skill." + skill, info.getSkillLevel(skill))); + } + } + + /* + * update all references after /mmocore reload so there can be garbage + * collection with old plugin objects like class or skill instances. + */ + public void update() { + + try { + profess = profess == null ? null : MMOCore.plugin.classManager.get(profess.getId()); + } catch (NullPointerException exception) { + MMOCore.log(Level.SEVERE, "[Userdata] Could not find class " + getProfess().getId() + " while refreshing player data."); + } + + int j = 0; + while (j < boundSkills.size()) + try { + boundSkills.set(j, getProfess().getSkill(boundSkills.get(j).getSkill())); + j++; + } catch (NullPointerException notFound) { + boundSkills.remove(j); + MMOCore.log(Level.SEVERE, "[Userdata] Could not find skill " + boundSkills.get(j).getSkill().getId() + " in class " + getProfess().getId() + " while refreshing player data."); + } + } + + public static PlayerData get(OfflinePlayer player) { + return get(player.getUniqueId()); + } + + public static PlayerData get(UUID uuid) { + return playerData.get(uuid); + } + + public static void remove(Player player) { + playerData.remove(player.getUniqueId()); + } + + public static PlayerData setup(Player player) { + if (!playerData.containsKey(player.getUniqueId())) + playerData.put(player.getUniqueId(), new PlayerData(player).load(new ConfigFile(player).getConfig())); + return get(player); + } + + public static boolean isLoaded(UUID uuid) { + return playerData.containsKey(uuid); + } + + public static Collection getAll() { + return playerData.values(); + } + + public PlayerData setPlayer(Player player) { + this.player = player; + this.lastLogin = System.currentTimeMillis(); + return this; + } + + public List getFriends() { + return friends; + } + + public Professions getCollectionSkills() { + return collectSkills; + } + + public PlayerQuests getQuestData() { + return questData; + } + + public Player getPlayer() { + return player; + } + + public UUID getUniqueId() { + return uuid; + } + + public long getLastLogin() { + return lastLogin; + } + + public long getLastFriendRequest() { + return lastFriendRequest; + } + + public int getLevel() { + return Math.max(1, level); + } + + public Party getParty() { + return party; + } + + public int getClassPoints() { + return classPoints; + } + + public int getSkillPoints() { + return skillPoints; + } + + // public int getSkillReallocationPoints() { + // return skillReallocationPoints; + // } + + public int getAttributePoints() { + return attributePoints; + } + + public int getAttributeReallocationPoints() { + return attributeReallocationPoints; + } + + public boolean hasParty() { + return party != null; + } + + public boolean isOnline() { + return player.isOnline(); + } + + public void setLevel(int level) { + this.level = Math.max(1, level); + getStats().updateAll(); + } + + public void setExperience(int value) { + experience = Math.max(0, value); + refreshVanillaExp(MMOCore.plugin.configManager.getNeededExperience(getLevel() + 1)); + } + + public void refreshVanillaExp(float needed) { + if (MMOCore.plugin.configManager.overrideVanillaExp) { + player.setLevel(getLevel()); + player.setExp((float) experience / needed); + } + } + + // public void setSkillReallocationPoints(int value) { + // skillReallocationPoints = Math.max(0, value); + // } + + public void setAttributePoints(int value) { + attributePoints = Math.max(0, value); + } + + public void setAttributeReallocationPoints(int value) { + attributeReallocationPoints = Math.max(0, value); + } + + public void setSkillPoints(int value) { + skillPoints = Math.max(0, value); + } + + public void setClassPoints(int value) { + classPoints = Math.max(0, value); + } + + public boolean hasSavedClass(PlayerClass profess) { + return classSlots.containsKey(profess.getId()); + } + + public SavedClassInformation getClassInfo(PlayerClass profess) { + return classSlots.get(profess.getId()); + } + + public void applyClassInfo(PlayerClass profess, SavedClassInformation info) { + classSlots.put(profess.getId(), info); + } + + public void unloadClassInfo(PlayerClass profess) { + classSlots.remove(profess.getId()); + } + + public Set getWaypoints() { + return waypoints; + } + + public boolean hasWaypoint(Waypoint waypoint) { + return waypoints.contains(waypoint.getId()); + } + + public void unlockWaypoint(Waypoint waypoint) { + waypoints.add(waypoint.getId()); + } + + public long getNextWaypointMillis() { + return Math.max(0, lastWaypoint + 5000 - System.currentTimeMillis()); + } + + public void heal(double heal) { + getPlayer().setHealth(Math.min(player.getHealth() + heal, player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue())); + } + + public void addFriend(UUID uuid) { + friends.add(uuid); + } + + public void removeFriend(UUID uuid) { + friends.remove(uuid); + } + + public boolean hasFriend(UUID uuid) { + return friends.contains(uuid); + } + + public void setParty(Party party) { + this.party = party; + } + + public void log(Level level, String message) { + MMOCore.plugin.getLogger().log(level, "[Userdata:" + player.getName() + "] " + message); + } + + public void sendFriendRequest(PlayerData target) { + lastFriendRequest = System.currentTimeMillis(); + + FriendRequest request = new FriendRequest(this, target); + new ConfigMessage("friend-request").addPlaceholders("player", getPlayer().getName(), "uuid", request.getUniqueId().toString()).sendAsJSon(target.getPlayer()); + MMOCore.plugin.requestManager.registerRequest(request); + } + + public void warp(Waypoint waypoint) { + lastWaypoint = System.currentTimeMillis(); + giveStellium(-waypoint.getStelliumCost()); + + new BukkitRunnable() { + String message = MMOCore.plugin.configManager.getSimpleMessage("warping-comencing"); + int x = player.getLocation().getBlockX(), y = player.getLocation().getBlockY(), z = player.getLocation().getBlockZ(), t; + + public void run() { + if (player.getLocation().getBlockX() != x || player.getLocation().getBlockY() != y || player.getLocation().getBlockZ() != z) { + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, .5f); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("warping-canceled")); + giveStellium(waypoint.getStelliumCost()); + cancel(); + return; + } + + displayActionBar(message.replace("{left}", "" + ((120 - t) / 20))); + if (t++ >= 100) { + player.teleport(waypoint.getLocation()); + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 20, 1, false, false)); + player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1, .5f); + cancel(); + return; + } + + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BELL, 1, (float) (t / Math.PI * .015 + .5)); + double r = Math.sin((double) t / 100 * Math.PI); + for (double j = 0; j < Math.PI * 2; j += Math.PI / 4) + player.getWorld().spawnParticle(Particle.REDSTONE, player.getLocation().add(Math.cos((double) t / 20 + j) * r, (double) t / 50, Math.sin((double) t / 20 + j) * r), 0, new Particle.DustOptions(Color.PURPLE, 1.25f)); + } + }.runTaskTimer(MMOCore.plugin, 0, 1); + } + + public boolean hasReachedMaxLevel() { + return getProfess().getMaxLevel() > 0 && getLevel() >= getProfess().getMaxLevel(); + } + + public void giveExperience(int value) { + if (profess == null || hasReachedMaxLevel()) { + setExperience(0); + return; + } + + value = MMOCore.plugin.boosterManager.calculateExp(null, value); + value *= 1 + getStats().getStat(StatType.ADDITIONAL_EXPERIENCE) / 100; + + experience += value; + + int needed; + boolean check = false; + while (experience >= (needed = MMOCore.plugin.configManager.getNeededExperience(getLevel() + 1))) { + + if (hasReachedMaxLevel()) { + experience = 0; + break; + } + + experience -= needed; + level = getLevel() + 1; + check = true; + Bukkit.getPluginManager().callEvent(new PlayerLevelUpEvent(this, null, level + 1)); + } + + if (check) { + new ConfigMessage("level-up").addPlaceholders("level", "" + level).send(player); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1); + new SmallParticleEffect(player, Particle.SPELL_INSTANT); + getStats().updateAll(); + } + + refreshVanillaExp(needed); + } + + public int getExperience() { + return experience; + } + + public PlayerClass getProfess() { + return profess == null ? MMOCore.plugin.classManager.getDefaultClass() : profess; + } + + public void giveMana(double amount) { + if (mana != (mana = Math.max(0, Math.min(getStats().getStat(StatType.MAX_MANA), mana + amount)))) + displayMana(); + } + + public void giveStamina(double amount) { + stamina = Math.max(0, Math.min(getStats().getStat(StatType.MAX_STAMINA), stamina + amount)); + } + + public void giveStellium(double amount) { + stellium = Math.max(0, Math.min(getStats().getStat(StatType.MAX_STELLIUM), stellium + amount)); + } + + public double getMana() { + return mana; + } + + public double getStamina() { + return stamina; + } + + public double getStellium() { + return stellium; + } + + public PlayerStats getStats() { + return playerStats; + } + + public PlayerAttributes getAttributes() { + return attributes; + } + + public boolean canRegen(PlayerResource resource) { + return getProfess().getHandler(resource).isAvailable(this); + } + + public double calculateRegen(PlayerResource resource) { + return getProfess().getHandler(resource).getRegen(this); + } + + public void setMana(double amount) { + mana = Math.max(0, Math.min(amount, getStats().getStat(StatType.MAX_MANA))); + } + + public void setStamina(double amount) { + stamina = Math.max(0, Math.min(amount, getStats().getStat(StatType.MAX_STAMINA))); + } + + public void setStellium(double amount) { + stellium = Math.max(0, Math.min(amount, getStats().getStat(StatType.MAX_STELLIUM))); + } + + public boolean isCasting() { + return skillCasting != null; + } + + public void displayActionBar(String message) { + lastActionbarUpdate = System.currentTimeMillis(); + player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(message)); + } + + /* + * 200ms timeout to prevent action bar displayed gitches when casting spells + * and when using a waypoint. + */ + public void displayMana() { + if (System.currentTimeMillis() > lastActionbarUpdate + 1200) + player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(getProfess().getManaDisplay().generateBar(getMana(), getStats().getStat(StatType.MAX_MANA)))); + } + + public void setSkillLevel(Skill skill, int level) { + setSkillLevel(skill.getId(), level); + } + + public void setSkillLevel(String skill, int level) { + skills.put(skill, level); + } + + public void lockSkill(Skill skill) { + skills.remove(skill.getId()); + } + + public boolean hasSkillUnlocked(Skill skill) { + return skills.containsKey(skill.getId()); + } + + public int getSkillLevel(Skill skill) { + return skills.containsKey(skill.getId()) ? skills.get(skill.getId()) : 1; + } + + public Map mapSkillLevels() { + return new HashMap<>(skills); + } + + public void clearSkillLevels() { + skills.clear(); + } + + public void giveClassPoints(int value) { + setClassPoints(classPoints + value); + } + + public void giveSkillPoints(int value) { + setSkillPoints(skillPoints + value); + } + + public void giveAttributePoints(int value) { + setAttributePoints(attributePoints + value); + } + + // public void giveSkillReallocationPoints(int value) { + // setSkillReallocationPoints(skillReallocationPoints + value); + // } + + public void giveAttributeReallocationPoints(int value) { + setAttributeReallocationPoints(attributeReallocationPoints + value); + } + + public PlayerSkillData getSkillData() { + return skillData; + } + + public void setClass(PlayerClass profess) { + this.profess = profess; + + // for (Iterator iterator = boundSkills.iterator(); + // iterator.hasNext();) + // if (!getProfess().hasSkill(iterator.next().getSkill())) + // iterator.remove(); + + getStats().updateAll(); + } + + public void setProfess(PlayerClass profess) { + this.profess = profess; + } + + public boolean hasSkillBound(int slot) { + return slot < boundSkills.size(); + } + + public SkillInfo getBoundSkill(int slot) { + return slot >= boundSkills.size() ? null : boundSkills.get(slot); + } + + public void setBoundSkill(int slot, SkillInfo skill) { + if (boundSkills.size() < 6) + boundSkills.add(skill); + else + boundSkills.set(slot, skill); + } + + public void unbindSkill(int slot) { + boundSkills.remove(slot); + } + + public List getBoundSkills() { + return boundSkills; + } + + public boolean isInCombat() { + return combat != null; + } + + public boolean canChooseSubclass() { + for (Subclass subclass : getProfess().getSubclasses()) + if (getLevel() >= subclass.getLevel()) + return true; + return false; + } + + public void updateCombat() { + if (isInCombat()) + combat.update(); + else + combat = new CombatRunnable(this); + } + + private List toStringList(List list) { + if (list.isEmpty()) + return null; + + List stringList = new ArrayList<>(); + list.forEach(uuid -> stringList.add(uuid.toString())); + return stringList; + } + + public SkillResult cast(Skill skill) { + return cast(getProfess().getSkill(skill)); + } + + public SkillResult cast(SkillInfo skill) { + + if (skill.getSkill().isPassive()) + return new SkillResult(this, skill, CancelReason.OTHER); + + PlayerCastSkillEvent event = new PlayerCastSkillEvent(this, skill); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) + return new SkillResult(this, skill, CancelReason.OTHER); + + SkillResult cast = skill.getSkill().whenCast(this, skill); + if (!cast.isSuccessful()) { + + if (cast.getCancelReason() == CancelReason.MANA) + getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("casting.no-mana")); + + if (cast.getCancelReason() == CancelReason.COOLDOWN) + getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("casting.on-cooldown")); + + return cast; + } + + if (!nocd) { + skillData.setLastCast(cast.getSkill(), System.currentTimeMillis() + (long) (getStats().getStat(StatType.COOLDOWN_REDUCTION) * cast.getCooldown() * 10)); + giveMana(-cast.getManaCost()); + } + + return cast; + } + + @Override + public boolean equals(Object obj) { + return obj != null && obj instanceof PlayerData && ((PlayerData) obj).uuid.equals(uuid); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/PlayerQuests.java b/src/net/Indyuce/mmocore/api/player/PlayerQuests.java new file mode 100644 index 00000000..b0791c62 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/PlayerQuests.java @@ -0,0 +1,141 @@ +package net.Indyuce.mmocore.api.player; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.quest.Quest; +import net.Indyuce.mmocore.api.quest.QuestProgress; + +public class PlayerQuests { + private final PlayerData playerData; + private final BossBar bossbar; + private final Map finished = new HashMap<>(); + + private QuestProgress current; + + public PlayerQuests(PlayerData playerData) { + this.playerData = playerData; + bossbar = Bukkit.createBossBar(new NamespacedKey(MMOCore.plugin, "quest_bar_" + playerData.getUniqueId().toString()), "", BarColor.PURPLE, BarStyle.SEGMENTED_20, new BarFlag[0]); + bossbar.addPlayer(playerData.getPlayer()); + } + + public PlayerQuests load(ConfigurationSection config) { + if (config.contains("current")) + try { + current = MMOCore.plugin.questManager.get(config.getString("current.id")).generateNewProgress(playerData, config.getInt("current.objective")); + } catch (Exception e) { + playerData.log(Level.WARNING, "Couldn't load current quest progress (ID '" + config.getString("current.id") + "'"); + } + + if (config.contains("finished")) + for (String key : config.getConfigurationSection("finished").getKeys(false)) + finished.put(key, config.getLong("finished." + key)); + + /* + * must update the boss bar once the instance is loaded, otherwise it + * won't detect the current quest. THE BOSS BAR UPDATE is in the player + * data class, this way it is still set invisible even if the player has + * no quest data + */ + + return this; + } + + public void save(ConfigurationSection config) { + if (current != null) { + config.set("current.id", current.getQuest().getId()); + config.set("current.objective", current.getObjectiveNumber()); + } else + config.set("current", null); + + for (String key : finished.keySet()) + config.set("finished." + key, finished.get(key)); + } + + public QuestProgress getCurrent() { + return current; + } + + public boolean hasCurrent() { + return current != null; + } + + public Set getFinishedQuests() { + return finished.keySet(); + } + + public boolean hasCurrent(Quest quest) { + return hasCurrent() && current.getQuest().equals(quest); + } + + public boolean hasFinished(Quest quest) { + return finished.containsKey(quest.getId()); + } + + public void finishCurrent() { + finished.put(current.getQuest().getId(), System.currentTimeMillis()); + start(null); + } + + public void resetFinishedQuests() { + finished.clear(); + } + + public Date getFinishDate(Quest quest) { + return new Date(finished.get(quest.getId())); + } + + public void start(Quest quest) { + + // close current objective progress if quest is active + if (hasCurrent()) + current.closeObjectiveProgress(); + + // apply newer quest + current = quest == null ? null : quest.generateNewProgress(playerData); + updateBossBar(); + } + + public boolean checkCooldownAvailability(Quest quest) { + return (finished.get(quest.getId()) + quest.getDelayMillis()) < System.currentTimeMillis(); + } + + public long getDelayFeft(Quest quest) { + return Math.max(finished.get(quest.getId()) + quest.getDelayMillis() - System.currentTimeMillis(), 0); + } + + public boolean checkParentAvailability(Quest quest) { + for (Quest parent : quest.getParents()) + if (!hasFinished(parent)) + return false; + return true; + } + + public void updateBossBar() { + if (!hasCurrent()) { + bossbar.setVisible(false); + return; + } + + bossbar.setVisible(true); + bossbar.setColor(current.getProgress().getObjective().getBarColor()); + bossbar.setTitle(current.getFormattedLore()); + bossbar.setProgress((double) current.getObjectiveNumber() / current.getQuest().getObjectives().size()); + } + + public void resetBossBar() { + bossbar.removePlayer(playerData.getPlayer()); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/PlayerSkillData.java b/src/net/Indyuce/mmocore/api/player/PlayerSkillData.java new file mode 100644 index 00000000..f1f9ccef --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/PlayerSkillData.java @@ -0,0 +1,90 @@ +package net.Indyuce.mmocore.api.player; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.Skill.SkillInfo; +import net.Indyuce.mmocore.comp.mythicmobs.MythicMobSkill; + +public class PlayerSkillData { + private final Map cooldowns = new HashMap<>(); + private final PlayerData data; + + /* + * MythicMobs skill damage is handled via math formula which can retrieve + * PAPI placeholders. when a skill is cast, all skill modifiers are cached + * into that map: 1- for easier and faster access 2- it removes interference + * for example when stats are calculating not when the spell is cast but + * rather when the spell hits + */ + private final Map cache = new HashMap<>(); + + public int ambers; + + public PlayerSkillData(PlayerData data) { + this.data = data; + } + + public long getCooldown(SkillInfo skill) { + return Math.max(0, lastCast(skill.getSkill()) + 1000 * (long) skill.getModifier("cooldown", data.getSkillLevel(skill.getSkill())) - System.currentTimeMillis()); + } + + public long lastCast(Skill skill) { + return cooldowns.containsKey(skill.getId()) ? cooldowns.get(skill.getId()) : 0; + } + + public void setLastCast(Skill skill) { + setLastCast(skill, System.currentTimeMillis()); + } + + public void setLastCast(Skill skill, long ms) { + cooldowns.put(skill.getId(), ms); + } + + public void reduceCooldown(SkillInfo skill, double value, boolean relative) { + cooldowns.put(skill.getSkill().getId(), lastCast(skill.getSkill()) + (long) (relative ? value * getCooldown(skill) : value * 1000)); + } + + public void resetData() { + ambers = 0; + } + + public double getCachedModifier(String name) { + return cache.containsKey(name) ? cache.get(name).getValue() : 0; + } + + public void cacheModifiers(MythicMobSkill mmSkill, SkillResult cast) { + for (String modifier : cast.getSkill().getModifiers()) + cacheModifier(mmSkill, modifier, cast.getModifier(modifier)); + } + + public void cacheModifier(MythicMobSkill skill, String name, double value) { + cache.put(skill.getInternalName() + "." + name, new CachedModifier(value)); + } + + public void refresh() { + for (Iterator iterator = cache.values().iterator(); iterator.hasNext();) + if (iterator.next().isTimedOut()) + iterator.remove(); + } + + public class CachedModifier { + private final long date = System.currentTimeMillis(); + private final double value; + + public CachedModifier(double value) { + this.value = value; + } + + public boolean isTimedOut() { + return date + 1000 * 60 < System.currentTimeMillis(); + } + + public double getValue() { + return value; + } + } +} diff --git a/src/net/Indyuce/mmocore/api/player/Professions.java b/src/net/Indyuce/mmocore/api/player/Professions.java new file mode 100644 index 00000000..4d427787 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/Professions.java @@ -0,0 +1,102 @@ +package net.Indyuce.mmocore.api.player; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigMessage; +import net.Indyuce.mmocore.api.event.PlayerLevelUpEvent; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; + +public class Professions { + private final Map exp = new HashMap<>(); + private final Map level = new HashMap<>(); + private final PlayerData playerData; + + public Professions(PlayerData playerData) { + this.playerData = playerData; + } + + public Professions load(ConfigurationSection config) { + for (String key : config.getKeys(false)) + if (MMOCore.plugin.professionManager.has(key)) { + exp.put(key, config.getInt(key + ".exp")); + level.put(key, config.getInt(key + ".level")); + } + + return this; + } + + public void save(ConfigurationSection config) { + for (String id : exp.keySet()) + config.set(id + ".exp", exp.get(id)); + for (String id : level.keySet()) + config.set(id + ".level", level.get(id)); + } + + public PlayerData getPlayerData() { + return playerData; + } + + public int getLevel(String profession) { + return Math.max(1, level.containsKey(profession) ? level.get(profession) : 1); + } + + public int getLevel(Profession profession) { + return getLevel(profession.getId()); + } + + public int getExperience(Profession profession) { + return exp.containsKey(profession.getId()) ? exp.get(profession.getId()) : 0; + } + + public void setExperience(Profession profession, int value) { + exp.put(profession.getId(), value); + } + + public void setLevel(Profession profession, int value) { + level.put(profession.getId(), value); + } + + public void giveExperience(Profession profession, int value) { + giveExperience(profession, value, null); + } + + public void giveExperience(Profession profession, int value, Location loc) { + value = MMOCore.plugin.boosterManager.calculateExp(profession, value); + exp.put(profession.getId(), exp.containsKey(profession.getId()) ? exp.get(profession.getId()) + value : value); + + // display hologram + if (loc != null && MMOCore.plugin.hologramSupport != null) + MMOCore.plugin.hologramSupport.displayIndicator(loc, MMOCore.plugin.configManager.getSimpleMessage("exp-hologram", "exp", "" + value), playerData.getPlayer()); + + int needed, exp, level; + boolean check = false; + while ((exp = this.exp.get(profession.getId())) >= (needed = MMOCore.plugin.configManager.getNeededExperience((level = getLevel(profession)) + 1))) { + this.exp.put(profession.getId(), exp - needed); + this.level.put(profession.getId(), level + 1); + check = true; + playerData.giveExperience((int) profession.getExperience().calculate(level)); + Bukkit.getPluginManager().callEvent(new PlayerLevelUpEvent(playerData, profession, level + 1)); + + new ConfigMessage("profession-level-up").addPlaceholders("level", "" + (level + 1), "profession", profession.getName()).send(playerData.getPlayer()); + playerData.getPlayer().playSound(playerData.getPlayer().getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 2); + } + if (check) + new SmallParticleEffect(playerData.getPlayer(), Particle.SPELL_INSTANT); + + String bar = "" + ChatColor.BOLD; + int chars = (int) ((double) exp / needed * 20); + for (int j = 0; j < 20; j++) + bar += (j == chars ? "" + ChatColor.WHITE + ChatColor.BOLD : "") + "|"; + playerData.displayActionBar(MMOCore.plugin.configManager.getSimpleMessage("exp-notification", "profession", profession.getName(), "progress", bar, "ratio", MMOCore.digit.format((double) exp / needed * 100))); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/attribute/PlayerAttribute.java b/src/net/Indyuce/mmocore/api/player/attribute/PlayerAttribute.java new file mode 100644 index 00000000..434ba246 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/attribute/PlayerAttribute.java @@ -0,0 +1,62 @@ +package net.Indyuce.mmocore.api.player.attribute; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.api.player.stats.stat.modifier.StatModifier; + +public class PlayerAttribute { + private final String id, name; + private final int max; + private final Map buffs = new HashMap<>(); + + public PlayerAttribute(ConfigurationSection config) { + Validate.notNull(config, "Could not load config"); + id = config.getName().toLowerCase().replace("_", "-").replace(" ", "-"); + + name = config.getString("name"); + Validate.isTrue(name != null && !name.isEmpty(), "Could not read name"); + + max = config.contains("max-points") ? Math.max(1, config.getInt("max-points")) : 0; + + if (config.contains("buff")) + for (String key : config.getConfigurationSection("buff").getKeys(false)) + try { + StatType stat = StatType.valueOf(key.toUpperCase().replace("-", "_").replace(" ", "_")); + buffs.put(stat, new StatModifier(config.getString("buff." + key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[PlayerAttributes:" + id + "] Could not load buff '" + key + "': " + exception.getMessage()); + } + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public boolean hasMax() { + return max > 0; + } + + public int getMax() { + return max; + } + + public Set getStats() { + return buffs.keySet(); + } + + public StatModifier getBuff(StatType stat) { + return buffs.get(stat); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/attribute/PlayerAttributes.java b/src/net/Indyuce/mmocore/api/player/attribute/PlayerAttributes.java new file mode 100644 index 00000000..2319be69 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/attribute/PlayerAttributes.java @@ -0,0 +1,162 @@ +package net.Indyuce.mmocore.api.player.attribute; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.stat.modifier.StatModifier; + +public class PlayerAttributes { + private final PlayerData data; + private final Map extra = new HashMap<>(); + + public PlayerAttributes(PlayerData data) { + this.data = data; + } + + public void load(ConfigurationSection config) { + for (String key : config.getKeys(false)) + try { + String id = key.toLowerCase().replace("_", "-").replace(" ", "-"); + Validate.isTrue(MMOCore.plugin.attributeManager.has(id), "Could not find attribute '" + id + "'"); + + PlayerAttribute attribute = MMOCore.plugin.attributeManager.get(id); + AttributeInstance ins = new AttributeInstance(attribute); + ins.setBase(config.getInt(key)); + extra.put(id, ins); + } catch (IllegalArgumentException exception) { + data.log(Level.WARNING, exception.getMessage()); + } + } + + public void save(ConfigurationSection config) { + extra.values().forEach(ins -> config.set(ins.id, ins.getBase())); + } + + public PlayerData getData() { + return data; + } + + public int getAttribute(PlayerAttribute attribute) { + return getInstance(attribute).getTotal(); + } + + public Collection getAttributeInstances() { + return extra.values(); + } + + public AttributeInstance getInstance(PlayerAttribute attribute) { + if (extra.containsKey(attribute.getId())) + return extra.get(attribute.getId()); + + AttributeInstance ins = new AttributeInstance(attribute); + extra.put(attribute.getId(), ins); + return ins; + } + + public int countSkillPoints() { + int n = 0; + for (AttributeInstance ins : extra.values()) + n += ins.getBase(); + return n; + } + + public class AttributeInstance { + private int spent; + + private final String id; + private final Map map = new HashMap<>(); + + public AttributeInstance(PlayerAttribute attribute) { + id = new String(attribute.getId()); + } + + public int getBase() { + return spent; + } + + public void setBase(int value) { + spent = Math.max(0, value); + + update(); + } + + public void addBase(int value) { + setBase(spent + value); + } + + public int getTotal() { + return (int) getTotal(spent); + } + + /* + * 1) two types of attributes: flat attributes which add X to the value, + * and relative attributes which add X% and which must be applied + * afterwards 2) the 'd' parameter lets you choose if the relative + * attributes also apply on the base stat, or if they only apply on the + * extra stat value + */ + public double getTotal(double d) { + + for (StatModifier attr : map.values()) + if (attr.isRelative()) + d = attr.apply(d); + + for (StatModifier attr : map.values()) + if (!attr.isRelative()) + d = attr.apply(d); + + return d; + } + + public StatModifier getModifier(String key) { + return map.get(key); + } + + public void addModifier(String key, double value) { + addModifier(key, new StatModifier(value)); + } + + public void addModifier(String key, StatModifier modifier) { + map.put(key, modifier); + + update(); + } + + public Set getKeys() { + return map.keySet(); + } + + public boolean contains(String key) { + return map.containsKey(key); + } + + public void remove(String key) { + + /* + * closing stat is really important with temporary stats because + * otherwise the runnable will try to remove the key from the map + * even though the attribute was cancelled before hand + */ + if (map.containsKey(key)) { + map.get(key).close(); + map.remove(key); + } + + update(); + } + + public void update() { + PlayerAttribute attribute = MMOCore.plugin.attributeManager.get(id); + int total = getTotal(); + attribute.getStats().forEach(stat -> data.getStats().getInstance(stat).addModifier("attribute." + attribute.getId(), attribute.getBuff(stat).multiply(total))); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/ManaDisplayOptions.java b/src/net/Indyuce/mmocore/api/player/profess/ManaDisplayOptions.java new file mode 100644 index 00000000..e8f02853 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/ManaDisplayOptions.java @@ -0,0 +1,49 @@ +package net.Indyuce.mmocore.api.player.profess; + +import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; +import org.bukkit.configuration.ConfigurationSection; + +public class ManaDisplayOptions { + private final ChatColor color; + private final String name; + private final char barCharacter; + + public ManaDisplayOptions(ConfigurationSection config) { + Validate.notNull(config, "Could not load mana display options"); + + name = config.getString("name"); + Validate.notNull(name, "Could not load mana name"); + + String format = config.getString("color").toUpperCase().replace("-", "_").replace(" ", "_"); + Validate.notNull(format, "Could not load mana color"); + color = ChatColor.valueOf(format); + + format = config.getString("char"); + Validate.notNull(format, "Could not load mana bar character"); + Validate.notEmpty(format, "Could not load mana bar character"); + barCharacter = format.charAt(0); + } + + public ManaDisplayOptions(ChatColor color, String name, char barCharacter) { + Validate.notNull(color, "Color cannot be null"); + Validate.notNull(name, "name cannot be null"); + Validate.notNull(barCharacter, "barCharacter cannot be null"); + + this.color = color; + this.name = name; + this.barCharacter = barCharacter; + } + + public String getName() { + return name; + } + + public String generateBar(double mana, double max) { + String format = ""; + double ratio = 20 * mana / max; + for (double j = 1; j < 20; j++) + format += "" + (ratio >= j ? color : ChatColor.WHITE) + barCharacter; + return format; + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/PlayerClass.java b/src/net/Indyuce/mmocore/api/player/profess/PlayerClass.java new file mode 100644 index 00000000..ca183b69 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/PlayerClass.java @@ -0,0 +1,320 @@ +package net.Indyuce.mmocore.api.player.profess; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.AltChar; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.CastingParticle; +import net.Indyuce.mmocore.api.player.profess.event.EventTrigger; +import net.Indyuce.mmocore.api.player.profess.resource.PlayerResource; +import net.Indyuce.mmocore.api.player.profess.resource.ResourceHandler; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.Skill.SkillInfo; +import net.Indyuce.mmocore.manager.ClassManager; + +public class PlayerClass { + private final String name, id, fileName; + private final List description = new ArrayList<>(), attrDescription = new ArrayList<>(); + private final ItemStack icon; + private final Map options = new HashMap<>(); + private final ManaDisplayOptions manaDisplay; + private final int maxLevel; + + private final Map stats = new HashMap<>(); + private final Map skills = new LinkedHashMap<>(); + private final List subclasses = new ArrayList<>(); + + private Map eventTriggers = new HashMap<>(); + + private final Map resources = new HashMap<>(); + private CastingParticle castParticle = new CastingParticle(Particle.SPELL_INSTANT); + + /* + * easy load + */ + private FileConfiguration loaded; + + public PlayerClass(String id, FileConfiguration config) { + this.id = (fileName = id).toUpperCase().replace("-", "_").replace(" ", "_"); + this.loaded = config; + + name = ChatColor.translateAlternateColorCodes('&', config.getString("display.name")); + icon = MMOCoreUtils.readIcon(config.getString("display.item")); + for (String string : config.getStringList("display.lore")) + description.add(ChatColor.GRAY + ChatColor.translateAlternateColorCodes('&', string)); + for (String string : config.getStringList("display.attribute-lore")) + attrDescription.add(ChatColor.GRAY + ChatColor.translateAlternateColorCodes('&', string)); + manaDisplay = new ManaDisplayOptions(config.getConfigurationSection("mana")); + maxLevel = config.getInt("max-level"); + + if (config.contains("attributes")) + for (String key : config.getConfigurationSection("attributes").getKeys(false)) + try { + stats.put(StatType.valueOf(key.toUpperCase().replace("-", "_")), new LinearValue(config.getConfigurationSection("attributes." + key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[PlayerClasses:" + id + "] Could not load stat info '" + key + "': " + exception.getMessage()); + } + + if (config.contains("skills")) + for (String key : config.getConfigurationSection("skills").getKeys(false)) + try { + Validate.isTrue(MMOCore.plugin.skillManager.has(key), "Could not find skill " + key); + skills.put(key, MMOCore.plugin.skillManager.get(key).newSkillInfo(config.getConfigurationSection("skills." + key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[PlayerClasses:" + id + "] Could not load skill info '" + key + "': " + exception.getMessage()); + } + + if (config.contains("cast-particle")) + try { + castParticle = new CastingParticle(config.getConfigurationSection("cast-particle")); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[PlayerClasses:" + id + "] Could not read casting particle, using default: " + exception.getMessage()); + } + + if (config.contains("options")) + for (String key : config.getConfigurationSection("options").getKeys(false)) + try { + setOption(ClassOption.valueOf(key.toUpperCase().replace("-", "_").replace(" ", "_")), config.getBoolean("options." + key)); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[PlayerClasses:" + id + "] Could not read class option from '" + key + "'"); + } + + if (config.contains("main-exp-sources")) + for (String key : config.getStringList("main-exp-sources")) + try { + ExperienceSource source = MMOCore.plugin.loadManager.loadExperienceSource(new MMOLineConfig(key), null); + source.setClass(this); + MMOCore.plugin.professionManager.registerExpSource(source); + } catch (MMOLoadException exception) { + exception.printConsole("PlayerClasses:" + id, "exp source"); + } + + if (config.contains("triggers")) + for (String key : config.getConfigurationSection("triggers").getKeys(false)) { + try { + String format = key.toLowerCase().replace("_", "-").replace(" ", "-"); + // Validate.isTrue(MMOCore.plugin.classManager.isEventRegistered(format), + // "Could not find trigger event called '" + format + "'"); + eventTriggers.put(format, new EventTrigger(format, config.getStringList("triggers." + key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[PlayerClasses:" + id + "] " + exception.getMessage()); + continue; + } + } + + for (PlayerResource resource : PlayerResource.values()) + resources.put(resource, new ResourceHandler(this, resource)); + } + + /* + * used to generate display class + */ + public PlayerClass(String id, String name, Material material) { + this.id = id; + this.name = name; + this.fileName = id; + manaDisplay = new ManaDisplayOptions(ChatColor.BLUE, "Mana", AltChar.listSquare.charAt(0)); + maxLevel = 0; + + this.icon = new ItemStack(material); + setOption(ClassOption.DISPLAY, false); + setOption(ClassOption.DEFAULT, false); + + for (PlayerResource resource : PlayerResource.values()) + resources.put(resource, new ResourceHandler(this, resource)); + } + + public void loadSubclasses(ClassManager manager) { + if (loaded.contains("subclasses")) + for (String key : loaded.getConfigurationSection("subclasses").getKeys(false)) + subclasses.add(new Subclass(manager.get(key.toUpperCase().replace("-", "_").replace(" ", "_")), loaded.getInt("subclasses." + key))); + loaded = null; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public ManaDisplayOptions getManaDisplay() { + return manaDisplay; + } + + public ResourceHandler getHandler(PlayerResource resource) { + return resources.get(resource); + } + + public int getMaxLevel() { + return maxLevel; + } + + public String getFileName() { + return fileName; + } + + public ItemStack getIcon() { + return icon.clone(); + } + + public CastingParticle getCastParticle() { + return castParticle; + } + + public List getDescription() { + return description; + } + + public List getAttributeDescription() { + return attrDescription; + } + + public void setOption(ClassOption option, boolean value) { + options.put(option, value); + } + + public boolean hasOption(ClassOption option) { + return options.containsKey(option) ? options.get(option) : option.getDefault(); + } + + public void setStat(StatType type, double base, double perLevel) { + stats.put(type, new LinearValue(base, perLevel)); + } + + public double calculateStat(StatType stat, int level) { + return getStatInfo(stat).calculate(level); + } + + public List getSubclasses() { + return subclasses; + } + + public boolean hasSkill(Skill skill) { + return skills.containsKey(skill.getId()); + } + + public SkillInfo getSkill(Skill skill) { + return getSkill(skill.getId()); + } + + public SkillInfo getSkill(String id) { + return skills.get(id); + } + + public Set getEventTriggers() { + return eventTriggers.keySet(); + } + + public boolean hasEventTriggers(String name) { + return eventTriggers.containsKey(name); + } + + public EventTrigger getEventTriggers(String name) { + return eventTriggers.get(name); + } + + public Collection getSkills() { + return skills.values(); + } + + private LinearValue getStatInfo(StatType type) { + return stats.containsKey(type) ? stats.get(type) : type.getDefault(); + } + + @Override + public boolean equals(Object obj) { + return obj != null && obj instanceof PlayerClass && ((PlayerClass) obj).id.equals(id); + } + + public class Subclass { + private PlayerClass profess; + private int level; + + public Subclass(PlayerClass profess, int level) { + this.profess = profess; + this.level = level; + } + + public PlayerClass getProfess() { + return profess; + } + + public int getLevel() { + return level; + } + } + + public enum ClassOption { + + /* + * is class by default + */ + DEFAULT, + + /* + * displays in the /class GUI + */ + DISPLAY(true), + + /* + * resource regeneration depends on max resource + */ + MISSING_HEALTH_REGEN, + MISSING_MANA_REGEN, + MISSING_STAMINA_REGEN, + + /* + * resource regeneration depends on missing resource + */ + MAX_HEALTH_REGEN, + MAX_MANA_REGEN, + MAX_STAMINA_REGEN, + + /* + * only regen resource when out of combat + */ + OFF_COMBAT_HEALTH_REGEN, + OFF_COMBAT_MANA_REGEN(true), + OFF_COMBAT_STAMINA_REGEN; + + private final boolean def; + + private ClassOption() { + this(false); + } + + private ClassOption(boolean def) { + this.def = def; + } + + public boolean getDefault() { + return def; + } + + public String getPath() { + return name().toLowerCase().replace("_", "-"); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/SavedClassInformation.java b/src/net/Indyuce/mmocore/api/player/profess/SavedClassInformation.java new file mode 100644 index 00000000..f0a9223a --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/SavedClassInformation.java @@ -0,0 +1,106 @@ +package net.Indyuce.mmocore.api.player.profess; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass.ClassOption; +import net.Indyuce.mmocore.api.skill.Skill; + +public class SavedClassInformation { + private final int level, experience, skillPoints; + private final Map skills; + + public SavedClassInformation(ConfigurationSection config) { + level = config.getInt("level"); + experience = config.getInt("experience"); + skillPoints = config.getInt("skill-points"); + + skills = new HashMap<>(); + if (config.contains("skill")) + config.getKeys(false).forEach(key -> skills.put(key, config.getInt(key))); + } + + public SavedClassInformation(PlayerData player) { + level = player.getLevel(); + skillPoints = player.getSkillPoints(); + experience = player.getExperience(); + skills = player.mapSkillLevels(); + } + + public SavedClassInformation(int level, int experience, int skillPoints) { + this.level = level; + this.experience = experience; + this.skillPoints = skillPoints; + skills = new HashMap<>(); + } + + public int getLevel() { + return level; + } + + public int getExperience() { + return experience; + } + + public int getSkillPoints() { + return skillPoints; + } + + public Set getSkillKeys() { + return skills.keySet(); + } + + public int getSkillLevel(Skill skill) { + return getSkillLevel(skill.getId()); + } + + public int getSkillLevel(String id) { + return skills.get(id); + } + + public void registerSkillLevel(Skill skill, int level) { + registerSkillLevel(skill.getId(), level); + } + + public void registerSkillLevel(String skill, int level) { + skills.put(skill, level); + } + + public void load(PlayerClass profess, PlayerData player) { + + /* + * saves current class info inside a SavedClassInformation, only if the + * class is a real class and not the default one. + */ + if (!player.getProfess().hasOption(ClassOption.DEFAULT)) + player.applyClassInfo(player.getProfess(), new SavedClassInformation(player)); + + /* + * resets information which much be reset after everything is saved. + */ + player.clearSkillLevels(); + while (player.hasSkillBound(0)) + player.unbindSkill(0); + + /* + * reads this class info, applies it to the player. set class after + * changing level so the player stats can be calculated based on new + * level. + */ + player.setLevel(level); + player.setExperience(experience); + player.setSkillPoints(skillPoints); + skills.keySet().forEach(id -> player.setSkillLevel(id, skills.get(id))); + + /* + * unload current class information and set the new profess once + * everything is changed + */ + player.setClass(profess); + player.unloadClassInfo(profess); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/event/EventTrigger.java b/src/net/Indyuce/mmocore/api/player/profess/event/EventTrigger.java new file mode 100644 index 00000000..07b48379 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/event/EventTrigger.java @@ -0,0 +1,38 @@ +package net.Indyuce.mmocore.api.player.profess.event; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang.Validate; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class EventTrigger { + private final String event; + private final Set triggers = new LinkedHashSet<>(); + + public EventTrigger(String event, List list) { + Validate.notNull(list, "Could not load trigger list"); + + this.event = event; + + for (String format : list) + try { + triggers.add(MMOCore.plugin.loadManager.loadTrigger(new MMOLineConfig(format))); + } catch (MMOLoadException exception) { + exception.printConsole("EventTrigger:" + event, "trigger"); + } + } + + public String getEvent() { + return event; + } + + public Set getTriggers() { + return triggers; + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/event/EventTriggerHandler.java b/src/net/Indyuce/mmocore/api/player/profess/event/EventTriggerHandler.java new file mode 100644 index 00000000..bc36320d --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/event/EventTriggerHandler.java @@ -0,0 +1,7 @@ +package net.Indyuce.mmocore.api.player.profess.event; + +import org.bukkit.event.Listener; + +public interface EventTriggerHandler extends Listener { + public boolean handles(String event); +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/event/trigger/AttackEventTrigger.java b/src/net/Indyuce/mmocore/api/player/profess/event/trigger/AttackEventTrigger.java new file mode 100644 index 00000000..ecad8fd9 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/event/trigger/AttackEventTrigger.java @@ -0,0 +1,30 @@ +package net.Indyuce.mmocore.api.player.profess.event.trigger; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; + +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.api.player.profess.event.EventTriggerHandler; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class AttackEventTrigger implements EventTriggerHandler { + + @Override + public boolean handles(String event) { + return event.endsWith("-damage"); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void a(PlayerAttackEvent event) { + PlayerData player = event.getData(); + PlayerClass profess = player.getProfess(); + + for (DamageType type : event.getDamageInfo().getTypes()) { + String path = type.getPath() + "-damage"; + if (profess.hasEventTriggers(path)) + profess.getEventTriggers(path).getTriggers().forEach(trigger -> trigger.apply(player)); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/event/trigger/ClassChosenEventTrigger.java b/src/net/Indyuce/mmocore/api/player/profess/event/trigger/ClassChosenEventTrigger.java new file mode 100644 index 00000000..1879146a --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/event/trigger/ClassChosenEventTrigger.java @@ -0,0 +1,23 @@ +package net.Indyuce.mmocore.api.player.profess.event.trigger; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; + +import net.Indyuce.mmocore.api.event.PlayerChangeClassEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.event.EventTriggerHandler; + +public class ClassChosenEventTrigger implements EventTriggerHandler { + + @Override + public boolean handles(String event) { + return event.startsWith("class-chosen"); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void a(PlayerChangeClassEvent event) { + PlayerData player = event.getData(); + if (event.getNewClass().hasEventTriggers("class-chosen")) + event.getNewClass().getEventTriggers("class-chosen").getTriggers().forEach(trigger -> trigger.apply(player)); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/event/trigger/LevelUpEventTrigger.java b/src/net/Indyuce/mmocore/api/player/profess/event/trigger/LevelUpEventTrigger.java new file mode 100644 index 00000000..eb38058f --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/event/trigger/LevelUpEventTrigger.java @@ -0,0 +1,33 @@ +package net.Indyuce.mmocore.api.player.profess.event.trigger; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; + +import net.Indyuce.mmocore.api.event.PlayerLevelUpEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.api.player.profess.event.EventTriggerHandler; + +public class LevelUpEventTrigger implements EventTriggerHandler { + + @Override + public boolean handles(String event) { + return event.startsWith("level-up"); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void a(PlayerLevelUpEvent event) { + PlayerData player = event.getData(); + PlayerClass profess = player.getProfess(); + + if (event.hasProfession()) + for (String event1 : profess.getEventTriggers()) + if (event1.startsWith("level-up-") && event1.substring(9).equalsIgnoreCase(event.getProfession().getId())) { + profess.getEventTriggers(event1).getTriggers().forEach(trigger -> trigger.apply(player)); + break; + } + + if (!event.hasProfession() && profess.hasEventTriggers("level-up")) + profess.getEventTriggers("level-up").getTriggers().forEach(trigger -> trigger.apply(player)); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/resource/PlayerResource.java b/src/net/Indyuce/mmocore/api/player/profess/resource/PlayerResource.java new file mode 100644 index 00000000..7493e7f3 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/resource/PlayerResource.java @@ -0,0 +1,56 @@ +package net.Indyuce.mmocore.api.player.profess.resource; + +import java.util.function.Function; + +import org.bukkit.attribute.Attribute; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass.ClassOption; +import net.Indyuce.mmocore.api.player.stats.StatType; + +public enum PlayerResource { + + /* + * used to handle resource regeneration. + */ + HEALTH(StatType.HEALTH_REGENERATION, ClassOption.OFF_COMBAT_HEALTH_REGEN, ClassOption.MAX_HEALTH_REGEN, ClassOption.MISSING_HEALTH_REGEN, (data) -> data.getPlayer().getHealth(), (data) -> data.getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()), + MANA(StatType.MANA_REGENERATION, ClassOption.OFF_COMBAT_MANA_REGEN, ClassOption.MAX_MANA_REGEN, ClassOption.MISSING_MANA_REGEN, (data) -> data.getMana(), (data) -> data.getStats().getStat(StatType.MAX_MANA)), + STAMINA(StatType.STAMINA_REGENERATION, ClassOption.OFF_COMBAT_STAMINA_REGEN, ClassOption.MAX_STAMINA_REGEN, ClassOption.MISSING_STAMINA_REGEN, (data) -> data.getStamina(), (data) -> data.getStats().getStat(StatType.MAX_STAMINA)); + + private final StatType regenStat; + private final ClassOption offCombatRegen, maxRegen, missingRegen; + private final Function current, max; + + private PlayerResource(StatType regenStat, ClassOption offCombatRegen, ClassOption maxRegen, ClassOption missingRegen, Function current, Function max) { + this.regenStat = regenStat; + this.offCombatRegen = offCombatRegen; + this.maxRegen = maxRegen; + this.missingRegen = missingRegen; + this.current = current; + this.max = max; + } + + public ClassOption getMaxRegen() { + return maxRegen; + } + + public ClassOption getMissingRegen() { + return missingRegen; + } + + public ClassOption getOffCombatRegen() { + return offCombatRegen; + } + + public StatType getRegenStat() { + return regenStat; + } + + public double getCurrent(PlayerData data) { + return current.apply(data); + } + + public double getMax(PlayerData data) { + return max.apply(data); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/profess/resource/ResourceHandler.java b/src/net/Indyuce/mmocore/api/player/profess/resource/ResourceHandler.java new file mode 100644 index 00000000..97b4d0f4 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/profess/resource/ResourceHandler.java @@ -0,0 +1,25 @@ +package net.Indyuce.mmocore.api.player.profess.resource; + +import java.util.function.Function; +import java.util.function.Predicate; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; + +public class ResourceHandler { + private final Function regen; + private final Predicate available; + + public ResourceHandler(PlayerClass profess, PlayerResource resource) { + available = profess.hasOption(resource.getOffCombatRegen()) ? (data) -> !data.isInCombat() : (data) -> true; + regen = profess.hasOption(resource.getMaxRegen()) ? (data) -> resource.getMax(data) * data.getStats().getStat(resource.getRegenStat()) / 100 : profess.hasOption(resource.getMissingRegen()) ? (data) -> Math.max(0, resource.getMax(data) - resource.getCurrent(data)) * data.getStats().getStat(resource.getRegenStat()) / 100 : (data) -> data.getStats().getStat(resource.getRegenStat()); + } + + public boolean isAvailable(PlayerData data) { + return available.test(data); + } + + public double getRegen(PlayerData data) { + return regen.apply(data); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/social/FriendRequest.java b/src/net/Indyuce/mmocore/api/player/social/FriendRequest.java new file mode 100644 index 00000000..ad6133cd --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/social/FriendRequest.java @@ -0,0 +1,36 @@ +package net.Indyuce.mmocore.api.player.social; + +import org.bukkit.Sound; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class FriendRequest extends Request { + private PlayerData creator, target; + + public FriendRequest(PlayerData creator, PlayerData target) { + this.creator = creator; + this.target = target; + } + + public PlayerData getCreator() { + return creator; + } + + public PlayerData getTarget() { + return target; + } + + public void deny() { + MMOCore.plugin.requestManager.unregisterRequest(getUniqueId()); + target.getPlayer().playSound(target.getPlayer().getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + } + + public void accept() { + creator.addFriend(target.getUniqueId()); + target.addFriend(creator.getUniqueId()); + creator.getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("now-friends", "player", target.getPlayer().getName())); + target.getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("now-friends", "player", creator.getPlayer().getName())); + MMOCore.plugin.requestManager.unregisterRequest(getUniqueId()); + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/api/player/social/Party.java b/src/net/Indyuce/mmocore/api/player/social/Party.java new file mode 100644 index 00000000..04552e65 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/social/Party.java @@ -0,0 +1,142 @@ +package net.Indyuce.mmocore.api.player.social; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigMessage; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.social.party.EditablePartyView.PartyViewInventory; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class Party { + private final PartyMembers members = new PartyMembers(); + private final Map invites = new HashMap<>(); + + /* + * owner changes when the old owner leaves party + */ + private PlayerData owner; + + // used to check if two parties are the same + // private UUID uuid = UUID.randomUUID(); + + public Party(PlayerData owner) { + this.owner = owner; + addMember(owner); + } + + public PlayerData getOwner() { + return owner; + } + + public PartyMembers getMembers() { + return members; + } + + public long getLastInvite(Player player) { + return invites.containsKey(player.getUniqueId()) ? invites.get(player.getUniqueId()) : 0; + } + + public void removeMember(PlayerData data) { + if (data.isOnline() && data.getPlayer().getOpenInventory() != null && data.getPlayer().getOpenInventory().getTopInventory().getHolder() instanceof PartyViewInventory) + InventoryManager.PARTY_CREATION.newInventory(data).open(); + + members.remove(data); + data.setParty(null); + + reopenInventories(); + + // disband the party if no member left + if (members.count() < 1) { + MMOCore.plugin.partyManager.unregisterParty(this); + return; + } + + // transfer ownership + if (owner.equals(data)) { + owner = members.get(0); + owner.getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("transfer-party-ownership")); + } + } + + public void addMember(PlayerData data) { + if (data.hasParty()) + data.getParty().removeMember(data); + + data.setParty(this); + members.add(data); + + reopenInventories(); + } + + public void reopenInventories() { + for (PlayerData member : members.members) + if (member.isOnline() && member.getPlayer().getOpenInventory() != null && member.getPlayer().getOpenInventory().getTopInventory().getHolder() instanceof PartyViewInventory) + ((PluginInventory) member.getPlayer().getOpenInventory().getTopInventory().getHolder()).open(); + } + + public void sendPartyInvite(Player inviter, PlayerData target) { + invites.put(target.getUniqueId(), System.currentTimeMillis()); + Request request = new PartyInvite(this, target); + new ConfigMessage("party-invite").addPlaceholders("player", inviter.getName(), "uuid", request.getUniqueId().toString()).sendAsJSon(target.getPlayer()); + MMOCore.plugin.requestManager.registerRequest(request); + } + + // @Override + // public boolean equals(Object obj) { + // return obj instanceof Party && ((Party) obj).uuid.equals(uuid); + // } + + /* + * this class makes controling entries and departures and APPLYING PARTY + * STAT ATTRIBUTES much easier + */ + public class PartyMembers { + private final List members = new ArrayList<>(); + + public PlayerData get(int count) { + return members.get(count); + } + + public boolean has(PlayerData player) { + return members.contains(player); + } + + public void add(PlayerData player) { + members.add(player); + + refreshAttributes(); + } + + public void remove(PlayerData player) { + members.remove(player); + + refreshAttributes(); + refreshAttributes(player); + } + + public void forEach(Consumer action) { + members.forEach(action); + } + + public int count() { + return members.size(); + } + + public void refreshAttributes() { + members.forEach(member -> refreshAttributes(member)); + } + + public void refreshAttributes(PlayerData player) { + MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).addModifier("party", MMOCore.plugin.partyManager.getBonus(stat))); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/player/social/PartyInvite.java b/src/net/Indyuce/mmocore/api/player/social/PartyInvite.java new file mode 100644 index 00000000..f8bdb3e2 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/social/PartyInvite.java @@ -0,0 +1,35 @@ +package net.Indyuce.mmocore.api.player.social; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class PartyInvite extends Request { + private PlayerData target; + private Party party; + + public PartyInvite(Party party, PlayerData target) { + this.party = party; + this.target = target; + } + + public Party getParty() { + return party; + } + + public PlayerData getPlayer() { + return target; + } + + public void deny() { + MMOCore.plugin.requestManager.unregisterRequest(getUniqueId()); + } + + public void accept() { + party.getMembers().forEach(member -> member.getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("party-joined-other", "player", target.getPlayer().getName()))); + target.getPlayer().sendMessage(MMOCore.plugin.configManager.getSimpleMessage("party-joined", "owner", party.getOwner().getPlayer().getName())); + party.addMember(target); + InventoryManager.PARTY_VIEW.newInventory(target).open(); + MMOCore.plugin.requestManager.unregisterRequest(getUniqueId()); + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/api/player/social/Request.java b/src/net/Indyuce/mmocore/api/player/social/Request.java new file mode 100644 index 00000000..cf4de3b5 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/social/Request.java @@ -0,0 +1,20 @@ +package net.Indyuce.mmocore.api.player.social; + +import java.util.UUID; + +public abstract class Request { + private UUID uuid = UUID.randomUUID(); + private long date = System.currentTimeMillis(); + + public UUID getUniqueId() { + return uuid; + } + + public boolean isTimedOut() { + return date + 1000 * 60 * 2 < System.currentTimeMillis(); + } + + public abstract void accept(); + + public abstract void deny(); +} diff --git a/src/net/Indyuce/mmocore/api/player/stats/PlayerStats.java b/src/net/Indyuce/mmocore/api/player/stats/PlayerStats.java new file mode 100644 index 00000000..43f0ebff --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/stats/PlayerStats.java @@ -0,0 +1,161 @@ +package net.Indyuce.mmocore.api.player.stats; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.bukkit.ChatColor; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.stat.modifier.StatModifier; + +public class PlayerStats { + private final PlayerData data; + + /* + * allows for extra compatibility with extra MMOCore plugins like item + * plugins which can apply other stats onto the player. + */ + private final Map extra = new HashMap<>(); + + public PlayerStats(PlayerData data) { + this.data = data; + } + + public PlayerData getData() { + return data; + } + + public StatInstance getInstance(StatType stat) { + if (extra.containsKey(stat.name())) + return extra.get(stat.name()); + + StatInstance ins = new StatInstance(stat); + extra.put(stat.name(), ins); + return ins; + } + + public String format(ChatColor color, Attribute attribute) { + AttributeInstance instance = data.getPlayer().getAttribute(attribute); + return format(color, instance.getBaseValue(), instance.getValue() - instance.getBaseValue(), MMOCore.digit); + } + + public String format(ChatColor color, StatType stat) { + return format(color, data.getProfess().calculateStat(stat, data.getLevel()), getExtraStat(stat), MMOCore.digit); + } + + public String format(ChatColor color, StatType stat, DecimalFormat format) { + return format(color, data.getProfess().calculateStat(stat, data.getLevel()), getExtraStat(stat), format); + } + + public String format(ChatColor color, double base, double extra, DecimalFormat format) { + return "" + color + format.format(base + extra) + ChatColor.GRAY + " (" + color + MMOCore.digit.format(base) + ChatColor.GRAY + "+" + color + MMOCore.digit.format(extra) + ChatColor.GRAY + ")"; + } + + /* + * applies relative attributes on the base stat too + */ + public double getStat(StatType stat) { + return getInstance(stat).getTotal(getBase(stat)); + } + + public double getBase(StatType stat) { + return data.getProfess().calculateStat(stat, stat.hasProfession() ? data.getCollectionSkills().getLevel(stat.getProfession()) : data.getLevel()); + } + + /* + * applies relative attributes on the extra stat value only + */ + public double getExtraStat(StatType stat) { + return getInstance(stat).getTotal(0); + } + + public void updateAll() { + for (StatType stat : StatType.values()) + update(stat); + } + + public void update(StatType stat) { + if (stat.hasHandler()) + stat.getHandler().refresh(data, getStat(stat)); + } + + public class StatInstance { + private final StatType stat; + private final Map map = new HashMap<>(); + + public StatInstance(StatType stat) { + this.stat = stat; + } + + public double getTotal() { + return getTotal(0); + } + + /* + * 1) two types of attributes: flat attributes which add X to the value, + * and relative attributes which add X% and which must be applied + * afterwards 2) the 'd' parameter lets you choose if the relative + * attributes also apply on the base stat, or if they only apply on the + * extra stat value + */ + public double getTotal(double d) { + + for (StatModifier attr : map.values()) + if (attr.isRelative()) + d = attr.apply(d); + + for (StatModifier attr : map.values()) + if (!attr.isRelative()) + d = attr.apply(d); + + return d; + } + + public StatModifier getAttribute(String key) { + return map.get(key); + } + + public void addModifier(String key, double value) { + addModifier(key, new StatModifier(value)); + } + + public void addModifier(String key, StatModifier modifier) { + map.put(key, modifier); + + update(stat); + } + + public Set getKeys() { + return map.keySet(); + } + + public boolean contains(String key) { + return map.containsKey(key); + } + + public void remove(String key) { + + /* + * closing stat is really important with temporary stats because + * otherwise the runnable will try to remove the key from the map + * even though the attribute was cancelled before hand + */ + if (map.containsKey(key)) { + map.get(key).close(); + map.remove(key); + } + + update(stat); + } + + @Deprecated + public void setValue(String key, double value) { + addModifier(key, new StatModifier(value)); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/player/stats/StatType.java b/src/net/Indyuce/mmocore/api/player/stats/StatType.java new file mode 100644 index 00000000..22808bee --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/stats/StatType.java @@ -0,0 +1,127 @@ +package net.Indyuce.mmocore.api.player.stats; + +import java.text.DecimalFormat; + +import org.bukkit.attribute.Attribute; +import org.bukkit.configuration.file.FileConfiguration; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.stats.stat.AttributeStat; +import net.Indyuce.mmocore.api.player.stats.stat.MovementSpeedStat; +import net.Indyuce.mmocore.api.player.stats.stat.PlayerStat; + +public enum StatType { + + ATTACK_DAMAGE(Attribute.GENERIC_ATTACK_DAMAGE), + ATTACK_SPEED(Attribute.GENERIC_ATTACK_SPEED), + MAX_HEALTH(Attribute.GENERIC_MAX_HEALTH), + HEALTH_REGENERATION, + + MOVEMENT_SPEED(new MovementSpeedStat()), + SPEED_MALUS_REDUCTION(new MovementSpeedStat()), + KNOCKBACK_RESISTANCE(Attribute.GENERIC_KNOCKBACK_RESISTANCE), + + MAX_MANA, + MAX_STAMINA, + MAX_STELLIUM, + MANA_REGENERATION, + STAMINA_REGENERATION, + STELLIUM_REGENERATION, + + ARMOR(Attribute.GENERIC_ARMOR), + ARMOR_TOUGHNESS(Attribute.GENERIC_ARMOR_TOUGHNESS), + + ADDITIONAL_EXPERIENCE, + COOLDOWN_REDUCTION, + + MAGICAL_DAMAGE, + PHYSICAL_DAMAGE, + PROJECTILE_DAMAGE, + WEAPON_DAMAGE, + SKILL_DAMAGE, + + // reduces amount of tugs needed to fish + FISHING_STRENGTH("fishing"), + + // chance of instant success when fishing + CRITICAL_FISHING_CHANCE("fishing"), + + // chance of crit fishing failure + CRITICAL_FISHING_FAILURE_CHANCE("fishing"), + + // chance of dropping more minerals when mining. + FORTUNE, + + // get haste when mining blocks. + GATHERING_HASTE, + + // chance of getting more crops when farming + LUCK_OF_THE_FIELD, + + ; + + private String profession; + private PlayerStat handler; + + private LinearValue defaultInfo; + private DecimalFormat format; + + private StatType() { + // completely custom stat. + } + + private StatType(PlayerStat handler) { + this.handler = handler; + } + + private StatType(Attribute attribute) { + this.handler = new AttributeStat(attribute); + } + + private StatType(String profession) { + this.profession = profession; + } + + public String getProfession() { + return profession; + } + + public Profession findProfession() { + return MMOCore.plugin.professionManager.get(profession); + } + + public boolean hasProfession() { + return profession != null; + } + + public boolean hasHandler() { + return handler != null; + } + + public PlayerStat getHandler() { + return handler; + } + + public LinearValue getDefault() { + return defaultInfo; + } + + public boolean matches(Profession profession) { + return this.profession != null && this.profession.equals(profession.getId()); + } + + public String format(double value) { + return format.format(value); + } + + public static void load() { + FileConfiguration config = new ConfigFile("stats").getConfig(); + for (StatType stat : values()) { + stat.defaultInfo = config.contains("default." + stat.name()) ? new LinearValue(config.getConfigurationSection("default." + stat.name())) : new LinearValue(0, 0); + stat.format = new DecimalFormat(config.contains("decimal-format." + stat.name()) ? config.getString("decimal-format." + stat.name()) : "0.#"); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/player/stats/TemporaryStats.java b/src/net/Indyuce/mmocore/api/player/stats/TemporaryStats.java new file mode 100644 index 00000000..fe77d7e9 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/stats/TemporaryStats.java @@ -0,0 +1,42 @@ +package net.Indyuce.mmocore.api.player.stats; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class TemporaryStats { + private final PlayerData playerData; + private final Player player; + + private final Map stats = new HashMap<>(); + + public TemporaryStats(PlayerStats stats) { + this.playerData = stats.getData(); + this.player = playerData.getPlayer(); + + for (StatType stat : StatType.values()) + this.stats.put(stat.name(), stats.getStat(stat)); + } + + public Player getPlayer() { + return player; + } + + public PlayerData getData() { + return playerData; + } + + public double getStat(StatType stat) { + return stats.get(stat.name()); + } + + public void damage(LivingEntity target, double value, DamageType... types) { + MMOCore.plugin.damage.damage(playerData, target, value, types); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/stats/stat/AttributeStat.java b/src/net/Indyuce/mmocore/api/player/stats/stat/AttributeStat.java new file mode 100644 index 00000000..7bbaa183 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/stats/stat/AttributeStat.java @@ -0,0 +1,24 @@ +package net.Indyuce.mmocore.api.player.stats.stat; + +import org.bukkit.attribute.Attribute; + +import net.Indyuce.mmocore.api.player.PlayerData; + +public class AttributeStat extends PlayerStat { + private final Attribute attribute; + + public AttributeStat(Attribute attribute) { + super(attribute.name().substring(8)); + + this.attribute = attribute; + } + + public Attribute getAttribute() { + return attribute; + } + + @Override + public void refresh(PlayerData player, double val) { + player.getPlayer().getAttribute(attribute).setBaseValue(val); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/stats/stat/MovementSpeedStat.java b/src/net/Indyuce/mmocore/api/player/stats/stat/MovementSpeedStat.java new file mode 100644 index 00000000..ee8f4a49 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/stats/stat/MovementSpeedStat.java @@ -0,0 +1,32 @@ +package net.Indyuce.mmocore.api.player.stats.stat; + +import org.bukkit.attribute.Attribute; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.StatType; + +public class MovementSpeedStat extends AttributeStat { + + /* + * both used for the 'movement speed' and for the 'speed malus reduction' + * stats because the movement speed must be refreshed every time one of + * these stats are changed. + */ + public MovementSpeedStat() { + super(Attribute.GENERIC_MOVEMENT_SPEED); + } + + @Override + public void refresh(PlayerData player, double val) { + double speedMalus = MMOCore.plugin.configManager.speedMalus * (1 - player.getStats().getStat(StatType.SPEED_MALUS_REDUCTION) / 100); + double movementSpeed = player.getStats().getStat(StatType.MOVEMENT_SPEED); + + for (ItemStack item : player.getPlayer().getEquipment().getArmorContents()) + if (item != null) + if (item.getType().name().contains("IRON") || item.getType().name().contains("DIAMOND")) + movementSpeed *= 1 - speedMalus; + player.getPlayer().setWalkSpeed((float) Math.min(1, movementSpeed)); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/stats/stat/PlayerStat.java b/src/net/Indyuce/mmocore/api/player/stats/stat/PlayerStat.java new file mode 100644 index 00000000..a6eb44ea --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/stats/stat/PlayerStat.java @@ -0,0 +1,17 @@ +package net.Indyuce.mmocore.api.player.stats.stat; + +import net.Indyuce.mmocore.api.player.PlayerData; + +public abstract class PlayerStat { + private final String id; + + public PlayerStat(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public abstract void refresh(PlayerData player, double val); +} diff --git a/src/net/Indyuce/mmocore/api/player/stats/stat/modifier/StatModifier.java b/src/net/Indyuce/mmocore/api/player/stats/stat/modifier/StatModifier.java new file mode 100644 index 00000000..43c442ac --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/stats/stat/modifier/StatModifier.java @@ -0,0 +1,47 @@ +package net.Indyuce.mmocore.api.player.stats.stat.modifier; + +import org.apache.commons.lang.Validate; + +import net.Indyuce.mmocore.MMOCore; + +public class StatModifier { + private final double d; + private final boolean relative; + + public StatModifier(double d) { + this(d, false); + } + + public StatModifier(double d, boolean relative) { + this.d = d; + this.relative = relative; + } + + public StatModifier(String str) { + Validate.notNull(str, "String cannot be null"); + Validate.notEmpty(str, "String cannot be empty"); + + relative = str.toCharArray()[str.length() - 1] == '%'; + d = Double.parseDouble(relative ? str.substring(0, str.length() - 1) : str); + } + + public StatModifier multiply(int coef) { + return new StatModifier(d * coef, relative); + } + + public boolean isRelative() { + return relative; + } + + public double apply(double in) { + return relative ? in * (1 + d / 100) : in + d; + } + + public void close() { + } + + @Override + public String toString() { + return MMOCore.digit.format(d) + (relative ? "%" : ""); + } +} diff --git a/src/net/Indyuce/mmocore/api/player/stats/stat/modifier/TemporaryStatModifier.java b/src/net/Indyuce/mmocore/api/player/stats/stat/modifier/TemporaryStatModifier.java new file mode 100644 index 00000000..1c484071 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/player/stats/stat/modifier/TemporaryStatModifier.java @@ -0,0 +1,25 @@ +package net.Indyuce.mmocore.api.player.stats.stat.modifier; + +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.stats.PlayerStats.StatInstance; + +public class TemporaryStatModifier extends StatModifier { + private final BukkitRunnable runnable; + + public TemporaryStatModifier(double d, long duration, boolean relative, String key, StatInstance ins) { + super(d, relative); + + (runnable = new BukkitRunnable() { + public void run() { + ins.remove(key); + } + }).runTaskLater(MMOCore.plugin, duration); + } + + public void close() { + if (!runnable.isCancelled()) + runnable.cancel(); + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/ObjectiveProgress.java b/src/net/Indyuce/mmocore/api/quest/ObjectiveProgress.java new file mode 100644 index 00000000..cb747478 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/ObjectiveProgress.java @@ -0,0 +1,41 @@ +package net.Indyuce.mmocore.api.quest; + +import org.bukkit.Bukkit; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.quest.objective.Objective; + +public abstract class ObjectiveProgress { + private final Objective objective; + private final QuestProgress questProgress; + + public ObjectiveProgress(QuestProgress questProgress, Objective objective) { + this.questProgress = questProgress; + this.objective = objective; + + if (this instanceof Listener) + Bukkit.getPluginManager().registerEvents((Listener) this, MMOCore.plugin); + } + + public PlayerData getPlayer() { + return questProgress.getPlayer(); + } + + public Objective getObjective() { + return objective; + } + + public QuestProgress getQuestProgress() { + return questProgress; + } + + public void close() { + if (this instanceof Listener) + HandlerList.unregisterAll((Listener) this); + } + + public abstract String formatLore(String lore); +} diff --git a/src/net/Indyuce/mmocore/api/quest/Quest.java b/src/net/Indyuce/mmocore/api/quest/Quest.java new file mode 100644 index 00000000..b2c13deb --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/Quest.java @@ -0,0 +1,150 @@ +package net.Indyuce.mmocore.api.quest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.quest.objective.Objective; +import net.Indyuce.mmocore.manager.QuestManager; + +public class Quest { + private final String id; + + private String name; + private List parents = new ArrayList<>(); + private List objectives = new ArrayList<>(); + private List lore; + + private int mainLevelRestriction; + private Map levelRestrictions = new HashMap<>(); + + // cooldown in millis + private long cooldown; + + /* + * cached to load other info to enable parent quests. + */ + private FileConfiguration loaded; + + public Quest(String id, FileConfiguration config) { + this.id = id; + loaded = config; + } + + /* + * forced to request a Questmanager instance because the one in the main + * MMOCore class has not been initialized yet, so it can't be accessed using + * MMOCore.plugin.questManager + */ + public void load(QuestManager manager) { + cooldown = (long) (loaded.contains("delay") ? loaded.getDouble("delay") * 60 * 60 * 1000 : -1); + + if (loaded.contains("parent")) + for (String parent : loaded.getStringList("parent")) + try { + parents.add(manager.get(parent)); + } catch (NullPointerException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Couldn't find quest ID '" + parent + "'"); + } + + name = loaded.getString("name"); + lore = loaded.getStringList("lore"); + + mainLevelRestriction = loaded.getInt("level-req.main"); + + if (loaded.contains("level-req")) + for (String key : loaded.getConfigurationSection("level-req").getKeys(false)) + if (!key.equals("main")) + try { + String id = key.toLowerCase().replace("_", "-"); + Validate.isTrue(MMOCore.plugin.professionManager.has(id)); + levelRestrictions.put(MMOCore.plugin.professionManager.get(id), loaded.getInt("level-req." + key)); + } catch (IllegalArgumentException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "[Quests:" + id + "] Couldn't find profession '" + key + "'"); + } + + for (String key : loaded.getConfigurationSection("objectives").getKeys(false)) + try { + ConfigurationSection section = loaded.getConfigurationSection("objectives." + key); + Validate.notNull(section, "Could not find config section"); + + String format = section.getString("type"); + Validate.notNull(format, "Objective is missing format"); + + objectives.add(MMOCore.plugin.loadManager.loadObjective(new MMOLineConfig(format), section)); + } catch (MMOLoadException exception) { + exception.printConsole("Quests:" + id, "objective"); + } + + loaded = null; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public List getLore() { + return lore; + } + + public boolean hasParent() { + return parents.size() > 0; + } + + public List getParents() { + return parents; + } + + public boolean isRedoable() { + return cooldown >= 0; + } + + public long getDelayMillis() { + return cooldown; + } + + public List getObjectives() { + return objectives; + } + + public Set getLevelRestrictions() { + return levelRestrictions.keySet(); + } + + public int countLevelRestrictions() { + return levelRestrictions.size() + (mainLevelRestriction > 0 ? 1 : 0); + } + + public int getLevelRestriction(Profession profession) { + return profession == null ? mainLevelRestriction : levelRestrictions.containsKey(profession) ? levelRestrictions.get(profession) : 0; + } + + public QuestProgress generateNewProgress(PlayerData player) { + return generateNewProgress(player, 0); + } + + public QuestProgress generateNewProgress(PlayerData player, int objective) { + return new QuestProgress(this, player, objective); + } + + @Override + public boolean equals(Object obj) { + return obj != null && obj instanceof Quest && ((Quest) obj).id.equals(id); + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/QuestProgress.java b/src/net/Indyuce/mmocore/api/quest/QuestProgress.java new file mode 100644 index 00000000..6ac9cea1 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/QuestProgress.java @@ -0,0 +1,70 @@ +package net.Indyuce.mmocore.api.quest; + +import org.bukkit.ChatColor; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.quest.objective.Objective; + +public class QuestProgress { + private final Quest quest; + private final PlayerData player; + + private int objective; + private ObjectiveProgress objectiveProgress; + + public QuestProgress(Quest quest, PlayerData player) { + this(quest, player, 0); + } + + public QuestProgress(Quest quest, PlayerData player, int objective) { + this.quest = quest; + this.player = player; + + this.objective = objective; + objectiveProgress = nextObjective().newProgress(this); + } + + public Quest getQuest() { + return quest; + } + + public PlayerData getPlayer() { + return player; + } + + public int getObjectiveNumber() { + return objective; + } + + public ObjectiveProgress getProgress() { + return objectiveProgress; + } + + private Objective nextObjective() { + return quest.getObjectives().get(objective); + } + + public void closeObjectiveProgress() { + objectiveProgress.close(); + } + + public void completeObjective() { + objective++; + closeObjectiveProgress(); + + // apply triggers + objectiveProgress.getObjective().getTriggers().forEach(trigger -> trigger.apply(getPlayer())); + + // end quest + if (objective >= quest.getObjectives().size()) + player.getQuestData().finishCurrent(); + else + objectiveProgress = nextObjective().newProgress(this); + + player.getQuestData().updateBossBar(); + } + + public String getFormattedLore() { + return ChatColor.translateAlternateColorCodes('&', objectiveProgress.formatLore(objectiveProgress.getObjective().getDefaultLore())); + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/api/quest/objective/ClickonObjective.java b/src/net/Indyuce/mmocore/api/quest/objective/ClickonObjective.java new file mode 100644 index 00000000..21ee4de8 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/objective/ClickonObjective.java @@ -0,0 +1,60 @@ +package net.Indyuce.mmocore.api.quest.objective; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.quest.ObjectiveProgress; +import net.Indyuce.mmocore.api.quest.QuestProgress; + +public class ClickonObjective extends Objective { + private final Location loc; + private final int rangeSquared; + + public ClickonObjective(ConfigurationSection section, MMOLineConfig config) { + super(section); + + config.validate("range", "world", "x", "y", "z"); + + rangeSquared = config.getInt("range") ^ 2; + + World world = Bukkit.getWorld(config.getString("world")); + Validate.notNull(world, "Could not find world " + config.getString("world")); + loc = new Location(world, config.getInt("x"), config.getInt("y"), config.getInt("z")); + } + + @Override + public ObjectiveProgress newProgress(QuestProgress questProgress) { + return new GotoProgress(questProgress, this); + } + + public class GotoProgress extends ObjectiveProgress implements Listener { + public GotoProgress(QuestProgress questProgress, Objective objective) { + super(questProgress, objective); + } + + @EventHandler + public void a(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) + return; + + Player player = event.getPlayer(); + if (player.equals(getPlayer().getPlayer())) + if (player.getWorld().equals(loc.getWorld()) && event.getClickedBlock().getLocation().distanceSquared(loc) < rangeSquared) + getQuestProgress().completeObjective(); + } + + @Override + public String formatLore(String lore) { + return lore; + } + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/objective/GoToObjective.java b/src/net/Indyuce/mmocore/api/quest/objective/GoToObjective.java new file mode 100644 index 00000000..02d35f3c --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/objective/GoToObjective.java @@ -0,0 +1,59 @@ +package net.Indyuce.mmocore.api.quest.objective; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.quest.ObjectiveProgress; +import net.Indyuce.mmocore.api.quest.QuestProgress; + +public class GoToObjective extends Objective { + private final Location loc; + private final int rangeSquared; + + public GoToObjective(ConfigurationSection section, MMOLineConfig config) { + super(section); + + config.validate("range", "world", "x", "y", "z"); + + rangeSquared = config.getInt("range") ^ 2; + + World world = Bukkit.getWorld(config.getString("world")); + Validate.notNull(world, "Could not find world " + config.getString("world")); + loc = new Location(world, config.getInt("x"), config.getInt("y"), config.getInt("z")); + } + + @Override + public ObjectiveProgress newProgress(QuestProgress questProgress) { + return new GotoProgress(questProgress, this); + } + + public class GotoProgress extends ObjectiveProgress implements Listener { + public GotoProgress(QuestProgress questProgress, Objective objective) { + super(questProgress, objective); + } + + @EventHandler + public void a(PlayerMoveEvent event) { + if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockY() == event.getTo().getBlockY() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) + return; + + Player player = event.getPlayer(); + if (player.equals(getPlayer().getPlayer())) + if (player.getWorld().equals(loc.getWorld()) && player.getLocation().distanceSquared(loc) < rangeSquared) + getQuestProgress().completeObjective(); + } + + @Override + public String formatLore(String lore) { + return lore; + } + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/objective/KillMobObjective.java b/src/net/Indyuce/mmocore/api/quest/objective/KillMobObjective.java new file mode 100644 index 00000000..8ad13d7f --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/objective/KillMobObjective.java @@ -0,0 +1,53 @@ +package net.Indyuce.mmocore.api.quest.objective; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import net.Indyuce.mmocore.api.event.EntityKillEntityEvent; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.quest.ObjectiveProgress; +import net.Indyuce.mmocore.api.quest.QuestProgress; + +public class KillMobObjective extends Objective { + private final EntityType type; + private final int required; + + public KillMobObjective(ConfigurationSection section, MMOLineConfig config) { + super(section); + + config.validate("amount", "type"); + + type = EntityType.valueOf(config.getString("type")); + required = config.getInt("amount"); + } + + @Override + public ObjectiveProgress newProgress(QuestProgress questProgress) { + return new KillMobProgress(questProgress, this); + } + + public class KillMobProgress extends ObjectiveProgress implements Listener { + private int count; + + public KillMobProgress(QuestProgress questProgress, Objective objective) { + super(questProgress, objective); + } + + @EventHandler + public void a(EntityKillEntityEvent event) { + if (event.getTarget().getType() == type && event.getEntity().equals(getPlayer().getPlayer())) { + count++; + getQuestProgress().getPlayer().getQuestData().updateBossBar(); + if (count >= required) + getQuestProgress().completeObjective(); + } + } + + @Override + public String formatLore(String lore) { + return lore.replace("{left}", "" + (required - count)); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/objective/MineBlockObjective.java b/src/net/Indyuce/mmocore/api/quest/objective/MineBlockObjective.java new file mode 100644 index 00000000..2fda9578 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/objective/MineBlockObjective.java @@ -0,0 +1,54 @@ +package net.Indyuce.mmocore.api.quest.objective; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.quest.ObjectiveProgress; +import net.Indyuce.mmocore.api.quest.QuestProgress; + +public class MineBlockObjective extends Objective { + private final Material block; + private final int required; + + public MineBlockObjective(ConfigurationSection section, MMOLineConfig config) { + super(section); + + config.validate("type", "amount"); + + block = Material.valueOf(config.getString("type").replace("-", "_").toUpperCase()); + required = config.getInt("amount"); + } + + @Override + public ObjectiveProgress newProgress(QuestProgress questProgress) { + return new MineBlockProgress(questProgress, this); + } + + public class MineBlockProgress extends ObjectiveProgress implements Listener { + private int count; + + public MineBlockProgress(QuestProgress questProgress, Objective objective) { + super(questProgress, objective); + } + + @EventHandler(priority = EventPriority.HIGH) + public void a(BlockBreakEvent event) { + if (!event.isCancelled() && event.getPlayer().equals(getQuestProgress().getPlayer().getPlayer()) && event.getBlock().getType() == block) { + count++; + getQuestProgress().getPlayer().getQuestData().updateBossBar(); + if (count >= required) + getQuestProgress().completeObjective(); + } + } + + @Override + public String formatLore(String lore) { + return lore.replace("{left}", "" + (required - count)); + } + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/objective/Objective.java b/src/net/Indyuce/mmocore/api/quest/objective/Objective.java new file mode 100644 index 00000000..6e67ec10 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/objective/Objective.java @@ -0,0 +1,63 @@ +package net.Indyuce.mmocore.api.quest.objective; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.bukkit.boss.BarColor; +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.quest.ObjectiveProgress; +import net.Indyuce.mmocore.api.quest.QuestProgress; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public abstract class Objective { + private final String id, lore; + + private BarColor barColor; + private final List triggers = new ArrayList<>(); + + public Objective(ConfigurationSection config) { + this.id = config.getName(); + this.lore = config.getString("lore"); + + Validate.notNull(lore, "Could not find objective lore"); + Validate.notNull(config.getStringList("triggers"), "Could not load trigger list"); + + try { + String format = config.getString("bar-color"); + Validate.notNull(format); + barColor = BarColor.valueOf(format.toUpperCase().replace("-", "_").replace(" ", "_")); + } catch (IllegalArgumentException exeption) { + barColor = BarColor.PURPLE; + } + + for (String key : config.getStringList("triggers")) + try { + triggers.add(MMOCore.plugin.loadManager.loadTrigger(new MMOLineConfig(key))); + } catch (MMOLoadException exception) { + exception.printConsole("Objectives:" + id, "trigger"); + } + } + + public String getId() { + return id; + } + + public BarColor getBarColor() { + return barColor; + } + + public String getDefaultLore() { + return lore; + } + + public List getTriggers() { + return triggers; + } + + public abstract ObjectiveProgress newProgress(QuestProgress questProgress); +} diff --git a/src/net/Indyuce/mmocore/api/quest/trigger/CommandTrigger.java b/src/net/Indyuce/mmocore/api/quest/trigger/CommandTrigger.java new file mode 100644 index 00000000..2003db4f --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/trigger/CommandTrigger.java @@ -0,0 +1,28 @@ +package net.Indyuce.mmocore.api.quest.trigger; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class CommandTrigger extends Trigger { + private final String command; + + public CommandTrigger(MMOLineConfig config) { + super(config); + + config.validate("format"); + command = config.getString("format"); + } + + @Override + public void apply(PlayerData player) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), format(player.getPlayer())); + } + + private String format(Player player) { + return MMOCore.plugin.placeholderParser.parse(player, command.replace("%player%", player.getName())); + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/trigger/ExperienceTrigger.java b/src/net/Indyuce/mmocore/api/quest/trigger/ExperienceTrigger.java new file mode 100644 index 00000000..baafaef1 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/trigger/ExperienceTrigger.java @@ -0,0 +1,44 @@ +package net.Indyuce.mmocore.api.quest.trigger; + +import org.apache.commons.lang.Validate; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.ExperienceInfo; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.math.formula.RandomAmount; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class ExperienceTrigger extends Trigger { + private final RandomAmount amount; + private Profession profession; + + public ExperienceTrigger(MMOLineConfig config) { + super(config); + + config.validate("amount"); + + if (config.contains("profession")) { + String id = config.getString("profession").toLowerCase().replace("_", "-"); + Validate.isTrue(MMOCore.plugin.professionManager.has(id), "Could not find profession"); + profession = MMOCore.plugin.professionManager.get(id); + } + amount = new RandomAmount(config.getString("amount")); + } + + @Override + public void apply(PlayerData player) { + if (profession == null) + player.giveExperience(amount.calculateInt()); + else + player.getCollectionSkills().giveExperience(profession, amount.calculateInt()); + } + + /* + * experience must be accessible from the custom mine event. this method + * allows to generate an experience info instance for easy manipulations + */ + public ExperienceInfo newInfo() { + return new ExperienceInfo(amount.calculateInt(), profession); + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/trigger/ItemTrigger.java b/src/net/Indyuce/mmocore/api/quest/trigger/ItemTrigger.java new file mode 100644 index 00000000..76d215c9 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/trigger/ItemTrigger.java @@ -0,0 +1,27 @@ +package net.Indyuce.mmocore.api.quest.trigger; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.item.SmartGive; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class ItemTrigger extends Trigger { + private final Material material; + private final int amount; + + public ItemTrigger(MMOLineConfig config) { + super(config); + + config.validate("type"); + + material = Material.valueOf(config.getString("type").toUpperCase().replace("-", "_")); + amount = config.contains("amount") ? Math.max(1, config.getInt("amount")) : 1; + } + + @Override + public void apply(PlayerData player) { + new SmartGive(player.getPlayer()).give(new ItemStack(material, amount)); + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/trigger/ManaTrigger.java b/src/net/Indyuce/mmocore/api/quest/trigger/ManaTrigger.java new file mode 100644 index 00000000..512d0431 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/trigger/ManaTrigger.java @@ -0,0 +1,46 @@ +package net.Indyuce.mmocore.api.quest.trigger; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.math.formula.RandomAmount; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class ManaTrigger extends Trigger { + private final RandomAmount amount; + private final Operation operation; + + public ManaTrigger(MMOLineConfig config) { + super(config); + + config.validate("amount"); + amount = new RandomAmount(config.getString("amount")); + operation = config.contains("operation") ? Operation.valueOf(config.getString("operation").toUpperCase()) : Operation.GIVE; + } + + @Override + public void apply(PlayerData player) { + + /* + * give mana + */ + if (operation == Operation.GIVE) + player.giveMana(amount.calculate()); + + /* + * set mana + */ + else if (operation == Operation.SET) + player.setMana(amount.calculate()); + + /* + * take mana + */ + else + player.giveMana(-amount.calculate()); + } + + public enum Operation { + GIVE, + SET, + TAKE; + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/trigger/MessageTrigger.java b/src/net/Indyuce/mmocore/api/quest/trigger/MessageTrigger.java new file mode 100644 index 00000000..88cc8f22 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/trigger/MessageTrigger.java @@ -0,0 +1,27 @@ +package net.Indyuce.mmocore.api.quest.trigger; + +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class MessageTrigger extends Trigger { + private final String message; + + public MessageTrigger(MMOLineConfig config) { + super(config); + + config.validate("format"); + message = config.getString("format"); + } + + @Override + public void apply(PlayerData player) { + player.getPlayer().sendMessage(format(player.getPlayer())); + } + + private String format(Player player) { + return MMOCore.plugin.placeholderParser.parse(player, message.replace("%player%", player.getName())); + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/trigger/SoundTrigger.java b/src/net/Indyuce/mmocore/api/quest/trigger/SoundTrigger.java new file mode 100644 index 00000000..86692c9d --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/trigger/SoundTrigger.java @@ -0,0 +1,26 @@ +package net.Indyuce.mmocore.api.quest.trigger; + +import org.bukkit.Sound; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class SoundTrigger extends Trigger { + private final Sound sound; + private final float vol, pitch; + + public SoundTrigger(MMOLineConfig config) { + super(config); + + config.validate("sound"); + + sound = Sound.valueOf(config.getString("sound").toUpperCase().replace("-", "_")); + vol = config.contains("volume") ? (float) config.getDouble("volume") : 1f; + pitch = config.contains("pitch") ? (float) config.getDouble("pitch") : 1f; + } + + @Override + public void apply(PlayerData player) { + player.getPlayer().playSound(player.getPlayer().getLocation(), sound, vol, pitch); + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/trigger/StelliumTrigger.java b/src/net/Indyuce/mmocore/api/quest/trigger/StelliumTrigger.java new file mode 100644 index 00000000..d6e27773 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/trigger/StelliumTrigger.java @@ -0,0 +1,41 @@ +package net.Indyuce.mmocore.api.quest.trigger; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.math.formula.RandomAmount; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.quest.trigger.ManaTrigger.Operation; + +public class StelliumTrigger extends Trigger { + private final RandomAmount amount; + private final Operation operation; + + public StelliumTrigger(MMOLineConfig config) { + super(config); + + config.validate("amount"); + amount = new RandomAmount(config.getString("amount")); + operation = config.contains("operation") ? Operation.valueOf(config.getString("operation").toUpperCase()) : Operation.GIVE; + } + + @Override + public void apply(PlayerData player) { + + /* + * give mana + */ + if (operation == Operation.GIVE) + player.giveStellium(amount.calculate()); + + /* + * set mana + */ + else if (operation == Operation.SET) + player.setStellium(amount.calculate()); + + /* + * take mana + */ + else + player.giveStellium(-amount.calculate()); + } +} diff --git a/src/net/Indyuce/mmocore/api/quest/trigger/Trigger.java b/src/net/Indyuce/mmocore/api/quest/trigger/Trigger.java new file mode 100644 index 00000000..f95984d8 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/quest/trigger/Trigger.java @@ -0,0 +1,30 @@ +package net.Indyuce.mmocore.api.quest.trigger; + +import org.bukkit.Bukkit; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; + +public abstract class Trigger { + private long delay; + + public Trigger(MMOLineConfig config) { + if (config.contains("delay")) + delay = (long) (config.getDouble("delay") * 20); + } + + public boolean hasDelay() { + return delay > 0; + } + + public long getDelay() { + return delay; + } + + public void schedule(PlayerData player) { + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> apply(player), delay); + } + + public abstract void apply(PlayerData player); +} diff --git a/src/net/Indyuce/mmocore/api/skill/Skill.java b/src/net/Indyuce/mmocore/api/skill/Skill.java new file mode 100644 index 00000000..9345114d --- /dev/null +++ b/src/net/Indyuce/mmocore/api/skill/Skill.java @@ -0,0 +1,229 @@ +package net.Indyuce.mmocore.api.skill; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.math.formula.IntegerLinearValue; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.SkillResult.CancelReason; + +public abstract class Skill { + private final String id; + + private String name; + private ItemStack icon = new ItemStack(Material.BOOK); + private List lore; + private boolean passive; + + private final Map modifiers = new HashMap<>(); + private final Skill skill = this; + + protected static final Random random = new Random(); + + public Skill() { + id = getClass().getSimpleName().toUpperCase(); + name = getClass().getSimpleName().replace("_", " "); + + Validate.notNull(id, "ID cannot be null"); + Validate.notNull(id, "Name cannot be null"); + } + + public Skill(String id) { + this.id = id.toUpperCase().replace(" ", "_").replace("-", "_"); + } + + public void setName(String name) { + this.name = name; + } + + public void setMaterial(Material material) { + setIcon(new ItemStack(material)); + } + + public void setIcon(ItemStack item) { + this.icon = item; + } + + public void setLore(String... lore) { + setLore(Arrays.asList(lore)); + } + + public void setLore(List lore) { + this.lore = lore; + } + + public void setPassive() { + passive = true; + } + + public String getName() { + return name; + } + + public String getId() { + return id; + } + + public String getLowerCaseId() { + return id.replace("_", "-").toLowerCase(); + } + + public List getLore() { + return lore; + } + + public ItemStack getIcon() { + return icon.clone(); + } + + public boolean isPassive() { + return passive; + } + + public boolean hasModifier(String modifier) { + return modifiers.containsKey(modifier); + } + + public void addModifier(String modifier, LinearValue linear) { + modifiers.put(modifier, linear); + } + + public LinearValue getModifierInfo(String modifier) { + return modifiers.get(modifier); + } + + public double getModifier(String modifier, int level) { + return modifiers.get(modifier).calculate(level); + } + + public Set getModifiers() { + return modifiers.keySet(); + } + + public void update(FileConfiguration config) { + name = config.getString("name"); + lore = config.getStringList("lore"); + icon = MMOCoreUtils.readIcon(config.getString("material")); + + for (String modifier : modifiers.keySet()) + if (config.contains(modifier)) + modifiers.put(modifier, readLinearValue(modifiers.get(modifier), config.getConfigurationSection(modifier))); + } + + private LinearValue readLinearValue(LinearValue current, ConfigurationSection config) { + return current instanceof IntegerLinearValue ? new IntegerLinearValue(config) : new LinearValue(config); + } + + /* + * not overriden for passive skills therefore not abstract. + */ + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + SkillResult cast = new SkillResult(data, skill); + cast.abort(CancelReason.OTHER); + return cast; + } + + public SkillInfo newSkillInfo(ConfigurationSection config) { + return new SkillInfo(config); + } + + public SkillInfo newSkillInfo(int level) { + return new SkillInfo(level); + } + + public class SkillInfo { + private final int level, max; + private final Map modifiers = new HashMap<>(skill.modifiers); + + /* + * class used to save information about skills IN A CLASS CONTEXT i.e at + * which level the skill can be unlocked, etc. + */ + public SkillInfo(int level) { + this.level = level; + this.max = 0; + } + + public SkillInfo(ConfigurationSection config) { + level = config.getInt("level"); + max = config.getInt("max-level"); + + for (String key : config.getKeys(false)) + if (config.get(key) instanceof ConfigurationSection && modifiers.containsKey(key)) + modifiers.put(key, readLinearValue(modifiers.get(key), config.getConfigurationSection(key))); + } + + public Skill getSkill() { + return skill; + } + + public int getUnlockLevel() { + return level; + } + + public boolean hasMaxLevel() { + return max > 0; + } + + public int getMaxLevel() { + return max; + } + + /* + * this method can only OVERRIDE default modifiers + */ + public void addModifier(String modifier, LinearValue linear) { + if (modifiers.containsKey(modifier)) + modifiers.put(modifier, linear); + } + + public double getModifier(String modifier, int level) { + return modifiers.get(modifier).calculate(level); + } + + public boolean isUnlocked(PlayerData profess) { + return profess.getLevel() >= level; + } + + public List calculateLore(PlayerData data) { + return calculateLore(data, data.getSkillLevel(skill)); + } + + public List calculateLore(PlayerData data, int x) { + List list = new ArrayList<>(); + + Map placeholders = calculateModifiers(x); + placeholders.put("mana_name", data.getProfess().getManaDisplay().getName()); + lore.forEach(str -> list.add(applyPlaceholders(placeholders, str))); + + return list; + } + + private String applyPlaceholders(Map placeholders, String str) { + while (str.contains("{") && str.substring(str.indexOf("{")).contains("}")) { + String holder = str.substring(str.indexOf("{") + 1, str.indexOf("}")); + str = str.replace("{" + holder + "}", placeholders.containsKey(holder) ? placeholders.get(holder) : "PHE"); + } + return str; + } + + private Map calculateModifiers(int x) { + Map map = new HashMap<>(); + modifiers.keySet().forEach(modifier -> map.put(modifier, modifiers.get(modifier).getDisplay(x))); + return map; + } + } +} diff --git a/src/net/Indyuce/mmocore/api/skill/SkillResult.java b/src/net/Indyuce/mmocore/api/skill/SkillResult.java new file mode 100644 index 00000000..8ae895c0 --- /dev/null +++ b/src/net/Indyuce/mmocore/api/skill/SkillResult.java @@ -0,0 +1,76 @@ +package net.Indyuce.mmocore.api.skill; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill.SkillInfo; + +public class SkillResult { + private final SkillInfo skill; + private final int level; + + private final double mana, cooldown; + private CancelReason cancelReason; + + public SkillResult(PlayerData data, SkillInfo skill) { + this.skill = skill; + + level = data.getSkillLevel(skill.getSkill()); + cooldown = (skill.getSkill().hasModifier("cooldown") ? data.getSkillData().getCooldown(skill) : 0); + mana = (skill.getSkill().hasModifier("mana") ? skill.getModifier("mana", level) : 0); + cancelReason = cooldown > 0 ? CancelReason.COOLDOWN : mana > data.getMana() ? CancelReason.MANA : null; + } + + public SkillResult(PlayerData data, SkillInfo skill, CancelReason reason) { + this.skill = skill; + this.cancelReason = reason; + + level = data.getSkillLevel(skill.getSkill()); + cooldown = (skill.getSkill().hasModifier("cooldown") ? data.getSkillData().getCooldown(skill) : 0); + mana = (skill.getSkill().hasModifier("mana") ? skill.getModifier("mana", level) : 0); + } + + public Skill getSkill() { + return skill.getSkill(); + } + + public SkillInfo getInfo() { + return skill; + } + + public int getLevel() { + return level; + } + + public double getManaCost() { + return mana; + } + + public double getCooldown() { + return cooldown; + } + + public boolean isSuccessful() { + return cancelReason == null; + } + + public CancelReason getCancelReason() { + return cancelReason; + } + + public void abort() { + abort(CancelReason.OTHER); + } + + public void abort(CancelReason reason) { + cancelReason = reason; + } + + public double getModifier(String modifier) { + return skill.getModifier(modifier, level); + } + + public enum CancelReason { + MANA, + COOLDOWN, + OTHER; + } +} diff --git a/src/net/Indyuce/mmocore/api/skill/TargetSkillResult.java b/src/net/Indyuce/mmocore/api/skill/TargetSkillResult.java new file mode 100644 index 00000000..f749954e --- /dev/null +++ b/src/net/Indyuce/mmocore/api/skill/TargetSkillResult.java @@ -0,0 +1,28 @@ +package net.Indyuce.mmocore.api.skill; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.util.RayTraceResult; + +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill.SkillInfo; + +public class TargetSkillResult extends SkillResult { + private LivingEntity target; + + public TargetSkillResult(PlayerData data, SkillInfo skill, double range) { + super(data, skill); + + if (isSuccessful()) { + RayTraceResult result = data.getPlayer().getWorld().rayTraceEntities(data.getPlayer().getEyeLocation(), data.getPlayer().getEyeLocation().getDirection(), range, (entity) -> MMOCoreUtils.canTarget(data.getPlayer(), entity)); + if (result == null) + abort(CancelReason.OTHER); + else + target = (LivingEntity) result.getHitEntity(); + } + } + + public LivingEntity getTarget() { + return target; + } +} diff --git a/src/net/Indyuce/mmocore/command/AttributesCommand.java b/src/net/Indyuce/mmocore/command/AttributesCommand.java new file mode 100644 index 00000000..a517d89b --- /dev/null +++ b/src/net/Indyuce/mmocore/command/AttributesCommand.java @@ -0,0 +1,23 @@ +package net.Indyuce.mmocore.command; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class AttributesCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "This command is for players only."); + return true; + } + + InventoryManager.ATTRIBUTE_VIEW.newInventory(PlayerData.get((Player) sender)).open(); + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/ClassCommand.java b/src/net/Indyuce/mmocore/command/ClassCommand.java new file mode 100644 index 00000000..af3daf8d --- /dev/null +++ b/src/net/Indyuce/mmocore/command/ClassCommand.java @@ -0,0 +1,32 @@ +package net.Indyuce.mmocore.command; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class ClassCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!sender.hasPermission("mmocore.class-select")) + return false; + + Player player = args.length > 0 && sender.hasPermission("mmocore.admin") ? Bukkit.getPlayer(args[0]) : sender instanceof Player ? (Player) sender : null; + if (player == null) { + sender.sendMessage(ChatColor.RED + "Please specify a valid player."); + return true; + } + + PlayerData data = PlayerData.get(player); + if (data.getProfess().getSubclasses().stream().filter(sub -> sub.getLevel() <= data.getLevel()).count() > 0) + InventoryManager.SUBCLASS_SELECT.newInventory(data).open(); + else + InventoryManager.CLASS_SELECT.newInventory(data).open(); + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/DepositCommand.java b/src/net/Indyuce/mmocore/command/DepositCommand.java new file mode 100644 index 00000000..dc70d84d --- /dev/null +++ b/src/net/Indyuce/mmocore/command/DepositCommand.java @@ -0,0 +1,43 @@ +package net.Indyuce.mmocore.command; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.gui.eco.DepositMenu; + +public class DepositCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!sender.hasPermission("mmocore.currency")) + return false; + + Player player = args.length > 0 && sender.hasPermission("mmocore.admin") ? Bukkit.getPlayer(args[0]) : sender instanceof Player ? (Player) sender : null; + if (player == null) { + sender.sendMessage(ChatColor.RED + "Please specify a valid player."); + return true; + } + + // if (sender instanceof Player) + // if (!isNearEnderchest(((Player) sender).getLocation())) { + // sender.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("stand-near-enderchest")); + // return true; + // } + + new DepositMenu(player).open(); + return true; + } + + // private boolean isNearEnderchest(Location loc) { + // for (int x = -5; x < 6; x++) + // for (int y = -5; y < 6; y++) + // for (int z = -5; z < 6; z++) + // if (loc.clone().add(x, y, z).getBlock().getType() == + // Material.ENDER_CHEST) + // return true; + // return false; + // } +} diff --git a/src/net/Indyuce/mmocore/command/FriendsCommand.java b/src/net/Indyuce/mmocore/command/FriendsCommand.java new file mode 100644 index 00000000..04808e37 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/FriendsCommand.java @@ -0,0 +1,58 @@ +package net.Indyuce.mmocore.command; + +import java.util.UUID; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.OfflinePlayerData; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.social.FriendRequest; +import net.Indyuce.mmocore.api.player.social.Request; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class FriendsCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "This command is for players only."); + return true; + } + + if (args.length > 1) { + UUID uuid; + try { + uuid = UUID.fromString(args[1]); + } catch (Exception e) { + return true; + } + + Request request = MMOCore.plugin.requestManager.getRequest(uuid); + if (request == null || !(request instanceof FriendRequest)) + return true; + + if (request.isTimedOut()) { + MMOCore.plugin.requestManager.unregisterRequest(uuid); + return true; + } + + if (new OfflinePlayerData(((Player) sender).getUniqueId()).hasFriend(uuid)) { + MMOCore.plugin.requestManager.unregisterRequest(uuid); + return true; + } + + if (args[0].equalsIgnoreCase("accept")) + ((FriendRequest) request).accept(); + if (args[0].equalsIgnoreCase("deny")) + ((FriendRequest) request).deny(); + return true; + } + + InventoryManager.FRIEND_LIST.newInventory(PlayerData.get((Player) sender)).open(); + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/MMOCoreCommand.java b/src/net/Indyuce/mmocore/command/MMOCoreCommand.java new file mode 100644 index 00000000..25201187 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/MMOCoreCommand.java @@ -0,0 +1,55 @@ +package net.Indyuce.mmocore.command; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; + +import net.Indyuce.mmocore.command.api.CommandRoot; +import net.Indyuce.mmocore.command.rpg.CoinsCommandMap; +import net.Indyuce.mmocore.command.rpg.NoteCommandMap; +import net.Indyuce.mmocore.command.rpg.ReloadCommandMap; +import net.Indyuce.mmocore.command.rpg.admin.AdminCommandMap; +import net.Indyuce.mmocore.command.rpg.booster.BoosterCommandMap; +import net.Indyuce.mmocore.command.rpg.waypoint.WaypointsCommandMap; + +public class MMOCoreCommand extends CommandRoot implements CommandExecutor, TabCompleter { + public MMOCoreCommand() { + super("mmocore"); + + addFloor(new ReloadCommandMap(this)); + addFloor(new CoinsCommandMap(this)); + addFloor(new NoteCommandMap(this)); + addFloor(new AdminCommandMap(this)); + addFloor(new BoosterCommandMap(this)); + addFloor(new WaypointsCommandMap(this)); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + return CommandResult.THROW_USAGE; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!sender.hasPermission("mmocore.admin")) + return false; + + executeCommand(sender, args); + return true; + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { + if (!sender.hasPermission("mmocore.admin")) + return new ArrayList<>(); + + CommandReader reader = readCommand(args); + List list = reader.readTabCompletion(); + return args[args.length - 1].isEmpty() ? list : list.stream().filter(string -> string.toLowerCase().startsWith(args[args.length - 1].toLowerCase())).collect(Collectors.toList()); + } +} diff --git a/src/net/Indyuce/mmocore/command/PartyCommand.java b/src/net/Indyuce/mmocore/command/PartyCommand.java new file mode 100644 index 00000000..6eec1c88 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/PartyCommand.java @@ -0,0 +1,63 @@ +package net.Indyuce.mmocore.command; + +import java.util.UUID; + +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.social.PartyInvite; +import net.Indyuce.mmocore.api.player.social.Request; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class PartyCommand implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "This command is for players only."); + return true; + } + + if (args.length > 1) { + UUID uuid; + try { + uuid = UUID.fromString(args[1]); + } catch (Exception e) { + return true; + } + + Request request = MMOCore.plugin.requestManager.getRequest(uuid); + if (request == null || !(request instanceof PartyInvite)) + return true; + + if (request.isTimedOut()) { + MMOCore.plugin.requestManager.unregisterRequest(uuid); + return true; + } + + if (!MMOCore.plugin.partyManager.isRegistered(((PartyInvite) request).getParty())) { + MMOCore.plugin.requestManager.unregisterRequest(uuid); + return true; + } + + if (args[0].equalsIgnoreCase("accept")) + ((PartyInvite) request).accept(); + if (args[0].equalsIgnoreCase("deny")) + ((PartyInvite) request).deny(); + return true; + } + + PlayerData data = PlayerData.get((OfflinePlayer) sender); + if (data.hasParty()) + InventoryManager.PARTY_VIEW.newInventory(data).open(); + else + InventoryManager.PARTY_CREATION.newInventory(data).open(); + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/PlayerStatsCommand.java b/src/net/Indyuce/mmocore/command/PlayerStatsCommand.java new file mode 100644 index 00000000..4b525c96 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/PlayerStatsCommand.java @@ -0,0 +1,23 @@ +package net.Indyuce.mmocore.command; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class PlayerStatsCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "This command is for players only."); + return true; + } + + InventoryManager.PLAYER_STATS.newInventory(PlayerData.get((Player) sender)).open(); + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/QuestsCommand.java b/src/net/Indyuce/mmocore/command/QuestsCommand.java new file mode 100644 index 00000000..6cf0de36 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/QuestsCommand.java @@ -0,0 +1,18 @@ +package net.Indyuce.mmocore.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class QuestsCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (sender instanceof Player) + InventoryManager.QUEST_LIST.newInventory(PlayerData.get((Player) sender)).open(); + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/SkillsCommand.java b/src/net/Indyuce/mmocore/command/SkillsCommand.java new file mode 100644 index 00000000..17ceac57 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/SkillsCommand.java @@ -0,0 +1,26 @@ +package net.Indyuce.mmocore.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class SkillsCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (sender instanceof Player) { + PlayerData data = PlayerData.get((Player) sender); + if (data.getProfess().getSkills().size() < 1) { + sender.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("no-class-skill")); + return true; + } + + InventoryManager.SKILL_LIST.newInventory(data).open(); + } + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/WaypointsCommand.java b/src/net/Indyuce/mmocore/command/WaypointsCommand.java new file mode 100644 index 00000000..c55d8630 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/WaypointsCommand.java @@ -0,0 +1,18 @@ +package net.Indyuce.mmocore.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class WaypointsCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (sender instanceof Player && sender.hasPermission("mmocore.waypoints")) + InventoryManager.WAYPOINTS.newInventory(PlayerData.get((Player) sender)).open(); + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/WithdrawCommand.java b/src/net/Indyuce/mmocore/command/WithdrawCommand.java new file mode 100644 index 00000000..77e3f66c --- /dev/null +++ b/src/net/Indyuce/mmocore/command/WithdrawCommand.java @@ -0,0 +1,58 @@ +package net.Indyuce.mmocore.command; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.eco.Withdraw; + +public class WithdrawCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!sender.hasPermission("mmocore.currency")) + return false; + + String playerArgument = args.length < 2 ? null : args[args.length - 2]; + String amountArgument = args.length == 0 ? "0" : args[args.length - 1]; + + Player player = playerArgument != null && sender.hasPermission("mmocore.admin") ? Bukkit.getPlayer(playerArgument) : sender instanceof Player ? (Player) sender : null; + if (player == null) { + sender.sendMessage(ChatColor.RED + "Please specify a valid player."); + return true; + } + + int amount = 0; + try { + amount = Integer.parseInt(amountArgument); + Validate.isTrue(amount >= 0); + } catch (IllegalArgumentException exception) { + sender.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("wrong-number", "arg", "" + args[0])); + return true; + } + + Withdraw request = new Withdraw(player); + + if (amount == 0) { + request.open(); + return true; + } + + int left = (int) MMOCore.plugin.economy.getEconomy().getBalance(player) - amount; + if (left < 0) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-enough-money", "left", "" + -left)); + return true; + } + + MMOCore.plugin.economy.getEconomy().withdrawPlayer(player, amount); + request.withdrawAlgorythm(amount); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("withdrew", "worth", "" + amount)); + return true; + } +} diff --git a/src/net/Indyuce/mmocore/command/api/CommandEnd.java b/src/net/Indyuce/mmocore/command/api/CommandEnd.java new file mode 100644 index 00000000..ea79017d --- /dev/null +++ b/src/net/Indyuce/mmocore/command/api/CommandEnd.java @@ -0,0 +1,27 @@ +package net.Indyuce.mmocore.command.api; + +import java.util.ArrayList; +import java.util.List; + +public abstract class CommandEnd extends CommandMap { + private final List parameters = new ArrayList<>(); + + public CommandEnd(CommandMap parent, String id) { + super(parent, id); + } + + public void addParameter(Parameter param) { + parameters.add(param); + } + + public List getParameters() { + return parameters; + } + + public String formatParameters() { + String str = ""; + for (Parameter param : parameters) + str += param.getKey() + " "; + return str; + } +} diff --git a/src/net/Indyuce/mmocore/command/api/CommandMap.java b/src/net/Indyuce/mmocore/command/api/CommandMap.java new file mode 100644 index 00000000..9c2608ae --- /dev/null +++ b/src/net/Indyuce/mmocore/command/api/CommandMap.java @@ -0,0 +1,114 @@ +package net.Indyuce.mmocore.command.api; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bukkit.command.CommandSender; + +public abstract class CommandMap { + private final Map floors = new HashMap<>(); + private final String id; + private final CommandMap parent; + + public CommandMap(CommandMap parent, String id) { + this.id = id; + this.parent = parent; + } + + public String getId() { + return id; + } + + public String getPath() { + return (hasParent() ? parent.getPath() + " " : "") + getId(); + } + + public Collection getFloors() { + return floors.values(); + } + + public boolean hasParent() { + return parent != null; + } + + public CommandMap getFloor(String str) { + return floors.get(str.toLowerCase()); + } + + public boolean hasFloor(String str) { + return floors.containsKey(str.toLowerCase()); + } + + public void addFloor(CommandMap floor) { + floors.put(floor.getId(), floor); + } + + public abstract CommandResult execute(CommandSender sender, String[] args); + + public Set getKeys() { + return floors.keySet(); + } + + public List calculateTabCompletion(int n) { + + /* + * add extra floor keys + */ + List list = new ArrayList<>(); + getKeys().forEach(key -> list.add(key)); + + /* + * add final arguments if needed. + */ + if (isEnd()) + if (((CommandEnd) this).getParameters().size() > n) + ((CommandEnd) this).getParameters().get(n).autoComplete(list); + + return list; + } + + public List calculateUsageList() { + return calculateUsageList(getPath(), new ArrayList<>()); + } + + private List calculateUsageList(String path, List usages) { + + /* + * calculate for final arguments + */ + if (isEnd()) + usages.add(path + " " + ((CommandEnd) this).formatParameters()); + + for (CommandMap floor : getFloors()) + floor.calculateUsageList(new String(path + " " + floor.getId()), usages); + + return usages; + } + + public boolean isEnd() { + return this instanceof CommandEnd; + } + + public enum CommandResult { + + /* + * command cast successfully, nothing to do + */ + SUCCESS, + + /* + * command cast unsuccessfully, display message handled via command + * floor + */ + FAILURE, + + /* + * send command usage + */ + THROW_USAGE; + } +} diff --git a/src/net/Indyuce/mmocore/command/api/CommandRoot.java b/src/net/Indyuce/mmocore/command/api/CommandRoot.java new file mode 100644 index 00000000..27650e32 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/api/CommandRoot.java @@ -0,0 +1,61 @@ +package net.Indyuce.mmocore.command.api; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +public abstract class CommandRoot extends CommandMap { + public CommandRoot(String id) { + super(null, id); + } + + public void executeCommand(CommandSender sender, String[] args) { + CommandMap read = readCommand(args).read(); + + if (read.execute(sender, args) == CommandResult.THROW_USAGE) + read.calculateUsageList().forEach(str -> sender.sendMessage(ChatColor.YELLOW + "/" + str)); + } + + public CommandReader readCommand(String[] args) { + return new CommandReader(this, args); + } + + public class CommandReader { + private CommandMap current; + private int parameter = 0; + + public CommandReader(CommandRoot begin, String[] args) { + this.current = begin; + + for (String arg : args) + + /* + * check if current command map has the corresponding arg, if so + * let the next map handle the command. + */ + if (parameter == 0 && current.hasFloor(arg)) + current = current.getFloor(arg); + + /* + * if the plugin cannot find a command map higher, then the + * current map will handle the command. + */ + else + parameter++; + } + + public CommandMap read() { + return current; + } + + public int extraCount() { + return parameter; + } + + public List readTabCompletion() { + return parameter < 0 ? new ArrayList<>() : current.calculateTabCompletion(Math.max(0, parameter - 1)); + } + } +} diff --git a/src/net/Indyuce/mmocore/command/api/Parameter.java b/src/net/Indyuce/mmocore/command/api/Parameter.java new file mode 100644 index 00000000..043cddee --- /dev/null +++ b/src/net/Indyuce/mmocore/command/api/Parameter.java @@ -0,0 +1,59 @@ +package net.Indyuce.mmocore.command.api; + +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.util.Consumer; + +import net.Indyuce.mmocore.MMOCore; + +public class Parameter { + private final String key; + // private final ParameterType type;ParameterType type, + private final Consumer> autoComplete; + + public static final Parameter PROFESSION = new Parameter("", (list) -> { + MMOCore.plugin.professionManager.getAll().forEach(profession -> list.add(profession.getId())); + list.add("main"); + }); + public static final Parameter PLAYER = new Parameter("", (list) -> Bukkit.getOnlinePlayers().forEach(online -> list.add(online.getName()))); + public static final Parameter PLAYER_OPTIONAL = new Parameter("(player)", (list) -> Bukkit.getOnlinePlayers().forEach(online -> list.add(online.getName()))); + public static final Parameter AMOUNT = new Parameter("", (list) -> { + for (int j = 0; j <= 10; j++) + list.add("" + j); + }); + + public Parameter(String key, Consumer> autoComplete) { + this.key = key; + // this.type = type; + this.autoComplete = autoComplete; + } + + public String getKey() { + return key; + } + + // public ParameterType getType() { + // return type; + // } + + public void autoComplete(List list) { + autoComplete.accept(list); + } + + // public enum ParameterType { + // OPTIONAL('(', ')'), + // REQUIRED('<', '>'); + // + // private final char prefix, suffix; + // + // private ParameterType(char... chars) { + // this.prefix = chars[0]; + // this.suffix = chars[1]; + // } + // + // public String format(Parameter parameter) { + // return prefix + parameter.getKey() + suffix; + // } + // } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/command/rpg/CoinsCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/CoinsCommandMap.java new file mode 100644 index 00000000..fe261f46 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/CoinsCommandMap.java @@ -0,0 +1,45 @@ +package net.Indyuce.mmocore.command.rpg; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.item.CurrencyItem; +import net.Indyuce.mmocore.api.item.SmartGive; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class CoinsCommandMap extends CommandEnd { + public CoinsCommandMap(CommandMap parent) { + super(parent, "coins"); + + addParameter(Parameter.PLAYER); + addParameter(Parameter.AMOUNT); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 3) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[1]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[1] + "."); + return CommandResult.FAILURE; + } + + int amount; + try { + amount = Integer.parseInt(args[2]); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + args[2] + " is not a valid number."); + return CommandResult.FAILURE; + } + + new SmartGive(player).give(new CurrencyItem("GOLD_COIN", 1, amount).build()); + return CommandResult.SUCCESS; + } + +} diff --git a/src/net/Indyuce/mmocore/command/rpg/NoteCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/NoteCommandMap.java new file mode 100644 index 00000000..5c91031d --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/NoteCommandMap.java @@ -0,0 +1,47 @@ +package net.Indyuce.mmocore.command.rpg; + +import java.util.Arrays; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.item.CurrencyItem; +import net.Indyuce.mmocore.api.item.SmartGive; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class NoteCommandMap extends CommandEnd { + public NoteCommandMap(CommandMap parent) { + super(parent, "note"); + + addParameter(Parameter.PLAYER); + addParameter(new Parameter("", (list) -> list.addAll(Arrays.asList("10", "20", "30", "40", "50", "60", "70", "80", "90", "100")))); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 3) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[1]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[1] + "."); + return CommandResult.FAILURE; + } + + int worth; + try { + worth = Integer.parseInt(args[2]); + } catch (NumberFormatException exception) { + sender.sendMessage(ChatColor.RED + args[2] + " is not a valid number."); + return CommandResult.FAILURE; + } + + new SmartGive(player).give(new CurrencyItem("NOTE", worth).build()); + return CommandResult.SUCCESS; + } + +} diff --git a/src/net/Indyuce/mmocore/command/rpg/ReloadCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/ReloadCommandMap.java new file mode 100644 index 00000000..aeb87513 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/ReloadCommandMap.java @@ -0,0 +1,33 @@ +package net.Indyuce.mmocore.command.rpg; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; + +public class ReloadCommandMap extends CommandEnd { + public ReloadCommandMap(CommandMap parent) { + super(parent, "reload"); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + + sender.sendMessage(ChatColor.YELLOW + "Reloading " + MMOCore.plugin.getName() + " " + MMOCore.plugin.getDescription().getVersion() + "..."); + long ms = System.currentTimeMillis(); + + MMOCore.plugin.reloadConfig(); + MMOCore.plugin.reloadPlugin(); + + PlayerData.getAll().forEach(data -> data.update()); + + ms = System.currentTimeMillis() - ms; + sender.sendMessage(ChatColor.YELLOW + MMOCore.plugin.getName() + " " + MMOCore.plugin.getDescription().getVersion() + " successfully reloaded."); + sender.sendMessage(ChatColor.YELLOW + "Time Taken: " + ChatColor.GOLD + ms + ChatColor.YELLOW + "ms (" + ChatColor.GOLD + (double) ms / 50 + ChatColor.YELLOW + " ticks)"); + return CommandResult.SUCCESS; + } + +} diff --git a/src/net/Indyuce/mmocore/command/rpg/admin/AdminCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/admin/AdminCommandMap.java new file mode 100644 index 00000000..e958c907 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/admin/AdminCommandMap.java @@ -0,0 +1,29 @@ +package net.Indyuce.mmocore.command.rpg.admin; + +import org.bukkit.command.CommandSender; + +import net.Indyuce.mmocore.command.api.CommandMap; + +public class AdminCommandMap extends CommandMap { + public AdminCommandMap(CommandMap parent) { + super(parent, "admin"); + + addFloor(new NoCooldownCommandMap(this)); + addFloor(new ResetCommandMap(this)); + addFloor(new InfoCommandMap(this)); + addFloor(new ClassCommandMap(this)); + + addFloor(new ExperienceCommandMap(this)); + addFloor(new LevelCommandMap(this)); + + addFloor(new PointsCommandMap("skill", this, (data, points) -> data.setSkillPoints(points), (data, points) -> data.giveSkillPoints(points), (data) -> data.getSkillPoints())); + addFloor(new PointsCommandMap("class", this, (data, points) -> data.setClassPoints(points), (data, points) -> data.giveClassPoints(points), (data) -> data.getClassPoints())); + addFloor(new PointsCommandMap("attribute", this, (data, points) -> data.setAttributePoints(points), (data, points) -> data.giveAttributePoints(points), (data) -> data.getAttributePoints())); + addFloor(new PointsCommandMap("attr-realloc", this, (data, points) -> data.setAttributeReallocationPoints(points), (data, points) -> data.giveAttributeReallocationPoints(points), (data) -> data.getAttributeReallocationPoints())); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + return CommandResult.THROW_USAGE; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/admin/ClassCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/admin/ClassCommandMap.java new file mode 100644 index 00000000..9052dcae --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/admin/ClassCommandMap.java @@ -0,0 +1,47 @@ +package net.Indyuce.mmocore.command.rpg.admin; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class ClassCommandMap extends CommandEnd { + public ClassCommandMap(CommandMap parent) { + super(parent, "class"); + + addParameter(Parameter.PLAYER); + addParameter(new Parameter("", (list) -> MMOCore.plugin.classManager.getAll().forEach(profess -> list.add(profess.getId())))); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 4) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[2]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[2] + "."); + return CommandResult.FAILURE; + } + + String format = args[3].toUpperCase().replace("-", "_"); + if (!MMOCore.plugin.classManager.has(format)) { + sender.sendMessage(ChatColor.RED + "Could not find class " + format + "."); + return CommandResult.FAILURE; + } + + PlayerClass profess = MMOCore.plugin.classManager.get(format); + + PlayerData data = PlayerData.get(player); + data.setClass(profess); + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " is now a " + ChatColor.GOLD + profess.getName() + ChatColor.YELLOW + "."); + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/admin/ExperienceCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/admin/ExperienceCommandMap.java new file mode 100644 index 00000000..c349d82e --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/admin/ExperienceCommandMap.java @@ -0,0 +1,62 @@ +package net.Indyuce.mmocore.command.rpg.admin; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class ExperienceCommandMap extends CommandEnd { + public ExperienceCommandMap(CommandMap parent) { + super(parent, "exp"); + + addParameter(Parameter.PLAYER); + addParameter(Parameter.PROFESSION); + addParameter(Parameter.AMOUNT); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 5) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[2]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[2] + "."); + return CommandResult.FAILURE; + } + + int amount; + try { + amount = Integer.parseInt(args[4]); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + args[4] + " is not a valid number."); + return CommandResult.FAILURE; + } + + if (args[3].equalsIgnoreCase("main")) { + PlayerData data = PlayerData.get(player); + data.giveExperience(amount); + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " now has " + ChatColor.GOLD + data.getExperience() + ChatColor.YELLOW + " EXP."); + return CommandResult.SUCCESS; + } + + String format = args[3].toLowerCase().replace("_", "-"); + if (!MMOCore.plugin.professionManager.has(format)) { + sender.sendMessage(ChatColor.RED + format + " is not a valid profession."); + return CommandResult.FAILURE; + } + + Profession profession = MMOCore.plugin.professionManager.get(format); + PlayerData data = PlayerData.get(player); + data.getCollectionSkills().giveExperience(profession, amount); + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " now has " + ChatColor.GOLD + data.getCollectionSkills().getExperience(profession) + ChatColor.YELLOW + " EXP in " + profession.getName() + "."); + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/admin/InfoCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/admin/InfoCommandMap.java new file mode 100644 index 00000000..615df88b --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/admin/InfoCommandMap.java @@ -0,0 +1,46 @@ +package net.Indyuce.mmocore.command.rpg.admin; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class InfoCommandMap extends CommandEnd { + public InfoCommandMap(CommandMap parent) { + super(parent, "info"); + + addParameter(Parameter.PLAYER); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 3) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[2]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[2] + "."); + return CommandResult.FAILURE; + } + + PlayerData playerData = PlayerData.get(player); + sender.sendMessage(ChatColor.YELLOW + "----------------------------------------------------"); + sender.sendMessage(ChatColor.YELLOW + "Class: " + ChatColor.GOLD + playerData.getProfess().getName()); + sender.sendMessage(ChatColor.YELLOW + "Level: " + ChatColor.GOLD + playerData.getLevel()); + sender.sendMessage(ChatColor.YELLOW + "Experience: " + ChatColor.GOLD + playerData.getExperience() + ChatColor.YELLOW + " / " + ChatColor.GOLD + MMOCore.plugin.configManager.getNeededExperience(playerData.getLevel() + 1)); + sender.sendMessage(ChatColor.YELLOW + "Class Points: " + ChatColor.GOLD + playerData.getClassPoints()); + sender.sendMessage(ChatColor.YELLOW + "Quests: " + ChatColor.GOLD + playerData.getQuestData().getFinishedQuests().size() + ChatColor.YELLOW + " / " + ChatColor.GOLD + MMOCore.plugin.questManager.getAll().size()); + sender.sendMessage(ChatColor.YELLOW + "----------------------------------------------------"); + for (Profession profession : MMOCore.plugin.professionManager.getAll()) + sender.sendMessage(ChatColor.YELLOW + profession.getName() + ": Lvl " + ChatColor.GOLD + playerData.getCollectionSkills().getLevel(profession) + ChatColor.YELLOW + " - " + ChatColor.GOLD + playerData.getCollectionSkills().getExperience(profession) + ChatColor.YELLOW + " / " + ChatColor.GOLD + MMOCore.plugin.configManager.getNeededExperience(playerData.getCollectionSkills().getLevel(profession) + 1)); + sender.sendMessage(ChatColor.YELLOW + "----------------------------------------------------"); + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/admin/LevelCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/admin/LevelCommandMap.java new file mode 100644 index 00000000..2471869e --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/admin/LevelCommandMap.java @@ -0,0 +1,68 @@ +package net.Indyuce.mmocore.command.rpg.admin; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class LevelCommandMap extends CommandEnd { + public LevelCommandMap(CommandMap parent) { + super(parent, "level"); + + addParameter(Parameter.PLAYER); + addParameter(Parameter.PROFESSION); + addParameter(Parameter.AMOUNT); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 5) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[2]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[2] + "."); + return CommandResult.FAILURE; + } + + int amount = 0; + try { + amount = Integer.parseInt(args[4]); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + args[4] + " is not a valid number."); + return CommandResult.FAILURE; + } + + if (args[3].equalsIgnoreCase("main")) { + PlayerData data = PlayerData.get(player); + + int total = 0; + while (amount-- > 0) + total += MMOCore.plugin.configManager.getNeededExperience(data.getLevel() + amount + 1); + data.giveExperience(total); + + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " is now Lvl " + ChatColor.GOLD + data.getLevel() + ChatColor.YELLOW + "."); + return CommandResult.SUCCESS; + } + + String format = args[3].toLowerCase().replace("_", "-"); + if (!MMOCore.plugin.professionManager.has(format)) { + sender.sendMessage(ChatColor.RED + format + " is not a valid profession."); + return CommandResult.FAILURE; + } + + Profession profession = MMOCore.plugin.professionManager.get(format); + PlayerData data = PlayerData.get(player); + while (amount-- > 0) + data.getCollectionSkills().giveExperience(profession, MMOCore.plugin.configManager.getNeededExperience(data.getCollectionSkills().getLevel(profession) + 1)); + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " is now Lvl " + ChatColor.GOLD + data.getCollectionSkills().getLevel(profession) + ChatColor.YELLOW + " in " + profession.getName() + "."); + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/admin/NoCooldownCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/admin/NoCooldownCommandMap.java new file mode 100644 index 00000000..490993ca --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/admin/NoCooldownCommandMap.java @@ -0,0 +1,36 @@ +package net.Indyuce.mmocore.command.rpg.admin; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class NoCooldownCommandMap extends CommandEnd { + public NoCooldownCommandMap(CommandMap parent) { + super(parent, "nocd"); + + addParameter(Parameter.PLAYER); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 3) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[2]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[2] + "."); + return CommandResult.FAILURE; + } + + PlayerData data = PlayerData.get(player); + data.nocd = !data.nocd; + sender.sendMessage(ChatColor.YELLOW + "NoCD " + (data.nocd ? "enabled" : "disabled") + " for " + player.getName() + "."); + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/admin/PointsCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/admin/PointsCommandMap.java new file mode 100644 index 00000000..0b3eadbd --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/admin/PointsCommandMap.java @@ -0,0 +1,72 @@ +package net.Indyuce.mmocore.command.rpg.admin; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class PointsCommandMap extends CommandMap { + private final String type; + private final Function get; + + public PointsCommandMap(String type, CommandMap parent, BiConsumer set, BiConsumer give, Function get) { + super(parent, type + "-points"); + + this.type = type; + this.get = get; + + addFloor(new SetCommandMap(this, "set", set)); + addFloor(new SetCommandMap(this, "give", give)); + } + + public class SetCommandMap extends CommandEnd { + private final BiConsumer action; + + public SetCommandMap(CommandMap parent, String type, BiConsumer action) { + super(parent, type); + + this.action = action; + + addParameter(Parameter.PLAYER); + addParameter(Parameter.AMOUNT); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 5) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[3]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[2] + "."); + return CommandResult.FAILURE; + } + + int amount = 0; + try { + amount = Integer.parseInt(args[4]); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + args[4] + " is not a valid number."); + return CommandResult.FAILURE; + } + + PlayerData data = PlayerData.get(player); + action.accept(data, amount); + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " now has " + ChatColor.GOLD + get.apply(data) + ChatColor.YELLOW + " " + type + " points."); + return CommandResult.SUCCESS; + } + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + return CommandResult.THROW_USAGE; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/admin/ResetCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/admin/ResetCommandMap.java new file mode 100644 index 00000000..5bc6e300 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/admin/ResetCommandMap.java @@ -0,0 +1,150 @@ +package net.Indyuce.mmocore.command.rpg.admin; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class ResetCommandMap extends CommandEnd { + public ResetCommandMap(CommandMap parent) { + super(parent, "reset"); + + addFloor(new ResetLevelsCommandMap(this)); + addFloor(new ResetSkillsCommandMap(this)); + addFloor(new ResetAllCommandMap(this)); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + return CommandResult.THROW_USAGE; + } + + public class ResetAllCommandMap extends CommandEnd { + public ResetAllCommandMap(CommandMap parent) { + super(parent, "all"); + + addParameter(Parameter.PLAYER); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 4) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[3]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[3] + "."); + return CommandResult.FAILURE; + } + + PlayerData data = PlayerData.get(player); + data.setLevel(1); + data.setExperience(0); + for (Profession profession : MMOCore.plugin.professionManager.getAll()) { + data.getCollectionSkills().setExperience(profession, 0); + data.getCollectionSkills().setLevel(profession, 0); + } + MMOCore.plugin.classManager.getAll().forEach(profess -> data.unloadClassInfo(profess)); + data.setClassPoints(0); + data.setSkillPoints(0); + MMOCore.plugin.skillManager.getAll().forEach(skill -> data.lockSkill(skill)); + while (data.hasSkillBound(0)) + data.unbindSkill(0); + data.getQuestData().resetFinishedQuests(); + data.getQuestData().start(null); + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + "'s data was succesfully reset."); + return CommandResult.SUCCESS; + } + } + + public class ResetQuestsCommandMap extends CommandEnd { + public ResetQuestsCommandMap(CommandMap parent) { + super(parent, "quests"); + + addParameter(Parameter.PLAYER); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 4) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[3]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[3] + "."); + return CommandResult.FAILURE; + } + + PlayerData data = PlayerData.get(player); + data.getQuestData().resetFinishedQuests(); + data.getQuestData().start(null); + return CommandResult.SUCCESS; + } + } + + public class ResetSkillsCommandMap extends CommandEnd { + public ResetSkillsCommandMap(CommandMap parent) { + super(parent, "skills"); + + addParameter(Parameter.PLAYER); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 4) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[3]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[3] + "."); + return CommandResult.FAILURE; + } + + PlayerData data = PlayerData.get(player); + data.setSkillPoints(0); + MMOCore.plugin.skillManager.getAll().forEach(skill -> data.lockSkill(skill)); + while (data.hasSkillBound(0)) + data.unbindSkill(0); + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + "'s skill data was succesfully reset."); + return CommandResult.SUCCESS; + } + } + + public class ResetLevelsCommandMap extends CommandEnd { + public ResetLevelsCommandMap(CommandMap parent) { + super(parent, "levels"); + + addParameter(Parameter.PLAYER); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 4) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[3]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[3] + "."); + return CommandResult.FAILURE; + } + + PlayerData data = PlayerData.get(player); + data.setLevel(1); + data.setExperience(0); + for (Profession profession : MMOCore.plugin.professionManager.getAll()) { + data.getCollectionSkills().setExperience(profession, 0); + data.getCollectionSkills().setLevel(profession, 0); + } + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + "'s levels were succesfully reset."); + + return CommandResult.SUCCESS; + } + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/booster/BoosterCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/booster/BoosterCommandMap.java new file mode 100644 index 00000000..9a570942 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/booster/BoosterCommandMap.java @@ -0,0 +1,20 @@ +package net.Indyuce.mmocore.command.rpg.booster; + +import org.bukkit.command.CommandSender; + +import net.Indyuce.mmocore.command.api.CommandMap; + +public class BoosterCommandMap extends CommandMap { + public BoosterCommandMap(CommandMap parent) { + super(parent, "booster"); + + addFloor(new CreateCommandMap(this)); + addFloor(new ListCommandMap(this)); + addFloor(new RemoveCommandMap(this)); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + return CommandResult.THROW_USAGE; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/booster/CreateCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/booster/CreateCommandMap.java new file mode 100644 index 00000000..3f83a7c6 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/booster/CreateCommandMap.java @@ -0,0 +1,68 @@ +package net.Indyuce.mmocore.command.rpg.booster; + +import java.util.Arrays; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.command.CommandSender; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigMessage; +import net.Indyuce.mmocore.api.experience.Booster; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class CreateCommandMap extends CommandEnd { + public CreateCommandMap(CommandMap parent) { + super(parent, "create"); + + addParameter(Parameter.PROFESSION); + addParameter(new Parameter("", (list) -> list.addAll(Arrays.asList("0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "1")))); + addParameter(new Parameter("", (list) -> list.addAll(Arrays.asList("60", "300", "3600", "43200", "86400")))); + addParameter(Parameter.PLAYER_OPTIONAL); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 5) + return CommandResult.THROW_USAGE; + + double extra; + try { + extra = Double.parseDouble(args[3]); + } catch (NumberFormatException exception) { + sender.sendMessage(ChatColor.RED + args[3] + " is not a valid number."); + return CommandResult.FAILURE; + } + + long length; + try { + length = Long.parseLong(args[4]); + } catch (NumberFormatException exception) { + sender.sendMessage(ChatColor.RED + args[4] + " is not a valid number."); + return CommandResult.FAILURE; + } + + if (args[2].equalsIgnoreCase("main")) { + MMOCore.plugin.boosterManager.register(new Booster(args.length > 5 ? args[5] : null, extra, length)); + new ConfigMessage("booster-main").addPlaceholders("multiplier", "" + (1 + extra)).send(Bukkit.getOnlinePlayers()); + Bukkit.getOnlinePlayers().forEach(player -> player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1)); + return CommandResult.SUCCESS; + } + + String format = args[2].toLowerCase().replace("_", "-"); + if (!MMOCore.plugin.professionManager.has(format)) { + sender.sendMessage(ChatColor.RED + format + " is not a valid profession."); + return CommandResult.FAILURE; + } + + Profession profession = MMOCore.plugin.professionManager.get(format); + MMOCore.plugin.boosterManager.register(new Booster(args.length > 5 ? args[5] : null, profession, extra, length)); + new ConfigMessage("booster-skill").addPlaceholders("multiplier", "" + (1 + extra), "profession", profession.getName()).send(Bukkit.getOnlinePlayers()); + Bukkit.getOnlinePlayers().forEach(player -> player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1)); + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/booster/ListCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/booster/ListCommandMap.java new file mode 100644 index 00000000..836c5450 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/booster/ListCommandMap.java @@ -0,0 +1,31 @@ +package net.Indyuce.mmocore.command.rpg.booster; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Booster; +import net.Indyuce.mmocore.api.math.format.DelayFormat; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; + +public class ListCommandMap extends CommandEnd { + public ListCommandMap(CommandMap parent) { + super(parent, "list"); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (!(sender instanceof Player)) + return CommandResult.FAILURE; + + sender.sendMessage(ChatColor.YELLOW + "----------------------------------------------------"); + for (Booster booster : MMOCore.plugin.boosterManager.getBoosters()) + if (!booster.isTimedOut()) + MMOCore.plugin.nms.sendJson((Player) sender, "{\"text\":\"" + ChatColor.YELLOW + "- " + ChatColor.GOLD + MMOCore.digit2.format((1 + booster.getExtra())) + "x" + ChatColor.YELLOW + " Booster - " + ChatColor.GOLD + (!booster.hasProfession() ? "Main" : booster.getProfession().getName()) + ChatColor.YELLOW + " - " + ChatColor.GOLD + new DelayFormat().format(booster.getCreationDate() + booster.getLength() - System.currentTimeMillis()) + "\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"/mmocore booster remove " + booster.getUniqueId().toString() + "\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"Click to remove.\"}}}"); + sender.sendMessage(ChatColor.YELLOW + "----------------------------------------------------"); + + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/booster/RemoveCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/booster/RemoveCommandMap.java new file mode 100644 index 00000000..4f2ac856 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/booster/RemoveCommandMap.java @@ -0,0 +1,45 @@ +package net.Indyuce.mmocore.command.rpg.booster; + +import java.util.Iterator; +import java.util.UUID; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Booster; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class RemoveCommandMap extends CommandEnd { + public RemoveCommandMap(CommandMap parent) { + super(parent, "remove"); + + addParameter(new Parameter("", (list) -> MMOCore.plugin.boosterManager.getBoosters().forEach(booster -> list.add("" + booster.getUniqueId().toString())))); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 3) + return CommandResult.THROW_USAGE; + + UUID uuid; + try { + uuid = UUID.fromString(args[2]); + } catch (IllegalArgumentException exception) { + sender.sendMessage(ChatColor.RED + "Couldn't load ID " + args[2] + "."); + return CommandResult.FAILURE; + } + + for (Iterator iterator = MMOCore.plugin.boosterManager.getBoosters().iterator(); iterator.hasNext();) { + Booster booster = iterator.next(); + if (booster.getUniqueId().equals(uuid)) { + iterator.remove(); + sender.sendMessage(ChatColor.YELLOW + "Successfully unregistered this booster."); + break; + } + } + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/waypoint/OpenCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/waypoint/OpenCommandMap.java new file mode 100644 index 00000000..c7ee2b43 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/waypoint/OpenCommandMap.java @@ -0,0 +1,34 @@ +package net.Indyuce.mmocore.command.rpg.waypoint; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class OpenCommandMap extends CommandEnd { + public OpenCommandMap(CommandMap parent) { + super(parent, "open"); + + addParameter(Parameter.PLAYER); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 3) + return CommandResult.THROW_USAGE; + + Player player = Bukkit.getPlayer(args[2]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find player " + args[2]); + return CommandResult.FAILURE; + } + + InventoryManager.WAYPOINTS.newInventory(net.Indyuce.mmocore.api.player.PlayerData.get(player)).open(); + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/waypoint/UnlockCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/waypoint/UnlockCommandMap.java new file mode 100644 index 00000000..adeb7921 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/waypoint/UnlockCommandMap.java @@ -0,0 +1,44 @@ +package net.Indyuce.mmocore.command.rpg.waypoint; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.Waypoint; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.command.api.CommandEnd; +import net.Indyuce.mmocore.command.api.CommandMap; +import net.Indyuce.mmocore.command.api.Parameter; + +public class UnlockCommandMap extends CommandEnd { + public UnlockCommandMap(CommandMap parent) { + super(parent, "unlock"); + + addParameter(new Parameter("", (list) -> MMOCore.plugin.waypointManager.getAll().forEach(way -> list.add(way.getId())))); + addParameter(Parameter.PLAYER); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + if (args.length < 4) + return CommandResult.THROW_USAGE; + + if (!MMOCore.plugin.waypointManager.has(args[2])) { + sender.sendMessage(ChatColor.RED + "Could not find waypoint " + args[2]); + return CommandResult.FAILURE; + } + + Player player = Bukkit.getPlayer(args[3]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Could not find player " + args[3]); + return CommandResult.FAILURE; + } + + Waypoint waypoint = MMOCore.plugin.waypointManager.get(args[2]); + PlayerData.get(player).unlockWaypoint(waypoint); + sender.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.YELLOW + " successfully unlocked " + ChatColor.GOLD + waypoint.getId() + ChatColor.YELLOW + "."); + return CommandResult.SUCCESS; + } +} diff --git a/src/net/Indyuce/mmocore/command/rpg/waypoint/WaypointsCommandMap.java b/src/net/Indyuce/mmocore/command/rpg/waypoint/WaypointsCommandMap.java new file mode 100644 index 00000000..2b9bcce2 --- /dev/null +++ b/src/net/Indyuce/mmocore/command/rpg/waypoint/WaypointsCommandMap.java @@ -0,0 +1,19 @@ +package net.Indyuce.mmocore.command.rpg.waypoint; + +import org.bukkit.command.CommandSender; + +import net.Indyuce.mmocore.command.api.CommandMap; + +public class WaypointsCommandMap extends CommandMap { + public WaypointsCommandMap(CommandMap parent) { + super(parent, "waypoints"); + + addFloor(new UnlockCommandMap(this)); + addFloor(new OpenCommandMap(this)); + } + + @Override + public CommandResult execute(CommandSender sender, String[] args) { + return CommandResult.THROW_USAGE; + } +} diff --git a/src/net/Indyuce/mmocore/comp/Metrics.java b/src/net/Indyuce/mmocore/comp/Metrics.java new file mode 100644 index 00000000..f3e57cec --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/Metrics.java @@ -0,0 +1,710 @@ +package net.Indyuce.mmocore.comp; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +import javax.net.ssl.HttpsURLConnection; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +/** + * bStats collects some data for plugin authors. + * + * Check out https://bStats.org/ to learn more about bStats! + */ +@SuppressWarnings("unchecked") +public class Metrics { + + static { + // You can use the property to disable the check in your test + // environment + if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have + // to use this little "trick" ... :D + final String defaultPackage = new String(new byte[] { 'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't' }); + final String examplePackage = new String(new byte[] { 'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e' }); + // We want to make sure nobody just copy & pastes the example and + // use the wrong package names + if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/bukkit"; + + // Should failed requests be logged? + private static boolean logFailedRequests; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private final JavaPlugin plugin; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param plugin + * The plugin which stats should be submitted. + */ + public Metrics(JavaPlugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + this.plugin = plugin; + + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + + // Inform the server owners about bStats + config.options().header("bStats collects some data for plugin authors like how many servers are using their plugins.\n" + "To honor their work, you should not disable it.\n" + "This has nearly no effect on the server performance!\n" + "Check out https://bStats.org/ to learn more :)").copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + + // Load the data + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + if (config.getBoolean("enabled", true)) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the + // first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { + } + } + // Register our service + Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Adds a custom chart. + * + * @param chart + * The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit + // scheduler is affected by + // server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main + // thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still + // async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, new Runnable() { + @Override + public void run() { + submitData(); + } + }); + } + }, 1000 * 60 * 5, 1000 * 60 * 30); + // Submit the data every 30 minutes, first time after 5 minutes to give + // other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be + // blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JSONObject getPluginData() { + JSONObject data = new JSONObject(); + + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.put("pluginName", pluginName); // Append the name of the plugin + data.put("pluginVersion", pluginVersion); // Append the version of the + // plugin + JSONArray customCharts = new JSONArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JSONObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.put("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JSONObject getServerData() { + // Minecraft specific data + int playerAmount; + try { + // Around MC 1.8 the return type was changed to a collection from an + // array, + // This fixes java.lang.NoSuchMethodError: + // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new + // method if the + // Reflection + // failed + } + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = org.bukkit.Bukkit.getVersion(); + bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JSONObject data = new JSONObject(); + + data.put("serverUUID", serverUUID); + + data.put("playerAmount", playerAmount); + data.put("onlineMode", onlineMode); + data.put("bukkitVersion", bukkitVersion); + + data.put("javaVersion", javaVersion); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JSONObject data = getServerData(); + + JSONArray pluginData = new JSONArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + + for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { + try { + pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider())); + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { + } + } + } catch (NoSuchFieldException ignored) { + } + } + + data.put("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + } + + /** + * Sends the data to the bStats server. + * + * @param data + * The data to send. + * @throws Exception + * If the request failed. + */ + private static void sendData(JSONObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip + // our + // request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We + // send + // our + // data + // in + // JSON + // format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + connection.getInputStream().close(); // We don't care about the response + // - Just send our data :) + } + + /** + * Gzips the given String. + * + * @param str + * The string to gzip. + * @return The gzipped String. + * @throws IOException + * If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes("UTF-8")); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + final String chartId; + + /** + * Class constructor. + * + * @param chartId + * The id of the chart. + */ + CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + private JSONObject getRequestJsonObject() { + JSONObject chart = new JSONObject(); + chart.put("chartId", chartId); + try { + JSONObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.put("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JSONObject getChartData() throws Exception; + + } + + /** + * Represents a custom simple pie. + */ + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId + * The id of the chart. + * @param callable + * The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId + * The id of the chart. + * @param callable + * The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom drilldown pie. + */ + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId + * The id of the chart. + * @param callable + * The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JSONObject value = new JSONObject(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.put(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + values.put(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId + * The id of the chart. + * @param callable + * The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId + * The id of the chart. + * @param callable + * The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId + * The id of the chart. + * @param callable + * The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JSONArray categoryValues = new JSONArray(); + categoryValues.add(entry.getValue()); + values.put(entry.getKey(), categoryValues); + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId + * The id of the chart. + * @param callable + * The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; + JSONArray categoryValues = new JSONArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.put(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } +} diff --git a/src/net/Indyuce/mmocore/comp/ShopKeepersEntityHandler.java b/src/net/Indyuce/mmocore/comp/ShopKeepersEntityHandler.java new file mode 100644 index 00000000..c7fb1801 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/ShopKeepersEntityHandler.java @@ -0,0 +1,15 @@ +package net.Indyuce.mmocore.comp; + +import org.bukkit.entity.Entity; + +import com.nisovin.shopkeepers.api.ShopkeepersPlugin; + +import net.Indyuce.mmocore.comp.entity.EntityHandler; + +public class ShopKeepersEntityHandler implements EntityHandler { + + @Override + public boolean isCustomEntity(Entity entity) { + return ShopkeepersPlugin.getInstance().getShopkeeperRegistry().isShopkeeper(entity); + } +} diff --git a/src/net/Indyuce/mmocore/comp/citizens/CitizenInteractEvent.java b/src/net/Indyuce/mmocore/comp/citizens/CitizenInteractEvent.java new file mode 100644 index 00000000..8a6715bc --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/citizens/CitizenInteractEvent.java @@ -0,0 +1,31 @@ +package net.Indyuce.mmocore.comp.citizens; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +import net.citizensnpcs.api.npc.NPC; + +public class CitizenInteractEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + + private NPC npc; + + public CitizenInteractEvent(Player who, NPC npc) { + super(who); + this.npc = npc; + } + + public NPC getNPC() { + return npc; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/net/Indyuce/mmocore/comp/citizens/CitizenInteractEventListener.java b/src/net/Indyuce/mmocore/comp/citizens/CitizenInteractEventListener.java new file mode 100644 index 00000000..991656b9 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/citizens/CitizenInteractEventListener.java @@ -0,0 +1,34 @@ +package net.Indyuce.mmocore.comp.citizens; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEntityEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.comp.entity.EntityHandler; +import net.citizensnpcs.api.CitizensAPI; + +public class CitizenInteractEventListener implements Listener, EntityHandler { + + public CitizenInteractEventListener() { + + /* + * prevents NPCs from being skill targets. + */ + MMOCore.plugin.entities.registerHandler(this); + } + + @EventHandler + public void a(PlayerInteractEntityEvent event) { + Entity entity = event.getRightClicked(); + if (CitizensAPI.getNPCRegistry().isNPC(entity)) + Bukkit.getPluginManager().callEvent(new CitizenInteractEvent(event.getPlayer(), CitizensAPI.getNPCRegistry().getNPC(entity))); + } + + @Override + public boolean isCustomEntity(Entity entity) { + return entity.hasMetadata("NPC"); + } +} diff --git a/src/net/Indyuce/mmocore/comp/citizens/CitizensMMOLoader.java b/src/net/Indyuce/mmocore/comp/citizens/CitizensMMOLoader.java new file mode 100644 index 00000000..bc1a9d82 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/citizens/CitizensMMOLoader.java @@ -0,0 +1,51 @@ +package net.Indyuce.mmocore.comp.citizens; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoader; +import net.Indyuce.mmocore.api.quest.objective.Objective; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class CitizensMMOLoader implements MMOLoader { + + @Override + public Condition loadCondition(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Trigger loadTrigger(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public DropItem loadDropItem(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Objective loadObjective(MMOLineConfig config, ConfigurationSection section) { + + if (config.getKey().equals("talkto")) + return new TalktoCitizenObjective(section, config); + + if (config.getKey().equals("getitem")) + return new GetItemObjective(section, config); + + return null; + } + + @Override + public ExperienceSource loadExperienceSource(MMOLineConfig config, Profession profession) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/net/Indyuce/mmocore/comp/citizens/GetItemObjective.java b/src/net/Indyuce/mmocore/comp/citizens/GetItemObjective.java new file mode 100644 index 00000000..e69663eb --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/citizens/GetItemObjective.java @@ -0,0 +1,56 @@ +package net.Indyuce.mmocore.comp.citizens; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.quest.ObjectiveProgress; +import net.Indyuce.mmocore.api.quest.QuestProgress; +import net.Indyuce.mmocore.api.quest.objective.Objective; + +public class GetItemObjective extends Objective { + private Material material; + private int required, npcId; + + public GetItemObjective(ConfigurationSection section, MMOLineConfig config) { + super(section); + + config.validate("type", "amount", "npc"); + + material = Material.valueOf(config.getString("type").toUpperCase().replace("-", "_")); + required = config.getInt("amount"); + npcId = config.getInt("npc"); + } + + @Override + public ObjectiveProgress newProgress(QuestProgress questProgress) { + return new GotoProgress(questProgress, this); + } + + public class GotoProgress extends ObjectiveProgress implements Listener { + public GotoProgress(QuestProgress questProgress, Objective objective) { + super(questProgress, objective); + } + + @EventHandler + public void a(CitizenInteractEvent event) { + Player player = event.getPlayer(); + if (player.equals(getQuestProgress().getPlayer().getPlayer()) && event.getNPC().getId() == npcId && player.getInventory().getItemInMainHand() != null) { + ItemStack item = player.getInventory().getItemInMainHand(); + if (item.getType() == material && item.getAmount() >= required) { + item.setAmount(item.getAmount() - required); + getQuestProgress().completeObjective(); + } + } + } + + @Override + public String formatLore(String lore) { + return lore; + } + } +} diff --git a/src/net/Indyuce/mmocore/comp/citizens/TalktoCitizenObjective.java b/src/net/Indyuce/mmocore/comp/citizens/TalktoCitizenObjective.java new file mode 100644 index 00000000..fc34c9b2 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/citizens/TalktoCitizenObjective.java @@ -0,0 +1,44 @@ +package net.Indyuce.mmocore.comp.citizens; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.quest.ObjectiveProgress; +import net.Indyuce.mmocore.api.quest.QuestProgress; +import net.Indyuce.mmocore.api.quest.objective.Objective; + +public class TalktoCitizenObjective extends Objective { + private int id; + + public TalktoCitizenObjective(ConfigurationSection section, MMOLineConfig config) { + super(section); + + config.validate("npc"); + + id = config.getInt("npc"); + } + + @Override + public ObjectiveProgress newProgress(QuestProgress questProgress) { + return new TalktoProgress(questProgress, this); + } + + public class TalktoProgress extends ObjectiveProgress implements Listener { + public TalktoProgress(QuestProgress questProgress, Objective objective) { + super(questProgress, objective); + } + + @EventHandler + public void a(CitizenInteractEvent event) { + if (event.getPlayer().equals(getQuestProgress().getPlayer().getPlayer()) && event.getNPC().getId() == id) + getQuestProgress().completeObjective(); + } + + @Override + public String formatLore(String lore) { + return lore; + } + } +} diff --git a/src/net/Indyuce/mmocore/comp/entity/EntityHandler.java b/src/net/Indyuce/mmocore/comp/entity/EntityHandler.java new file mode 100644 index 00000000..bc732418 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/entity/EntityHandler.java @@ -0,0 +1,12 @@ +package net.Indyuce.mmocore.comp.entity; + +import org.bukkit.entity.Entity; + +public interface EntityHandler { + + /* + * this method lets you check if a specific entity was created using a plugin + * and therefore which should not be a skill target. + */ + boolean isCustomEntity(Entity entity); +} diff --git a/src/net/Indyuce/mmocore/comp/entity/MyPetEntityHandler.java b/src/net/Indyuce/mmocore/comp/entity/MyPetEntityHandler.java new file mode 100644 index 00000000..a56dc33d --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/entity/MyPetEntityHandler.java @@ -0,0 +1,13 @@ +package net.Indyuce.mmocore.comp.entity; + +import org.bukkit.entity.Entity; + +import de.Keyle.MyPet.api.entity.MyPetBukkitEntity; + +public class MyPetEntityHandler implements EntityHandler { + + @Override + public boolean isCustomEntity(Entity entity) { + return entity instanceof MyPetBukkitEntity; + } +} diff --git a/src/net/Indyuce/mmocore/comp/holograms/CMIPlugin.java b/src/net/Indyuce/mmocore/comp/holograms/CMIPlugin.java new file mode 100644 index 00000000..809dc426 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/holograms/CMIPlugin.java @@ -0,0 +1,26 @@ +package net.Indyuce.mmocore.comp.holograms; + +import java.util.Arrays; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import com.Zrips.CMI.CMI; +import com.Zrips.CMI.Modules.Holograms.CMIHologram; + +import net.Indyuce.mmocore.MMOCore; + +public class CMIPlugin extends HologramSupport { + @Override + public void displayIndicator(Location loc, String format, Player player) { + final CMIHologram hologram = new CMIHologram("MMOItems_" + UUID.randomUUID().toString(), loc); + hologram.setLines(Arrays.asList(format)); + if (player != null) + hologram.hide(player.getUniqueId()); + CMI.getInstance().getHologramManager().addHologram(hologram); + hologram.update(); + Bukkit.getScheduler().runTaskLater(MMOCore.plugin, () -> CMI.getInstance().getHologramManager().removeHolo(hologram), 20); + } +} diff --git a/src/net/Indyuce/mmocore/comp/holograms/HologramSupport.java b/src/net/Indyuce/mmocore/comp/holograms/HologramSupport.java new file mode 100644 index 00000000..a5605970 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/holograms/HologramSupport.java @@ -0,0 +1,22 @@ +package net.Indyuce.mmocore.comp.holograms; + +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public abstract class HologramSupport { + + /* + * the third argument is the player which the hologram needs to be hidden + * from to prevent the indicator from taking too much space on the player + * screen + */ + public abstract void displayIndicator(Location loc, String message, Player player); + + public void displayIndicator(Location loc, String message) { + displayIndicator(loc, message, null); + } + + public void displayIndicator(Player player, String message) { + displayIndicator(player.getLocation().add(0, 1, 0), message, player); + } +} diff --git a/src/net/Indyuce/mmocore/comp/holograms/HologramsPlugin.java b/src/net/Indyuce/mmocore/comp/holograms/HologramsPlugin.java new file mode 100644 index 00000000..56bdc74d --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/holograms/HologramsPlugin.java @@ -0,0 +1,27 @@ +package net.Indyuce.mmocore.comp.holograms; + +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import com.sainttx.holograms.HologramPlugin; +import com.sainttx.holograms.api.Hologram; +import com.sainttx.holograms.api.HologramManager; +import com.sainttx.holograms.api.line.TextLine; + +import net.Indyuce.mmocore.MMOCore; + +public class HologramsPlugin extends HologramSupport { + private HologramManager hologramManager = JavaPlugin.getPlugin(HologramPlugin.class).getHologramManager(); + + @Override + public void displayIndicator(Location loc, String message, Player player) { + Hologram hologram = new Hologram("MMOItems_" + UUID.randomUUID().toString(), loc); + hologramManager.addActiveHologram(hologram); + hologram.addLine(new TextLine(hologram, message)); + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> hologramManager.deleteHologram(hologram), 20); + } +} diff --git a/src/net/Indyuce/mmocore/comp/holograms/HolographicDisplaysPlugin.java b/src/net/Indyuce/mmocore/comp/holograms/HolographicDisplaysPlugin.java new file mode 100644 index 00000000..e70db4f5 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/holograms/HolographicDisplaysPlugin.java @@ -0,0 +1,21 @@ +package net.Indyuce.mmocore.comp.holograms; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.holographicdisplays.api.Hologram; +import com.gmail.filoghost.holographicdisplays.api.HologramsAPI; + +import net.Indyuce.mmocore.MMOCore; + +public class HolographicDisplaysPlugin extends HologramSupport { + @Override + public void displayIndicator(Location loc, String format, Player player) { + Hologram hologram = HologramsAPI.createHologram(MMOCore.plugin, loc); + hologram.appendTextLine(format); + if (player != null) + hologram.getVisibilityManager().hideTo(player); + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> hologram.delete(), 20); + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/LootColor.java b/src/net/Indyuce/mmocore/comp/mythicmobs/LootColor.java new file mode 100644 index 00000000..dbf8c02e --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/LootColor.java @@ -0,0 +1,32 @@ +package net.Indyuce.mmocore.comp.mythicmobs; + +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.entity.Item; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; + +public class LootColor extends BukkitRunnable { + private final Item item; + private final Color color; + + private int j = 0; + + public LootColor(Item item, Color color) { + this.item = item; + this.color = color; + + runTaskTimerAsynchronously(MMOCore.plugin, 0, 1); + } + + @Override + public void run() { + if (j++ > 100 || item.isDead() || item.isOnGround()) { + cancel(); + return; + } + + item.getWorld().spawnParticle(Particle.REDSTONE, item.getLocation(), 0, new Particle.DustOptions(color, 1.3f)); + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/Lootsplosion.java b/src/net/Indyuce/mmocore/comp/mythicmobs/Lootsplosion.java new file mode 100644 index 00000000..7b128f1a --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/Lootsplosion.java @@ -0,0 +1,85 @@ +package net.Indyuce.mmocore.comp.mythicmobs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Item; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.ItemSpawnEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import io.lumine.xikage.mythicmobs.api.bukkit.events.MythicMobDeathEvent; +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.ItemTier; + +public class Lootsplosion implements Listener { + private static final Random random = new Random(); + + private final boolean colored; + + public Lootsplosion() { + colored = Bukkit.getPluginManager().getPlugin("MMOItems") != null && MMOCore.plugin.getConfig().getBoolean("lootsplosion.mmoitems-color"); + } + + @EventHandler + public void b(MythicMobDeathEvent event) { + new LootsplosionHandler(event); + } + + public class LootsplosionHandler implements Listener { + + private final List drops; + + /* + * Y coordinate offset so the velocity is not directly negated when the + * item spawns on the ground + */ + private final double offset; + + public LootsplosionHandler(MythicMobDeathEvent event) { + offset = event.getEntity().getHeight() / 2; + drops = new ArrayList<>(event.getDrops()); + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + private void close() { + ItemSpawnEvent.getHandlerList().unregister(this); + } + + @EventHandler + public void a(ItemSpawnEvent event) { + Item item = event.getEntity(); + if (!drops.contains(item.getItemStack())) { + close(); + return; + } + + drops.remove(item.getItemStack()); + item.teleport(item.getLocation().add(0, offset, 0)); + item.setVelocity(randomVector()); + + if (colored) + Bukkit.getScheduler().runTaskAsynchronously(MMOCore.plugin, () -> { + NBTItem nbt = MMOCore.plugin.nms.getNBTItem(item.getItemStack()); + if (nbt.has("MMOITEMS_TIER")) { + ItemTier tier = MMOItems.plugin.getTiers().get(nbt.getString("MMOITEMS_TIER")); + if (tier.hasColor()) + new LootColor(item, tier.getColor().toBukkit()); + } + }); + } + + } + + private Vector randomVector() { + double offset = MMOCore.plugin.getConfig().getDouble("lootsplosion.offset"), height = MMOCore.plugin.getConfig().getDouble("lootsplosion.height"); + return new Vector(Math.cos(random.nextDouble() * Math.PI * 2) * offset, height, Math.sin(random.nextDouble() * Math.PI * 2) * offset); + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobSkill.java b/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobSkill.java new file mode 100644 index 00000000..0564ea1d --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobSkill.java @@ -0,0 +1,92 @@ +package net.Indyuce.mmocore.comp.mythicmobs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Entity; + +import io.lumine.xikage.mythicmobs.MythicMobs; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.math.formula.IntegerLinearValue; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.SkillResult.CancelReason; + +public class MythicMobSkill extends Skill { + private final io.lumine.xikage.mythicmobs.skills.Skill skill; + + // private final BiFunction cast; + + public MythicMobSkill(String id, FileConfiguration config) { + super(id); + + String mmId = config.getString("mythicmobs-skill-id"); + Validate.notNull(mmId, "Could not find MM skill ID"); + + Optional opt = MythicMobs.inst().getSkillManager().getSkill(mmId); + Validate.isTrue(opt.isPresent(), "Could not find MM skill " + mmId); + skill = opt.get(); + + String format = config.getString("material"); + Validate.notNull(format, "Could not load skill material"); + setIcon(MMOCoreUtils.readIcon(format)); + + setName(config.getString("name")); + setLore(config.getStringList("lore")); + + for (String key : config.getKeys(false)) { + Object mod = config.get(key); + if (mod instanceof ConfigurationSection) + addModifier(key, readLinearValue((ConfigurationSection) mod)); + } + + // cast = config.getBoolean("target") ? (data, info) -> new + // TargetSkillResult(data, info, def(config.getDouble("range"), 50)) : + // (data, info) -> new SkillResult(data, info); + } + + // private double def(double d, double def) { + // return d == 0 ? def : d; + // } + + public String getInternalName() { + return skill.getInternalName(); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + SkillResult cast = new SkillResult(data, skill); + if (!cast.isSuccessful()) + return cast; + + List targets = new ArrayList<>(); + // targets.add(cast instanceof TargetSkillResult ? ((TargetSkillResult) + // cast).getTarget() : stats.getPlayer()); + targets.add(data.getPlayer()); + + /* + * cache placeholders so they can be retrieved later by MythicMobs math + * formulas + */ + data.getSkillData().cacheModifiers(this, cast); + + if (!MythicMobs.inst().getAPIHelper().castSkill(data.getPlayer(), this.skill.getInternalName(), data.getPlayer(), data.getPlayer().getEyeLocation(), targets, null, 1)) + cast.abort(CancelReason.OTHER); + + return cast; + } + + /* + * used to load double modifiers from the config with a specific type, since + * modifiers have initially a type for mmocore default skills + */ + private LinearValue readLinearValue(ConfigurationSection section) { + return section.getBoolean("int") ? new IntegerLinearValue(section) : new LinearValue(section); + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsDamagerHandler.java b/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsDamagerHandler.java new file mode 100644 index 00000000..349b6a81 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsDamagerHandler.java @@ -0,0 +1,20 @@ +package net.Indyuce.mmocore.comp.mythicmobs; + +import org.bukkit.entity.Entity; + +import net.Indyuce.mmocore.comp.rpg.damage.DamageHandler; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class MythicMobsDamagerHandler implements DamageHandler { + + @Override + public DamageInfo getDamage(Entity entity) { + return new DamageInfo(DamageType.MAGICAL); + } + + @Override + public boolean hasDamage(Entity entity) { + return entity.hasMetadata("skill-damage"); + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsDrops.java b/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsDrops.java new file mode 100644 index 00000000..21eca16c --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsDrops.java @@ -0,0 +1,51 @@ +package net.Indyuce.mmocore.comp.mythicmobs; + +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import io.lumine.xikage.mythicmobs.MythicMobs; +import io.lumine.xikage.mythicmobs.api.bukkit.events.MythicDropLoadEvent; +import io.lumine.xikage.mythicmobs.api.bukkit.events.MythicReloadedEvent; +import io.lumine.xikage.mythicmobs.skills.placeholders.Placeholder; +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.comp.mythicmobs.load.CurrencyItemDrop; +import net.Indyuce.mmocore.comp.mythicmobs.load.GoldPouchDrop; + +public class MythicMobsDrops implements Listener { + public MythicMobsDrops() { + if (MMOCore.plugin.getConfig().getBoolean("lootsplosion.enabled")) + Bukkit.getPluginManager().registerEvents(new Lootsplosion(), MMOCore.plugin); + registerPlaceholders(); + } + + @EventHandler + public void a(MythicDropLoadEvent event) { + + // random gold pouches + if (event.getDropName().equalsIgnoreCase("gold_pouch") || event.getDropName().equalsIgnoreCase("goldpouch")) + event.register(new GoldPouchDrop(event.getConfig())); + + // gold coins + if (event.getDropName().equalsIgnoreCase("gold_coin") || event.getDropName().equalsIgnoreCase("coin")) + event.register(new CurrencyItemDrop("GOLD_COIN", event.getConfig())); + + // notes + if (event.getDropName().equalsIgnoreCase("note") || event.getDropName().equalsIgnoreCase("banknote") || event.getDropName().equalsIgnoreCase("bank_note")) + event.register(new CurrencyItemDrop("NOTE", event.getConfig())); + } + + /* + * when MythicMobs is reloaded, the placeholders are emptied. Add them again + */ + @EventHandler + public void b(MythicReloadedEvent event) { + registerPlaceholders(); + } + + private void registerPlaceholders() { + MythicMobs.inst().getPlaceholderManager().register("mmocore.skill", Placeholder.meta((metadata, arg) -> String.valueOf(PlayerData.get(metadata.getCaster().getEntity().getUniqueId()).getSkillData().getCachedModifier(arg)))); + MythicMobs.inst().getPlaceholderManager().register("mmocore.mana", Placeholder.meta((metadata, arg) -> String.valueOf(PlayerData.get(metadata.getCaster().getEntity().getUniqueId()).getMana()))); + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsMMOLoader.java b/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsMMOLoader.java new file mode 100644 index 00000000..3b9ccce3 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/MythicMobsMMOLoader.java @@ -0,0 +1,57 @@ +package net.Indyuce.mmocore.comp.mythicmobs; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoader; +import net.Indyuce.mmocore.api.quest.objective.Objective; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; +import net.Indyuce.mmocore.comp.mythicmobs.load.KillMythicMobExperienceSource; +import net.Indyuce.mmocore.comp.mythicmobs.load.KillMythicMobObjective; +import net.Indyuce.mmocore.comp.mythicmobs.load.MythicMobSkillTrigger; + +public class MythicMobsMMOLoader implements MMOLoader { + + @Override + public Condition loadCondition(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Trigger loadTrigger(MMOLineConfig config) { + + if (config.getKey().equalsIgnoreCase("mmskill") || config.getKey().equalsIgnoreCase("mythicmobskill")) + return new MythicMobSkillTrigger(config); + + return null; + } + + @Override + public DropItem loadDropItem(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Objective loadObjective(MMOLineConfig config, ConfigurationSection section) { + + if (config.getKey().equalsIgnoreCase("killmythicmob")) + return new KillMythicMobObjective(section, config); + + return null; + } + + @Override + public ExperienceSource loadExperienceSource(MMOLineConfig config, Profession profession) { + + if (config.getKey().equalsIgnoreCase("killmythicmob")) + return new KillMythicMobExperienceSource(profession, config); + + return null; + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/load/CurrencyItemDrop.java b/src/net/Indyuce/mmocore/comp/mythicmobs/load/CurrencyItemDrop.java new file mode 100644 index 00000000..fe7d90dc --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/load/CurrencyItemDrop.java @@ -0,0 +1,39 @@ +package net.Indyuce.mmocore.comp.mythicmobs.load; + +import java.util.Random; + +import io.lumine.xikage.mythicmobs.adapters.bukkit.BukkitItemStack; +import io.lumine.xikage.mythicmobs.drops.Drop; +import io.lumine.xikage.mythicmobs.drops.DropMetadata; +import io.lumine.xikage.mythicmobs.drops.IMultiDrop; +import io.lumine.xikage.mythicmobs.drops.LootBag; +import io.lumine.xikage.mythicmobs.drops.droppables.ItemDrop; +import io.lumine.xikage.mythicmobs.io.MythicLineConfig; +import net.Indyuce.mmocore.api.item.CurrencyItem; + +public class CurrencyItemDrop extends Drop implements IMultiDrop { + private String key; + private int minw, maxw; + + private static final Random random = new Random(); + + public CurrencyItemDrop(String key, MythicLineConfig config) { + super(config.getLine(), config); + + this.key = key; + minw = config.getInteger("minw", 1); + maxw = config.getInteger("maxw", 1); + } + + @SuppressWarnings("deprecation") + @Override + public LootBag get(DropMetadata metadata) { + LootBag loot = new LootBag(metadata); + loot.add(new ItemDrop(this.getLine(), (MythicLineConfig) this.getConfig(), new BukkitItemStack(new CurrencyItem(key, random(minw, maxw)).build()))); + return loot; + } + + private int random(int a, int b) { + return random.nextInt(b - a + 1) + a; + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/load/GoldPouchDrop.java b/src/net/Indyuce/mmocore/comp/mythicmobs/load/GoldPouchDrop.java new file mode 100644 index 00000000..7518e647 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/load/GoldPouchDrop.java @@ -0,0 +1,65 @@ +package net.Indyuce.mmocore.comp.mythicmobs.load; + +import java.util.Random; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import io.lumine.xikage.mythicmobs.adapters.bukkit.BukkitItemStack; +import io.lumine.xikage.mythicmobs.drops.Drop; +import io.lumine.xikage.mythicmobs.drops.DropMetadata; +import io.lumine.xikage.mythicmobs.drops.IMultiDrop; +import io.lumine.xikage.mythicmobs.drops.LootBag; +import io.lumine.xikage.mythicmobs.drops.droppables.ItemDrop; +import io.lumine.xikage.mythicmobs.io.MythicLineConfig; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.item.ConfigItem; +import net.Indyuce.mmocore.api.item.CurrencyItem; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class GoldPouchDrop extends Drop implements IMultiDrop { + private int min, max; + + private static final Random random = new Random(); + + public GoldPouchDrop(MythicLineConfig config) { + super(config.getLine(), config); + + min = config.getInteger("min", 1); + max = config.getInteger("max", 10); + } + + @SuppressWarnings("deprecation") + @Override + public LootBag get(DropMetadata metadata) { + LootBag loot = new LootBag(metadata); + NBTItem nbt = NBTItem.get(new ConfigItem("MOB_GOLD_POUCH").build()); + + ItemStack[] content = new ItemStack[18]; + int money = random.nextInt(max - min + 1) + min; + + for (int j = 0; j < 7 && money > 0; j++) { + int a = j == 6 ? money : Math.max(1, (int) ((.12 + random.nextDouble() * .4) * (double) money)); + money -= a; + + if (a < 30 && random.nextDouble() < .3) { + content[getAvailableSlot(content)] = new CurrencyItem("GOLD_COIN", 1, a).build(); + continue; + } + + content[getAvailableSlot(content)] = new CurrencyItem("NOTE", a, 1).build(); + } + + nbt.add(new ItemTag("RpgPouchSize", 18), new ItemTag("RpgPouchMob", true), new ItemTag("RpgPouchInventory", MMOCoreUtils.toBase64(content))); + loot.add(new ItemDrop(this.getLine(), (MythicLineConfig) this.getConfig(), new BukkitItemStack(nbt.toItem()))); + return loot; + } + + private int getAvailableSlot(ItemStack[] content) { + int slot; + while (content[slot = random.nextInt(content.length)] != null && content[slot].getType() != Material.AIR) + continue; + return slot; + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/load/KillMythicMobExperienceSource.java b/src/net/Indyuce/mmocore/comp/mythicmobs/load/KillMythicMobExperienceSource.java new file mode 100644 index 00000000..8581752f --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/load/KillMythicMobExperienceSource.java @@ -0,0 +1,44 @@ +package net.Indyuce.mmocore.comp.mythicmobs.load; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; + +import io.lumine.xikage.mythicmobs.api.bukkit.events.MythicMobDeathEvent; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.SpecificExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.profession.ExperienceManager; + +public class KillMythicMobExperienceSource extends SpecificExperienceSource { + private final String internalName; + + public KillMythicMobExperienceSource(Profession profession, MMOLineConfig config) { + super(profession, config); + + config.validate("type"); + internalName = config.getString("type"); + } + + @Override + public ExperienceManager newManager() { + return new ExperienceManager() { + + @EventHandler + public void a(MythicMobDeathEvent event) { + if (!(event.getKiller() instanceof Player)) + return; + + PlayerData data = PlayerData.get((Player) event.getKiller()); + for (KillMythicMobExperienceSource source : getSources()) + if (source.matches(data, event.getMobType().getInternalName())) + source.giveExperience(data); + } + }; + } + + @Override + public boolean matches(PlayerData player, String name) { + return hasRightClass(player) && name.equals(internalName); + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/load/KillMythicMobObjective.java b/src/net/Indyuce/mmocore/comp/mythicmobs/load/KillMythicMobObjective.java new file mode 100644 index 00000000..9f927459 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/load/KillMythicMobObjective.java @@ -0,0 +1,54 @@ +package net.Indyuce.mmocore.comp.mythicmobs.load; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import io.lumine.xikage.mythicmobs.api.bukkit.events.MythicMobDeathEvent; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.quest.ObjectiveProgress; +import net.Indyuce.mmocore.api.quest.QuestProgress; +import net.Indyuce.mmocore.api.quest.objective.Objective; + +public class KillMythicMobObjective extends Objective { + private final String internalName; + private final int required; + + public KillMythicMobObjective(ConfigurationSection section, MMOLineConfig config) { + super(section); + + config.validate("amount", "name"); + + internalName = config.getString("name"); + required = config.getInt("amount"); + } + + @Override + public ObjectiveProgress newProgress(QuestProgress questProgress) { + return new KillMobProgress(questProgress, this); + } + + public class KillMobProgress extends ObjectiveProgress implements Listener { + private int count; + + public KillMobProgress(QuestProgress questProgress, Objective objective) { + super(questProgress, objective); + } + + @EventHandler + public void a(MythicMobDeathEvent event) { + if (event.getKiller() instanceof Player && event.getKiller().equals(getQuestProgress().getPlayer().getPlayer()) && event.getMob().getType().getInternalName().equals(internalName)) { + count++; + getQuestProgress().getPlayer().getQuestData().updateBossBar(); + if (count >= required) + getQuestProgress().completeObjective(); + } + } + + @Override + public String formatLore(String lore) { + return lore.replace("{left}", "" + (required - count)); + } + } +} diff --git a/src/net/Indyuce/mmocore/comp/mythicmobs/load/MythicMobSkillTrigger.java b/src/net/Indyuce/mmocore/comp/mythicmobs/load/MythicMobSkillTrigger.java new file mode 100644 index 00000000..6dcadbdb --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/mythicmobs/load/MythicMobSkillTrigger.java @@ -0,0 +1,35 @@ +package net.Indyuce.mmocore.comp.mythicmobs.load; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.lang.Validate; +import org.bukkit.entity.Entity; + +import io.lumine.xikage.mythicmobs.MythicMobs; +import io.lumine.xikage.mythicmobs.skills.Skill; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class MythicMobSkillTrigger extends Trigger { + private final Skill skill; + + public MythicMobSkillTrigger(MMOLineConfig config) { + super(config); + + config.validate("id"); + String id = config.getString("id"); + Optional opt = MythicMobs.inst().getSkillManager().getSkill(id); + Validate.isTrue(opt.isPresent(), "Could not find MM skill " + id); + skill = opt.get(); + } + + @Override + public void apply(PlayerData player) { + List targets = new ArrayList<>(); + targets.add(player.getPlayer()); + MythicMobs.inst().getAPIHelper().castSkill(player.getPlayer(), this.skill.getInternalName(), player.getPlayer(), player.getPlayer().getEyeLocation(), targets, null, 1); + } +} diff --git a/src/net/Indyuce/mmocore/comp/placeholder/DefaultParser.java b/src/net/Indyuce/mmocore/comp/placeholder/DefaultParser.java new file mode 100644 index 00000000..a2bb8392 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/placeholder/DefaultParser.java @@ -0,0 +1,11 @@ +package net.Indyuce.mmocore.comp.placeholder; + +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; + +public class DefaultParser implements PlaceholderParser { + @Override + public String parse(OfflinePlayer player, String string) { + return ChatColor.translateAlternateColorCodes('&', string.replace("%player%", player.getName())); + } +} diff --git a/src/net/Indyuce/mmocore/comp/placeholder/PlaceholderAPIParser.java b/src/net/Indyuce/mmocore/comp/placeholder/PlaceholderAPIParser.java new file mode 100644 index 00000000..431110e4 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/placeholder/PlaceholderAPIParser.java @@ -0,0 +1,16 @@ +package net.Indyuce.mmocore.comp.placeholder; + +import org.bukkit.OfflinePlayer; + +import me.clip.placeholderapi.PlaceholderAPI; + +public class PlaceholderAPIParser implements PlaceholderParser { + public PlaceholderAPIParser() { + new RPGPlaceholders().register(); + } + + @Override + public String parse(OfflinePlayer player, String string) { + return PlaceholderAPI.setPlaceholders(player, string.replace("%player%", player.getName())); + } +} diff --git a/src/net/Indyuce/mmocore/comp/placeholder/PlaceholderParser.java b/src/net/Indyuce/mmocore/comp/placeholder/PlaceholderParser.java new file mode 100644 index 00000000..41f26ab7 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/placeholder/PlaceholderParser.java @@ -0,0 +1,7 @@ +package net.Indyuce.mmocore.comp.placeholder; + +import org.bukkit.OfflinePlayer; + +public interface PlaceholderParser { + public String parse(OfflinePlayer player, String string); +} diff --git a/src/net/Indyuce/mmocore/comp/placeholder/RPGPlaceholders.java b/src/net/Indyuce/mmocore/comp/placeholder/RPGPlaceholders.java new file mode 100644 index 00000000..74087b9f --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/placeholder/RPGPlaceholders.java @@ -0,0 +1,111 @@ +package net.Indyuce.mmocore.comp.placeholder; + +import org.bukkit.ChatColor; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.Player; + +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.AltChar; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.StatType; + +public class RPGPlaceholders extends PlaceholderExpansion { + // private static final DelayFormat delayFormat = new DelayFormat(); + + @Override + public String getAuthor() { + return "Indyuce"; + } + + @Override + public String getIdentifier() { + return "mmocore"; + } + + @Override + public String getVersion() { + return "1.0"; + } + + @Override + public String onPlaceholderRequest(Player player, String identifier) { + + if (identifier.equals("level")) + return "" + PlayerData.get(player).getLevel(); + + else if (identifier.equals("combat")) + return String.valueOf(PlayerData.get(player).isInCombat()); + + else if (identifier.equals("health")) + return MMOCore.digit2.format(player.getHealth()); + + else if (identifier.startsWith("attribute_")) + return String.valueOf(PlayerData.get(player).getAttributes().getAttribute(MMOCore.plugin.attributeManager.get(identifier.substring(10).toLowerCase().replace("_", "-")))); + + else if (identifier.startsWith("profession_")) + return "" + PlayerData.get(player).getCollectionSkills().getLevel(identifier.substring(11).replace(" ", "-").replace("_", "-").toLowerCase()); + + else if (identifier.equals("max_health")) + return MMOCore.digit2.format(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + + else if (identifier.equals("experience")) + return "" + PlayerData.get(player).getExperience(); + + else if (identifier.equals("next_level")) + return "" + MMOCore.plugin.configManager.getNeededExperience(PlayerData.get(player).getLevel() + 1); + + else if (identifier.equals("class_points")) + return "" + PlayerData.get(player).getClassPoints(); + + else if (identifier.equals("mana")) + return "" + MMOCore.digit.format(PlayerData.get(player).getMana()); + + else if (identifier.equals("max_mana")) + return "" + MMOCore.digit.format(PlayerData.get(player).getStats().getStat(StatType.MAX_MANA)); + + else if (identifier.equals("mana_bar")) { + String format = ""; + PlayerData data = PlayerData.get(player); + double ratio = 20 * data.getMana() / data.getStats().getStat(StatType.MAX_MANA); + for (double j = 1; j < 20; j++) + format += (ratio >= j ? ChatColor.BLUE : ratio >= j - .5 ? ChatColor.AQUA : ChatColor.WHITE) + AltChar.listSquare; + return format; + } + + else if (identifier.equals("stamina")) + return "" + MMOCore.digit.format(PlayerData.get(player).getStamina()); + + else if (identifier.equals("max_stamina")) + return "" + MMOCore.digit.format(PlayerData.get(player).getStats().getStat(StatType.MAX_STAMINA)); + + else if (identifier.equals("stamina_bar")) { + String format = ""; + PlayerData data = PlayerData.get(player); + double ratio = 20 * data.getStamina() / data.getStats().getStat(StatType.MAX_STAMINA); + for (double j = 1; j < 20; j++) + format += (ratio >= j ? ChatColor.BLUE : ratio >= j - .5 ? ChatColor.AQUA : ChatColor.WHITE) + AltChar.listSquare; + return format; + } + + else if (identifier.equals("stellium")) + return "" + MMOCore.digit.format(PlayerData.get(player).getStellium()); + + else if (identifier.equals("max_stellium")) + return "" + MMOCore.digit.format(PlayerData.get(player).getStats().getStat(StatType.MAX_STELLIUM)); + + else if (identifier.equals("stellium_bar")) { + String format = ""; + PlayerData data = PlayerData.get(player); + double ratio = 20 * data.getStellium() / data.getStats().getStat(StatType.MAX_STELLIUM); + for (double j = 1; j < 20; j++) + format += (ratio >= j ? ChatColor.BLUE : ratio >= j - .5 ? ChatColor.AQUA : ChatColor.WHITE) + AltChar.listSquare; + return format; + } + + else if (identifier.equals("class")) + return PlayerData.get(player).getProfess().getName(); + + return null; + } +} diff --git a/src/net/Indyuce/mmocore/comp/rpg/DefaultRPGUtilHandler.java b/src/net/Indyuce/mmocore/comp/rpg/DefaultRPGUtilHandler.java new file mode 100644 index 00000000..e686a2f7 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/rpg/DefaultRPGUtilHandler.java @@ -0,0 +1,11 @@ +package net.Indyuce.mmocore.comp.rpg; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.TemporaryStats; + +public class DefaultRPGUtilHandler implements RPGUtilHandler { + @Override + public TemporaryStats cachePlayerStats(PlayerData playerData) { + return new TemporaryStats(playerData.getStats()); + } +} diff --git a/src/net/Indyuce/mmocore/comp/rpg/RPGUtilHandler.java b/src/net/Indyuce/mmocore/comp/rpg/RPGUtilHandler.java new file mode 100644 index 00000000..bc2f42f1 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/rpg/RPGUtilHandler.java @@ -0,0 +1,9 @@ +package net.Indyuce.mmocore.comp.rpg; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.TemporaryStats; + +public interface RPGUtilHandler { + public TemporaryStats cachePlayerStats(PlayerData playerData); + +} diff --git a/src/net/Indyuce/mmocore/comp/rpg/damage/DamageHandler.java b/src/net/Indyuce/mmocore/comp/rpg/damage/DamageHandler.java new file mode 100644 index 00000000..3e8c9a14 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/rpg/damage/DamageHandler.java @@ -0,0 +1,9 @@ +package net.Indyuce.mmocore.comp.rpg.damage; + +import org.bukkit.entity.Entity; + +public interface DamageHandler { + public DamageInfo getDamage(Entity entity); + + public boolean hasDamage(Entity entity); +} diff --git a/src/net/Indyuce/mmocore/comp/rpg/damage/DamageInfo.java b/src/net/Indyuce/mmocore/comp/rpg/damage/DamageInfo.java new file mode 100644 index 00000000..113172d7 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/rpg/damage/DamageInfo.java @@ -0,0 +1,92 @@ +package net.Indyuce.mmocore.comp.rpg.damage; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.Indyuce.mmocore.api.player.stats.StatType; + +public class DamageInfo { + private final Set types; + private final double value; + + public DamageInfo(DamageType... type) { + this(0, type); + } + + public DamageInfo(double value, DamageType... types) { + this(value, Arrays.asList(types)); + } + + public DamageInfo(double value, List types) { + this(value, new HashSet<>(types)); + } + + public DamageInfo(double value, Set types) { + // Validate.notEmpty(types, "Damage must have at least one damage + // type!"); + + this.types = types; + this.value = value; + } + + public DamageInfo merge(DamageInfo info) { + types.addAll(info.getTypes()); + return this; + } + + public Set getTypes() { + return types; + } + + public boolean hasType(DamageType type) { + return types.contains(type); + } + + public double getValue() { + return value; + } + + public enum DamageType { + + /* + * skills or abilities dealing magic damage + */ + MAGICAL(StatType.MAGICAL_DAMAGE), + + /* + * skills or abilities dealing physical damage + */ + PHYSICAL(StatType.PHYSICAL_DAMAGE), + + /* + * weapons dealing damage + */ + WEAPON(StatType.WEAPON_DAMAGE), + + /* + * skill damage + */ + SKILL(StatType.SKILL_DAMAGE), + + /* + * projectile based weapons or skills + */ + PROJECTILE(StatType.PROJECTILE_DAMAGE); + + private final StatType stat; + + private DamageType(StatType stat) { + this.stat = stat; + } + + public StatType getStat() { + return stat; + } + + public String getPath() { + return name().toLowerCase().replace("_", "-"); + } + } +} diff --git a/src/net/Indyuce/mmocore/comp/vault/MoneyTrigger.java b/src/net/Indyuce/mmocore/comp/vault/MoneyTrigger.java new file mode 100644 index 00000000..9b5f2ec9 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/vault/MoneyTrigger.java @@ -0,0 +1,47 @@ +package net.Indyuce.mmocore.comp.vault; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.math.formula.RandomAmount; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class MoneyTrigger extends Trigger { + private final RandomAmount amount; + private final Operation operation; + + public MoneyTrigger(MMOLineConfig config) { + super(config); + + config.validate("amount"); + amount = new RandomAmount(config.getString("amount")); + operation = config.contains("operation") ? Operation.valueOf(config.getString("operation").toUpperCase()) : Operation.GIVE; + } + + @Override + public void apply(PlayerData player) { + + /* + * cannot run this safe check on server startup since the mmoloader is + * instanced when the plugin loads and the economy has not been loaded + * yet. + */ + if (!MMOCore.plugin.economy.isValid()) + return; + + if (operation == Operation.GIVE) + MMOCore.plugin.economy.getEconomy().depositPlayer(player.getPlayer(), amount.calculate()); + + else if (operation == Operation.SET) + throw new IllegalArgumentException("Operation SET is not available for the money trigger."); + + else + MMOCore.plugin.economy.getEconomy().withdrawPlayer(player.getPlayer(), amount.calculate()); + } + + public enum Operation { + GIVE, + SET, + TAKE; + } +} diff --git a/src/net/Indyuce/mmocore/comp/vault/VaultEconomy.java b/src/net/Indyuce/mmocore/comp/vault/VaultEconomy.java new file mode 100644 index 00000000..a9fe6a51 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/vault/VaultEconomy.java @@ -0,0 +1,32 @@ +package net.Indyuce.mmocore.comp.vault; + +import java.util.logging.Level; + +import org.bukkit.Bukkit; + +import net.Indyuce.mmocore.MMOCore; +import net.milkbowl.vault.economy.Economy; + +public class VaultEconomy { + private Economy economy; + + public VaultEconomy() { + try { + economy = Bukkit.getServicesManager().getRegistration(Economy.class).getProvider(); + MMOCore.log(Level.INFO, "Hooked onto Vault"); + } catch (Exception exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Vault was found but MMOCore was unable to successfully find/load an economy plugin."); + } + } + + /* + * checks if an economy plugin was found. + */ + public boolean isValid() { + return economy != null; + } + + public Economy getEconomy() { + return economy; + } +} diff --git a/src/net/Indyuce/mmocore/comp/vault/VaultMMOLoader.java b/src/net/Indyuce/mmocore/comp/vault/VaultMMOLoader.java new file mode 100644 index 00000000..0914cf04 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/vault/VaultMMOLoader.java @@ -0,0 +1,48 @@ +package net.Indyuce.mmocore.comp.vault; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoader; +import net.Indyuce.mmocore.api.quest.objective.Objective; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class VaultMMOLoader implements MMOLoader { + + @Override + public Condition loadCondition(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Trigger loadTrigger(MMOLineConfig config) { + + if (config.getKey().equalsIgnoreCase("money")) + return new MoneyTrigger(config); + + return null; + } + + @Override + public DropItem loadDropItem(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Objective loadObjective(MMOLineConfig config, ConfigurationSection section) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ExperienceSource loadExperienceSource(MMOLineConfig config, Profession profession) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/net/Indyuce/mmocore/comp/worldguard/DefaultRegionHandler.java b/src/net/Indyuce/mmocore/comp/worldguard/DefaultRegionHandler.java new file mode 100644 index 00000000..81c59c89 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/worldguard/DefaultRegionHandler.java @@ -0,0 +1,14 @@ +package net.Indyuce.mmocore.comp.worldguard; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Location; + +public class DefaultRegionHandler implements RegionHandler { + + @Override + public List getRegions(Location loc) { + return new ArrayList<>(); + } +} diff --git a/src/net/Indyuce/mmocore/comp/worldguard/RegionCondition.java b/src/net/Indyuce/mmocore/comp/worldguard/RegionCondition.java new file mode 100644 index 00000000..ccb36621 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/worldguard/RegionCondition.java @@ -0,0 +1,24 @@ +package net.Indyuce.mmocore.comp.worldguard; + +import java.util.Arrays; +import java.util.List; + +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.condition.ConditionInstance; +import net.Indyuce.mmocore.api.load.MMOLineConfig; + +public class RegionCondition extends Condition { + private final List names; + + public RegionCondition(MMOLineConfig config) { + super(config); + + config.validate("name"); + names = Arrays.asList(config.getString("name").split("\\,")); + } + + @Override + public boolean isMet(ConditionInstance entity) { + return entity.getRegionStream().filter(str -> names.contains(str)).count() > 0; + } +} diff --git a/src/net/Indyuce/mmocore/comp/worldguard/RegionHandler.java b/src/net/Indyuce/mmocore/comp/worldguard/RegionHandler.java new file mode 100644 index 00000000..e2bba3f9 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/worldguard/RegionHandler.java @@ -0,0 +1,9 @@ +package net.Indyuce.mmocore.comp.worldguard; + +import java.util.List; + +import org.bukkit.Location; + +public interface RegionHandler { + public List getRegions(Location loc); +} diff --git a/src/net/Indyuce/mmocore/comp/worldguard/WorldGuardMMOLoader.java b/src/net/Indyuce/mmocore/comp/worldguard/WorldGuardMMOLoader.java new file mode 100644 index 00000000..86d2270a --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/worldguard/WorldGuardMMOLoader.java @@ -0,0 +1,48 @@ +package net.Indyuce.mmocore.comp.worldguard; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoader; +import net.Indyuce.mmocore.api.quest.objective.Objective; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class WorldGuardMMOLoader implements MMOLoader { + + @Override + public Condition loadCondition(MMOLineConfig config) { + + if (config.getKey().equals("region")) + return new RegionCondition(config); + + return null; + } + + @Override + public Trigger loadTrigger(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public DropItem loadDropItem(MMOLineConfig config) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Objective loadObjective(MMOLineConfig config, ConfigurationSection section) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ExperienceSource loadExperienceSource(MMOLineConfig config, Profession profession) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/net/Indyuce/mmocore/comp/worldguard/WorldGuardRegionHandler.java b/src/net/Indyuce/mmocore/comp/worldguard/WorldGuardRegionHandler.java new file mode 100644 index 00000000..7b7d6da5 --- /dev/null +++ b/src/net/Indyuce/mmocore/comp/worldguard/WorldGuardRegionHandler.java @@ -0,0 +1,19 @@ +package net.Indyuce.mmocore.comp.worldguard; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Location; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.WorldGuard; + +public class WorldGuardRegionHandler implements RegionHandler { + + @Override + public List getRegions(Location loc) { + List regions = new ArrayList<>(); + WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(loc)).getRegions().forEach(region -> regions.add(region.getId())); + return regions; + } +} diff --git a/src/net/Indyuce/mmocore/gui/AttributeView.java b/src/net/Indyuce/mmocore/gui/AttributeView.java new file mode 100644 index 00000000..856610e8 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/AttributeView.java @@ -0,0 +1,135 @@ +package net.Indyuce.mmocore.gui; + +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.attribute.PlayerAttribute; +import net.Indyuce.mmocore.api.player.attribute.PlayerAttributes.AttributeInstance; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.api.player.stats.stat.modifier.StatModifier; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; + +public class AttributeView extends EditableInventory { + public AttributeView() { + super("attribute-view"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + if (function.equalsIgnoreCase("reallocation")) + return new InventoryPlaceholderItem(config) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Placeholders holders = new Placeholders(); + holders.register("points", inv.getPlayerData().getAttributeReallocationPoints()); + holders.register("total", inv.getPlayerData().getAttributes().countSkillPoints()); + return holders; + } + }; + + return function.startsWith("attribute_") ? new AttributeItem(function, config) : new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data) { + return new SkillViewerInventory(data, this); + } + + public class AttributeItem extends InventoryPlaceholderItem { + private final PlayerAttribute attribute; + + public AttributeItem(String function, ConfigurationSection config) { + super(config); + + attribute = MMOCore.plugin.attributeManager.get(function.substring("attribute_".length()).toLowerCase().replace(" ", "-").replace("_", "-")); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + int total = inv.getPlayerData().getAttributes().getInstance(attribute).getTotal(); + + Placeholders holders = new Placeholders(); + holders.register("name", attribute.getName()); + holders.register("buffs", attribute.getStats().size()); + holders.register("spent", inv.getPlayerData().getAttributes().getInstance(attribute).getBase()); + holders.register("max", attribute.getMax()); + holders.register("current", total); + holders.register("attribute_points", inv.getPlayerData().getAttributePoints()); + for (StatType stat : attribute.getStats()) { + StatModifier buff = attribute.getBuff(stat); + holders.register("buff_" + stat.name().toLowerCase(), buff); + holders.register("total_" + stat.name().toLowerCase(), buff.multiply(total)); + } + return holders; + } + } + + public class SkillViewerInventory extends GeneratedInventory { + public SkillViewerInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + } + + @Override + public String calculateName() { + return getName(); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + + if (item.getFunction().equalsIgnoreCase("reallocation")) { + int spent = playerData.getAttributes().countSkillPoints(); + if (spent < 1) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("no-attribute-points-spent")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + return; + } + + if (playerData.getAttributeReallocationPoints() < 1) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-attribute-reallocation-point")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + return; + } + + playerData.getAttributes().getAttributeInstances().forEach(ins -> ins.setBase(0)); + playerData.giveAttributePoints(spent); + playerData.giveAttributeReallocationPoints(-1); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("attribute-points-reallocated", "points", "" + playerData.getAttributePoints())); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1); + open(); + } + + if (item.getFunction().startsWith("attribute_")) { + PlayerAttribute attribute = ((AttributeItem) item).attribute; + + if (playerData.getAttributePoints() < 1) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-attribute-point")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + return; + } + + AttributeInstance ins = playerData.getAttributes().getInstance(attribute); + if (attribute.hasMax() && ins.getBase() >= attribute.getMax()) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("attribute-max-points-hit")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + return; + } + + ins.addBase(1); + playerData.giveAttributePoints(-1); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("attribute-level-up", "attribute", attribute.getName(), "level", "" + ins.getBase())); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1); + open(); + } + } + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/gui/ClassConfirmation.java b/src/net/Indyuce/mmocore/gui/ClassConfirmation.java new file mode 100644 index 00000000..272d6ac1 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/ClassConfirmation.java @@ -0,0 +1,143 @@ +package net.Indyuce.mmocore.gui; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.PlayerChangeClassEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.api.player.profess.SavedClassInformation; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; + +public class ClassConfirmation extends EditableInventory { + public ClassConfirmation() { + super("class-confirm"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + return function.equalsIgnoreCase("yes") ? new YesItem(config) : new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data, PlayerClass profess, PluginInventory last) { + return new ClassConfirmationInventory(data, this, profess, last); + } + + public class UnlockedItem extends InventoryPlaceholderItem { + + public UnlockedItem(ConfigurationSection config) { + super(config); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + PlayerClass profess = ((ClassConfirmationInventory) inv).profess; + SavedClassInformation info = inv.getPlayerData().getClassInfo(profess); + Placeholders holders = new Placeholders(); + + int nextLevelExp = MMOCore.plugin.configManager.getNeededExperience(info.getLevel() + 1); + double ratio = (double) info.getExperience() / (double) nextLevelExp; + + String bar = "" + ChatColor.BOLD; + int chars = (int) (ratio * 20); + for (int j = 0; j < 20; j++) + bar += (j == chars ? "" + ChatColor.WHITE + ChatColor.BOLD : "") + "|"; + + holders.register("percent", MMOCore.digit.format(ratio * 100)); + holders.register("progress", bar); + holders.register("class", profess.getName()); + holders.register("unlocked_skills", info.getSkillKeys().size()); + holders.register("class_skills", profess.getSkills().size()); + holders.register("next_level", "" + nextLevelExp); + holders.register("level", info.getLevel()); + holders.register("exp", info.getExperience()); + holders.register("skill_points", info.getSkillPoints()); + + return holders; + } + } + + public class YesItem extends InventoryItem { + private final InventoryPlaceholderItem unlocked, locked; + + public YesItem(ConfigurationSection config) { + super(config); + + Validate.isTrue(config.contains("unlocked"), "Could not load 'unlocked' config"); + Validate.isTrue(config.contains("locked"), "Could not load 'locked' config"); + + unlocked = new UnlockedItem(config.getConfigurationSection("unlocked")); + locked = new InventoryPlaceholderItem(config.getConfigurationSection("locked")) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Placeholders holders = new Placeholders(); + holders.register("class", ((ClassConfirmationInventory) inv).profess.getName()); + return holders; + } + }; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + return inv.getPlayerData().hasSavedClass(((ClassConfirmationInventory) inv).profess) ? unlocked.display(inv, n) : locked.display(inv, n); + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + } + + public class ClassConfirmationInventory extends GeneratedInventory { + private final PlayerClass profess; + private final PluginInventory last; + + public ClassConfirmationInventory(PlayerData playerData, EditableInventory editable, PlayerClass profess, PluginInventory last) { + super(playerData, editable); + + this.profess = profess; + this.last = last; + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (event.getInventory() != event.getClickedInventory()) + return; + + if (item.getFunction().equals("back")) + last.open(); + + else if (item.getFunction().equals("yes")) { + + PlayerChangeClassEvent called = new PlayerChangeClassEvent(playerData, profess); + Bukkit.getPluginManager().callEvent(called); + if (called.isCancelled()) + return; + + playerData.giveClassPoints(-1); + (playerData.hasSavedClass(profess) ? playerData.getClassInfo(profess) : new SavedClassInformation(1, 0, 0)).load(profess, playerData); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("class-select", "class", profess.getName())); + player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1, 1); + player.closeInventory(); + } + } + + @Override + public String calculateName() { + return getName(); + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/ClassSelect.java b/src/net/Indyuce/mmocore/gui/ClassSelect.java new file mode 100644 index 00000000..95333a4a --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/ClassSelect.java @@ -0,0 +1,134 @@ +package net.Indyuce.mmocore.gui; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigMessage; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.api.player.profess.PlayerClass.ClassOption; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.manager.InventoryManager; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class ClassSelect extends EditableInventory { + public ClassSelect() { + super("class-select"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + return function.equals("class") ? new ClassItem(config) : new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data) { + return new ProfessSelectionInventory(data, this); + } + + public class ClassItem extends NoPlaceholderItem { + private final String name; + private final List lore; + + public ClassItem(ConfigurationSection config) { + super(Material.BARRIER, config); + + this.name = config.getString("name"); + this.lore = config.getStringList("lore"); + } + + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + ProfessSelectionInventory generated = (ProfessSelectionInventory) inv; + + if (n >= generated.classes.size()) + return null; + + PlayerClass profess = generated.classes.get(n); + ItemStack item = profess.getIcon(); + ItemMeta meta = item.getItemMeta(); + if (hideFlags()) + meta.addItemFlags(ItemFlag.values()); + meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name).replace("{name}", profess.getName())); + List lore = new ArrayList<>(this.lore); + + int index = lore.indexOf("{lore}"); + if (index >= 0) { + lore.remove(index); + for (int j = 0; j < profess.getDescription().size(); j++) + lore.add(index + j, profess.getDescription().get(j)); + } + + index = lore.indexOf("{attribute-lore}"); + if (index >= 0) { + lore.remove(index); + for (int j = 0; j < profess.getAttributeDescription().size(); j++) + lore.add(index + j, profess.getAttributeDescription().get(j)); + } + + meta.setLore(lore); + item.setItemMeta(meta); + return NBTItem.get(item).add(new ItemTag("classId", profess.getId())).toItem(); + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + } + + public class ProfessSelectionInventory extends GeneratedInventory { + private final List classes = MMOCore.plugin.classManager.getAll().stream().filter(c -> c.hasOption(ClassOption.DISPLAY)).collect(Collectors.toList()); + + public ProfessSelectionInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + } + + @Override + public String calculateName() { + return getName(); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (item.getFunction().equals("class")) { + String tag = NBTItem.get(event.getCurrentItem()).getString("classId"); + if (tag.equals("")) + return; + + if (playerData.getClassPoints() < 1) { + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + new ConfigMessage("cant-choose-new-class").send(player); + return; + } + + PlayerClass profess = MMOCore.plugin.classManager.get(tag); + if (profess.equals(playerData.getProfess())) { + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("already-on-class", "class", profess.getName())); + return; + } + + InventoryManager.CLASS_CONFIRM.newInventory(playerData, profess, this).open(); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/PlayerStats.java b/src/net/Indyuce/mmocore/gui/PlayerStats.java new file mode 100644 index 00000000..f0660775 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/PlayerStats.java @@ -0,0 +1,301 @@ +package net.Indyuce.mmocore.gui; + +import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Booster; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.math.format.DelayFormat; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.attribute.PlayerAttribute; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; + +public class PlayerStats extends EditableInventory { + public PlayerStats() { + super("player-stats"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + + if (function.equals("boost")) + return new BoostItem(config); + + if (function.equals("boost-next")) + return new NoPlaceholderItem(config) { + + @Override + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + InventoryItem boost = inv.getByFunction("boost"); + return boost != null && ((PlayerStatsInventory) inv).boostOffset + boost.getSlots().size() < MMOCore.plugin.boosterManager.getBoosters().size(); + } + }; + + if (function.equals("boost-previous")) + return new NoPlaceholderItem(config) { + + @Override + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return ((PlayerStatsInventory) inv).boostOffset > 0; + } + }; + + if (function.equals("party")) + return new PartyMoraleItem(config); + + if (function.startsWith("profession_")) { + String id = function.substring("profession_".length()).toLowerCase(); + Validate.isTrue(MMOCore.plugin.professionManager.has(id)); + Profession profession = MMOCore.plugin.professionManager.get(id); + + return new InventoryPlaceholderItem(config) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + + Placeholders holders = new Placeholders(); + net.Indyuce.mmocore.api.player.stats.PlayerStats stats = inv.getPlayerData().getStats(); + + double ratio = (double) inv.getPlayerData().getCollectionSkills().getExperience(profession) / MMOCore.plugin.configManager.getNeededExperience(inv.getPlayerData().getCollectionSkills().getLevel(profession) + 1); + + String bar = "" + ChatColor.BOLD; + int chars = (int) (ratio * 20); + for (int j = 0; j < 20; j++) + bar += (j == chars ? "" + ChatColor.WHITE + ChatColor.BOLD : "") + "|"; + + // holders.register("profession", type.getName()); + holders.register("progress", bar); + holders.register("level", "" + inv.getPlayerData().getCollectionSkills().getLevel(profession)); + holders.register("percent", MMOCore.digit.format(ratio * 100)); + for (StatType stat : StatType.values()) + if (stat.matches(profession)) + holders.register(stat.name().toLowerCase(), stat.format(stats.getStat(stat))); + + return holders; + } + }; + } + + if (function.equals("profile")) + return new PlayerProfileItem(config); + + if (function.equals("stats")) + return new InventoryPlaceholderItem(config) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + + net.Indyuce.mmocore.api.player.stats.PlayerStats stats = inv.getPlayerData().getStats(); + Placeholders holders = new Placeholders(); + + for (StatType stat : StatType.values()) { + double extra = stats.getExtraStat(stat); + double base = stats.getBase(stat); + holders.register(stat.name().toLowerCase(), stat.format(extra + base)); + holders.register(stat.name().toLowerCase() + "_base", stat.format(base)); + holders.register(stat.name().toLowerCase() + "_extra", stat.format(extra)); + } + + for (PlayerAttribute attribute : MMOCore.plugin.attributeManager.getAll()) + holders.register("attribute_" + attribute.getId().replace("-", "_"), inv.getPlayerData().getAttributes().getAttribute(attribute)); + + return holders; + } + }; + + return new NoPlaceholderItem(config); + } + + public PlayerStatsInventory newInventory(PlayerData data) { + return new PlayerStatsInventory(data, this); + } + + public class PlayerStatsInventory extends GeneratedInventory { + private int boostOffset; + + public PlayerStatsInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + } + + @Override + public String calculateName() { + return getName(); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (item.hasFunction()) + if (item.getFunction().equals("boost-next")) { + boostOffset++; + open(); + + } else if (item.getFunction().equals("boost-previous")) { + boostOffset--; + open(); + } + } + } + + public class BoostItem extends InventoryItem { + private final InventoryPlaceholderItem noBoost, mainLevel, profession; + + public BoostItem(ConfigurationSection config) { + super(config); + + ConfigurationSection noBoost = config.getConfigurationSection("no-boost"); + Validate.notNull(noBoost, "Could not load 'no-boost' config"); + this.noBoost = new NoPlaceholderItem(noBoost); + + ConfigurationSection mainLevel = config.getConfigurationSection("main-level"); + Validate.notNull(mainLevel, "Could not load 'main-level' config"); + this.mainLevel = new InventoryPlaceholderItem(mainLevel) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Placeholders holders = new Placeholders(); + Booster boost = MMOCore.plugin.boosterManager.get(((PlayerStatsInventory) inv).boostOffset + n); + + holders.register("author", boost.hasAuthor() ? boost.getAuthor() : "Server"); + holders.register("value", (int) (boost.getExtra() * 100)); + holders.register("left", new DelayFormat(2).format(boost.getLeft())); + + return holders; + } + }; + + ConfigurationSection profession = config.getConfigurationSection("profession"); + Validate.notNull(profession, "Could not load 'profession' config"); + this.profession = new InventoryPlaceholderItem(profession) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Placeholders holders = new Placeholders(); + Booster boost = MMOCore.plugin.boosterManager.get(((PlayerStatsInventory) inv).boostOffset + n); + + holders.register("author", boost.hasAuthor() ? boost.getAuthor() : "Server"); + holders.register("profession", boost.getProfession().getName()); + holders.register("value", (int) (boost.getExtra() * 100)); + holders.register("left", new DelayFormat(2).format(boost.getLeft())); + + return holders; + } + }; + } + + @Override + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + int offset = ((PlayerStatsInventory) inv).boostOffset; + if (n + offset >= MMOCore.plugin.boosterManager.getBoosters().size()) + return noBoost.display(inv, n); + + Booster boost = MMOCore.plugin.boosterManager.get(((PlayerStatsInventory) inv).boostOffset + n); + return amount(boost.hasProfession() ? profession.display(inv, n) : mainLevel.display(inv, n), n + offset + 1); + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + } + + private ItemStack amount(ItemStack item, int amount) { + item.setAmount(amount); + return item; + } + + public class PartyMoraleItem extends InventoryPlaceholderItem { + public PartyMoraleItem(ConfigurationSection config) { + super(config); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Placeholders holders = new Placeholders(); + + int count = inv.getPlayerData().getParty().getMembers().count(); + holders.register("count", "" + count); + for (StatType stat : MMOCore.plugin.partyManager.getBonuses()) + holders.register("buff_" + stat.name().toLowerCase(), MMOCore.plugin.partyManager.getBonus(stat).multiply(count - 1).toString()); + + return holders; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return inv.getPlayerData().hasParty() && inv.getPlayerData().getParty().getMembers().count() > 1; + } + + } + + public class PlayerProfileItem extends InventoryPlaceholderItem { + public PlayerProfileItem(ConfigurationSection config) { + super(config); + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + ItemStack item = super.display(inv, n); + if (item.getType() == Material.PLAYER_HEAD) { + SkullMeta meta = (SkullMeta) item.getItemMeta(); + meta.setOwningPlayer(inv.getPlayer()); + item.setItemMeta(meta); + } + return item; + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + + PlayerData data = inv.getPlayerData(); + Placeholders holders = new Placeholders(); + + int nextLevelExp = MMOCore.plugin.configManager.getNeededExperience(data.getLevel() + 1); + double ratio = (double) data.getExperience() / (double) nextLevelExp; + + String bar = "" + ChatColor.BOLD; + int chars = (int) (ratio * 20); + for (int j = 0; j < 20; j++) + bar += (j == chars ? "" + ChatColor.WHITE + ChatColor.BOLD : "") + "|"; + + holders.register("percent", MMOCore.digit.format(ratio * 100)); + holders.register("exp", "" + data.getExperience()); + holders.register("level", "" + data.getLevel()); + holders.register("class_points", "" + data.getClassPoints()); + holders.register("skill_points", "" + data.getSkillPoints()); + holders.register("progress", bar); + holders.register("next_level", "" + nextLevelExp); + holders.register("player", "" + data.getPlayer().getName()); + holders.register("class", "" + data.getProfess().getName()); + + return holders; + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/QuestViewer.java b/src/net/Indyuce/mmocore/gui/QuestViewer.java new file mode 100644 index 00000000..2cc9d998 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/QuestViewer.java @@ -0,0 +1,377 @@ +package net.Indyuce.mmocore.gui; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.math.format.DelayFormat; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.quest.Quest; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class QuestViewer extends EditableInventory { + public QuestViewer() { + super("quest-list"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + return function.equals("quest") ? new QuestItem(config) : new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data) { + return new QuestInventory(data, this); + } + + public class QuestItem extends NoPlaceholderItem { + private final InventoryPlaceholderItem noQuest, locked; + + private final String mainHit, mainNotHit, professionHit, professionNotHit; + private final SimpleDateFormat dateFormat; + + public QuestItem(ConfigurationSection config) { + super(config); + + Validate.isTrue(config.contains("no-quest"), "Could not load config 'no-quest'"); + Validate.isTrue(config.contains("locked"), "Could not load config 'locked'"); + + locked = new NoPlaceholderItem(config.getConfigurationSection("locked")); + noQuest = new NoPlaceholderItem(config.getConfigurationSection("no-quest")); + + Validate.isTrue(config.contains("date-format"), "Could not find date-format"); + dateFormat = new SimpleDateFormat(config.getString("date-format")); + + Validate.notNull(mainHit = config.getString("level-requirement.main.hit"), "Could not load 'level-requirement.main.hit'"); + Validate.notNull(mainNotHit = config.getString("level-requirement.main.not-hit"), "Could not load 'level-requirement.main.not-hit'"); + Validate.notNull(professionHit = config.getString("level-requirement.profession.hit"), "Could not load 'level-requirement.profession.hit'"); + Validate.notNull(professionNotHit = config.getString("level-requirement.profession.not-hit"), "Could not load 'level-requirement.profession.not-hit'"); + } + + @Override + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + + QuestInventory list = (QuestInventory) inv; + int index = list.page * list.perPage + n; + if (index >= list.quests.size()) + return noQuest.display(inv, n); + + Quest quest = list.quests.get(index); + if (quest.hasParent() && !inv.getPlayerData().getQuestData().checkParentAvailability(quest)) + return locked.display(inv, n); + + List lore = new ArrayList<>(getLore()); + + /* + * replace quest lore + */ + index = lore.indexOf("{lore}"); + if (index >= 0) { + lore.remove(index); + for (int j = 0; j < quest.getLore().size(); j++) + lore.add(index + j, quest.getLore().get(j)); + } + + /* + * calculate quest info for later. + */ + int reqCount = quest.countLevelRestrictions(); + boolean started = inv.getPlayerData().getQuestData().hasCurrent(quest), completed = inv.getPlayerData().getQuestData().hasFinished(quest), cooldown = completed ? inv.getPlayerData().getQuestData().checkCooldownAvailability(quest) : false; + + for (Iterator iterator = lore.iterator(); iterator.hasNext();) { + String next = iterator.next(); + + if ((next.startsWith("{level_req}") && reqCount < 1) || (next.startsWith("{started}") && !started) || (next.startsWith("{!started}") && started) || (next.startsWith("{completed}") && !completed) || (next.startsWith("{completed_cannot_redo}") && !(completed && !quest.isRedoable())) || (next.startsWith("{completed_can_redo}") && !(cooldown && quest.isRedoable())) || (next.startsWith("{completed_delay}") && !(completed && !cooldown))) + iterator.remove(); + } + + /* + * replace level requirements + */ + index = lore.indexOf("{level_req}{level_requirements}"); + if (index >= 0) { + lore.remove(index); + int mainRequired = quest.getLevelRestriction(null); + if (mainRequired > 0) + lore.add(index, (inv.getPlayerData().getLevel() >= mainRequired ? mainHit : mainNotHit).replace("{level}", "" + mainRequired)); + + for (Profession profession : quest.getLevelRestrictions()) { + int required = quest.getLevelRestriction(profession); + lore.add(index + (mainRequired > 0 ? 1 : 0), (inv.getPlayerData().getCollectionSkills().getLevel(profession) >= required ? professionHit : professionNotHit).replace("{level}", "" + required).replace("{profession}", profession.getName())); + } + } + + Placeholders holders = getPlaceholders(inv.getPlayerData(), quest); + + for (int j = 0; j < lore.size(); j++) + lore.set(j, ChatColor.GRAY + holders.apply(inv.getPlayer(), lore.get(j))); + + /* + * generate item + */ + ItemStack item = new ItemStack(getMaterial()); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(holders.apply(inv.getPlayer(), getName())); + meta.addItemFlags(ItemFlag.values()); + meta.setLore(lore); + item.setItemMeta(meta); + + return NBTItem.get(item).add(new ItemTag("questId", quest.getId())).toItem(); + } + + private Placeholders getPlaceholders(PlayerData data, Quest quest) { + Placeholders holders = new Placeholders(); + holders.register("name", quest.getName()); + holders.register("total_level_req", quest.getLevelRestrictions().size() + (quest.getLevelRestriction(null) > 0 ? 1 : 0)); + holders.register("current_level_req", (data.getLevel() >= quest.getLevelRestriction(null) ? 1 : 0) + quest.getLevelRestrictions().stream().filter(type -> data.getCollectionSkills().getLevel(type) >= quest.getLevelRestriction(type)).collect(Collectors.toSet()).size()); + + if (data.getQuestData().hasCurrent(quest)) { + holders.register("objective", data.getQuestData().getCurrent().getFormattedLore()); + holders.register("progress", new DecimalFormat("0").format((double) data.getQuestData().getCurrent().getObjectiveNumber() / quest.getObjectives().size() * 100)); + } + + if (data.getQuestData().hasFinished(quest)) { + holders.register("date", dateFormat.format(data.getQuestData().getFinishDate(quest))); + holders.register("delay", new DelayFormat(2).format(data.getQuestData().getDelayFeft(quest))); + } + + return holders; + } + } + + public class QuestInventory extends GeneratedInventory { + private final List quests = new ArrayList<>(MMOCore.plugin.questManager.getAll()); + private final int perPage; + + private int page; + + public QuestInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + + perPage = editable.getByFunction("quest").getSlots().size(); + } + + @Override + public String calculateName() { + return getName(); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (item.getFunction().equals("previous")) { + page--; + open(); + return; + } + + if (item.getFunction().equals("next")) { + page++; + open(); + return; + } + + if (item.getFunction().equals("quest")) { + String tag = NBTItem.get(event.getCurrentItem()).getString("questId"); + if (tag.equals("")) + return; + + Quest quest = MMOCore.plugin.questManager.get(tag); + + if (playerData.getQuestData().hasCurrent()) { + + /* + * check if the player is cancelling his ongoing quest. + */ + if (playerData.getQuestData().hasCurrent(quest)) { + if (event.getAction() == InventoryAction.PICKUP_HALF) { + playerData.getQuestData().start(null); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("cancel-quest")); + open(); + } + return; + } + + /* + * the player cannot start a new quest if he is already + * doing one. + */ + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("already-on-quest")); + return; + } + + /* + * check for level requirements. + */ + int level; + if (playerData.getLevel() < (level = quest.getLevelRestriction(null))) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("quest-level-restriction", "level", "Lvl", "count", "" + level)); + return; + } + + for (Profession profession : quest.getLevelRestrictions()) + if (playerData.getCollectionSkills().getLevel(profession) < (level = quest.getLevelRestriction(profession))) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("quest-level-restriction", "level", profession.getName() + " Lvl", "count", "" + level)); + return; + } + + if (playerData.getQuestData().hasFinished(quest)) { + + /* + * if the player has already finished this quest, he can't + * start it again. + */ + if (!quest.isRedoable()) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("cant-redo-quest")); + return; + } + + /* + * + */ + if (!playerData.getQuestData().checkCooldownAvailability(quest)) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("quest-cooldown", "delay", new DelayFormat(2).format(playerData.getQuestData().getDelayFeft(quest)))); + return; + } + } + + /* + * eventually start a new quest. + */ + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("start-quest", "quest", quest.getName())); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1); + playerData.getQuestData().start(quest); + open(); + } + } + } + + // + // @Override + // public Inventory getInventory() { + // Inventory inv = Bukkit.createInventory(this, 54, "Quests"); + // + + // for (int j = (page - 1) * 21; j < Math.min(21 * page, quests.size()); + // j++) { + // Quest quest = quests.get(j); + // + // ItemStack item = new ItemStack(Material.BOOK); + // ItemMeta meta = item.getItemMeta(); + // meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + // + // if (quest.hasParent() && + // !playerData.getQuestData().checkParentAvailability(quest)) { + // item.setType(Material.PAPER); + // meta.setDisplayName(ChatColor.RED + "Not Available"); + // item.setItemMeta(meta); + // + // inv.setItem(slots[j % 21], item); + // continue; + // } + // + // meta.setDisplayName(ChatColor.GREEN + quest.getName()); + // List lore = new ArrayList<>(); + // for (String line : quest.getLore()) + // lore.add(ChatColor.GRAY + ChatColor.translateAlternateColorCodes('&', + // line)); + // + // boolean current; + // if (current = playerData.getQuestData().hasCurrent(quest)) { + // meta.addEnchant(Enchantment.DURABILITY, 1, true); + // lore.add(""); + // lore.add(ChatColor.YELLOW + "Quest Started!"); + // lore.add(ChatColor.GRAY + AltChar.listDash + " Progression: " + + // ChatColor.YELLOW + new DecimalFormat("0").format((double) + // playerData.getQuestData().getCurrent().getObjectiveNumber() / + // quest.getObjectives().size() * 100) + "%"); + // lore.add(ChatColor.GRAY + "" + ChatColor.ITALIC + AltChar.listDash + " " + // + playerData.getQuestData().getCurrent().getFormattedLore()); + // } + // + // int restrictions = quest.getLevelRestrictions().size() + + // (quest.getLevelRestriction(null) > 0 ? 1 : 0); + // int non = (playerData.getLevel() >= quest.getLevelRestriction(null) ? 1 : + // 0) + quest.getLevelRestrictions().stream().filter(type -> + // playerData.getCollectionSkills().getLevel(type) >= + // quest.getLevelRestriction(type)).collect(Collectors.toSet()).size(); + // + // if (playerData.getQuestData().hasFinished(quest)) { + // lore.add(""); + // lore.add(ChatColor.DARK_GRAY + "You've completed this quest on the " + + // new SimpleDateFormat("MMM d + // yyyy").format(playerData.getQuestData().getFinishDate(quest))); + // if (!quest.isRedoable()) + // lore.add(ChatColor.DARK_GRAY + "You can't do this quest twice."); + // else if (!playerData.getQuestData().checkCooldownAvailability(quest)) + // lore.add(ChatColor.DARK_GRAY + "You can start the quest in " + new + // DelayFormat(2).format(playerData.getQuestData().getDelayFeft(quest))); + // else + // lore.add(ChatColor.DARK_GRAY + "You can start this quest."); + // } + // + // if (restrictions > 0) { + // lore.add(""); + // lore.add(ChatColor.GRAY + "Level Requirements (" + non + "/" + + // restrictions + "):"); + // if (quest.getLevelRestriction(null) > 0) + // lore.add(booleanSymbol(playerData.getLevel() >= + // quest.getLevelRestriction(null)) + " Level: " + + // quest.getLevelRestriction(null)); + // for (Profession profession : quest.getLevelRestrictions()) + // lore.add(booleanSymbol(playerData.getCollectionSkills().getLevel(profession) + // >= quest.getLevelRestriction(profession)) + " " + profession.getName() + + // " Level: " + quest.getLevelRestriction(profession)); + // } + // + // if (current) { + // lore.add(""); + // lore.add(ChatColor.RED + AltChar.listDash + " Right click to cancel."); + // } + // + // meta.setLore(lore); + // item.setItemMeta(meta); + // + // inv.setItem(slots[j % 21], NBTItem.get(item).add(new ItemTag("questId", + // quest.getId())).toItem()); + // } + // + // if (page > 1) + // inv.setItem(18, prev = new ConfigItem("PREVIOUS_PAGE").build()); + // if (21 * page < quests.size()) + // inv.setItem(26, next = new ConfigItem("NEXT_PAGE").build()); + // + // return inv; + // } + // + // private String booleanSymbol(boolean bool) { + // return bool ? ChatColor.GREEN + AltChar.ok : ChatColor.RED + AltChar.no; + // } + // +} diff --git a/src/net/Indyuce/mmocore/gui/SkillList.java b/src/net/Indyuce/mmocore/gui/SkillList.java new file mode 100644 index 00000000..e2f9c005 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/SkillList.java @@ -0,0 +1,414 @@ +package net.Indyuce.mmocore.gui; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.Skill.SkillInfo; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class SkillList extends EditableInventory { + public SkillList() { + super("skill-list"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + + if (function.equals("skill")) + return new SkillItem(config); + + if (function.equals("switch")) + return new SwitchItem(config); + + if (function.equals("level")) + return new LevelItem(config); + + if (function.equals("upgrade")) + return new InventoryPlaceholderItem(config) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Skill selected = ((SkillViewerInventory) inv).selected.getSkill(); + Placeholders holders = new Placeholders(); + + holders.register("skill_caps", selected.getName().toUpperCase()); + holders.register("skill", selected.getName()); + holders.register("skill_points", "" + inv.getPlayerData().getSkillPoints()); + + return holders; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return !((SkillViewerInventory) inv).binding; + } + }; + + if (function.equals("slot")) + return new InventoryPlaceholderItem(config) { + private final String none = ChatColor.translateAlternateColorCodes('&', config.getString("no-skill")); + private final Material emptyMaterial = Material.valueOf(config.getString("empty-item").toUpperCase().replace("-", "_").replace(" ", "_")); + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Skill selected = ((SkillViewerInventory) inv).selected.getSkill(); + Skill skill = inv.getPlayerData().hasSkillBound(n) ? inv.getPlayerData().getBoundSkill(n).getSkill() : null; + + Placeholders holders = new Placeholders(); + + holders.register("skill", skill == null ? none : skill.getName()); + holders.register("index", "" + (n + 1)); + holders.register("slot", MMOCoreUtils.intToRoman(n + 1)); + holders.register("selected", selected.getName()); + + return holders; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + ItemStack item = super.display(inv, n); + if (!inv.getPlayerData().hasSkillBound(n)) + item.setType(emptyMaterial); + return item; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return ((SkillViewerInventory) inv).binding; + } + + @Override + public boolean hasDifferentDisplay() { + return true; + } + }; + + return new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data) { + return new SkillViewerInventory(data, this); + } + + public class SwitchItem extends NoPlaceholderItem { + private final InventoryPlaceholderItem binding, upgrading; + + public SwitchItem(ConfigurationSection config) { + super(config); + + Validate.isTrue(config.contains("binding"), "Config must have 'binding'"); + Validate.isTrue(config.contains("upgrading"), "Config must have 'upgrading'"); + + binding = new NoPlaceholderItem(config.getConfigurationSection("binding")); + upgrading = new NoPlaceholderItem(config.getConfigurationSection("upgrading")); + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + return ((SkillViewerInventory) inv).binding ? upgrading.display(inv) : binding.display(inv); + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + } + + public class LevelItem extends InventoryPlaceholderItem { + private final int offset; + + public LevelItem(ConfigurationSection config) { + super(config); + + offset = config.getInt("offset"); + } + + @Override + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + + SkillInfo skill = ((SkillViewerInventory) inv).selected; + int skillLevel = inv.getPlayerData().getSkillLevel(skill.getSkill()) + n - offset; + if (skillLevel < 1) + return new ItemStack(Material.AIR); + + List lore = new ArrayList<>(getLore()); + int index = lore.indexOf("{lore}"); + lore.remove(index); + List skillLore = skill.calculateLore(inv.getPlayerData(), skillLevel); + for (int j = 0; j < skillLore.size(); j++) + lore.add(index + j, skillLore.get(j)); + + for (int j = 0; j < lore.size(); j++) + lore.set(j, ChatColor.GRAY + ChatColor.translateAlternateColorCodes('&', lore.get(j))); + + ItemStack item = new ItemStack(getMaterial()); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', getName().replace("{skill}", skill.getSkill().getName()).replace("{roman}", MMOCoreUtils.intToRoman(skillLevel)).replace("{level}", "" + skillLevel))); + meta.addItemFlags(ItemFlag.values()); + meta.setLore(lore); + item.setItemMeta(meta); + + return NBTItem.get(item).add(new ItemTag("skillId", skill.getSkill().getId())).toItem(); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + return null; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return !((SkillViewerInventory) inv).binding; + } + } + + public class SkillItem extends InventoryPlaceholderItem { + private final int selectedSkillSlot; + + public SkillItem(ConfigurationSection config) { + super(Material.BARRIER, config); + + selectedSkillSlot = config.getInt("selected-slot"); + } + + @Override + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + + /* + * calculate placeholders + */ + SkillViewerInventory viewer = (SkillViewerInventory) inv; + SkillInfo skill = viewer.skills.get(mod(n + inv.getPlayerData().skillGuiDisplayOffset, viewer.skills.size())); + Placeholders holders = getPlaceholders(inv.getPlayerData(), skill); + + List lore = new ArrayList<>(getLore()); + + int index = lore.indexOf("{lore}"); + lore.remove(index); + List skillLore = skill.calculateLore(inv.getPlayerData()); + for (int j = 0; j < skillLore.size(); j++) + lore.add(index + j, skillLore.get(j)); + + boolean unlocked = skill.getUnlockLevel() <= inv.getPlayerData().getLevel(); + + for (Iterator iterator = lore.iterator(); iterator.hasNext();) { + String next = iterator.next(); + if ((next.startsWith("{unlocked}") && !unlocked) || (next.startsWith("{locked}") && unlocked) || (next.startsWith("{max_level}") && (!skill.hasMaxLevel() || skill.getMaxLevel() > inv.getPlayerData().getSkillLevel(skill.getSkill())))) + iterator.remove(); + } + + for (int j = 0; j < lore.size(); j++) + lore.set(j, ChatColor.GRAY + holders.apply(inv.getPlayer(), lore.get(j))); + + /* + * generate item + */ + ItemStack item = skill.getSkill().getIcon(); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(holders.apply(inv.getPlayer(), new String(getName()))); + meta.addItemFlags(ItemFlag.values()); + meta.setLore(lore); + item.setItemMeta(meta); + + return NBTItem.get(item).add(new ItemTag("skillId", skill.getSkill().getId())).toItem(); + } + + public Placeholders getPlaceholders(PlayerData player, SkillInfo skill) { + Placeholders holders = new Placeholders(); + holders.register("skill", skill.getSkill().getName()); + holders.register("unlock", "" + skill.getUnlockLevel()); + holders.register("level", "" + player.getSkillLevel(skill.getSkill())); + return holders; + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + return null; + } + } + + public class SkillViewerInventory extends GeneratedInventory { + + /* + * cached information + */ + private final List skills; + private final List skillSlots; + private final List slotSlots; + + private boolean binding; + private SkillInfo selected; + + public SkillViewerInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + + skills = new ArrayList<>(playerData.getProfess().getSkills()); + skillSlots = getEditable().getByFunction("skill").getSlots(); + slotSlots = getEditable().getByFunction("slot").getSlots(); + } + + @Override + public String calculateName() { + return getName(); + } + + @Override + public void open() { + int selectedSkillSlot = ((SkillItem) getEditable().getByFunction("skill")).selectedSkillSlot; + selected = skills.get(mod(selectedSkillSlot + playerData.skillGuiDisplayOffset, skills.size())); + + super.open(); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + + if (skillSlots.contains(event.getRawSlot()) && event.getRawSlot() != ((SkillItem) getEditable().getByFunction("skill")).selectedSkillSlot) { + player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 2); + playerData.skillGuiDisplayOffset = (playerData.skillGuiDisplayOffset + (event.getRawSlot() - 13)) % skills.size(); + open(); + return; + } + + if (item.getFunction().equals("previous")) { + player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 2); + playerData.skillGuiDisplayOffset = (playerData.skillGuiDisplayOffset - 1) % skills.size(); + open(); + return; + } + + if (item.getFunction().equals("next")) { + player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 2); + playerData.skillGuiDisplayOffset = (playerData.skillGuiDisplayOffset + 1) % skills.size(); + open(); + return; + } + + if (item.getFunction().equals("switch")) { + player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 2); + binding = !binding; + open(); + return; + } + + /* + * binding or unbinding skills. + */ + if (binding) { + for (int index = 0; index < slotSlots.size(); index++) { + int slot = slotSlots.get(index); + if (event.getRawSlot() == slot) { + + // unbind if there is a current spell. + if (event.getAction() == InventoryAction.PICKUP_HALF) { + if (!playerData.hasSkillBound(index)) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("no-skill-bound")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 2); + return; + } + + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 2); + playerData.unbindSkill(index); + open(); + return; + } + + if (selected == null) + return; + + if (selected.getSkill().isPassive()) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-active-skill")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 2); + return; + } + + if (!selected.isUnlocked(playerData)) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-unlocked-skill")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 2); + return; + } + + // if (playerData.getSkillLevel(selected.getSkill()) < + // 1) { + // player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-unlocked-skill")); + // player.playSound(player.getLocation(), + // Sound.ENTITY_VILLAGER_NO, 1, 2); + // return; + // } + + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 2); + playerData.setBoundSkill(index, selected); + open(); + return; + } + } + + /* + * upgrading a player skill + */ + } else if (item.getFunction().equals("upgrade")) { + if (!selected.isUnlocked(playerData)) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-unlocked-skill")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 2); + return; + } + + if (playerData.getSkillPoints() < 1) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-enough-skill-points")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 2); + return; + } + + if (playerData.getSkillLevel(selected.getSkill()) >= selected.getMaxLevel()) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("skill-max-level-hit")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 2); + return; + } + + playerData.giveSkillPoints(-1); + playerData.setSkillLevel(selected.getSkill(), playerData.getSkillLevel(selected.getSkill()) + 1); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("upgrade-skill", "skill", selected.getSkill().getName(), "level", "" + playerData.getSkillLevel(selected.getSkill()))); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 2); + open(); + } + } + } + + private int mod(int x, int n) { + return x < 0 ? (x + n) : (x % n); + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/gui/SubclassConfirmation.java b/src/net/Indyuce/mmocore/gui/SubclassConfirmation.java new file mode 100644 index 00000000..4e0b9ae7 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/SubclassConfirmation.java @@ -0,0 +1,81 @@ +package net.Indyuce.mmocore.gui; + +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.PlayerChangeClassEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; + +public class SubclassConfirmation extends EditableInventory { + public SubclassConfirmation() { + super("subclass-confirm"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + return function.equalsIgnoreCase("yes") ? new InventoryPlaceholderItem(config) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + + Placeholders holders = new Placeholders(); + holders.register("class", ((ClassConfirmationInventory) inv).profess.getName()); + return holders; + } + } : new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data, PlayerClass profess, PluginInventory last) { + return new ClassConfirmationInventory(data, this, profess, last); + } + + public class ClassConfirmationInventory extends GeneratedInventory { + private final PlayerClass profess; + private final PluginInventory last; + + public ClassConfirmationInventory(PlayerData playerData, EditableInventory editable, PlayerClass profess, PluginInventory last) { + super(playerData, editable); + + this.profess = profess; + this.last = last; + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (event.getInventory() != event.getClickedInventory()) + return; + + if (item.getFunction().equals("back")) + last.open(); + + else if (item.getFunction().equals("yes")) { + + PlayerChangeClassEvent called = new PlayerChangeClassEvent(playerData, profess); + Bukkit.getPluginManager().callEvent(called); + if (called.isCancelled()) + return; + + playerData.setClass(profess); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("class-select", "class", profess.getName())); + player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1, 1); + player.closeInventory(); + } + } + + @Override + public String calculateName() { + return getName(); + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/SubclassSelect.java b/src/net/Indyuce/mmocore/gui/SubclassSelect.java new file mode 100644 index 00000000..87ed9f7f --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/SubclassSelect.java @@ -0,0 +1,129 @@ +package net.Indyuce.mmocore.gui; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigMessage; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.api.player.profess.PlayerClass.Subclass; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.manager.InventoryManager; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class SubclassSelect extends EditableInventory { + public SubclassSelect() { + super("subclass-select"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + return function.equals("class") ? new ClassItem(config) : new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data) { + return new SubclassSelectionInventory(data, this); + } + + public class ClassItem extends InventoryItem { + private final String name; + private final List lore; + + public ClassItem(ConfigurationSection config) { + super(config); + + this.name = config.getString("name"); + this.lore = config.getStringList("lore"); + } + + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + SubclassSelectionInventory generated = (SubclassSelectionInventory) inv; + + if (n >= generated.subclasses.size()) + return null; + + PlayerClass profess = generated.subclasses.get(n).getProfess(); + ItemStack item = profess.getIcon(); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name).replace("{name}", profess.getName())); + List lore = new ArrayList<>(this.lore); + + int index = lore.indexOf("{lore}"); + if (index >= 0) { + lore.remove(index); + for (int j = 0; j < profess.getDescription().size(); j++) + lore.add(index + j, profess.getDescription().get(j)); + } + + index = lore.indexOf("{attribute-lore}"); + if (index >= 0) { + lore.remove(index); + for (int j = 0; j < profess.getAttributeDescription().size(); j++) + lore.add(index + j, profess.getAttributeDescription().get(j)); + } + + meta.setLore(lore); + item.setItemMeta(meta); + return NBTItem.get(item).add(new ItemTag("classId", profess.getId())).toItem(); + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + } + + public class SubclassSelectionInventory extends GeneratedInventory { + private final List subclasses; + + public SubclassSelectionInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + + subclasses = playerData.getProfess().getSubclasses().stream().filter(sub -> playerData.getLevel() >= sub.getLevel()).collect(Collectors.toList()); + } + + @Override + public String calculateName() { + return getName(); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (item.getFunction().equals("back")) + InventoryManager.CLASS_SELECT.newInventory(playerData).open(); + + if (item.getFunction().equals("class")) { + String tag = NBTItem.get(event.getCurrentItem()).getString("classId"); + if (tag.equals("")) + return; + + if (playerData.getClassPoints() < 1) { + player.closeInventory(); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + new ConfigMessage("cant-choose-new-class").send(player); + return; + } + + InventoryManager.CLASS_CONFIRM.newInventory(playerData, MMOCore.plugin.classManager.get(tag), this).open(); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/WaypointViewer.java b/src/net/Indyuce/mmocore/gui/WaypointViewer.java new file mode 100644 index 00000000..07b6be41 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/WaypointViewer.java @@ -0,0 +1,213 @@ +package net.Indyuce.mmocore.gui; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.Waypoint; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class WaypointViewer extends EditableInventory { + public WaypointViewer() { + super("waypoints"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + + if (function.equals("waypoint")) + return new WaypointItem(config); + + if (function.equals("previous")) + return new NoPlaceholderItem(config) { + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return ((WaypointViewerInventory) inv).page > 0; + } + }; + + if (function.equals("next")) + return new NoPlaceholderItem(config) { + + @Override + public boolean canDisplay(GeneratedInventory inv) { + WaypointViewerInventory generated = (WaypointViewerInventory) inv; + return inv.getEditable().getByFunction("waypoint").getSlots().size() * (generated.page + 1) < generated.waypoints.size(); + } + }; + + return new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data) { + return newInventory(data, null); + } + + public GeneratedInventory newInventory(PlayerData data, Waypoint waypoint) { + return new WaypointViewerInventory(data, this, waypoint); + } + + public class WaypointDisplayItem extends InventoryPlaceholderItem { + private final Material notReady; + + public WaypointDisplayItem(ConfigurationSection config) { + super(config); + + Validate.isTrue(config.contains("not-ready"), "Could not read 'not-ready' material"); + notReady = Material.valueOf(config.getString("not-ready")); + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + WaypointViewerInventory generated = (WaypointViewerInventory) inv; + ItemStack disp = super.display(inv, n); + + Waypoint waypoint = generated.waypoints.get(generated.page * 27 + n); + if (inv.getPlayerData().getStellium() < waypoint.getStelliumCost() || (generated.current == null && !waypoint.isDynamic())) + disp.setType(notReady); + + return NBTItem.get(disp).add(new ItemTag("waypointId", waypoint.getId())).toItem(); + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + WaypointViewerInventory generated = (WaypointViewerInventory) inv; + Placeholders holders = new Placeholders(); + + Waypoint waypoint = generated.waypoints.get(generated.page * 27 + n); + holders.register("name", waypoint.getName()); + holders.register("stellium", "" + MMOCore.digit.format(waypoint.getStelliumCost())); + + return holders; + } + } + + public class WaypointItem extends InventoryItem { + private final InventoryPlaceholderItem noWaypoint, locked; + private final WaypointDisplayItem availWaypoint; + + public WaypointItem(ConfigurationSection config) { + super(config); + + Validate.notNull(config.getConfigurationSection("no-waypoint"), "Could not load 'no-waypoint' config"); + Validate.notNull(config.getConfigurationSection("locked"), "Could not load 'locked' config"); + Validate.notNull(config.getConfigurationSection("display"), "Could not load 'display' config"); + + noWaypoint = new NoPlaceholderItem(config.getConfigurationSection("no-waypoint")); + locked = new NoPlaceholderItem(config.getConfigurationSection("locked")); + availWaypoint = new WaypointDisplayItem(config.getConfigurationSection("display")); + } + + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + WaypointViewerInventory generated = (WaypointViewerInventory) inv; + + int index = generated.page * 27 + n; + if (index >= generated.waypoints.size()) + return noWaypoint.display(inv, n); + + Waypoint waypoint = generated.waypoints.get(generated.page * 27 + n); + return inv.getPlayerData().hasWaypoint(waypoint) ? availWaypoint.display(inv, n) : locked.display(inv); + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + } + + public class WaypointViewerInventory extends GeneratedInventory { + private final List waypoints = new ArrayList<>(MMOCore.plugin.waypointManager.getAll()); + private final Waypoint current; + + private int page; + + public WaypointViewerInventory(PlayerData playerData, EditableInventory editable, Waypoint current) { + super(playerData, editable); + + this.current = current; + } + + @Override + public String calculateName() { + return getName(); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (item.getFunction().equals("next")) { + page++; + open(); + return; + } + + if (item.getFunction().equals("previous")) { + page--; + open(); + return; + } + + if (item.getFunction().equals("waypoint")) { + String tag = NBTItem.get(event.getCurrentItem()).getString("waypointId"); + if (tag.equals("")) + return; + + Waypoint waypoint = MMOCore.plugin.waypointManager.get(tag); + if (!playerData.hasWaypoint(waypoint)) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-unlocked-waypoint")); + return; + } + + if (waypoint.equals(current)) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("standing-on-waypoint")); + return; + } + + if (current == null && !waypoint.isDynamic()) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-dynamic-waypoint")); + return; + } + + double left = waypoint.getStelliumCost() - playerData.getStellium(); + if (left > 0) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-enough-stellium", "more", MMOCore.digit.format(left))); + return; + } + + double next = (double) playerData.getNextWaypointMillis() / 1000; + if (next < 0) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-enough-stellium", "cooldown", MMOCore.digit.format(next))); + return; + } + + player.closeInventory(); + playerData.warp(waypoint); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/api/EditableInventory.java b/src/net/Indyuce/mmocore/gui/api/EditableInventory.java new file mode 100644 index 00000000..605217d2 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/api/EditableInventory.java @@ -0,0 +1,87 @@ +package net.Indyuce.mmocore.gui.api; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.TriggerItem; + +public abstract class EditableInventory { + private final String id; + + private String name; + private int slots; + + /* + * this set is linked so it keeps the order/priority in which the items are + * loaded from the config. + */ + private final Set items = new LinkedHashSet<>(); + + public EditableInventory(String id) { + this.id = id; + Validate.notNull(id, "ID must not be null"); + } + + public void reload(FileConfiguration config) { + + this.name = config.getString("name"); + Validate.notNull(name, "Name must not be null"); + + this.slots = Math.min(Math.max(9, config.getInt("slots")), 54); + Validate.isTrue((slots % 9) == 0, "Slots must be a multiple of 9"); + + items.clear(); + if (config.contains("items")) { + Validate.notNull(config.getConfigurationSection("items"), "Could not load item list"); + for (String key : config.getConfigurationSection("items").getKeys(false)) + try { + ConfigurationSection section = config.getConfigurationSection("items." + key); + Validate.notNull(section, "Could not load config"); + items.add(loadInventoryItem(section)); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[Inventories] Could not load item '" + key + "' from inventory '" + getId() + "': " + exception.getMessage()); + } + } + } + + public String getId() { + return id; + } + + public Set getItems() { + return items; + } + + public String getName() { + return name; + } + + public int getSlots() { + return slots; + } + + public InventoryItem getByFunction(String function) { + for (InventoryItem item : items) + if (item.getFunction().equals(function)) + return item; + return null; + } + + public abstract InventoryItem load(String function, ConfigurationSection config); + + private InventoryItem loadInventoryItem(ConfigurationSection config) { + String function = config.contains("function") ? config.getString("function").toLowerCase() : ""; + + if (function.startsWith("trigger:")) + return new TriggerItem(config, function.substring("trigger:".length())); + + return load(function, config); + } +} diff --git a/src/net/Indyuce/mmocore/gui/api/GeneratedInventory.java b/src/net/Indyuce/mmocore/gui/api/GeneratedInventory.java new file mode 100644 index 00000000..b198a1b4 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/api/GeneratedInventory.java @@ -0,0 +1,94 @@ +package net.Indyuce.mmocore.gui.api; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.TriggerItem; + +public abstract class GeneratedInventory extends PluginInventory { + private final EditableInventory editable; + private final List loaded = new ArrayList<>(); + + public GeneratedInventory(PlayerData playerData, EditableInventory editable) { + super(playerData); + + this.editable = editable; + } + + public List getLoaded() { + return loaded; + } + + public EditableInventory getEditable() { + return editable; + } + + public InventoryItem getByFunction(String function) { + for (InventoryItem item : loaded) + if (item.getFunction().equals(function)) + return item; + return null; + } + + public InventoryItem getBySlot(int slot) { + for (InventoryItem item : loaded) + if (item.getSlots().contains(slot)) + return item; + return null; + } + + /* + * this method must use an ordered collection because of GUI items + * overriding possibilities. + */ + public void addLoaded(InventoryItem item) { + loaded.add(0, item); + } + + @Override + public Inventory getInventory() { + Inventory inv = Bukkit.createInventory(this, editable.getSlots(), calculateName()); + + for (InventoryItem item : editable.getItems()) + if (item.canDisplay(this)) + item.setDisplayed(inv, this); + + return inv; + } + + public void open() { + + /* + * very important, in order to prevent ghost items, the loaded items map + * must be cleared when the inventory is updated or open at least twice + */ + loaded.clear(); + + getPlayer().openInventory(getInventory()); + } + + public void whenClicked(InventoryClickEvent event) { + event.setCancelled(true); + + if (event.getClickedInventory() != null && event.getClickedInventory().equals(event.getInventory())) { + InventoryItem item = getBySlot(event.getSlot()); + if (item == null) + return; + + if (item instanceof TriggerItem) + ((TriggerItem) item).getTrigger().apply(getPlayerData()); + else + whenClicked(event, item); + } + } + + public abstract String calculateName(); + + public abstract void whenClicked(InventoryClickEvent event, InventoryItem item); +} diff --git a/src/net/Indyuce/mmocore/gui/api/PluginInventory.java b/src/net/Indyuce/mmocore/gui/api/PluginInventory.java new file mode 100644 index 00000000..f9dde71e --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/api/PluginInventory.java @@ -0,0 +1,43 @@ +package net.Indyuce.mmocore.gui.api; + +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import net.Indyuce.mmocore.api.player.PlayerData; + +public abstract class PluginInventory implements InventoryHolder { + protected final Player player; + protected final PlayerData playerData; + + public PluginInventory(PlayerData playerData) { + this.playerData = playerData; + player = playerData.getPlayer(); + } + + public PluginInventory(Player player) { + this.player = player; + this.playerData = player.getOpenInventory() != null && player.getOpenInventory().getTopInventory().getHolder() instanceof PluginInventory ? ((PluginInventory) player.getOpenInventory().getTopInventory().getHolder()).playerData : PlayerData.get(player); + } + + public PlayerData getPlayerData() { + return playerData; + } + + public Player getPlayer() { + return player; + } + + public void open() { + getPlayer().openInventory(getInventory()); + } + + public abstract Inventory getInventory(); + + public abstract void whenClicked(InventoryClickEvent event); + + public void whenClosed(InventoryCloseEvent event) { + } +} diff --git a/src/net/Indyuce/mmocore/gui/api/item/InventoryItem.java b/src/net/Indyuce/mmocore/gui/api/item/InventoryItem.java new file mode 100644 index 00000000..8613d55a --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/api/item/InventoryItem.java @@ -0,0 +1,69 @@ +package net.Indyuce.mmocore.gui.api.item; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.gui.api.GeneratedInventory; + +public abstract class InventoryItem { + private final String id, function; + private final List slots = new ArrayList<>(); + + public InventoryItem(ConfigurationSection config) { + this(config.getName(), config.getString("function")); + + config.getStringList("slots").forEach(str -> slots.add(Integer.parseInt(str))); + } + + public InventoryItem(String id, String function) { + this.id = id; + this.function = function == null ? "" : function.toLowerCase(); + } + + public String getId() { + return id; + } + + public String getFunction() { + return function; + } + + public boolean hasFunction() { + return !function.isEmpty(); + } + + public List getSlots() { + return slots; + } + + public boolean hasDifferentDisplay() { + return false; + } + + public void setDisplayed(Inventory inv, GeneratedInventory generated) { + generated.addLoaded(this); + + if (!hasDifferentDisplay()) { + ItemStack display = display(generated); + for (int slot : getSlots()) + inv.setItem(slot, display); + } + + else + for (int j = 0; j < slots.size(); j++) + inv.setItem(slots.get(j), display(generated, j)); + + } + + public ItemStack display(GeneratedInventory inv) { + return display(inv, 0); + } + + public abstract ItemStack display(GeneratedInventory inv, int n); + + public abstract boolean canDisplay(GeneratedInventory inv); +} diff --git a/src/net/Indyuce/mmocore/gui/api/item/InventoryPlaceholderItem.java b/src/net/Indyuce/mmocore/gui/api/item/InventoryPlaceholderItem.java new file mode 100644 index 00000000..c56ee144 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/api/item/InventoryPlaceholderItem.java @@ -0,0 +1,122 @@ +package net.Indyuce.mmocore.gui.api.item; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; + +public abstract class InventoryPlaceholderItem extends InventoryItem { + private final Material material; + private final String name, texture; + private final List lore; + private final boolean placeholders, hideFlags; + + public InventoryPlaceholderItem(ConfigurationSection config) { + this(Material.valueOf(config.getString("item")), config); + } + + public InventoryPlaceholderItem(Material material, ConfigurationSection config) { + super(config); + + this.material = material; + this.name = config.getString("name"); + this.lore = config.getStringList("lore"); + this.hideFlags = config.getBoolean("hide-flags"); + this.texture = config.getString("texture"); + this.placeholders = config.getBoolean("placeholders"); + } + + public Material getMaterial() { + return material; + } + + public boolean hideFlags() { + return hideFlags; + } + + public boolean hasName() { + return name != null; + } + + public String getName() { + return name; + } + + public boolean hasLore() { + return lore != null && !lore.isEmpty(); + } + + public List getLore() { + return lore; + } + + public boolean supportPlaceholders() { + return placeholders; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + + public Placeholders getPlaceholders(PluginInventory inv) { + return getPlaceholders(inv, 0); + } + + public abstract Placeholders getPlaceholders(PluginInventory inv, int n); + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + + Placeholders placeholders = getPlaceholders(inv, n); + ItemStack item = new ItemStack(getMaterial()); + ItemMeta meta = item.getItemMeta(); + + if (texture != null && meta instanceof SkullMeta) + applyTexture(texture, (SkullMeta) meta); + + if (hasName()) + meta.setDisplayName(placeholders.apply(inv.getPlayer(), new String(getName()))); + + if (hideFlags()) + meta.addItemFlags(ItemFlag.values()); + + if (hasLore()) { + List lore = new ArrayList<>(); + getLore().forEach(line -> lore.add(ChatColor.GRAY + placeholders.apply(inv.getPlayer(), line))); + meta.setLore(lore); + } + + item.setItemMeta(meta); + return item; + } + + private void applyTexture(String value, SkullMeta meta) { + try { + GameProfile profile = new GameProfile(UUID.randomUUID(), null); + profile.getProperties().put("textures", new Property("textures", value)); + + Field profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profileField.set(meta, profile); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { + MMOCore.log(Level.WARNING, "Could not apply item texture value of " + getId()); + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/api/item/NoPlaceholderItem.java b/src/net/Indyuce/mmocore/gui/api/item/NoPlaceholderItem.java new file mode 100644 index 00000000..4e31fe95 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/api/item/NoPlaceholderItem.java @@ -0,0 +1,21 @@ +package net.Indyuce.mmocore.gui.api.item; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.gui.api.PluginInventory; + +public class NoPlaceholderItem extends InventoryPlaceholderItem { + public NoPlaceholderItem(ConfigurationSection config) { + super(config); + } + + public NoPlaceholderItem(Material material, ConfigurationSection config) { + super(material, config); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + return new Placeholders(); + } +} diff --git a/src/net/Indyuce/mmocore/gui/api/item/Placeholders.java b/src/net/Indyuce/mmocore/gui/api/item/Placeholders.java new file mode 100644 index 00000000..f11e0890 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/api/item/Placeholders.java @@ -0,0 +1,35 @@ +package net.Indyuce.mmocore.gui.api.item; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; + +public class Placeholders { + private final Map placeholders = new HashMap<>(); + + public void register(String path, Object obj) { + placeholders.put(path, obj.toString()); + } + + public String apply(Player player, String str) { + + /* + * remove potential conditions, apply color codes and external + * placeholders if needed. + */ + str = MMOCore.plugin.placeholderParser.parse(player, removeCondition(str)); + + while (str.contains("{") && str.substring(str.indexOf("{")).contains("}")) { + String holder = str.substring(str.indexOf("{") + 1, str.indexOf("}")); + str = str.replace("{" + holder + "}", placeholders.containsKey(holder) ? placeholders.get(holder) : "PHE"); + } + return str; + } + + private String removeCondition(String str) { + return str.startsWith("{") && str.contains("}") ? str.substring(str.indexOf("}") + 1) : str; + } +} diff --git a/src/net/Indyuce/mmocore/gui/api/item/TriggerItem.java b/src/net/Indyuce/mmocore/gui/api/item/TriggerItem.java new file mode 100644 index 00000000..30d03ae3 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/api/item/TriggerItem.java @@ -0,0 +1,27 @@ +package net.Indyuce.mmocore.gui.api.item; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; +import net.Indyuce.mmocore.gui.api.PluginInventory; + +public class TriggerItem extends InventoryPlaceholderItem { + private final Trigger trigger; + + public TriggerItem(ConfigurationSection config, String format) { + super(config); + + trigger = MMOCore.plugin.loadManager.loadTrigger(new MMOLineConfig(format)); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + return new Placeholders(); + } + + public Trigger getTrigger() { + return trigger; + } +} diff --git a/src/net/Indyuce/mmocore/gui/eco/DepositMenu.java b/src/net/Indyuce/mmocore/gui/eco/DepositMenu.java new file mode 100644 index 00000000..01ddf376 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/eco/DepositMenu.java @@ -0,0 +1,117 @@ +package net.Indyuce.mmocore.gui.eco; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.item.ConfigItem; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.item.SmartGive; +import net.Indyuce.mmocore.gui.api.PluginInventory; + +public class DepositMenu extends PluginInventory { + private ItemStack depositItem; + private int deposit; + + public DepositMenu(Player player) { + super(player); + } + + @Override + public Inventory getInventory() { + Inventory inv = Bukkit.createInventory(this, 27, "Deposit"); + + inv.setItem(26, depositItem = new ConfigItem("DEPOSIT_ITEM").addPlaceholders("worth", "0").build()); + + new BukkitRunnable() { + + @Override + public void run() { + if (inv.getViewers().size() < 1) { + cancel(); + return; + } + + updateDeposit(inv); + } + }.runTaskTimer(MMOCore.plugin, 0, 20); + return inv; + } + + @Override + public void whenClicked(InventoryClickEvent event) { + // event.setCancelled(true); + if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) + return; + + if (event.getCurrentItem().isSimilar(depositItem)) { + event.setCancelled(true); + + updateDeposit(event.getInventory()); + if (deposit <= 0) + return; + + MMOCore.plugin.economy.getEconomy().depositPlayer(player, deposit); + event.getInventory().clear(); + player.closeInventory(); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 2); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("deposit", "worth", "" + deposit)); + return; + } + + int worth = NBTItem.get(event.getCurrentItem()).getInt("RpgWorth"); + if (worth < 1) { + event.setCancelled(true); + return; + } + + // in deposit menu + // if (event.getRawSlot() < 27) { + // int empty = player.getInventory().firstEmpty(); + // if (empty < 0) + // return; + // + // player.playSound(player.getLocation(), Sound.ENTITY_SHULKER_TELEPORT, + // 1, 2); + // player.getInventory().addItem(event.getCurrentItem()); + // event.setCurrentItem(null); + // updateDeposit(event.getInventory()); + // return; + // } + + // in player inventory + // int empty = event.getInventory().firstEmpty(); + // if (empty < 0) + // return; + // + // player.playSound(player.getLocation(), + // Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 2); + // event.getInventory().addItem(event.getCurrentItem()); + // event.setCurrentItem(null); + // updateDeposit(event.getInventory()); + // return; + } + + @Override + public void whenClosed(InventoryCloseEvent event) { + SmartGive smart = new SmartGive(player); + for (int j = 0; j < 26; j++) { + ItemStack item = event.getInventory().getItem(j); + if (item != null) + smart.give(item); + } + } + + private void updateDeposit(Inventory inv) { + deposit = MMOCoreUtils.getWorth(inv.getContents()); + inv.setItem(26, depositItem = new ConfigItem("DEPOSIT_ITEM").addPlaceholders("worth", "" + deposit).build()); + } +} diff --git a/src/net/Indyuce/mmocore/gui/eco/GoldPouch.java b/src/net/Indyuce/mmocore/gui/eco/GoldPouch.java new file mode 100644 index 00000000..4f7ce6d6 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/eco/GoldPouch.java @@ -0,0 +1,88 @@ +package net.Indyuce.mmocore.gui.eco; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class GoldPouch extends PluginInventory { + private final boolean mob; + private final NBTItem nbt; + + public GoldPouch(Player player, NBTItem nbt) { + super(player); + this.nbt = nbt; + this.mob = nbt.getBoolean("RpgPouchMob"); + } + + @Override + public Inventory getInventory() { + Inventory inv = (Inventory) Bukkit.createInventory(this, 18, ChatColor.UNDERLINE + "Gold Pouch"); + inv.setContents(MMOCoreUtils.itemStackArrayFromBase64(nbt.getString("RpgPouchInventory"))); + return inv; + } + + /* + * if the player has opened a backpack, he cannot click a backpack. bug fix + * - the player can move the backpack and lose the inventory he had opened + */ + @Override + public void whenClicked(InventoryClickEvent event) { + + ItemStack item = event.getCurrentItem(); + NBTItem nbt = NBTItem.get(item); + if (!nbt.has("RpgWorth")) { + event.setCancelled(true); + return; + } + + if (mob) { + event.setCancelled(true); + + // in deposit menu + if (event.getRawSlot() < 18) { + int empty = player.getInventory().firstEmpty(); + if (empty < 0) + return; + + player.playSound(player.getLocation(), Sound.ENTITY_SHULKER_TELEPORT, 1, 2); + player.getInventory().addItem(event.getCurrentItem()); + event.setCurrentItem(null); + } + + return; + } + + if (nbt.has("RpgPouchInventory")) + event.setCancelled(true); + } + + @Override + public void whenClosed(InventoryCloseEvent event) { + Player player = (Player) event.getPlayer(); + if (mob && isEmpty(event.getInventory())) { + player.getEquipment().setItemInMainHand(null); + return; + } + + ItemStack updated = NBTItem.get(player.getEquipment().getItemInMainHand()).add(new ItemTag("RpgPouchInventory", MMOCoreUtils.toBase64(event.getInventory().getContents()))).toItem(); + player.getEquipment().setItemInMainHand(updated); + } + + private boolean isEmpty(Inventory inv) { + for (ItemStack item : inv.getContents()) + if (item != null && item.getType() != Material.AIR) + return false; + return true; + } +} diff --git a/src/net/Indyuce/mmocore/gui/social/friend/EditableFriendList.java b/src/net/Indyuce/mmocore/gui/social/friend/EditableFriendList.java new file mode 100644 index 00000000..3b6bad1d --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/social/friend/EditableFriendList.java @@ -0,0 +1,297 @@ +package net.Indyuce.mmocore.gui.social.friend; + +import java.util.UUID; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.input.PlayerInput.InputType; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.math.format.DelayFormat; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; +import net.Indyuce.mmocore.manager.InventoryManager; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class EditableFriendList extends EditableInventory { + public EditableFriendList() { + super("friend-list"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + + if (function.equals("friend")) + return new FriendItem(config); + + if (function.equals("previous")) + return new NoPlaceholderItem(config) { + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return ((FriendListInventory) inv).page > 0; + } + }; + + if (function.equals("next")) + return new NoPlaceholderItem(config) { + + @Override + public boolean canDisplay(GeneratedInventory inv) { + FriendListInventory generated = (FriendListInventory) inv; + return inv.getEditable().getByFunction("friend").getSlots().size() * generated.page < inv.getPlayerData().getFriends().size(); + } + }; + + return new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data) { + return new FriendListInventory(data, this); + } + + public class OfflineFriendItem extends InventoryPlaceholderItem { + public OfflineFriendItem(ConfigurationSection config) { + super(config); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + OfflinePlayer friend = Bukkit.getOfflinePlayer(inv.getPlayerData().getFriends().get(n)); + + Placeholders holders = new Placeholders(); + holders.register("name", friend.getName()); + holders.register("last_seen", new DelayFormat(2).format(System.currentTimeMillis() - friend.getLastPlayed())); + return holders; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + OfflinePlayer friend = Bukkit.getOfflinePlayer(inv.getPlayerData().getFriends().get(n)); + + ItemStack disp = super.display(inv, n); + ItemMeta meta = disp.getItemMeta(); + + /* + * run async to save performance + */ + if (meta instanceof SkullMeta) { + ((SkullMeta) meta).setOwningPlayer(friend); + disp.setItemMeta(meta); + } + + return NBTItem.get(disp).add(new ItemTag("uuid", friend.getUniqueId().toString())).toItem(); + } + } + + public class OnlineFriendItem extends InventoryPlaceholderItem { + public OnlineFriendItem(ConfigurationSection config) { + super(config); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Player friend = Bukkit.getPlayer(inv.getPlayerData().getFriends().get(n)); + PlayerData data = PlayerData.get(friend); + + Placeholders holders = new Placeholders(); + holders.register("name", data.getPlayer().getName()); + holders.register("class", data.getProfess().getName()); + holders.register("level", data.getLevel()); + holders.register("online_since", new DelayFormat(2).format(System.currentTimeMillis() - data.getLastLogin())); + return holders; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + Player friend = Bukkit.getPlayer(inv.getPlayerData().getFriends().get(n)); + + ItemStack disp = super.display(inv, n); + ItemMeta meta = disp.getItemMeta(); + + /* + * run async to save performance + */ + if (meta instanceof SkullMeta) { + ((SkullMeta) meta).setOwningPlayer(friend); + disp.setItemMeta(meta); + } + + return NBTItem.get(disp).add(new ItemTag("uuid", friend.getUniqueId().toString())).toItem(); + } + } + + public class FriendItem extends NoPlaceholderItem { + private final OnlineFriendItem online; + private final OfflineFriendItem offline; + + public FriendItem(ConfigurationSection config) { + super(config); + + Validate.notNull(config.contains("online"), "Could not load online config"); + Validate.notNull(config.contains("offline"), "Could not load offline config"); + + online = new OnlineFriendItem(config.getConfigurationSection("online")); + offline = new OfflineFriendItem(config.getConfigurationSection("offline")); + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + return inv.getPlayerData().getFriends().size() <= n ? super.display(inv, n) : Bukkit.getOfflinePlayer(inv.getPlayerData().getFriends().get(n)).isOnline() ? online.display(inv, n) : offline.display(inv, n); + } + + @Override + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + } + + public class FriendListInventory extends GeneratedInventory { + private int page; + + public FriendListInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + } + + @Override + public String calculateName() { + return getName(); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (item.getFunction().equals("previous")) { + page--; + open(); + return; + } + + if (item.getFunction().equals("next")) { + page++; + open(); + return; + } + + if (item.getFunction().equals("request")) { + + long remaining = playerData.getLastFriendRequest() + 60 * 2 * 1000 - System.currentTimeMillis(); + if (remaining > 0) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("friend-request-cooldown", "cooldown", new DelayFormat().format(remaining))); + return; + } + + MMOCore.plugin.configManager.newPlayerInput(player, InputType.FRIEND_REQUEST, (input) -> { + Player target = Bukkit.getPlayer(input); + if (target == null) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-online-player", "player", input)); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + open(); + return; + } + + if (playerData.hasFriend(target.getUniqueId())) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("already-friends", "player", target.getName())); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + open(); + return; + } + + playerData.sendFriendRequest(PlayerData.get(target)); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("sent-friend-request", "player", target.getName())); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1); + open(); + }); + } + + if (item.getFunction().equals("friend") && event.getAction() == InventoryAction.PICKUP_HALF) + InventoryManager.FRIEND_REMOVAL.newInventory(playerData, Bukkit.getOfflinePlayer(UUID.fromString(NBTItem.get(event.getCurrentItem()).getString("uuid"))), this).open(); + } + } + + // private int page; + // private ItemStack next, prev, newreq; + // + // private int[] slots = { 10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, + // 24, 25, 28, 29, 30, 31, 32, 33, 34 }; + // + // public EditableFriendList(Player player) { + // this(player, 1); + // } + // + // public EditableFriendList(Player player, int page) { + // super(player); + // this.page = page; + // } + // + // @Override + // public Inventory getInventory() { + // Inventory inv = Bukkit.createInventory(this, 54, "Friends"); + // + // int n = 0; + // for (int j = 21 * (page - 1); j < Math.min(21 * page, + // playerData.getFriends().size()); j++) { + // + // /* + // * if the player is not found, delete it from the friend list. + // */ + // UUID uuid = playerData.getFriends().get(j); + // OfflinePlayer offline = Bukkit.getOfflinePlayer(uuid); + // if (offline == null || offline.getName() == null) { + // playerData.removeFriend(uuid); + // continue; + // } + // + // PlayerData data; + // ItemStack item = offline.isOnline() ? new + // ConfigItem("ONLINE_FRIEND").addPlaceholders("name", offline.getName(), + // "online_since", "" + new DelayFormat(2).format(System.currentTimeMillis() + // - (data = PlayerData.get(offline)).getLastLogin()), "class", + // data.getProfess().getName(), "level", "" + data.getLevel()).build() : new + // ConfigItem("OFFLINE_FRIEND").addPlaceholders("name", offline.getName(), + // "last_seen", new DelayFormat(2).format(System.currentTimeMillis() - + // offline.getLastPlayed())).build(); + // + // SkullMeta meta = (SkullMeta) item.getItemMeta(); + // meta.setOwningPlayer(offline); + // item.setItemMeta(meta); + // + // inv.setItem(slots[n++], NBTItem.get(item).add(new ItemTag("uuid", + // offline.getUniqueId().toString())).toItem()); + // } + // + // if (page > 1) + // inv.setItem(18, prev = new ConfigItem("PREVIOUS_PAGE").build()); + // + // if (playerData.getFriends().size() > 21 * page) + // inv.setItem(26, next = new ConfigItem("NEXT_PAGE").build()); + // + // ItemStack fill = new ConfigItem("NO_FRIEND").build(); + // while (n < 21 * page) + // inv.setItem(slots[n++], fill); + // + // inv.setItem(49, newreq = new ConfigItem("NEW_FRIEND_REQUEST").build()); + // + // return inv; + // } +} diff --git a/src/net/Indyuce/mmocore/gui/social/friend/EditableFriendRemoval.java b/src/net/Indyuce/mmocore/gui/social/friend/EditableFriendRemoval.java new file mode 100644 index 00000000..6fe89179 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/social/friend/EditableFriendRemoval.java @@ -0,0 +1,71 @@ +package net.Indyuce.mmocore.gui.social.friend; + +import org.bukkit.OfflinePlayer; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.OfflinePlayerData; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; + +public class EditableFriendRemoval extends EditableInventory { + public EditableFriendRemoval() { + super("friend-removal"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + + return new InventoryPlaceholderItem(config) { + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + Placeholders holders = new Placeholders(); + holders.register("name", ((ClassConfirmationInventory) inv).friend.getName()); + return holders; + } + }; + } + + public GeneratedInventory newInventory(PlayerData data, OfflinePlayer friend, GeneratedInventory last) { + return new ClassConfirmationInventory(data, this, friend, last); + } + + public class ClassConfirmationInventory extends GeneratedInventory { + private final OfflinePlayer friend; + private final GeneratedInventory last; + + public ClassConfirmationInventory(PlayerData playerData, EditableInventory editable, OfflinePlayer friend, GeneratedInventory last) { + super(playerData, editable); + + this.friend = friend; + this.last = last; + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (item.getFunction().equals("yes")) { + playerData.removeFriend(friend.getUniqueId()); + new OfflinePlayerData(friend.getUniqueId()).removeFriend(playerData.getUniqueId()); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("no-longer-friends", "unfriend", friend.getName())); + last.open(); + } + + if (item.getFunction().equals("back")) + last.open(); + } + + @Override + public String calculateName() { + return getName(); + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/social/party/EditablePartyCreation.java b/src/net/Indyuce/mmocore/gui/social/party/EditablePartyCreation.java new file mode 100644 index 00000000..67506e74 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/social/party/EditablePartyCreation.java @@ -0,0 +1,54 @@ +package net.Indyuce.mmocore.gui.social.party; + +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.inventory.InventoryClickEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class EditablePartyCreation extends EditableInventory { + public EditablePartyCreation() { + super("party-creation"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + return new NoPlaceholderItem(config); + } + + public GeneratedInventory newInventory(PlayerData data) { + return new ClassConfirmationInventory(data, this); + } + + public class ClassConfirmationInventory extends GeneratedInventory { + public ClassConfirmationInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + if (event.getInventory() != event.getClickedInventory()) + return; + + if (item.getFunction().equals("create")) { + MMOCore.plugin.partyManager.newRegisteredParty(playerData); + InventoryManager.PARTY_VIEW.newInventory(playerData).open(); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1); + } + + if (item.getFunction().equals("back")) + player.closeInventory(); + } + + @Override + public String calculateName() { + return getName(); + } + } +} diff --git a/src/net/Indyuce/mmocore/gui/social/party/EditablePartyView.java b/src/net/Indyuce/mmocore/gui/social/party/EditablePartyView.java new file mode 100644 index 00000000..928f2b18 --- /dev/null +++ b/src/net/Indyuce/mmocore/gui/social/party/EditablePartyView.java @@ -0,0 +1,188 @@ +package net.Indyuce.mmocore.gui.social.party; + +import java.util.UUID; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.input.PlayerInput.InputType; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.math.format.DelayFormat; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.api.GeneratedInventory; +import net.Indyuce.mmocore.gui.api.PluginInventory; +import net.Indyuce.mmocore.gui.api.item.InventoryItem; +import net.Indyuce.mmocore.gui.api.item.InventoryPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.NoPlaceholderItem; +import net.Indyuce.mmocore.gui.api.item.Placeholders; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class EditablePartyView extends EditableInventory { + public EditablePartyView() { + super("party-view"); + } + + @Override + public InventoryItem load(String function, ConfigurationSection config) { + return function.equals("member") ? new MemberItem(config) : new NoPlaceholderItem(config); + } + + public class MemberDisplayItem extends InventoryPlaceholderItem { + public MemberDisplayItem(ConfigurationSection config) { + super(config); + } + + @Override + public Placeholders getPlaceholders(PluginInventory inv, int n) { + PlayerData member = inv.getPlayerData().getParty().getMembers().get(n); + + Placeholders holders = new Placeholders(); + holders.register("name", member.getPlayer().getName()); + holders.register("class", member.getProfess().getName()); + holders.register("level", "" + member.getLevel()); + holders.register("since", new DelayFormat(2).format(System.currentTimeMillis() - member.getLastLogin())); + return holders; + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + PlayerData member = inv.getPlayerData().getParty().getMembers().get(n); + + ItemStack disp = super.display(inv, n); + ItemMeta meta = disp.getItemMeta(); + + /* + * run async to save performance + */ + if (meta instanceof SkullMeta) { + ((SkullMeta) meta).setOwningPlayer(member.getPlayer()); + disp.setItemMeta(meta); + } + + return NBTItem.get(disp).add(new ItemTag("uuid", member.getUniqueId().toString())).toItem(); + } + } + + public class MemberItem extends InventoryItem { + private final InventoryPlaceholderItem empty; + private final MemberDisplayItem member; + + public MemberItem(ConfigurationSection config) { + super(config); + + Validate.notNull(config.contains("empty"), "Could not load empty config"); + Validate.notNull(config.contains("member"), "Could not load member config"); + + empty = new NoPlaceholderItem(config.getConfigurationSection("empty")); + member = new MemberDisplayItem(config.getConfigurationSection("member")); + } + + @Override + public ItemStack display(GeneratedInventory inv, int n) { + return inv.getPlayerData().getParty().getMembers().count() > n ? member.display(inv, n) : empty.display(inv, n); + } + + @Override + public boolean hasDifferentDisplay() { + return true; + } + + @Override + public boolean canDisplay(GeneratedInventory inv) { + return true; + } + } + + public GeneratedInventory newInventory(PlayerData data) { + return new PartyViewInventory(data, this); + } + + public class PartyViewInventory extends GeneratedInventory { + private final int max; + + public PartyViewInventory(PlayerData playerData, EditableInventory editable) { + super(playerData, editable); + + max = editable.getByFunction("member").getSlots().size(); + } + + @Override + public String calculateName() { + return getName().replace("{max}", "" + max).replace("{players}", "" + getPlayerData().getParty().getMembers().count()); + } + + @Override + public void whenClicked(InventoryClickEvent event, InventoryItem item) { + + if (item.getFunction().equals("leave")) { + playerData.getParty().removeMember(playerData); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1); + player.closeInventory(); + return; + } + + if (item.getFunction().equals("invite")) { + + if (playerData.getParty().getMembers().count() >= max) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("party-is-full")); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + return; + } + + MMOCore.plugin.configManager.newPlayerInput(player, InputType.PARTY_INVITE, (input) -> { + Player target = Bukkit.getPlayer(input); + if (target == null) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("not-online-player", "player", input)); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + open(); + return; + } + + long remaining = playerData.getParty().getLastInvite(target) + 60 * 2 * 1000 - System.currentTimeMillis(); + if (remaining > 0) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("party-invite-cooldown", "player", target.getName(), "cooldown", new DelayFormat().format(remaining))); + open(); + return; + } + + PlayerData targetData = PlayerData.get(target); + if (playerData.getParty().getMembers().has(targetData)) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("already-in-party", "player", target.getName())); + player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1, 1); + open(); + return; + } + + playerData.getParty().sendPartyInvite(player, targetData); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("sent-party-invite", "player", target.getName())); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1); + open(); + }); + } + + if (item.getFunction().equals("member") && event.getAction() == InventoryAction.PICKUP_HALF) { + if (!playerData.getParty().getOwner().equals(playerData)) + return; + + OfflinePlayer target = Bukkit.getOfflinePlayer(UUID.fromString(NBTItem.get(event.getCurrentItem()).getString("uuid"))); + if (target.equals(player)) + return; + + playerData.getParty().removeMember(PlayerData.get(target)); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("kick-from-party", "player", target.getName())); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/listener/BlockListener.java b/src/net/Indyuce/mmocore/listener/BlockListener.java new file mode 100644 index 00000000..c22d2da0 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/BlockListener.java @@ -0,0 +1,114 @@ +package net.Indyuce.mmocore.listener; + +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.CustomBlockMineEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.CustomBlockManager.BlockInfo; +import net.Indyuce.mmocore.manager.RestrictionManager.BlockPermissions; + +public class BlockListener implements Listener { + private static final BlockFace[] order = { BlockFace.UP, BlockFace.DOWN, BlockFace.EAST, BlockFace.NORTH, BlockFace.WEST, BlockFace.SOUTH }; + + @EventHandler(priority = EventPriority.HIGH) + public void a(BlockBreakEvent event) { + Player player = event.getPlayer(); + if (player.getGameMode() == GameMode.CREATIVE || event.isCancelled()) + return; + + Block block = event.getBlock(); + + /* + * if custom mining enabled, check for item breaking restrictions + */ + boolean customMine = MMOCore.plugin.mineManager.isEnabled(player); + ItemStack item = player.getInventory().getItemInMainHand(); + if (customMine) { + + BlockPermissions perms = MMOCore.plugin.restrictionManager.getPermissions(item.getType()); + if (perms == null) { + event.setCancelled(true); + return; + } + + if (!perms.canMine(block.getType())) { + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("cannot-break")); + event.setCancelled(true); + return; + } + } + + BlockInfo info = MMOCore.plugin.mineManager.getInfo(block.getType()); + if (info == null) + return; + + /* + * calls the event and listen for cancel & for drops changes... also + * allows to apply tool durability & enchants to drops, etc. + */ + CustomBlockMineEvent called = new CustomBlockMineEvent(PlayerData.get(player), block, info); + Bukkit.getPluginManager().callEvent(called); + if (called.isCancelled()) { + event.setCancelled(true); + return; + } + + /* + * remove vanilla drops if needed + */ + if (!info.hasVanillaDrops()) { + event.setCancelled(true); + event.getBlock().setType(Material.AIR); + } + + /* + * apply triggers, add experience info to the event so the other events + * can give exp to other TOOLS and display HOLOGRAMS + */ + if (info.hasTriggers()) { + PlayerData playerData = PlayerData.get(player); + info.getTriggers().forEach(trigger -> trigger.apply(playerData)); + if (info.hasExperience() && MMOCore.plugin.hasHolograms()) + MMOCore.plugin.hologramSupport.displayIndicator(block.getLocation().add(.5, .5, .5), MMOCore.plugin.configManager.getSimpleMessage("exp-hologram", "exp", "" + called.getGainedExperience().getValue()), player); + } + + /* + * apply drop tables + */ + if (info.hasDropTable()) { + Location dropLocation = getSafeDropLocation(block, !info.hasDropTable()); + for (ItemStack drop : called.getDrops()) + if (drop.getType() != Material.AIR && drop.getAmount() > 0) + block.getWorld().dropItemNaturally(dropLocation, drop); + } + + /* + * enable block regen only if custom mine is enabled. + */ + if (customMine && info.hasRegen()) + MMOCore.plugin.mineManager.initialize(info.generateRegenInfo(event.getBlock().getLocation())); + } + + private Location getSafeDropLocation(Block block, boolean self) { + if (block.getType() == Material.AIR && self) + return block.getLocation(); + + Block relative; + for (BlockFace face : order) + if (!(relative = block.getRelative(face)).getType().isSolid()) + return relative.getLocation().add(block.getLocation().subtract(relative.getLocation()).multiply(.3)); + return block.getLocation(); + } +} diff --git a/src/net/Indyuce/mmocore/listener/GoldPouchesListener.java b/src/net/Indyuce/mmocore/listener/GoldPouchesListener.java new file mode 100644 index 00000000..f34c6822 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/GoldPouchesListener.java @@ -0,0 +1,53 @@ +package net.Indyuce.mmocore.listener; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.gui.eco.GoldPouch; + +public class GoldPouchesListener implements Listener { + @EventHandler + public void a(PlayerInteractEvent event) { + if (!event.getAction().name().startsWith("RIGHT") || event.getHand() != EquipmentSlot.HAND) + return; + + NBTItem nbt = NBTItem.get(event.getItem()); + if (!nbt.has("RpgPouchInventory")) + return; + + // that way ppl can't open a chest when right clicking a backpack + // when they wanted to open the backpack + event.setCancelled(true); + + // dupe bug : open 2 stacked backpacks and split them to dupe. + if (event.getItem().getAmount() > 1) + return; + + new GoldPouch(event.getPlayer(), nbt).open(); + } + + /* + * if a player has a backpack open, he cannot pick up a backpack. bug fix - + * he can pick up a backpack, and dupe items when the items are saved in a + * amount=2 backpack itemstack TODO register and unregister listener. + */ + @EventHandler + public void b(EntityPickupItemEvent event) { + if (!(event.getEntity() instanceof Player)) + return; + + Player player = (Player) event.getEntity(); + if (player.getOpenInventory() == null || !(player.getOpenInventory().getTopInventory().getHolder() instanceof GoldPouch)) + return; + + ItemStack item = event.getItem().getItemStack(); + if (NBTItem.get(item).has("RpgPouchInventory")) + event.setCancelled(true); + } +} diff --git a/src/net/Indyuce/mmocore/listener/LootableChestsListener.java b/src/net/Indyuce/mmocore/listener/LootableChestsListener.java new file mode 100644 index 00000000..147bdeb8 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/LootableChestsListener.java @@ -0,0 +1,22 @@ +package net.Indyuce.mmocore.listener; + +import org.bukkit.block.Chest; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryCloseEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.manager.LootableChestManager.LootableChest; + +public class LootableChestsListener implements Listener { + @EventHandler + public void a(InventoryCloseEvent event) { + if (!(event.getInventory().getHolder() instanceof Chest)) + return; + + Chest chest = (Chest) event.getInventory().getHolder(); + LootableChest lootable = MMOCore.plugin.chestManager.getLootableChest(chest.getLocation()); + if (lootable != null) + lootable.whenClosed(true); + } +} diff --git a/src/net/Indyuce/mmocore/listener/PartyListener.java b/src/net/Indyuce/mmocore/listener/PartyListener.java new file mode 100644 index 00000000..13deeef8 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/PartyListener.java @@ -0,0 +1,40 @@ +package net.Indyuce.mmocore.listener; + +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.social.PartyChatEvent; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class PartyListener implements Listener { + + @EventHandler(priority = EventPriority.LOW) + public void a(AsyncPlayerChatEvent event) { + if (!event.getMessage().startsWith(MMOCore.plugin.configManager.partyChatPrefix)) + return; + + PlayerData data = PlayerData.get(event.getPlayer()); + if (!data.hasParty()) + return; + + event.setCancelled(true); + + /* + * running it in a delayed task is recommended + */ + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> { + String format = MMOCore.plugin.configManager.getSimpleMessage("party-chat", "player", data.getPlayer().getName(), "message", event.getMessage().substring(MMOCore.plugin.configManager.partyChatPrefix.length())); + PartyChatEvent called = new PartyChatEvent(data, format); + Bukkit.getPluginManager().callEvent(called); + if (!called.isCancelled()) + data.getParty().getMembers().forEach(member -> { + if (member.isOnline()) + member.getPlayer().sendMessage(format); + }); + }); + } +} diff --git a/src/net/Indyuce/mmocore/listener/PlayerListener.java b/src/net/Indyuce/mmocore/listener/PlayerListener.java new file mode 100644 index 00000000..9dde416c --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/PlayerListener.java @@ -0,0 +1,107 @@ + +package net.Indyuce.mmocore.listener; + +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import com.codingforcookies.armorequip.ArmorEquipEvent; + +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.event.PlayerCombatEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.PlayerStats; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; +import net.Indyuce.mmocore.gui.api.PluginInventory; + +public class PlayerListener implements Listener { + + /* + * initialize player data + */ + @EventHandler(priority = EventPriority.LOWEST) + public void a(PlayerJoinEvent event) { + Player player = event.getPlayer(); + PlayerData.setup(player).setPlayer(player); + } + + /* + * custom inventories register + */ + @EventHandler + public void b(InventoryClickEvent event) { + if (event.getInventory().getHolder() instanceof PluginInventory) + ((PluginInventory) event.getInventory().getHolder()).whenClicked(event); + } + + @EventHandler + public void c(InventoryCloseEvent event) { + if (event.getInventory().getHolder() instanceof PluginInventory) + ((PluginInventory) event.getInventory().getHolder()).whenClosed(event); + } + + /* + * updates the player's combat log data every time he hits an entity, or + * gets hit by an entity or a projectile sent by another entity. updates + * this stuff on HIGH level so other plugins can check if the player just + * entered combat + */ + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void d(EntityDamageByEntityEvent event) { + if (event.getEntity() instanceof Player) + PlayerData.get((Player) event.getEntity()).updateCombat(); + + if (event.getDamager() instanceof Player) + PlayerData.get((Player) event.getDamager()).updateCombat(); + + if (event.getDamager() instanceof Projectile && ((Projectile) event.getDamager()).getShooter() instanceof Player) + PlayerData.get((Player) ((Projectile) event.getDamager()).getShooter()).updateCombat(); + } + + @EventHandler + public void e(PlayerQuitEvent event) { + PlayerData playerData = PlayerData.get(event.getPlayer()); + if (playerData.hasParty()) + playerData.getParty().removeMember(playerData); + } + + /* + * reset skill data when leaving combat + */ + @EventHandler + public void f(PlayerCombatEvent event) { + if (!event.entersCombat()) + event.getData().getSkillData().resetData(); + } + + /* + * updates the player's movement speed when equipping an armor to update the + * speed malus reduction from the armors. + */ + @EventHandler + public void g(ArmorEquipEvent event) { + PlayerData.get(event.getPlayer()).getStats().update(StatType.MOVEMENT_SPEED); + } + + /* + * apply damage modifiers. + */ + @EventHandler + public void h(PlayerAttackEvent event) { + double d = 1; + + PlayerStats stats = event.getData().getStats(); + for (DamageType type : event.getDamageInfo().getTypes()) + d += stats.getStat(type.getStat()) / 100; + + event.setDamage(event.getDamage() * d); + } +} diff --git a/src/net/Indyuce/mmocore/listener/SpellCast.java b/src/net/Indyuce/mmocore/listener/SpellCast.java new file mode 100644 index 00000000..ba1e8af7 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/SpellCast.java @@ -0,0 +1,127 @@ +package net.Indyuce.mmocore.listener; + +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class SpellCast implements Listener { + @EventHandler + public void a(PlayerSwapHandItemsEvent event) { + Player player = event.getPlayer(); + if (player.getGameMode() == GameMode.CREATIVE || player.getGameMode() == GameMode.SPECTATOR) + return; + + PlayerData playerData = PlayerData.get(player); + event.setCancelled(true); + + if (!player.isSneaking()) { + if (!playerData.isCasting() && playerData.getBoundSkills().size() > 0) { + playerData.skillCasting = new SkillCasting(playerData); + player.playSound(player.getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 2); + } + + /* + * hotbar swap feature only if the player is sneaking while entering + * skill casting mode + */ + } else if (MMOCore.plugin.configManager.hotbarSwap) { + player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1, 1); + for (int j = 0; j < 9; j++) { + ItemStack replaced = player.getInventory().getItem(j + 9 * 3); + player.getInventory().setItem(j + 9 * 3, player.getInventory().getItem(j)); + player.getInventory().setItem(j, replaced); + } + } + } + + public class SkillCasting extends BukkitRunnable implements Listener { + private final PlayerData playerData; + + private final String format = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar"); + private final String split = MMOCore.plugin.configManager.getSimpleMessage("casting.split"); + + private int j; + + public SkillCasting(PlayerData playerData) { + this.playerData = playerData; + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + runTaskTimer(MMOCore.plugin, 0, 1); + } + + @EventHandler(ignoreCancelled = false) + public void onSkillCast(PlayerItemHeldEvent event) { + Player player = event.getPlayer(); + if (!event.getPlayer().equals(playerData.getPlayer())) + return; + + /* + * when the event is cancelled, another playerItemHeldEvent is + * called and previous and next slots are equal. the event must not + * listen to that non-player called event. + */ + if (event.getPreviousSlot() == event.getNewSlot()) + return; + + event.setCancelled(true); + int slot = event.getNewSlot() + (event.getNewSlot() >= player.getInventory().getHeldItemSlot() ? -1 : 0); + + /* + * the event is called again soon after the first since when + * cancelling the first one, the player held item slot must go back + * to the previous one. + */ + if (slot >= 0 && playerData.hasSkillBound(slot)) + playerData.cast(playerData.getBoundSkill(slot)); + } + + @EventHandler + public void stopCasting(PlayerSwapHandItemsEvent event) { + Player player = event.getPlayer(); + if (event.getPlayer().equals(playerData.getPlayer()) && !player.isSneaking()) { + player.playSound(player.getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, 1, 2); + playerData.displayActionBar(MMOCore.plugin.configManager.getSimpleMessage("casting.no-longer")); + close(); + } + } + + private void close() { + playerData.skillCasting = null; + HandlerList.unregisterAll(this); + cancel(); + } + + private String getFormat(PlayerData data) { + String str = ""; + for (int j = 0; j < data.getBoundSkills().size(); j++) + str += (str.isEmpty() ? "" : split) + format.replace("{index}", "" + (j + 1 + (data.getPlayer().getInventory().getHeldItemSlot() <= j ? 1 : 0))).replace("{skill}", data.getBoundSkill(j).getSkill().getName()); + + return str; + } + + @Override + public void run() { + if (!playerData.isOnline() || playerData.getPlayer().isDead()) + close(); + + if (j % 20 == 0) + playerData.displayActionBar(getFormat(playerData)); + + for (int k = 0; k < 2; k++) { + double a = (double) j++ / 5; + playerData.getProfess().getCastParticle().display(playerData.getPlayer().getLocation().add(Math.cos(a), 1 + Math.sin(a / 3) / 1.3, Math.sin(a))); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/listener/WaypointsListener.java b/src/net/Indyuce/mmocore/listener/WaypointsListener.java new file mode 100644 index 00000000..5c79b866 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/WaypointsListener.java @@ -0,0 +1,38 @@ +package net.Indyuce.mmocore.listener; + +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerToggleSneakEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.Waypoint; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.manager.InventoryManager; + +public class WaypointsListener implements Listener { + @EventHandler + public void a(PlayerToggleSneakEvent event) { + Player player = event.getPlayer(); + if (!event.isSneaking()) + return; + + Waypoint waypoint = MMOCore.plugin.waypointManager.getCurrentWaypoint(player); + if (waypoint == null || !waypoint.hasSneakEnabled()) + return; + + PlayerData data = PlayerData.get(player); + if (!data.hasWaypoint(waypoint)) { + data.unlockWaypoint(waypoint); + new SmallParticleEffect(player, Particle.SPELL_WITCH); + player.sendMessage(MMOCore.plugin.configManager.getSimpleMessage("new-waypoint", "waypoint", waypoint.getName())); + player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1, 1.2f); + return; + } + + InventoryManager.WAYPOINTS.newInventory(data, waypoint).open(); + } +} diff --git a/src/net/Indyuce/mmocore/listener/event/PlayerAttackEventListener.java b/src/net/Indyuce/mmocore/listener/event/PlayerAttackEventListener.java new file mode 100644 index 00000000..e08c4ace --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/event/PlayerAttackEventListener.java @@ -0,0 +1,82 @@ +package net.Indyuce.mmocore.listener.event; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Damageable; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.EntityKillEntityEvent; +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class PlayerAttackEventListener implements Listener { + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void a(EntityDamageByEntityEvent event) { + if (!(event.getEntity() instanceof Damageable)) + return; + + /* + * check for event. + */ + DamageInfo info = new DamageInfo(); + Entity damager = getDamager(info, event); + + /* + * check for last damage + */ + if (damager == null && (event.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent)) + damager = getDamager(info, (EntityDamageByEntityEvent) event.getEntity().getLastDamageCause()); + + /* + * if the damage source cannot be found, just return. + */ + if (damager == null) + return; + + /* + * check damage systems from other MMOCore plugins + from MMOCore, and + * register an attack damage for easier plugin calculations + */ + if (damager instanceof Player) + Bukkit.getPluginManager().callEvent(new PlayerAttackEvent(PlayerData.get((Player) damager), event, MMOCore.plugin.damage.findInfo(event.getEntity()).merge(info))); + + /* + * checks for killing + */ + if (event.getFinalDamage() >= ((Damageable) event.getEntity()).getHealth()) + Bukkit.getPluginManager().callEvent(new EntityKillEntityEvent(damager, event.getEntity())); + } + + private Entity getDamager(DamageInfo damageInfo, EntityDamageByEntityEvent event) { + + /* + * check direct damager + */ + if (event.getDamager() instanceof LivingEntity) + return event.getDamager(); + + /* + * checks projectile and add damage type, which supports every vanilla + * projectile like snowballs, tridents and arrows + */ + if (event.getDamager() instanceof Projectile) { + Projectile proj = (Projectile) event.getDamager(); + if (proj.getShooter() instanceof Entity) { + damageInfo.getTypes().add(DamageType.PROJECTILE); + return (Entity) proj.getShooter(); + } + } + + return null; + } +} diff --git a/src/net/Indyuce/mmocore/listener/profession/Alchemy.java b/src/net/Indyuce/mmocore/listener/profession/Alchemy.java new file mode 100644 index 00000000..6c231213 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/profession/Alchemy.java @@ -0,0 +1,351 @@ +package net.Indyuce.mmocore.listener.profession; + +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BrewingStand; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.Type; + +public class Alchemy implements Listener { + private Set runnables = new HashSet<>(); + + private static Set recipes = new HashSet<>(); + + public Alchemy(ConfigurationSection config) { + Alchemy.recipes.clear(); + for (String key : config.getKeys(false)) { + BrewingRecipe recipe = new BrewingRecipe(config.getConfigurationSection(key)); + if (recipe.isValid()) + recipes.add(recipe); + } + } + + /* + * force place the item in the brewing stand inventory so it can start the + * recipe. + */ + @EventHandler + public void a(InventoryClickEvent event) { + if (event.getInventory().getType() != InventoryType.BREWING) + return; + + event.setCancelled(true); + ItemStack item = event.getCurrentItem(); + if (item == null || item.getType() == Material.AIR) + return; + + Player player = (Player) event.getWhoClicked(); + BrewingStand stand = (BrewingStand) event.getInventory().getHolder(); + BrewerInventory inv = (BrewerInventory) event.getInventory(); + + // send ingredient back in the player inventory + if (event.getRawSlot() == 3) { + if (player.getInventory().firstEmpty() == -1) + return; + + player.getInventory().addItem(inv.getIngredient()); + inv.setIngredient(null); + + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_PICKUP, 1, 1); + checkForBrewing(player, stand); + return; + } + + // send fuel back in the player inventory + if (event.getRawSlot() == 4) { + if (player.getInventory().firstEmpty() == -1) + return; + + player.getInventory().addItem(inv.getFuel()); + inv.setFuel(null); + + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_PICKUP, 1, 1); + checkForBrewing(player, stand); + return; + } + + // send bottle back in the player inventory + if (event.getRawSlot() >= 0 && event.getRawSlot() < 3) { + if (player.getInventory().firstEmpty() == -1) + return; + + player.getInventory().addItem(inv.getItem(event.getRawSlot())); + inv.setItem(event.getRawSlot(), null); + + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_PICKUP, 1, 1); + checkForBrewing(player, stand); + return; + } + + BrewingRecipe recipe = getCorrespondingRecipe(NBTItem.get(item)); + + // send fuel in the brewing stand + if (recipe == null) { + if (item.getType() == Material.BLAZE_POWDER && (inv.getFuel() == null || inv.getFuel().getType() == Material.AIR || item.isSimilar(inv.getFuel()))) { + int fuel = inv.getFuel() == null ? 0 : inv.getFuel().getAmount(); + int needed = 64 - fuel; + int sent = Math.min(needed, item.getAmount()); + + if (sent == 0) + return; + + ItemStack fuelItem = item.clone(); + fuelItem.setAmount(fuel + sent); + inv.setFuel(fuelItem); + + if (sent == item.getAmount()) + event.setCurrentItem(null); + else + item.setAmount(item.getAmount() - sent); + + player.playSound(player.getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 2); + checkForBrewing(player, stand); + return; + } + + // send potion in the brewing stand + if (item.getType() == Material.POTION && ((PotionMeta) item.getItemMeta()).getCustomEffects().isEmpty()) { + int empty = getEmptyBottleSlot(inv); + if (empty == -1) + return; + + inv.setItem(empty, event.getCurrentItem()); + event.setCurrentItem(null); + + player.playSound(player.getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 2); + checkForBrewing(player, stand); + } + return; + } + + // send ingredient in the brewing stand + if (inv.getIngredient() == null || inv.getIngredient().getType() == Material.AIR || item.isSimilar(inv.getIngredient())) { + int ingredient = inv.getIngredient() == null ? 0 : inv.getIngredient().getAmount(); + int needed = 64 - ingredient; + int sent = Math.min(needed, item.getAmount()); + + if (sent == 0) + return; + + ItemStack ingredientItem = item.clone(); + ingredientItem.setAmount(ingredient + sent); + inv.setIngredient(ingredientItem); + + if (sent == item.getAmount()) + event.setCurrentItem(null); + else + item.setAmount(item.getAmount() - sent); + + player.playSound(player.getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 2); + checkForBrewing(player, stand); + } + } + + private void checkForBrewing(Player player, BrewingStand stand) { + BrewingRecipe recipe = getCorrespondingRecipe(NBTItem.get(stand.getInventory().getIngredient())); + Location loc = stand.getLocation(); + if (!runnables.contains(loc.getBlockY() + "-" + loc.getBlockY() + "-" + loc.getBlockZ()) && recipe != null && stand.getFuelLevel() > 0 && hasAtLeastOnePotion(stand.getInventory())) + new BrewingRunnable(player, stand).start(recipe); + } + + private int getEmptyBottleSlot(BrewerInventory inv) { + ItemStack item; + for (int j = 0; j < 3; j++) + if ((item = inv.getItem(j)) == null || item.getType() == Material.AIR) + return j; + return -1; + } + + /* + * returns if the corresponding brewing inventory has at least ONE potion + * and if + */ + private boolean hasAtLeastOnePotion(BrewerInventory inv) { + ItemStack item; + for (int j = 0; j < 3; j++) + if ((item = inv.getItem(j)) != null) + return item.getType() == Material.POTION; + return false; + } + + private boolean hasNoBottle(BrewerInventory inv) { + ItemStack item; + for (int j = 0; j < 3; j++) + if ((item = inv.getItem(j)) != null && item.getType() == Material.POTION) + return false; + return true; + } + + private BrewingRecipe getCorrespondingRecipe(NBTItem item) { + for (BrewingRecipe recipe : recipes) + if (recipe.matchesIngredient(item)) + return recipe; + return null; + } + + private ItemStack consume(ItemStack item) { + if (item.getAmount() < 2) + return null; + + item.setAmount(item.getAmount() - 1); + return item; + } + + public class BrewingRunnable extends BukkitRunnable { + private int time = 0; + private Block block; + private Location loc; + private BrewingRecipe recipe; + private String mapPath; + private Player player; + + public BrewingRunnable(Player player, BrewingStand stand) { + this.block = stand.getBlock(); + this.player = player; + loc = stand.getLocation().add(.5, .5, .5); + } + + public void start(BrewingRecipe recipe) { + this.recipe = recipe; + BrewingStand stand = (BrewingStand) block.getState(); + stand.setFuelLevel(stand.getFuelLevel() - 1); + stand.update(); + runnables.add(mapPath = loc.getBlockY() + "-" + loc.getBlockY() + "-" + loc.getBlockZ()); + runTaskTimer(MMOCore.plugin, 1, 1); + } + + @Override + public void run() { + BrewingStand stand = (BrewingStand) block.getState(); + stand.getWorld().spawnParticle(Particle.SPELL_MOB, loc.clone().add(Math.cos((double) time / 3.) * .4, 0, Math.sin((double) time / 3.) * .4), 0); + + // cancel the recipe if ingredient was changed + if (!recipe.matchesIngredient(stand.getInventory().getIngredient()) || hasNoBottle(stand.getInventory())) { + runnables.remove(mapPath); + cancel(); + return; + } + + if (time++ > recipe.getCookingTime()) { + runnables.remove(mapPath); + stand.getInventory().setIngredient(consume(stand.getInventory().getIngredient())); + + int count = 0; + ItemStack item, result = recipe.getResult(); + for (int j = 0; j < 3; j++) + if ((item = stand.getInventory().getItem(j)) != null && item.getType() != Material.AIR) { + count++; + stand.getInventory().setItem(j, result); + } + + if (MMOCore.plugin.professionManager.has("alchemy")) + PlayerData.get(player).getCollectionSkills().giveExperience(MMOCore.plugin.professionManager.get("alchemy"), count * recipe.experience, loc); + + stand.getWorld().playSound(stand.getLocation(), Sound.BLOCK_BREWING_STAND_BREW, 1, 1); + cancel(); + return; + } + + stand.setBrewingTime((int) (400. * (1. - (double) time / recipe.getCookingTime()))); + stand.update(); + } + } + + public class BrewingRecipe { + + // ingredient + private Type ingredientType; + private String ingredientId; + + private int time, experience; + + // result + private Type type; + private String id; + + private boolean valid = true; + + public BrewingRecipe(ConfigurationSection section) { + try { + String[] split = section.getString("ingredient").split("\\."); + ingredientType = MMOItems.plugin.getTypes().get(split[0]); + ingredientId = split[1]; + + split = section.getString("result").split("\\."); + type = MMOItems.plugin.getTypes().get(split[0]); + id = split[1]; + + time = (int) (section.getDouble("cook-time") * 20.); + experience = section.getInt("exp"); + } catch (Exception e) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not register brewing recipe named " + section.getName()); + valid = false; + } + } + + public BrewingRecipe(Type ingredientType, String ingredientId, int time, Type type, String id) { + this.ingredientType = ingredientType; + this.ingredientId = ingredientId; + this.time = time; + this.type = type; + this.id = id; + } + + public boolean isValid() { + return valid; + } + + public boolean matchesIngredient(NBTItem nbt) { + return nbt.getString("MMOITEMS_ITEM_TYPE").equals(ingredientType.getId()) && nbt.getString("MMOITEMS_ITEM_ID").equals(ingredientId); + } + + public boolean matchesIngredient(ItemStack item) { + return matchesIngredient(NBTItem.get(item)); + } + + public int getCookingTime() { + return time; + } + + public ItemStack getResult() { + return MMOItems.plugin.getItems().getItem(type, id); + } + } + + // @EventHandler + // public void a(ProjectileLaunchEvent event) { + // if (!(event.getEntity() instanceof ThrownPotion) || + // !(event.getEntity().getShooter() instanceof Player)) + // return; + // + // double c = 1 + random.nextDouble() * 2; + // + // Vector vec = event.getEntity().getVelocity(); + // vec.setX(vec.getX() * c); + // vec.setZ(vec.getZ() * c); + // event.getEntity().setVelocity(vec); + // + // new PotionParticles((ThrownPotion) event.getEntity()).start(); + // } +} diff --git a/src/net/Indyuce/mmocore/listener/profession/FishingListener.java b/src/net/Indyuce/mmocore/listener/profession/FishingListener.java new file mode 100644 index 00000000..fd500aa3 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/profession/FishingListener.java @@ -0,0 +1,181 @@ +package net.Indyuce.mmocore.listener.profession; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.event.player.PlayerFishEvent.State; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.dropitem.fishing.FishingDropItem; +import net.Indyuce.mmocore.api.event.CustomPlayerFishEvent; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.manager.profession.FishingManager.FishingDropTable; + +public class FishingListener implements Listener { + private Set fishing = new HashSet<>(); + + private static final Random random = new Random(); + + @EventHandler(priority = EventPriority.LOW) + public void a(PlayerFishEvent event) { + Player player = event.getPlayer(); + FishHook hook = event.getHook(); + + if (event.getState() == State.BITE && !fishing.contains(player.getUniqueId())) { + + /* + * checks for drop tables. if no drop table, just plain vanilla + * fishing OTHERWISE initialize fishing, register other listener. + */ + FishingDropTable table = MMOCore.plugin.fishingManager.calculateDropTable(player); + if (table == null) + return; + + new FishingData(player, hook, table); + if (MMOCore.plugin.hasHolograms()) + MMOCore.plugin.hologramSupport.displayIndicator(hook.getLocation(), MMOCore.plugin.configManager.getSimpleMessage("caught-fish")); + } + } + + public class FishingData extends BukkitRunnable implements Listener { + private final Location location; + private final FishingDropItem caught; + private final PlayerData playerData; + private final Player player; + private final FishHook hook; + + private final int total, exp; + + private int pulls; + private long last = System.currentTimeMillis(); + + public FishingData(Player player, FishHook hook, FishingDropTable table) { + this.location = hook.getLocation(); + this.caught = table.getRandomItem(); + this.playerData = PlayerData.get(this.player = player); + this.hook = hook; + + this.total = (int) (caught.rollTugs() * (1 - PlayerData.get(player).getStats().getStat(StatType.FISHING_STRENGTH) / 100)); + this.exp = caught.rollExperience(); + + fishing.add(player.getUniqueId()); + runTaskTimer(MMOCore.plugin, 0, 2); + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + public void criticalFish() { + pulls = total + 2; + } + + public boolean isTimedOut() { + return last + 1000 < System.currentTimeMillis(); + } + + public boolean pull() { + last = System.currentTimeMillis(); + return pulls++ > total; + } + + public boolean isCrit() { + return pulls > total + 1; + } + + public void close() { + fishing.remove(player.getUniqueId()); + hook.remove(); + + HandlerList.unregisterAll(this); + cancel(); + } + + @Override + public void run() { + if (isTimedOut()) + close(); + + location.getWorld().spawnParticle(Particle.CRIT, location, 0, 2 * (random.nextDouble() - .5), 3, 2 * (random.nextDouble() - .5), .6); + } + + @EventHandler + public void a(PlayerFishEvent event) { + if (event.getPlayer().equals(player) && (event.getState() == State.CAUGHT_FISH || event.getState() == State.FAILED_ATTEMPT || event.getState() == State.REEL_IN)) { + + /* + * lose the catch if the current fish is gone! + */ + event.setCancelled(true); + if (isTimedOut()) { + close(); + hook.getWorld().spawnParticle(Particle.SMOKE_NORMAL, hook.getLocation(), 16, 0, 0, 0, .1); + return; + } + + if (pulls == 0 && random.nextDouble() < PlayerData.get(player).getStats().getStat(StatType.CRITICAL_FISHING_CHANCE) / 100) + criticalFish(); + + /* + * checks for enough pulls. if not, return and wait for next + * fish event. + */ + if (!pull()) + return; + + /* + * successfully pulls the fish + */ + close(); + + if (!isCrit() && random.nextDouble() < PlayerData.get(player).getStats().getStat(StatType.CRITICAL_FISHING_FAILURE_CHANCE) / 100) { + player.setVelocity(hook.getLocation().subtract(player.getLocation()).toVector().setY(0).multiply(3).setY(.5)); + hook.getWorld().spawnParticle(Particle.SMOKE_NORMAL, location, 24, 0, 0, 0, .08); + return; + } + + CustomPlayerFishEvent called = new CustomPlayerFishEvent(playerData, caught.getDropItem()); + Bukkit.getPluginManager().callEvent(called); + if (called.isCancelled()) + return; + + ItemStack collect = caught.collect(); + if (collect == null) { + hook.getWorld().spawnParticle(Particle.SMOKE_NORMAL, location, 24, 0, 0, 0, .08); + return; + } + + // calculate velocity + Item item = hook.getWorld().dropItemNaturally(hook.getLocation(), collect); + if (MMOCore.plugin.hasHolograms()) + MMOCore.plugin.hologramSupport.displayIndicator(location, MMOCore.plugin.configManager.getSimpleMessage("fish-out-water" + (isCrit() ? "-crit" : ""))); + Vector vec = player.getLocation().subtract(hook.getLocation()).toVector(); + vec.setY(vec.getY() * .031 + vec.length() * .05); + vec.setX(vec.getX() * .08); + vec.setZ(vec.getZ() * .08); + item.setVelocity(vec); + player.getWorld().playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_HAT, 1, 0); + for (int j = 0; j < 16; j++) + location.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, location, 0, 4 * (random.nextDouble() - .5), 2, 4 * (random.nextDouble() - .5), .05); + + if (MMOCore.plugin.professionManager.has("fishing")) + playerData.getCollectionSkills().giveExperience(MMOCore.plugin.professionManager.get("fishing"), exp, location); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/listener/profession/PlayerCollectStats.java b/src/net/Indyuce/mmocore/listener/profession/PlayerCollectStats.java new file mode 100644 index 00000000..2f84c689 --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/profession/PlayerCollectStats.java @@ -0,0 +1,58 @@ +package net.Indyuce.mmocore.listener.profession; + +import java.util.Random; + +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.block.data.Ageable; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import net.Indyuce.mmocore.api.event.CustomBlockMineEvent; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.stats.StatType; + +public class PlayerCollectStats implements Listener { + private static final Random random = new Random(); + + @EventHandler + public void a(CustomBlockMineEvent event) { + Player player = event.getPlayer(); + + // give haste if right enchant + double h = event.getData().getStats().getStat(StatType.GATHERING_HASTE); + if (h > 0 && random.nextDouble() < h * .045) { + new SmallParticleEffect(player, Particle.SPELL_INSTANT); + player.removePotionEffect(PotionEffectType.FAST_DIGGING); + player.addPotionEffect(new PotionEffect(PotionEffectType.FAST_DIGGING, (int) (10 * h), (int) (1 + h / 7))); + } + + // drop more items if fortune enchant + double f = event.getData().getStats().getStat(StatType.FORTUNE); + if (f > 0 && random.nextDouble() < f * .045) { + int a = (int) (1.5 * Math.sqrt(f / 1.1)); + for (ItemStack item : event.getDrops()) + item.setAmount(item.getAmount() + a); + } + + if (event.getBlock().getBlockData() instanceof Ageable) { + Ageable ageable = (Ageable) event.getBlock().getBlockData(); + if (ageable.getAge() < ageable.getMaximumAge()) + return; + + // drop more items if fortune enchant + double l = event.getData().getStats().getStat(StatType.LUCK_OF_THE_FIELD); + if (l > 0 && random.nextDouble() < l * .045) { + int a = (int) (1.5 * Math.sqrt(l / 1.1)); + Location loc = event.getBlock().getLocation().add(.5, .1, .5); + loc.getWorld().spawnParticle(Particle.VILLAGER_HAPPY, loc.clone().add(0, .2, 0), 10, .3, .2, .3, 0); + for (ItemStack item : event.getDrops()) + item.setAmount(item.getAmount() + a); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/listener/profession/Smelting.java b/src/net/Indyuce/mmocore/listener/profession/Smelting.java new file mode 100644 index 00000000..69117c0b --- /dev/null +++ b/src/net/Indyuce/mmocore/listener/profession/Smelting.java @@ -0,0 +1,134 @@ +package net.Indyuce.mmocore.listener.profession; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.FurnaceSmeltEvent; +import org.bukkit.inventory.FurnaceRecipe; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.Type; + +public class Smelting implements Listener { + + /* + * FurnaceSmeltEvent is called when an item has actually been smelted. + * FurnaceBurnEvent is called when a fuel piece is used to refill up the + * furnace fuel bar. + */ + + private static Set recipes = new HashSet<>(); + private static Set vanillaKeys = new HashSet<>(); + + public Smelting(ConfigurationSection config) { + for (Iterator iterator = Bukkit.recipeIterator(); iterator.hasNext();) { + Recipe recipe = iterator.next(); + if (recipe instanceof FurnaceRecipe && vanillaKeys.contains(((FurnaceRecipe) recipe).getKey())) + iterator.remove(); + } + + Smelting.recipes.clear(); + Smelting.vanillaKeys.clear(); + + for (String key : config.getKeys(false)) { + SmeltingRecipe recipe = new SmeltingRecipe(config.getConfigurationSection(key)); + if (recipe.isValid()) { + recipes.add(recipe); + NamespacedKey vanillaKey = new NamespacedKey(MMOCore.plugin, "furnace_recipe_" + key.replace("-", "_").toLowerCase()); + vanillaKeys.add(vanillaKey); + Bukkit.addRecipe(new FurnaceRecipe(vanillaKey, new ItemStack(Material.BARRIER), recipe.getIngredientMaterial(), 0, recipe.getCookingTime())); + } + } + } + + @EventHandler + public void a(FurnaceSmeltEvent event) { + NBTItem ingredient = NBTItem.get(event.getSource()); + SmeltingRecipe recipe = getCorrespondingRecipe(ingredient); + if (recipe == null) + event.setCancelled(true); + else + event.setResult(recipe.getResult()); + } + + private SmeltingRecipe getCorrespondingRecipe(NBTItem item) { + for (SmeltingRecipe recipe : recipes) + if (recipe.matchesIngredient(item)) + return recipe; + return null; + } + + public class SmeltingRecipe { + + // ingredient & result + private Type ingredientType, resultType; + private String ingredientId, resultId; + private Material ingredientMaterial; + + private int time; + + public SmeltingRecipe(ConfigurationSection section) { + try { + String[] split = section.getString("ingredient").split("\\."); + ingredientMaterial = MMOItems.plugin.getItems().getItem(ingredientType = MMOItems.plugin.getTypes().get(split[0]), ingredientId = split[1]).getType(); + + split = section.getString("result").split("\\."); + resultType = MMOItems.plugin.getTypes().get(split[0]); + resultId = split[1]; + + time = (int) (section.getDouble("cook-time") * 20.); + } catch (Exception e) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load furnace recipe named " + section.getName()); + } + } + + public SmeltingRecipe(Type ingredientType, String ingredientId, int time, Type resultType, String resultId) { + this.ingredientType = ingredientType; + this.ingredientId = ingredientId; + this.time = time; + this.resultType = resultType; + this.resultId = resultId; + } + + public boolean isValid() { + return ingredientType != null && ingredientId != null && time > 0 && resultType != null && resultId != null && ingredientMaterial != null; + } + + public boolean matchesIngredient(NBTItem nbt) { + return nbt.getString("MMOITEMS_ITEM_TYPE").equals(ingredientType.getId()) && nbt.getString("MMOITEMS_ITEM_ID").equals(ingredientId); + } + + @Deprecated + public boolean matchesIngredient(ItemStack item) { + return matchesIngredient(NBTItem.get(item)); + } + + public int getCookingTime() { + return time; + } + + public ItemStack getResult() { + return MMOItems.plugin.getItems().getItem(resultType, resultId); + } + + public Material getIngredientMaterial() { + return ingredientMaterial; + } + + public String toString() { + return "{ingredient=" + ingredientType.getId() + "." + ingredientId + ", time" + time + ", result=" + resultType.getId() + "." + resultId + ", valid=" + isValid() + "}"; + } + } +} diff --git a/src/net/Indyuce/mmocore/manager/AttributeManager.java b/src/net/Indyuce/mmocore/manager/AttributeManager.java new file mode 100644 index 00000000..7d0ea990 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/AttributeManager.java @@ -0,0 +1,45 @@ +package net.Indyuce.mmocore.manager; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.api.player.attribute.PlayerAttribute; + +public class AttributeManager extends MMOManager { + private final Map map = new HashMap<>(); + + public PlayerAttribute get(String id) { + return map.get(id); + } + + public boolean has(String id) { + return map.containsKey(id); + } + + public Collection getAll() { + return map.values(); + } + + @Override + public void reload() { + + ConfigFile config = new ConfigFile("attributes"); + for (String key : config.getConfig().getKeys(false)) + try { + String path = key.toLowerCase().replace("_", "-").replace(" ", "-"); + map.put(path, new PlayerAttribute(config.getConfig().getConfigurationSection(key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[PlayerAttributes] Could not load '" + key + "': " + exception.getMessage()); + } + } + + @Override + public void clear() { + map.clear(); + } + +} diff --git a/src/net/Indyuce/mmocore/manager/ClassManager.java b/src/net/Indyuce/mmocore/manager/ClassManager.java new file mode 100644 index 00000000..a1840bf9 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/ClassManager.java @@ -0,0 +1,103 @@ +package net.Indyuce.mmocore.manager; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.profess.PlayerClass; +import net.Indyuce.mmocore.api.player.profess.PlayerClass.ClassOption; +import net.Indyuce.mmocore.api.player.profess.event.EventTriggerHandler; +import net.Indyuce.mmocore.api.player.profess.event.trigger.AttackEventTrigger; +import net.Indyuce.mmocore.api.player.profess.event.trigger.ClassChosenEventTrigger; +import net.Indyuce.mmocore.api.player.profess.event.trigger.LevelUpEventTrigger; + +public class ClassManager extends MMOManager { + private final Map map = new HashMap<>(); + + /* + * cached default class. if there are two default classes, the last one + * overrides the previous. + */ + private PlayerClass defaultClass; + + /* + * same different types of trigger events to be able to map them later in + * the player class instances. + */ + private final Set triggerHandlers = new HashSet<>(); + + public ClassManager() { + registerEvent(new LevelUpEventTrigger()); + registerEvent(new AttackEventTrigger()); + registerEvent(new ClassChosenEventTrigger()); + } + + public void registerEvent(EventTriggerHandler handler) { + triggerHandlers.add(handler); + } + + public void register(PlayerClass playerClass) { + map.put(playerClass.getId(), playerClass); + } + + public boolean has(String id) { + return map.containsKey(id); + } + + public PlayerClass get(String id) { + return map.containsKey(id) ? map.get(id) : null; + } + + public Collection getAll() { + return map.values(); + } + + public PlayerClass getDefaultClass() { + return defaultClass; + } + + public void reloadPlayerClasses() { + PlayerData.getAll().forEach(data -> data.setProfess(get(data.getProfess().getId()))); + } + + @Override + public void reload() { + for (File file : new File(MMOCore.plugin.getDataFolder() + "/classes").listFiles()) + try { + String id = file.getName().substring(0, file.getName().length() - 4); + register(new PlayerClass(id, YamlConfiguration.loadConfiguration(file))); + } catch (IllegalArgumentException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load class " + file.getName() + ": " + exception.getMessage()); + } + + map.values().forEach(profess -> profess.loadSubclasses(this)); + defaultClass = map.values().stream().filter(profess -> profess.hasOption(ClassOption.DEFAULT)).findFirst().orElse(new PlayerClass("HUMAN", "Human", Material.LEATHER_BOOTS)); + + /* + * register event triggers + */ + triggerHandlers.forEach(handler -> Bukkit.getPluginManager().registerEvents(handler, MMOCore.plugin)); + } + + @Override + public void clear() { + map.clear(); + triggerHandlers.forEach(handler -> HandlerList.unregisterAll(handler)); + + /* + * do not clear the list of trigger listeners, since it's only setup + * once the server loads and it is never modified. + */ + } +} diff --git a/src/net/Indyuce/mmocore/manager/ConfigItemManager.java b/src/net/Indyuce/mmocore/manager/ConfigItemManager.java new file mode 100644 index 00000000..20755b47 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/ConfigItemManager.java @@ -0,0 +1,31 @@ +package net.Indyuce.mmocore.manager; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import org.bukkit.configuration.file.FileConfiguration; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.item.ConfigItem; + +public class ConfigItemManager { + private Map map = new HashMap<>(); + + public ConfigItemManager(FileConfiguration config) { + for (String key : config.getKeys(false)) + try { + register(new ConfigItem(config.getConfigurationSection(key))); + } catch (NullPointerException | IllegalArgumentException exception) { + MMOCore.plugin.getLogger().log(Level.INFO, "Could not load config item " + key); + } + } + + public void register(ConfigItem item) { + map.put(item.getId(), item); + } + + public ConfigItem get(String id) { + return map.get(id); + } +} diff --git a/src/net/Indyuce/mmocore/manager/ConfigManager.java b/src/net/Indyuce/mmocore/manager/ConfigManager.java new file mode 100644 index 00000000..d94f8ab3 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/ConfigManager.java @@ -0,0 +1,175 @@ +package net.Indyuce.mmocore.manager; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerExpChangeEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.util.Consumer; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.api.input.AnvilGUI; +import net.Indyuce.mmocore.api.input.ChatInput; +import net.Indyuce.mmocore.api.input.PlayerInput; +import net.Indyuce.mmocore.api.input.PlayerInput.InputType; + +public class ConfigManager { + + public float speedMalus; + public boolean overrideVanillaExp, hotbarSwap; + public double expPartyBuff, regenPartyBuff; + public String partyChatPrefix; + + private List neededExp = new ArrayList<>(); + private FileConfiguration messages; + private boolean chatInput; + + /* + * the instance must be created after the other managers since all it does + * is to update them based on the config except for the classes which are + * already loaded based on the config + */ + public ConfigManager() { + + // loadDefaultFile("recipes", "brewing.yml"); + // loadDefaultFile("recipes", "furnace.yml"); + + if (!new File(MMOCore.plugin.getDataFolder() + "/drop-tables").exists()) + loadDefaultFile("drop-tables", "example-drop-tables.yml"); + + if (!new File(MMOCore.plugin.getDataFolder() + "/professions").exists()) { + loadDefaultFile("professions", "alchemy.yml"); + loadDefaultFile("professions", "farming.yml"); + loadDefaultFile("professions", "fishing.yml"); + loadDefaultFile("professions", "mining.yml"); + loadDefaultFile("professions", "smelting.yml"); + loadDefaultFile("professions", "smithing.yml"); + loadDefaultFile("professions", "woodcutting.yml"); + loadDefaultFile("professions", "enchanting.yml"); + } + + if (!new File(MMOCore.plugin.getDataFolder() + "/quests").exists()) { + loadDefaultFile("quests", "adv-begins.yml"); + loadDefaultFile("quests", "tutorial.yml"); + loadDefaultFile("quests", "fetch-mango.yml"); + } + + if (!new File(MMOCore.plugin.getDataFolder() + "/classes").exists()) { + loadDefaultFile("classes", "arcane-mage.yml"); + loadDefaultFile("classes", "human.yml"); + loadDefaultFile("classes", "mage.yml"); + loadDefaultFile("classes", "marksman.yml"); + loadDefaultFile("classes", "paladin.yml"); + loadDefaultFile("classes", "rogue.yml"); + loadDefaultFile("classes", "warrior.yml"); + } + + loadDefaultFile("attributes.yml"); + loadDefaultFile("items.yml"); + loadDefaultFile("messages.yml"); + loadDefaultFile("levels.txt"); + loadDefaultFile("stats.yml"); + loadDefaultFile("waypoints.yml"); + loadDefaultFile("restrictions.yml"); + loadDefaultFile("chests.yml"); + + loadOptions(); + } + + public void loadOptions() { + speedMalus = (float) MMOCore.plugin.getConfig().getDouble("heavy-armors.speed-malus") / 100; + messages = new ConfigFile("messages").getConfig(); + hotbarSwap = MMOCore.plugin.getConfig().getBoolean("hotbar-swap"); + chatInput = MMOCore.plugin.getConfig().getBoolean("use-chat-input"); + expPartyBuff = MMOCore.plugin.getConfig().getDouble("party.buff.experience"); + regenPartyBuff = MMOCore.plugin.getConfig().getDouble("party.buff.health-regen"); + partyChatPrefix = MMOCore.plugin.getConfig().getString("party.chat-prefix"); + + if (overrideVanillaExp = MMOCore.plugin.getConfig().getBoolean("override-vanilla-exp")) + Bukkit.getPluginManager().registerEvents(new Listener() { + + @EventHandler + public void a(PlayerExpChangeEvent event) { + if (MMOCore.plugin.configManager.overrideVanillaExp) + event.setAmount(0); + } + }, MMOCore.plugin); + + if (MMOCore.plugin.getConfig().getBoolean("health-scale.enabled")) + Bukkit.getPluginManager().registerEvents(new Listener() { + private final double scale = MMOCore.plugin.getConfig().getDouble("health-scale.scale"); + + @EventHandler + public void a(PlayerJoinEvent event) { + Player player = event.getPlayer(); + player.setHealthScaled(true); + player.setHealthScale(scale); + } + }, MMOCore.plugin); + + neededExp.clear(); + int line = 0; + try { + line++; + File txt = new File(MMOCore.plugin.getDataFolder(), "levels.txt"); + BufferedReader reader = new BufferedReader(new FileReader(txt)); + String readLine; + while ((readLine = reader.readLine()) != null) + neededExp.add(Integer.valueOf(readLine)); + reader.close(); + } catch (IOException | IllegalArgumentException e) { + MMOCore.plugin.getLogger().log(Level.SEVERE, "Could not read line " + line + " from levels.txt"); + e.printStackTrace(); + } + } + + public PlayerInput newPlayerInput(Player player, InputType type, Consumer output) { + return chatInput ? new ChatInput(player, type, output) : new AnvilGUI(player, type, output); + } + + public void loadDefaultFile(String name) { + loadDefaultFile("", name); + } + + public void loadDefaultFile(String path, String name) { + File folder = new File(MMOCore.plugin.getDataFolder() + (path.isEmpty() ? "" : "/" + path)); + if (!folder.exists()) + folder.mkdir(); + + File file = new File(MMOCore.plugin.getDataFolder() + (path.isEmpty() ? "" : "/" + path), name); + if (!file.exists()) + try { + Files.copy(MMOCore.plugin.getResource("default/" + (path.isEmpty() ? "" : path + "/") + name), file.getAbsoluteFile().toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public int getNeededExperience(int level) { + return neededExp.get(level - 1 >= neededExp.size() ? neededExp.size() - 1 : level - 1); + } + + public List getMessage(String key) { + return messages.getStringList(key); + } + + public String getSimpleMessage(String key, String... placeholders) { + String format = messages.getString(key); + for (int j = 0; j < placeholders.length - 1; j += 2) + format = format.replace("{" + placeholders[j] + "}", placeholders[j + 1]); + return ChatColor.translateAlternateColorCodes('&', format); + } +} diff --git a/src/net/Indyuce/mmocore/manager/CustomBlockManager.java b/src/net/Indyuce/mmocore/manager/CustomBlockManager.java new file mode 100644 index 00000000..d315ce81 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/CustomBlockManager.java @@ -0,0 +1,227 @@ +package net.Indyuce.mmocore.manager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.DropTable; +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.condition.ConditionInstance; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.quest.trigger.ExperienceTrigger; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class CustomBlockManager extends MMOManager { + private final Map map = new HashMap<>(); + private final Set active = new HashSet<>(); + + /* + * list in which both block regen and block permissions are enabled. + */ + private final List customMineConditions = new ArrayList<>(); + + public void loadDropTables(ConfigurationSection config) { + for (String key : config.getKeys(false)) + try { + register(new BlockInfo(config.getConfigurationSection(key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "Could not load custom block '" + key + "': " + exception.getMessage()); + } + } + + public void register(BlockInfo regen) { + map.put(regen.getBlock(), regen); + } + + public BlockInfo getInfo(Material material) { + return map.containsKey(material) ? map.get(material) : null; + } + + /* + * called when the server disables so every mined block which was in timer + * are reset and put back in place. + */ + public void resetRemainingBlocks() { + active.forEach(info -> info.getLocation().getBlock().setType(info.getRegen().getBlock())); + } + + public void initialize(RegenInfo info) { + + active.add(info); + info.getLocation().getBlock().setType(info.getRegen().getTemporaryBlock()); + + new BukkitRunnable() { + public void run() { + active.remove(info); + info.getLocation().getBlock().setType(info.getRegen().getBlock()); + } + }.runTaskLater(MMOCore.plugin, info.getRegen().getRegenTime()); + } + + public boolean isEnabled(Entity entity) { + return isEnabled(entity, entity.getLocation()); + } + + public boolean isEnabled(Entity entity, Location loc) { + + ConditionInstance conditionEntity = new ConditionInstance(entity, loc); + for (Condition condition : customMineConditions) + if (!condition.isMet(conditionEntity)) + return false; + + return true; + } + + public class BlockInfo { + private final Material block; + private final DropTable table; + private final boolean vanillaDrops; + + private final List triggers = new ArrayList<>(); + private final ExperienceTrigger experience; + + /* + * options for block regen. + */ + private Material temporary; + private int regenTime = -1; + + public BlockInfo(ConfigurationSection config) { + Validate.notNull(config, "Could not load config"); + block = Material.valueOf(config.getName().toUpperCase().replace("-", "_").replace(" ", "_")); + table = config.contains("drop-table") ? MMOCore.plugin.dropTableManager.loadDropTable(config.get("drop-table")) : null; + vanillaDrops = config.contains("vanilla-drops") ? config.getBoolean("vanilla-drops") : true; + + if (config.contains("regen")) { + String format = config.getString("regen.temp-block"); + Validate.notNull(config, "Could not load temporary block"); + temporary = Material.valueOf(format.toUpperCase().replace("-", "_").replace(" ", "_")); + + regenTime = config.getInt("regen.time"); + } + + if (config.contains("triggers")) { + List list = config.getStringList("triggers"); + Validate.notNull(list, "Could not load triggers"); + + for (String key : list) + try { + triggers.add(MMOCore.plugin.loadManager.loadTrigger(new MMOLineConfig(key))); + } catch (MMOLoadException exception) { + exception.printConsole("BlockRegen", "trigger"); + } + } + + Optional opt = triggers.stream().filter(trigger -> (trigger instanceof ExperienceTrigger)).findFirst(); + experience = opt.isPresent() ? (ExperienceTrigger) opt.get() : null; + } + + public boolean hasVanillaDrops() { + return vanillaDrops; + } + + public Material getBlock() { + return block; + } + + public DropTable getDropTable() { + return table; + } + + public List collectDrops() { + return hasDropTable() ? table.collect() : new ArrayList<>(); + } + + public boolean hasDropTable() { + return table != null; + } + + public boolean hasRegen() { + return regenTime > 0; + } + + public int getRegenTime() { + return regenTime; + } + + public Material getTemporaryBlock() { + return temporary; + } + + public RegenInfo generateRegenInfo(Location loc) { + return new RegenInfo(loc, this); + } + + public boolean hasExperience() { + return experience != null; + } + + public ExperienceTrigger getExperience() { + return experience; + } + + public boolean hasTriggers() { + return !triggers.isEmpty(); + } + + public List getTriggers() { + return triggers; + } + } + + public class RegenInfo { + private final Location loc; + private final BlockInfo regen; + + private final long date = System.currentTimeMillis(); + + public RegenInfo(Location loc, BlockInfo regen) { + this.loc = loc; + this.regen = regen; + } + + public boolean isTimedOut() { + return date + regen.getRegenTime() * 50 < System.currentTimeMillis(); + } + + public Location getLocation() { + return loc; + } + + public BlockInfo getRegen() { + return regen; + } + } + + @Override + public void reload() { + customMineConditions.clear(); + + for (String key : MMOCore.plugin.getConfig().getStringList("custom-mine-conditions")) + try { + customMineConditions.add(MMOCore.plugin.loadManager.loadCondition(new MMOLineConfig(key))); + } catch (MMOLoadException exception) { + exception.printConsole("CustomMine", "condition"); + } + } + + @Override + public void clear() { + map.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/DamageManager.java b/src/net/Indyuce/mmocore/manager/DamageManager.java new file mode 100644 index 00000000..b7e316fc --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/DamageManager.java @@ -0,0 +1,72 @@ +package net.Indyuce.mmocore.manager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.comp.rpg.damage.DamageHandler; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class DamageManager implements Listener, DamageHandler { + private final Map customDamage = new HashMap<>(); + private final List handlers = new ArrayList<>(); + + public DamageManager() { + handlers.add(this); + } + + public void registerHandler(DamageHandler handler) { + handlers.add(handler); + } + + public void damage(PlayerData data, LivingEntity target, double value, DamageType... types) { + customDamage.put(target.getEntityId(), new DamageInfo(value, types)); + target.damage(value, data.getPlayer()); + } + + @Override + public DamageInfo getDamage(Entity entity) { + return customDamage.get(entity.getEntityId()); + } + + @Override + public boolean hasDamage(Entity entity) { + return customDamage.containsKey(entity.getEntityId()); + } + + @Deprecated + public boolean isCustomDamaged(Entity target) { + return findInfo(target) != null; + } + + public DamageInfo findInfo(Entity target) { + + for (DamageHandler handler : handlers) + if (handler.hasDamage(target)) + return handler.getDamage(target); + + /* + * any non registered damage source is considered weapon + */ + return new DamageInfo(DamageType.WEAPON); + } + + /* + * remove custom damage info as soon as the event is finished to save + * memory. if ignore cancelled, some can remain + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false) + public void a(EntityDamageByEntityEvent event) { + customDamage.remove(event.getEntity().getEntityId()); + } +} diff --git a/src/net/Indyuce/mmocore/manager/DropTableManager.java b/src/net/Indyuce/mmocore/manager/DropTableManager.java new file mode 100644 index 00000000..ca2b4a81 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/DropTableManager.java @@ -0,0 +1,83 @@ +package net.Indyuce.mmocore.manager; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.DropTable; + +public class DropTableManager extends MMOManager { + private Map map = new HashMap<>(); + + public void register(DropTable table) { + map.put(table.getId(), table); + } + + public DropTable get(String path) { + return map.get(path); + } + + public boolean has(String path) { + return map.containsKey(path); + } + + public Collection getDropTables() { + return map.values(); + } + + public Set getKeys() { + return map.keySet(); + } + + /* + * can only be used once the plugin has been fully loaded (+ enabled). used + * to load extra drop tables from different config files e.g blocks, fishing + * drop tables + */ + public DropTable loadDropTable(Object obj) throws IllegalArgumentException { + + if (obj instanceof String) + return get((String) obj); + + if (obj instanceof ConfigurationSection) { + DropTable table = new DropTable((ConfigurationSection) obj); + table.load(); + return table; + } + + throw new IllegalArgumentException("Could not parse drop table."); + } + + @Override + public void reload() { + for (File file : new File(MMOCore.plugin.getDataFolder() + "/drop-tables").listFiles()) + try { + + FileConfiguration config = YamlConfiguration.loadConfiguration(file); + for (String key : config.getKeys(false)) + try { + register(new DropTable(config.getConfigurationSection(key))); + } catch (IllegalArgumentException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load drop table '" + key + "': " + exception.getMessage()); + } + + } catch (IllegalArgumentException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load drop table file '" + file.getName() + "': " + exception.getMessage()); + } + + map.values().forEach(table -> table.load()); + } + + @Override + public void clear() { + map.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/EntityManager.java b/src/net/Indyuce/mmocore/manager/EntityManager.java new file mode 100644 index 00000000..6aecfeaa --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/EntityManager.java @@ -0,0 +1,29 @@ +package net.Indyuce.mmocore.manager; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.entity.Entity; + +import net.Indyuce.mmocore.comp.entity.EntityHandler; + +public class EntityManager { + public final List handlers = new ArrayList<>(); + + public void registerHandler(EntityHandler handler) { + handlers.add(handler); + } + + /* + * determines if an entity is from another plugin and therefore cannot be + * target of skill or attack + */ + public boolean findCustom(Entity entity) { + + for (EntityHandler handler : handlers) + if (handler.isCustomEntity(entity)) + return true; + + return false; + } +} diff --git a/src/net/Indyuce/mmocore/manager/InventoryManager.java b/src/net/Indyuce/mmocore/manager/InventoryManager.java new file mode 100644 index 00000000..a85f6ea7 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/InventoryManager.java @@ -0,0 +1,51 @@ +package net.Indyuce.mmocore.manager; + +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.gui.AttributeView; +import net.Indyuce.mmocore.gui.ClassConfirmation; +import net.Indyuce.mmocore.gui.ClassSelect; +import net.Indyuce.mmocore.gui.PlayerStats; +import net.Indyuce.mmocore.gui.QuestViewer; +import net.Indyuce.mmocore.gui.SkillList; +import net.Indyuce.mmocore.gui.SubclassConfirmation; +import net.Indyuce.mmocore.gui.SubclassSelect; +import net.Indyuce.mmocore.gui.WaypointViewer; +import net.Indyuce.mmocore.gui.api.EditableInventory; +import net.Indyuce.mmocore.gui.social.friend.EditableFriendList; +import net.Indyuce.mmocore.gui.social.friend.EditableFriendRemoval; +import net.Indyuce.mmocore.gui.social.party.EditablePartyCreation; +import net.Indyuce.mmocore.gui.social.party.EditablePartyView; + +public class InventoryManager { + public static final PlayerStats PLAYER_STATS = new PlayerStats(); + public static final SkillList SKILL_LIST = new SkillList(); + public static final ClassSelect CLASS_SELECT = new ClassSelect(); + public static final SubclassSelect SUBCLASS_SELECT = new SubclassSelect(); + public static final ClassConfirmation CLASS_CONFIRM = new ClassConfirmation(); + public static final SubclassConfirmation SUBCLASS_CONFIRM = new SubclassConfirmation(); + public static final WaypointViewer WAYPOINTS = new WaypointViewer(); + public static final EditableFriendList FRIEND_LIST = new EditableFriendList(); + public static final EditableFriendRemoval FRIEND_REMOVAL = new EditableFriendRemoval(); + public static final EditablePartyView PARTY_VIEW = new EditablePartyView(); + public static final EditablePartyCreation PARTY_CREATION = new EditablePartyCreation(); + public static final QuestViewer QUEST_LIST = new QuestViewer(); + public static final AttributeView ATTRIBUTE_VIEW = new AttributeView(); + + public static final List list = Arrays.asList(PLAYER_STATS, ATTRIBUTE_VIEW, SKILL_LIST, CLASS_SELECT, SUBCLASS_SELECT, SUBCLASS_CONFIRM, QUEST_LIST, WAYPOINTS, CLASS_CONFIRM, FRIEND_LIST, FRIEND_REMOVAL, PARTY_VIEW, PARTY_CREATION); + + public InventoryManager() { + list.forEach(inv -> { + MMOCore.plugin.configManager.loadDefaultFile("gui", inv.getId() + ".yml"); + try { + inv.reload(new ConfigFile("/gui", inv.getId()).getConfig()); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "[Inventories] Could not load inventory " + inv.getId() + ": " + exception.getMessage()); + } + }); + } +} diff --git a/src/net/Indyuce/mmocore/manager/LootableChestManager.java b/src/net/Indyuce/mmocore/manager/LootableChestManager.java new file mode 100644 index 00000000..c7c92d00 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/LootableChestManager.java @@ -0,0 +1,168 @@ +package net.Indyuce.mmocore.manager; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Chest; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.DropTable; +import net.Indyuce.mmocore.api.math.particle.ChestParticleEffect; + +public class LootableChestManager { + private Set map = new HashSet<>(); + + private static BukkitRunnable runnable; + private static final Random random = new Random(); + + public LootableChestManager(FileConfiguration config) { + for (String key : config.getKeys(false)) + register(new LootableChest(config.getConfigurationSection(key))); + + if (runnable != null) + runnable.cancel(); + + (runnable = new BukkitRunnable() { + public void run() { + map.forEach(chest -> { + if (chest.hasEffect() && chest.isSpawned() && chest.hasPlayerNearby()) + chest.playEffect(); + }); + } + }).runTaskTimerAsynchronously(MMOCore.plugin, 100, 4 * 20); + } + + public void register(LootableChest chest) { + if (chest.isValid()) { + map.add(chest); + chest.whenClosed(false); + } + } + + public LootableChest getLootableChest(Location loc) { + for (LootableChest chest : map) + if (blockCheck(chest.getLocation(), loc)) + return chest; + return null; + } + + private boolean blockCheck(Location loc1, Location loc2) { + return loc1.getWorld().equals(loc2.getWorld()) && loc1.getBlockX() == loc2.getBlockX() && loc1.getBlockY() == loc2.getBlockY() && loc1.getBlockZ() == loc2.getBlockZ(); + } + + public class LootableChest { + private Location loc; + private DropTable table; + private int regenTime = -1; + private long lastDisappear; + private Particle effectParticle; + private ChestParticleEffect effect; + + public LootableChest(ConfigurationSection config) { + try { + loc = readLocation(config.getName()); + regenTime = config.getInt("regen-time"); + table = MMOCore.plugin.dropTableManager.loadDropTable(config.get("drop-table")); + + if (config.contains("effect")) { + String format = config.getString("effect.particle"); + Validate.notNull(format, "Particle is missing particle"); + effectParticle = Particle.valueOf(format.toUpperCase().replace("-", "_")); + + format = config.getString("effect.type"); + Validate.notNull(format, "Particle is missing effect type"); + effect = ChestParticleEffect.valueOf(format.toUpperCase().replace("-", "_")); + } + } catch (IllegalArgumentException | IndexOutOfBoundsException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Couldn't read the loot chest config '" + config.getName() + "':" + exception.getMessage()); + } + } + + public boolean isValid() { + return loc != null && table != null && regenTime > -1; + } + + public boolean hasEffect() { + return effectParticle != null && effect != null; + } + + public boolean isSpawned() { + return System.currentTimeMillis() > lastDisappear + 50 * regenTime; + } + + public Location getLocation() { + return loc; + } + + public DropTable getDropTable() { + return table; + } + + public int getRegenTime() { + return regenTime; + } + + public void playEffect() { + effect.play(loc.clone().add(.5, .5, .5), effectParticle); + } + + public void whenClosed(boolean sound) { + if (sound) { + loc.getWorld().playSound(loc, Sound.ITEM_ARMOR_EQUIP_LEATHER, 1, 1); + loc.getWorld().spawnParticle(Particle.CRIT, loc.clone().add(.5, .5, .5), 16, 0, 0, 0, .5); + } + loc.getBlock().setType(Material.AIR); + lastDisappear = System.currentTimeMillis(); + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> whenSpawn(), regenTime); + } + + public boolean hasPlayerNearby() { + for (Player player : loc.getWorld().getPlayers()) + if (player.getLocation().distanceSquared(loc) < 625) + return true; + return false; + } + + public void whenSpawn() { + List slots = new ArrayList<>(); + for (int j = 0; j < 27; j++) + slots.add(j); + + loc.getBlock().setType(Material.CHEST); + Chest chest = (Chest) loc.getBlock().getState(); + table.collect().forEach(item -> { + Integer slot = slots.get(random.nextInt(slots.size())); + chest.getInventory().setItem(slot, item); + slots.remove(slot); + }); + } + + private Location readLocation(String string) { + String[] split = string.split("\\ "); + + World world = Bukkit.getWorld(split[0]); + Validate.notNull(world, "Could not find world '" + split[0] + "'"); + + double x = Double.parseDouble(split[1]); + double y = Double.parseDouble(split[2]); + double z = Double.parseDouble(split[3]); + + return new Location(world, x, y, z); + } + } +} diff --git a/src/net/Indyuce/mmocore/manager/MMOLoadManager.java b/src/net/Indyuce/mmocore/manager/MMOLoadManager.java new file mode 100644 index 00000000..95f7f9ef --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/MMOLoadManager.java @@ -0,0 +1,68 @@ +package net.Indyuce.mmocore.manager; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import org.bukkit.configuration.ConfigurationSection; + +import com.google.gson.JsonParseException; + +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.api.load.DefaultMMOLoader; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.load.MMOLoader; +import net.Indyuce.mmocore.api.quest.objective.Objective; +import net.Indyuce.mmocore.api.quest.trigger.Trigger; + +public class MMOLoadManager { + private final List loaders = new ArrayList<>(); + + public MMOLoadManager() { + loaders.add(new DefaultMMOLoader()); + } + + public void registerLoader(MMOLoader loader) { + loaders.add(loader); + } + + public Condition loadCondition(MMOLineConfig config) { + return load(Condition.class, config, (loader) -> loader.loadCondition(config)); + } + + public Objective loadObjective(MMOLineConfig config, ConfigurationSection section) { + return load(Objective.class, config, (loader) -> loader.loadObjective(config, section)); + } + + public ExperienceSource loadExperienceSource(MMOLineConfig config, Profession profession) { + return load(ExperienceSource.class, config, (loader) -> loader.loadExperienceSource(config, profession)); + } + + public Trigger loadTrigger(MMOLineConfig config) { + return load(Trigger.class, config, (loader) -> loader.loadTrigger(config)); + } + + public DropItem loadDropItem(MMOLineConfig config) { + return load(DropItem.class, config, (loader) -> loader.loadDropItem(config)); + } + + private T load(Class c, MMOLineConfig config, Function func) { + + for (MMOLoader loader : loaders) { + + try { + T found = func.apply(loader); + if (found != null) + return found; + } catch (IllegalArgumentException | JsonParseException | IndexOutOfBoundsException exception) { + throw new MMOLoadException("Could not load '" + config.toString() + "': " + exception.getMessage()); + } + } + + throw new MMOLoadException("Could not load '" + config.toString() + "': Could not find corresponding " + c.getSimpleName() + " in database"); + } +} diff --git a/src/net/Indyuce/mmocore/manager/MMOManager.java b/src/net/Indyuce/mmocore/manager/MMOManager.java new file mode 100644 index 00000000..8b9cdd4f --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/MMOManager.java @@ -0,0 +1,7 @@ +package net.Indyuce.mmocore.manager; + +public abstract class MMOManager { + public abstract void reload(); + + public abstract void clear(); +} diff --git a/src/net/Indyuce/mmocore/manager/QuestManager.java b/src/net/Indyuce/mmocore/manager/QuestManager.java new file mode 100644 index 00000000..a91cb161 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/QuestManager.java @@ -0,0 +1,51 @@ +package net.Indyuce.mmocore.manager; + +import java.io.File; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; + +import org.bukkit.configuration.file.YamlConfiguration; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.quest.Quest; + +public class QuestManager extends MMOManager { + private Map quests = new LinkedHashMap<>(); + + public void load(File file) { + if (file.isDirectory()) + for (File subfile : file.listFiles()) + load(subfile); + else + try { + register(new Quest(file.getName().substring(0, file.getName().length() - 4), YamlConfiguration.loadConfiguration(file))); + } catch (IllegalArgumentException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load quest '" + file.getName() + "': " + exception.getMessage()); + } + } + + public void register(Quest quest) { + quests.put(quest.getId(), quest); + } + + public Quest get(String id) { + return quests.get(id); + } + + public Collection getAll() { + return quests.values(); + } + + @Override + public void reload() { + load(new File(MMOCore.plugin.getDataFolder() + "/quests")); + quests.values().forEach(quest -> quest.load(this)); + } + + @Override + public void clear() { + quests.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/RestrictionManager.java b/src/net/Indyuce/mmocore/manager/RestrictionManager.java new file mode 100644 index 00000000..24f488c6 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/RestrictionManager.java @@ -0,0 +1,116 @@ +package net.Indyuce.mmocore.manager; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; + +import net.Indyuce.mmocore.MMOCore; + +public class RestrictionManager { + private Set breakBlackList = new HashSet<>(); + private Map map = new HashMap<>(); + + public RestrictionManager(FileConfiguration config) { + + for (String key : config.getKeys(false)) + try { + register(new BlockPermissions(config.getConfigurationSection(key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "Could not load block perms " + key + ": " + exception.getMessage()); + } + + for (BlockPermissions perms : map.values()) + try { + perms.load(); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "Could not load block perms " + perms.getTool().name() + ": " + exception.getMessage()); + } + } + + public void register(BlockPermissions perms) { + if (perms.isValid()) { + map.put(perms.getTool(), perms); + perms.getMinable().forEach(material -> breakBlackList.add(material)); + } + } + + public boolean isBlackListed(Material material) { + return breakBlackList.contains(material); + } + + public BlockPermissions getPermissions(Material tool) { + return map.containsKey(tool) ? map.get(tool) : null; + } + + public class BlockPermissions { + private final Set canMine = new HashSet<>(); + private final Material tool; + + private BlockPermissions parent; + + /* + * cache configuration section for easier laod + */ + private ConfigurationSection loaded; + + /* + * these instances must be initialized before loading data about them + * because in order to load PARENT permissions, every instance needs to + * be added to the map first + */ + public BlockPermissions(ConfigurationSection section) { + Validate.notNull(section, "Could not load config"); + + this.loaded = section; + tool = Material.valueOf(section.getName()); + } + + public void load() { + if (loaded.contains("parent")) { + String str = loaded.getString("parent").toUpperCase().replace("-", "_").replace(" ", "_"); + Validate.notNull(str, "Could not load parent"); + parent = map.get(Material.valueOf(str)); + } + + for (String key : loaded.getStringList("can-mine")) + canMine.add(Material.valueOf(key.toUpperCase().replace("-", "_"))); + + loaded = null; + } + + public void setParent(BlockPermissions parent) { + this.parent = parent; + } + + public void addPermission(Material material) { + canMine.add(material); + } + + // recursive function to check for parent permissions + public boolean canMine(Material material) { + return canMine.contains(material) || (parent != null && parent.canMine(material)); + } + + public Set getMinable() { + Set total = new HashSet<>(canMine); + if (parent != null) + total.addAll(parent.getMinable()); + return total; + } + + public Material getTool() { + return tool; + } + + public boolean isValid() { + return tool != null; + } + } +} diff --git a/src/net/Indyuce/mmocore/manager/SkillManager.java b/src/net/Indyuce/mmocore/manager/SkillManager.java new file mode 100644 index 00000000..4c3c7cc5 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/SkillManager.java @@ -0,0 +1,113 @@ +package net.Indyuce.mmocore.manager; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.comp.mythicmobs.MythicMobSkill; + +public class SkillManager { + private Map skills = new LinkedHashMap<>(); + + public SkillManager() { + + if (skills.isEmpty()) + try { + JarEntry entry; + for (Enumeration en = new JarFile(MMOCore.plugin.getJarFile()).entries(); en.hasMoreElements();) + if ((entry = en.nextElement()).getName().startsWith("net/Indyuce/mmocore/skill/") && !entry.isDirectory() && !entry.getName().contains("$")) + register((Skill) Class.forName(entry.getName().replace("/", ".").replace(".class", "")).newInstance()); + } catch (IOException | InstantiationException | IllegalAccessException | ClassNotFoundException exception) { + exception.printStackTrace(); + MMOCore.log(Level.WARNING, "Could not load skills! Careful with player data :("); + } + + if (!new File(MMOCore.plugin.getDataFolder() + "/skills").exists()) + new File(MMOCore.plugin.getDataFolder() + "/skills").mkdir(); + + File mythicMobs = new File(MMOCore.plugin.getDataFolder() + "/skills/mythic-mobs"); + if (!mythicMobs.exists()) + mythicMobs.mkdir(); + + /* + * load MythicMobs addon skills + */ + if (Bukkit.getPluginManager().getPlugin("MythicMobs") != null) + for (File file : mythicMobs.listFiles()) { + try { + register(new MythicMobSkill(file.getName().substring(0, file.getName().length() - 4), YamlConfiguration.loadConfiguration(file))); + } catch (Exception exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load skill from " + file.getName() + ": " + exception.getMessage()); + } + } + + for (Skill skill : getAll()) + if (!(skill instanceof MythicMobSkill)) { + File file = new File(MMOCore.plugin.getDataFolder() + "/skills", skill.getLowerCaseId() + ".yml"); + ConfigFile config = new ConfigFile("/skills", skill.getLowerCaseId()); + + if (!file.exists()) { + config.getConfig().set("name", skill.getName()); + config.getConfig().set("lore", skill.getLore()); + + /* + * it does support custom modeled items but it does not + * provide default configs for that. + */ + config.getConfig().set("material", skill.getIcon().getType().name()); + + for (String mod : skill.getModifiers()) { + LinearValue value = skill.getModifierInfo(mod); + config.getConfig().set(mod + ".base", value.getBaseValue()); + config.getConfig().set(mod + ".per-level", value.getPerLevel()); + if (value.hasMax()) + config.getConfig().set(mod + ".max", value.getMax()); + if (value.hasMin()) + config.getConfig().set(mod + ".min", value.getMin()); + } + } + + skill.update(config.getConfig()); + config.save(); + } + } + + public void register(Skill skill) { + skills.put(skill.getId(), skill); + } + + public Skill get(String id) { + return skills.get(id); + } + + public boolean has(String id) { + return skills.containsKey(id); + } + + public Collection getAll() { + return skills.values(); + } + + public Set getActive() { + return skills.values().stream().filter(skill -> !skill.isPassive()).collect(Collectors.toSet()); + } + + public Set getKeys() { + return skills.keySet(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/WaypointManager.java b/src/net/Indyuce/mmocore/manager/WaypointManager.java new file mode 100644 index 00000000..008387d0 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/WaypointManager.java @@ -0,0 +1,54 @@ +package net.Indyuce.mmocore.manager; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.Waypoint; + +public class WaypointManager { + private Map waypoints = new LinkedHashMap<>(); + + public WaypointManager(FileConfiguration config) { + for (String key : config.getKeys(false)) + try { + register(new Waypoint(config.getConfigurationSection(key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "Could not load waypoint " + key + ": " + exception.getMessage()); + } + } + + public Collection getAll() { + return waypoints.values(); + } + + public Set getDefault() { + return getAll().stream().filter(waypoint -> waypoint.isDefault()).collect(Collectors.toSet()); + } + + public boolean has(String id) { + return waypoints.containsKey(id); + } + + public Waypoint get(String id) { + return waypoints.get(id); + } + + public void register(Waypoint waypoint) { + waypoints.put(waypoint.getId(), waypoint); + } + + public Waypoint getCurrentWaypoint(Player player) { + for (Waypoint waypoint : getAll()) + if (waypoint.isOnWaypoint(player)) + return waypoint; + return null; + } +} diff --git a/src/net/Indyuce/mmocore/manager/profession/AlchemyManager.java b/src/net/Indyuce/mmocore/manager/profession/AlchemyManager.java new file mode 100644 index 00000000..8eb92e36 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/profession/AlchemyManager.java @@ -0,0 +1,53 @@ +package net.Indyuce.mmocore.manager.profession; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.potion.PotionType; + +import net.Indyuce.mmocore.manager.MMOManager; + +public class AlchemyManager extends MMOManager { + public double splash, lingering, upgrade, extend; + + // private Map custom = new HashMap<>(); + private Map base = new HashMap<>(); + + // public double getWeight(PotionEffectType type) { + // return custom.containsKey(type) ? custom.get(type) : 0; + // } + + // public void registerEffectWeight(PotionEffectType type, double weight) { + // custom.put(type, weight); + // } + + public void registerBaseExperience(PotionType type, double value) { + base.put(type, value); + } + + public double getBaseExperience(PotionType type) { + return base.get(type); + } + + // public class BaseExperience { + // private final double normal, extend, upgrade; + // + // public BaseExperience(double normal, double extend, double upgrade) { + // this.extend = extend; + // this.upgrade = upgrade; + // this.normal = normal; + // } + // } + + @Override + public void reload() { + // TODO Auto-generated method stub + + } + + @Override + public void clear() { + splash = lingering = upgrade = extend = 1; + base.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/profession/EnchantManager.java b/src/net/Indyuce/mmocore/manager/profession/EnchantManager.java new file mode 100644 index 00000000..073a5e1a --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/profession/EnchantManager.java @@ -0,0 +1,31 @@ +package net.Indyuce.mmocore.manager.profession; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.enchantments.Enchantment; + +import net.Indyuce.mmocore.manager.MMOManager; + +public class EnchantManager extends MMOManager { + private final Map base = new HashMap<>(); + + public void registerBaseExperience(Enchantment enchant, double value) { + base.put(enchant, value); + } + + public double getBaseExperience(Enchantment enchant) { + return base.get(enchant); + } + + @Override + public void reload() { + // TODO Auto-generated method stub + + } + + @Override + public void clear() { + base.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/profession/ExperienceManager.java b/src/net/Indyuce/mmocore/manager/profession/ExperienceManager.java new file mode 100644 index 00000000..3fc25a01 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/profession/ExperienceManager.java @@ -0,0 +1,25 @@ +package net.Indyuce.mmocore.manager.profession; + +import java.util.HashSet; +import java.util.Set; + +import org.bukkit.Bukkit; +import org.bukkit.event.Listener; + +import net.Indyuce.mmocore.MMOCore; + +public abstract class ExperienceManager implements Listener { + private final Set sources = new HashSet<>(); + + public ExperienceManager() { + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + public void register(T source) { + sources.add(source); + } + + public Set getSources() { + return sources; + } +} diff --git a/src/net/Indyuce/mmocore/manager/profession/FishingManager.java b/src/net/Indyuce/mmocore/manager/profession/FishingManager.java new file mode 100644 index 00000000..89450865 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/profession/FishingManager.java @@ -0,0 +1,121 @@ +package net.Indyuce.mmocore.manager.profession; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Entity; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.condition.Condition; +import net.Indyuce.mmocore.api.droptable.condition.ConditionInstance; +import net.Indyuce.mmocore.api.droptable.dropitem.fishing.FishingDropItem; +import net.Indyuce.mmocore.api.load.MMOLineConfig; +import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.manager.MMOManager; + +public class FishingManager extends MMOManager { + private final Set tables = new LinkedHashSet<>(); + + private static final Random random = new Random(); + + public void loadDropTables(ConfigurationSection config) { + for (String key : config.getKeys(false)) + try { + tables.add(new FishingDropTable(config.getConfigurationSection(key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "Could not load fishing drop table " + key + ": " + exception.getMessage()); + } + } + + public FishingDropTable calculateDropTable(Entity entity) { + ConditionInstance conditionEntity = new ConditionInstance(entity); + + for (FishingDropTable table : tables) + if (table.areConditionsMet(conditionEntity)) + return table; + + return null; + } + + public class FishingDropTable { + private final String id; + private final int maxCoef; + + private final Set conditions = new HashSet<>(); + private final List items = new ArrayList<>(); + + public FishingDropTable(ConfigurationSection section) { + Validate.notNull(section, "Could not load config"); + this.id = section.getName(); + + if (section.contains("conditions")) { + List list = section.getStringList("conditions"); + Validate.notNull(list, "Could not load conditions"); + + for (String str : list) + try { + conditions.add(MMOCore.plugin.loadManager.loadCondition(new MMOLineConfig(str))); + } catch (MMOLoadException exception) { + exception.printConsole("FishDropTables", "fish drop item"); + } + } + + List list = section.getStringList("items"); + Validate.notNull(list, "Could not load item list"); + + int coef = 0; + for (String str : list) + try { + FishingDropItem dropItem = new FishingDropItem(coef, str); + coef = dropItem.getMaxCoefficient(); + items.add(dropItem); + } catch (MMOLoadException exception) { + exception.printConsole("FishDropTables:" + id, "drop item"); + } catch (IllegalArgumentException | IndexOutOfBoundsException exception) { + MMOCore.log(Level.WARNING, "[FishDropTables:" + id + "] Could not load item '" + str + "': " + exception.getMessage()); + } + maxCoef = coef; + + Validate.notEmpty(list, "The item list must not be empty."); + } + + public boolean areConditionsMet(ConditionInstance entity) { + for (Condition condition : conditions) + if (!condition.isMet(entity)) + return false; + return true; + } + + public Set getConditions() { + return conditions; + } + + public FishingDropItem getRandomItem() { + int c = random.nextInt(maxCoef); + + for (FishingDropItem item : items) + if (item.matchesCoefficient(c)) + return item; + + throw new NullPointerException("Could not find item in drop table"); + } + } + + @Override + public void reload() { + // TODO Auto-generated method stub + + } + + @Override + public void clear() { + tables.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/profession/ProfessionManager.java b/src/net/Indyuce/mmocore/manager/profession/ProfessionManager.java new file mode 100644 index 00000000..a4395a5f --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/profession/ProfessionManager.java @@ -0,0 +1,78 @@ +package net.Indyuce.mmocore.manager.profession; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.experience.Profession; +import net.Indyuce.mmocore.api.experience.source.type.ExperienceSource; +import net.Indyuce.mmocore.manager.MMOManager; + +public class ProfessionManager extends MMOManager { + + /* + * loaded professions. + */ + private final Map professions = new HashMap<>(); + + /* + * saves different experience sources based on experience source type. + */ + private final Map, ExperienceManager> managers = new HashMap<>(); + + @SuppressWarnings("unchecked") + public ExperienceManager getManager(Class t) { + return (ExperienceManager) managers.get(t); + } + + @SuppressWarnings("unchecked") + public > void registerExpSource(T source) { + Class path = (Class) source.getClass(); + + if (!managers.containsKey(path)) + managers.put(path, source.newManager()); + getManager(path).register(source); + } + + public void register(Profession profession) { + professions.put(profession.getId(), profession); + } + + public Profession get(String id) { + return professions.get(id); + } + + public boolean has(String id) { + return professions.containsKey(id); + } + + public Collection getAll() { + return professions.values(); + } + + @Override + public void reload() { + for (File file : new File(MMOCore.plugin.getDataFolder() + "/professions").listFiles()) + try { + String id = file.getName().substring(0, file.getName().length() - 4); + register(new Profession(id, YamlConfiguration.loadConfiguration(file))); + } catch (IllegalArgumentException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load profession " + file.getName() + ": " + exception.getMessage()); + } + + getAll().forEach(profession -> profession.loadOptions()); + } + + @Override + public void clear() { + managers.values().forEach(manager -> HandlerList.unregisterAll(manager)); + managers.clear(); + professions.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/profession/SmithingManager.java b/src/net/Indyuce/mmocore/manager/profession/SmithingManager.java new file mode 100644 index 00000000..a70415c8 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/profession/SmithingManager.java @@ -0,0 +1,35 @@ +package net.Indyuce.mmocore.manager.profession; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.Material; + +import net.Indyuce.mmocore.manager.MMOManager; + +public class SmithingManager extends MMOManager { + private final Map base = new HashMap<>(); + + public void registerBaseExperience(Material material, double value) { + base.put(material, value); + } + + public double getBaseExperience(Material material) { + return base.get(material); + } + + public boolean hasExperience(Material material) { + return base.containsKey(material); + } + + @Override + public void reload() { + // TODO Auto-generated method stub + + } + + @Override + public void clear() { + base.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/social/BoosterManager.java b/src/net/Indyuce/mmocore/manager/social/BoosterManager.java new file mode 100644 index 00000000..cc24a641 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/social/BoosterManager.java @@ -0,0 +1,42 @@ +package net.Indyuce.mmocore.manager.social; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import net.Indyuce.mmocore.api.experience.Booster; +import net.Indyuce.mmocore.api.experience.Profession; + +public class BoosterManager { + private List map = new ArrayList<>(); + + public void register(Booster booster) { + flush(); + map.add(booster); + } + + public void flush() { + for (Iterator iterator = map.iterator(); iterator.hasNext();) { + Booster next = iterator.next(); + if (next.isTimedOut()) + iterator.remove(); + } + } + + public int calculateExp(Profession profession, double exp) { + flush(); + for (Booster booster : map) + if (booster.getProfession() == profession) + exp = booster.calculateExp(exp); + return (int) exp; + } + + public List getBoosters() { + flush(); + return map; + } + + public Booster get(int index) { + return map.get(index); + } +} diff --git a/src/net/Indyuce/mmocore/manager/social/PartyManager.java b/src/net/Indyuce/mmocore/manager/social/PartyManager.java new file mode 100644 index 00000000..9751ae24 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/social/PartyManager.java @@ -0,0 +1,70 @@ +package net.Indyuce.mmocore.manager.social; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.configuration.ConfigurationSection; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.social.Party; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.api.player.stats.stat.modifier.StatModifier; +import net.Indyuce.mmocore.manager.MMOManager; + +public class PartyManager extends MMOManager { + private final Set parties = new HashSet<>(); + private final Map buffs = new HashMap<>(); + + public void registerParty(Party party) { + parties.add(party); + } + + public Party newRegisteredParty(PlayerData owner) { + Party party = new Party(owner); + registerParty(party); + return party; + } + + public boolean isRegistered(Party party) { + return parties.contains(party); + } + + public void unregisterParty(Party party) { + party.getMembers().forEach(member -> party.removeMember(member)); + parties.remove(party); + } + + public boolean hasBonus(StatType stat) { + return buffs.containsKey(stat); + } + + public StatModifier getBonus(StatType stat) { + return buffs.get(stat); + } + + public Set getBonuses() { + return buffs.keySet(); + } + + @Override + public void reload() { + ConfigurationSection config = MMOCore.plugin.getConfig().getConfigurationSection("party.buff"); + if (config != null) + for (String key : config.getKeys(false)) + try { + StatType stat = StatType.valueOf(key.toUpperCase().replace("-", "_").replace(" ", "_")); + buffs.put(stat, new StatModifier(config.getString(key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "Could not load party buff '" + key + "': " + exception.getMessage()); + } + } + + @Override + public void clear() { + buffs.clear(); + } +} diff --git a/src/net/Indyuce/mmocore/manager/social/RequestManager.java b/src/net/Indyuce/mmocore/manager/social/RequestManager.java new file mode 100644 index 00000000..32972f19 --- /dev/null +++ b/src/net/Indyuce/mmocore/manager/social/RequestManager.java @@ -0,0 +1,56 @@ +package net.Indyuce.mmocore.manager.social; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.social.Request; + +public class RequestManager { + private Set requests = new HashSet<>(); + + /* + * flush friend requests async not to consume performance every 5 minutes so + * there is no memory overleak. + */ + public RequestManager() { + new BukkitRunnable() { + public void run() { + flushRequests(); + } + }.runTaskTimerAsynchronously(MMOCore.plugin, 60 * 20, 60 * 20 * 5); + } + + public Request getRequest(UUID uuid) { + for (Request request : requests) + if (request.getUniqueId().equals(uuid)) + return request; + return null; + } + + public void registerRequest(Request request) { + requests.add(request); + } + + public void unregisterRequest(UUID uuid) { + for (Iterator iterator = requests.iterator(); iterator.hasNext();) { + Request next = iterator.next(); + if (next.getUniqueId().equals(uuid)) { + iterator.remove(); + break; + } + } + } + + public void flushRequests() { + for (Iterator iterator = requests.iterator(); iterator.hasNext();) { + Request next = iterator.next(); + if (next.isTimedOut()) + iterator.remove(); + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Ambers.java b/src/net/Indyuce/mmocore/skill/Ambers.java new file mode 100644 index 00000000..52f0ea74 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Ambers.java @@ -0,0 +1,86 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.ParabolicProjectile; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.StatType; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; + +public class Ambers extends Skill implements Listener { + public Ambers() { + super(); + setMaterial(Material.EMERALD); + setLore("Dealing magic damage has &630% &7chance", "of dropping an amber on the ground.", "", "When picked up, ambers stack and", "refund &915% &7of your missing mana.", "", "&oAmbers can be used in other damaging skills.", "&oThe more you collect, the more powerful the skills.", "", "&e{cooldown}s Cooldown"); + setPassive(); + + addModifier("cooldown", new LinearValue(3, -.1, 1, 3)); + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + @EventHandler + public void a(PlayerAttackEvent event) { + PlayerData data = event.getData(); + if (event.isWeapon() || !data.getProfess().hasSkill(this)) + return; + + SkillResult cast = data.cast(this); + if (!cast.isSuccessful()) + return; + + Location loc = event.getEntity().getLocation(); + double a = random.nextDouble() * 2 * Math.PI; + + new Amber(data, loc.add(0, event.getEntity().getHeight() / 2, 0), loc.clone().add(4 * Math.cos(a), 0, 4 * Math.sin(a))); + } + + public class Amber extends BukkitRunnable { + private final Location loc; + private final PlayerData data; + + private int j; + + private Amber(PlayerData data, Location source, Location loc) { + this.loc = loc; + this.data = data; + + final Amber amber = this; + new ParabolicProjectile(source, loc, Particle.REDSTONE, () -> amber.runTaskTimer(MMOCore.plugin, 0, 3), 1, Color.ORANGE, 1.3f); + } + + @Override + public void run() { + if (j++ > 66 || !data.getPlayer().getWorld().equals(loc.getWorld())) { + cancel(); + return; + } + + if (data.getPlayer().getLocation().distanceSquared(loc) < 2) { + + data.getPlayer().playSound(data.getPlayer().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 1); + data.getSkillData().ambers++; + data.giveMana((data.getStats().getStat(StatType.MAX_MANA) - data.getMana()) * .15); + + cancel(); + return; + } + + for (int j = 0; j < 5; j++) + loc.getWorld().spawnParticle(Particle.SPELL_MOB, loc, 0, 1, 0.647, 0, 1); + loc.getWorld().spawnParticle(Particle.REDSTONE, loc, 0, new Particle.DustOptions(Color.ORANGE, 1.3f)); + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Combo_Attack.java b/src/net/Indyuce/mmocore/skill/Combo_Attack.java new file mode 100644 index 00000000..cb793f1f --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Combo_Attack.java @@ -0,0 +1,56 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Combo_Attack extends Skill { + public Combo_Attack() { + super(); + setMaterial(Material.IRON_SWORD); + setLore("Violenty slashes your target &8{count}", "times for a total of &8{damage} &7damage.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(20, -.1, 5, 20)); + addModifier("damage", new LinearValue(9, .3)); + addModifier("count", new LinearValue(3, .2)); + addModifier("mana", new LinearValue(10, -.1, 3, 5)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + TargetSkillResult cast = new TargetSkillResult(data, skill, 3); + if (!cast.isSuccessful()) + return cast; + + new BukkitRunnable() { + final int count = (int) cast.getModifier("count"); + final double damage = cast.getModifier("damage") / count; + LivingEntity target = cast.getTarget(); + + int c; + + @Override + public void run() { + if (c++ > count) { + cancel(); + return; + } + + target.getWorld().playSound(target.getLocation(), Sound.ENTITY_PLAYER_ATTACK_KNOCKBACK, 1, 2); + target.getWorld().spawnParticle(Particle.CRIT, target.getLocation().add(0, target.getHeight() / 2, 0), 24, 0, 0, 0, .7); + MMOCore.plugin.damage.damage(data, target, damage, DamageType.SKILL, DamageType.PHYSICAL); + } + }.runTaskTimer(MMOCore.plugin, 0, 5); + return cast; + } +} diff --git a/src/net/Indyuce/mmocore/skill/Control.java b/src/net/Indyuce/mmocore/skill/Control.java new file mode 100644 index 00000000..61e1794d --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Control.java @@ -0,0 +1,103 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; + +public class Control extends Skill { + public Control() { + super(); + setMaterial(Material.MAGENTA_DYE); + setLore("Your target is temporarily slowed for &8{duration} &7seconds.", "As soon as you left click, it gets", "pushed back where you are looking at.", "Knockback force: &f{knockback}%", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(18, -.3, 10, 20)); + addModifier("mana", new LinearValue(15, 1.5)); + addModifier("knockback", new LinearValue(30, 3)); + addModifier("duration", new LinearValue(2, 0)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + TargetSkillResult cast = new TargetSkillResult(data, skill, 7); + if (!cast.isSuccessful()) + return cast; + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 1); + cast.getTarget().addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 20 * 2, 0)); + new TelekinesyRunnable(data, cast.getTarget(), cast.getModifier("knockback") / 100, cast.getModifier("duration")); + return cast; + } + + public class TelekinesyRunnable extends BukkitRunnable implements Listener { + private final LivingEntity entity; + private final PlayerData data; + + private final double f, d; + + private int j; + + public TelekinesyRunnable(PlayerData data, LivingEntity entity, double force, double duration) { + this.entity = entity; + this.data = data; + + d = duration * 20; + f = force; + + runTaskTimer(MMOCore.plugin, 0, 1); + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + @EventHandler + public void a(PlayerInteractEvent event) { + if (event.getPlayer().equals(data.getPlayer()) && event.getAction().name().contains("LEFT_CLICK")) { + Vector vec = data.getPlayer().getEyeLocation().getDirection().multiply(3 * f); + vec.setY(Math.max(.5, vec.getY() / 2)); + entity.setVelocity(vec); + + /* + * try not to interfere with other potion effects + */ + PotionEffect effect = entity.getPotionEffect(PotionEffectType.SLOW); + if (effect.getDuration() < d && effect.getAmplifier() == 0) + entity.removePotionEffect(PotionEffectType.SLOW); + + entity.getWorld().spawnParticle(Particle.SPELL_WITCH, entity.getLocation().add(0, entity.getHeight() / 2, 0), 16); + entity.getWorld().playSound(entity.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 2, 1); + close(); + } + } + + @Override + public void run() { + if (!data.isOnline() || entity.isDead() || j++ >= d) { + close(); + return; + } + + double a = (double) j / 3; + entity.getWorld().spawnParticle(Particle.SPELL_WITCH, entity.getLocation().add(Math.cos(a), entity.getHeight() / 2, Math.sin(a)), 0); + } + + private void close() { + cancel(); + HandlerList.unregisterAll(this); + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Deep_Wound.java b/src/net/Indyuce/mmocore/skill/Deep_Wound.java new file mode 100644 index 00000000..43ac8901 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Deep_Wound.java @@ -0,0 +1,47 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.LivingEntity; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Deep_Wound extends Skill { + public Deep_Wound() { + super(); + setMaterial(Material.REDSTONE); + setLore("You puncture your target, dealing &c{damage} &7damage.", "Damage is increased up to &c+{extra}% &7based", "on your target's missing health.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(20, -.1, 5, 20)); + addModifier("mana", new LinearValue(8, 3)); + addModifier("damage", new LinearValue(5, 1.5)); + addModifier("extra", new LinearValue(50, 20)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + TargetSkillResult cast = new TargetSkillResult(data, skill, 3); + if (!cast.isSuccessful()) + return cast; + + LivingEntity target = cast.getTarget(); + target.getWorld().playSound(target.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 2, 2); + target.getWorld().spawnParticle(Particle.CRIT, target.getLocation().add(0, target.getHeight() / 2, 0), 32, 0, 0, 0, .7); + target.getWorld().spawnParticle(Particle.BLOCK_CRACK, target.getLocation().add(0, target.getHeight() / 2, 0), 32, 0, 0, 0, 2, Material.REDSTONE_BLOCK.createBlockData()); + + double max = target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); + double ratio = (max - target.getHealth()) / max; + + double damage = cast.getModifier("damage") * (1 + cast.getModifier("extra") * ratio / 100); + MMOCore.plugin.damage.damage(data, target, damage, DamageType.SKILL, DamageType.PHYSICAL); + return cast; + } +} diff --git a/src/net/Indyuce/mmocore/skill/Empowered_Attack.java b/src/net/Indyuce/mmocore/skill/Empowered_Attack.java new file mode 100644 index 00000000..95cc0b20 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Empowered_Attack.java @@ -0,0 +1,117 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Empowered_Attack extends Skill { + private static final double perb = 5; + + public Empowered_Attack() { + super(); + setMaterial(Material.BONE_MEAL); + setLore("You charge your weapon with lightning.", "Your next attack deals &f{extra}% &7extra damage", "and spreads to enemies within &f{radius} &7blocks", "for &f{ratio}% &7of the initial damage.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(10, -.2, 5, 10)); + addModifier("mana", new LinearValue(4, 1)); + addModifier("radius", new LinearValue(4, 0)); + addModifier("ratio", new LinearValue(30, 10, 30, 100)); + addModifier("extra", new LinearValue(30, 8)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + SkillResult cast = new SkillResult(data, skill); + if (!cast.isSuccessful()) + return cast; + + data.getPlayer().playSound(data.getPlayer().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 1); + new EmpoweredAttack(data, cast.getModifier("extra"), cast.getModifier("ratio"), cast.getModifier("radius")); + return cast; + } + + private void drawVector(Location loc, Vector vec) { + + double steps = vec.length() * perb; + Vector v = vec.clone().normalize().multiply((double) 1 / perb); + + for (int j = 0; j < Math.min(steps, 124); j++) + loc.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, loc.add(v), 0); + } + + public class EmpoweredAttack implements Listener { + private final PlayerData player; + private final double c, r, rad; + + public EmpoweredAttack(PlayerData player, double extra, double ratio, double radius) { + this.player = player; + this.c = 1 + extra / 100; + this.r = ratio / 100; + this.rad = radius; + + new SmallParticleEffect(player.getPlayer(), Particle.FIREWORKS_SPARK); + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> close(), 80); + } + + private void close() { + PlayerAttackEvent.getHandlerList().unregister(this); + } + + @EventHandler + public void a(PlayerAttackEvent event) { + if (event.getPlayer().equals(player.getPlayer()) && event.isWeapon()) { + close(); + + Entity target = event.getEntity(); + + /* + * play lightning effect + */ + final Location loc = target.getLocation().add(0, target.getHeight() / 2, 0); + for (int j = 0; j < 3; j++) { + Location clone = loc.clone(); + double a = random.nextDouble() * Math.PI * 2; + loc.add(Math.cos(a), 5, Math.sin(a)); + drawVector(clone, loc.clone().subtract(clone).toVector()); + } + + target.getWorld().playSound(target.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 2, .5f); + target.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, target.getLocation().add(0, target.getHeight() / 2, 0), 32, 0, 0, 0, .2); + + double sweep = event.getDamage() * r; + Location src = target.getLocation().add(0, target.getHeight() / 2, 0); + + for (Entity entity : target.getNearbyEntities(rad, rad, rad)) + if (MMOCoreUtils.canTarget(player.getPlayer(), entity)) { + drawVector(src, entity.getLocation().add(0, entity.getHeight() / 2, 0).subtract(src).toVector()); + MMOCore.plugin.damage.damage(player, (LivingEntity) entity, sweep, DamageType.SKILL, DamageType.PHYSICAL); + } + + /* + * apply damage afterwards otherwise the damage dealt to nearby + * entities scale with the extra ability damage. + */ + event.setDamage(event.getDamage() * c); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Evade.java b/src/net/Indyuce/mmocore/skill/Evade.java new file mode 100644 index 00000000..d661fa73 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Evade.java @@ -0,0 +1,80 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; + +public class Evade extends Skill { + public Evade() { + super(); + setMaterial(Material.LEATHER_BOOTS); + setLore("You become imune to damage for &8{duration} &7seconds.", "Cancels when dealing weapon damage.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(20, 0)); + addModifier("mana", new LinearValue(8, 3)); + addModifier("duration", new LinearValue(2, .3, 2, 10)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + SkillResult cast = new SkillResult(data, skill); + if (!cast.isSuccessful()) + return cast; + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1, 2); + new SmallParticleEffect(data.getPlayer(), Particle.CLOUD); + new EvadeSkill(data, cast.getModifier("duration")); + return cast; + } + + private class EvadeSkill extends BukkitRunnable implements Listener { + private final PlayerData data; + + public EvadeSkill(PlayerData data, double duration) { + this.data = data; + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + runTaskTimer(MMOCore.plugin, 0, 1); + Bukkit.getScheduler().runTaskLater(MMOCore.plugin, () -> close(), (long) (duration * 20)); + } + + private void close() { + cancel(); + EntityDamageEvent.getHandlerList().unregister(this); + } + + @EventHandler(priority = EventPriority.LOW) + public void a(EntityDamageEvent event) { + if (event.getEntity().equals(data.getPlayer())) + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void b(PlayerAttackEvent event) { + if (event.isWeapon() && !event.isCancelled() && event.getData().equals(data)) + close(); + } + + @Override + public void run() { + if (!data.isOnline() || data.getPlayer().isDead()) + close(); + else + data.getPlayer().getWorld().spawnParticle(Particle.CLOUD, data.getPlayer().getLocation().add(0, 1, 0), 0); + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Fire_Berserker.java b/src/net/Indyuce/mmocore/skill/Fire_Berserker.java new file mode 100644 index 00000000..9204f73f --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Fire_Berserker.java @@ -0,0 +1,34 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; + +public class Fire_Berserker extends Skill implements Listener { + public Fire_Berserker() { + super(); + setMaterial(Material.FLINT_AND_STEEL); + setLore("You deal &c{extra}% &7more damage when on fire."); + setPassive(); + + addModifier("extra", new LinearValue(10, 5)); + // addModifier("duration", new LinearValue(10, .1)); + // addModifier("cooldown", new LinearValue(30, 0)); + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + @EventHandler + public void a(PlayerAttackEvent event) { + PlayerData data = event.getData(); + if (event.getPlayer().getFireTicks() > 0 && data.hasSkillUnlocked(this)) + event.setDamage(event.getDamage() * (1 + data.getProfess().getSkill(this).getModifier("extra", data.getSkillLevel(this)))); + } +} diff --git a/src/net/Indyuce/mmocore/skill/Fire_Rage.java b/src/net/Indyuce/mmocore/skill/Fire_Rage.java new file mode 100644 index 00000000..4949539f --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Fire_Rage.java @@ -0,0 +1,157 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.math.VectorRotation; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Fire_Rage extends Skill { + public Fire_Rage() { + super(); + setMaterial(Material.FIRE_CHARGE); + setLore("For {duration} seconds, you slow down and are able", "to cast up to {count} fireballs by left clicking.", "", "Fireballs deal &c{damage} &7damage upon contact", "and ignite your target for &c{ignite} &7seconds.", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("duration", new LinearValue(8, 0)); + addModifier("count", new LinearValue(4, 0)); + addModifier("mana", new LinearValue(15, 1)); + addModifier("damage", new LinearValue(5, 3)); + addModifier("ignite", new LinearValue(2, .1)); + addModifier("cooldown", new LinearValue(9, -.1, 1, 5)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + SkillResult cast = new SkillResult(data, skill); + if (!cast.isSuccessful()) + return cast; + + new Rage(data, cast); + return cast; + } + + public class Rage extends BukkitRunnable implements Listener { + private final PlayerData data; + private final int count, ignite; + private final double damage; + + private int c; + private double b; + private long last = System.currentTimeMillis(); + + /* + * time the player needs to wait before firing two fireballs. + */ + private static final long timeOut = 700; + + public Rage(PlayerData data, SkillResult cast) { + this.data = data; + this.ignite = (int) (20 * cast.getModifier("ignite")); + this.damage = cast.getModifier("damage"); + c = count = (int) cast.getModifier("count"); + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + Bukkit.getScheduler().runTaskLater(MMOCore.plugin, () -> close(), (long) (cast.getModifier("duration") * 20)); + runTaskTimer(MMOCore.plugin, 0, 1); + + data.getPlayer().removePotionEffect(PotionEffectType.SLOW); + data.getPlayer().addPotionEffect(new PotionEffect(PotionEffectType.SLOW, (int) (cast.getModifier("duration") * 20), 1)); + } + + @EventHandler + public void a(PlayerInteractEvent event) { + if (event.getPlayer().equals(data.getPlayer()) && event.getAction().name().contains("LEFT_CLICK") && (System.currentTimeMillis() - last) > timeOut) { + last = System.currentTimeMillis(); + castEffect(); + fireball(c < 2); + if (c-- < 2) + close(); + } + } + + private void castEffect() { + VectorRotation rotation = new VectorRotation(data.getPlayer().getEyeLocation()); + for (double a = 0; a < Math.PI * 2; a += Math.PI / 13) { + Vector vec = rotation.rotate(new Vector(Math.cos(a), Math.sin(a), 0)).add(data.getPlayer().getEyeLocation().getDirection().multiply(.5)).multiply(.3); + data.getPlayer().getWorld().spawnParticle(Particle.FLAME, data.getPlayer().getLocation().add(0, 1.3, 0).add(data.getPlayer().getEyeLocation().getDirection().multiply(.5)), 0, vec.getX(), vec.getY(), vec.getZ(), .3); + } + } + + private void close() { + if (isCancelled()) + return; + + cancel(); + HandlerList.unregisterAll(this); + } + + private void fireball(boolean last) { + if (last) { + data.getPlayer().removePotionEffect(PotionEffectType.SLOW); + data.getPlayer().removePotionEffect(PotionEffectType.SLOW); + } + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1, last ? 0 : 1); + new BukkitRunnable() { + int j = 0; + Vector vec = data.getPlayer().getEyeLocation().getDirection(); + Location loc = data.getPlayer().getLocation().add(0, 1.3, 0); + + public void run() { + if (j++ > 40) + cancel(); + + loc.add(vec); + + if (j % 2 == 0) + loc.getWorld().playSound(loc, Sound.BLOCK_FIRE_AMBIENT, 2, 1); + loc.getWorld().spawnParticle(Particle.FLAME, loc, 4, .1, .1, .1, 0); + loc.getWorld().spawnParticle(Particle.LAVA, loc, 0); + + for (Entity target : MMOCoreUtils.getNearbyChunkEntities(loc)) + if (target.getBoundingBox().expand(.2, .2, .2).contains(loc.toVector()) && MMOCoreUtils.canTarget(data.getPlayer(), target)) { + loc.getWorld().spawnParticle(Particle.LAVA, loc, 8); + loc.getWorld().spawnParticle(Particle.FLAME, loc, 32, 0, 0, 0, .1); + loc.getWorld().playSound(loc, Sound.ENTITY_BLAZE_HURT, 2, 1); + target.setFireTicks((int) (target.getFireTicks() + ignite)); + MMOCore.plugin.damage.damage(data, (LivingEntity) target, damage, DamageType.SKILL, DamageType.PROJECTILE, DamageType.MAGICAL); + cancel(); + } + } + }.runTaskTimer(MMOCore.plugin, 0, 1); + } + + @Override + public void run() { + if (data.getPlayer().isDead() || !data.getPlayer().isOnline()) { + close(); + return; + } + + b += Math.PI / 30; + for (int j = 0; j < c; j++) { + double a = Math.PI * 2 * j / count + b; + data.getPlayer().spawnParticle(Particle.FLAME, data.getPlayer().getLocation().add(Math.cos(a) * 1.5, 1 + Math.sin(a * 1.5) * .7, Math.sin(a) * 1.5), 0); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Fire_Storm.java b/src/net/Indyuce/mmocore/skill/Fire_Storm.java new file mode 100644 index 00000000..cb70a29c --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Fire_Storm.java @@ -0,0 +1,74 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.ParabolicProjectile; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Fire_Storm extends Skill { + public Fire_Storm() { + super(); + setMaterial(Material.BLAZE_POWDER); + setLore("Casts a flurry of 6 fire projectiles onto", "nearby enemies, proritizing the initial", "target. Each projectile deals &c{damage} &7damage", "and ignite the target for &c{ignite} &7seconds.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("mana", new LinearValue(15, 2)); + addModifier("damage", new LinearValue(5, 3)); + addModifier("ignite", new LinearValue(2, .1)); + addModifier("cooldown", new LinearValue(5, -.1, 1, 5)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + TargetSkillResult cast = new TargetSkillResult(data, skill, 20); + if (!cast.isSuccessful()) + return cast; + + LivingEntity target = cast.getTarget(); + + double damage = cast.getModifier("damage"); + int ignite = (int) (20 * cast.getModifier("ignite")); + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1, 1); + new BukkitRunnable() { + int j = 0; + + @Override + public void run() { + if (j++ > 5 || data.getPlayer().isDead() || !data.getPlayer().isOnline() || target.isDead() || !data.getPlayer().getWorld().equals(target.getWorld())) { + cancel(); + return; + } + + // TODO dynamic target location + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.BLOCK_FIRE_AMBIENT, 1, 1); + new ParabolicProjectile(data.getPlayer().getLocation().add(0, 1, 0), target.getLocation().add(0, target.getHeight() / 2, 0), randomVector(data.getPlayer()), () -> { + target.getWorld().playSound(target.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_TWINKLE, 1, 2); + target.getWorld().spawnParticle(Particle.SMOKE_NORMAL, target.getLocation().add(0, target.getHeight() / 2, 0), 8, 0, 0, 0, .15); + MMOCore.plugin.damage.damage(data, target, damage, DamageType.SKILL, DamageType.PROJECTILE, DamageType.MAGICAL); + target.setFireTicks(ignite); + + }, 2, Particle.FLAME); + } + }.runTaskTimer(MMOCore.plugin, 0, 4); + return cast; + } + + private Vector randomVector(Player player) { + double a = Math.toRadians(player.getEyeLocation().getYaw() + 90); + a += (random.nextBoolean() ? 1 : -1) * (random.nextDouble() * 2 + 1) * Math.PI / 6; + return new Vector(Math.cos(a), .8, Math.sin(a)).normalize().multiply(.4); + } +} diff --git a/src/net/Indyuce/mmocore/skill/Fireball.java b/src/net/Indyuce/mmocore/skill/Fireball.java new file mode 100644 index 00000000..3cf6f5fd --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Fireball.java @@ -0,0 +1,73 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Fireball extends Skill { + public Fireball() { + super(); + setMaterial(Material.FIRE_CHARGE); + setLore("Casts a deadly fireball onto your", "target, dealing &c{damage} &7damage upon contact", "and igniting it for &c{ignite} &7seconds.", "", "Shatters into 3 blazing hot shards which stick", "to walls and explode 3 seconds later, dealing", "33% of the initial spell damage.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("mana", new LinearValue(15, 1)); + addModifier("damage", new LinearValue(5, 3)); + addModifier("ignite", new LinearValue(2, .1)); + addModifier("cooldown", new LinearValue(9, -.1, 1, 5)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + SkillResult cast = new SkillResult(data, skill); + if (!cast.isSuccessful()) + return cast; + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1, 1); + new BukkitRunnable() { + int j = 0; + Vector vec = data.getPlayer().getEyeLocation().getDirection(); + Location loc = data.getPlayer().getLocation().add(0, 1.3, 0); + + public void run() { + if (j++ > 40) { + cancel(); + return; + } + + loc.add(vec); + + loc.getWorld().playSound(loc, Sound.BLOCK_FIRE_AMBIENT, 2, 1); + // loc.getWorld().spawnParticle(Particle.FLAME, loc, 5, .12, + // .12, .12, 0); + loc.getWorld().spawnParticle(Particle.FLAME, loc, 4, .02, .02, .02, 0); + loc.getWorld().spawnParticle(Particle.LAVA, loc, 0); + // if (random.nextDouble() < .3) + // loc.getWorld().spawnParticle(Particle.LAVA, loc, 0); + + for (Entity target : MMOCoreUtils.getNearbyChunkEntities(loc)) + if (target.getBoundingBox().expand(.2, .2, .2).contains(loc.toVector()) && MMOCoreUtils.canTarget(data.getPlayer(), target)) { + loc.getWorld().spawnParticle(Particle.LAVA, loc, 8); + loc.getWorld().spawnParticle(Particle.FLAME, loc, 32, 0, 0, 0, .1); + loc.getWorld().playSound(loc, Sound.ENTITY_BLAZE_HURT, 2, 1); + target.setFireTicks((int) (target.getFireTicks() + cast.getModifier("ignite") * 20)); + MMOCore.plugin.damage.damage(data, (LivingEntity) target, cast.getModifier("damage"), DamageType.SKILL, DamageType.PROJECTILE, DamageType.MAGICAL); + cancel(); + } + } + }.runTaskTimer(MMOCore.plugin, 0, 1); + return cast; + } +} diff --git a/src/net/Indyuce/mmocore/skill/Furtive_Strike.java b/src/net/Indyuce/mmocore/skill/Furtive_Strike.java new file mode 100644 index 00000000..903fd163 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Furtive_Strike.java @@ -0,0 +1,52 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Furtive_Strike extends Skill { + public Furtive_Strike() { + super(); + setMaterial(Material.COAL); + setLore("Deals &c{damage} &7damage, increased by &c{extra}% &7if", "there are not any enemies within &c{radius} &7blocks.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(20, -.1, 5, 20)); + addModifier("mana", new LinearValue(8, 3)); + addModifier("damage", new LinearValue(5, 1.5)); + addModifier("extra", new LinearValue(50, 20)); + addModifier("radius", new LinearValue(7, 0)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + TargetSkillResult cast = new TargetSkillResult(data, skill, 3); + if (!cast.isSuccessful()) + return cast; + + LivingEntity target = cast.getTarget(); + target.getWorld().playSound(target.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 2, 1.5f); + target.getWorld().spawnParticle(Particle.CRIT, target.getLocation().add(0, target.getHeight() / 2, 0), 32, 0, 0, 0, .5); + target.getWorld().spawnParticle(Particle.SMOKE_NORMAL, target.getLocation().add(0, target.getHeight() / 2, 0), 64, 0, 0, 0, .08); + + double damage = cast.getModifier("damage"); + + double radius = cast.getModifier("radius"); + if (target.getNearbyEntities(radius, radius, radius).stream().filter(entity -> !entity.equals(data.getPlayer())).count() == 0) { + new SmallParticleEffect(target, Particle.SPELL_WITCH); + damage *= 1 + cast.getModifier("extra") / 100; + } + + MMOCore.plugin.damage.damage(data, target, damage, DamageType.SKILL, DamageType.PHYSICAL); + return cast; + } +} diff --git a/src/net/Indyuce/mmocore/skill/Greater_Healings.java b/src/net/Indyuce/mmocore/skill/Greater_Healings.java new file mode 100644 index 00000000..c1851192 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Greater_Healings.java @@ -0,0 +1,42 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.LivingEntity; + +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; + +public class Greater_Healings extends Skill { + public Greater_Healings() { + super(); + setMaterial(Material.GOLDEN_APPLE); + setLore("Instantly grants &a{heal} &7HP to the", "target. Sneak to cast it on yourself.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("mana", new LinearValue(4, 2)); + addModifier("heal", new LinearValue(10, 4)); + addModifier("cooldown", new LinearValue(9, -.1, 1, 5)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + SkillResult cast = data.getPlayer().isSneaking() ? new SkillResult(data, skill) : new TargetSkillResult(data, skill, 50); + if (!cast.isSuccessful()) + return cast; + + LivingEntity target = cast instanceof TargetSkillResult ? ((TargetSkillResult) cast).getTarget() : data.getPlayer(); + + double max = target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); + target.setHealth(Math.min(max, target.getHealth() + cast.getModifier("heal"))); + + new SmallParticleEffect(target, Particle.HEART, 1); + target.getWorld().playSound(target.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 2, 2); + return cast; + } +} diff --git a/src/net/Indyuce/mmocore/skill/Human_Shield.java b/src/net/Indyuce/mmocore/skill/Human_Shield.java new file mode 100644 index 00000000..a3dd0496 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Human_Shield.java @@ -0,0 +1,105 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; + +public class Human_Shield extends Skill { + public Human_Shield() { + super(); + setMaterial(Material.TOTEM_OF_UNDYING); + setLore("Casts a protection charm onto target ally,", "reducing damage taken by &a{reduction}%&7.", "&a{redirect}% &7of this damage is redirected to you.", "Charm is cancelled when reaching &c{low}%&7 health.", "Lasts &a{duration} &7seconds.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(18, -.3, 14, 18)); + addModifier("mana", new LinearValue(15, 1.5)); + addModifier("reduction", new LinearValue(30, 3, 30, 70)); + addModifier("redirect", new LinearValue(30, -2, 20, 30)); + addModifier("duration", new LinearValue(7, 0)); + addModifier("low", new LinearValue(10, 0)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + TargetSkillResult cast = new TargetSkillResult(data, skill, 7); + if (!cast.isSuccessful()) + return cast; + + if (!(cast.getTarget() instanceof Player)) { + cast.abort(); + return cast; + } + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.ENTITY_BLAZE_AMBIENT, 1, 1); + new HumanShield(data, (Player) cast.getTarget(), cast.getModifier("reduction"), cast.getModifier("redirect"), cast.getModifier("duration"), cast.getModifier("low")); + return cast; + } + + public class HumanShield extends BukkitRunnable implements Listener { + private final PlayerData data; + private final Player target; + private final double r, rd, d, l; + + private int j; + + public HumanShield(PlayerData data, Player target, double reduction, double redirect, double duration, double low) { + this.target = target; + this.data = data; + + r = 1 - Math.min(1, reduction / 100); + rd = redirect / 100; + d = duration * 20; + l = low / 100; + + runTaskTimer(MMOCore.plugin, 0, 1); + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + @EventHandler + public void a(EntityDamageEvent event) { + if (event.getEntity().equals(target)) { + + double damage = event.getDamage() * r; + event.setDamage(damage); + + double health = data.getPlayer().getHealth() - damage * rd; + if (health > data.getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() * l) + data.getPlayer().setHealth(health); + else { + data.getPlayer().setHealth(1); + close(); + } + } + } + + @Override + public void run() { + if (!data.isOnline() || data.getPlayer().isDead() || !target.isOnline() || target.isDead() || j++ >= d) { + close(); + return; + } + + double a = (double) j / 5; + target.getWorld().spawnParticle(Particle.VILLAGER_HAPPY, target.getLocation().add(Math.cos(a), 1 + Math.sin(a / 3) / 1.3, Math.sin(a)), 0); + } + + private void close() { + cancel(); + HandlerList.unregisterAll(this); + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Ice_Spikes.java b/src/net/Indyuce/mmocore/skill/Ice_Spikes.java new file mode 100644 index 00000000..193dbc91 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Ice_Spikes.java @@ -0,0 +1,91 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.math.Line3D; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Ice_Spikes extends Skill { + + private static final double r = 3; + + public Ice_Spikes() { + super(); + setMaterial(Material.SNOWBALL); + setLore("Ice spikes summon from the ground", "and shatters, each dealing &9{damage} &7damage", "to hit enemies and slowing them down", "for &9{slow} &7seconds.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(6, -.1, 2, 6)); + addModifier("mana", new LinearValue(20, 2)); + addModifier("damage", new LinearValue(3, 1)); + addModifier("slow", new LinearValue(4, 0)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + IceSpikesCast cast = new IceSpikesCast(data, skill); + if (!cast.isSuccessful()) + return cast; + + Location loc = cast.loc.getHitBlock().getLocation(); + + double damage = cast.getModifier("damage"); + int slow = (int) (20 * cast.getModifier("slow")); + + new BukkitRunnable() { + + int j = 0; + + @Override + public void run() { + + if (j++ > 8) { + cancel(); + return; + } + + Location loc1 = loc.clone().add(offset() * r, 0, offset() * r).add(0, 2, 0); + loc.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, loc1, 32, 0, 2, 0, 0); + loc.getWorld().spawnParticle(Particle.SNOWBALL, loc1, 32, 0, 2, 0, 0); + loc.getWorld().playSound(loc1, Sound.BLOCK_GLASS_BREAK, 2, 0); + + Line3D line = new Line3D(loc.toVector(), loc.toVector().add(new Vector(0, 1, 0))); + for (Entity entity : MMOCoreUtils.getNearbyChunkEntities(loc1)) + if (line.distanceSquared(entity.getLocation().toVector()) < 3 && Math.abs(entity.getLocation().getY() - loc1.getY()) < 10 && MMOCoreUtils.canTarget(data.getPlayer(), entity)) { + MMOCore.plugin.damage.damage(data, (LivingEntity) entity, damage, DamageType.SKILL, DamageType.MAGICAL); + ((LivingEntity) entity).addPotionEffect(new PotionEffect(PotionEffectType.SLOW, slow, 0)); + } + } + }.runTaskTimer(MMOCore.plugin, 0, 5); + return cast; + } + + private double offset() { + return random.nextDouble() * (random.nextBoolean() ? 1 : -1); + } + + private class IceSpikesCast extends SkillResult { + private RayTraceResult loc; + + public IceSpikesCast(PlayerData data, SkillInfo skill) { + super(data, skill); + if (isSuccessful() && (loc = data.getPlayer().rayTraceBlocks(30)) == null) + abort(); + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Minor_Healings.java b/src/net/Indyuce/mmocore/skill/Minor_Healings.java new file mode 100644 index 00000000..a757b95b --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Minor_Healings.java @@ -0,0 +1,42 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.LivingEntity; + +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; + +public class Minor_Healings extends Skill { + public Minor_Healings() { + super(); + setMaterial(Material.GOLDEN_APPLE); + setLore("Instantly grants &a{heal} &7HP to the", "target. Sneak to cast it on yourself.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("mana", new LinearValue(4, 2)); + addModifier("heal", new LinearValue(4, 2)); + addModifier("cooldown", new LinearValue(9, -.1, 1, 5)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + SkillResult cast = data.getPlayer().isSneaking() ? new SkillResult(data, skill) : new TargetSkillResult(data, skill, 50); + if (!cast.isSuccessful()) + return cast; + + LivingEntity target = cast instanceof TargetSkillResult ? ((TargetSkillResult) cast).getTarget() : data.getPlayer(); + + double max = target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); + target.setHealth(Math.min(max, target.getHealth() + cast.getModifier("heal"))); + + new SmallParticleEffect(target, Particle.HEART, 1); + target.getWorld().playSound(target.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 2, 2); + return cast; + } +} diff --git a/src/net/Indyuce/mmocore/skill/Power_Mark.java b/src/net/Indyuce/mmocore/skill/Power_Mark.java new file mode 100644 index 00000000..6d82a2ed --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Power_Mark.java @@ -0,0 +1,137 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.MMOCoreUtils; +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.ParabolicProjectile; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.player.stats.TemporaryStats; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.comp.rpg.damage.DamageInfo.DamageType; + +public class Power_Mark extends Skill implements Listener { + public Power_Mark() { + super(); + setMaterial(Material.WITHER_SKELETON_SKULL); + setLore("Attacking an enemy applies a deadly", "magical mark which spreads accross the", "ground. This mark accumulates &6{ratio}%", "of the damage dealt to the initial", "target over &6{duration} &7seconds.", "", "After this duration, the mark bursts, dealing", "accumulated damage to nearby enemies and", "stunning them for &6{stun}+ &7seconds.", "", "The more damage, the longer the stun.", "", "&e{cooldown}s Cooldown"); + setPassive(); + + addModifier("stun", new LinearValue(.4, .03)); + addModifier("ratio", new LinearValue(10, 5)); + addModifier("duration", new LinearValue(10, .1)); + addModifier("cooldown", new LinearValue(30, 0)); + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + @EventHandler + public void a(PlayerAttackEvent event) { + PlayerData data = event.getData(); + if (!event.isWeapon() || !data.getProfess().hasSkill(this)) + return; + + SkillResult cast = data.cast(this); + if (!cast.isSuccessful()) + return; + + new PowerMark(data, cast, event.getEntity().getLocation()); + } + + public class PowerMark extends BukkitRunnable implements Listener { + private final PlayerData data; + private final Location loc; + private final TemporaryStats stats; + + private double duration, ratio, stun; + + private double accumulate; + private int j; + + public PowerMark(PlayerData data, SkillResult cast, Location loc) { + this.data = data; + this.loc = loc; + this.stats = MMOCore.plugin.rpgUtilHandler.cachePlayerStats(data); + + loc.getWorld().playSound(loc, Sound.BLOCK_END_PORTAL_FRAME_FILL, 2, 1); + + duration = cast.getModifier("duration"); + ratio = cast.getModifier("ratio") / 100; + stun = cast.getModifier("stun"); + + runTaskTimer(MMOCore.plugin, 0, 1); + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + public void unregister() { + PlayerAttackEvent.getHandlerList().unregister(this); + cancel(); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void stackDamage(PlayerAttackEvent event) { + if (!event.isCancelled() && j < 20 * (duration - 2) && radiusCheck(event.getEntity().getLocation()) && event.getData().equals(data)) { + accumulate += event.getDamage() * ratio; + new ParabolicProjectile(event.getEntity().getLocation().add(0, event.getEntity().getHeight() / 2, 0), loc, () -> loc.getWorld().playSound(loc, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1), Color.PURPLE); + } + } + + private boolean radiusCheck(Location loc) { + return loc.getWorld().equals(this.loc.getWorld()) && loc.distanceSquared(this.loc) < 16; + } + + @Override + public void run() { + + if (j++ > duration * 20) { + unregister(); + + for (double a = 0; a < Math.PI * 2; a += Math.PI * 2 / 17) + new ParabolicProjectile(loc, loc.clone().add(6 * Math.cos(a), 0, 6 * Math.sin(a)), Particle.SPELL_WITCH); + + loc.getWorld().playSound(loc, Sound.ENTITY_GENERIC_EXPLODE, 2, 0); + loc.getWorld().spawnParticle(Particle.EXPLOSION_LARGE, loc.clone().add(0, 1, 0), 16, 2, 2, 2, 0); + loc.getWorld().spawnParticle(Particle.EXPLOSION_NORMAL, loc.clone().add(0, 1, 0), 24, 0, 0, 0, .3f); + + stun += Math.log(Math.max(1, accumulate - 10)) / 8; + + for (Entity entity : MMOCoreUtils.getNearbyChunkEntities(loc)) + if (entity.getLocation().distanceSquared(loc) < 25 && MMOCoreUtils.canTarget(data.getPlayer(), entity)) { + entity.setVelocity(format(entity.getLocation().subtract(loc).toVector().setY(0)).setY(.3)); + ((LivingEntity) entity).addPotionEffect(new PotionEffect(PotionEffectType.SLOW, (int) (stun * 20), 10, false, false)); + stats.damage((LivingEntity) entity, accumulate, DamageType.SKILL, DamageType.MAGICAL); + } + return; + } + + if (j % 2 == 0 && j > 20 * (duration - 2)) + loc.getWorld().playSound(loc, Sound.BLOCK_NOTE_BLOCK_PLING, 1, (float) (1 + (j - 20 * (duration - 2)) / 40)); + + double a = (double) j / 16; + double r = Math.sqrt(Math.min(duration * 2 - (double) j / 10, 4)) * 2; + for (double k = 0; k < Math.PI * 2; k += Math.PI * 2 / 5) + loc.getWorld().spawnParticle(Particle.SPELL_WITCH, loc.clone().add(r * Math.cos(k + a), 0, r * Math.sin(k + a)), 0); + } + } + + private Vector format(Vector vec) { + return vec.length() < .01 ? new Vector(0, 0, 0) : vec.normalize(); + } +} diff --git a/src/net/Indyuce/mmocore/skill/Sneaky_Picky.java b/src/net/Indyuce/mmocore/skill/Sneaky_Picky.java new file mode 100644 index 00000000..40097174 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Sneaky_Picky.java @@ -0,0 +1,44 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; + +import net.Indyuce.mmocore.api.event.PlayerAttackEvent; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; + +public class Sneaky_Picky extends Skill { + public Sneaky_Picky() { + super(); + setMaterial(Material.DIAMOND_SWORD); + setLore("Your attack is empowered by &f{extra}% &7when", "delivering the first blow during a fight.", "", "&9Costs {mana} {mana_name}"); + setPassive(); + + addModifier("cooldown", new LinearValue(0, 0)); + addModifier("mana", new LinearValue(8, 1)); + addModifier("extra", new LinearValue(50, 20)); + } + + @EventHandler + public void a(PlayerAttackEvent event) { + PlayerData data = event.getData(); + if (!event.isWeapon() || data.isInCombat() || !data.getProfess().hasSkill(this)) + return; + + SkillResult cast = data.cast(this); + if (!cast.isSuccessful()) + return; + + data.cast(cast.getInfo()); + + event.setDamage((1 + cast.getModifier("extra") / 100) * event.getDamage()); + LivingEntity target = (LivingEntity) event.getEntity(); + target.getWorld().spawnParticle(Particle.SMOKE_NORMAL, target.getLocation().add(0, target.getHeight() / 2, 0), 64, 0, 0, 0, .05); + target.getWorld().playSound(target.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 1, 2); + } +} diff --git a/src/net/Indyuce/mmocore/skill/Telekinesy.java b/src/net/Indyuce/mmocore/skill/Telekinesy.java new file mode 100644 index 00000000..e36d6336 --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Telekinesy.java @@ -0,0 +1,97 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.ParabolicProjectile; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; + +public class Telekinesy extends Skill { + public Telekinesy() { + super(); + setMaterial(Material.MAGENTA_DYE); + setLore("You take the control over your target", "for &9{duration} &7seconds. Left click to throw him.", "Knockback force: &f{knockback}%", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(20, -.3, 10, 20)); + addModifier("mana", new LinearValue(20, 2)); + addModifier("knockback", new LinearValue(50, 10)); + addModifier("duration", new LinearValue(3, .1, 3, 6)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + TargetSkillResult cast = new TargetSkillResult(data, skill, 7); + if (!cast.isSuccessful()) + return cast; + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 1); + new TelekinesyRunnable(data, cast.getTarget(), cast.getModifier("duration"), cast.getModifier("knockback") / 100); + return cast; + } + + public class TelekinesyRunnable extends BukkitRunnable implements Listener { + private final Entity entity; + private final PlayerData data; + + private final long duration; + private final double d, f; + + private int j; + + public TelekinesyRunnable(PlayerData data, Entity entity, double duration, double force) { + this.entity = entity; + this.data = data; + + d = data.getPlayer().getLocation().distance(entity.getLocation()); + f = force; + this.duration = (long) (20 * duration); + + runTaskTimer(MMOCore.plugin, 0, 1); + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + } + + @EventHandler + public void a(PlayerInteractEvent event) { + if (event.getPlayer().equals(data.getPlayer()) && event.getAction().name().contains("LEFT_CLICK")) { + entity.setVelocity(data.getPlayer().getEyeLocation().getDirection().multiply(1.5 * f)); + entity.getWorld().playSound(entity.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 2, 1); + entity.getWorld().spawnParticle(Particle.SPELL_WITCH, entity.getLocation().add(0, entity.getHeight() / 2, 0), 16); + close(); + } + } + + @Override + public void run() { + if (!data.isOnline() || entity.isDead() || j++ >= duration) { + close(); + return; + } + + if (j % 8 == 0) + new ParabolicProjectile(data.getPlayer().getEyeLocation(), entity.getLocation().add(0, entity.getHeight() / 2, 0), Particle.SPELL_WITCH); + + Location loc = data.getPlayer().getEyeLocation().add(data.getPlayer().getEyeLocation().getDirection().multiply(d)); + entity.setVelocity(loc.subtract(entity.getLocation().add(0, entity.getHeight() / 2, 0)).toVector().multiply(2)); + entity.setFallDistance(0); + } + + private void close() { + cancel(); + HandlerList.unregisterAll(this); + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Warp.java b/src/net/Indyuce/mmocore/skill/Warp.java new file mode 100644 index 00000000..cf8b33ec --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Warp.java @@ -0,0 +1,58 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; + +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.ParabolicProjectile; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; + +public class Warp extends Skill { + public Warp() { + super(); + setMaterial(Material.ENDER_PEARL); + setLore("Teleports you to target location.", "Max. Range: &5{range}", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(15, -.3, 2, 15)); + addModifier("mana", new LinearValue(8, 3)); + addModifier("range", new LinearValue(16, 1, 0, 100)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + WarpCast cast = new WarpCast(data, skill); + if (!cast.isSuccessful()) + return cast; + + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1, 2); + + Location loc = cast.block.getLocation().add(0, 1, 0); + loc.setYaw(data.getPlayer().getLocation().getYaw()); + loc.setPitch(data.getPlayer().getLocation().getPitch()); + + new ParabolicProjectile(data.getPlayer().getLocation().add(0, 1, 0), loc.clone().add(0, 1, 0), () -> { + if (data.getPlayer().isOnline() && !data.getPlayer().isDead()) { + data.getPlayer().teleport(loc); + data.getPlayer().getWorld().spawnParticle(Particle.EXPLOSION_LARGE, data.getPlayer().getLocation().add(0, 1, 0), 0); + data.getPlayer().getWorld().spawnParticle(Particle.SPELL_INSTANT, data.getPlayer().getLocation().add(0, 1, 0), 32, 0, 0, 0, .1); + data.getPlayer().getWorld().playSound(data.getPlayer().getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1, 1); + } + }, 2, Particle.SPELL_INSTANT); + return cast; + } + + private class WarpCast extends SkillResult { + private Block block; + + public WarpCast(PlayerData data, SkillInfo skill) { + super(data, skill); + if (isSuccessful() && (block = data.getPlayer().getTargetBlock(null, (int) getModifier("range"))) == null) + abort(); + } + } +} diff --git a/src/net/Indyuce/mmocore/skill/Weaken.java b/src/net/Indyuce/mmocore/skill/Weaken.java new file mode 100644 index 00000000..a521334b --- /dev/null +++ b/src/net/Indyuce/mmocore/skill/Weaken.java @@ -0,0 +1,77 @@ +package net.Indyuce.mmocore.skill; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.util.Vector; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.math.formula.LinearValue; +import net.Indyuce.mmocore.api.math.particle.ParabolicProjectile; +import net.Indyuce.mmocore.api.math.particle.SmallParticleEffect; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.api.skill.Skill; +import net.Indyuce.mmocore.api.skill.SkillResult; +import net.Indyuce.mmocore.api.skill.TargetSkillResult; + +public class Weaken extends Skill { + public Weaken() { + super(); + setMaterial(Material.MAGENTA_DYE); + setLore("The target is weakened for", "&8{duration} &7seconds and is dealt", "&7extra &8{ratio}% &7damage.", "", "&e{cooldown}s Cooldown", "&9Costs {mana} {mana_name}"); + + addModifier("cooldown", new LinearValue(20, -.1, 5, 20)); + addModifier("mana", new LinearValue(4, 1)); + addModifier("ratio", new LinearValue(30, 3)); + addModifier("duration", new LinearValue(10, -.1, 5, 10)); + } + + @Override + public SkillResult whenCast(PlayerData data, SkillInfo skill) { + TargetSkillResult cast = new TargetSkillResult(data, skill, 7); + if (!cast.isSuccessful()) + return cast; + + LivingEntity target = cast.getTarget(); + new ParabolicProjectile(data.getPlayer().getLocation().add(0, 1, 0), target.getLocation().add(0, target.getHeight() / 2, 0), randomVector(data.getPlayer()), () -> { + if (!target.isDead()) + new Weakened(target, cast.getModifier("ratio"), cast.getModifier("duration")); + }, 2, Particle.SPELL_WITCH); + return cast; + } + + private Vector randomVector(Player player) { + double a = Math.toRadians(player.getEyeLocation().getYaw() + 90); + a += (random.nextBoolean() ? 1 : -1) * (random.nextDouble() + .5) * Math.PI / 6; + return new Vector(Math.cos(a), .8, Math.sin(a)).normalize().multiply(.4); + } + + public class Weakened implements Listener { + private final Entity entity; + private final double c; + + public Weakened(Entity entity, double ratio, double duration) { + this.entity = entity; + this.c = 1 + ratio / 100; + + new SmallParticleEffect(entity, Particle.SPELL_WITCH); + + Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); + Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> EntityDamageByEntityEvent.getHandlerList().unregister(this), (int) duration * 20); + } + + @EventHandler + public void a(EntityDamageByEntityEvent event) { + if (event.getEntity().equals(entity)) { + event.getEntity().getWorld().spawnParticle(Particle.SPELL_WITCH, entity.getLocation().add(0, entity.getHeight() / 2, 0), 16, .5, .5, .5, 0); + event.setDamage(event.getDamage() * c); + } + } + } +} diff --git a/src/net/Indyuce/mmocore/version/ServerVersion.java b/src/net/Indyuce/mmocore/version/ServerVersion.java new file mode 100644 index 00000000..0daed96e --- /dev/null +++ b/src/net/Indyuce/mmocore/version/ServerVersion.java @@ -0,0 +1,49 @@ +package net.Indyuce.mmocore.version; + +import net.Indyuce.mmocore.version.texture.CustomModelDataHandler; +import net.Indyuce.mmocore.version.texture.TextureByDurabilityHandler; +import net.Indyuce.mmocore.version.texture.TextureHandler; + +public class ServerVersion { + private final String version; + private final int[] integers; + private final TextureHandler textureHandler; + + public ServerVersion(Class clazz) { + this.version = clazz.getPackage().getName().replace(".", ",").split(",")[3]; + String[] split = version.substring(1).split("\\_"); + this.integers = new int[] { Integer.parseInt(split[0]), Integer.parseInt(split[1]) }; + + textureHandler = isBelowOrEqual(1, 13) ? new TextureByDurabilityHandler() : new CustomModelDataHandler(); + } + + public boolean isBelowOrEqual(int... version) { + return version[0] > integers[0] ? true : version[1] >= integers[1]; + } + + public boolean isStrictlyHigher(int... version) { + return version[0] < integers[0] ? true : version[1] < integers[1]; + // return !isBelowOrEqual(version); + } + + public int getRevisionNumber() { + return Integer.parseInt(version.split("\\_")[2].replaceAll("[^0-9]", "")); + } + + public int[] toNumbers() { + return integers; + } + + public boolean isTextureByDurability() { + return textureHandler instanceof TextureByDurabilityHandler; + } + + public TextureHandler getTextureHandler() { + return textureHandler; + } + + @Override + public String toString() { + return version; + } +} diff --git a/src/net/Indyuce/mmocore/version/SpigotPlugin.java b/src/net/Indyuce/mmocore/version/SpigotPlugin.java new file mode 100644 index 00000000..fcead616 --- /dev/null +++ b/src/net/Indyuce/mmocore/version/SpigotPlugin.java @@ -0,0 +1,74 @@ +package net.Indyuce.mmocore.version; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; + +import javax.net.ssl.HttpsURLConnection; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.java.JavaPlugin; + +public class SpigotPlugin { + private JavaPlugin plugin; + private int id; + private String version; + + public SpigotPlugin(int id, JavaPlugin plugin) { + this.plugin = plugin; + this.id = id; + } + + /* + * the request is executed asynchronously as not to block the main thread. + */ + public void checkForUpdate() { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + HttpsURLConnection connection = (HttpsURLConnection) new URL("https://api.spigotmc.org/legacy/update.php?resource=" + id).openConnection(); + connection.setRequestMethod("GET"); + version = new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine(); + } catch (IOException e) { + plugin.getLogger().log(Level.INFO, "Couldn't check the latest plugin version :/"); + return; + } + + if (version.equals(plugin.getDescription().getVersion())) + return; + + plugin.getLogger().log(Level.INFO, "A new build is available: " + version + " (you are running " + plugin.getDescription().getVersion() + ")"); + plugin.getLogger().log(Level.INFO, "Download it here: " + getResourceUrl()); + + /* + * registers the event to notify op players when they join only if + * the corresponding option is enabled + */ + if (plugin.getConfig().getBoolean("update-notify")) + Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getPluginManager().registerEvents(new Listener() { + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (player.hasPermission(plugin.getName().toLowerCase() + ".update-notify")) + getOutOfDateMessage().forEach(msg -> player.sendMessage(ChatColor.translateAlternateColorCodes('&', msg))); + } + }, plugin)); + }); + } + + private List getOutOfDateMessage() { + return Arrays.asList("&8--------------------------------------------", "&a" + plugin.getName() + " " + version + " is available!", "&a" + getResourceUrl(), "&7&oYou can disable this notification in the config file.", "&8--------------------------------------------"); + } + public String getResourceUrl() { + return "https://www.spigotmc.org/resources/" + id + "/"; + } +} diff --git a/src/net/Indyuce/mmocore/version/nms/ItemTag.java b/src/net/Indyuce/mmocore/version/nms/ItemTag.java new file mode 100644 index 00000000..2d432385 --- /dev/null +++ b/src/net/Indyuce/mmocore/version/nms/ItemTag.java @@ -0,0 +1,19 @@ +package net.Indyuce.mmocore.version.nms; + +public class ItemTag { + private final String path; + private final Object value; + + public ItemTag(String path, Object value) { + this.path = path; + this.value = value; + } + + public String getPath() { + return path; + } + + public Object getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/net/Indyuce/mmocore/version/nms/NMSHandler.java b/src/net/Indyuce/mmocore/version/nms/NMSHandler.java new file mode 100644 index 00000000..d9b242ce --- /dev/null +++ b/src/net/Indyuce/mmocore/version/nms/NMSHandler.java @@ -0,0 +1,37 @@ +package net.Indyuce.mmocore.version.nms; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.item.NBTItem; + +public interface NMSHandler { + NBTItem getNBTItem(ItemStack item); + + void sendTitle(Player player, String title, String subtitle, int fadeIn, int ticks, int fadeOut); + + void sendActionBar(Player player, String message); + + void sendJson(Player player, String message); + + int getNextContainerId(Player player); + + void handleInventoryCloseEvent(Player player); + + void sendPacketOpenWindow(Player player, int containerId); + + void sendPacketCloseWindow(Player player, int containerId); + + void setActiveContainerDefault(Player player); + + void setActiveContainer(Player player, Object container); + + void setActiveContainerId(Object container, int containerId); + + void addActiveContainerSlotListener(Object container, Player player); + + Inventory toBukkitInventory(Object container); + + Object newContainerAnvil(Player player); +} diff --git a/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_13_R1.java b/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_13_R1.java new file mode 100644 index 00000000..fd671dab --- /dev/null +++ b/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_13_R1.java @@ -0,0 +1,176 @@ +package net.Indyuce.mmocore.version.nms; + +import java.util.Set; + +import org.bukkit.craftbukkit.v1_13_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_13_R1.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_13_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; + +import net.Indyuce.mmocore.api.item.NBTItem; +import net.minecraft.server.v1_13_R1.BlockPosition; +import net.minecraft.server.v1_13_R1.Blocks; +import net.minecraft.server.v1_13_R1.ChatMessage; +import net.minecraft.server.v1_13_R1.ChatMessageType; +import net.minecraft.server.v1_13_R1.Container; +import net.minecraft.server.v1_13_R1.ContainerAnvil; +import net.minecraft.server.v1_13_R1.EntityHuman; +import net.minecraft.server.v1_13_R1.IChatBaseComponent.ChatSerializer; +import net.minecraft.server.v1_13_R1.ItemStack; +import net.minecraft.server.v1_13_R1.NBTTagCompound; +import net.minecraft.server.v1_13_R1.PacketPlayOutChat; +import net.minecraft.server.v1_13_R1.PacketPlayOutCloseWindow; +import net.minecraft.server.v1_13_R1.PacketPlayOutOpenWindow; +import net.minecraft.server.v1_13_R1.PacketPlayOutTitle; +import net.minecraft.server.v1_13_R1.PacketPlayOutTitle.EnumTitleAction; + +public class NMSHandler_1_13_R1 implements NMSHandler { + @Override + public void sendJson(Player player, String message) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutChat(ChatSerializer.a(message))); + } + + @Override + public void sendTitle(Player player, String msgTitle, String msgSubTitle, int fadeIn, int ticks, int fadeOut) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.TITLE, ChatSerializer.a("{\"text\": \"" + msgTitle + "\"}"))); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.SUBTITLE, ChatSerializer.a("{\"text\": \"" + msgSubTitle + "\"}"))); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.TIMES, null, fadeIn, ticks, fadeOut)); + } + + @Override + public void sendActionBar(Player player, String message) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutChat(ChatSerializer.a("{\"text\": \"" + message + "\"}"), ChatMessageType.GAME_INFO)); + } + + @Override + public int getNextContainerId(Player player) { + return ((CraftPlayer) player).getHandle().nextContainerCounter(); + } + + @Override + public void handleInventoryCloseEvent(Player player) { + CraftEventFactory.handleInventoryCloseEvent(((CraftPlayer) player).getHandle()); + } + + @Override + public void sendPacketOpenWindow(Player player, int containerId) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutOpenWindow(containerId, "minecraft:anvil", new ChatMessage(Blocks.ANVIL.a() + ".name"))); + } + + @Override + public void sendPacketCloseWindow(Player player, int containerId) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutCloseWindow(containerId)); + } + + @Override + public void setActiveContainerDefault(Player player) { + ((CraftPlayer) player).getHandle().activeContainer = ((CraftPlayer) player).getHandle().defaultContainer; + } + + @Override + public void setActiveContainer(Player player, Object container) { + ((CraftPlayer) player).getHandle().activeContainer = (Container) container; + } + + @Override + public void setActiveContainerId(Object container, int containerId) { + ((Container) container).windowId = containerId; + } + + @Override + public void addActiveContainerSlotListener(Object container, Player player) { + ((Container) container).addSlotListener(((CraftPlayer) player).getHandle()); + } + + @Override + public Inventory toBukkitInventory(Object container) { + return ((Container) container).getBukkitView().getTopInventory(); + } + + @Override + public Object newContainerAnvil(Player player) { + return new AnvilContainer(((CraftPlayer) player).getHandle()); + } + + private class AnvilContainer extends ContainerAnvil { + public AnvilContainer(EntityHuman entityhuman) { + super(entityhuman.inventory, entityhuman.world, new BlockPosition(0, 0, 0), entityhuman); + this.checkReachable = false; + } + } + + @Override + public NBTItem getNBTItem(org.bukkit.inventory.ItemStack item) { + return new NBTItem_v1_13_1(item); + } + + public class NBTItem_v1_13_1 extends NBTItem { + private final ItemStack nms; + private final NBTTagCompound compound; + + public NBTItem_v1_13_1(org.bukkit.inventory.ItemStack item) { + super(item); + nms = CraftItemStack.asNMSCopy(item); + compound = nms.hasTag() ? nms.getTag() : new NBTTagCompound(); + } + + @Override + public String getString(String path) { + return compound.getString(path); + } + + @Override + public boolean has(String path) { + return compound.hasKey(path); + } + + @Override + public boolean getBoolean(String path) { + return compound.getBoolean(path); + } + + @Override + public double getDouble(String path) { + return compound.getDouble(path); + } + + @Override + public int getInt(String path) { + return compound.getInt(path); + } + + @Override + public NBTItem add(ItemTag... tags) { + for (ItemTag tag : tags) { + if (tag.getValue() instanceof Boolean) + compound.setBoolean(tag.getPath(), (boolean) tag.getValue()); + else if (tag.getValue() instanceof Double) + compound.setDouble(tag.getPath(), (double) tag.getValue()); + else if (tag.getValue() instanceof String) + compound.setString(tag.getPath(), (String) tag.getValue()); + else if (tag.getValue() instanceof Integer) + compound.setInt(tag.getPath(), (int) tag.getValue()); + } + return this; + } + + @Override + public NBTItem remove(String... paths) { + for (String path : paths) + compound.remove(path); + return this; + } + + @Override + public Set getTags() { + return compound.getKeys(); + } + + @Override + public org.bukkit.inventory.ItemStack toItem() { + nms.setTag(compound); + return CraftItemStack.asBukkitCopy(nms); + } + } +} diff --git a/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_13_R2.java b/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_13_R2.java new file mode 100644 index 00000000..74dc7c1c --- /dev/null +++ b/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_13_R2.java @@ -0,0 +1,177 @@ +package net.Indyuce.mmocore.version.nms; + +import java.util.Set; + +import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_13_R2.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; + +import net.Indyuce.mmocore.api.item.NBTItem; +import net.minecraft.server.v1_13_R2.BlockPosition; +import net.minecraft.server.v1_13_R2.Blocks; +import net.minecraft.server.v1_13_R2.ChatMessage; +import net.minecraft.server.v1_13_R2.ChatMessageType; +import net.minecraft.server.v1_13_R2.Container; +import net.minecraft.server.v1_13_R2.ContainerAnvil; +import net.minecraft.server.v1_13_R2.EntityHuman; +import net.minecraft.server.v1_13_R2.IChatBaseComponent.ChatSerializer; +import net.minecraft.server.v1_13_R2.ItemStack; +import net.minecraft.server.v1_13_R2.NBTTagCompound; +import net.minecraft.server.v1_13_R2.PacketPlayOutChat; +import net.minecraft.server.v1_13_R2.PacketPlayOutCloseWindow; +import net.minecraft.server.v1_13_R2.PacketPlayOutOpenWindow; +import net.minecraft.server.v1_13_R2.PacketPlayOutTitle; +import net.minecraft.server.v1_13_R2.PacketPlayOutTitle.EnumTitleAction; + +public class NMSHandler_1_13_R2 implements NMSHandler { + @Override + public void sendJson(Player player, String message) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutChat(ChatSerializer.a(message))); + } + + @Override + public void sendTitle(Player player, String msgTitle, String msgSubTitle, int fadeIn, int ticks, int fadeOut) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.TITLE, ChatSerializer.a("{\"text\": \"" + msgTitle + "\"}"))); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.SUBTITLE, ChatSerializer.a("{\"text\": \"" + msgSubTitle + "\"}"))); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.TIMES, null, fadeIn, ticks, fadeOut)); + } + + @Override + public void sendActionBar(Player player, String message) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutChat(ChatSerializer.a("{\"text\": \"" + message + "\"}"), ChatMessageType.GAME_INFO)); + } + + @Override + public int getNextContainerId(Player player) { + return ((CraftPlayer) player).getHandle().nextContainerCounter(); + } + + @Override + public void handleInventoryCloseEvent(Player player) { + CraftEventFactory.handleInventoryCloseEvent(((CraftPlayer) player).getHandle()); + } + + @Override + public void sendPacketOpenWindow(Player player, int containerId) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutOpenWindow(containerId, "minecraft:anvil", new ChatMessage(Blocks.ANVIL.a() + ".name"))); + } + + @Override + public void sendPacketCloseWindow(Player player, int containerId) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutCloseWindow(containerId)); + } + + @Override + public void setActiveContainerDefault(Player player) { + ((CraftPlayer) player).getHandle().activeContainer = ((CraftPlayer) player).getHandle().defaultContainer; + } + + @Override + public void setActiveContainer(Player player, Object container) { + ((CraftPlayer) player).getHandle().activeContainer = (Container) container; + } + + @Override + public void setActiveContainerId(Object container, int containerId) { + ((Container) container).windowId = containerId; + } + + @Override + public void addActiveContainerSlotListener(Object container, Player player) { + ((Container) container).addSlotListener(((CraftPlayer) player).getHandle()); + } + + @Override + public Inventory toBukkitInventory(Object container) { + return ((Container) container).getBukkitView().getTopInventory(); + } + + @Override + public Object newContainerAnvil(Player player) { + return new AnvilContainer(((CraftPlayer) player).getHandle()); + } + + private class AnvilContainer extends ContainerAnvil { + public AnvilContainer(EntityHuman entityhuman) { + super(entityhuman.inventory, entityhuman.world, new BlockPosition(0, 0, 0), entityhuman); + this.checkReachable = false; + } + } + + @Override + public NBTItem getNBTItem(org.bukkit.inventory.ItemStack item) { + return new NBTItem_v1_13_2(item); + } + + public class NBTItem_v1_13_2 extends NBTItem { + private final ItemStack nms; + private final NBTTagCompound compound; + + public NBTItem_v1_13_2(org.bukkit.inventory.ItemStack item) { + super(item); + + nms = CraftItemStack.asNMSCopy(item); + compound = nms.hasTag() ? nms.getTag() : new NBTTagCompound(); + } + + @Override + public String getString(String path) { + return compound.getString(path); + } + + @Override + public boolean has(String path) { + return compound.hasKey(path); + } + + @Override + public boolean getBoolean(String path) { + return compound.getBoolean(path); + } + + @Override + public double getDouble(String path) { + return compound.getDouble(path); + } + + @Override + public int getInt(String path) { + return compound.getInt(path); + } + + @Override + public NBTItem add(ItemTag... tags) { + for (ItemTag tag : tags) { + if (tag.getValue() instanceof Boolean) + compound.setBoolean(tag.getPath(), (boolean) tag.getValue()); + else if (tag.getValue() instanceof Double) + compound.setDouble(tag.getPath(), (double) tag.getValue()); + else if (tag.getValue() instanceof String) + compound.setString(tag.getPath(), (String) tag.getValue()); + else if (tag.getValue() instanceof Integer) + compound.setInt(tag.getPath(), (int) tag.getValue()); + } + return this; + } + + @Override + public NBTItem remove(String... paths) { + for (String path : paths) + compound.remove(path); + return this; + } + + @Override + public Set getTags() { + return compound.getKeys(); + } + + @Override + public org.bukkit.inventory.ItemStack toItem() { + nms.setTag(compound); + return CraftItemStack.asBukkitCopy(nms); + } + } +} diff --git a/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_14_R1.java b/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_14_R1.java new file mode 100644 index 00000000..77aeca92 --- /dev/null +++ b/src/net/Indyuce/mmocore/version/nms/NMSHandler_1_14_R1.java @@ -0,0 +1,200 @@ +package net.Indyuce.mmocore.version.nms; + +import java.lang.reflect.Field; +import java.util.Set; + +import org.bukkit.craftbukkit.libs.org.apache.commons.lang3.reflect.FieldUtils; +import org.bukkit.craftbukkit.v1_14_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_14_R1.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; + +import net.Indyuce.mmocore.api.item.NBTItem; +import net.minecraft.server.v1_14_R1.BlockPosition; +import net.minecraft.server.v1_14_R1.ChatMessage; +import net.minecraft.server.v1_14_R1.ChatMessageType; +import net.minecraft.server.v1_14_R1.Container; +import net.minecraft.server.v1_14_R1.ContainerAccess; +import net.minecraft.server.v1_14_R1.ContainerAnvil; +import net.minecraft.server.v1_14_R1.Containers; +import net.minecraft.server.v1_14_R1.EntityPlayer; +import net.minecraft.server.v1_14_R1.IChatBaseComponent.ChatSerializer; +import net.minecraft.server.v1_14_R1.ItemStack; +import net.minecraft.server.v1_14_R1.NBTTagCompound; +import net.minecraft.server.v1_14_R1.PacketPlayOutChat; +import net.minecraft.server.v1_14_R1.PacketPlayOutCloseWindow; +import net.minecraft.server.v1_14_R1.PacketPlayOutOpenWindow; +import net.minecraft.server.v1_14_R1.PacketPlayOutTitle; +import net.minecraft.server.v1_14_R1.PacketPlayOutTitle.EnumTitleAction; + +public class NMSHandler_1_14_R1 implements NMSHandler { + @Override + public void sendJson(Player player, String message) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutChat(ChatSerializer.a(message))); + } + + @Override + public void sendTitle(Player player, String msgTitle, String msgSubTitle, int fadeIn, int ticks, int fadeOut) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.TITLE, ChatSerializer.a("{\"text\": \"" + msgTitle + "\"}"))); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.SUBTITLE, ChatSerializer.a("{\"text\": \"" + msgSubTitle + "\"}"))); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTitle(EnumTitleAction.TIMES, null, fadeIn, ticks, fadeOut)); + } + + @Override + public void sendActionBar(Player player, String message) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutChat(ChatSerializer.a("{\"text\": \"" + message + "\"}"), ChatMessageType.GAME_INFO)); + } + + @Override + public int getNextContainerId(Player player) { + return toNMS(player).nextContainerCounter(); + } + + @Override + public void handleInventoryCloseEvent(Player player) { + CraftEventFactory.handleInventoryCloseEvent(toNMS(player)); + } + + @Override + public void sendPacketOpenWindow(Player player, int containerId) { + toNMS(player).playerConnection.sendPacket(new PacketPlayOutOpenWindow(containerId, Containers.ANVIL, new ChatMessage("Repair & Name"))); + } + + @Override + public void sendPacketCloseWindow(Player player, int containerId) { + toNMS(player).playerConnection.sendPacket(new PacketPlayOutCloseWindow(containerId)); + } + + @Override + public void setActiveContainerDefault(Player player) { + toNMS(player).activeContainer = toNMS(player).defaultContainer; + } + + @Override + public void setActiveContainer(Player player, Object container) { + toNMS(player).activeContainer = (Container) container; + } + + @Override + public void setActiveContainerId(Object container, int containerId) { + Field field = null; + + try { + field = Container.class.getField("windowId"); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + + FieldUtils.removeFinalModifier(field); + + try { + FieldUtils.writeField(field, container, containerId); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + @Override + public void addActiveContainerSlotListener(Object container, Player player) { + ((Container) container).addSlotListener(toNMS(player)); + } + + @Override + public Inventory toBukkitInventory(Object container) { + return ((Container) container).getBukkitView().getTopInventory(); + } + + @Override + public Object newContainerAnvil(Player player) { + return new AnvilContainer(player); + } + + private EntityPlayer toNMS(Player player) { + return ((CraftPlayer) player).getHandle(); + } + + private class AnvilContainer extends ContainerAnvil { + public AnvilContainer(Player player) { + super(getNextContainerId(player), ((CraftPlayer) player).getHandle().inventory, ContainerAccess.at(((CraftWorld) player.getWorld()).getHandle(), new BlockPosition(0, 0, 0))); + this.checkReachable = false; + setTitle(new ChatMessage("Repair & Name")); + } + } + + @Override + public NBTItem getNBTItem(org.bukkit.inventory.ItemStack item) { + return new NBTItem_v1_13_2(item); + } + + public class NBTItem_v1_13_2 extends NBTItem { + private final ItemStack nms; + private final NBTTagCompound compound; + + public NBTItem_v1_13_2(org.bukkit.inventory.ItemStack item) { + super(item); + + nms = CraftItemStack.asNMSCopy(item); + compound = nms.hasTag() ? nms.getTag() : new NBTTagCompound(); + } + + @Override + public String getString(String path) { + return compound.getString(path); + } + + @Override + public boolean has(String path) { + return compound.hasKey(path); + } + + @Override + public boolean getBoolean(String path) { + return compound.getBoolean(path); + } + + @Override + public double getDouble(String path) { + return compound.getDouble(path); + } + + @Override + public int getInt(String path) { + return compound.getInt(path); + } + + @Override + public NBTItem add(ItemTag... tags) { + for (ItemTag tag : tags) { + if (tag.getValue() instanceof Boolean) + compound.setBoolean(tag.getPath(), (boolean) tag.getValue()); + else if (tag.getValue() instanceof Double) + compound.setDouble(tag.getPath(), (double) tag.getValue()); + else if (tag.getValue() instanceof String) + compound.setString(tag.getPath(), (String) tag.getValue()); + else if (tag.getValue() instanceof Integer) + compound.setInt(tag.getPath(), (int) tag.getValue()); + } + return this; + } + + @Override + public NBTItem remove(String... paths) { + for (String path : paths) + compound.remove(path); + return this; + } + + @Override + public Set getTags() { + return compound.getKeys(); + } + + @Override + public org.bukkit.inventory.ItemStack toItem() { + nms.setTag(compound); + return CraftItemStack.asBukkitCopy(nms); + } + } +} diff --git a/src/net/Indyuce/mmocore/version/texture/CustomModelDataHandler.java b/src/net/Indyuce/mmocore/version/texture/CustomModelDataHandler.java new file mode 100644 index 00000000..34fceeb8 --- /dev/null +++ b/src/net/Indyuce/mmocore/version/texture/CustomModelDataHandler.java @@ -0,0 +1,25 @@ +package net.Indyuce.mmocore.version.texture; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class CustomModelDataHandler implements TextureHandler { + @Override + public NBTItem copyTexture(NBTItem item) { + return MMOCore.plugin.nms.getNBTItem(new ItemStack(item.getItem().getType())).add(new ItemTag("CustomModelData", item.getInt("CustomModelData"))); + } + + @Override + public ItemStack textureItem(Material material, int model) { + return MMOCore.plugin.nms.getNBTItem(new ItemStack(material)).add(new ItemTag("CustomModelData", model)).toItem(); + } + + @Override + public NBTItem applyTexture(NBTItem item, int model) { + return item.add(new ItemTag("CustomModelData", model)); + } +} diff --git a/src/net/Indyuce/mmocore/version/texture/TextureByDurabilityHandler.java b/src/net/Indyuce/mmocore/version/texture/TextureByDurabilityHandler.java new file mode 100644 index 00000000..3c149b2a --- /dev/null +++ b/src/net/Indyuce/mmocore/version/texture/TextureByDurabilityHandler.java @@ -0,0 +1,25 @@ +package net.Indyuce.mmocore.version.texture; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.item.NBTItem; +import net.Indyuce.mmocore.version.nms.ItemTag; + +public class TextureByDurabilityHandler implements TextureHandler { + @Override + public NBTItem copyTexture(NBTItem item) { + return MMOCore.plugin.nms.getNBTItem(new ItemStack(item.getItem().getType())).add(new ItemTag("Damage", item.getInt("Damage"))); + } + + @Override + public ItemStack textureItem(Material material, int model) { + return MMOCore.plugin.nms.getNBTItem(new ItemStack(material)).add(new ItemTag("Damage", model), new ItemTag("Unbreakable", true)).toItem(); + } + + @Override + public NBTItem applyTexture(NBTItem item, int model) { + return item.add(new ItemTag("Damage", model)); + } +} diff --git a/src/net/Indyuce/mmocore/version/texture/TextureHandler.java b/src/net/Indyuce/mmocore/version/texture/TextureHandler.java new file mode 100644 index 00000000..1225f68c --- /dev/null +++ b/src/net/Indyuce/mmocore/version/texture/TextureHandler.java @@ -0,0 +1,14 @@ +package net.Indyuce.mmocore.version.texture; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.item.NBTItem; + +public interface TextureHandler { + public NBTItem copyTexture(NBTItem item); + + public ItemStack textureItem(Material material, int model); + + public NBTItem applyTexture(NBTItem item, int model); +}