diff --git a/.gitignore b/.gitignore index f2e64c4b..bde8e30c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .idea/ out/ *.iml +lib/ ################# ## Eclipse diff --git a/builder.xml b/builder.xml new file mode 100644 index 00000000..777cfc50 --- /dev/null +++ b/builder.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/attributes.yml b/configs/attributes.yml similarity index 50% rename from attributes.yml rename to configs/attributes.yml index 2f390be1..3d0ddc23 100644 --- a/attributes.yml +++ b/configs/attributes.yml @@ -4,14 +4,29 @@ # http://dev.bukkit.org/bukkit-plugins/skillapi/pages/attributes/ # # List of available stats to modify: -# health - the max health of the player -# mana - the amount of mana the player has -# mana-regen - how much mana the player regenerates over time -# physical-damage - damage dealt without using skills -# physical-defense - damage taken from non-skill attacks -# skill-damage - damage dealt with skills -# skill-defense - damage taken from skills -# move-speed - movement speed of the player +# armor | [PREM, 1.9+] Vanilla damage mitigation +# armor-toughness | [PREM, 1.9+] Secondary vanilla damage mitigation +# attack-speed | [PREM, 1.9+] Weapon recharge time +# cooldown | [PREM] Modifies skill cooldowns +# defense- | [PREM] Reduces damage taken from various damage sources. +# | See the DamageCause docs to see supported types. +# | Use lower-case versions of it, such as "defense-block_explosion". +# exp | [PREM] increases all class experience gained +# health | The max health of the player +# hunger | [PREM] Increases how long hunger lasts. This attribute is always based off of a base value of 1. A resulting value of 2 would double how long the hunger bar lasts, for example. +# hunger-heal | [PREM] Increases how much you heal while satiated +# knockback-resist | [PREM, 1.9+] Probability of resisting knockback as a decimal (1.0 is 100% change to resist) +# luck | [PREM, 1.9+] loot table chances +# mana | The max mana of the player +# mana-regen | The amount of mana regeneration the player has +# move-speed | The movement speed of the player +# physical-damage | The amount of damage done by physical (basic or projectile) attacks +# physical-defense | The amount of damage taken by physical (basic or projectile) attacks +# skill-damage | The amount of damage done by skills +# skill-defense | The amount of damage taken by skills + +# skill-damage- | [PREM] The amount of damage done by skills with the specified classification +# skill-defense- | [PREM] The amount of damage taken by skills with the specified classification vitality: display: 'Vitality' max: 999 diff --git a/config.yml b/configs/config.yml similarity index 64% rename from config.yml rename to configs/config.yml index 15267f28..de419500 100644 --- a/config.yml +++ b/configs/config.yml @@ -12,7 +12,12 @@ Accounts: # The main class group used for GUI displays main-class-group: class # - # Currently disabled, ignore this + # Allows one "account" per class, providing the "/class switch" + # command to change between them. This is an alternative to + # using "/class acc " which professes as a class at the same + # time and makes it easier to correlate an account to a class. + # This will not work well if you have one common class + # that turns into the rest. one-per-class: false # # The max number of accounts a normal user can use @@ -56,6 +61,15 @@ Targeting: # - world1 # - world2 player-ally: false + # + # Whether or not to check for player allies via Parties + parties-ally: false + # + # Whether or not for skills to affect NPCs + affect-npcs: false + # + # Whether or not for skills to affect armor stands + affect-armor-stands: false # Saving: # @@ -75,6 +89,11 @@ Saving: database: plugins username: username password: password + # Time to wait on loading data from the SQL database in ticks. + # Can be used to give time for other servers to synchronize data. Note: + # this does not apply when loading player data on server startup since + # players wouldn't be coming from another server. + delay: 0 # Classes: # @@ -166,10 +185,22 @@ Items: # The text used for providing attributes attribute-text: '{attr}: ' # - # How many players to check for the requirements each tick - # This should be increased on larger servers to prevent - # large delays before the same player is checked again - players-per-check: 1 + # The slots to check for items in and apply requirements to. + # This does not include held item, as that fluctuates and is assumed + # Slots are based on the following: + # 0-8 = hot bar + # 9-35 = main inventory + # 36 = boots + # 37 = leggings + # 38 = chestplate + # 39 = helmet + # 40 = off hand + slots: + - 36 + - 37 + - 38 + - 39 + - 40 # GUI: # @@ -246,12 +277,117 @@ GUI: # This always appears as {level} {text} class-level-text: 'Level' # - # Whether or not to use map trees instead of the regular ones - # View map.yml for further customization for this option - # Also add more schemes by adding folders in the "img" folder - # Allow for both map skill trees and regular ones by setting - # this to "partial" - map-tree-enabled: false + # Whether or not to append text to skill icons to show what type of item the skill is bound to + show-binds: false + # + # Text to show for bound materials + show-bind-text: 'Bound to {material}' +# +Casting: + # + # Whether or not the main casting option is enabled + enabled: false + # + # Whether or not to use the mult-bar implementation. + # When enabled: + # - Left/Right clicking on item opens skill bars + # - Skills assigned through tree + # - Preview when hovering in the skill bar + # - Limited number of skills can be put on skill bars + # When disabled: + # - Left/Right clicking on item cycles through skills + # - Preview when hovering the item + # - No limit on skills (though makes cycling hard to find skills) + bars: true + # + # Whether or not to use the combat bar implementation. Details: + # - Specified slot becomes a toggle item + # - Can optionally move over or require interacting (left, right, or drop) to swap modes + # - Swaps between combat mode and passive mode + # - In combat mode, works like the classic skill bar + # - In passive mode, no skills are shown + # - Passive and combat modes each have their own stored contents + # - combat mode uses the skill bar settings + combat: false + # + # Global cooldown between skill casts in seconds + cooldown: 0 + # + # Settings for skill target indicators that play effects + # to show where a skill will hit + cast-indicator: + # + # Whether or not the feature is enabled + enabled: true + # + # How tightly to pack particles in the effect. A higher + # density will play more particles. It represents the + # amount of particles played per block units + density: 1 + # + # How often the particles are played for the effect + # in plays per second + frequency: 10 + # + # How fast position animations happen in blocks per second + animation: 1 + # + # Particle to use when it has a target + particle: + particle: 'crit' + dx: 0 + dy: 0 + dz: 0 + speed: 0 + amount: 1 + # + # The slot the item is kept in, must be in the range 1-9 + slot: 9 + # + # The item to use in the cast slot. + # When not using bars, this only shows up when no skills + # are available for use. + item: + type: BOOK + data: 0 + durability: 0 + name: '&dSkills' + lore: + - '' + - '&6Left Click&2 - First skill set' + - '&6Right Click&2 - Second skill set' + - '&6Q&2 - Organize skills' + # + # The item used in the bar GUI to describe the hover bar + hover-item: + type: BOOKSHELF + data: 0 + durability: 0 + name: '&6Hover Bar' + lore: + - '' + - 'Skills in this row will' + - 'be usable via left clicking' + - 'the cast item and will let' + - 'you see where they will hit' + - 'before casting them.' + # + # The item used in the bar GUI to describe the instant bar + instant-item: + type: BOOKSHELF + data: 0 + durability: 0 + name: '&6Instant Bar' + data: 0 + durability: 0 + name: '&6Instant Bar' + lore: + - '' + - 'Skills in this row will' + - 'be usable via right clicking' + - 'the cast item and will be' + - 'cast immediately when switching' + - 'to their slot.' # Click Combos: # @@ -261,6 +397,12 @@ Click Combos: # Whether or not players can customize their combos allow-custom: false # + # Whether or not to automatically assign combos to skills + # without a combo manually defined. When disabled, only skills + # configured to have a combo or have had a combo set by + # a command will have combos. + auto-assign: true + # # Whether or not left clicks are allowed at all use-click-left: true # @@ -270,6 +412,21 @@ Click Combos: # Whether or not shift clicks are allowed at all use-click-shift: false # + # Whether or not right shift clicks are allowed at all + # This will disable "use-click-shift" if enabled + use-click-right-shift: false + # + # Whether or not left shift clicks are allowed at all + # This will disable "use-click-shift" if enabled + use-click-left-shift: false + # + # Whether or not jump clicks are allowed at all + use-click-space: false + # + # Whether or not Q clicks are allowed at all. + # Enabling this disables dropping items via Q outside of menus. + use-click-q: false + # # How many clicks are needed to perform a combo combo-size: 4 # @@ -350,6 +507,10 @@ Experience: # Whether or not to show a message when losing exp de to dying lose-exp-message: true # + # Worlds where experience is not lost on death + lose-exp-blacklist: + - 'pvpWorld' + # # The formula used for calculating required experience # The formula is: x*lvl*lvl + y*lvl + z formula: @@ -361,34 +522,41 @@ Experience: use-custom: false # # The custom formula to use with 'lvl' being the current player level + # Note: this formula does not use x, y, or z. Use numbers directly + # in the formula instead. custom-formula: '25(1.1^(lvl-1))' # # The experience yields from each mob type # When exp orbs are enabled, these values are ignored yields: - blaze: 10 - cavespider: 5 - creeper: 3 - elderguardian: 10 - enderdragon: 400 - enderman: 5 - endermite: 3 - ghast: 5 - giant: 20 - guardian: 10 - irongolem: 10 - magmacube: 1 - pigzombie: 5 - player: 5 - shulker: 5 - silverfish: 2 - skeleton: 3 - slime: 1 - spider: 3 - witch: 3 - wither: 500 - witherskeleton: 5 - zombie: 2 + blaze: '10' + cavespider: '5' + creeper: '3' + elderguardian: '10' + enderdragon: '400' + enderman: '5' + endermite: '3' + evoker: '10' + ghast: '5' + giant: '20' + guardian: '10' + husk: '2' + irongolem: '10' + magmacube: '1' + pigzombie: '5' + player: '5' + shulker: '5' + silverfish: '2' + skeleton: '3' + slime: '1' + spider: '3' + stray: '5' + vex: '3' + vindicator: '5' + witch: '3' + wither: '500' + witherskeleton: '5' + zombie: '2' # # How much logging to do when loading SkillAPI # When testing setting up skills/classes, increase this to 1-5 diff --git a/configs/effects.yml b/configs/effects.yml new file mode 100644 index 00000000..99384157 --- /dev/null +++ b/configs/effects.yml @@ -0,0 +1,128 @@ +single: + formula: 0 + steps: 1 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +linear: + formula: 1 + steps: 20 + copies: 1 + domain: 0 + x: 0 + y: 0 + z: 0 +linear-quick: + formula: 1 + steps: 10 + copies: 1 + domain: 0 + x: 0 + y: 0 + z: 0 +still: + formula: 0 + steps: 20 + copies: 1 + domain: 0 + x: 0 + y: 0 + z: 0 +one-point: + formula: 1 + steps: 1 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +two-point: + formula: 1 + steps: 2 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +three-point: + formula: 1 + steps: 3 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +one-circle: + formula: 1 + steps: 20 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +two-circle: + formula: 1 + steps: 20 + copies: 2 + domain: 1 + x: 0 + y: 0 + z: 0 +three-circle: + formula: 1 + steps: 20 + copies: 3 + domain: 1 + x: 0 + y: 0 + z: 0 +one-spiral: + formula: t + steps: 20 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +two-spiral: + formula: t*0.5 + steps: 40 + copies: 1 + domain: 2 + x: 0 + y: 0 + z: 0 +three-spiral: + formula: t*0.333 + steps: 60 + copies: 1 + domain: 3 + x: 0 + y: 0 + z: 0 +square: + formula: 1/c + steps: 5 + copies: 8 + domain: 0.125 + x: 0 + y: 0 + z: 0 +pentagon: + formula: 1/c + steps: 5 + copies: 10 + domain: 0.1 + x: 0 + y: 0 + z: 0 +hexagon: + formula: 1/c + steps: 4 + copies: 12 + domain: 0.08333 + x: 0 + y: 0 + z: 0 \ No newline at end of file diff --git a/configs/exp.yml b/configs/exp.yml new file mode 100644 index 00000000..ba395697 --- /dev/null +++ b/configs/exp.yml @@ -0,0 +1,76 @@ +# Whether or not to enable controlling experience from broken/placed blocks or crafted items +enabled: false + +# Experience yields for anything except combat. Combat experience is either controlled +# via vanilla experience dropped or the yields defined in config.yml. +# +# These are experience values provided when a player breaks a block. +break: + # + # Whether or not to allow players to place a block back down and break it again + # to get additional experience. Note that disabling this will cause all block + # changes to be tracked in order to remember what was placed or not. Blocks + # existing before this is disabled will not be protected from experience yields. + allow-replace: true + # + # The yields per block type. You can add any block types not in this list by simply + # appending the bukkit name of the block type. See + # https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + # for valid types. This list is not case-sensitive. + types: + # + # You can add classes as well that allow certain blocks only to benefit + # that class. For example, if you want a miner to benefit from ore but + # a farmer to benefit from crops, you can add a "miner" section containing + # the ore and a "farmer" section containing the crops. This would look something + # like: + # + # # Blocks only for miners + # miner: + # COAL: 1 + # IRON_ORE: 10 + # # Blocks only for farmers + # farmer: + # CROPS: 5 + default: + COAL: 1 + QUARTZ_ORE: 1 + IRON_ORE: 10 + GOLD_ORE: 15 + REDSTONE_ORE: 20 + LAPIS_ORE: 25 + DIAMOND_ORE: 50 + EMERALD_ORE: 100 +# +# Experience yields when a player places a block. There's not a built-in way to check +# for players repeatedly placing down the same block, so use this type of experience +# with caution. Works similar to break, just without the replace option. +place: + default: + DIAMOND_BLOCK: 1 +# +# Experience yields when a player crafts items. +craft: + # + # Similar to above, you can specify classes to get + # experience from different crafts. + default: + BOW: 5 + IRON_CHESTPLATE: 80 + IRON_LEGGINGS: 70 + IRON_HELMET: 50 + IRON_BOOTS: 40 + IRON_SWORD: 20 + IRON_AXE: 30 + GOLD_CHESTPLATE: 120 + GOLD_LEGGINGS: 105 + GOLD_HELMET: 75 + GOLD_BOOTS: 60 + GOLD_SWORD: 30 + GOLD_AXE: 45 + DIAMOND_CHESTPLATE: 400 + DIAMOND_LEGGINGS: 350 + DIAMOND_HELMET: 250 + DIAMOND_BOOTS: 200 + DIAMOND_SWORD: 100 + DIAMOND_AXE: 150 \ No newline at end of file diff --git a/configs/internal/mobSizes.yml b/configs/internal/mobSizes.yml new file mode 100644 index 00000000..0d3c3c08 --- /dev/null +++ b/configs/internal/mobSizes.yml @@ -0,0 +1,249 @@ +Baby Turtle: + size: 0.32 + height: 0.12 +Baby Rabbit: + size: 0.2 + height: 0.25 +Cod: + size: 0.5 + height: 0.3 +Baby Chicken: + size: 0.2 + height: 0.35 +Baby Ocelot/Cat: + size: 0.3 + height: 0.35 +Tropical Fish: + size: 0.5 + height: 0.4 +Salmon: + size: 0.7 + height: 0.4 +Turtle: + size: 1.1 + height: 0.4 +Baby Wolf: + size: 0.3 + height: 0.425 +Baby Pig: + size: 0.45 + height: 0.45 +Rabbit: + size: 0.4 + height: 0.5 +Pufferfish: + size: 0.7 + height: 0.7 +Dolphin: + size: 0.9 + height: 0.6 +Baby Panda: + size: 0.65 + height: 0.625 +Baby Sheep: + size: 0.45 + height: 0.675 +Chicken: + size: 0.4 + height: 0.7 +Baby Cow: + size: 0.45 + height: 0.7 +Baby Mooshroom: + size: 0.45 + height: 0.7 +Baby Polar Bear: + size: 0.65 + height: 0.7 +Ocelot: + size: 0.6 + height: 0.7 +Cat: + size: 0.6 + height: 0.7 +Baby Horses: + size: 0.6982 + height: 0.8 +Wolf: + size: 0.6 + height: 0.85 +Dog: + size: 0.6 + height: 0.85 +Pig: + size: 0.9 + height: 0.9 +Baby Llama: + size: 0.45 + height: 0.9375 +Panda: + size: 1.3 + height: 1.25 +Sheep: + size: 0.9 + height: 1.3 +Cow: + size: 0.9 + height: 1.4 +Mooshroom: + size: 0.9 + height: 1.4 +Polar Bear: + size: 1.3 + height: 1.4 +Horse: + size: 1.3964 + height: 1.6 +Llama: + size: 0.9 + height: 1.875 +Ravager: + size: 1.95 + height: 2.2 +Endermite: + size: 0.4 + height: 0.3 +Silverfish: + size: 0.4 + height: 0.3 +Cave Spider: + size: 0.7 + height: 0.5 +Phantom: + size: 0.8 + height: 0.5 +Small Slime: + size: 0.51 + height: 0.51 +Magma Cube: + size: 0.51 + height: 0.51 +Vex: + size: 0.4 + height: 0.8 +Guardian: + size: 0.85 + height: 0.85 +Spider: + size: 1.4 + height: 0.9 +Baby Zombie: + size: 0.3 + height: 0.975 +Baby Husk: + size: 0.3 + height: 0.975 +Baby Drowned: + size: 0.3 + height: 0.975 +Shulker: + size: 1 + height: 1 +Medium Slime: + size: 1.02 + height: 1.02 +Medium Magma Cube: + size: 1.02 + height: 1.02 +Creeper: + size: 0.6 + height: 1.7 +Blaze: + size: 0.6 + height: 1.8 +Zombie: + size: 0.6 + height: 1.95 +Evoker: + size: 0.6 + height: 1.95 +Villager: + size: 0.6 + height: 1.95 +Husk: + size: 0.6 + height: 1.95 +Witch: + size: 0.6 + height: 1.95 +Vindicator: + size: 0.6 + height: 1.95 +Illusioner: + size: 0.6 + height: 1.95 +Drowned: + size: 0.6 + height: 1.95 +Pigman: + size: 0.6 + height: 1.95 +Pillager: + size: 0.6 + height: 1.95 +Skeleton/Stray: + size: 0.6 + height: 1.99 +Elder Guardian: + size: 2 + height: 2 +Large Slime: + size: 2.04 + height: 2.04 +Large Magma Cube: + size: 2.04 + height: 2.04 +Wither Skeleton: + size: 0.7 + height: 2.4 +Enderman: + size: 0.6 + height: 2.9 +Wither: + size: 0.9 + height: 3.5 +Ghast: + size: 4 + height: 4 +Giant: + size: 3.6 + height: 11.7 +Ender Dragon: + size: 16 + height: 8 +Squid: + size: 0.8 + height: 0.8 +Bat/Parrot: + size: 0.5 + height: 0.9 +Snow Golem: + size: 0.7 + height: 1.9 +Iron Golem: + size: 1.4 + height: 2.7 +Minecart: + size: 0.98 + height: 0.7 +Boat: + size: 1.375 + height: 0.5625 +Armor Stand: + size: 0.5 + height: 1.975 +Falling Block: + size: 0.98 + height: 0.98 +Xp Orb: + size: 0.5 + height: 0.5 +Item: + size: 0.25 + height: 0.25 +Ender Crystal: + size: 2 + height: 2 +Player: + size: 0.6 + height: 1.8 \ No newline at end of file diff --git a/language.yml b/configs/language.yml similarity index 95% rename from language.yml rename to configs/language.yml index 20d18e99..672001b7 100644 --- a/language.yml +++ b/configs/language.yml @@ -20,6 +20,7 @@ Errors: cannot-use: '&4You cannot equip that item' GUI: attribute-title: 'Attributes ({points} points)' + profess-title: 'Profess' skill-tree: '{class}' skill-class-list: '{player}' Skill Tree: @@ -62,3 +63,8 @@ Combo: left: '&6Left' right: '&6Right' shift: '&6Shift' + left_shift: '&6Shift L' + right_shift: '&6Shift R' + space: '&6Jump' + q: '&6Drop' + diff --git a/plugin.yml b/configs/plugin.yml similarity index 87% rename from plugin.yml rename to configs/plugin.yml index a4059bc8..49f92ef5 100644 --- a/plugin.yml +++ b/configs/plugin.yml @@ -1,18 +1,16 @@ name: SkillAPI main: com.sucy.skill.SkillAPI -version: 3.99 +version: 1.90 author: Eniripsa96 depend: [MCCore] softdepend: [Vault] loadbefore: [Quests] +api-version: 1.13 permissions: skillapi.basic: description: access to skill trees and using skills default: true - skillapi.backup: - dscription: allows backing up the SQL database - default: op skillapi.reset: description: access to resetting your class default: true @@ -46,9 +44,12 @@ permissions: skillapi.attrib: description: access to giving attribute points default: op + skillapi.gui: + description: access to GUI editor menu + default: op skillapi.*: - description: access to all commands + description: access to all commands and features default: op children: skillapi.basic: true @@ -60,4 +61,5 @@ permissions: skillapi.reload: true skillapi.class: true skillapi.skill: true - skillapi.lore: true \ No newline at end of file + skillapi.lore: true + skillapi.gui: true diff --git a/configs/tool.yml b/configs/tool.yml new file mode 100644 index 00000000..646281f4 --- /dev/null +++ b/configs/tool.yml @@ -0,0 +1,17 @@ +# Define extra items usable in the GUI editor here. +# NEXT_PAGE nad PREV_PAGE are provided as the items +# used for pages. Do not remove them. +NEXT_PAGE: + type: 'Book' + data: 0 + durability: 0 + name: '&6Next Page' + lore: + - '' +PREV_PAGE: + type: 'Book' + data: 0 + durability: 0 + name: '&6Prev Page' + lore: + - '' \ No newline at end of file diff --git a/configs/worldGuard.yml b/configs/worldGuard.yml new file mode 100644 index 00000000..77bf9ebe --- /dev/null +++ b/configs/worldGuard.yml @@ -0,0 +1,10 @@ +# +# WorldGuard region IDs that players cannot cast skills in +disable-skills: +- fakeZoneId1 +- fakeZoneId2 +# +# WorldGuard region IDs that players cannot earn experience in +disable-exp: +- fakeZoneId1 +- fakeZoneId2 \ No newline at end of file diff --git a/editor/builderStyle.css b/editor/builderStyle.css index 0b4dda4d..3669a6cd 100644 --- a/editor/builderStyle.css +++ b/editor/builderStyle.css @@ -2,8 +2,8 @@ h3 { padding: 5px; - border-radius: 8px; - background-color: #111; + border-radius: 5px; + background-color: transparent; transition: background-color 600ms; } @@ -41,10 +41,10 @@ h3:hover { .component { padding: 5px; margin: 5px; - border: 2px solid #ccc; + border: 2px solid #333; border-radius: 10px; font: 18px Sertig-Web; - background-color: #111; + background-color: #181818; width: 260px; height: 90px; } @@ -55,17 +55,16 @@ h3:hover { .builderButton { float: left; - background-color: #222; + background-color: #043; width: 90px; height: 32px; font: 16px Sertig-Web; - border-radius: 8px; + border-radius: 5px; padding: 5px; padding-left: 10px; color: white; cursor: pointer; margin: 5px; - border: 1px solid #ccc; transition: background-color 600ms; } diff --git a/editor/js/class.js b/editor/js/class.js index f38ec2a5..190f38bf 100644 --- a/editor/js/class.js +++ b/editor/js/class.js @@ -30,7 +30,9 @@ function Class(name) new IntValue('Icon Data', 'icon-data', 0).setTooltip('The data/durability value of the item that represents the class in GUIs'), new StringListValue('Icon Lore', 'icon-lore', [ '&d' + name - ]).setTooltip('The text shown on the item for the class in GUIs') + ]), + new StringListValue('Unusable Items', 'blacklist', [ ]).setTooltip('The types of items that the class cannot use (one per line)'), + new StringValue('Action Bar', 'action-bar', '').setTooltip('The format for the action bar. Leave blank to use the default formatting.') ]; this.updateAttribs(10); @@ -94,6 +96,10 @@ Class.prototype.createFormHTML = function() // Append attributes if (this.data[i].name == 'Mana') { + var dragInstructions = document.createElement('label'); + dragInstructions.id = 'attribute-label'; + dragInstructions.innerHTML = 'Drag/Drop your attributes.yml file to see custom attributes'; + form.appendChild(dragInstructions); this.updateAttribs(i + 1); } } @@ -102,7 +108,7 @@ Class.prototype.createFormHTML = function() form.appendChild(hr); var save = document.createElement('h5'); - save.innerHTML = 'Save', + save.innerHTML = 'Save Class', save.classData = this; save.addEventListener('click', function(e) { this.classData.update(); diff --git a/editor/js/component.js b/editor/js/component.js index a227b63a..7d810fd6 100644 --- a/editor/js/component.js +++ b/editor/js/component.js @@ -27,6 +27,8 @@ var Type = { * Available triggers for activating skill effects */ var Trigger = { + BLOCK_BREAK : { name: 'Block Break', container: true, construct: TriggerBlockBreak, premium: true }, + BLOCK_PLACE : { name: 'Block Place', container: true, construct: TriggerBlockPlace, premium: true }, CAST : { name: 'Cast', container: true, construct: TriggerCast }, CLEANUP : { name: 'Cleanup', container: true, construct: TriggerCleanup }, CROUCH : { name: 'Crouch', container: true, construct: TriggerCrouch }, @@ -36,6 +38,7 @@ var Trigger = { KILL : { name: 'Kill', container: true, construct: TriggerKill }, LAND : { name: 'Land', container: true, construct: TriggerLand }, LAUNCH : { name: 'Launch', container: true, construct: TriggerLaunch }, + MOVE : { name: 'Move', container: true, construct: TriggerMove, premium: true }, PHYSICAL_DAMAGE : { name: 'Physical Damage', container: true, construct: TriggerPhysicalDamage }, SKILL_DAMAGE : { name: 'Skill Damage', container: true, construct: TriggerSkillDamage }, TOOK_PHYSICAL_DAMAGE : { name: 'Took Physical Damage', container: true, construct: TriggerTookPhysicalDamage }, @@ -65,6 +68,7 @@ var Condition = { ATTRIBUTE: { name: 'Attribute', container: true, construct: ConditionAttribute }, BIOME: { name: 'Biome', container: true, construct: ConditionBiome }, BLOCK: { name: 'Block', container: true, construct: ConditionBlock }, + CEILING: { name: 'Ceiling', container: true, construct: ConditionCeiling, premium: true }, CHANCE: { name: 'Chance', container: true, construct: ConditionChance }, CLASS: { name: 'Class', container: true, construct: ConditionClass }, CLASS_LEVEL: { name: 'Class Level', container: true, construct: ConditionClassLevel }, @@ -76,6 +80,7 @@ var Condition = { ENTITY_TYPE: { name: 'Entity Type', container: true, construct: ConditionEntityType,premium: true }, FIRE: { name: 'Fire', container: true, construct: ConditionFire }, FLAG: { name: 'Flag', container: true, construct: ConditionFlag }, + GROUND: { name: 'Ground', container: true, construct: ConditionGround, premium: true }, HEALTH: { name: 'Health', container: true, construct: ConditionHealth }, INVENTORY: { name: 'Inventory', container: true, construct: ConditionInventory }, ITEM: { name: 'Item', container: true, construct: ConditionItem }, @@ -91,7 +96,8 @@ var Condition = { TIME: { name: 'Time', container: true, construct: ConditionTime }, TOOL: { name: 'Tool', container: true, construct: ConditionTool }, VALUE: { name: 'Value', container: true, construct: ConditionValue }, - WATER: { name: 'Water', container: true, construct: ConditionWater } + WATER: { name: 'Water', container: true, construct: ConditionWater }, + WEATHER: { name: 'Weather', container: true, construct: ConditionWeather, premium: true } }; /** @@ -100,6 +106,7 @@ var Condition = { var Mechanic = { ATTRIBUTE: { name: 'Attribute', container: false, construct: MechanicAttribute }, BLOCK: { name: 'Block', container: false, construct: MechanicBlock }, + BUFF: { name: 'Buff', container: false, construct: MechanicBuff, premium: true }, CANCEL: { name: 'Cancel', container: false, construct: MechanicCancel }, CHANNEL: { name: 'Channel', container: true, construct: MechanicChannel }, CLEANSE: { name: 'Cleanse', container: false, construct: MechanicCleanse }, @@ -111,12 +118,16 @@ var Mechanic = { DEFENSE_BUFF: { name: 'Defense Buff', container: false, construct: MechanicDefenseBuff }, DELAY: { name: 'Delay', container: true, construct: MechanicDelay }, DISGUISE: { name: 'Disguise', container: false, construct: MechanicDisguise }, + DURABILITY: { name: 'Durability', container: false, construct: MechanicDurability, premium: true }, EXPLOSION: { name: 'Explosion', container: false, construct: MechanicExplosion }, FIRE: { name: 'Fire', container: false, construct: MechanicFire }, FLAG: { name: 'Flag', container: false, construct: MechanicFlag }, FLAG_CLEAR: { name: 'Flag Clear', container: false, construct: MechanicFlagClear }, FLAG_TOGGLE: { name: 'Flag Toggle', container: false, construct: MechanicFlagToggle }, + FOOD: { name: 'Food', container: false, construct: MechanicFood, premium: true }, + FORGET_TARGETS: { name: 'Forget Targets', container: false, construct: MechanicForgetTargets, premium: true }, HEAL: { name: 'Heal', container: false, construct: MechanicHeal }, + HEALTH_SET: { name: 'Health Set', container: false, construct: MechanicHealthSet, premium: true }, HELD_ITEM: { name: 'Held Item', container: false, construct: MechanicHeldItem, premium: true }, IMMUNITY: { name: 'Immunity', container: false, construct: MechanicImmunity }, INTERRUPT: { name: 'Interrupt', container: false, construct: MechanicInterrupt }, @@ -145,14 +156,18 @@ var Mechanic = { SPEED: { name: 'Speed', container: false, construct: MechanicSpeed }, STATUS: { name: 'Status', container: false, construct: MechanicStatus }, TAUNT: { name: 'Taunt', container: false, construct: MechanicTaunt }, + TRIGGER: { name: 'Trigger', container: true, construct: MechanicTrigger, premium: true }, VALUE_ADD: { name: 'Value Add', container: false, construct: MechanicValueAdd }, VALUE_ATTRIBUTE: { name: 'Value Attribute', container: false, construct: MechanicValueAttribute }, + VALUE_COPY: { name: 'Value Copy', container: false, construct: MechanicValueCopy, premium: true }, + VALUE_DISTANCE: { name: 'Value Distance', container: false, construct: MechanicValueDistance, premium: true }, VALUE_HEALTH: { name: 'Value Health', container: false, construct: MechanicValueHealth, premium: true }, VALUE_LOCATION: { name: 'Value Location', container: false, construct: MechanicValueLocation }, VALUE_LORE: { name: 'Value Lore', container: false, construct: MechanicValueLore }, VALUE_LORE_SLOT: { name: 'Value Lore Slot', container: false, construct: MechanicValueLoreSlot, premium: true}, VALUE_MANA: { name: 'Value Mana', container: false, construct: MechanicValueMana, premium: true }, VALUE_MULTIPLY: { name: 'Value Multiply', container: false, construct: MechanicValueMultiply }, + VALUE_PLACEHOLDER: { name: 'Value Placeholder', container: false, construct: MechanicValuePlaceholder, premium: true }, VALUE_RANDOM: { name: 'Value Random', container: false, construct: MechanicValueRandom }, VALUE_SET: { name: 'Value Set', container: false, construct: MechanicValueSet }, WARP: { name: 'Warp', container: false, construct: MechanicWarp }, @@ -168,7 +183,7 @@ var saveIndex; /** * Represents a component of a dynamic skill - * + * * @param {string} name - name of the component * @param {string} type - type of the component * @param {boolean} container - whether or not the component can contain others @@ -199,7 +214,7 @@ function Component(name, type, container, parent) .setTooltip('Whether or not this trigger requires to be off cooldown to activate') ); } - + this.dataKey = 'data'; this.componentKey = 'children'; } @@ -210,12 +225,14 @@ Component.prototype.dupe = function(parent) var ele = new Component(this.name, this.type, this.container, parent); for (i = 0; i < this.components.length; i++) { - ele.components.push(this.components[i].dupe()); + ele.components.push(this.components[i].dupe(ele)); } + ele.data = ele.data.slice(0, 1); for (i = ele.data.length; i < this.data.length; i++) { - ele.data.push(this.data[i].dupe()); + ele.data.push(copyRequirements(this.data[i], this.data[i].dupe())); } + ele.description = this.description; return ele; } @@ -233,7 +250,7 @@ Component.prototype.createBuilderHTML = function(target) if (this.type == Type.TRIGGER) { container.className = 'componentWrapper'; } - + var div = document.createElement('div'); div.className = 'component ' + this.type; if (this.type != Type.TRIGGER) { @@ -244,7 +261,7 @@ Component.prototype.createBuilderHTML = function(target) if (this.container) { div.ondragover = this.allowDrop; } - + // Component label var label = document.createElement('h3'); label.title = 'Edit ' + this.name + ' options'; @@ -256,20 +273,20 @@ Component.prototype.createBuilderHTML = function(target) showSkillPage('skillForm'); }); div.appendChild(label); - + // Container components can add children so they get a button - if (this.container) + if (this.container) { var add = document.createElement('div'); add.className = 'builderButton'; add.innerHTML = '+ Add Child'; add.component = this; add.addEventListener('click', function(e) { - activeComponent = this.component; + activeComponent = this.component; showSkillPage('componentChooser'); }); div.appendChild(add); - + var vision = document.createElement('div'); vision.title = 'Hide Children'; vision.className = 'builderButton smallButton'; @@ -282,7 +299,7 @@ Component.prototype.createBuilderHTML = function(target) comp.childDiv.style.display = 'block'; this.style.backgroundImage = 'url("editor/img/eye.png")'; } - else + else { comp.childDiv.style.display = 'none'; this.style.backgroundImage = 'url("editor/img/eyeShaded.png")'; @@ -292,7 +309,7 @@ Component.prototype.createBuilderHTML = function(target) div.appendChild(vision); this.childrenHidden = false; } - + // Add the duplicate button if (this.type != Type.TRIGGER) { @@ -309,7 +326,7 @@ Component.prototype.createBuilderHTML = function(target) }); div.appendChild(duplicate); } - + // Add the remove button var remove = document.createElement('div'); remove.title = 'Remove'; @@ -318,7 +335,7 @@ Component.prototype.createBuilderHTML = function(target) remove.component = this; remove.addEventListener('click', function(e) { var list = this.component.parent.components; - for (var i = 0; i < list.length; i++) + for (var i = 0; i < list.length; i++) { if (list[i] == this.component) { @@ -329,26 +346,26 @@ Component.prototype.createBuilderHTML = function(target) this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode); }); div.appendChild(remove); - + container.appendChild(div); - + // Apply child components var childContainer = document.createElement('div'); childContainer.className = 'componentChildren'; if (this.components.length > 0) { - for (var i = 0; i < this.components.length; i++) + for (var i = 0; i < this.components.length; i++) { this.components[i].createBuilderHTML(childContainer); } } container.appendChild(childContainer); this.childDiv = childContainer; - + // Append the content target.appendChild(container); - + this.html = childContainer; -} +}; Component.prototype.allowDrop = function(e) { e.preventDefault(); @@ -390,7 +407,7 @@ Component.prototype.drop = function(e) { hoverSpace.style.marginBottom = '0px'; hoverSpace = undefined; } - + e.preventDefault(); var thing = document.getElementById('dragComponent').parentNode; var target = e.target; @@ -405,7 +422,7 @@ Component.prototype.drop = function(e) { target = target.parentNode.childNodes[1]; thing.parentNode.removeChild(thing); target.appendChild(thing); - + thingComp.parent.components.splice(thingComp.parent.components.indexOf(thingComp), 1); thingComp.parent = targetComp; thingComp.parent.components.push(thingComp); @@ -418,25 +435,25 @@ Component.prototype.drop = function(e) { Component.prototype.createFormHTML = function() { var target = document.getElementById('skillForm'); - + var form = document.createElement('form'); - + var header = document.createElement('h4'); header.innerHTML = this.name; form.appendChild(header); - + if (this.description) { var desc = document.createElement('p'); desc.innerHTML = this.description; form.appendChild(desc); } - - if (this.data.length > 1) + + if (this.data.length > 1) { var h = document.createElement('hr'); form.appendChild(h); - + var i = 1; for (var j = 1; j < this.data.length; j++) { if (this.data[j] instanceof AttributeValue) { @@ -450,10 +467,10 @@ Component.prototype.createFormHTML = function() this.data[i].createHTML(form); } } - + var hr = document.createElement('hr'); form.appendChild(hr); - + var done = document.createElement('h5'); done.className = 'doneButton'; done.innerHTML = 'Done'; @@ -464,13 +481,13 @@ Component.prototype.createFormHTML = function() showSkillPage('builder'); }); form.appendChild(done); - + this.form = form; - + target.innerHTML = ''; target.appendChild(form); activeComponent = this; - + for (var i = 0; i < this.data.length; i++) { this.data[i].applyRequireValues(); @@ -496,7 +513,7 @@ Component.prototype.update = function() Component.prototype.getSaveString = function(spacing) { this.createFormHTML(); - + var id = ''; var index = saveIndex; while (index > 0 || id.length == 0) @@ -506,7 +523,7 @@ Component.prototype.getSaveString = function(spacing) } var result = spacing + this.name + '-' + id + ":\n"; saveIndex++; - + result += spacing + " type: '" + this.type + "'\n"; if (this.data.length > 0) { @@ -539,11 +556,37 @@ Component.prototype.load = loadSection; // -- Trigger constructors ----------------------------------------------------- // +extend('TriggerBlockBreak', 'Component'); +function TriggerBlockBreak() { + this.super('Block Break', Type.TRIGGER, true); + this.description = 'Applies skill effects when a player breaks a block matching the given details'; + + this.data.push(new MultiListValue('Material', 'material', [ 'Any' ].concat(materialList), [ 'Any' ]) + .setTooltip('The type of block expected to be broken') + ); + this.data.push(new IntValue('Data', 'data', -1) + .setTooltip('The expected data value of the block (-1 for any data value)') + ); +} + +extend('TriggerBlockPlace', 'Component'); +function TriggerBlockPlace() { + this.super('Block Place', Type.TRIGGER, true); + this.description = 'Applies skill effects when a player places a block matching the given details'; + + this.data.push(new MultiListValue('Material', 'material', [ 'Any' ].concat(materialList), [ 'Any' ]) + .setTooltip('The type of block expected to be placed') + ); + this.data.push(new IntValue('Data', 'data', -1) + .setTooltip('The expected data value of the block (-1 for any data value)') + ); +} + extend('TriggerCast', 'Component'); function TriggerCast() { this.super('Cast', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player casts the skill using either the cast command, the skill bar, or click combos.'; } @@ -551,7 +594,7 @@ extend('TriggerCleanup', 'Component'); function TriggerCleanup() { this.super('Cleanup', Type.TRIGGER, true); - + this.description = 'Applies skill effects when the player disconnects or unlearns the skill. This is always applied with a skill level of 1 just for the sake of math.'; } @@ -559,9 +602,9 @@ extend('TriggerCrouch', 'Component'); function TriggerCrouch() { this.super('Crouch', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player starts or stops crouching using the shift key.'; - + this.data.push(new ListValue('Type', 'type', [ 'Start Crouching', 'Stop Crouching', 'Both' ], 'Start Crouching') .setTooltip('Whether or not you want to apply components when crouching or not crouching') ); @@ -571,7 +614,7 @@ extend('TriggerDeath', 'Component'); function TriggerDeath() { this.super('Death', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player dies.'; } @@ -579,10 +622,12 @@ extend('TriggerEnvironmentDamage', 'Component'); function TriggerEnvironmentDamage() { this.super('Environment Damage', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player takes environmental damage.'; - - this.data.push(new ListValue('Type', 'type', DAMAGE_TYPES, 'FALL')); + + this.data.push(new ListValue('Type', 'type', DAMAGE_TYPES, 'FALL') + .setTooltip('The source of damage to apply for') + ); } @@ -590,7 +635,7 @@ extend('TriggerInitialize', 'Component'); function TriggerInitialize() { this.super('Initialize', Type.TRIGGER, true); - + this.description = 'Applies skill effects immediately. This can be used for passive abilities.'; } @@ -598,7 +643,7 @@ extend('TriggerKill', 'Component'); function TriggerKill() { this.super('Kill', Type.TRIGGER, true); - + this.description = 'Applies skill effects upon killing something'; } @@ -606,9 +651,9 @@ extend('TriggerLand', 'Component'); function TriggerLand() { this.super('Land', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player lands on the ground.'; - + this.data.push(new DoubleValue('Min Distance', 'min-distance', 0) .setTooltip('The minimum distance the player should fall before effects activating.') ); @@ -618,24 +663,32 @@ extend('TriggerLaunch', 'Component'); function TriggerLaunch() { this.super('Launch', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player launches a projectile.'; - + this.data.push(new ListValue('Type', 'type', [ 'Any', 'Arrow', 'Egg', 'Ender Pearl', 'Fireball', 'Fishing Hook', 'Snowball' ], 'Any') .setTooltip('The type of projectile that should be launched.') ); } +extend('TriggerMove', 'Component'); +function TriggerMove() +{ + this.super('Move', Type.TRIGGER, true); + + this.description = 'Applies skill effects when a player moves around. This triggers every tick the player is moving, so use this sparingly. Use the "api-moved" value to check/use the distance traveled.'; +} + extend('TriggerPhysicalDamage', 'Component'); function TriggerPhysicalDamage() { this.super('Physical Damage', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player deals physical (or non-skill) damage. This includes melee attacks and firing a bow.'; - + this.data.push(new ListValue('Target Caster', 'target', [ 'True', 'False' ], 'True') .setTooltip('True makes children target the caster. False makes children target the damaged entity') - ); + ); this.data.push(new ListValue('Type', 'type', [ 'Both', 'Melee', 'Projectile' ], 'Both') .setTooltip('The type of damage dealt') ); @@ -651,30 +704,33 @@ extend('TriggerSkillDamage', 'Component'); function TriggerSkillDamage() { this.super('Skill Damage', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player deals damage with a skill.'; - + this.data.push(new ListValue('Target Caster', 'target', [ 'True', 'False' ], 'True') .setTooltip('True makes children target the caster. False makes children target the damaged entity') - ); + ); this.data.push(new DoubleValue("Min Damage", "dmg-min", 0) .setTooltip('The minimum damage that needs to be dealt') ); this.data.push(new DoubleValue("Max Damage", "dmg-max", 999) .setTooltip('The maximum damage that needs to be dealt') ); + this.data.push(new StringListValue('Category', 'category', [ 'default' ] ) + .setTooltip('The type of skill damage to apply for. Leave this empty to apply to all skill damage.') + ); } extend('TriggerTookPhysicalDamage', 'Component'); function TriggerTookPhysicalDamage() { this.super('Took Physical Damage', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player takes physical (or non-skill) damage. This includes melee attacks and projectiles not fired by a skill.'; - + this.data.push(new ListValue('Target Caster', 'target', [ 'True', 'False' ], 'True') .setTooltip('True makes children target the caster. False makes children target the attacking entity') - ); + ); this.data.push(new ListValue('Type', 'type', [ 'Both', 'Melee', 'Projectile' ], 'Both') .setTooltip('The type of damage dealt') ); @@ -690,18 +746,21 @@ extend('TriggerTookSkillDamage', 'Component'); function TriggerTookSkillDamage() { this.super('Took Skill Damage', Type.TRIGGER, true); - + this.description = 'Applies skill effects when a player takes damage from a skill other than their own.'; - + this.data.push(new ListValue('Target Caster', 'target', [ 'True', 'False' ], 'True') .setTooltip('True makes children target the caster. False makes children target the attacking entity') - ); + ); this.data.push(new DoubleValue("Min Damage", "dmg-min", 0) .setTooltip('The minimum damage that needs to be dealt') ); this.data.push(new DoubleValue("Max Damage", "dmg-max", 999) .setTooltip('The maximum damage that needs to be dealt') ); + this.data.push(new StringListValue('Category', 'category', [ 'default' ] ) + .setTooltip('The type of skill damage to apply for. Leave this empty to apply to all skill damage.') + ); } // -- Target constructors ------------------------------------------------------ // @@ -710,9 +769,9 @@ extend('TargetArea', 'Component'); function TargetArea() { this.super('Area', Type.TARGET, true); - + this.description = 'Targets all units in a radius from the current target (the casting player is the default target).'; - + this.data.push(new AttributeValue("Radius", "radius", 3, 0) .setTooltip('The radius of the area to target in blocks') ); @@ -734,9 +793,9 @@ extend('TargetCone', 'Component'); function TargetCone() { this.super('Cone', Type.TARGET, true); - + this.description = 'Targets all units in a line in front of the current target (the casting player is the default target). If you include the caster, that counts towards the max amount.'; - + this.data.push(new AttributeValue("Range", "range", 5, 0) .setTooltip('The max distance away any target can be in blocks') ); @@ -761,9 +820,9 @@ extend('TargetLinear', 'Component'); function TargetLinear() { this.super('Linear', Type.TARGET, true); - + this.description = 'Targets all units in a line in front of the current target (the casting player is the default target).'; - + this.data.push(new AttributeValue("Range", "range", 5, 0) .setTooltip('The max distance away any target can be in blocks') ); @@ -788,21 +847,24 @@ extend('TargetLocation', 'Component'); function TargetLocation() { this.super('Location', Type.TARGET, true); - + this.description = 'Targets the reticle location of the target or caster. Combine this with another targeting type for ranged area effects.'; - + this.data.push(new AttributeValue('Range', 'range', 5, 0) .setTooltip('The max distance the location can be') ); + this.data.push(new ListValue('Ground Only', 'ground', [ 'True', 'False' ], 'True') + .setTooltip('Whether or not a player is only allowed to target the ground or other units') + ); } extend('TargetNearest', 'Component'); function TargetNearest() { this.super('Nearest', Type.TARGET, true); - + this.description = 'Targets the closest unit(s) in a radius from the current target (the casting player is the default target). If you include the caster, that counts towards the max number.'; - + this.data.push(new AttributeValue("Radius", "radius", 3, 0) .setTooltip('The radius of the area to target in blocks') ); @@ -824,9 +886,9 @@ extend('TargetOffset', 'Component'); function TargetOffset() { this.super('Offset', Type.TARGET, true); - + this.description = 'Targets a location that is the given offset away from each target.'; - + this.data.push(new AttributeValue('Forward', 'forward', 0, 0) .setTooltip('The offset from the target in the direction they are facing. Negative numbers go backwards.') ); @@ -842,9 +904,9 @@ extend('TargetRemember', 'Component'); function TargetRemember() { this.super('Remember', Type.TARGET, true); - + this.description = 'Targets entities stored using the "Remember Targets" mechanic for the matching key. If it was never set, this will fail.'; - + this.data.push(new StringValue('Key', 'key', 'target') .setTooltip('The unique key for the target group that should match that used by the "Remember Targets" skill') ); @@ -854,7 +916,7 @@ extend('TargetSelf', 'Component'); function TargetSelf() { this.super('Self', Type.TARGET, true); - + this.description = 'Returns the current target back to the caster.'; } @@ -862,9 +924,9 @@ extend('TargetSingle', 'Component'); function TargetSingle() { this.super('Single', Type.TARGET, true); - + this.description = 'Targets a single unit in front of the current target (the casting player is the default target).'; - + this.data.push(new AttributeValue("Range", "range", 5, 0) .setTooltip('The max distance away any target can be in blocks') ); @@ -886,21 +948,21 @@ function ConditionArmor() { this.super('Armor', Type.CONDITION, true); this.description = "Applies child components when the target is wearing an armor item matching the given details."; - + this.data.push(new ListValue('Armor', 'armor', [ 'Helmet', 'Chestplate', 'Leggings', 'Boots', 'Any' ], 'Any') .setTooltip('The type of armor to check') ); - + addItemOptions(this); } extend('ConditionAttribute', 'Component'); -function ConditionAttribute() +function ConditionAttribute() { this.super('Attribute', Type.CONDITION, true); - + this.description = 'Requires the target to have a given number of attributes'; - + this.data.push(new StringValue('Attribute', 'attribute', 'Vitality') .setTooltip('The name of the attribute you are checking the value of') ); @@ -916,9 +978,9 @@ extend('ConditionBiome', 'Component'); function ConditionBiome() { this.super('Biome', Type.CONDITION, true); - + this.description = 'Applies child components when in a specified biome.'; - + this.data.push(new ListValue('Type', 'type', [ 'In Biome', 'Not In Biome' ], 'In Biome') .setTooltip('Whether or not the target should be in the biome. If checking for in the biome, they must be in any one of the checked biomes. If checking for the opposite, they must not be in any of the checked biomes.') ); @@ -931,24 +993,39 @@ extend('ConditionBlock', 'Component'); function ConditionBlock() { this.super('Block', Type.CONDITION, true); - + this.description = 'Applies child components if the target is currently standing on a block of the given type.'; - + this.data.push(new ListValue('Type', 'standing', [ 'On Block', 'Not On Block' ], 'On Block') .setTooltip('Whether or not the target should be in the biome. If checking for in the biome, they must be in any one of the checked biomes. If checking for the opposite, they must not be in any of the checked biomes.') ); this.data.push(new ListValue('Material', 'material', materialList, 'Dirt') .setTooltip('The type of the block to require the targets to stand on') - ); + ); +} + +extend('ConditionCeiling', 'Component'); +function ConditionCeiling() +{ + this.super('Ceiling', Type.CONDITION, true); + + this.description = 'Checks the height of the ceiling above each target'; + + this.data.push(new AttributeValue('Distance', 'distance', 5, 0) + .setTooltip('How high to check for the ceiling') + ); + this.data.push(new ListValue('At least', 'at-least', [ 'True', 'False' ], 'True') + .setTooltip('When true, the ceiling must be at least the give number of blocks high. If false, the ceiling must be lower than the given number of blocks') + ); } extend('ConditionChance', 'Component'); function ConditionChance() { this.super('Chance', Type.CONDITION, true); - + this.description = 'Rolls a chance to apply child components.'; - + this.data.push(new AttributeValue('Chance', 'chance', 25, 0) .setTooltip('The chance to execute children as a percentage. "25" would be 25%.') ); @@ -958,9 +1035,9 @@ extend('ConditionClass', 'Component'); function ConditionClass() { this.super('Class', Type.CONDITION, true); - + this.description = 'Applies child components when the target is the given class or optionally a profession of that class. For example, if you check for "Fighter" which professes into "Warrior", a "Warrior" will pass the check if you do not enable "exact".'; - + this.data.push(new StringValue('Class', 'class', 'Fighter') .setTooltip('The class the player should be') ); @@ -973,9 +1050,9 @@ extend('ConditionClassLevel', 'Component'); function ConditionClassLevel() { this.super('Class Level', Type.CONDITION, true); - + this.description = 'Applies child components when the level of the class with this skill is within the range. This only checks the level of the caster, not the targets.'; - + this.data.push(new IntValue('Min Level', 'min-level', 2) .setTooltip('The minimum class level the player should be. If the player has multiple classes, this will be of their main class') ); @@ -988,9 +1065,9 @@ extend('ConditionCombat', 'Component'); function ConditionCombat() { this.super('Combat', Type.CONDITION, true); - + this.description = 'Applies child components to targets that are in/out of combat, depending on the settings.'; - + this.data.push(new ListValue('In Combat', 'combat', [ 'True', 'False' ], 'True') .setTooltip('Whether or not the target should be in or out of combat') ); @@ -1003,9 +1080,9 @@ extend('ConditionCrouch', 'Component'); function ConditionCrouch() { this.super('Crouch', Type.CONDITION, true); - + this.description = 'Applies child components if the target player(s) are crouching'; - + this.data.push(new ListValue('Crouching', 'crouch', [ 'True', 'False' ], 'True') .setTooltip('Whether or not the player should be crouching') ); @@ -1015,9 +1092,9 @@ extend('ConditionDirection', 'Component'); function ConditionDirection() { this.super('Direction', Type.CONDITION, true); - + this.description = 'Applies child components when the target or caster is facing the correct direction relative to the other.'; - + this.data.push(new ListValue('Type', 'type', [ 'Target', 'Caster' ], 'Target') .setTooltip('The entity to check the direction of') ); @@ -1030,9 +1107,9 @@ extend('ConditionElevation', 'Component'); function ConditionElevation() { this.super('Elevation', Type.CONDITION, true); - + this.description = 'Applies child components when the elevation of the target matches the settings.'; - + this.data.push(new ListValue('Type', 'type', [ 'Normal', 'Difference' ], 'Normal') .setTooltip('The type of comparison to make. Normal is just their Y-coordinate. Difference would be the difference between that the caster\'s Y-coordinate') ); @@ -1048,7 +1125,7 @@ extend('ConditionElse', 'Component'); function ConditionElse() { this.super('Else', Type.CONDITION, true); - + this.description = 'Applies child elements if the previous component failed to execute. This not only applies for conditions not passing, but mechanics failing due to no target or other cases.'; } @@ -1056,9 +1133,9 @@ extend('ConditionEntityType', 'Component'); function ConditionEntityType() { this.super('Entity Type', Type.CONDITION, true); - + this.description = 'Applies child elements if the target matches one of the selected entity types' - + this.data.push(new MultiListValue('Types', 'types', [ 'BAT', 'BLAZE', 'CAVE_SPIDER', 'CHICKEN', 'COW', 'CREEPER', 'DONKEY', 'ELDER_GUARDIAN', 'ENDER_DRAGON', 'ENDERMAN', 'ENDERMITE', 'EVOKER', 'GHAST', 'GIANT', 'GUARDIAN', 'HORSE', 'HUSK', 'IRON_GOLEM', 'LLAMA', 'MAGMA_CUBE', 'MULE', 'MUSHROOM_COW', 'OCELOT', 'PIG', 'PIG_ZOMBIE', 'PLAYER', 'POLAR_BEAR', 'RABBIT', 'SHEEP', 'SHULKER', 'SILVERFISH', 'SKELETON', 'SKELETON_HORSE', 'SLIME', 'SNOWMAN', 'SPIDER', 'SQUID', 'VEX', 'VILLAGER', 'VINDICATOR', 'WITCH', 'WITHER', 'WITHER_SKELETON', 'WOLF', 'ZOMBIE', 'ZOMBIE_HORSE', 'ZOMBIE_VILLAGER' ]) .setTooltip('The entity types to target') ); @@ -1068,9 +1145,9 @@ extend('ConditionFire', 'Component'); function ConditionFire() { this.super('Fire', Type.CONDITION, true); - + this.description = 'Applies child components when the target is on fire.'; - + this.data.push(new ListValue('Type', 'type', [ 'On Fire', 'Not On Fire' ], 'On Fire') .setTooltip('Whether or not the target should be on fire') ); @@ -1080,9 +1157,9 @@ extend('ConditionFlag', 'Component'); function ConditionFlag() { this.super('Flag', Type.CONDITION, true); - + this.description = 'Applies child components when the target is marked by the appropriate flag.'; - + this.data.push(new ListValue('Type', 'type', [ 'Set', 'Not Set' ], 'Set') .setTooltip('Whether or not the flag should be set') ); @@ -1091,13 +1168,25 @@ function ConditionFlag() ); } +extend('ConditionGround', 'Component'); +function ConditionGround() +{ + this.super('Ground', Type.CONDITION, true); + + this.description = 'Applies child components when the target is on the ground'; + + this.data.push(new ListValue('Type', 'type', [ 'On Ground', 'Not On Ground' ], 'On Ground') + .setTooltip('Whether or not the target should be on the ground') + ); +} + extend('ConditionHealth', 'Component'); function ConditionHealth() { this.super('Health', Type.CONDITION, true); this.description = "Applies child components when the target's health matches the settings."; - + this.data.push(new ListValue('Type', 'type', [ 'Health', 'Percent', 'Difference', 'Difference Percent' ], 'Health') .setTooltip('The type of measurement to use for the health. Health is their flat health left. Percent is the percentage of health they have left. Difference is the difference between the target\'s flat health and the caster\'s. Difference percent is the difference between the target\'s percentage health left and the caster\s') ); @@ -1114,7 +1203,7 @@ function ConditionItem() { this.super('Item', Type.CONDITION, true); this.description = "Applies child components when the target is wielding an item matching the given material."; - + addItemOptions(this); } @@ -1122,13 +1211,13 @@ extend('ConditionInventory', 'Component'); function ConditionInventory() { this.super('Inventory', Type.CONDITION, true); - + this.description = 'Applies child components when the target player contains the given item in their inventory. This does not work on mobs.'; - + this.data.push(new AttributeValue('Amount', 'amount', 1, 0) .setTooltip('The amount of the item needed in the player\'s inventory') ); - + addItemOptions(this); } @@ -1136,9 +1225,9 @@ extend('ConditionLight', 'Component'); function ConditionLight() { this.super('Light', Type.CONDITION, true); - + this.description = "Applies child components when the light level at the target's location matches the settings."; - + this.data.push(new AttributeValue('Min Light', 'min-light', 0, 0) .setTooltip('The minimum light level needed. 16 is full brightness while 0 is complete darkness') ); @@ -1153,7 +1242,7 @@ function ConditionMana() this.super('Mana', Type.CONDITION, true); this.description = "Applies child components when the target's mana matches the settings."; - + this.data.push(new ListValue('Type', 'type', [ 'Mana', 'Percent', 'Difference', 'Difference Percent' ], 'Mana') .setTooltip('The type of measurement to use for the mana. Mana is their flat mana left. Percent is the percentage of mana they have left. Difference is the difference between the target\'s flat mana and the caster\'s. Difference percent is the difference between the target\'s percentage mana left and the caster\s') ); @@ -1169,9 +1258,9 @@ extend('ConditionName', 'Component'); function ConditionName() { this.super('Name', Type.CONDITION, true); - + this.description = 'Applies child components when the target has a name matching the settings.'; - + this.data.push(new ListValue('Contains Text', 'contains', [ 'True', 'False' ], 'True') .setTooltip('Whether or not the target should have a name containing the text') ); @@ -1188,7 +1277,7 @@ function ConditionOffhand() { this.super('Offhand', Type.CONDITION, true); this.description = "Applies child components when the target is wielding an item matching the given material as an offhand item. This is for v1.9+ servers only."; - + addItemOptions(this); } @@ -1196,9 +1285,9 @@ extend('ConditionPermission', 'Component'); function ConditionPermission() { this.super('Permission', Type.CONDITION, true); - + this.description = 'Applies child components if the caster has the required permission'; - + this.data.push(new StringValue('Permission', 'perm', 'some.permission') .setTooltip('The permission the player needs to have') ); @@ -1208,9 +1297,9 @@ extend('ConditionPotion', 'Component'); function ConditionPotion() { this.super('Potion', Type.CONDITION, true); - + this.description = 'Applies child components when the target has the potion effect.'; - + this.data.push(new ListValue('Type', 'type', [ 'Active', 'Not Active' ], 'Active') .setTooltip('Whether or not the potion should be active') ); @@ -1229,9 +1318,9 @@ extend('ConditionSkillLevel', 'Component'); function ConditionSkillLevel(skill) { this.super('Skill Level', Type.CONDITION, true); - + this.description = 'Applies child components when the skill level is with the range. This checks the skill level of the caster, not the targets.'; - + this.data.push(new StringValue('Skill', 'skill', skill) .setTooltip('The name of the skill to check the level of. If you want to check the current skill, enter the current skill\'s name anyway') ); @@ -1248,11 +1337,11 @@ function ConditionSlot() { this.super('Slot', Type.CONDITION, true); this.description = "Applies child components when the target player has a matching item in the given slot."; - + this.data.push(new StringListValue('Slots (one per line)', 'slot', [9]) .setTooltip('The slots to look at. Slots 0-8 are the hot bar, 9-35 are the main inventory, 36-39 are armor, and 40 is the offhand slot. Multiple slots will check if any of the slots match.') ); - + addItemOptions(this); } @@ -1260,13 +1349,13 @@ extend('ConditionStatus', 'Component'); function ConditionStatus() { this.super('Status', Type.CONDITION, true); - + this.description = 'Applies child components when the target has the status condition.'; - + this.data.push(new ListValue('Type', 'type', [ 'Active', 'Not Active' ], 'Active') .setTooltip('Whether or not the status should be active') ); - this.data.push(new ListValue('Status', 'status', [ 'Any', 'Curse', 'Disarm', 'Root', 'Silence', 'Stun' ], 'Any') + this.data.push(new ListValue('Status', 'status', [ 'Any', 'Absorb', 'Curse', 'Disarm', 'Invincible', 'Root', 'Silence', 'Stun' ], 'Any') .setTooltip('The status to look for') ); } @@ -1275,9 +1364,9 @@ extend('ConditionTime', 'Component'); function ConditionTime() { this.super('Time', Type.CONDITION, true); - + this.description = 'Applies child components when the server time matches the settings.'; - + this.data.push(new ListValue('Time', 'time', [ 'Day', 'Night' ], 'Day') .setTooltip('The time to check for in the current world') ); @@ -1287,9 +1376,9 @@ extend('ConditionTool', 'Component'); function ConditionTool() { this.super('Tool', Type.CONDITION, true); - + this.description = 'Applies child components when the target is wielding a matching tool.'; - + this.data.push(new ListValue('Material', 'material', [ 'Any', 'Wood', 'Stone', 'Iron', 'Gold', 'Diamond' ], 'Any') .setTooltip('The material the held tool needs to be made out of') ); @@ -1302,9 +1391,9 @@ extend('ConditionValue', 'Component'); function ConditionValue() { this.super('Value', Type.CONDITION, true); - + this.description = 'Applies child components if a stored value is within the given range.'; - + this.data.push(new StringValue('Key', 'key', 'value') .setTooltip('The unique string used for the value set by the Value mechanics.') ); @@ -1320,23 +1409,35 @@ extend('ConditionWater', 'Component'); function ConditionWater() { this.super('Water', Type.CONDITION, true); - + this.description = 'Applies child components when the target is in or out of water, depending on the settings.'; - + this.data.push(new ListValue('State', 'state', [ 'In Water', 'Out Of Water' ], 'In Water') .setTooltip('Whether or not the target needs to be in the water') ); } +extend('ConditionWeather', 'Component'); +function ConditionWeather() +{ + this.super('Weather', Type.CONDITION, true); + + this.description = 'Applies child components when the target\'s location has the given weather condition'; + + this.data.push(new ListValue('Type', 'type', [ 'None', 'Rain', 'Snow', 'Thunder' ], 'Rain') + .setTooltip('Whether or not the target needs to be in the water') + ); +} + // -- Mechanic constructors ---------------------------------------------------- // extend('MechanicAttribute', 'Component'); function MechanicAttribute() { this.super('Attribute', Type.MECHANIC, false); - + this.description = 'Gives a player bonus attributes temporarily.'; - + this.data.push(new StringValue('Attribute', 'key', 'Intelligence') .setTooltip('The name of the attribute to add to') ); @@ -1346,15 +1447,18 @@ function MechanicAttribute() this.data.push(new AttributeValue('Seconds', 'seconds', 3, 0) .setTooltip('How long in seconds to give the attributes to the player') ); + this.data.push(new ListValue('Stackable', 'stackable', [ 'True', 'False' ], 'False') + .setTooltip('[PREM] Whether or not applying multiple times stacks the effects') + ); } extend('MechanicBlock', 'Component'); -function MechanicBlock() +function MechanicBlock() { this.super('Block', Type.MECHANIC, false); - + this.description = 'Changes blocks to the given type of block for a limited duration.'; - + this.data.push(new ListValue('Shape', 'shape', [ 'Sphere', 'Cuboid' ], 'Sphere' ) .setTooltip('The shape of the region to change the blocks for') ); @@ -1379,12 +1483,12 @@ function MechanicBlock() this.data.push(new AttributeValue('Right Offset', 'right', 0, 0) .setTooltip('How far to the right the region should be of the target. A negative value will put it to the left.') ); - + // Sphere options this.data.push(new AttributeValue('Radius', 'radius', 3, 0).requireValue('shape', [ 'Sphere' ]) .setTooltip('The radius of the sphere region in blocks') ); - + // Cuboid options this.data.push(new AttributeValue('Width (X)', 'width', 5, 0).requireValue('shape', [ 'Cuboid' ]) .setTooltip('The width of the cuboid in blocks') @@ -1397,11 +1501,41 @@ function MechanicBlock() ); } +extend('MechanicBuff', 'Component'); +function MechanicBuff() +{ + this.super('Buff', Type.MECHANIC, false); + + this.description = 'Buffs combat stats of the target'; + + this.data.push(new ListValue('Immediate', 'immediate', [ 'True', 'False' ], 'False') + .setTooltip('Whether or not to apply the buff to the current damage trigger.') + ); + this.data.push(new ListValue('Type', 'type', [ 'DAMAGE', 'DEFENSE', 'SKILL_DAMAGE', 'SKILL_DEFENSE', 'HEALING' ], 'DAMAGE') + .requireValue('immediate', [ 'False' ]) + .setTooltip('What type of buff to apply. DAMAGE/DEFENSE is for regular attacks, SKILL_DAMAGE/SKILL_DEFENSE are for damage from abilities, and HEALING is for healing from abilities') + ); + this.data.push(new ListValue('Modifier', 'modifier', [ 'Flat', 'Multiplier' ], 'Flat') + .setTooltip('The sort of scaling for the buff. Flat will increase/reduce incoming damage by a fixed amount where Multiplier does it by a percentage of the damage. Multipliers above 1 will increase damage taken while multipliers below 1 reduce damage taken.') + ); + this.data.push(new StringValue('Category', 'category', '') + .requireValue('type', [ 'SKILL_DAMAGE', 'SKILL_DEFENSE' ]) + .setTooltip('What kind of skill damage to affect. If left empty, this will affect all skill damage.') + ); + this.data.push(new AttributeValue('Value', 'value', 1, 0) + .setTooltip('The amount to increase/decrease incoming damage by') + ); + this.data.push(new AttributeValue('Seconds', 'seconds', 3, 0) + .requireValue('immediate', [ 'False' ]) + .setTooltip('The duration of the buff in seconds') + ); +} + extend('MechanicCancel', 'Component'); function MechanicCancel() { this.super('Cancel', Type.MECHANIC, false); - + this.description = 'Cancels the event that caused the trigger this is under to go off. For example, damage based triggers will stop the damage that was dealt while the Launch trigger would stop the projectile from firing.'; } @@ -1409,9 +1543,9 @@ extend('MechanicCancelEffect', 'Component'); function MechanicCancelEffect() { this.super('Cancel Effect', Type.MECHANIC, false); - + this.description = 'Stops a particle effect prematurely.'; - + this.data.push(new StringValue('Effect Key', 'effect-key', 'default') .setTooltip('The key used when setting up the effect') ); @@ -1421,9 +1555,9 @@ extend('MechanicChannel', 'Component'); function MechanicChannel() { this.super('Channel', Type.MECHANIC, true); - + this.description = 'Applies child effects after a duration which can be interrupted. During the channel, the player cannot move, attack, or use other spells.'; - + this.data.push(new ListValue('Still', 'still', [ 'True', 'False' ], 'True') .setTooltip('Whether or not to hold the player in place while channeling') ); @@ -1436,9 +1570,9 @@ extend('MechanicCleanse', 'Component'); function MechanicCleanse() { this.super('Cleanse', Type.MECHANIC, false); - + this.description = 'Cleanses negative potion or status effects from the targets.'; - + this.data.push(new ListValue('Potion', 'potion', [ 'None', 'All', 'Blindness', 'Confusion', 'Hunger', 'Levitation', 'Poison', 'Slow', 'Slow Digging', 'Weakness', 'Wither' ], 'All') .setTooltip('The type of potion effect to remove from the target') ); @@ -1451,9 +1585,9 @@ extend('MechanicCommand', 'Component'); function MechanicCommand() { this.super('Command', Type.MECHANIC, false); - + this.description ='Executes a command for each of the targets either from them directly by oping them or via the console using their name.'; - + this.data.push(new StringValue('Command', 'command', '') .setTooltip('The command to execute') ); @@ -1466,9 +1600,9 @@ extend('MechanicCooldown', 'Component'); function MechanicCooldown() { this.super('Cooldown', Type.MECHANIC, false); - + this.description = "Lowers the cooldowns of the target's skill(s). If you provide a negative amount, it will increase the cooldown."; - + this.data.push(new StringValue('Skill (or "all")', 'skill', 'all') .setTooltip('The skill to modify the cooldown for') ); @@ -1484,9 +1618,9 @@ extend('MechanicDamage', 'Component'); function MechanicDamage() { this.super('Damage', Type.MECHANIC, false); - + this.description = 'Inflicts skill damage to each target. Multiplier type would be a percentage of the target health.'; - + this.data.push(new ListValue('Type', 'type', [ 'Damage', 'Multiplier', 'Percent Left', 'Percent Missing' ], 'Damage') .setTooltip('The unit to use for the amount of damage. Damage will deal flat damage, Multiplier will deal a percentage of the target\'s max health, Percent Left will deal a percentage of their current health, and Percent Missing will deal a percentage of the difference between their max health and current health') ); @@ -1496,15 +1630,18 @@ function MechanicDamage() this.data.push(new ListValue('True Damage', 'true', [ 'True', 'False' ], 'False') .setTooltip('Whether or not to deal true damage. True damage ignores armor and all plugin checks.') ); + this.data.push(new StringValue('Classifier', 'classifier', 'default') + .setTooltip('[PREMIUM ONLY] The type of damage to deal. Can act as elemental damage or fake physical damage') + ); } extend('MechanicDamageBuff', 'Component'); function MechanicDamageBuff() { this.super('Damage Buff', Type.MECHANIC, false); - + this.description = 'Modifies the physical damage dealt by each target by a multiplier or a flat amount for a limited duration. Negative flat amounts or multipliers less than one will reduce damage dealt while the opposite will increase damage dealt. (e.g. a 5% damage buff would be a multiplier or 1.05)'; - + this.data.push(new ListValue('Type', 'type', [ 'Flat', 'Multiplier' ], 'Flat') .setTooltip('The type of buff to apply. Flat increases damage by a fixed amount while multiplier increases it by a percentage.') ); @@ -1523,9 +1660,9 @@ extend('MechanicDamageLore', 'Component'); function MechanicDamageLore() { this.super('Damage Lore', Type.MECHANIC, false); - + this.description = 'Damages each target based on a value found in the lore of the item held by the caster.'; - + this.data.push(new ListValue("Hand", "hand", [ 'Main', 'Offhand' ], 'Main') .setTooltip('The hand to check for the item. Offhand items are MC 1.9+ only.') ); @@ -1538,15 +1675,18 @@ function MechanicDamageLore() this.data.push(new ListValue('True Damage', 'true', [ 'True', 'False' ], 'False') .setTooltip('Whether or not to deal true damage. True damage ignores armor and all plugin checks.') ); + this.data.push(new StringValue('Classifier', 'classifier', 'default') + .setTooltip('[PREMIUM ONLY] The type of damage to deal. Can act as elemental damage or fake physical damage') + ); } extend('MechanicDefenseBuff', 'Component'); function MechanicDefenseBuff() { this.super('Defense Buff', Type.MECHANIC, false); - + this.description = 'Modifies the physical damage taken by each target by a multiplier or a flat amount for a limited duration. Negative flag amounts or multipliers less than one will reduce damage taken while the opposite will increase damage taken. (e.g. a 5% defense buff would be a multiplier or 0.95, since you would be taking 95% damage)'; - + this.data.push(new ListValue('Type', 'type', [ 'Flat', 'Multiplier' ], 'Flat') .setTooltip('The type of buff to apply. Flat will increase/reduce incoming damage by a fixed amount where Multiplier does it by a percentage of the damage. Multipliers above 1 will increase damage taken while multipliers below 1 reduce damage taken.') ); @@ -1565,9 +1705,9 @@ extend('MechanicDelay', 'Component'); function MechanicDelay() { this.super('Delay', Type.MECHANIC, true); - + this.description = 'Applies child components after a delay.'; - + this.data.push(new AttributeValue('Delay', 'delay', 2, 0) .setTooltip('The amount of time to wait before applying child components in seconds') ); @@ -1577,16 +1717,16 @@ extend('MechanicDisguise', 'Component'); function MechanicDisguise() { this.super('Disguise', Type.MECHANIC, false); - + this.description = 'Disguises each target according to the settings. This mechanic requires the LibsDisguise plugin to be installed on your server.'; - + this.data.push(new AttributeValue('Duration', 'duration', -1, 0) .setTooltip('How long to apply the disguise for in seconds. Use a negative number to permanently disguise the targets.') ); this.data.push(new ListValue('Type', 'type', [ 'Mob', 'Player', 'Misc' ], 'Mob') .setTooltip('The type of disguise to use, as defined by the LibsDisguise plugin.') ); - + this.data.push(new ListValue('Mob', 'mob', [ 'Bat', 'Blaze', 'Cave Spider', 'Chicken', 'Cow', 'Creeper', 'Donkey', 'Elder Guardian', 'Ender Dragon', 'Enderman', 'Endermite', 'Ghast', 'Giant', 'Guardian', 'Horse', 'Iron Golem', 'Magma Cube', 'Mule', 'Mushroom Cow', 'Ocelot', 'Pig', 'Pig Zombie', 'Rabbit', 'Sheep', 'Shulker', 'Silverfish', 'Skeleton', 'Slime', 'Snowman', 'Spider', 'Squid', 'Undead Horse', 'Villager', 'Witch', 'Wither', 'Wither Skeleton', 'Wolf', 'Zombie', 'Zombie Villager'], 'Zombie') .requireValue('type', [ 'Mob' ]) .setTooltip('The type of mob to disguise the target as') @@ -1595,12 +1735,12 @@ function MechanicDisguise() .requireValue('type', [ 'Mob' ]) .setTooltip('Whether or not to use the adult variant of the mob') ); - + this.data.push(new StringValue('Player', 'player', 'Eniripsa96') .requireValue('type', [ 'Player' ]) .setTooltip('The player to disguise the target as') ); - + this.data.push(new ListValue('Misc', 'misc', [ 'Area Effect Cloud', 'Armor Stand', 'Arrow', 'Boat', 'Dragon Fireball', 'Dropped Item', 'Egg', 'Ender Crystal', 'Ender Pearl', 'Ender Signal', 'Experience Orb', 'Falling Block', 'Fireball', 'Firework', 'Fishing Hook', 'Item Frame', 'Leash Hitch', 'Minecart', 'Minecart Chest', 'Minecart Command', 'Minecart Furnace', 'Minecart Hopper', 'Minecart Mob Spawner', 'Minecart TNT', 'Painting', 'Primed TNT', 'Shulker Bullet', 'Snowball', 'Spectral Arrow', 'Splash Potion', 'Tipped Arrow', 'Thrown EXP Bottle', 'Wither Skull' ], 'Painting') .requireValue('type', [ 'Misc' ]) .setTooltip('The object to disguise the target as') @@ -1611,13 +1751,28 @@ function MechanicDisguise() ); } +extend('MechanicDurability', 'Component'); +function MechanicDurability() +{ + this.super('Durability', Type.MECHANIC, false); + + this.description = 'Lowers the durability of a held item'; + + this.data.push(new AttributeValue('Amount', 'amount', 1, 0) + .setTooltip('Amount to reduce the item\'s durability by') + ); + this.data.push(new ListValue('Offhand', 'offhand', [ 'True', 'False' ], 'False') + .setTooltip('Whether or not to apply to the offhand slot') + ); +} + extend('MechanicExplosion', 'Component'); function MechanicExplosion() { this.super('Explosion', Type.MECHANIC, false); - + this.description = 'Causes an explosion at the current target\'s position'; - + this.data.push(new AttributeValue('Power', 'power', 3, 0) .setTooltip('The strength of the explosion') ); @@ -1633,9 +1788,9 @@ extend('MechanicFire', 'Component'); function MechanicFire() { this.super('Fire', Type.MECHANIC, false); - + this.description = 'Sets the target on fire for a duration.'; - + this.data.push(new AttributeValue('Seconds', 'seconds', 3, 1) .setTooltip('The duration of the fire in seconds') ); @@ -1645,24 +1800,24 @@ extend('MechanicFlag', 'Component'); function MechanicFlag() { this.super('Flag', Type.MECHANIC, false); - + this.description = 'Marks the target with a flag for a duration. Flags can be checked by other triggers, spells or the related for interesting synergies and effects.'; - + this.data.push(new StringValue('Key', 'key', 'key') .setTooltip('The unique string for the flag. Use the same key when checking it in a Flag Condition.') ); this.data.push(new AttributeValue('Seconds', 'seconds', 3, 1) .setTooltip('The duration the flag should be set for. To set one indefinitely, use Flag Toggle.') - ); + ); } extend('MechanicFlagClear', 'Component'); function MechanicFlagClear() { this.super('Flag Clear', Type.MECHANIC, false); - + this.description = 'Clears a flag from the target.'; - + this.data.push(new StringValue('Key', 'key', 'key') .setTooltip('The unique string for the flag. This should match that of the mechanic that set the flag to begin with.') ); @@ -1672,21 +1827,48 @@ extend('MechanicFlagToggle', 'Component'); function MechanicFlagToggle() { this.super('Flag Toggle', Type.MECHANIC, false); - + this.description = 'Toggles a flag on or off for the target. This can be used to make toggle effects.'; - + this.data.push(new StringValue('Key', 'key', 'key') .setTooltip('The unique string for the flag. Use the same key when checking it in a Flag Condition') ); } +extend('MechanicFood', 'Component'); +function MechanicFood() +{ + this.super('Food', Type.MECHANIC, false); + + this.description = 'Adds or removes to a player\'s hunger and saturation'; + + this.data.push(new AttributeValue('Food', 'food', 1, 1) + .setTooltip('The amount of food to give. Use a negative number to lower the food meter.') + ); + this.data.push(new AttributeValue('Saturation', 'saturation', 0, 0) + .setTooltip('How much saturation to give. Use a negative number to lower saturation. This is the hidden value that determines how long until food starts going down.') + ); +} + +extend('MechanicForgetTargets', 'Component'); +function MechanicForgetTargets() +{ + this.super('Forget Targets', Type.MECHANIC, false); + + this.description = 'Clears targets stored by the "Remember Targets" mechanic'; + + this.data.push(new StringValue('Key', 'key', 'key') + .setTooltip('The unique key the targets were stored under') + ); +} + extend('MechanicHeal', 'Component'); function MechanicHeal() { this.super('Heal', Type.MECHANIC, false); - + this.description = 'Restores health to each target.'; - + this.data.push(new ListValue("Type", "type", [ "Health", "Percent" ], "Health") .setTooltip('The unit to use for the amount of health to restore. Health restores a flat amount while Percent restores a percentage of their max health.') ); @@ -1695,13 +1877,25 @@ function MechanicHeal() ); } +extend('MechanicHealthSet', 'Component'); +function MechanicHealthSet() +{ + this.super('Health Set', Type.MECHANIC, false); + + this.description = 'Sets the target\'s health to the specified amount, ignoring resistances, damage buffs, and so on'; + + this.data.push(new AttributeValue("Health", "health", 1, 0) + .setTooltip('The health to set to') + ); +} + extend('MechanicHeldItem', 'Component'); function MechanicHeldItem() { this.super('Held Item', Type.MECHANIC, false); - + this.description = 'Sets the held item slot of the target player. This will do nothing if trying to set it to a skill slot.'; - + this.data.push(new AttributeValue("Slot", "slot", 0, 0) .setTooltip('The slot to set it to') ); @@ -1711,9 +1905,9 @@ extend('MechanicImmunity', 'Component'); function MechanicImmunity() { this.super('Immunity', Type.MECHANIC, false); - + this.description = 'Provides damage immunity from one source for a duration.' - + this.data.push(new ListValue('Type', 'type', DAMAGE_TYPES, 'Poison') .setTooltip('The damage type to give an immunity for') ); @@ -1729,7 +1923,7 @@ extend('MechanicInterrupt', 'Component'); function MechanicInterrupt() { this.super('Interrupt', Type.MECHANIC, false); - + this.description = 'Interrupts any channeling being done by each target if applicable.'; } @@ -1737,9 +1931,9 @@ extend('MechanicItem', 'Component'); function MechanicItem() { this.super('Item', Type.MECHANIC, false); - + this.description = 'Gives each player target the item defined by the settings.'; - + this.data.push(new ListValue('Material', 'material', materialList, 'Arrow') .setTooltip('The type of item to give to the player') ); @@ -1755,7 +1949,7 @@ function MechanicItem() this.data.push(new ListValue('Custom', 'custom', [ 'True', 'False' ], 'False') .setTooltip('Whether or not to apply a custom name/lore to the item') ); - + this.data.push(new StringValue('Name', 'name', 'Name').requireValue('custom', [ 'True' ]) .setTooltip('The name of the item') ); @@ -1768,17 +1962,17 @@ extend('MechanicItemProjectile', 'Component'); function MechanicItemProjectile() { this.super('Item Projectile', Type.MECHANIC, true); - + this.description = 'Launches a projectile using an item as its visual that applies child components upon landing. The target passed on will be the collided target or the location where it landed if it missed.'; - - + + this.data.push(new ListValue('Item', 'item', materialList, 'Jack O Lantern') .setTooltip('The item type to use as a projectile') ), this.data.push(new IntValue('Item Data', 'item-data', 0) .setTooltip('The durability value for the item to use as a projectile, most notably for dyes or colored items like wool') ), - + addProjectileOptions(this); addEffectOptions(this, true); } @@ -1787,13 +1981,13 @@ extend('MechanicItemRemove', 'Component'); function MechanicItemRemove() { this.super('Item Remove', Type.MECHANIC, false); - + this.description = 'Removes an item from a player inventory. This does nothing to mobs.'; - + this.data.push(new AttributeValue('Amount', 'amount', 1, 0) .setTooltip('The amount of the item needed in the player\'s inventory') ); - + addItemOptions(this); } @@ -1801,9 +1995,12 @@ extend('MechanicLaunch', 'Component'); function MechanicLaunch() { this.super('Launch', Type.MECHANIC, false); - + this.description = 'Launches the target relative to their forward direction. Use negative values to go in the opposite direction (e.g. negative forward makes the target go backwards)'; - + + this.data.push(new ListValue('[PREM] Relative', 'relative', [ 'Target', 'Caster', 'Between'], 'Target') + .setTooltip('Determines what is considered "forward". Target uses the direction the target is facing, Caster uses the direction the caster is facing, and Between uses the direction from the caster to the target.') + ); this.data.push(new AttributeValue('Forward Speed', 'forward', 0, 0) .setTooltip('The speed to give the target in the direction they are facing') ); @@ -1819,9 +2016,12 @@ extend('MechanicLightning', 'Component'); function MechanicLightning() { this.super('Lightning', Type.MECHANIC, false); - + this.description = 'Strikes lightning on or near the target. Negative offsets will offset it in the opposite direction (e.g. negative forward offset puts it behind the target).'; - + + this.data.push(new ListValue('Damage', 'damage', ['True', 'False'], 'True') + .setTooltip('Whether or not the lightning should deal damage') + ); this.data.push(new AttributeValue('Forward Offset', 'forward', 0, 0) .setTooltip('How far in front of the target in blocks to place the lightning') ); @@ -1834,9 +2034,9 @@ extend('MechanicMana', 'Component'); function MechanicMana() { this.super('Mana', Type.MECHANIC, false); - + this.description = 'Restores or deducts mana from the target.'; - + this.data.push(new ListValue('Type', 'type', [ 'Mana', 'Percent' ], 'Mana') .setTooltip('The unit to use for the amount of mana to restore/drain. Mana does a flat amount while Percent does a percentage of their max mana') ); @@ -1849,9 +2049,9 @@ extend('MechanicMessage', 'Component'); function MechanicMessage() { this.super('Message', Type.MECHANIC, false); - + this.description = 'Sends a message to each player target. To include numbers from Value mechanics, use the filters {} where is the key the value is stored under.' - + this.data.push(new StringValue('Message', 'message', 'text') .setTooltip('The message to display') ); @@ -1861,11 +2061,11 @@ extend('MechanicParticle', 'Component'); function MechanicParticle() { this.super('Particle', Type.MECHANIC, false); - + this.description = 'Plays a particle effect about the target.'; - + addParticleOptions(this); - + this.data.push(new DoubleValue('Forward Offset', 'forward', 0) .setTooltip('How far forward in front of the target in blocks to play the particles. A negative value will go behind.') ); @@ -1881,9 +2081,9 @@ extend('MechanicParticleAnimation', 'Component'); function MechanicParticleAnimation() { this.super('Particle Animation', Type.MECHANIC, false); - + this.description = 'Plays an animated particle effect at the location of each target over time by applying various transformations.'; - + this.data.push(new IntValue('Steps', 'steps', 1, 0) .setTooltip('The number of times to play particles and apply translations each application.') ); @@ -1911,9 +2111,9 @@ function MechanicParticleAnimation() this.data.push(new IntValue('V-Cycles', 'v-cycles', 1) .setTooltip('How many times to move the animation position throughout the animation. Every other cycle moves it back to where it started. For example, two cycles would move it up and then back down.') ); - + addParticleOptions(this); - + this.data.push(new DoubleValue('Forward Offset', 'forward', 0) .setTooltip('How far forward in front of the target in blocks to play the particles. A negative value will go behind.') ); @@ -1929,9 +2129,9 @@ extend('MechanicParticleEffect', 'Component'); function MechanicParticleEffect() { this.super('Particle Effect', Type.MECHANIC, false); - + this.description = 'Plays a particle effect that follows the current target, using formulas to determine shape, size, and motion'; - + addEffectOptions(this, false); } @@ -1939,19 +2139,27 @@ extend('MechanicParticleProjectile', 'Component'); function MechanicParticleProjectile() { this.super('Particle Projectile', Type.MECHANIC, true); - + this.description = 'Launches a projectile using particles as its visual that applies child components upon landing. The target passed on will be the collided target or the location where it landed if it missed.'; - + addProjectileOptions(this); + + this.data.push(new DoubleValue('Gravity', 'gravity', 0) + .setTooltip('How much gravity to apply each tick. Negative values make it fall while positive values make it rise') + ); + this.data.push(new ListValue('Pierce', 'pierce', [ 'True', 'False' ], 'False') + .setTooltip('Whether or not this projectile should pierce through initial targets and continue hitting those behind them') + ); + addParticleOptions(this); - + this.data.push(new DoubleValue('Frequency', 'frequency', 0.05) .setTooltip('How often to play a particle effect where the projectile is. It is recommended not to change this value unless there are too many particles playing') ); this.data.push(new DoubleValue('Lifespan', 'lifespan', 3) .setTooltip('How long in seconds before the projectile will expire in case it doesn\'t hit anything') ); - + addEffectOptions(this, true); } @@ -1959,9 +2167,9 @@ extend('MechanicPassive', 'Component'); function MechanicPassive() { this.super('Passive', Type.MECHANIC, true); - + this.description = 'Applies child components continuously every period. The seconds value below is the period or how often it applies.'; - + this.data.push(new AttributeValue('Seconds', 'seconds', 1, 0) .setTooltip('The delay in seconds between each application') ); @@ -1971,9 +2179,9 @@ extend('MechanicPermission', 'Component'); function MechanicPermission() { this.super('Permission', Type.MECHANIC, true); - + this.description = 'Grants each player target a permission for a limited duration. This mechanic requires Vault with an accompanying permissions plugin in order to work.'; - + this.data.push(new StringValue('Permission', 'perm', 'plugin.perm.key') .setTooltip('The permission to give to the player') ); @@ -1986,9 +2194,9 @@ extend('MechanicPotion', 'Component'); function MechanicPotion() { this.super('Potion', Type.MECHANIC, false); - + this.description = 'Applies a potion effect to the target for a duration.'; - + this.data.push(new ListValue('Potion', 'potion', [ 'Absorption', 'Blindness', 'Confusion', 'Damage Resistance', 'Fast Digging', 'Fire Resistance', 'Glowing', 'Health Boost', 'Hunger', 'Increase Damage', 'Invisibility', 'Jump', 'Levitation', 'Luck', 'Night Vision', 'Poison', 'Regeneration', 'Saturation', 'Slow', 'Slow Digging', 'Speed', 'Unluck', 'Water Breathing', 'Weakness', 'Wither' ], 'Absorption') .setTooltip('The type of potion effect to apply') ); @@ -2007,9 +2215,9 @@ extend('MechanicPotionProjectile', 'Component'); function MechanicPotionProjectile() { this.super('Potion Projectile', Type.MECHANIC, true); - + this.description = 'Drops a splash potion from each target that does not apply potion effects by default. This will apply child elements when the potion lands. The targets supplied will be everything hit by the potion. If nothing is hit by the potion, the target will be the location it landed.'; - + this.data.push(new ListValue('Type', 'type', [ 'Fire Resistance', 'Instant Damage', 'Instant Heal', 'Invisibility', 'Night Vision', 'Poison', 'Regen', 'Slowness', 'Speed', 'Strength', 'Water', 'Water Breathing', 'Weakness' ], 'Fire Resistance') .setTooltip('The type of the potion to use for the visuals') ); @@ -2025,9 +2233,9 @@ extend('MechanicProjectile', 'Component'); function MechanicProjectile() { this.super('Projectile', Type.MECHANIC, true); - + this.description = 'Launches a projectile that applies child components on hit. The target supplied will be the struck target.'; - + this.data.push(new ListValue('Projectile', 'projectile', [ 'Arrow', 'Egg', 'Ghast Fireball', 'Snowball' ], 'Arrow') .setTooltip('The type of projectile to fire') ); @@ -2037,18 +2245,18 @@ function MechanicProjectile() this.data.push(new ListValue('Cost', 'cost', [ 'None', 'All', 'One' ], 'None') .setTooltip('The cost of the skill of the fired item. All will cost the same number of items as the skill fired.') ); - + addProjectileOptions(this); addEffectOptions(this, true); } extend('MechanicPurge', 'Component'); -function MechanicPurge() +function MechanicPurge() { this.super('Purge', Type.MECHANIC, false); - + this.description = 'Purges the target of positive potion effects or statuses'; - + this.data.push(new ListValue('Potion', 'potion', [ 'None', 'All', 'Absorption', 'Damage Resistance', 'Fast Digging', 'Fire Resistance', 'Health Boost', 'Increase Damage', 'Invisibility', 'Jump', 'Night Vision', 'Regeneration', 'Saturation', 'Speed', 'Water Breathing' ], 'All') .setTooltip('The potion effect to remove from the target, if any') ); @@ -2061,24 +2269,27 @@ extend('MechanicPush', 'Component'); function MechanicPush() { this.super('Push', Type.MECHANIC, false); - + this.description = 'Pushes the target relative to the caster. This will do nothing if used with the caster as the target. Positive numbers apply knockback while negative numbers pull them in.'; - + this.data.push(new ListValue('Type', 'type', [ 'Fixed', 'Inverse', 'Scaled' ], 'Fixed') .setTooltip('How to scale the speed based on relative position. Fixed does the same speed to all targets. Inverse pushes enemies farther away faster. Scaled pushes enemies closer faster.') ); this.data.push(new AttributeValue('Speed', 'speed', 3, 1) .setTooltip('How fast to push the target away. Use a negative value to pull them closer.') ); + this.data.push(new StringValue('Source', 'source', 'none') + .setTooltip('The source to push/pull from. This should be a key used in a Remember Targets mechanic. If no targets are remembered, this will default to the caster.') + ); } extend('MechanicRememberTargets', 'Component'); function MechanicRememberTargets() { this.super('Remember Targets', Type.MECHANIC, false); - + this.description = 'Stores the current targets for later use under a specified key'; - + this.data.push(new StringValue('Key', 'key', 'target') .setTooltip('The unique key to store the targets under. The "Remember" target will use this key to apply effects to the targets later on.') ); @@ -2088,9 +2299,9 @@ extend('MechanicRepeat', 'Component'); function MechanicRepeat() { this.super('Repeat', Type.MECHANIC, true); - + this.description = 'Applies child components multiple times. When it applies them is determined by the delay (seconds before the first application) and period (seconds between successive applications).'; - + this.data.push(new AttributeValue('Repetitions', 'repetitions', 3, 0) .setTooltip('How many times to activate child components') ); @@ -2100,26 +2311,29 @@ function MechanicRepeat() this.data.push(new DoubleValue('Delay', 'delay', 0) .setTooltip('The initial delay before starting to apply child components') ); + this.data.push(new ListValue('Stop on Fail', 'stop-on-fail', [ 'True', 'False' ], 'False') + .setTooltip('Whether or not to stop the repeat task early if the effects fail') + ); } extend('MechanicSound', 'Component'); function MechanicSound() { this.super('Sound', Type.MECHANIC, false); - + this.description = "Plays a sound at the target's location."; - + this.data.push(new ListValue('Server Version', 'version', [ '1.9+', 'Pre 1.9' ], '1.9+') .setTooltip('The version of the server this will be playing for. Servers 1.9 and later have much different sounds available') ); - + this.data.push(new ListValue('Sound', 'newsound', SOUNDS_POST, 'Ambience Cave').requireValue('version', [ '1.9+' ]) .setTooltip('The sound clip to play') ); this.data.push(new ListValue('Sound', 'sound', SOUNDS_PRE, 'Ambience Cave').requireValue('version', [ 'Pre 1.9' ]) .setTooltip('The sound clip to play') ); - + this.data.push(new AttributeValue('Volume', 'volume', 100, 0) .setTooltip('The volume of the sound as a percentage. Numbers above 100 will not get any louder, but will be heard from a farther distance') ); @@ -2132,9 +2346,9 @@ extend('MechanicSpeed', 'Component'); function MechanicSpeed() { this.super('Speed', Type.MECHANIC, false); - + this.description = 'Modifies the base speed of a player using a multiplier (stacks with potions)'; - + this.data.push(new AttributeValue('Multiplier', 'multiplier', 1.2, 0) .setTooltip('The multiplier of the player\'s base speed to use') ); @@ -2147,9 +2361,9 @@ extend('MechanicStatus', 'Component'); function MechanicStatus() { this.super('Status', Type.MECHANIC, false); - + this.description = 'Applies a status effect to the target for a duration.'; - + this.data.push(new ListValue('Status', 'status', [ 'Absorb', 'Curse', 'Disarm', 'Invincible', 'Root', 'Silence', 'Stun' ], 'Stun') .setTooltip('The status to apply') ); @@ -2162,8 +2376,84 @@ extend('MechanicTaunt', 'Component'); function MechanicTaunt() { this.super('Taunt', Type.MECHANIC, false); - - this.description = 'Draws aggro of targeted creatures. This only works on newer server versions.'; + + this.description = 'Draws aggro of targeted creatures. Regular mobs are set to attack the caster. The Spigot/Bukkit API for this was not functional on older versions, so it may not work on older servers. For MythicMobs, this uses their aggro system using the amount chosen below.'; + + this.data.push(new AttributeValue('Amount', 'amount', 1, 0) + .setTooltip('The amount of aggro to apply if MythicMobs is active. Use negative amounts to reduce aggro') + ); +} + +extend('MechanicTrigger', 'Component'); +function MechanicTrigger() +{ + this.super('Trigger', Type.MECHANIC, true); + + this.description = 'Listens for a trigger on the current targets for a duration.'; + + this.data.push(new ListValue('Trigger', 'trigger', [ 'Crouch', 'Death', 'Environment Damage', 'Kill', 'Land', 'Launch', 'Physical Damage', 'Skill Damage', 'Took Physical Damage', 'Took Skill Damage' ], 'Death') + .setTooltip('The trigger to listen for') + ); + this.data.push(new AttributeValue('Duration', 'duration', 5, 0) + .setTooltip('How long to listen to the trigger for') + ); + this.data.push(new ListValue('Stackable', 'stackable', [ 'True', 'False', ], 'True') + .setTooltip('Whether or not different players (or the same player) can listen to the same target at the same time') + ); + this.data.push(new ListValue('Once', 'once', [ 'True', 'False' ], 'True') + .setTooltip('Whether or not the trigger should only be used once each cast. When false, the trigger can execute as many times as it happens for the duration.') + ); + + // CROUCH + this.data.push(new ListValue('Type', 'type', [ 'Start Crouching', 'Stop Crouching', 'Both' ], 'Start Crouching') + .requireValue('trigger', [ 'Crouch' ]) + .setTooltip('Whether or not you want to apply components when crouching or not crouching') + ); + + // ENVIRONMENT_DAMAGE + this.data.push(new ListValue('Type', 'type', DAMAGE_TYPES, 'FALL') + .requireValue('trigger', [ 'Environment Damage' ]) + .setTooltip('The source of damage to apply for') + ); + + // LAND + this.data.push(new DoubleValue('Min Distance', 'min-distance', 0) + .requireValue('trigger', [ 'Land' ]) + .setTooltip('The minimum distance the player should fall before effects activating.') + ); + + // LAUNCH + this.data.push(new ListValue('Type', 'type', [ 'Any', 'Arrow', 'Egg', 'Ender Pearl', 'Fireball', 'Fishing Hook', 'Snowball' ], 'Any') + .requireValue('trigger', [ 'Launch' ]) + .setTooltip('The type of projectile that should be launched.') + ); + + // PHYSICAL + this.data.push(new ListValue('Type', 'type', [ 'Both', 'Melee', 'Projectile' ], 'Both') + .requireValue('trigger', [ 'Physical Damage', 'Took Physical Damage' ]) + .setTooltip('The type of damage dealt') + ); + + // SKILL + this.data.push(new StringValue('Category', 'category', '') + .requireValue('trigger', [ 'Skill Damage', 'Took Skill Damage' ]) + .setTooltip('The type of skill damage to apply for. Leave this empty to apply to all skill damage.') + ); + + // DAMAGE + var damageTriggers = [ 'Physical Damage', 'Skill Damage', 'Took Physical Damage', 'Took Skill Damage' ]; + this.data.push(new ListValue('Target Listen Target', 'target', [ 'True', 'False' ], 'True') + .requireValue('trigger', damageTriggers) + .setTooltip('True makes children target the target that has been listened to. False makes children target the entity fighting the target entity.') + ); + this.data.push(new DoubleValue("Min Damage", "dmg-min", 0) + .requireValue('trigger', damageTriggers) + .setTooltip('The minimum damage that needs to be dealt') + ); + this.data.push(new DoubleValue("Max Damage", "dmg-max", 999) + .requireValue('trigger', damageTriggers) + .setTooltip('The maximum damage that needs to be dealt') + ); } extend('MechanicValueAdd', 'Component'); @@ -2196,6 +2486,36 @@ function MechanicValueAttribute() ); } +extend('MechanicValueCopy', 'Component'); +function MechanicValueCopy() +{ + this.super('Value Copy', Type.MECHANIC, false); + + this.description = 'Copies a stored value from the caster to the target or vice versa'; + + this.data.push(new StringValue('Key', 'key', 'value') + .setTooltip('The unique key to store the value under. This key can be used in place of attribute values to use the stored value.') + ); + this.data.push(new StringValue('Destination', 'destination', 'value') + .setTooltip('The key to copy the original value to') + ); + this.data.push(new ListValue('To target', 'to-target', [ 'True', 'False' ], 'True') + .setTooltip('The amount to add to the value') + ); +} + +extend('MechanicValueDistance', 'Component'); +function MechanicValueDistance() +{ + this.super('Value Distance', Type.MECHANIC, false); + + this.description = 'Stores the distance between the target and the caster into a value'; + + this.data.push(new StringValue('Key', 'key', 'attribute') + .setTooltip('The unique key to store the value under. This key can be used in place of attribute values to use the stored value.') + ); +} + extend('MechanicValueHealth', 'Component'); function MechanicValueHealth() { @@ -2206,6 +2526,9 @@ function MechanicValueHealth() this.data.push(new StringValue('Key', 'key', 'value') .setTooltip('The unique key to store the value under. This key can be used in place of attribute values to use the stored value.') ); + this.data.push(new ListValue('Type', 'type', [ 'Current', 'Max', 'Missing', 'Percent' ], 'Current') + .setTooltip('Current provides the health the target has, max provides their total health, missing provides how much health they have lost, and percent is the ratio of health to total health.') + ); } extend('MechanicValueLocation', 'Component'); @@ -2272,15 +2595,18 @@ function MechanicValueMana() this.data.push(new StringValue('Key', 'key', 'value') .setTooltip('The unique key to store the value under. This key can be used in place of attribute values to use the stored value.') ); + this.data.push(new ListValue('Type', 'type', [ 'Current', 'Max', 'Missing', 'Percent' ], 'Current') + .setTooltip('Current provides the mana the target has, max provides their total mana, missing provides how much mana they have lost, and percent is the ratio of health to total mana.') + ); } extend('MechanicValueMultiply', 'Component'); function MechanicValueMultiply() { this.super('Value Multiply', Type.MECHANIC, false); - + this.description = 'Multiplies a stored value under a unique key for the caster. If the value wasn\'t set before, this will not do anything.'; - + this.data.push(new StringValue('Key', 'key', 'value') .setTooltip('The unique key to store the value under. This key can be used in place of attribute values to use the stored value.') ); @@ -2289,6 +2615,24 @@ function MechanicValueMultiply() ); } +extend('MechanicValuePlaceholder', 'Component'); +function MechanicValuePlaceholder() +{ + this.super('Value Placeholder', Type.MECHANIC, false); + + this.description = 'Uses a placeholder string and stores it as a value for the caster'; + + this.data.push(new StringValue('Key', 'key', 'value') + .setTooltip('The unique key to store the value under. This key can be used in place of attribute values to use the stored value.') + ); + this.data.push(new ListValue("Type", "type", [ 'Number', 'String' ], 'Number') + .setTooltip('The type of value to store. Number values require numeric placeholders. String values can be used in messages or commands.') + ); + this.data.push(new StringValue('Placeholder', 'placeholder', '%player_food_level%') + .setTooltip('The placeholder string to use. Can contain multiple placeholders if using the String type.') + ); +} + extend('MechanicValueRandom', 'Component') function MechanicValueRandom() { @@ -2442,6 +2786,9 @@ function MechanicWolf() this.data.push(new AttributeValue('Damage', 'damage', 3, 0) .setTooltip('The damage dealt by the wolf each attack') ); + this.data.push(new ListValue('Sitting', 'sitting', [ 'True', 'False' ], 'False') + .setTooltip('[PREMIUM] whether or not the wolf starts of sitting') + ); this.data.push(new AttributeValue('Duration', 'seconds', 10, 0) .setTooltip('How long to summon the wolf for') ); @@ -2689,7 +3036,7 @@ function addEffectOptions(component, optional) component.data.push(opt(new StringValue('Shape Size', '-shape-size', '1') .setTooltip('Formula for deciding the size of the shape. This can be any sort of formula using the operations defined in the wiki.') )); - component.data.push(opt(new StringValue('Animation', '-animation', 'circle') + component.data.push(opt(new StringValue('Animation', '-animation', 'one-circle') .setTooltip('Key of a formula for deciding where the particle effect moves relative to the target. View "effects.yml" for a list of defined formulas and their keys.') )); component.data.push(opt(new ListValue('Animation Direction', '-anim-dir', [ 'XY', 'YZ', 'XZ' ], 'XZ') @@ -2785,4 +3132,4 @@ function appendOptional(value) function appendNone(value) { return value; -} \ No newline at end of file +} diff --git a/editor/js/input.js b/editor/js/input.js index a9ff74e8..150d0a27 100644 --- a/editor/js/input.js +++ b/editor/js/input.js @@ -13,6 +13,13 @@ function requireValue(key, values) return this; } +function copyRequirements(source, target) { + if (source.requirements) { + target.requirements = source.requirements; + } + return target; +} + /** * Applies the values required from above */ @@ -71,7 +78,8 @@ function checkRequireValue(e) */ function setTooltip(text) { - this.tooltip = text; + if (text.charAt(0) == '[') this.tooltip = text; + else this.tooltip = '[' + this.key + '] ' + text; return this; } @@ -200,8 +208,8 @@ IndexListValue.prototype.load = function(value) * * @param {string} name - the display name of the value * @param {string} key - the config key for the value - * @param {Array} list - the list of available options - * @param {Number} value - the current selected value + * @param {string[]} list - the list of available options + * @param {string} value - the current selected value * * @constructor */ @@ -255,7 +263,7 @@ ListValue.prototype.createHTML = function(target) option.innerHTML = this.list[i]; this.select.add(option); - var lower = this.list[i].toLowerCase(); + var lower = this.list[i].toLowerCase().replace('_', ' '); if (lower === vLower || (selected == -1 && this.list[i] == 'None')) { selected = i; @@ -1246,4 +1254,4 @@ ByteListValue.prototype.getSaveString = function(spacing) ByteListValue.prototype.load = function(value) { this.value = value; -} \ No newline at end of file +} diff --git a/editor/js/material.js b/editor/js/material.js index 22502e6c..5f378dfb 100644 --- a/editor/js/material.js +++ b/editor/js/material.js @@ -45,8 +45,12 @@ var materialList = [ 'BIRCH_FENCE', 'BIRCH_FENCE_GATE', 'BIRCH_WOOD_STAIRS', + 'BLACK_GLAZED_TERRACOTTA', + 'BLACK_SHULKER_BOX', 'BLAZE_POWDER', 'BLAZE_ROD', + 'BLUE_GLAZED_TERRACOTTA', + 'BLUE_SHULKER_BOX', 'BOAT', 'BOAT_ACACIA', 'BOAT_BIRCH', @@ -54,6 +58,7 @@ var materialList = [ 'BOAT_JUNGLE', 'BOAT_SPRUCE', 'BONE', + 'BONE_BLOCK', 'BOOK', 'BOOK_AND_QUILL', 'BOOKSHELF', @@ -64,7 +69,9 @@ var materialList = [ 'BREWING_STAND_ITEM', 'BRICK', 'BRICK_STAIRS', + 'BROWN_GLAZED_TERRACOTTA', 'BROWN_MUSHROOM', + 'BROWN_SHULKER_BOX', 'BUCKET', 'BURNING_FURNACE', 'CACTUS', @@ -100,6 +107,8 @@ var materialList = [ 'COMMAND_MINECART', 'COMMAND_REPEATING', 'COMPASS', + 'CONCRETE', + 'CONCRETE_POWDER', 'COOKED_BEEF', 'COOKED_CHICKEN', 'COOKED_FISH', @@ -107,6 +116,8 @@ var materialList = [ 'COOKED_RABBIT', 'COOKIE', 'CROPS', + 'CYAN_GLAZED_TERRACOTTA', + 'CYAN_SHULKER_BOX', 'DARK_OAK_DOOR', 'DARK_OAK_DOOR_ITEM', 'DARK_OAK_FENCE', @@ -202,7 +213,11 @@ var materialList = [ 'GRASS', 'GRASS_PATH', 'GRAVEL', + 'GRAY_GLAZED_TERRACOTTA', + 'GRAY_SHULKER_BOX', + 'GREEN_GLAZED_TERRACOTTA', 'GREEN_RECORD', + 'GREEN_SHULKER_BOX', 'GRILLED_PORK', 'HARD_CLAY', 'HAY_BLOCK', @@ -224,6 +239,7 @@ var materialList = [ 'IRON_HOE', 'IRON_INGOT', 'IRON_LEGGINGS', + 'IRON_NUGGET', 'IRON_ORE', 'IRON_PICKAXE', 'IRON_PLATE', @@ -238,6 +254,7 @@ var materialList = [ 'JUNGLE_FENCE', 'JUNGLE_FENCE_GATE', 'JUNGLE_WOOD_STAIRS', + 'KNOWLEDGE_BOOK', 'LADDER', 'LAPIS_BLOCK', 'LAPIS_ORE', @@ -252,10 +269,17 @@ var materialList = [ 'LEAVES', 'LEAVES_2', 'LEVER', + 'LIGHT_BLUE_GLAZED_TERRACOTTA', + 'LIGHT_BLUE_SHULKER_BOX', + 'LIME_GLAZED_TERRACOTTA', + 'LIME_SHULKER_BOX', 'LINGERING_POTION', 'LOG', 'LOG_2', 'LONG_GRASS', + 'MAGENTA_GLAZED_TERRACOTTA', + 'MAGENTA_SHULKER_BOX', + 'MAGMA', 'MAGMA_CREAM', 'MAP', 'MELON', @@ -278,13 +302,19 @@ var materialList = [ 'NETHER_FENCE', 'NETHER_STALK', 'NETHER_STAR', + 'NETHER_WART_BLOCK', 'NETHER_WARTS', 'NETHERRACK', 'NOTE_BLOCK', + 'OBSERVER', 'OBSIDIAN', + 'ORANGE_GLAZED_TERRACOTTA', + 'ORANGE_SHULKER_BOX', 'PACKED_ICE', 'PAINTING', 'PAPER', + 'PINK_GLAZED_TERRACOTTA', + 'PINK_SHULKER_BOX', 'PISTON_BASE', 'PISTON_EXTENSION', 'PISTON_MOVING_PIECE', @@ -304,6 +334,8 @@ var materialList = [ 'PUMPKIN_PIE', 'PUMPKIN_SEEDS', 'PUMPKIN_STEM', + 'PURPLE_GLAZED_TERRACOTTA', + 'PURPLE_SHULKER_BOX', 'PURPUR_BLOCK', 'PURPUR_DOUBLE_SLAB', 'PURPUR_PILLAR', @@ -331,10 +363,13 @@ var materialList = [ 'RECORD_7', 'RECORD_8', 'RECORD_9', + 'RED_GLAZED_TERRACOTTA', 'RED_MUSHROOM', + 'RED_NETHER_BRICK', 'RED_ROSE', 'RED_SANDSTONE', 'RED_SANDSTONE_STAIRS', + 'RED_SHULKER_BOX', 'REDSTONE', 'REDSTONE_BLOCK', 'REDSTONE_COMPARATOR', @@ -356,8 +391,11 @@ var materialList = [ 'SEEDS', 'SHEARS', 'SHIELD', + 'SHULKER_SHELL', 'SIGN', 'SIGN_POST', + 'SILVER_GLAZED_TERRACOTTA', + 'SILVER_SHULKER_BOX', 'SKULL', 'SKULL_ITEM', 'SLIME_BALL', @@ -399,6 +437,7 @@ var materialList = [ 'STORAGE_MINECART', 'STRING', 'STRUCTURE_BLOCK', + 'STRUCTURE_VOID', 'SUGAR', 'SUGAR_CANE', 'SUGAR_CANE_BLOCK', @@ -407,6 +446,7 @@ var materialList = [ 'TIPPED_ARROW', 'TNT', 'TORCH', + 'TOTEM', 'TRAP_DOOR', 'TRAPPED_CHEST', 'TRIPWIRE', @@ -420,6 +460,8 @@ var materialList = [ 'WATER_LILY', 'WEB', 'WHEAT', + 'WHITE_GLAZED_TERRACOTTA', + 'WHITE_SHULKER_BOX', 'WOOD', 'WOOD_AXE', 'WOOD_BUTTON', @@ -436,7 +478,9 @@ var materialList = [ 'WOOL', 'WORKBENCH', 'WRITTEN_BOOK', - 'YELLOW_FLOWER' + 'YELLOW_FLOWER', + 'YELLOW_GLAZED_TERRACOTTA', + 'YELLOW_SHULKER_BOX' ]; for (var i = 0; i < materialList.length; i++) diff --git a/editor/js/skill.js b/editor/js/skill.js index 42571d47..9b1256d5 100644 --- a/editor/js/skill.js +++ b/editor/js/skill.js @@ -29,6 +29,7 @@ function Skill(name) new AttributeValue('Cost', 'cost', 1, 0).setTooltip('The amount of skill points needed to unlock and upgrade this skill'), new AttributeValue('Cooldown', 'cooldown', 0, 0).setTooltip('The time in seconds before the skill can be cast again (only works with the Cast trigger)'), new AttributeValue('Mana', 'mana', 0, 0).setTooltip('The amount of mana it takes to cast the skill (only works with the Cast trigger)'), + new AttributeValue('Min Spent', 'points-spent-req', 0, 0).setTooltip('The amount of skill points that need to be spent before upgrading this skill'), new StringValue('Cast Message', 'msg', '&6{player} &2has cast &6{skill}').setTooltip('The message to display to players around the caster when the skill is cast. The radius of the area is in the config.yml options'), new StringValue('Combo', 'combo', '').setTooltip('The click combo to assign the skill (if enabled). Use L, R, and S for the types of clicks separated by spaces. For example, "L L R R" would work for 4 click combos.'), new ListValue('Indicator', 'indicator', [ '2D', '3D', 'None' ], '2D').setTooltip('[PREMIUM] What sort of display to use for cast previews. This applies to the "hover bar" in the casting bars setup.'), @@ -43,7 +44,8 @@ function Skill(name) '', '&2Mana: {attr:mana}', '&2Cooldown: {attr:cooldown}' - ]).setTooltip('The description shown for the item in skill trees. Include values of mechanics such as damage dealt using their "Icon Key" values') + ]).setTooltip('The description shown for the item in skill trees. Include values of mechanics such as damage dealt using their "Icon Key" values'), + new StringListValue('Incompatible', 'incompatible', []).setTooltip('List of skill names that must not be upgraded in order to upgrade this skill') ]; } @@ -74,8 +76,9 @@ Skill.prototype.createFormHTML = function() header.innerHTML = 'Skill Details'; form.appendChild(header); - var h = document.createElement('hr'); - form.appendChild(h); + form.appendChild(document.createElement('hr')); + form.appendChild(this.createEditButton(form)); + form.appendChild(document.createElement('hr')); this.data[3].list.splice(1, this.data[3].list.length - 1); for (var i = 0; i < skills.length; i++) @@ -93,7 +96,15 @@ Skill.prototype.createFormHTML = function() var hr = document.createElement('hr'); form.appendChild(hr); - var done = document.createElement('h5'); + form.appendChild(this.createEditButton(form)); + + var target = document.getElementById('skillForm'); + target.innerHTML = ''; + target.appendChild(form); +} + +Skill.prototype.createEditButton = function(form) { + var done = document.createElement('h5'); done.className = 'doneButton'; done.innerHTML = 'Edit Effects', done.skill = this; @@ -105,11 +116,7 @@ Skill.prototype.createFormHTML = function() this.form.parentNode.removeChild(this.form); showSkillPage('builder'); }); - form.appendChild(done); - - var target = document.getElementById('skillForm'); - target.innerHTML = ''; - target.appendChild(form); + return done; } /** @@ -165,13 +172,13 @@ Skill.prototype.getSaveString = function() saveString += this.data[0].value + ":\n"; for (var i = 0; i < this.data.length; i++) { - if (this.data[i] instanceof AttributeValue) continue; + if (isAttribute(this.data[i])) continue; saveString += this.data[i].getSaveString(' '); } saveString += ' attributes:\n'; for (var i = 0; i < this.data.length; i++) { - if (this.data[i] instanceof AttributeValue) + if (isAttribute(this.data[i])) { saveString += this.data[i].getSaveString(' '); } @@ -188,6 +195,10 @@ Skill.prototype.getSaveString = function() return saveString; } +function isAttribute(input) { + return (input instanceof AttributeValue) || (input.key == 'incompatible'); +} + /** * Loads skill data from the config lines stating at the given index * diff --git a/editor/js/sounds.js b/editor/js/sounds.js index 19ddccd1..99732b47 100644 --- a/editor/js/sounds.js +++ b/editor/js/sounds.js @@ -23,9 +23,12 @@ var SOUNDS_POST = [ 'BLOCK_DISPENSER_DISPENSE', 'BLOCK_DISPENSER_FAIL', 'BLOCK_DISPENSER_LAUNCH', - 'BLOCK_END_GATEWAY_SPAWN', + 'BLOCK_ENCHANTMENT_TABLE_USE', 'BLOCK_ENDERCHEST_CLOSE', 'BLOCK_ENDERCHEST_OPEN', + 'BLOCK_END_GATEWAY_SPAWN', + 'BLOCK_END_PORTAL_FRAME_FILL', + 'BLOCK_END_PORTAL_SPAWN', 'BLOCK_FENCE_GATE_CLOSE', 'BLOCK_FENCE_GATE_OPEN', 'BLOCK_FIRE_AMBIENT', @@ -68,10 +71,15 @@ var SOUNDS_POST = [ 'BLOCK_METAL_STEP', 'BLOCK_NOTE_BASEDRUM', 'BLOCK_NOTE_BASS', + 'BLOCK_NOTE_BELL', + 'BLOCK_NOTE_CHIME', + 'BLOCK_NOTE_FLUTE', + 'BLOCK_NOTE_GUITAR', 'BLOCK_NOTE_HARP', 'BLOCK_NOTE_HAT', 'BLOCK_NOTE_PLING', 'BLOCK_NOTE_SNARE', + 'BLOCK_NOTE_XYLOPHONE', 'BLOCK_PISTON_CONTRACT', 'BLOCK_PISTON_EXTEND', 'BLOCK_PORTAL_AMBIENT', @@ -83,6 +91,8 @@ var SOUNDS_POST = [ 'BLOCK_SAND_HIT', 'BLOCK_SAND_PLACE', 'BLOCK_SAND_STEP', + 'BLOCK_SHULKER_BOX_CLOSE', + 'BLOCK_SHULKER_BOX_OPEN', 'BLOCK_SLIME_BREAK', 'BLOCK_SLIME_FALL', 'BLOCK_SLIME_HIT', @@ -106,8 +116,12 @@ var SOUNDS_POST = [ 'BLOCK_TRIPWIRE_CLICK_OFF', 'BLOCK_TRIPWIRE_CLICK_ON', 'BLOCK_TRIPWIRE_DETACH', - 'BLOCK_WATER_AMBIENT', 'BLOCK_WATERLILY_PLACE', + 'BLOCK_WATER_AMBIENT', + 'BLOCK_WOODEN_DOOR_CLOSE', + 'BLOCK_WOODEN_DOOR_OPEN', + 'BLOCK_WOODEN_TRAPDOOR_CLOSE', + 'BLOCK_WOODEN_TRAPDOOR_OPEN', 'BLOCK_WOOD_BREAK', 'BLOCK_WOOD_BUTTON_CLICK_OFF', 'BLOCK_WOOD_BUTTON_CLICK_ON', @@ -117,10 +131,6 @@ var SOUNDS_POST = [ 'BLOCK_WOOD_PRESSUREPLATE_CLICK_OFF', 'BLOCK_WOOD_PRESSUREPLATE_CLICK_ON', 'BLOCK_WOOD_STEP', - 'BLOCK_WOODEN_DOOR_CLOSE', - 'BLOCK_WOODEN_DOOR_OPEN', - 'BLOCK_WOODEN_TRAPDOOR_CLOSE', - 'BLOCK_WOODEN_TRAPDOOR_OPEN', 'ENCHANT_THORNS_HIT', 'ENTITY_ARMORSTAND_BREAK', 'ENTITY_ARMORSTAND_FALL', @@ -139,6 +149,9 @@ var SOUNDS_POST = [ 'ENTITY_BLAZE_DEATH', 'ENTITY_BLAZE_HURT', 'ENTITY_BLAZE_SHOOT', + 'ENTITY_BOAT_PADDLE_LAND', + 'ENTITY_BOAT_PADDLE_WATER', + 'ENTITY_BOBBER_RETRIEVE', 'ENTITY_BOBBER_SPLASH', 'ENTITY_BOBBER_THROW', 'ENTITY_CAT_AMBIENT', @@ -171,6 +184,7 @@ var SOUNDS_POST = [ 'ENTITY_ELDER_GUARDIAN_CURSE', 'ENTITY_ELDER_GUARDIAN_DEATH', 'ENTITY_ELDER_GUARDIAN_DEATH_LAND', + 'ENTITY_ELDER_GUARDIAN_FLOP', 'ENTITY_ELDER_GUARDIAN_HURT', 'ENTITY_ELDER_GUARDIAN_HURT_LAND', 'ENTITY_ENDERDRAGON_AMBIENT', @@ -180,6 +194,7 @@ var SOUNDS_POST = [ 'ENTITY_ENDERDRAGON_GROWL', 'ENTITY_ENDERDRAGON_HURT', 'ENTITY_ENDERDRAGON_SHOOT', + 'ENTITY_ENDEREYE_DEATH', 'ENTITY_ENDEREYE_LAUNCH', 'ENTITY_ENDERMEN_AMBIENT', 'ENTITY_ENDERMEN_DEATH', @@ -192,9 +207,16 @@ var SOUNDS_POST = [ 'ENTITY_ENDERMITE_HURT', 'ENTITY_ENDERMITE_STEP', 'ENTITY_ENDERPEARL_THROW', + 'ENTITY_EVOCATION_FANGS_ATTACK', + 'ENTITY_EVOCATION_ILLAGER_AMBIENT', + 'ENTITY_EVOCATION_ILLAGER_CAST_SPELL', + 'ENTITY_EVOCATION_ILLAGER_DEATH', + 'ENTITY_EVOCATION_ILLAGER_HURT', + 'ENTITY_EVOCATION_ILLAGER_PREPARE_ATTACK', + 'ENTITY_EVOCATION_ILLAGER_PREPARE_SUMMON', + 'ENTITY_EVOCATION_ILLAGER_PREPARE_WOLOLO', 'ENTITY_EXPERIENCE_BOTTLE_THROW', 'ENTITY_EXPERIENCE_ORB_PICKUP', - 'ENTITY_EXPERIENCE_ORB_TOUCH', 'ENTITY_FIREWORK_BLAST', 'ENTITY_FIREWORK_BLAST_FAR', 'ENTITY_FIREWORK_LARGE_BLAST', @@ -247,22 +269,42 @@ var SOUNDS_POST = [ 'ENTITY_HOSTILE_SMALL_FALL', 'ENTITY_HOSTILE_SPLASH', 'ENTITY_HOSTILE_SWIM', + 'ENTITY_HUSK_AMBIENT', + 'ENTITY_HUSK_DEATH', + 'ENTITY_HUSK_HURT', + 'ENTITY_HUSK_STEP', + 'ENTITY_ILLUSION_ILLAGER_AMBIENT', + 'ENTITY_ILLUSION_ILLAGER_CAST_SPELL', + 'ENTITY_ILLUSION_ILLAGER_DEATH', + 'ENTITY_ILLUSION_ILLAGER_HURT', + 'ENTITY_ILLUSION_ILLAGER_MIRROR_MOVE', + 'ENTITY_ILLUSION_ILLAGER_PREPARE_BLINDNESS', + 'ENTITY_ILLUSION_ILLAGER_PREPARE_MIRROR', 'ENTITY_IRONGOLEM_ATTACK', 'ENTITY_IRONGOLEM_DEATH', 'ENTITY_IRONGOLEM_HURT', 'ENTITY_IRONGOLEM_STEP', - 'ENTITY_ITEM_BREAK', - 'ENTITY_ITEM_PICKUP', 'ENTITY_ITEMFRAME_ADD_ITEM', 'ENTITY_ITEMFRAME_BREAK', 'ENTITY_ITEMFRAME_PLACE', 'ENTITY_ITEMFRAME_REMOVE_ITEM', 'ENTITY_ITEMFRAME_ROTATE_ITEM', + 'ENTITY_ITEM_BREAK', + 'ENTITY_ITEM_PICKUP', 'ENTITY_LEASHKNOT_BREAK', 'ENTITY_LEASHKNOT_PLACE', 'ENTITY_LIGHTNING_IMPACT', 'ENTITY_LIGHTNING_THUNDER', 'ENTITY_LINGERINGPOTION_THROW', + 'ENTITY_LLAMA_AMBIENT', + 'ENTITY_LLAMA_ANGRY', + 'ENTITY_LLAMA_CHEST', + 'ENTITY_LLAMA_DEATH', + 'ENTITY_LLAMA_EAT', + 'ENTITY_LLAMA_HURT', + 'ENTITY_LLAMA_SPIT', + 'ENTITY_LLAMA_STEP', + 'ENTITY_LLAMA_SWAG', 'ENTITY_MAGMACUBE_DEATH', 'ENTITY_MAGMACUBE_HURT', 'ENTITY_MAGMACUBE_JUMP', @@ -271,10 +313,44 @@ var SOUNDS_POST = [ 'ENTITY_MINECART_RIDING', 'ENTITY_MOOSHROOM_SHEAR', 'ENTITY_MULE_AMBIENT', + 'ENTITY_MULE_CHEST', 'ENTITY_MULE_DEATH', 'ENTITY_MULE_HURT', 'ENTITY_PAINTING_BREAK', 'ENTITY_PAINTING_PLACE', + 'ENTITY_PARROT_AMBIENT', + 'ENTITY_PARROT_DEATH', + 'ENTITY_PARROT_EAT', + 'ENTITY_PARROT_FLY', + 'ENTITY_PARROT_HURT', + 'ENTITY_PARROT_IMITATE_BLAZE', + 'ENTITY_PARROT_IMITATE_CREEPER', + 'ENTITY_PARROT_IMITATE_ELDER_GUARDIAN', + 'ENTITY_PARROT_IMITATE_ENDERDRAGON', + 'ENTITY_PARROT_IMITATE_ENDERMAN', + 'ENTITY_PARROT_IMITATE_ENDERMITE', + 'ENTITY_PARROT_IMITATE_EVOCATION_ILLAGER', + 'ENTITY_PARROT_IMITATE_GHAST', + 'ENTITY_PARROT_IMITATE_HUSK', + 'ENTITY_PARROT_IMITATE_ILLUSION_ILLAGER', + 'ENTITY_PARROT_IMITATE_MAGMACUBE', + 'ENTITY_PARROT_IMITATE_POLAR_BEAR', + 'ENTITY_PARROT_IMITATE_SHULKER', + 'ENTITY_PARROT_IMITATE_SILVERFISH', + 'ENTITY_PARROT_IMITATE_SKELETON', + 'ENTITY_PARROT_IMITATE_SLIME', + 'ENTITY_PARROT_IMITATE_SPIDER', + 'ENTITY_PARROT_IMITATE_STRAY', + 'ENTITY_PARROT_IMITATE_VEX', + 'ENTITY_PARROT_IMITATE_VINDICATION_ILLAGER', + 'ENTITY_PARROT_IMITATE_WITCH', + 'ENTITY_PARROT_IMITATE_WITHER', + 'ENTITY_PARROT_IMITATE_WITHER_SKELETON', + 'ENTITY_PARROT_IMITATE_WOLF', + 'ENTITY_PARROT_IMITATE_ZOMBIE', + 'ENTITY_PARROT_IMITATE_ZOMBIE_PIGMAN', + 'ENTITY_PARROT_IMITATE_ZOMBIE_VILLAGER', + 'ENTITY_PARROT_STEP', 'ENTITY_PIG_AMBIENT', 'ENTITY_PIG_DEATH', 'ENTITY_PIG_HURT', @@ -291,10 +367,18 @@ var SOUNDS_POST = [ 'ENTITY_PLAYER_BURP', 'ENTITY_PLAYER_DEATH', 'ENTITY_PLAYER_HURT', + 'ENTITY_PLAYER_HURT_DROWN', + 'ENTITY_PLAYER_HURT_ON_FIRE', 'ENTITY_PLAYER_LEVELUP', 'ENTITY_PLAYER_SMALL_FALL', 'ENTITY_PLAYER_SPLASH', 'ENTITY_PLAYER_SWIM', + 'ENTITY_POLAR_BEAR_AMBIENT', + 'ENTITY_POLAR_BEAR_BABY_AMBIENT', + 'ENTITY_POLAR_BEAR_DEATH', + 'ENTITY_POLAR_BEAR_HURT', + 'ENTITY_POLAR_BEAR_STEP', + 'ENTITY_POLAR_BEAR_WARNING', 'ENTITY_RABBIT_AMBIENT', 'ENTITY_RABBIT_ATTACK', 'ENTITY_RABBIT_DEATH', @@ -353,13 +437,24 @@ var SOUNDS_POST = [ 'ENTITY_SQUID_AMBIENT', 'ENTITY_SQUID_DEATH', 'ENTITY_SQUID_HURT', + 'ENTITY_STRAY_AMBIENT', + 'ENTITY_STRAY_DEATH', + 'ENTITY_STRAY_HURT', + 'ENTITY_STRAY_STEP', 'ENTITY_TNT_PRIMED', + 'ENTITY_VEX_AMBIENT', + 'ENTITY_VEX_CHARGE', + 'ENTITY_VEX_DEATH', + 'ENTITY_VEX_HURT', 'ENTITY_VILLAGER_AMBIENT', 'ENTITY_VILLAGER_DEATH', 'ENTITY_VILLAGER_HURT', 'ENTITY_VILLAGER_NO', 'ENTITY_VILLAGER_TRADING', 'ENTITY_VILLAGER_YES', + 'ENTITY_VINDICATION_ILLAGER_AMBIENT', + 'ENTITY_VINDICATION_ILLAGER_DEATH', + 'ENTITY_VINDICATION_ILLAGER_HURT', 'ENTITY_WITCH_AMBIENT', 'ENTITY_WITCH_DEATH', 'ENTITY_WITCH_DRINK', @@ -370,6 +465,10 @@ var SOUNDS_POST = [ 'ENTITY_WITHER_DEATH', 'ENTITY_WITHER_HURT', 'ENTITY_WITHER_SHOOT', + 'ENTITY_WITHER_SKELETON_AMBIENT', + 'ENTITY_WITHER_SKELETON_DEATH', + 'ENTITY_WITHER_SKELETON_HURT', + 'ENTITY_WITHER_SKELETON_STEP', 'ENTITY_WITHER_SPAWN', 'ENTITY_WOLF_AMBIENT', 'ENTITY_WOLF_DEATH', @@ -403,10 +502,12 @@ var SOUNDS_POST = [ 'ENTITY_ZOMBIE_VILLAGER_STEP', 'ITEM_ARMOR_EQUIP_CHAIN', 'ITEM_ARMOR_EQUIP_DIAMOND', + 'ITEM_ARMOR_EQUIP_ELYTRA', 'ITEM_ARMOR_EQUIP_GENERIC', 'ITEM_ARMOR_EQUIP_GOLD', 'ITEM_ARMOR_EQUIP_IRON', 'ITEM_ARMOR_EQUIP_LEATHER', + 'ITEM_BOTTLE_EMPTY', 'ITEM_BOTTLE_FILL', 'ITEM_BOTTLE_FILL_DRAGONBREATH', 'ITEM_BUCKET_EMPTY', @@ -414,12 +515,14 @@ var SOUNDS_POST = [ 'ITEM_BUCKET_FILL', 'ITEM_BUCKET_FILL_LAVA', 'ITEM_CHORUS_FRUIT_TELEPORT', + 'ITEM_ELYTRA_FLYING', 'ITEM_FIRECHARGE_USE', 'ITEM_FLINTANDSTEEL_USE', 'ITEM_HOE_TILL', 'ITEM_SHIELD_BLOCK', 'ITEM_SHIELD_BREAK', 'ITEM_SHOVEL_FLATTEN', + 'ITEM_TOTEM_USE', 'MUSIC_CREATIVE', 'MUSIC_CREDITS', 'MUSIC_DRAGON', @@ -440,6 +543,9 @@ var SOUNDS_POST = [ 'RECORD_WAIT', 'RECORD_WARD', 'UI_BUTTON_CLICK', + 'UI_TOAST_CHALLENGE_COMPLETE', + 'UI_TOAST_IN', + 'UI_TOAST_OUT', 'WEATHER_RAIN', 'WEATHER_RAIN_ABOVE' ]; @@ -655,4 +761,4 @@ function formatSound(name) { result += words[i].charAt(0) + words[i].substr(1).toLowerCase(); } return result; -} \ No newline at end of file +} diff --git a/editor/style.css b/editor/style.css index 2b5355fa..86b962ce 100644 --- a/editor/style.css +++ b/editor/style.css @@ -50,6 +50,12 @@ html, body { background-color: black; } +#attribute-label { + width: 90% !important; + text-align: center !important; + color: #888; +} + #badBrowser p { text-align: center; margin-top: 40%; @@ -69,12 +75,10 @@ html, body { #skills, #classes { float: left; - width: calc(100% - 16px); - margin-left: 5px; - height: calc(100% - 55px); - border: 3px solid white; + width: 100%; + height: calc(100% - 45px); background-color: black; - border-radius: 20px; + border-radius: 0; overflow: hidden; } @@ -112,11 +116,12 @@ html, body { float: left; width: 200px; font: 18px Sertig-Web; - background-color: #000; + background-color: #181818; color: white; font-weight: bold; height: 100%; padding: 5px; + border: none; } #skillList option, #classList option { @@ -158,11 +163,11 @@ h5 { float: left; width: 225px; padding: 10px 5px; - background-color: #111; + background-color: #181818; margin-bottom: 10px; margin-left: 10px; - border-radius: 10px; - border: 2px solid #ccc; + border-radius: 5px; + border: 2px solid #333; text-align: center; font: 24px Sertig-Web; font-weight: bold; @@ -171,7 +176,7 @@ h5 { } h5:hover { - background-color: #666; + background-color: #777; cursor: pointer; } @@ -180,12 +185,11 @@ h6 { display: inline-block; width: calc(25% - 26px); padding: 5px 5px; - background-color: #111; - margin: 0 3px; + background-color: #043; + margin-left: 3px; margin-top: 10px; margin-bottom: 5px; - border-radius: 10px; - border: 2px solid #ccc; + border-radius: 5px; text-align: center; font: 24px Sertig-Web; font-weight: bold; @@ -263,24 +267,41 @@ input, select, textarea { } .doneButton { - background-color: blue; + background-color: #008966; + border: 2px solid #004433; color: white; - transition: background-color: 600ms, color 600ms; + transition: background-color 600ms, color 600ms; } .doneButton:hover { - background-color: white - color: red; + background-color: #004433; + border: 2px solid #002211; +} + +hr { + display: block; + background-color: #888; + height: 1px; + width: 100%; + clear: both; +} + +#saveSkill { + background-color: #375a7f; +} + +#saveSkill:hover { + background-color: #6af; } .ioButton { height: 30px; margin: 5px 10px; width: calc(100% - 20px); - background-color: #333; + background-color: #375a7f; color: white; - border: 2px solid white; - border-radius: 10px; + border: none; + border-radius: 5px; font: 20px Sertig-Web; font-weight: bold; cursor: pointer; @@ -288,7 +309,7 @@ input, select, textarea { } .ioButton:hover { - background-color: #888; + background-color: #6af; } .ioText { @@ -298,7 +319,7 @@ input, select, textarea { } .premium { - background-color: #066; + background-color: #033; } .premium:hover { @@ -311,7 +332,6 @@ input, select, textarea { width: calc(45% - 36px); margin-left: 5px; background-color: black; - border: 3px solid #fff; border-bottom: none; margin: 5px 5px 0; color: white; @@ -325,6 +345,7 @@ input, select, textarea { .tab { cursor: pointer; + background: #008966; } .tab:hover { @@ -341,8 +362,8 @@ input, select, textarea { left: -1px; top: -1px; content: ''; - border-top: 15px solid #fff; - border-right: 15px solid transparent; + border-top: 20px solid #333; + border-right: 20px solid transparent; } .tabLeft:after { @@ -350,8 +371,8 @@ input, select, textarea { left: -3px; top: -3px; content: ''; - border-top: 15px solid #333; - border-right: 15px solid transparent; + border-top: 20px solid #333; + border-right: 20px solid transparent; } .tabRight:before { @@ -359,8 +380,8 @@ input, select, textarea { right: -1px; top: -1px; content: ''; - border-top: 15px solid #fff; - border-left: 15px solid transparent; + border-top: 20px solid #333; + border-left: 20px solid transparent; } .tabRight:after { @@ -368,8 +389,8 @@ input, select, textarea { right: -3px; top: -3px; content: ''; - border-top: 15px solid #333; - border-left: 15px solid transparent; + border-top: 20px solid #333; + border-left: 20px solid transparent; } #io { @@ -385,16 +406,20 @@ input, select, textarea { font: 24px Sertig-Web; font-weight: bold; padding-top: 10px; + clear: both; } #skillForm input, #skillForm select, #classForm input, #classForm select, #skillForm .byteList, #classForm .byteList { font: 20px Sertig-Web; font-weight: bold; - background-color: #333; - border: 2px solid white; - border-radius: 10px; + background-color: #181818; + border: 1px solid #888; + border-top: none; + border-right: none; + border-radius: 5px; color: white; padding: 5px 10px; + padding-right: 0; margin: 2px 5px; width: 40%; float: left; @@ -428,9 +453,11 @@ input, select, textarea { #skillForm textarea, #classForm textArea { font: 20px Sertig-Web; font-weight: bold; - background-color: #333; - border: 2px solid white; - border-radius: 10px; + background-color: #181818; + border: 1px solid #888; + border-top: none; + border-right: none; + border-radius: 5px; color: white; padding: 5px 10px; margin: 2px 5px; @@ -439,6 +466,10 @@ input, select, textarea { height: 200px; } +textArea { + resize: vertical; +} + #skillForm p, #classForm p { text-align: center; font: 20px Sertig-Web; @@ -454,6 +485,7 @@ input, select, textarea { margin: 0; padding: 0; padding-top: 10px; + clear: none; } #skillForm .base, #classForm .base { @@ -475,7 +507,7 @@ input, select, textarea { ::-webkit-scrollbar-track { border-radius: 10px; - border: 2px solid #aaa; + border: 1px solid #aaa; } ::-webkit-scrollbar-thumb { @@ -483,7 +515,7 @@ input, select, textarea { background-color: #aaa; } -@media screen and (max-width: 459px) { +@media screen and (max-width: 599px) { #main { display: none; diff --git a/editor/tooltips.css b/editor/tooltips.css index f684d92d..a682e078 100644 --- a/editor/tooltips.css +++ b/editor/tooltips.css @@ -69,8 +69,8 @@ padding: 8px; width: 100%; background-color: #222; - border: 2px solid white; - border-radius: 15px; + border-radius: 5px; + border: 1px solid white; color: #fff; content: attr(data-tooltip); font-size: 18px; diff --git a/img/back0.png b/img/back0.png deleted file mode 100644 index aff4a59e..00000000 Binary files a/img/back0.png and /dev/null differ diff --git a/img/back1.png b/img/back1.png deleted file mode 100644 index e57b373e..00000000 Binary files a/img/back1.png and /dev/null differ diff --git a/img/background.png b/img/background.png deleted file mode 100644 index 360ec07e..00000000 Binary files a/img/background.png and /dev/null differ diff --git a/img/down0.png b/img/down0.png deleted file mode 100644 index 0e888e82..00000000 Binary files a/img/down0.png and /dev/null differ diff --git a/img/down1.png b/img/down1.png deleted file mode 100644 index 207eadfd..00000000 Binary files a/img/down1.png and /dev/null differ diff --git a/img/more0.png b/img/more0.png deleted file mode 100644 index c2d18f72..00000000 Binary files a/img/more0.png and /dev/null differ diff --git a/img/more1.png b/img/more1.png deleted file mode 100644 index 261da847..00000000 Binary files a/img/more1.png and /dev/null differ diff --git a/img/nameplate.png b/img/nameplate.png deleted file mode 100644 index cbb0d4bb..00000000 Binary files a/img/nameplate.png and /dev/null differ diff --git a/img/selector.png b/img/selector.png deleted file mode 100644 index b6faeb34..00000000 Binary files a/img/selector.png and /dev/null differ diff --git a/img/title.png b/img/title.png deleted file mode 100644 index e95defa1..00000000 Binary files a/img/title.png and /dev/null differ diff --git a/img/up0.png b/img/up0.png deleted file mode 100644 index eb14b15b..00000000 Binary files a/img/up0.png and /dev/null differ diff --git a/img/up1.png b/img/up1.png deleted file mode 100644 index 8f97d463..00000000 Binary files a/img/up1.png and /dev/null differ diff --git a/index.html b/index.html index e76ead47..3c75dc91 100644 --- a/index.html +++ b/index.html @@ -59,7 +59,7 @@

Dynamic Editor

Details
Triggers
-
Save
+
Save
Delete
diff --git a/modules/BungeeCord.jar b/modules/BungeeCord.jar new file mode 100644 index 00000000..c3b73809 Binary files /dev/null and b/modules/BungeeCord.jar differ diff --git a/modules/MCCore.jar b/modules/MCCore.jar index ddc092f7..2971494b 100644 Binary files a/modules/MCCore.jar and b/modules/MCCore.jar differ diff --git a/modules/MythicMobs-4.2.0.jar b/modules/MythicMobs-4.2.0.jar new file mode 100644 index 00000000..70121bc6 Binary files /dev/null and b/modules/MythicMobs-4.2.0.jar differ diff --git a/modules/Parties.jar b/modules/Parties.jar new file mode 100644 index 00000000..ef5dd952 Binary files /dev/null and b/modules/Parties.jar differ diff --git a/modules/PlaceholderAPI-2.8.2.jar b/modules/PlaceholderAPI-2.8.2.jar new file mode 100644 index 00000000..8f727228 Binary files /dev/null and b/modules/PlaceholderAPI-2.8.2.jar differ diff --git a/modules/RPG Parties.jar b/modules/RPG Parties.jar new file mode 100644 index 00000000..326d7024 Binary files /dev/null and b/modules/RPG Parties.jar differ diff --git a/modules/RPGInventory.jar b/modules/RPGInventory.jar new file mode 100644 index 00000000..cac272e9 Binary files /dev/null and b/modules/RPGInventory.jar differ diff --git a/modules/SkillAPIModule.jar b/modules/SkillAPIModule.jar new file mode 100644 index 00000000..06ea641f Binary files /dev/null and b/modules/SkillAPIModule.jar differ diff --git a/modules/SkillAPIPlaceholders.jar b/modules/SkillAPIPlaceholders.jar new file mode 100644 index 00000000..cdbf13a5 Binary files /dev/null and b/modules/SkillAPIPlaceholders.jar differ diff --git a/modules/WorldEdit.jar b/modules/WorldEdit.jar new file mode 100644 index 00000000..101f5336 Binary files /dev/null and b/modules/WorldEdit.jar differ diff --git a/modules/WorldGuard.jar b/modules/WorldGuard.jar new file mode 100644 index 00000000..e4821f00 Binary files /dev/null and b/modules/WorldGuard.jar differ diff --git a/modules/craftbukkit-1.13.jar b/modules/craftbukkit-1.13.jar new file mode 100644 index 00000000..96ef215c Binary files /dev/null and b/modules/craftbukkit-1.13.jar differ diff --git a/modules/hamcrest-core-1.3.jar b/modules/hamcrest-core-1.3.jar new file mode 100644 index 00000000..9d5fe16e Binary files /dev/null and b/modules/hamcrest-core-1.3.jar differ diff --git a/modules/junit-4.12.jar b/modules/junit-4.12.jar new file mode 100644 index 00000000..3a7fc266 Binary files /dev/null and b/modules/junit-4.12.jar differ diff --git a/modules/mockito-core-1.10.19.jar b/modules/mockito-core-1.10.19.jar new file mode 100644 index 00000000..d94e289d Binary files /dev/null and b/modules/mockito-core-1.10.19.jar differ diff --git a/modules/objenesis-2.4.jar b/modules/objenesis-2.4.jar new file mode 100644 index 00000000..f76ea51f Binary files /dev/null and b/modules/objenesis-2.4.jar differ diff --git a/modules/spigot-1.9.jar b/modules/spigot-1.9.jar deleted file mode 100644 index c7716ac6..00000000 Binary files a/modules/spigot-1.9.jar and /dev/null differ diff --git a/src/SkillAPIModule.jar b/src/SkillAPIModule.jar new file mode 100644 index 00000000..06ea641f Binary files /dev/null and b/src/SkillAPIModule.jar differ diff --git a/src/SkillAPIPlaceholders.jar b/src/SkillAPIPlaceholders.jar new file mode 100644 index 00000000..cdbf13a5 Binary files /dev/null and b/src/SkillAPIPlaceholders.jar differ diff --git a/src/attributes.yml b/src/attributes.yml new file mode 100644 index 00000000..3d0ddc23 --- /dev/null +++ b/src/attributes.yml @@ -0,0 +1,115 @@ +# Attributes.yml +# +# For full details, visit +# http://dev.bukkit.org/bukkit-plugins/skillapi/pages/attributes/ +# +# List of available stats to modify: +# armor | [PREM, 1.9+] Vanilla damage mitigation +# armor-toughness | [PREM, 1.9+] Secondary vanilla damage mitigation +# attack-speed | [PREM, 1.9+] Weapon recharge time +# cooldown | [PREM] Modifies skill cooldowns +# defense- | [PREM] Reduces damage taken from various damage sources. +# | See the DamageCause docs to see supported types. +# | Use lower-case versions of it, such as "defense-block_explosion". +# exp | [PREM] increases all class experience gained +# health | The max health of the player +# hunger | [PREM] Increases how long hunger lasts. This attribute is always based off of a base value of 1. A resulting value of 2 would double how long the hunger bar lasts, for example. +# hunger-heal | [PREM] Increases how much you heal while satiated +# knockback-resist | [PREM, 1.9+] Probability of resisting knockback as a decimal (1.0 is 100% change to resist) +# luck | [PREM, 1.9+] loot table chances +# mana | The max mana of the player +# mana-regen | The amount of mana regeneration the player has +# move-speed | The movement speed of the player +# physical-damage | The amount of damage done by physical (basic or projectile) attacks +# physical-defense | The amount of damage taken by physical (basic or projectile) attacks +# skill-damage | The amount of damage done by skills +# skill-defense | The amount of damage taken by skills + +# skill-damage- | [PREM] The amount of damage done by skills with the specified classification +# skill-defense- | [PREM] The amount of damage taken by skills with the specified classification +vitality: + display: 'Vitality' + max: 999 + icon: 'ink sack' + icon-data: 1 + icon-lore: + - '&6Vitality &7(&2{amount}&7)' + - '' + - '&7Grants 1 health per' + - '&7point invested.' + global: + condition: {} + mechanic: {} + target: {} + stats: + health: 'a+v' +spirit: + display: 'Spirit' + max: 999 + icon: 'ink sack' + icon-data: 6 + icon-lore: + - '&6Spirit &7(&2{amount}&7)' + - '' + - '&7Grants 1 mana and 2.5%' + - '&7mana regeneration per' + - '&7point invested.' + global: + condition: {} + mechanic: {} + target: {} + stats: + mana: 'a+v' + mana-regen: 'a*0.025+1*v' +intelligence: + display: 'Intelligence' + max: 999 + icon: 'ink sack' + icon-data: 5 + icon-lore: + - '&6Intelligence &7(&2{amount}&7)' + - '' + - '&7Grants 2.5% increased' + - '&7skill damage per point' + - '&7invested.' + global: + condition: {} + mechanic: + Damage-value: 'a*0.025+1*v' + target: {} +dexterity: + display: 'Dexterity' + max: 999 + icon: 'ink sack' + icon-data: 10 + icon-lore: + - '&6Dexterity &7(&2{amount}&7)' + - '' + - '&7Grants 2.5% increased' + - '&7range per point invested.' + global: + condition: {} + mechanic: {} + target: + Cone-range: 'a*0.025+1*v' + Linear-range: 'a*0.025+1*v' + Location-range: 'a*0.025+1*v' + Nearest-radius: 'a*0.025+1*v' + Single-range: 'a*0.025+1*v' +strength: + display: 'Strength' + max: 999 + icon: 'ink sack' + icon-data: 14 + icon-lore: + - '&6Strength &7(&2{amount}&7)' + - '' + - '&7Grants 2.5% increased' + - '&7non-skill damage per' + - '&7point invested.' + global: + condition: {} + mechanic: {} + target: {} + stats: + physical-damage: 'a*0.025+1*v' \ No newline at end of file diff --git a/src/com/sucy/skill/SkillAPI.java b/src/com/sucy/skill/SkillAPI.java index 6551873a..beb516a6 100644 --- a/src/com/sucy/skill/SkillAPI.java +++ b/src/com/sucy/skill/SkillAPI.java @@ -24,10 +24,13 @@ package com.sucy.skill; +import com.rit.sucy.config.CommentedConfig; import com.rit.sucy.config.CommentedLanguageConfig; import com.rit.sucy.version.VersionManager; import com.rit.sucy.version.VersionPlayer; import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.api.particle.EffectManager; +import com.sucy.skill.api.particle.Particle; import com.sucy.skill.api.player.PlayerAccounts; import com.sucy.skill.api.player.PlayerClass; import com.sucy.skill.api.player.PlayerData; @@ -40,25 +43,27 @@ import com.sucy.skill.data.io.SQLIO; import com.sucy.skill.dynamic.DynamicClass; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.mechanic.BlockMechanic; -import com.sucy.skill.dynamic.mechanic.PassiveMechanic; -import com.sucy.skill.dynamic.mechanic.RepeatMechanic; -import com.sucy.skill.dynamic.mechanic.WolfMechanic; -import com.sucy.skill.gui.Menu; +import com.sucy.skill.gui.tool.GUITool; +import com.sucy.skill.hook.BungeeHook; import com.sucy.skill.hook.PluginChecker; import com.sucy.skill.listener.*; import com.sucy.skill.manager.*; -import com.sucy.skill.task.*; +import com.sucy.skill.packet.PacketInjector; +import com.sucy.skill.task.CooldownTask; +import com.sucy.skill.task.GUITask; +import com.sucy.skill.task.ManaTask; +import com.sucy.skill.task.SaveTask; +import com.sucy.skill.thread.MainThread; import org.bukkit.Bukkit; -import org.bukkit.GameMode; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; import org.bukkit.metadata.Metadatable; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; import java.util.ArrayList; import java.util.HashMap; @@ -68,14 +73,15 @@ *

The main class of the plugin which has the accessor methods into most of the API.

*

You can retrieve a reference to this through Bukkit the same way as any other plugin.

*/ -public class SkillAPI extends JavaPlugin -{ +public class SkillAPI extends JavaPlugin { private static SkillAPI singleton; - private final HashMap skills = new HashMap(); - private final HashMap classes = new HashMap(); - private final HashMap players = new HashMap(); - private final ArrayList groups = new ArrayList(); + private final HashMap skills = new HashMap<>(); + private final HashMap classes = new HashMap<>(); + private final HashMap players = new HashMap<>(); + private final ArrayList groups = new ArrayList<>(); + + private final List listeners = new ArrayList<>(); private CommentedLanguageConfig language; private Settings settings; @@ -86,26 +92,26 @@ public class SkillAPI extends JavaPlugin private RegistrationManager registrationManager; private AttributeManager attributeManager; - private ManaTask manaTask; - private CooldownTask cdTask; - private InventoryTask invTask; - private SaveTask saveTask; - private GUITask guiTask; + private MainThread mainThread; + private BukkitTask manaTask; private boolean loaded = false; + private boolean disabling = false; /** *

Enables SkillAPI, setting up listeners, managers, and loading data. This * should not be called by other plugins.

*/ @Override - public void onEnable() - { + public void onEnable() { // Set up the singleton - if (singleton != null) - throw new IllegalStateException("Cannot enable SkillAPI twice!"); + if (singleton != null) { throw new IllegalStateException("Cannot enable SkillAPI twice!"); } singleton = this; + mainThread = new MainThread(); + Particle.init(); + EffectManager.init(); + // Load settings settings = new Settings(this); language = new CommentedLanguageConfig(this, "language"); @@ -114,7 +120,7 @@ public void onEnable() language.save(); // Hook plugins - PluginChecker.isVaultActive(); + if (PluginChecker.isBungeeActive()) { BungeeHook.init(this); } // Set up managers comboManager = new ComboManager(); @@ -123,9 +129,7 @@ public void onEnable() io = settings.isUseSql() ? new SQLIO(this) : new ConfigIO(this); PlayerStats.init(); ClassBoardManager.registerText(); - ResourceManager.copyQuestsModule(); - if (settings.isAttributesEnabled()) - attributeManager = new AttributeManager(this); + if (settings.isAttributesEnabled()) { attributeManager = new AttributeManager(this); } // Load classes and skills registrationManager.initialize(); @@ -134,47 +138,73 @@ public void onEnable() settings.loadGroupSettings(); // Set up listeners - listen(new CastListener(), true); + listen(new BindListener(), true); + listen(new BuffListener(), true); listen(new MainListener(), true); listen(new MechanicListener(), true); listen(new StatusListener(), true); + listen(new ToolListener(), true); listen(new KillListener(), true); - listen(new TreeListener(), !settings.isMapTreeEnabled()); + listen(new AddonListener(), true); listen(new ItemListener(), settings.isCheckLore()); listen(new BarListener(), settings.isSkillBarEnabled()); - listen(new ClickListener(), settings.isCombosEnabled()); + if (VersionManager.isVersionAtLeast(VersionManager.V1_8_0)) { + final PacketInjector injector = new PacketInjector(this); + listen(new PacketListener(injector), true); + listen(new ClickListener(), settings.isCombosEnabled()); + } + listen(new ComboListener(), settings.isCombosEnabled()); listen(new AttributeListener(), settings.isAttributesEnabled()); + listen(new CastListener(), settings.isUsingBars()); + listen( + new CastOffhandListener(), + settings.isCastEnabled() && VersionManager.isVersionAtLeast(VersionManager.V1_9_0)); + listen(new CastItemListener(), settings.isUsingWand()); + listen(new CastCombatListener(), settings.isUsingCombat()); listen(new DeathListener(), !VersionManager.isVersionAtLeast(11000)); - - if (settings.isMapTreeAvailable()) - Menu.initialize(this); + listen(new LingeringPotionListener(), VersionManager.isVersionAtLeast(VersionManager.V1_9_0)); + listen(new ExperienceListener(), settings.yieldsEnabled()); // Set up tasks - if (settings.isManaEnabled()) - manaTask = new ManaTask(this); - if (settings.isSkillBarCooldowns()) - cdTask = new CooldownTask(this); - if (settings.isCheckLore()) - invTask = new InventoryTask(this, settings.getPlayersPerCheck()); - if (settings.isAutoSave()) - saveTask = new SaveTask(this); - guiTask = new GUITask(this); + if (settings.isManaEnabled()) { + if (VersionManager.isVersionAtLeast(11400)) { + manaTask = Bukkit.getScheduler().runTaskTimer( + this, + new ManaTask(), + SkillAPI.getSettings().getGainFreq(), + SkillAPI.getSettings().getGainFreq() + ); + } else { + MainThread.register(new ManaTask()); + } + } + if (settings.isSkillBarCooldowns()) { MainThread.register(new CooldownTask()); } + if (settings.isAutoSave()) { MainThread.register(new SaveTask(this)); } + MainThread.register(new GUITask(this)); + + GUITool.init(); // Load player data - for (Player player : VersionManager.getOnlinePlayers()) - { - PlayerData data = loadPlayerData(player).getActiveData(); - data.init(player); + players.putAll(io.loadAll()); + for (PlayerAccounts accounts : players.values()) { accounts.getActiveData().init(accounts.getPlayer()); } + + // Must initialize listeners AFTER player data is loaded since the + // player objects would otherwise change and mess a lot of things up. + for (SkillAPIListener listener : listeners) { + listener.init(); } - if (settings.isUseSql()) ((SQLIO) io).cleanup(); + + ResourceManager.copyQuestsModule(); + ResourceManager.copyPlaceholdersModule(); loaded = true; } - private void listen(Listener listener, boolean enabled) - { - if (enabled) + private void listen(SkillAPIListener listener, boolean enabled) { + if (enabled) { Bukkit.getPluginManager().registerEvents(listener, this); + listeners.add(listener); + } } /** @@ -182,56 +212,32 @@ private void listen(Listener listener, boolean enabled) * listeners. This should not be called by other plugins.

*/ @Override - public void onDisable() - { + public void onDisable() { // Validate instance - if (singleton != this) - throw new IllegalStateException("This is not a valid, enabled SkillAPI copy!"); - - // Clear tasks - WolfMechanic.removeWolves(); - BlockMechanic.revertAll(); - PassiveMechanic.stopAll(); - RepeatMechanic.stopAll(); - if (manaTask != null) - { + if (singleton != this) { throw new IllegalStateException("This is not a valid, enabled SkillAPI copy!"); } + + disabling = true; + + GUITool.cleanUp(); + EffectManager.cleanUp(); + + mainThread.disable(); + mainThread = null; + + if (manaTask != null) { manaTask.cancel(); manaTask = null; } - if (cdTask != null) - { - cdTask.cancel(); - cdTask = null; - } - if (invTask != null) - { - invTask.cancel(); - invTask = null; - } - if (saveTask != null) - { - saveTask.cancel(); - saveTask = null; - } - if (guiTask != null && guiTask.isRunning()) - { - guiTask.cancel(); - guiTask = null; - } + + for (SkillAPIListener listener : listeners) { listener.cleanup(); } + listeners.clear(); // Clear scoreboards ClassBoardManager.clearAll(); // Clear skill bars and stop passives before disabling - for (Player player : VersionManager.getOnlinePlayers()) - { - getPlayerData(player).stopPassives(player); - if (player.getGameMode() != GameMode.CREATIVE && !player.isDead()) - { - getPlayerData(player).getSkillBar().clear(player); - } - player.setMaxHealth(20); - player.setWalkSpeed(0.2f); + for (Player player : VersionManager.getOnlinePlayers()) { + MainListener.unload(player); } io.saveAll(); @@ -241,12 +247,10 @@ public void onDisable() players.clear(); HandlerList.unregisterAll(this); - AttributeListener.cleanup(); - MechanicListener.cleanup(); - StatusListener.cleanup(); cmd.clear(); loaded = false; + disabling = false; singleton = null; } @@ -256,8 +260,7 @@ public void onDisable() * * @return true if loaded and set up, false otherwise */ - public static boolean isLoaded() - { + public static boolean isLoaded() { return singleton != null && singleton.loaded; } @@ -266,10 +269,11 @@ public static boolean isLoaded() * * @throws IllegalStateException if SkillAPI isn't enabled */ - private static SkillAPI singleton() - { - if (singleton == null) - throw new IllegalStateException("Cannot use SkillAPI methods before it is enabled - add it to your plugin.yml as a dependency"); + private static SkillAPI singleton() { + if (singleton == null) { + throw new IllegalStateException( + "Cannot use SkillAPI methods before it is enabled - add it to your plugin.yml as a dependency"); + } return singleton; } @@ -278,8 +282,7 @@ private static SkillAPI singleton() * * @return SkillAPI settings data */ - public static Settings getSettings() - { + public static Settings getSettings() { return singleton().settings; } @@ -288,8 +291,7 @@ public static Settings getSettings() * * @return SkillAPI language file data */ - public static CommentedLanguageConfig getLanguage() - { + public static CommentedLanguageConfig getLanguage() { return singleton().language; } @@ -298,8 +300,7 @@ public static CommentedLanguageConfig getLanguage() * * @return click combo manager */ - public static ComboManager getComboManager() - { + public static ComboManager getComboManager() { return singleton().comboManager; } @@ -308,8 +309,7 @@ public static ComboManager getComboManager() * * @return attribute manager */ - public static AttributeManager getAttributeManager() - { + public static AttributeManager getAttributeManager() { return singleton().attributeManager; } @@ -321,10 +321,8 @@ public static AttributeManager getAttributeManager() * * @return skill with the name or null if not found */ - public static Skill getSkill(String name) - { - if (name == null) - return null; + public static Skill getSkill(String name) { + if (name == null) { return null; } return singleton().skills.get(name.toLowerCase()); } @@ -334,8 +332,7 @@ public static Skill getSkill(String name) * * @return the map of registered skills */ - public static HashMap getSkills() - { + public static HashMap getSkills() { return singleton().skills; } @@ -346,8 +343,7 @@ public static HashMap getSkills() * * @return true if registered, false otherwise */ - public static boolean isSkillRegistered(String name) - { + public static boolean isSkillRegistered(String name) { return getSkill(name) != null; } @@ -358,8 +354,7 @@ public static boolean isSkillRegistered(String name) * * @return true if registered, false otherwise */ - public static boolean isSkillRegistered(PlayerSkill skill) - { + public static boolean isSkillRegistered(PlayerSkill skill) { return isSkillRegistered(skill.getData().getName()); } @@ -370,8 +365,7 @@ public static boolean isSkillRegistered(PlayerSkill skill) * * @return true if registered, false otherwise */ - public static boolean isSkillRegistered(Skill skill) - { + public static boolean isSkillRegistered(Skill skill) { return isSkillRegistered(skill.getName()); } @@ -383,10 +377,8 @@ public static boolean isSkillRegistered(Skill skill) * * @return class with the name or null if not found */ - public static RPGClass getClass(String name) - { - if (name == null) - return null; + public static RPGClass getClass(String name) { + if (name == null) { return null; } return singleton().classes.get(name.toLowerCase()); } @@ -396,8 +388,7 @@ public static RPGClass getClass(String name) * * @return the map of registered skills */ - public static HashMap getClasses() - { + public static HashMap getClasses() { return singleton().classes; } @@ -406,12 +397,11 @@ public static HashMap getClasses() * * @return the list of base classes */ - public static ArrayList getBaseClasses(String group) - { - ArrayList list = new ArrayList(); - for (RPGClass c : singleton.classes.values()) - if (!c.hasParent() && c.getGroup().equals(group)) - list.add(c); + public static ArrayList getBaseClasses(String group) { + ArrayList list = new ArrayList<>(); + for (RPGClass c : singleton.classes.values()) { + if (!c.hasParent() && c.getGroup().equals(group)) { list.add(c); } + } return list; } @@ -422,8 +412,7 @@ public static ArrayList getBaseClasses(String group) * * @return true if registered, false otherwise */ - public static boolean isClassRegistered(String name) - { + public static boolean isClassRegistered(String name) { return getClass(name) != null; } @@ -434,8 +423,7 @@ public static boolean isClassRegistered(String name) * * @return true if registered, false otherwise */ - public static boolean isClassRegistered(PlayerClass playerClass) - { + public static boolean isClassRegistered(PlayerClass playerClass) { return isClassRegistered(playerClass.getData().getName()); } @@ -446,8 +434,7 @@ public static boolean isClassRegistered(PlayerClass playerClass) * * @return true if registered, false otherwise */ - public static boolean isClassRegistered(RPGClass rpgClass) - { + public static boolean isClassRegistered(RPGClass rpgClass) { return isClassRegistered(rpgClass.getName()); } @@ -459,10 +446,8 @@ public static boolean isClassRegistered(RPGClass rpgClass) * * @return the class data of the player */ - public static PlayerData getPlayerData(OfflinePlayer player) - { - if (player == null) - return null; + public static PlayerData getPlayerData(OfflinePlayer player) { + if (player == null) { return null; } return getPlayerAccountData(player).getActiveData(); } @@ -474,28 +459,48 @@ public static PlayerData getPlayerData(OfflinePlayer player) * * @param player player to load the data for */ - public static PlayerAccounts loadPlayerData(OfflinePlayer player) - { - if (player == null) - return null; + public static PlayerAccounts loadPlayerData(OfflinePlayer player) { + if (player == null) { return null; } // Already loaded for some reason, no need to load again String id = new VersionPlayer(player).getIdString(); - if (singleton().players.containsKey(id)) return singleton.players.get(id); + if (singleton().players.containsKey(id)) { return singleton.players.get(id); } + // Load the data + return doLoad(player); + } + + private static PlayerAccounts doLoad(OfflinePlayer player) { // Load the data PlayerAccounts data = singleton.io.loadData(player); - singleton.players.put(id, data); + singleton.players.put(player.getUniqueId().toString(), data); return data; } + /** + * Used to fake player data until SQL data is loaded when both SQL and the SQL delay are enabled. + * This should not be used by other plugins. If the player data already exists, this does nothing. + * + * @param player player to fake data for + */ + public static void initFakeData(final OfflinePlayer player) { + singleton().players.computeIfAbsent(player.getUniqueId().toString(), id -> new PlayerAccounts(player)); + } + + /** + * Do not use this method outside of onJoin. This will delete any progress a player + * has made since joining. + */ + public static void reloadPlayerData(final Player player) { + doLoad(player); + } + /** * Saves all player data to the configs. This * should be called asynchronously to avoid problems * with the main server loop. */ - public static void saveData() - { + public static void saveData() { singleton().io.saveAll(); } @@ -509,8 +514,7 @@ public static void saveData() * * @return true if has loaded data, false otherwise */ - public static boolean hasPlayerData(OfflinePlayer player) - { + public static boolean hasPlayerData(OfflinePlayer player) { return singleton != null && player != null && singleton.players.containsKey(new VersionPlayer(player).getIdString()); } @@ -520,23 +524,22 @@ public static boolean hasPlayerData(OfflinePlayer player) * * @param player player to unload data for */ - public static void unloadPlayerData(final OfflinePlayer player) - { - if (singleton == null || player == null || !singleton.players.containsKey(new VersionPlayer(player).getIdString())) + public static void unloadPlayerData(final OfflinePlayer player) { + unloadPlayerData(player, false); + } + + public static void unloadPlayerData(final OfflinePlayer player, final boolean skipSaving) { + if (singleton == null || player == null || singleton.disabling || !singleton.players.containsKey(new VersionPlayer(player).getIdString())) { return; + } - singleton.getServer().getScheduler().runTaskAsynchronously( - singleton, new Runnable() - { - @Override - public void run() - { - PlayerAccounts accounts = getPlayerAccountData(player); - singleton.io.saveData(accounts); - singleton.players.remove(new VersionPlayer(player).getIdString()); - } + singleton.getServer().getScheduler().runTaskAsynchronously(singleton, () -> { + PlayerAccounts accounts = getPlayerAccountData(player); + if (!skipSaving) { + singleton.io.saveData(accounts); } - ); + singleton.players.remove(new VersionPlayer(player).getIdString()); + }); } /** @@ -548,20 +551,15 @@ public void run() * * @return the class data of the player */ - public static PlayerAccounts getPlayerAccountData(OfflinePlayer player) - { - if (player == null) - return null; + public static PlayerAccounts getPlayerAccountData(OfflinePlayer player) { + if (player == null) { return null; } String id = new VersionPlayer(player).getIdString(); - if (!singleton().players.containsKey(id)) - { + if (!singleton().players.containsKey(id)) { PlayerAccounts data = loadPlayerData(player); singleton.players.put(id, data); return data; - } - else - return singleton.players.get(id); + } else { return singleton.players.get(id); } } /** @@ -570,8 +568,7 @@ public static PlayerAccounts getPlayerAccountData(OfflinePlayer player) * * @return all SkillAPI player data */ - public static HashMap getPlayerAccountData() - { + public static HashMap getPlayerAccountData() { return singleton().players; } @@ -581,8 +578,7 @@ public static HashMap getPlayerAccountData() * * @return list of active class groups */ - public static List getGroups() - { + public static List getGroups() { return singleton().groups; } @@ -592,12 +588,10 @@ public static List getGroups() * * @param skill the dynamic skill to register */ - public void addDynamicSkill(DynamicSkill skill) - { - if (registrationManager.isAddingDynamicSkills()) - skills.put(skill.getName().toLowerCase(), skill); - else + public void addDynamicSkill(DynamicSkill skill) { + if (registrationManager.isAddingDynamicSkills()) { skills.put(skill.getName().toLowerCase(), skill); } else { throw new IllegalStateException("Cannot add dynamic skills from outside SkillAPI"); + } } /** @@ -607,11 +601,9 @@ public void addDynamicSkill(DynamicSkill skill) * * @param skill skill to register */ - public void addSkill(Skill skill) - { + public void addSkill(Skill skill) { skill = registrationManager.validate(skill); - if (skill != null) - skills.put(skill.getName().toLowerCase(), skill); + if (skill != null) { skills.put(skill.getName().toLowerCase(), skill); } } /** @@ -621,10 +613,8 @@ public void addSkill(Skill skill) * * @param skills skills to register */ - public void addSkills(Skill... skills) - { - for (Skill skill : skills) - addSkill(skill); + public void addSkills(Skill... skills) { + for (Skill skill : skills) { addSkill(skill); } } /** @@ -634,15 +624,12 @@ public void addSkills(Skill... skills) * * @param rpgClass class to register */ - public void addClass(RPGClass rpgClass) - { + public void addClass(RPGClass rpgClass) { rpgClass = registrationManager.validate(rpgClass); - if (rpgClass != null) - { + if (rpgClass != null) { classes.put(rpgClass.getName().toLowerCase(), rpgClass); ClassBoardManager.registerClass(rpgClass); - if (!groups.contains(rpgClass.getGroup())) - groups.add(rpgClass.getGroup()); + if (!groups.contains(rpgClass.getGroup())) { groups.add(rpgClass.getGroup()); } } } @@ -652,15 +639,12 @@ public void addClass(RPGClass rpgClass) * * @param rpgClass dynamic class to add */ - public void addDynamicClass(DynamicClass rpgClass) - { + public void addDynamicClass(DynamicClass rpgClass) { String key; - if (rpgClass != null && !classes.containsKey(key = rpgClass.getName().toLowerCase())) - { + if (rpgClass != null && !classes.containsKey(key = rpgClass.getName().toLowerCase())) { classes.put(key, rpgClass); ClassBoardManager.registerClass(rpgClass); - if (!groups.contains(rpgClass.getGroup())) - groups.add(rpgClass.getGroup()); + if (!groups.contains(rpgClass.getGroup())) { groups.add(rpgClass.getGroup()); } } } @@ -671,10 +655,8 @@ public void addDynamicClass(DynamicClass rpgClass) * * @param classes classes to register */ - public void addClasses(RPGClass... classes) - { - for (RPGClass rpgClass : classes) - addClass(rpgClass); + public void addClasses(RPGClass... classes) { + for (RPGClass rpgClass : classes) { addClass(rpgClass); } } /** @@ -683,9 +665,8 @@ public void addClasses(RPGClass... classes) * @param runnable the task to schedule * @param delay the delay in ticks */ - public static void schedule(BukkitRunnable runnable, int delay) - { - runnable.runTaskLater(singleton(), delay); + public static BukkitTask schedule(BukkitRunnable runnable, int delay) { + return runnable.runTaskLater(singleton(), delay); } /** @@ -694,9 +675,8 @@ public static void schedule(BukkitRunnable runnable, int delay) * @param runnable the task to schedule * @param delay the delay in ticks */ - public static void schedule(Runnable runnable, int delay) - { - Bukkit.getScheduler().runTaskLater(singleton, runnable, delay); + public static BukkitTask schedule(Runnable runnable, int delay) { + return Bukkit.getScheduler().runTaskLater(singleton, runnable, delay); } /** @@ -706,9 +686,8 @@ public static void schedule(Runnable runnable, int delay) * @param delay the delay in ticks before the first tick * @param period how often to run in ticks */ - public static void schedule(BukkitRunnable runnable, int delay, int period) - { - runnable.runTaskTimer(singleton(), delay, period); + public static BukkitTask schedule(BukkitRunnable runnable, int delay, int period) { + return runnable.runTaskTimer(singleton(), delay, period); } /** @@ -718,8 +697,7 @@ public static void schedule(BukkitRunnable runnable, int delay, int period) * @param key key to store under * @param value value to store */ - public static void setMeta(Metadatable target, String key, Object value) - { + public static void setMeta(Metadatable target, String key, Object value) { target.setMetadata(key, new FixedMetadataValue(singleton(), value)); } @@ -731,9 +709,9 @@ public static void setMeta(Metadatable target, String key, Object value) * * @return the stored value */ - public static Object getMeta(Metadatable target, String key) - { - return target.getMetadata(key).get(0).value(); + public static Object getMeta(Metadatable target, String key) { + List meta = target.getMetadata(key); + return meta == null || meta.size() == 0 ? null : meta.get(0).value(); } /** @@ -744,8 +722,7 @@ public static Object getMeta(Metadatable target, String key) * * @return the stored value */ - public static int getMetaInt(Metadatable target, String key) - { + public static int getMetaInt(Metadatable target, String key) { return target.getMetadata(key).get(0).asInt(); } @@ -757,8 +734,7 @@ public static int getMetaInt(Metadatable target, String key) * * @return the stored value */ - public static double getMetaDouble(Metadatable target, String key) - { + public static double getMetaDouble(Metadatable target, String key) { return target.getMetadata(key).get(0).asDouble(); } @@ -768,16 +744,25 @@ public static double getMetaDouble(Metadatable target, String key) * @param target entity to remove from * @param key key metadata was stored under */ - public static void removeMeta(Metadatable target, String key) - { + public static void removeMeta(Metadatable target, String key) { target.removeMetadata(key, singleton()); } + /** + * Grabs a config for SkillAPI + * + * @param name config file name + * + * @return config data + */ + public static CommentedConfig getConfig(String name) { + return new CommentedConfig(singleton, name); + } + /** * Reloads the plugin */ - public static void reload() - { + public static void reload() { SkillAPI inst = singleton(); inst.onDisable(); inst.onEnable(); diff --git a/src/com/sucy/skill/api/CombatProtection.java b/src/com/sucy/skill/api/CombatProtection.java new file mode 100644 index 00000000..67dfcb74 --- /dev/null +++ b/src/com/sucy/skill/api/CombatProtection.java @@ -0,0 +1,14 @@ +package com.sucy.skill.api; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +/** + * SkillAPI © 2018 + * com.sucy.skill.api.AllyChecker + */ +public interface CombatProtection { + boolean canAttack(final Player attacker, final Player defender); + boolean canAttack(final Player attacker, final LivingEntity defender); + boolean canAttack(final LivingEntity attacker, final LivingEntity defender); +} diff --git a/src/com/sucy/skill/api/DefaultCombatProtection.java b/src/com/sucy/skill/api/DefaultCombatProtection.java new file mode 100644 index 00000000..8c338047 --- /dev/null +++ b/src/com/sucy/skill/api/DefaultCombatProtection.java @@ -0,0 +1,36 @@ +package com.sucy.skill.api; + +import com.rit.sucy.player.Protection; +import com.sucy.skill.hook.NoCheatHook; +import com.sucy.skill.hook.PluginChecker; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +/** + * SkillAPI © 2018 + * com.sucy.skill.data.DefaultCombatProtection + */ +public class DefaultCombatProtection implements CombatProtection { + @Override + public boolean canAttack(final Player attacker, final Player defender) { + return canAttack((LivingEntity) attacker, defender); + } + + @Override + public boolean canAttack(final Player attacker, final LivingEntity defender) { + return canAttack((LivingEntity) attacker, defender); + } + + @Override + public boolean canAttack(final LivingEntity attacker, final LivingEntity defender) { + boolean canAttack; + if (PluginChecker.isNoCheatActive() && attacker instanceof Player) { + Player player = (Player) attacker; + NoCheatHook.exempt(player); + canAttack = Protection.canAttack(attacker, defender); + NoCheatHook.unexempt(player); + } else { canAttack = Protection.canAttack(attacker, defender); } + + return canAttack; + } +} diff --git a/src/com/sucy/skill/api/Settings.java b/src/com/sucy/skill/api/Settings.java index 584e5d73..b974a088 100644 --- a/src/com/sucy/skill/api/Settings.java +++ b/src/com/sucy/skill/api/Settings.java @@ -26,23 +26,32 @@ */ package com.sucy.skill.api; +import com.google.common.collect.ImmutableList; import com.rit.sucy.config.parse.DataSection; -import com.sucy.skill.api.util.NumberParser; +import com.rit.sucy.config.parse.NumberParser; import com.sucy.skill.log.Logger; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; /** *

Represents a set of settings that store configurable data for an object.

*/ -public class Settings -{ +public class Settings { private static final String BASE = "-base"; private static final String SCALE = "-scale"; - private final HashMap settings = new HashMap(); + private final HashMap settings; + + public Settings() { + this.settings = new LinkedHashMap<>(); + } + + public Settings(final Settings settings) { + this.settings = new HashMap<>(settings.settings); + } /** * Sets the value for a setting. You should only provide a @@ -52,8 +61,7 @@ public class Settings * @param key setting key * @param value setting value */ - public void set(String key, Object value) - { + public void set(String key, Object value) { settings.put(key, value); } @@ -68,8 +76,7 @@ public void set(String key, Object value) * @param base base value * @param scale value scale */ - public void set(String key, double base, double scale) - { + public void set(String key, double base, double scale) { settings.put(key + BASE, base); settings.put(key + SCALE, scale); } @@ -85,10 +92,8 @@ public void set(String key, double base, double scale) * @param key scaling setting name * @param value new base value */ - public void setBase(String key, double value) - { - if (!settings.containsKey(key + SCALE)) - { + public void setBase(String key, double value) { + if (!settings.containsKey(key + SCALE)) { settings.put(key + SCALE, 0.0); } settings.put(key + BASE, value); @@ -105,10 +110,8 @@ public void setBase(String key, double value) * @param key scaling setting name * @param value new scale value */ - public void setScale(String key, double value) - { - if (!settings.containsKey(key + BASE)) - { + public void setScale(String key, double value) { + if (!settings.containsKey(key + BASE)) { settings.put(key + BASE, 0.0); } settings.put(key + SCALE, value); @@ -122,8 +125,7 @@ public void setScale(String key, double value) * * @return double setting value */ - public double getDouble(String key) - { + public double getDouble(String key) { return getDouble(key, 0); } @@ -136,14 +138,10 @@ public double getDouble(String key) * * @return double setting value */ - public double getDouble(String key, double defaultValue) - { - if (settings.containsKey(key)) - { + public double getDouble(String key, double defaultValue) { + if (settings.containsKey(key)) { return NumberParser.parseDouble(settings.get(key).toString()); - } - else - { + } else { set(key, defaultValue); return defaultValue; } @@ -157,8 +155,7 @@ public double getDouble(String key, double defaultValue) * * @return integer setting value */ - public int getInt(String key) - { + public int getInt(String key) { return getInt(key, 0); } @@ -171,14 +168,10 @@ public int getInt(String key) * * @return integer setting value */ - public int getInt(String key, int defaultValue) - { - if (settings.containsKey(key)) - { + public int getInt(String key, int defaultValue) { + if (settings.containsKey(key)) { return Integer.parseInt(settings.get(key).toString()); - } - else - { + } else { set(key, defaultValue); return defaultValue; } @@ -192,8 +185,7 @@ public int getInt(String key, int defaultValue) * * @return boolean setting value */ - public boolean getBool(String key) - { + public boolean getBool(String key) { return settings.containsKey(key) && Boolean.parseBoolean(settings.get(key).toString()); } @@ -206,14 +198,10 @@ public boolean getBool(String key) * * @return boolean setting value */ - public boolean getBool(String key, boolean defaultValue) - { - if (settings.containsKey(key)) - { + public boolean getBool(String key, boolean defaultValue) { + if (settings.containsKey(key)) { return Boolean.parseBoolean(settings.get(key).toString()); - } - else - { + } else { set(key, defaultValue); return defaultValue; } @@ -227,8 +215,7 @@ public boolean getBool(String key, boolean defaultValue) * * @return String setting value */ - public String getString(String key) - { + public String getString(String key) { return getString(key, null); } @@ -241,14 +228,10 @@ public String getString(String key) * * @return String setting value */ - public String getString(String key, String defaultValue) - { - if (settings.containsKey(key) && settings.get(key) != null) - { + public String getString(String key, String defaultValue) { + if (settings.containsKey(key) && settings.get(key) != null) { return settings.get(key).toString(); - } - else - { + } else { set(key, defaultValue); return defaultValue; } @@ -262,15 +245,16 @@ public String getString(String key, String defaultValue) * @return string list or empty list if not found */ @SuppressWarnings("unchecked") - public List getStringList(String key) - { - if (settings.containsKey(key) && settings.get(key) instanceof List) - { - return (List) settings.get(key); - } - else - { - return new ArrayList(); + public List getStringList(String key) { + if (settings.containsKey(key)) { + final Object value = settings.get(key); + if (value instanceof List) { + return (List) settings.get(key); + } else { + return ImmutableList.of(value.toString()); + } + } else { + return new ArrayList<>(); } } @@ -283,8 +267,7 @@ public List getStringList(String key) * * @return scaled setting value */ - public double getAttr(String key, int level) - { + public double getAttr(String key, int level) { return getAttr(key, level, 0); } @@ -299,10 +282,8 @@ public double getAttr(String key, int level) * * @return scaled setting value */ - public double getAttr(String key, int level, double defaultValue) - { - if (!has(key)) - { + public double getAttr(String key, int level, double defaultValue) { + if (!has(key)) { set(key, defaultValue, 0); return defaultValue; } @@ -317,14 +298,10 @@ public double getAttr(String key, int level, double defaultValue) * * @return base value */ - public double getBase(String key) - { - if (!settings.containsKey(key + BASE)) - { + public double getBase(String key) { + if (!settings.containsKey(key + BASE)) { return 0; - } - else - { + } else { return NumberParser.parseDouble(settings.get(key + BASE).toString()); } } @@ -337,14 +314,10 @@ public double getBase(String key) * * @return change in value per level */ - public double getScale(String key) - { - if (!settings.containsKey(key + SCALE)) - { + public double getScale(String key) { + if (!settings.containsKey(key + SCALE)) { return 0; - } - else - { + } else { return NumberParser.parseDouble(settings.get(key + SCALE).toString()); } } @@ -358,18 +331,12 @@ public double getScale(String key) * * @return attribute value or 0 if not found */ - public Object getObj(String key, int level) - { - if (settings.containsKey(key)) - { + public Object getObj(String key, int level) { + if (settings.containsKey(key)) { return settings.get(key); - } - else if (settings.containsKey(key + BASE)) - { + } else if (settings.containsKey(key + BASE)) { return getAttr(key, level); - } - else - { + } else { return 0; } } @@ -383,8 +350,7 @@ else if (settings.containsKey(key + BASE)) * * @return true if defined, false otherwise */ - public boolean has(String key) - { + public boolean has(String key) { return settings.containsKey(key) || settings.containsKey(key + BASE); } @@ -394,8 +360,7 @@ public boolean has(String key) * * @param key name of the attribute */ - public void remove(String key) - { + public void remove(String key) { settings.remove(key); settings.remove(key + BASE); settings.remove(key + SCALE); @@ -411,10 +376,8 @@ public void remove(String key) * @param defaultBase default base value * @param defaultScale default scale value */ - public void checkDefault(String key, double defaultBase, double defaultScale) - { - if (!has(key)) - { + public void checkDefault(String key, double defaultBase, double defaultScale) { + if (!has(key)) { set(key, defaultBase, defaultScale); } } @@ -425,14 +388,11 @@ public void checkDefault(String key, double defaultBase, double defaultScale) * * @param config configuration section to save to */ - public void save(DataSection config) - { - if (config == null) - { + public void save(DataSection config) { + if (config == null) { return; } - for (String key : settings.keySet()) - { + for (String key : settings.keySet()) { config.set(key, settings.get(key)); } } @@ -448,15 +408,12 @@ public void save(DataSection config) * * @param config configuration section to load from */ - public void load(DataSection config) - { - if (config == null) - { + public void load(DataSection config) { + if (config == null) { return; } - for (String key : config.keys()) - { + for (String key : config.keys()) { settings.put(key, config.get(key)); } } @@ -464,11 +421,9 @@ public void load(DataSection config) /** * Dumps the settings to the console for debugging purposes */ - public void dumpToConsole() - { + public void dumpToConsole() { Logger.log("Settings:"); - for (String key : settings.keySet()) - { + for (String key : settings.keySet()) { Logger.log("- " + key + ": " + settings.get(key).toString()); } } diff --git a/src/com/sucy/skill/api/SkillPlugin.java b/src/com/sucy/skill/api/SkillPlugin.java index 535caabd..d3a7fc5f 100644 --- a/src/com/sucy/skill/api/SkillPlugin.java +++ b/src/com/sucy/skill/api/SkillPlugin.java @@ -26,7 +26,12 @@ */ package com.sucy.skill.api; +import com.google.common.collect.ImmutableList; import com.sucy.skill.SkillAPI; +import com.sucy.skill.dynamic.custom.CustomEffectComponent; +import com.sucy.skill.dynamic.trigger.Trigger; + +import java.util.List; /** *

Interface for plugins that define new classes and skills

@@ -34,8 +39,7 @@ * (e.g. adding classes in the registerClasses method and skills * in the registerSkills method). It keeps the API working nicely!

*/ -public interface SkillPlugin -{ +public interface SkillPlugin { /** *

Method to add new skills to the game

*

Use api.addSkills(Skill ... skills) to add them

@@ -53,4 +57,12 @@ public interface SkillPlugin *

This is called after registerSkills

*/ void registerClasses(SkillAPI api); + + default List getTriggers() { + return ImmutableList.of(); + } + + default List getComponents() { + return ImmutableList.of(); + } } diff --git a/src/com/sucy/skill/api/classes/RPGClass.java b/src/com/sucy/skill/api/classes/RPGClass.java index 895273f4..3f6c0d44 100644 --- a/src/com/sucy/skill/api/classes/RPGClass.java +++ b/src/com/sucy/skill/api/classes/RPGClass.java @@ -32,29 +32,41 @@ import com.sucy.skill.api.ReadOnlySettings; import com.sucy.skill.api.Settings; import com.sucy.skill.api.enums.ExpSource; +import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.api.skills.Skill; import com.sucy.skill.api.util.Data; import com.sucy.skill.data.GroupSettings; +import com.sucy.skill.data.Permissions; +import com.sucy.skill.gui.tool.IconHolder; import com.sucy.skill.log.LogType; import com.sucy.skill.log.Logger; -import com.sucy.skill.tree.SkillTree; -import com.sucy.skill.tree.map.MapTree; +import com.sucy.skill.tree.basic.InventoryTree; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; /** * Represents a template for a class used in the RPG system. This is * the class to extend when creating your own classes. */ -public abstract class RPGClass +public abstract class RPGClass implements IconHolder { - private final ArrayList skills = new ArrayList(); + private final HashMap skillMap = new HashMap(); + private final ArrayList skills = new ArrayList(); - private SkillTree skillTree; + private final HashSet blacklist = new HashSet(); + + private InventoryTree skillTree; + + private String actionBar; private String parent; private ItemStack icon; private TreeType tree; @@ -205,7 +217,7 @@ public ChatColor getPrefixColor() * * @return class skill tree */ - public SkillTree getSkillTree() + public InventoryTree getSkillTree() { return skillTree; } @@ -250,6 +262,12 @@ public RPGClass getParent() return SkillAPI.getClass(parent); } + public RPGClass getRoot() { + RPGClass root = this; + while (root.parent != null) root = root.getParent(); + return root; + } + /** * Retrieves the icon representing this class for menus * @@ -260,6 +278,69 @@ public ItemStack getIcon() return icon; } + /** + * @return map of skills for use in menus + */ + public HashMap getSkillMap() + { + if (skillMap.isEmpty()) + { + RPGClass current = this; + while (current != null) + { + for (Skill skill : current.skills) + skillMap.put(skill.getName().toLowerCase(), skill); + current = current.getParent(); + } + } + return skillMap; + } + + /** + * Retrieves the icon representing this class for menus + * + * @param data player to get the icon for + * + * @return icon representation of the class + */ + @Override + public ItemStack getIcon(PlayerData data) + { + return getIcon(); + } + + @Override + public boolean isAllowed(final Player player) { + return !needsPermission + || player.hasPermission(Permissions.CLASS) + || player.hasPermission(Permissions.CLASS + "." + name.toLowerCase().replace(" ", "-")); + } + + /** + * Gets the indicator for the class for the GUI tools + * + * @return GUI tool indicator + */ + public ItemStack getToolIcon() + { + ItemStack item = icon.clone(); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(name); + item.setItemMeta(meta); + return item; + } + + /** + * @return text to display in the action bar for the class (nullable) + */ + public String getActionBarText() { + return actionBar; + } + + public boolean hasActionBarText() { + return actionBar.trim().length() > 0; + } + /** * Checks whether or not the class receives experience * from the given source @@ -435,15 +516,15 @@ public ArrayList getOptions() { ArrayList list = new ArrayList(); for (RPGClass c : SkillAPI.getClasses().values()) - { if (c.getParent() == this) - { list.add(c); - } - } return list; } + public boolean canUse(final Material type) { + return !blacklist.contains(type); + } + /////////////////////////////////////////////////////// // // // Setting Methods // @@ -558,6 +639,7 @@ public void setManaRegen(double amount) private static final String PARENT = "parent"; private static final String NAME = "name"; private static final String PREFIX = "prefix"; + private static final String ACTION_BAR = "action-bar"; private static final String GROUP = "group"; private static final String MANA = "mana"; private static final String MAX = "max-level"; @@ -566,6 +648,7 @@ public void setManaRegen(double amount) private static final String PERM = "needs-permission"; private static final String ATTR = "attributes"; private static final String TREE = "tree"; + private static final String BLACKLIST = "blacklist"; /** * Saves the class template data to the config @@ -575,6 +658,7 @@ public void setManaRegen(double amount) public void save(DataSection config) { config.set(NAME, name); + config.set(ACTION_BAR, actionBar.replace(ChatColor.COLOR_CHAR, '&')); config.set(PREFIX, prefix.replace(ChatColor.COLOR_CHAR, '&')); config.set(GROUP, group); config.set(MANA, mana.replace(ChatColor.COLOR_CHAR, '&')); @@ -584,6 +668,7 @@ public void save(DataSection config) settings.save(config.createSection(ATTR)); config.set(REGEN, manaRegen); config.set(TREE, tree.toString()); + config.set(BLACKLIST, new ArrayList(blacklist)); ArrayList skillNames = new ArrayList(); for (Skill skill : skills) @@ -621,6 +706,7 @@ public void load(DataSection config) parent = config.getString(PARENT); icon = Data.parseIcon(config); name = config.getString(NAME, name); + actionBar = TextFormatter.colorString(config.getString(ACTION_BAR, "")); prefix = TextFormatter.colorString(config.getString(PREFIX, prefix)); group = config.getString(GROUP, "class"); mana = TextFormatter.colorString(config.getString(MANA, mana)); @@ -629,6 +715,15 @@ public void load(DataSection config) manaRegen = config.getDouble(REGEN, manaRegen); needsPermission = config.getString(PERM, needsPermission + "").equalsIgnoreCase("true"); tree = DefaultTreeType.getByName(config.getString(TREE, "requirement")); + for (final String type : config.getList(BLACKLIST)) { + if (type.isEmpty()) continue; + final Material mat = Material.matchMaterial(type.toUpperCase().replace(' ', '_')); + if (mat != null) { + blacklist.add(mat); + } else { + Logger.invalid(type + " is not a valid material for class " + name); + } + } settings.load(config.getSection(ATTR)); @@ -646,14 +741,7 @@ public void load(DataSection config) } } - if (SkillAPI.getSettings().isMapTreeEnabled()) - { - this.skillTree = new MapTree((SkillAPI) Bukkit.getPluginManager().getPlugin("SkillAPI"), this); - } - else - { - this.skillTree = this.tree.getTree((SkillAPI) Bukkit.getPluginManager().getPlugin("SkillAPI"), this); - } + this.skillTree = this.tree.getTree((SkillAPI) Bukkit.getPluginManager().getPlugin("SkillAPI"), this); } /** diff --git a/src/com/sucy/skill/api/event/KeyPressEvent.java b/src/com/sucy/skill/api/event/KeyPressEvent.java new file mode 100644 index 00000000..85d1d82a --- /dev/null +++ b/src/com/sucy/skill/api/event/KeyPressEvent.java @@ -0,0 +1,42 @@ +package com.sucy.skill.api.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * SkillAPI © 2018 + * com.sucy.skill.api.event.KeyPressEvent + */ +public class KeyPressEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + private final Key key; + + public KeyPressEvent(final Player player, final Key key) { + this.player = player; + this.key = key; + } + + public Player getPlayer() { + return player; + } + + public Key getKey() { + return key; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public enum Key { + LEFT, RIGHT, Q + } +} diff --git a/src/com/sucy/skill/api/event/PlayerCastSkillEvent.java b/src/com/sucy/skill/api/event/PlayerCastSkillEvent.java index 9efdc64a..1fc777cd 100644 --- a/src/com/sucy/skill/api/event/PlayerCastSkillEvent.java +++ b/src/com/sucy/skill/api/event/PlayerCastSkillEvent.java @@ -39,6 +39,7 @@ public class PlayerCastSkillEvent extends Event implements Cancellable private PlayerData playerData; private PlayerSkill skill; private Player player; + private double manaCost; private boolean cancelled; public PlayerCastSkillEvent(PlayerData playerData, PlayerSkill skill, Player player) @@ -46,6 +47,7 @@ public PlayerCastSkillEvent(PlayerData playerData, PlayerSkill skill, Player pla this.playerData = playerData; this.skill = skill; this.player = player; + this.manaCost = skill.getManaCost(); this.cancelled = false; } @@ -64,6 +66,14 @@ public PlayerSkill getSkill() return skill; } + public double getManaCost() { + return manaCost; + } + + public void setManaCost(final double manaCost) { + this.manaCost = manaCost; + } + @Override public boolean isCancelled() { diff --git a/src/com/sucy/skill/api/event/PlayerClassChangeEvent.java b/src/com/sucy/skill/api/event/PlayerClassChangeEvent.java index 6e6c1c71..bce4646c 100644 --- a/src/com/sucy/skill/api/event/PlayerClassChangeEvent.java +++ b/src/com/sucy/skill/api/event/PlayerClassChangeEvent.java @@ -37,7 +37,6 @@ */ public class PlayerClassChangeEvent extends Event { - private static final HandlerList handlers = new HandlerList(); private PlayerClass playerClass; private RPGClass previousClass; diff --git a/src/com/sucy/skill/api/event/PlayerExperienceGainEvent.java b/src/com/sucy/skill/api/event/PlayerExperienceGainEvent.java index 2a66fa03..348bfb03 100644 --- a/src/com/sucy/skill/api/event/PlayerExperienceGainEvent.java +++ b/src/com/sucy/skill/api/event/PlayerExperienceGainEvent.java @@ -98,7 +98,7 @@ public ExpSource getSource() * * @throws IllegalArgumentException if experience is less than 0 */ - public void setExp(int amount) + public void setExp(double amount) { if (amount < 0) { diff --git a/src/com/sucy/skill/api/event/PlayerPreClassChangeEvent.java b/src/com/sucy/skill/api/event/PlayerPreClassChangeEvent.java new file mode 100644 index 00000000..ac0d9056 --- /dev/null +++ b/src/com/sucy/skill/api/event/PlayerPreClassChangeEvent.java @@ -0,0 +1,89 @@ +package com.sucy.skill.api.event; + +import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.api.player.PlayerClass; +import com.sucy.skill.api.player.PlayerData; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * SkillAPI © 2018 + * com.sucy.skill.api.event.PlayerPreClassChangeEvent + */ +public class PlayerPreClassChangeEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private PlayerData playerData; + private PlayerClass playerClass; + private RPGClass previousClass; + private RPGClass newClass; + private boolean cancelled; + + /** + * Constructor + * + * @param playerData player information + * @param playerClass data of the player changing classes + * @param previousClass previous class of the player (null if wasn't a profession) + * @param newClass new class of the player (null if using the reset command) + */ + public PlayerPreClassChangeEvent(PlayerData playerData, PlayerClass playerClass, RPGClass previousClass, RPGClass newClass) { + this.playerData = playerData; + this.playerClass = playerClass; + this.previousClass = previousClass; + this.newClass = newClass; + this.cancelled = false; + } + + /** + * @return modified player class (null if not professed before) + */ + public PlayerClass getPlayerClass() { + return playerClass; + } + + /** + * @return Data of the player changing classes + */ + public PlayerData getPlayerData() { + return playerData; + } + + /** + * @return previous class of the player (null if not professed before) + */ + public RPGClass getPreviousClass() { + return previousClass; + } + + /** + * @return new class of the player + */ + public RPGClass getNewClass() { + return newClass; + } + + /** + * @return gets the handlers for the event + */ + public HandlerList getHandlers() { + return handlers; + } + + /** + * @return gets the handlers for the event + */ + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(final boolean b) { + cancelled = b; + } +} diff --git a/src/com/sucy/skill/api/event/PlayerSkillCastFailedEvent.java b/src/com/sucy/skill/api/event/PlayerSkillCastFailedEvent.java new file mode 100644 index 00000000..656ad821 --- /dev/null +++ b/src/com/sucy/skill/api/event/PlayerSkillCastFailedEvent.java @@ -0,0 +1,79 @@ +package com.sucy.skill.api.event; + +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.player.PlayerSkill; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * SkillAPI © 2018 + * com.sucy.skill.api.event.PlayerSkillCastFailedEvent + */ +public class PlayerSkillCastFailedEvent extends Event { + + public enum Cause { + CANCELED, + CASTER_DEAD, + EFFECT_FAILED, + NO_MANA, + NO_TARGET, + NOT_UNLOCKED, + ON_COOLDOWN, + SPECTATOR + } + + private static final HandlerList handlers = new HandlerList(); + private PlayerSkill skill; + private Cause cause; + + private PlayerSkillCastFailedEvent(final PlayerSkill skill, final Cause cause) { + this.skill = skill; + this.cause = cause; + } + + public static boolean invoke(final PlayerSkill skill, final Cause cause) { + Bukkit.getPluginManager().callEvent(new PlayerSkillCastFailedEvent(skill, cause)); + return false; + } + + /** + * @return player trying to cast the skill + */ + public PlayerData getPlayerData() { + return skill.getPlayerData(); + } + + /** + * @return skill that was attempted to be cast + */ + public PlayerSkill getSkill() { + return skill; + } + + /** + * @return reason the skill cast failed + */ + public Cause getCause() { + return cause; + } + + /** + * Retrieves the handlers for the event + * + * @return list of event handlers + */ + @Override + public HandlerList getHandlers() { + return handlers; + } + + /** + * Retrieves the handlers for the event + * + * @return list of event handlers + */ + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/com/sucy/skill/api/event/SkillDamageEvent.java b/src/com/sucy/skill/api/event/SkillDamageEvent.java index e55a512a..06dabe8f 100644 --- a/src/com/sucy/skill/api/event/SkillDamageEvent.java +++ b/src/com/sucy/skill/api/event/SkillDamageEvent.java @@ -26,6 +26,7 @@ */ package com.sucy.skill.api.event; +import com.sucy.skill.api.skills.Skill; import org.bukkit.entity.LivingEntity; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -40,6 +41,8 @@ public class SkillDamageEvent extends Event implements Cancellable private static final HandlerList handlers = new HandlerList(); private LivingEntity damager; private LivingEntity target; + private String classification; + private Skill skill; private double damage; private boolean cancelled; @@ -50,14 +53,23 @@ public class SkillDamageEvent extends Event implements Cancellable * @param target entity receiving the damage * @param damage the amount of damage dealt */ - public SkillDamageEvent(LivingEntity damager, LivingEntity target, double damage) + public SkillDamageEvent(Skill skill, LivingEntity damager, LivingEntity target, double damage, String classification) { + this.skill = skill; this.damager = damager; this.target = target; this.damage = damage; + this.classification = classification; this.cancelled = false; } + /** + * @return skill used to deal the damage + */ + public Skill getSkill() { + return skill; + } + /** * Retrieves the entity that dealt the damage * @@ -88,6 +100,10 @@ public double getDamage() return damage; } + public String getClassification() { + return classification; + } + /** * Sets the amount of damage dealt * diff --git a/src/com/sucy/skill/api/event/TrueDamageEvent.java b/src/com/sucy/skill/api/event/TrueDamageEvent.java index 788e45ed..de4d5b61 100644 --- a/src/com/sucy/skill/api/event/TrueDamageEvent.java +++ b/src/com/sucy/skill/api/event/TrueDamageEvent.java @@ -26,6 +26,7 @@ */ package com.sucy.skill.api.event; +import com.sucy.skill.api.skills.Skill; import org.bukkit.entity.LivingEntity; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -39,6 +40,7 @@ public class TrueDamageEvent extends Event implements Cancellable private static final HandlerList handlers = new HandlerList(); private LivingEntity damager; private LivingEntity target; + private Skill skill; private double damage; private boolean cancelled; @@ -49,14 +51,22 @@ public class TrueDamageEvent extends Event implements Cancellable * @param target entity receiving the damage * @param damage the amount of damage dealt */ - public TrueDamageEvent(LivingEntity damager, LivingEntity target, double damage) + public TrueDamageEvent(Skill skill, LivingEntity damager, LivingEntity target, double damage) { + this.skill = skill; this.damager = damager; this.target = target; this.damage = damage; this.cancelled = false; } + /** + * @return skill used to deal the damage + */ + public Skill getSkill() { + return skill; + } + /** * Retrieves the entity that dealt the damage * diff --git a/src/com/sucy/skill/api/exception/SkillTreeException.java b/src/com/sucy/skill/api/exception/SkillTreeException.java index bd757e23..6a6c0961 100644 --- a/src/com/sucy/skill/api/exception/SkillTreeException.java +++ b/src/com/sucy/skill/api/exception/SkillTreeException.java @@ -31,7 +31,8 @@ */ public class SkillTreeException extends Exception { - /** + + /** * Constructor * * @param message exception message diff --git a/src/com/sucy/skill/api/particle/EffectData.java b/src/com/sucy/skill/api/particle/EffectData.java new file mode 100644 index 00000000..62ccb6b5 --- /dev/null +++ b/src/com/sucy/skill/api/particle/EffectData.java @@ -0,0 +1,123 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.EffectData + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import com.sucy.skill.api.particle.target.EffectTarget; + +import java.util.HashMap; +import java.util.Iterator; + +/** + * A collection of effects played on a target + */ +public class EffectData +{ + private static HashMap effects = new HashMap(); + + private EffectTarget target; + + /** + * @param target target of each effect + */ + public EffectData(EffectTarget target) + { + this.target = target; + } + + /** + * Checks whether or not an effect is still running + * + * @param key effect key + * + * @return true if running + */ + public static boolean isEffectActive(String key) + { + return effects.containsKey(key); + } + + /** + * Fetches an active effect by key + * + * @param key effect key + * + * @return active effect or null if not found + */ + public EffectInstance getEffect(String key) + { + return effects.get(key); + } + + /** + * @return true if should keep the data, false otherwise + */ + public boolean isValid() + { + return effects.size() > 0 && target.isValid(); + } + + /** + * Starts running an effect for the target. If the effect is already + * running for the target, the running effect will be stopped before + * the new one is started. + * + * @param effect effect to run + * @param ticks ticks to run for + * @param level effect level + */ + public void runEffect(ParticleEffect effect, int ticks, int level) + { + EffectInstance instance = new EffectInstance(effect, target, level); + instance.extend(ticks); + effects.put(effect.getName(), instance); + } + + /** + * Cancels an effect via its associated key + * + * @param key key of the effect to cancel + */ + public void cancel(String key) { + effects.remove(key); + } + + /** + * Ticks each effect for the target + */ + public void tick() + { + Iterator iterator = effects.values().iterator(); + while (iterator.hasNext()) + { + EffectInstance effect = iterator.next(); + if (effect.isValid()) + effect.tick(); + else + iterator.remove(); + } + } +} diff --git a/src/com/sucy/skill/api/particle/EffectInstance.java b/src/com/sucy/skill/api/particle/EffectInstance.java new file mode 100644 index 00000000..775e8141 --- /dev/null +++ b/src/com/sucy/skill/api/particle/EffectInstance.java @@ -0,0 +1,92 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.EffectInstance + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import com.sucy.skill.api.particle.target.EffectTarget; + +/** + * An instanced particle effect + */ +public class EffectInstance +{ + private ParticleEffect effect; + private EffectTarget target; + + private int level; + private int life; + private int tick; + private int frame; + + /** + * @param effect the effect to play + * @param target target to play an effect for + * @param level the level of the effect + */ + public EffectInstance(ParticleEffect effect, EffectTarget target, int level) + { + this.effect = effect; + this.target = target; + this.level = level; + + life = 0; + tick = -1; + frame = 0; + } + + /** + * @return true if the target is still valid + */ + public boolean isValid() + { + return target.isValid() && life > 0; + } + + /** + * Extends the effect duration + * + * @param duration effect duration + */ + public void extend(int duration) + { + life = Math.max(life, duration); + } + + /** + * Ticks the effect + */ + public void tick() + { + tick++; + if (tick % effect.getInterval() == 0) + { + effect.play(target.getLocation(), frame, level); + frame++; + tick = 0; + } + life--; + } +} diff --git a/src/com/sucy/skill/api/particle/EffectManager.java b/src/com/sucy/skill/api/particle/EffectManager.java new file mode 100644 index 00000000..626f7582 --- /dev/null +++ b/src/com/sucy/skill/api/particle/EffectManager.java @@ -0,0 +1,183 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.EffectManager + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import com.rit.sucy.config.CommentedConfig; +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.particle.direction.XZHandler; +import com.sucy.skill.api.particle.target.EffectTarget; +import com.sucy.skill.api.particle.target.EntityTarget; +import com.sucy.skill.task.EffectTask; +import com.sucy.skill.thread.MainThread; +import org.bukkit.entity.LivingEntity; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Handles the management of particle effects and related components + */ +public class EffectManager { + private static Map instances = new ConcurrentHashMap(); + private static Map effects = new HashMap(); + private static Map formulas = new HashMap(); + + /** + * Initializes the utility, loading formulas from the config file + */ + public static void init() { + CommentedConfig config = SkillAPI.getConfig("effects"); + config.saveDefaultConfig(); + DataSection data = config.getConfig(); + for (String key : data.keys()) { + formulas.put(key, new PolarSettings(data.getSection(key))); + if (key.equals("one-circle")) { formulas.get(key).getPoints(XZHandler.instance); } + } + + MainThread.register(new EffectTask()); + } + + public static void cleanUp() { + formulas.clear(); + effects.clear(); + instances.clear(); + } + + /** + * Registers a new particle effect, replacing any conflicting + * effects already registered under the key + * + * @param effect effect to register + */ + public static void register(ParticleEffect effect) { + if (effect != null) { effects.put(effect.getName(), effect); } + } + + /** + * Registers a new formula for effects + * + * @param key key to register under + * @param formula formula to register + */ + public static void register(String key, PolarSettings formula) { + if (formula != null) { formulas.put(key, formula); } + } + + /** + * Gets a formula by key + * + * @param key formula key + * + * @return formula + */ + public static PolarSettings getFormula(String key) { + return formulas.get(key); + } + + /** + * Fetches an effect by key + * + * @param name name of the effect + * + * @return particle effect + */ + public static ParticleEffect getEffect(String name) { + return effects.get(name); + } + + /** + * Clears effects for a given target + * + * @param target target to clear for + */ + public static void clear(EffectTarget target) { + instances.remove(target); + } + + /** + * Clears effects for a given target + * + * @param target target to clear for + */ + public static void clear(LivingEntity target) { + instances.entrySet() + .removeIf(entry -> entry.getKey() instanceof EntityTarget && ((EntityTarget) entry.getKey()).getEntity() == target); + } + + /** + * Gets the effect data for the given target + * + * @param target target to get the data for + * @return effect data for the target or null if doesn't exist + */ + public static EffectData getEffectData(EffectTarget target) { + return instances.get(target); + } + + /** + * Fetches an active effect for a given target + * + * @param target target to get the effect for + * @param key effect key + * + * @return active effect or null if not found + */ + public static EffectInstance getEffect(EffectTarget target, String key) { + if (!instances.containsKey(target)) { return null; } + + return instances.get(target).getEffect(key); + } + + /** + * Starts running an effect for a target. If the effect is already + * running for the target, the running effect will be stopped before + * the new one is started. + * + * @param effect effect to run + * @param target target to run for + * @param ticks ticks to run for + * @param level effect level + */ + public static void runEffect(ParticleEffect effect, EffectTarget target, int ticks, int level) { + if (!instances.containsKey(target)) { instances.put(target, new EffectData(target)); } + instances.get(target).runEffect(effect, ticks, level); + } + + /** + * Ticks all active effects + */ + public static void tick() { + Iterator iterator = instances.values().iterator(); + while (iterator.hasNext()) { + EffectData data = iterator.next(); + if (data.isValid()) { data.tick(); } else { iterator.remove(); } + } + } +} diff --git a/src/com/sucy/skill/api/particle/EffectPlayer.java b/src/com/sucy/skill/api/particle/EffectPlayer.java new file mode 100644 index 00000000..f502b4bf --- /dev/null +++ b/src/com/sucy/skill/api/particle/EffectPlayer.java @@ -0,0 +1,176 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.EffectPlayer + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2016 Steven Sucy + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.particle.direction.Directions; +import com.sucy.skill.api.particle.target.EffectTarget; +import com.sucy.skill.log.Logger; +import org.bukkit.Material; + +/** + * Handles playing effects based on configuration settings + */ +public class EffectPlayer +{ + public static final String SHAPE = "-shape"; + public static final String SHAPE_DIR = "-shape-dir"; + public static final String SHAPE_SIZE = "-shape-size"; + public static final String ANIMATION = "-animation"; + public static final String ANIM_DIR = "-anim-dir"; + public static final String ANIM_SIZE = "-anim-size"; + public static final String INTERVAL = "-interval"; + public static final String VIEW_RANGE = "-view-range"; + + public static final String P_TYPE = "-particle-type"; + public static final String MAT = "-particle-material"; + public static final String DATA = "-particle-data"; + public static final String AMOUNT = "-particle-amount"; + public static final String DX = "-particle-dx"; + public static final String DY = "-particle-dy"; + public static final String DZ = "-particle-dz"; + public static final String SPEED = "-particle-speed"; + + private Settings settings; + + /** + * Sets up an effect player that applies effects based of the values in the provided settings. + * All of the available settings are provided as static values in this class. + * + * @param settings settings to read from + */ + public EffectPlayer(Settings settings) + { + this.settings = settings; + } + + /** + * Plays a particle effect, grabbing values from the settings data + * + * @param target target to play for + * @param key effect key to use + * @param ticks duration of effect in ticks + * @param level level of the effect + */ + public void start(EffectTarget target, String key, int ticks, int level) + { + start(target, key, ticks, level, false); + } + + /** + * Plays a particle effect, grabbing values from the settings data + * + * @param target target to play for + * @param key effect key to use + * @param ticks duration of effect in ticks + * @param level level of the effect + * @param noPrefix exclude prefix when grabbing settings + */ + public void start(EffectTarget target, String key, int ticks, int level, boolean noPrefix) + { + // If the effect is already running, just refresh it + EffectInstance instance = EffectManager.getEffect(target, key); + if (instance != null) + { + instance.extend(ticks); + return; + } + + // If the effect is not registered, make it + if (EffectManager.getEffect(key) == null) + makeEffect(key, noPrefix); + + // Play the effect + EffectManager.runEffect(EffectManager.getEffect(key), target, ticks, level); + } + + /** + * Creates and registers an effect + * + * @param key effect key + * @param noPrefix exclude prefix when grabbing settings + */ + private void makeEffect(String key, boolean noPrefix) + { + String keyMod = noPrefix ? "" : key; + + // Grab the particle type + ParticleType particleType = ParticleLookup.find(settings.getString(keyMod + P_TYPE, "SPELL")); + ParticleSettings particle; + + // Block Crack and the related use materials + if (particleType.usesMat()) + { + try + { + particle = new ParticleSettings( + particleType, + (float) settings.getDouble(keyMod + DX), + (float) settings.getDouble(keyMod + DY), + (float) settings.getDouble(keyMod + DZ), + (float) settings.getDouble(keyMod + SPEED, 1), + settings.getInt(keyMod + AMOUNT, 1), + Material.matchMaterial(settings.getString(keyMod + MAT, "DIRT")), + settings.getInt(keyMod + DATA) + ); + } + catch (Exception ex) + { + Logger.invalid("Bad material for particle effect - " + settings.getString(keyMod + MAT)); + return; + } + } + + // Others just use basic data + else + particle = new ParticleSettings( + particleType, + (float) settings.getDouble(keyMod + DX), + (float) settings.getDouble(keyMod + DY), + (float) settings.getDouble(keyMod + DZ), + (float) settings.getDouble(keyMod + SPEED, 1), + settings.getInt(keyMod + AMOUNT, 1) + ); + + // Make the effect + ParticleEffect effect = new ParticleEffect( + key, + EffectManager.getFormula(settings.getString(keyMod + SHAPE, "single")), + EffectManager.getFormula(settings.getString(keyMod + ANIMATION, "single")), + particle, + Directions.byName(settings.getString(keyMod + SHAPE_DIR, "XZ")), + Directions.byName(settings.getString(keyMod + ANIM_DIR, "XZ")), + settings.getString(keyMod + SHAPE_SIZE, "1"), + settings.getString(keyMod + ANIM_SIZE, "1"), + settings.getInt(keyMod + INTERVAL, 1), + settings.getInt(keyMod + VIEW_RANGE, 25) + ); + + // Register the effect + EffectManager.register(effect); + } +} diff --git a/src/com/sucy/skill/api/particle/Particle.java b/src/com/sucy/skill/api/particle/Particle.java new file mode 100644 index 00000000..ceb0cffb --- /dev/null +++ b/src/com/sucy/skill/api/particle/Particle.java @@ -0,0 +1,300 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.Particle + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import com.rit.sucy.version.VersionManager; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; + +/** + * Simplified particle utility compared to MCCore's + */ +public class Particle { + private static Constructor packet; + + private static Method toNms; + private static Method getHandle; + private static Method sendPacket; + + private static Field connection; + + private static HashMap particleTypes = new HashMap<>(); + + /** + * Initializes the SkillAPI particle utility + */ + public static void init() { + try { + String version = Bukkit.getServer().getClass().getPackage().getName().substring(23); + String nms = "net.minecraft.server." + version + '.'; + String craft = "org.bukkit.craftbukkit." + version + '.'; + getHandle = Class.forName(craft + "entity.CraftPlayer").getMethod("getHandle"); + connection = Class.forName(nms + "EntityPlayer").getDeclaredField("playerConnection"); + sendPacket = Class.forName(nms + "PlayerConnection") + .getDeclaredMethod("sendPacket", Class.forName(nms + "Packet")); + + Class packetClass; + // 1.13+ Servers + Class particleEnum; + if (VersionManager.isVersionAtLeast(11300)) { + Class craftParticle = Class.forName(craft + "CraftParticle"); + toNms = craftParticle.getDeclaredMethod("toNMS", org.bukkit.Particle.class, Object.class); + particleEnum = Class.forName(nms + "ParticleParam"); + packetClass = Class.forName(nms + "PacketPlayOutWorldParticles"); + packet = packetClass.getConstructor( + particleEnum, + boolean.class, + float.class, + float.class, + float.class, + float.class, + float.class, + float.class, + float.class, + int.class); + } + + // 1.8+ servers + else if (VersionManager.isVersionAtLeast(VersionManager.V1_8_0)) { + particleEnum = Class.forName(nms + "EnumParticle"); + for (Object value : particleEnum.getEnumConstants()) { particleTypes.put(value.toString(), value); } + packetClass = Class.forName(nms + "PacketPlayOutWorldParticles"); + packet = packetClass.getConstructor( + particleEnum, + Boolean.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Integer.TYPE, + int[].class); + } + + // 1.7.x servers + else { + packetClass = Class.forName(nms + "PacketPlayOutWorldParticles"); + packet = packetClass.getConstructor( + String.class, + Boolean.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Float.TYPE, + Integer.TYPE); + } + } catch (Exception ex) { + System.out.println("Failed to set up particles, are you using Cauldron?"); + } + } + + /** + * Sends a list of packets to a player + * + * @param player player to send to + * @param packets packets to send + * + * @throws Exception + */ + public static void send(Player player, List packets) + throws Exception { + Object network = connection.get(getHandle.invoke(player)); + for (Object packet : packets) { sendPacket.invoke(network, packet); } + } + + /** + * Sends a list of packets to a player + * + * @param player player to send to + * @param packets packets to send + * + * @throws Exception when reflection fails + */ + public static void send(Player player, Object[] packets) + throws Exception { + Object network = connection.get(getHandle.invoke(player)); + for (Object packet : packets) { sendPacket.invoke(network, packet); } + } + + /** + * Sends packets to all players within a range + * + * @param loc location of the effect + * @param packets packets from the effect + * @param range range to play for + */ + public static void send(Location loc, List packets, double range) + throws Exception { + range *= range; + for (Player player : loc.getWorld().getPlayers()) { + if (player.getLocation().distanceSquared(loc) < range) { send(player, packets); } + } + } + + /** + * Sends packets to all players within a range + * + * @param loc location of the effect + * @param packets packets from the effect + * @param range range to play for + */ + public static void send(Location loc, Object[] packets, double range) + throws Exception { + range *= range; + for (Player player : loc.getWorld().getPlayers()) { + if (player.getLocation().distanceSquared(loc) < range) { send(player, packets); } + } + } + + /** + * Make a particle packet using the given data + * + * @param settings particle details + * @param loc location to play at + * + * @return particle object or null if invalid + * + * @throws Exception + */ + public static Object make(ParticleSettings settings, Location loc) + throws Exception { + return make(settings, loc.getX(), loc.getY(), loc.getZ()); + } + + /** + * Make a particle packet using the given data + * + * @param settings particle details + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * + * @return particle object or null if invalid + * + * @throws Exception + */ + public static Object make(ParticleSettings settings, double x, double y, double z) throws Exception { + // Invalid particle settings + if (settings == null || settings.type == null) { return null; } + + return make( + settings.type.name(), + x, + y, + z, + settings.dx, + settings.dy, + settings.dz, + settings.speed, + settings.amount, + settings.material, + settings.data); + } + + public static Object make( + final String name, + double x, + double y, + double z, + float dx, + float dy, + float dz, + float speed, + int amount, + Material material, + int data) throws Exception { + + // 1.13+ servers + if (VersionManager.isVersionAtLeast(11300)) { + Object obj = data; + final org.bukkit.Particle bukkit = org.bukkit.Particle.valueOf(name); + switch (bukkit) { + case REDSTONE: + final Color color = Color.fromRGB(mapColor(dx + 1), mapColor(dy), mapColor(dz)); + dx = 0; + dy = 0; + dz = 0; + obj = new org.bukkit.Particle.DustOptions(color, amount); + break; + case ITEM_CRACK: + obj = new ItemStack(material, 1); + break; + case BLOCK_CRACK: + case BLOCK_DUST: + case FALLING_DUST: + obj = material.createBlockData(); + default: + break; + } + final Object particle = toNms.invoke(null, bukkit, obj); + return packet.newInstance(particle, true, (float) x, (float) y, (float) z, dx, dy, dz, speed, amount); + } + + // 1.8+ servers use an enum value to validate the particle type + else if (VersionManager.isVersionAtLeast(VersionManager.V1_8_0)) { + Object enumType = particleTypes.get(name); + return packet.newInstance( + enumType, + true, + (float) x, + (float) y, + (float) z, + dx, + dy, + dz, + speed, + amount, + material == null ? new int[0] : new int[] { material.ordinal(), data }); + } + + // 1.7.x servers just use a string for the type, + // so make sure it is a usable type before blindly + // sending it through + else { + return packet.newInstance(name, (float) x, (float) y, (float) z, dx, dy, dz, amount, 1); + } + } + + private static int mapColor(double decimal) { + return (int) Math.max(0, Math.min(255, decimal * 255)); + } +} diff --git a/src/com/sucy/skill/api/particle/ParticleEffect.java b/src/com/sucy/skill/api/particle/ParticleEffect.java new file mode 100644 index 00000000..92b5c90d --- /dev/null +++ b/src/com/sucy/skill/api/particle/ParticleEffect.java @@ -0,0 +1,185 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.ParticleEffect + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import com.sucy.skill.api.particle.direction.DirectionHandler; +import com.sucy.skill.data.Point2D; +import com.sucy.skill.data.Point3D; +import com.sucy.skill.data.formula.Formula; +import com.sucy.skill.data.formula.IValue; +import com.sucy.skill.data.formula.value.CustomValue; +import org.bukkit.Location; + +/** + * A particle effect that can be played + */ +public class ParticleEffect +{ + private Object[] packets; + + private PolarSettings shape; + private PolarSettings animation; + private ParticleSettings particle; + private IValue size; + private IValue animSize; + + private DirectionHandler shapeDir; + private DirectionHandler animDir; + + private String name; + + private int interval; + private int view; + + /** + * @param shape shape formula details + * @param animation motion animation formula details + * @param particle settings of the particle to use + * @param shapeDir shape orientation + * @param animDir animation orientation + * @param size formula string for shape size + * @param animSize formula string for animation size + * @param interval time between animation frames in ticks + * @param viewRange range in blocks players can see the effect from + */ + public ParticleEffect( + String name, + PolarSettings shape, + PolarSettings animation, + ParticleSettings particle, + DirectionHandler shapeDir, + DirectionHandler animDir, + String size, + String animSize, + int interval, + int viewRange) + { + this.name = name; + this.shape = shape; + this.animation = animation; + this.size = new Formula( + size, + new CustomValue("t"), + new CustomValue("p"), + new CustomValue("c"), + new CustomValue("s"), + new CustomValue("x"), + new CustomValue("y"), + new CustomValue("z"), + new CustomValue("v") + ); + this.particle = particle; + this.shapeDir = shapeDir; + this.animDir = animDir; + this.animSize = new Formula( + animSize, + new CustomValue("t"), + new CustomValue("p"), + new CustomValue("c"), + new CustomValue("s"), + new CustomValue("x"), + new CustomValue("y"), + new CustomValue("z"), + new CustomValue("v") + ); + this.interval = interval; + this.view = viewRange; + + int points = shape.getPoints(shapeDir).length; + animation.getPoints(animDir); + packets = new Object[animation.getCopies() * points]; + } + + /** + * @return name of the effect + */ + public String getName() + { + return name; + } + + /** + * @return time between each frame in ticks + */ + public int getInterval() + { + return interval; + } + + /** + * @return number of animation frames + */ + public int getFrames() + { + return animation.getSteps(); + } + + /** + * Plays the effect + * + * @param loc location to play at + * @param frame frame of the animation to play + * @param level level of the effect + */ + public void play(Location loc, int frame, int level) + { + frame = frame % animation.getSteps(); + try + { + int next = (frame + 1) * animation.getCopies(); + Point3D[] animPoints = animation.getPoints(animDir); + Point3D[] shapePoints = shape.getPoints(shapeDir); + Point2D[] trig = animation.getTrig(frame); + + Point2D cs = trig[0]; + double t = animation.getT(frame); + double p = (double) frame / animation.getSteps(); + + int j = 0, k = 0; + for (int i = frame * animation.getCopies(); i < next; i++) + { + Point3D p1 = animPoints[i]; + double animSize = this.animSize.compute(t, p, cs.x, cs.y, p1.x, p1.y, p1.z, level); + for (Point3D p2 : shapePoints) + { + double size = this.size.compute(t, p, cs.x, cs.y, p2.x, p2.y, p2.z, level); + packets[k++] = particle.instance( + p1.x * animSize + animDir.rotateX(p2, trig[j]) * size + loc.getX(), + p1.y * animSize + animDir.rotateY(p2, trig[j]) * size + loc.getY(), + p1.z * animSize + animDir.rotateZ(p2, trig[j]) * size + loc.getZ() + ); + } + j++; + } + Particle.send(loc, packets, view); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } +} diff --git a/src/com/sucy/skill/api/particle/ParticleLookup.java b/src/com/sucy/skill/api/particle/ParticleLookup.java new file mode 100644 index 00000000..f252d77b --- /dev/null +++ b/src/com/sucy/skill/api/particle/ParticleLookup.java @@ -0,0 +1,68 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.ParticleLookup + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import java.util.HashMap; + +public class ParticleLookup +{ + private static final HashMap BY_EDITOR = new HashMap(); + private static final HashMap BY_OLD = new HashMap(); + + public static ParticleType find(String key) + { + key = key.toLowerCase(); + ParticleType type = BY_EDITOR.get(key); + if (type == null) + type = getByName(key); + if (type == null) + type = BY_OLD.get(key); + return type; + } + + public static ParticleType getByEditor(String editorKey) + { + return BY_EDITOR.get(editorKey.toLowerCase()); + } + + public static ParticleType getByOld(String oldName) + { + return BY_OLD.get(oldName.toLowerCase()); + } + + public static ParticleType getByName(String name) + { + return ParticleType.valueOf(name.toUpperCase().replace(" ", "_")); + } + + public static void register(ParticleType type) + { + BY_EDITOR.put(type.editorKey(), type); + if (type.oldName() != null) + BY_OLD.put(type.oldName().toLowerCase(), type); + } +} diff --git a/src/com/sucy/skill/api/particle/ParticleSettings.java b/src/com/sucy/skill/api/particle/ParticleSettings.java new file mode 100644 index 00000000..8519c724 --- /dev/null +++ b/src/com/sucy/skill/api/particle/ParticleSettings.java @@ -0,0 +1,167 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.ParticleSettings + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import com.rit.sucy.config.parse.DataSection; +import org.bukkit.Material; + +/** + * Settings for playing a particle + */ +public class ParticleSettings { + private static final String + PARTICLE_KEY = "particle", + MATERIAL_KEY = "material", + DATA_KEY = "type", + AMOUNT_KEY = "amount", + DX_KEY = "dx", + DY_KEY = "dy", + DZ_KEY = "dz", + SPEED_KEY = "speed"; + + // Particle type + public final ParticleType type; + + // Offset values + public final float dx, dy, dz; + + // Particle speed + public final float speed; + + // Particle amount + public final int amount; + + // Particle extra data + public final Material material; + public final int data; + + /** + * Sets up a particle that doesn't require material data + * + * @param type particle type + * @param dx DX value + * @param dy DY value + * @param dz DZ value + * @param speed particle speed + * @param amount particle amount + */ + public ParticleSettings(ParticleType type, float dx, float dy, float dz, float speed, int amount) { + this.type = type; + this.dx = dx; + this.dy = dy; + this.dz = dz; + this.speed = speed; + this.amount = amount; + if (type.usesMat()) { + throw new IllegalArgumentException("Must provide material data for " + type.name()); + } else { + material = null; + data = 0; + } + } + + /** + * Sets up a particle that requires material data + * + * @param type particle type + * @param dx DX value + * @param dy DY value + * @param dz DZ value + * @param speed particle speed + * @param amount particle amount + * @param material material to use + * @param data material data value + */ + public ParticleSettings( + ParticleType type, + float dx, + float dy, + float dz, + float speed, + int amount, + Material material, + int data) { + this.type = type; + this.dx = dx; + this.dy = dy; + this.dz = dz; + this.speed = speed; + this.amount = amount; + if (type.usesMat()) { + this.material = material; + this.data = data; + } else { + this.material = material; + this.data = data; + } + } + + /** + * Loads a particle setup from config data + * + * @param config config data to load from + */ + public ParticleSettings(DataSection config) { + String type = config.getString(PARTICLE_KEY); + this.type = ParticleLookup.find(type); + this.dx = config.getFloat(DX_KEY, 0); + this.dy = config.getFloat(DY_KEY, 0); + this.dz = config.getFloat(DZ_KEY, 0); + this.speed = config.getFloat(SPEED_KEY, 1); + this.amount = config.getInt(AMOUNT_KEY, 1); + + if (this.type.usesMat()) { + Material mat = null; + int data = 0; + try { + mat = Material.valueOf(config.getString(MATERIAL_KEY).toUpperCase().replace(" ", "_")); + data = config.getInt(DATA_KEY); + } catch (Exception ex) { /* */ } + this.material = mat; + this.data = data; + } else { + this.material = null; + this.data = 0; + } + } + + /** + * Makes a new instance of the particle effect + * + * @param x X-axis coordinates + * @param y Y-axis coordinates + * @param z Z-axis coordinates + * + * @return packet instance + * + * @throws Exception + */ + public Object instance(double x, double y, double z) + throws Exception { + return Particle.make(this, x, y, z); + } +} diff --git a/src/com/sucy/skill/api/particle/ParticleType.java b/src/com/sucy/skill/api/particle/ParticleType.java new file mode 100644 index 00000000..428c90bd --- /dev/null +++ b/src/com/sucy/skill/api/particle/ParticleType.java @@ -0,0 +1,111 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.ParticleType + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +public enum ParticleType +{ + BARRIER("barrier"), + BLOCK_CRACK("block crack", "blackcrack_", true), + CLOUD("cloud", "cloud"), + CRIT("crit", "crit"), + CRIT_MAGIC("magic crit", "magicCrit"), + DAMAGE_INDICATOR("damage indicator"), + DRAGON_BREATH("dragon breath"), + DRIP_LAVA("drip lava", "dripLava"), + DRIP_WATER("drip water", "dripWater"), + ENCHANTMENT_TABLE("enchantment table", "enchantmenttable"), + END_ROD("end rod"), + EXPLOSION_HUGE("huge explosion", "hugeexplosion"), + EXPLOSION_LARGE("large explode", "largeexplode"), + EXPLOSION_NORMAL("explode", "explode"), + FIREWORKS_SPARK("firework spark", "fireworksSpark"), + FLAME("flame", "flame"), + FOOTSTEP("footstep", "footstep"), + HEART("heart", "heart"), + LAVA("lava", "lava"), + MOB_APPEARANCE("mob appearance"), + NOTE("note", "note"), + PORTAL("portal", "portal"), + REDSTONE("red dust", "reddust"), + SLIME("slime", "slime"), + SMOKE_NORMAL("smoke", "smoke"), + SMOKE_LARGE("large smoke", "largesmoke"), + SNOWBALL("snowball poof", "snowballpoof"), + SNOW_SHOVEL("snow shovel", "snowshovel"), + SPELL("spell", "spell"), + SPELL_INSTANT("instant spell", "instantSpell"), + SPELL_MOB("mob spell", "mobSpell"), + SPELL_MOB_AMBIENT("mob spell ambient", "mobSpellAmbient"), + SPELL_WITCH("witch magic", "witchMagic"), + SUSPEND_DEPTH("depth suspend", "depthSuspend"), + SUSPENDED("suspend", "suspend"), + SWEEP_ATTACK("sweep attack"), + TOWN_AURA("town aura", "townaura"), + VILLAGER_ANGRY("angry villager", "angryVillager"), + VILLAGER_HAPPY("happy villager", "happyVillager"), + WATER_BUBBLE("bubble", "bubble"), + WATER_SPLASH("splash", "splash"), + WATER_WAKE("water wake"); + + private String editor; + private String old; + private boolean mat; + + ParticleType(String editorKey) + { + editor = editorKey; + ParticleLookup.register(this); + } + + ParticleType(String editorKey, String oldName) + { + editor = editorKey; + old = oldName; + ParticleLookup.register(this); + } + + ParticleType(String editorKey, String oldName, boolean usesMat) + { + this(editorKey, oldName); + mat = usesMat; + } + + public String editorKey() + { + return editor; + } + + public String oldName() + { + return old; + } + + public boolean usesMat() + { + return mat; + } +} diff --git a/src/com/sucy/skill/api/particle/PolarSettings.java b/src/com/sucy/skill/api/particle/PolarSettings.java new file mode 100644 index 00000000..455342cd --- /dev/null +++ b/src/com/sucy/skill/api/particle/PolarSettings.java @@ -0,0 +1,222 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.FormulaSettings + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle; + +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.api.particle.direction.DirectionHandler; +import com.sucy.skill.data.Point2D; +import com.sucy.skill.data.Point3D; +import com.sucy.skill.data.formula.Formula; +import com.sucy.skill.data.formula.IValue; +import com.sucy.skill.data.formula.value.CustomValue; + +import java.util.HashMap; + +/** + * Settings for a particle effect + */ +public class PolarSettings +{ + private HashMap points = new HashMap(); + + private Point2D[][] trig; + + private IValue formula; + private int copies; + private int steps; + private double domain; + private double xOff, yOff, zOff; + + /** + * Sets up a formula for particle effects + * + * @param formula formula to use + * @param steps the number of steps to apply + */ + public PolarSettings(IValue formula, int steps) + { + this(formula, steps, 1, 1); + } + + /** + * Sets up a formula for particle effects + * + * @param formula formula to use + * @param steps the number of steps to apply + * @param copies number of copies to use rotated about the origin + * @param domain domain of the input values + */ + public PolarSettings(IValue formula, int steps, int copies, double domain) + { + this.formula = formula; + this.copies = Math.max(1, copies); + this.domain = Math.max(0, domain / steps); + this.steps = Math.max(1, steps); + + double angle = Math.PI * 2 * this.domain; + double cos = Math.cos(angle); + double sin = Math.sin(angle); + double copyCos = Math.cos(Math.PI * 2 / copies); + double copySin = Math.sin(Math.PI * 2 / copies); + + this.trig = new Point2D[steps][copies]; + Point2D rot = new Point2D(1, 0); + Point2D copy = new Point2D(); + for (int i = 0; i < steps; i++) + { + copy.x = rot.x; + copy.y = rot.y; + for (int j = 0; j < copies; j++) + { + trig[i][j] = new Point2D(copy.x, copy.y); + copy.rotate(copyCos, copySin); + } + rot.rotate(cos, sin); + } + } + + /** + * Loads settings from config data + * + * @param settings settings to load from + */ + public PolarSettings(DataSection settings) + { + this( + new Formula( + settings.getString("formula"), + new CustomValue("t"), + new CustomValue("p"), + new CustomValue("c"), + new CustomValue("s") + ), + settings.getInt("steps"), + settings.getInt("copies"), + settings.getDouble("domain") + ); + setOffset( + settings.getDouble("x"), + settings.getDouble("y"), + settings.getDouble("z") + ); + } + + /** + * Fetches the trig values for the animation + * + * @param step animation step + * + * @return trig values + */ + public Point2D[] getTrig(int step) + { + return trig[step]; + } + + /** + * @return number of copies + */ + public int getCopies() + { + return copies; + } + + /** + * @return number of steps in the formula + */ + public int getSteps() + { + return steps; + } + + /** + * Sets the offset for the effect + * + * @param x X-Axis offset + * @param y Y-Axis offset + * @param z Z-Axis offset + * + * @return this + */ + public PolarSettings setOffset(double x, double y, double z) + { + this.xOff = x; + this.yOff = y; + this.zOff = z; + return this; + } + + /** + * Grabs the base points that make up the shape of the effect + * + * @return shape points + */ + public Point3D[] getPoints(DirectionHandler direction) + { + if (!points.containsKey(direction)) + calculate(direction); + return points.get(direction); + } + + /** + * Performs the calculations for the points making up the shape + */ + private void calculate(DirectionHandler direction) + { + Point3D[] points = new Point3D[copies * steps]; + this.points.put(direction, points); + int k = 0; + for (int i = 0; i < steps; i++) + { + Point2D rot = trig[i][0]; + double t = domain * i; + double r = formula.compute(t, (double) i / steps, rot.x, rot.y); + for (int j = 0; j < copies; j++) + { + Point2D copy = trig[i][j]; + Point3D p = new Point3D(); + direction.apply(p, r * copy.x, r * copy.y); + p.x += xOff; + p.y += yOff; + p.z += zOff; + points[k++] = p; + } + } + } + + /** + * Gets the time step value for the animation step + * + * @param step animation step + * + * @return time step + */ + public double getT(int step) + { + return domain * step; + } +} diff --git a/src/com/sucy/skill/api/particle/SpigotParticles.java b/src/com/sucy/skill/api/particle/SpigotParticles.java new file mode 100644 index 00000000..04119ceb --- /dev/null +++ b/src/com/sucy/skill/api/particle/SpigotParticles.java @@ -0,0 +1,99 @@ +package com.sucy.skill.api.particle; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.api.particle.SpigotParticles + */ +public class SpigotParticles { + private static boolean error = true; + + public static void play(final Location loc, final String particle, final float dx, final float dy, final float dz, final int count, final float speed, final double distance) { + play(loc, particle, dx, dy, dz, count, speed, distance, null); + } + + public static void playItem(final Location loc, final String particle, final float dx, final float dy, final float dz, final int count, final float speed, final double distance, final Material material) { + play(loc, particle, dx, dy, dz, count, speed, distance, material); + } + + public static void playBlock(final Location loc, final String particle, final float dx, final float dy, final float dz, final int count, final float speed, final double distance, final Material material) { + play(loc, particle, dx, dy, dz, count, speed, distance, material); + } + + private static void play(final Location loc, final String particle, final float dx, final float dy, final float dz, final int count, final float speed, final double distance, final Material material) { + final String key = particle.toLowerCase().replace('_', ' '); + final Particle effect = CONVERSION.get(key); + if (effect == null) return; + + try { + final Object packet = com.sucy.skill.api.particle.Particle.make( + effect.name(), loc.getX(), loc.getY(), loc.getZ(), dx, dy, dz, speed, count, material, 0); + com.sucy.skill.api.particle.Particle.send(loc, ImmutableList.of(packet), distance); + } catch (final Exception ex) { + if (error) { + ex.printStackTrace(); + error = false; + } + } + } + + private static final Map CONVERSION = ImmutableMap.builder() + .put("angry villager", Particle.VILLAGER_ANGRY) + .put("barrier", Particle.BARRIER) + .put("block crack", Particle.BLOCK_CRACK) + .put("bubble", Particle.WATER_BUBBLE) + .put("cloud", Particle.CLOUD) + .put("crit", Particle.CRIT) + .put("damage indicator", Particle.DAMAGE_INDICATOR) + .put("death", Particle.SUSPENDED) + .put("death suspend", Particle.SUSPENDED_DEPTH) + .put("dragon breath", Particle.DRAGON_BREATH) + .put("drip lava", Particle.DRIP_LAVA) + .put("drip water", Particle.DRIP_WATER) + .put("enchantment table", Particle.ENCHANTMENT_TABLE) + .put("end rod", Particle.END_ROD) + .put("ender signal", Particle.PORTAL) + .put("explode", Particle.EXPLOSION_NORMAL) + .put("firework spark", Particle.FIREWORKS_SPARK) + .put("flame", Particle.FLAME) + .put("footstep", Particle.CLOUD) + .put("happy villager", Particle.VILLAGER_HAPPY) + .put("heart", Particle.HEART) + .put("huge explosion", Particle.EXPLOSION_HUGE) + .put("hurt", Particle.DAMAGE_INDICATOR) + .put("icon crack", Particle.ITEM_CRACK) + .put("instant spell", Particle.SPELL_INSTANT) + .put("large explode", Particle.EXPLOSION_LARGE) + .put("large smoke", Particle.SMOKE_LARGE) + .put("lava", Particle.LAVA) + .put("magic crit", Particle.CRIT_MAGIC) + .put("mob spell", Particle.SPELL_MOB) + .put("mob spell ambient", Particle.SPELL_MOB_AMBIENT) + .put("mobspawner flames", Particle.FLAME) + .put("note", Particle.NOTE) + .put("portal", Particle.PORTAL) + .put("potion break", Particle.SPELL) + .put("red dust", Particle.REDSTONE) + .put("sheep eat", Particle.MOB_APPEARANCE) + .put("slime", Particle.SLIME) + .put("smoke", Particle.SMOKE_NORMAL) + .put("snowball poof", Particle.SNOWBALL) + .put("snow shovel", Particle.SNOW_SHOVEL) + .put("spell", Particle.SPELL) + .put("splash", Particle.WATER_SPLASH) + .put("sweep attack", Particle.SWEEP_ATTACK) + .put("suspend", Particle.SUSPENDED) + .put("town aura", Particle.TOWN_AURA) + .put("water drop", Particle.WATER_DROP) + .put("water wake", Particle.WATER_WAKE) + .put("witch magic", Particle.SPELL_WITCH) + .put("wolf hearts", Particle.HEART) + .build(); +} diff --git a/src/com/sucy/skill/api/particle/direction/DirectionHandler.java b/src/com/sucy/skill/api/particle/direction/DirectionHandler.java new file mode 100644 index 00000000..37964f19 --- /dev/null +++ b/src/com/sucy/skill/api/particle/direction/DirectionHandler.java @@ -0,0 +1,75 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.direction.DirectionHandler + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.direction; + +import com.sucy.skill.data.Point2D; +import com.sucy.skill.data.Point3D; + +/** + * Handles the directional application + */ +public interface DirectionHandler +{ + /** + * Applies the two results from the polar calculation to a point + * + * @param point the point to apply it to + * @param n1 first value + * @param n2 second value + */ + public void apply(Point3D point, double n1, double n2); + + /** + * Calculates the X value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateX(Point3D p, Point2D trig); + + /** + * Calculates the Y value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateY(Point3D p, Point2D trig); + + /** + * Calculates the Z value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateZ(Point3D p, Point2D trig); +} diff --git a/src/com/sucy/skill/api/particle/direction/Directions.java b/src/com/sucy/skill/api/particle/direction/Directions.java new file mode 100644 index 00000000..82ed0ed2 --- /dev/null +++ b/src/com/sucy/skill/api/particle/direction/Directions.java @@ -0,0 +1,51 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.direction.Directions + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.direction; + +/** + * Handles getting a direction handler by name + */ +public class Directions +{ + /** + * Fetches the direction handler by name, defaulting + * to XZ if invalid + * + * @param name XY, XZ, or YZ + * + * @return corresponding handler or XZ if invalid + */ + public static DirectionHandler byName(String name) + { + if (name.equalsIgnoreCase("XY")) + return XYHandler.instance; + else if (name.equals("YZ")) + return YZHandler.instance; + else + return XZHandler.instance; + } +} diff --git a/src/com/sucy/skill/api/particle/direction/XYHandler.java b/src/com/sucy/skill/api/particle/direction/XYHandler.java new file mode 100644 index 00000000..cb58a115 --- /dev/null +++ b/src/com/sucy/skill/api/particle/direction/XYHandler.java @@ -0,0 +1,91 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.direction.XYHandler + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.direction; + +import com.sucy.skill.data.Point2D; +import com.sucy.skill.data.Point3D; + +/** + * Handles the XY direction + */ +public class XYHandler implements DirectionHandler +{ + public static XYHandler instance = new XYHandler(); + + /** + * Applies the two results from the polar calculation to a point + * + * @param point the point to apply it to + * @param n1 first value + * @param n2 second value + */ + public void apply(Point3D point, double n1, double n2) + { + point.x = n1; + point.y = n2; + point.z = 0; + } + + /** + * Calculates the X value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateX(Point3D p, Point2D trig) + { + return p.x * trig.x - p.y * trig.y; + } + + /** + * Calculates the Y value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateY(Point3D p, Point2D trig) + { + return p.x * trig.y + p.y * trig.x; + } + + /** + * Calculates the Z value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateZ(Point3D p, Point2D trig) + { + return p.z; + } +} diff --git a/src/com/sucy/skill/api/particle/direction/XZHandler.java b/src/com/sucy/skill/api/particle/direction/XZHandler.java new file mode 100644 index 00000000..6d1cfc08 --- /dev/null +++ b/src/com/sucy/skill/api/particle/direction/XZHandler.java @@ -0,0 +1,91 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.direction.XZHandler + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.direction; + +import com.sucy.skill.data.Point2D; +import com.sucy.skill.data.Point3D; + +/** + * Handles the XZ direction + */ +public class XZHandler implements DirectionHandler +{ + public static XZHandler instance = new XZHandler(); + + /** + * Applies the two results from the polar calculation to a point + * + * @param point the point to apply it to + * @param n1 first value + * @param n2 second value + */ + public void apply(Point3D point, double n1, double n2) + { + point.x = n1; + point.y = 0; + point.z = n2; + } + + /** + * Calculates the X value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateX(Point3D p, Point2D trig) + { + return p.x * trig.x - p.z * trig.y; + } + + /** + * Calculates the Y value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateY(Point3D p, Point2D trig) + { + return p.y; + } + + /** + * Calculates the Z value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateZ(Point3D p, Point2D trig) + { + return p.x * trig.y + p.z * trig.x; + } +} diff --git a/src/com/sucy/skill/api/particle/direction/YZHandler.java b/src/com/sucy/skill/api/particle/direction/YZHandler.java new file mode 100644 index 00000000..4e801b09 --- /dev/null +++ b/src/com/sucy/skill/api/particle/direction/YZHandler.java @@ -0,0 +1,91 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.direction.YZHandler + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.direction; + +import com.sucy.skill.data.Point2D; +import com.sucy.skill.data.Point3D; + +/** + * Handles the YZ direction + */ +public class YZHandler implements DirectionHandler +{ + public static YZHandler instance = new YZHandler(); + + /** + * Applies the two results from the polar calculation to a point + * + * @param point the point to apply it to + * @param n1 first value + * @param n2 second value + */ + public void apply(Point3D point, double n1, double n2) + { + point.x = 0; + point.y = n1; + point.z = n2; + } + + /** + * Calculates the X value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateX(Point3D p, Point2D trig) + { + return p.x; + } + + /** + * Calculates the Y value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateY(Point3D p, Point2D trig) + { + return p.y * trig.x - p.z * trig.y; + } + + /** + * Calculates the Z value of a point after rotation + * + * @param p original point + * @param trig trig data + * + * @return rotation + */ + public double rotateZ(Point3D p, Point2D trig) + { + return p.y * trig.y + p.z * trig.x; + } +} diff --git a/src/com/sucy/skill/api/particle/target/EffectTarget.java b/src/com/sucy/skill/api/particle/target/EffectTarget.java new file mode 100644 index 00000000..e3a3a0df --- /dev/null +++ b/src/com/sucy/skill/api/particle/target/EffectTarget.java @@ -0,0 +1,47 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.entity.EffectTarget + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.target; + +import org.bukkit.Location; + +/** + * Handles grabbing the location for a particle effect + */ +public interface EffectTarget +{ + /** + * Gets the location to center the effect around + * + * @return effect location + */ + public Location getLocation(); + + /** + * @return tue if target is still valid, false otherwise + */ + public boolean isValid(); +} diff --git a/src/com/sucy/skill/api/particle/target/EntityTarget.java b/src/com/sucy/skill/api/particle/target/EntityTarget.java new file mode 100644 index 00000000..c2355681 --- /dev/null +++ b/src/com/sucy/skill/api/particle/target/EntityTarget.java @@ -0,0 +1,84 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.target.EntityTarget + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.target; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +import java.util.Objects; + +/** + * Causes an effect to follow the target entity + */ +public class EntityTarget implements EffectTarget +{ + private Entity entity; + private Location loc; + + /** + * @param target entity to follow + */ + public EntityTarget(Entity target) + { + this.entity = target; + this.loc = target.getLocation(); + } + + /** + * Gets the location to center the effect around + * + * @return effect location + */ + public Location getLocation() + { + return entity.getLocation(loc); + } + + public Entity getEntity() { + return entity; + } + + /** + * @return tue if target is still valid, false otherwise + */ + public boolean isValid() + { + return entity.isValid() && !entity.isDead(); + } + + @Override + public int hashCode() { + return Objects.hash(entity, loc); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof EntityTarget)) return false; + final EntityTarget target = (EntityTarget)o; + return target.entity == entity && target.loc.equals(loc); + } +} diff --git a/src/com/sucy/skill/api/particle/target/FixedTarget.java b/src/com/sucy/skill/api/particle/target/FixedTarget.java new file mode 100644 index 00000000..83dc3513 --- /dev/null +++ b/src/com/sucy/skill/api/particle/target/FixedTarget.java @@ -0,0 +1,74 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.target.FixedTarget + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.target; + +import org.bukkit.Location; + +/** + * A fixed location to play an effect + */ +public class FixedTarget implements EffectTarget +{ + private Location loc; + + /** + * @param loc location to play effect around + */ + public FixedTarget(Location loc) + { + this.loc = loc.clone(); + } + + /** + * Gets the location to center the effect around + * + * @return effect location + */ + public Location getLocation() + { + return loc; + } + + /** + * @return tue if target is still valid, false otherwise + */ + public boolean isValid() + { + return true; + } + + @Override + public int hashCode() { + return loc.hashCode(); + } + + @Override + public boolean equals(Object o) + { + return (o instanceof FixedTarget) && ((FixedTarget) o).loc.equals(loc); + } +} diff --git a/src/com/sucy/skill/api/particle/target/FollowTarget.java b/src/com/sucy/skill/api/particle/target/FollowTarget.java new file mode 100644 index 00000000..bf079839 --- /dev/null +++ b/src/com/sucy/skill/api/particle/target/FollowTarget.java @@ -0,0 +1,73 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.target.FollowTarget + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.target; + +import org.bukkit.Location; + +/** + * Tracks a followable target to play an effect around + */ +public class FollowTarget implements EffectTarget +{ + private Followable entity; + + /** + * @param target object to follow + */ + public FollowTarget(Followable target) + { + this.entity = target; + } + + /** + * Gets the location to center the effect around + * + * @return effect location + */ + public Location getLocation() + { + return entity.getLocation(); + } + + /** + * @return tue if target is still valid, false otherwise + */ + public boolean isValid() + { + return entity.isValid(); + } + + @Override + public int hashCode() { + return entity.hashCode(); + } + + @Override + public boolean equals(final Object o) { + return (o instanceof FollowTarget) && (((FollowTarget) o).entity.equals(entity)); + } +} diff --git a/src/com/sucy/skill/api/particle/target/Followable.java b/src/com/sucy/skill/api/particle/target/Followable.java new file mode 100644 index 00000000..9fccf9c6 --- /dev/null +++ b/src/com/sucy/skill/api/particle/target/Followable.java @@ -0,0 +1,45 @@ +/** + * SkillAPI + * com.sucy.skill.api.particle.target.Followable + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.particle.target; + +import org.bukkit.Location; + +/** + * Interface allowing something to be followed by particle effects + */ +public interface Followable +{ + /** + * @return retrieves the location of the object + */ + public Location getLocation(); + + /** + * @return true if can be followed still + */ + public boolean isValid(); +} diff --git a/src/com/sucy/skill/api/player/PlayerAccounts.java b/src/com/sucy/skill/api/player/PlayerAccounts.java index 511bd904..720d99c6 100644 --- a/src/com/sucy/skill/api/player/PlayerAccounts.java +++ b/src/com/sucy/skill/api/player/PlayerAccounts.java @@ -44,8 +44,7 @@ * SkillAPI.getPlayerData methods. This would be if you want * to extend functionality for handling the inactive accounts. */ -public class PlayerAccounts -{ +public class PlayerAccounts { private final HashMap classData = new HashMap(); private int active; @@ -58,8 +57,7 @@ public class PlayerAccounts * * @param player player to store data for */ - public PlayerAccounts(OfflinePlayer player) - { + public PlayerAccounts(OfflinePlayer player) { this.player = player; PlayerData data = new PlayerData(player, true); @@ -72,8 +70,7 @@ public PlayerAccounts(OfflinePlayer player) * * @return active account ID */ - public int getActiveId() - { + public int getActiveId() { return active; } @@ -82,8 +79,7 @@ public int getActiveId() * * @return active account data */ - public PlayerData getActiveData() - { + public PlayerData getActiveData() { return classData.get(active); } @@ -92,8 +88,7 @@ public PlayerData getActiveData() * * @return Bukkit player object or null if offline/dead */ - public Player getPlayer() - { + public Player getPlayer() { return player.getPlayer(); } @@ -102,8 +97,7 @@ public Player getPlayer() * * @return Bukkit offline player object */ - public OfflinePlayer getOfflinePlayer() - { + public OfflinePlayer getOfflinePlayer() { return player; } @@ -112,8 +106,7 @@ public OfflinePlayer getOfflinePlayer() * * @return owner's name */ - public String getPlayerName() - { + public String getPlayerName() { return player.getName(); } @@ -122,8 +115,7 @@ public String getPlayerName() * * @return available account number */ - public int getAccountLimit() - { + public int getAccountLimit() { return SkillAPI.getSettings().getMaxAccounts(getPlayer()); } @@ -136,8 +128,7 @@ public int getAccountLimit() * * @return true if data exists, false otherwise */ - public boolean hasData(int id) - { + public boolean hasData(int id) { return classData.containsKey(id); } @@ -148,8 +139,7 @@ public boolean hasData(int id) * * @return account data or null if not found */ - public PlayerData getData(int id) - { + public PlayerData getData(int id) { return classData.get(id); } @@ -165,10 +155,8 @@ public PlayerData getData(int id) * * @return account data or null if invalid id or player */ - public PlayerData getData(int id, OfflinePlayer player, boolean init) - { - if (!hasData(id) && id > 0 && player != null) - { + public PlayerData getData(int id, OfflinePlayer player, boolean init) { + if (!hasData(id) && id > 0 && player != null) { classData.put(id, new PlayerData(player, init)); } return classData.get(id); @@ -180,8 +168,7 @@ public PlayerData getData(int id, OfflinePlayer player, boolean init) * * @return all account data for the player */ - public HashMap getAllData() - { + public HashMap getAllData() { return classData; } @@ -192,8 +179,7 @@ public HashMap getAllData() * * @param id ID of the account to switch to */ - public void setAccount(int id) - { + public void setAccount(int id) { setAccount(id, true); } @@ -205,43 +191,39 @@ public void setAccount(int id) * @param id ID of the account to switch to * @param apply whether or not to apply the switch */ - public void setAccount(int id, boolean apply) - { + public void setAccount(int id, boolean apply) { Player player = getPlayer(); - if (player == null || id == active || !apply) - { + if (player == null || id == active || !apply) { active = id; return; } - if (id <= getAccountLimit() && id > 0 && !classData.containsKey(id)) - { + if (id <= getAccountLimit() && id > 0 && !classData.containsKey(id)) { classData.put(id, new PlayerData(player, false)); } - if (classData.containsKey(id)) - { + if (classData.containsKey(id)) { PlayerAccountChangeEvent event = new PlayerAccountChangeEvent(this, active, id); Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) - { + if (event.isCancelled()) { return; } - ClassBoardManager.clear(new VersionPlayer(player)); - getActiveData().stopPassives(player); - getActiveData().clearBonuses(); - AttributeListener.clearBonuses(player); - if (getActiveData().hasClass() && SkillAPI.getSettings().isSkillBarEnabled()) - { - getActiveData().getSkillBar().clear(player); - } - active = event.getNewID(); - getActiveData().startPassives(player); - getActiveData().updateScoreboard(); - getActiveData().updateHealthAndMana(player); - AttributeListener.updatePlayer(getActiveData()); - if (getActiveData().hasClass() && SkillAPI.getSettings().isSkillBarEnabled()) - { - getActiveData().getSkillBar().setup(player); + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { + ClassBoardManager.clear(new VersionPlayer(player)); + getActiveData().stopPassives(player); + AttributeListener.clearBonuses(player); + getActiveData().clearBonuses(); + active = event.getNewID(); + getActiveData().startPassives(player); + getActiveData().updateScoreboard(); + getActiveData().updateHealthAndMana(player); + AttributeListener.updatePlayer(getActiveData()); + if (getActiveData().hasClass() && SkillAPI.getSettings().isSkillBarEnabled() && !SkillAPI.getSettings() + .isUsingCombat()) { + getActiveData().getSkillBar().setup(player); + } + getActiveData().getEquips().update(player); + } else { + active = event.getNewID(); } } } diff --git a/src/com/sucy/skill/api/player/PlayerClass.java b/src/com/sucy/skill/api/player/PlayerClass.java index 7d525118..31849ee7 100644 --- a/src/com/sucy/skill/api/player/PlayerClass.java +++ b/src/com/sucy/skill/api/player/PlayerClass.java @@ -42,6 +42,7 @@ import com.sucy.skill.language.RPGFilter; import com.sucy.skill.manager.TitleManager; import org.bukkit.Bukkit; +import org.bukkit.entity.Player; /** *

Represents a player's class progress.

@@ -52,7 +53,7 @@ * rather what the player has within the class. For more general information * about the class, you should use the RPGClass class.

*/ -public final class PlayerClass +public class PlayerClass { private PlayerData player; private RPGClass classData; @@ -307,7 +308,22 @@ public void setPoints(int amount) * @param amount amount of experience to give * @param source type of the source of the experience */ - public void giveExp(double amount, ExpSource source) + public void giveExp(double amount, ExpSource source) { + giveExp(amount, source, true); + } + + /** + *

Gives experience to the class under the context of the experience source.

+ *

This will also check for leveling up after the experience is added.

+ *

If the class does not normally receive experience from the source, + * it will still launch an experience event, just it will start off as + * cancelled in case it should still be given in select circumstances.

+ * + * @param amount amount of experience to give + * @param source type of the source of the experience + * @param showMessage whether or not to show the configured message if enabled + */ + public void giveExp(double amount, ExpSource source, boolean showMessage) { // Cannot give a non-positive amount of exp if (amount <= 0 || level >= classData.getMaxLevel()) @@ -320,22 +336,24 @@ public void giveExp(double amount, ExpSource source) event.setCancelled(!classData.receivesExp(source)); Bukkit.getPluginManager().callEvent(event); + int rounded = (int)Math.ceil(event.getExp()); + // Add experience if not cancelled - if (!event.isCancelled() && event.getExp() > 0) + if (!event.isCancelled() && rounded > 0) { - if (SkillAPI.getSettings().isShowExpMessages() && player.getPlayer() != null) + if (showMessage && SkillAPI.getSettings().isShowExpMessages() && player.getPlayer() != null) { TitleManager.show( player.getPlayer(), TitleType.EXP_GAINED, NotificationNodes.EXP, - RPGFilter.EXP.setReplacement(amount + ""), + RPGFilter.EXP.setReplacement(rounded + ""), RPGFilter.CLASS.setReplacement(classData.getName()), - Filter.AMOUNT.setReplacement(amount + "") + Filter.AMOUNT.setReplacement(rounded + "") ); } - exp += amount; + exp += rounded; checkLevelUp(); } } @@ -396,12 +414,7 @@ private void checkLevelUp() { giveLevels(levels); - // Level up effect - if (SkillAPI.getSettings().hasLevelUpEffect()) - { - DynamicSkill skill = SkillAPI.getSettings().getLevelUpSkill(); - skill.cast(player.getPlayer(), level); - } + // Level up message if (SkillAPI.getSettings().isShowLevelMessages()) { TitleManager.show( @@ -442,12 +455,23 @@ public void giveLevels(int amount) getPlayerData().giveAttribPoints(classData.getGroupSettings().getAttribsForLevels(level, level - amount)); // Update health/mana - getPlayerData().updateHealthAndMana(getPlayerData().getPlayer()); + final Player player = getPlayerData().getPlayer(); + if (player != null) { + getPlayerData().updateHealthAndMana(getPlayerData().getPlayer()); + getPlayerData().getEquips().update(getPlayerData().getPlayer()); + } getPlayerData().autoLevel(); // Call the event PlayerLevelUpEvent event = new PlayerLevelUpEvent(this, amount); Bukkit.getPluginManager().callEvent(event); + + // Apply the effect + if (SkillAPI.getSettings().hasLevelUpEffect()) + { + DynamicSkill skill = SkillAPI.getSettings().getLevelUpSkill(); + skill.cast(player, level); + } } /** diff --git a/src/com/sucy/skill/api/player/PlayerCombos.java b/src/com/sucy/skill/api/player/PlayerCombos.java index 1a1f0e7c..8f4bf89d 100644 --- a/src/com/sucy/skill/api/player/PlayerCombos.java +++ b/src/com/sucy/skill/api/player/PlayerCombos.java @@ -42,10 +42,9 @@ * Represents the click combos available for a player to use along * with their current click pattern */ -public class PlayerCombos -{ - private HashMap skills = new HashMap(); - private HashMap reverse = new HashMap(); +public class PlayerCombos { + private HashMap skills = new HashMap<>(); + private HashMap reverse = new HashMap<>(); private PlayerData player; private Click[] clicks; @@ -57,8 +56,7 @@ public class PlayerCombos * * @param data owning player's data */ - public PlayerCombos(PlayerData data) - { + public PlayerCombos(PlayerData data) { this.player = data; this.clickIndex = 0; this.clicks = new Click[SkillAPI.getComboManager().getComboSize()]; @@ -72,8 +70,7 @@ public PlayerCombos(PlayerData data) * * @return current number of clicks in the active combo */ - public int getComboCount() - { + public int getComboCount() { return clickIndex; } @@ -82,8 +79,7 @@ public int getComboCount() * * @return map of combo IDs to skills */ - public HashMap getSkillMap() - { + public HashMap getSkillMap() { return skills; } @@ -92,8 +88,7 @@ public HashMap getSkillMap() * * @return owning player's data */ - public PlayerData getPlayerData() - { + public PlayerData getPlayerData() { return player; } @@ -105,18 +100,15 @@ public PlayerData getPlayerData() * * @return skill name bound to the ID or null if none */ - public String getSkillName(int id) - { + public String getSkillName(int id) { return skills.get(id); } - /** * Clears the player's current click combo, causing them * to not count their recent clicks towards a combo */ - public void clearCombo() - { + public void clearCombo() { clickIndex = 0; } @@ -126,10 +118,9 @@ public void clearCombo() * * @param click click to apply for the player */ - public void applyClick(Click click) - { + public void applyClick(Click click) { // Don't count disabled clicks - if (!SkillAPI.getComboManager().isClickEnabled(click.getId())) return; + if (!SkillAPI.getComboManager().isClickEnabled(click.getId())) { return; } checkExpired(); @@ -139,13 +130,11 @@ public void applyClick(Click click) // Cast skill when combo is completed int id = SkillAPI.getComboManager().convertCombo(clicks, clickIndex); - if (clickIndex == clicks.length || skills.containsKey(id)) - { + if (clickIndex == clicks.length || skills.containsKey(id)) { PlayerComboFinishEvent event = new PlayerComboFinishEvent(player, id, skills.get(id)); Bukkit.getPluginManager().callEvent(event); - if (skills.containsKey(id) && !event.isCancelled()) - { + if (skills.containsKey(id) && !event.isCancelled()) { player.cast(skills.get(id)); } } @@ -154,12 +143,10 @@ public void applyClick(Click click) /** * Checks for when the combo times out */ - private void checkExpired() - { + private void checkExpired() { // Reset combo if too much time passed if (clickIndex == clicks.length - || System.currentTimeMillis() - clickTime > SkillAPI.getSettings().getClickTime()) - { + || System.currentTimeMillis() - clickTime > SkillAPI.getSettings().getClickTime()) { clearCombo(); } } @@ -169,24 +156,18 @@ private void checkExpired() * * @return current combo string */ - public String getCurrentComboString() - { - if (clickIndex == 0) return ""; - else if (clickIndex == clicks.length) - { - int id = SkillAPI.getComboManager().convertCombo(clicks); - if (skills.containsKey(id)) - { - return skills.get(id); - } - else return ""; + public String getCurrentComboString() { + if (clickIndex == 0) { return ""; } else if (clickIndex == clicks.length) { + final int id = SkillAPI.getComboManager().convertCombo(clicks); + if (skills.containsKey(id)) { + return SkillAPI.getSkill(skills.get(id)).getName(); + } else { return ""; } } checkExpired(); - ArrayList active = new ArrayList(clickIndex); - for (int i = 0; i < clickIndex; i++) - { + ArrayList active = new ArrayList<>(clickIndex); + for (int i = 0; i < clickIndex; i++) { active.add(clicks[i]); } return SkillAPI.getComboManager().getComboString(active); @@ -200,8 +181,7 @@ else if (clickIndex == clicks.length) * * @return true if conflict, false otherwise */ - public boolean hasConflict(int id) - { + public boolean hasConflict(int id) { return getConflicts(id).size() > 0; } @@ -213,14 +193,11 @@ public boolean hasConflict(int id) * * @return ID of conflict or -1 if no conflict */ - public List getConflicts(int id) - { + public List getConflicts(int id) { ComboManager cm = SkillAPI.getComboManager(); - List conflicts = new ArrayList(); - for (int taken : skills.keySet()) - { - if (cm.conflicts(id, taken)) - conflicts.add(taken); + List conflicts = new ArrayList<>(); + for (int taken : skills.keySet()) { + if (cm.conflicts(id, taken)) { conflicts.add(taken); } } return conflicts; } @@ -231,31 +208,28 @@ public List getConflicts(int id) * * @param skill skill to add */ - public void addSkill(Skill skill) - { - if (skill == null || !skill.canCast()) return; + public void addSkill(Skill skill) { + if (skill == null || !skill.canCast() || !SkillAPI.getSettings().isCombosEnabled()) { return; } // Can't already be added - if (skill.hasCombo()) - { + if (skill.hasCombo()) { setSkill(skill, skill.getCombo()); return; + } else if (!SkillAPI.getSettings().shouldAutoAssignCombos()) { + return; } // Get next available combo ComboManager cm = SkillAPI.getComboManager(); int combo = 1 << (Click.BITS * (cm.getComboSize() - 1)); int max = (1 << (Click.BITS * cm.getComboSize())) - 1; - while (combo <= max && (!cm.isValidDefaultCombo(combo) || hasConflict(combo))) - combo++; + while (combo <= max && (!cm.isValidDefaultCombo(combo) || hasConflict(combo))) { combo++; } // Add it if valid - if (combo <= max) - { + if (combo <= max) { skills.put(combo, skill.getName().toLowerCase()); reverse.put(skill.getName(), combo); - } - else Logger.invalid("Failed to assign combo for " + skill.getName() + " - no remaining combos"); + } else { Logger.invalid("Failed to assign combo for " + skill.getName() + " - no remaining combos"); } } /** @@ -263,9 +237,8 @@ public void addSkill(Skill skill) * * @param skill skill to remove */ - public void removeSkill(Skill skill) - { - if (skill == null || !reverse.containsKey(skill.getName())) return; + public void removeSkill(Skill skill) { + if (skill == null || !reverse.containsKey(skill.getName())) { return; } skills.remove(reverse.remove(skill.getName())); } @@ -277,8 +250,7 @@ public void removeSkill(Skill skill) * * @return true if active, false otherwise */ - public boolean isComboUsed(int id) - { + public boolean isComboUsed(int id) { return skills.containsKey(id); } @@ -289,8 +261,7 @@ public boolean isComboUsed(int id) * * @return true if valid, false otherwise */ - public boolean isValidCombo(int id) - { + public boolean isValidCombo(int id) { return SkillAPI.getComboManager().isValidCombo(id); } @@ -301,8 +272,7 @@ public boolean isValidCombo(int id) * * @return true if has a combo, false otherwise */ - public boolean hasCombo(Skill skill) - { + public boolean hasCombo(Skill skill) { return reverse.containsKey(skill.getName()); } @@ -317,18 +287,14 @@ public boolean hasCombo(Skill skill) * * @return true if set successfully, false otherwise */ - public boolean setSkill(Skill skill, int id) - { - if (skill == null || !skill.canCast() || !isValidCombo(id)) return false; + public boolean setSkill(Skill skill, int id) { + if (skill == null || !skill.canCast() || !isValidCombo(id)) { return false; } removeSkill(skill); List conflicts = getConflicts(id); - if (conflicts.size() > 0) - { - for (int conflict : conflicts) - { - if (conflict == id) - { + if (conflicts.size() > 0) { + for (int conflict : conflicts) { + if (conflict == id) { Skill old = SkillAPI.getSkill(skills.get(conflict)); old.clearCombo(); addSkill(old); @@ -336,19 +302,15 @@ public boolean setSkill(Skill skill, int id) } skills.put(id, skill.getName().toLowerCase()); reverse.put(skill.getName(), id); - for (int conflict : conflicts) - { - if (conflict != id) - { + for (int conflict : conflicts) { + if (conflict != id) { Skill old = SkillAPI.getSkill(skills.get(conflict)); old.clearCombo(); addSkill(old); reverse.remove(skills.remove(conflict)); } } - } - else - { + } else { skills.put(id, skill.getName().toLowerCase()); reverse.put(skill.getName(), id); } @@ -363,8 +325,7 @@ public boolean setSkill(Skill skill, int id) * * @return combo string */ - public String getComboString(Skill skill) - { + public String getComboString(Skill skill) { int combo = reverse.get(skill.getName()); return SkillAPI.getComboManager().getComboString(combo); } diff --git a/src/com/sucy/skill/api/player/PlayerData.java b/src/com/sucy/skill/api/player/PlayerData.java index 523e9fe7..687f6b54 100644 --- a/src/com/sucy/skill/api/player/PlayerData.java +++ b/src/com/sucy/skill/api/player/PlayerData.java @@ -28,9 +28,10 @@ import com.rit.sucy.config.Filter; import com.rit.sucy.config.FilterType; -import com.rit.sucy.items.InventoryManager; +import com.rit.sucy.config.parse.DataSection; import com.rit.sucy.player.TargetHelper; import com.rit.sucy.version.VersionManager; +import com.rit.sucy.version.VersionPlayer; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.classes.RPGClass; import com.sucy.skill.api.enums.*; @@ -39,50 +40,56 @@ import com.sucy.skill.api.skills.Skill; import com.sucy.skill.api.skills.SkillShot; import com.sucy.skill.api.skills.TargetSkill; +import com.sucy.skill.cast.PlayerCastBars; import com.sucy.skill.data.GroupSettings; -import com.sucy.skill.data.Permissions; +import com.sucy.skill.data.PlayerEquips; import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.gui.handlers.AttributeHandler; +import com.sucy.skill.gui.handlers.DetailsHandler; +import com.sucy.skill.gui.handlers.ProfessHandler; +import com.sucy.skill.gui.handlers.SkillHandler; +import com.sucy.skill.gui.tool.GUITool; import com.sucy.skill.language.ErrorNodes; import com.sucy.skill.language.GUINodes; import com.sucy.skill.language.RPGFilter; import com.sucy.skill.listener.AttributeListener; -import com.sucy.skill.listener.TreeListener; import com.sucy.skill.log.LogType; import com.sucy.skill.log.Logger; import com.sucy.skill.manager.AttributeManager; -import com.sucy.skill.task.InventoryTask; import com.sucy.skill.task.ScoreboardTask; -import com.sucy.skill.tree.basic.InventoryTree; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.OfflinePlayer; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; +import java.util.*; + +import static com.sucy.skill.api.event.PlayerSkillCastFailedEvent.Cause.*; /** * Represents one account for a player which can contain one class from each group * and the skills in each of those classes. You should not instantiate this class * yourself and instead get it from the SkillAPI static methods. + * + * In order to get a player's data, use "SkillAPI.getPlayerData(...)". Do NOT + * try to instantaite your own PlayerData object. */ -public final class PlayerData -{ - private final HashMap classes = new HashMap(); - private final HashMap skills = new HashMap(); - private final HashMap binds = new HashMap(); - private final HashMap attributes = new HashMap(); - private final HashMap bonusAttrib = new HashMap(); - +public class PlayerData { + private final HashMap classes = new HashMap<>(); + private final HashMap skills = new HashMap<>(); + private final HashMap binds = new HashMap<>(); + private final HashMap attributes = new HashMap<>(); + private final HashMap bonusAttrib = new HashMap<>(); + + private DataSection extraData = new DataSection(); private OfflinePlayer player; private PlayerSkillBar skillBar; + private PlayerCastBars castBars; private PlayerCombos combos; + private PlayerEquips equips; private String scheme; private String menuClass; private double mana; @@ -90,29 +97,31 @@ public final class PlayerData private double bonusHealth; private double bonusMana; private double lastHealth; + private double hunger; private boolean init; private boolean passive; private int attribPoints; + private long skillTimer; /** * Initializes a new account data representation for a player. * * @param player player to store the data for */ - public PlayerData(OfflinePlayer player, boolean init) - { + PlayerData(OfflinePlayer player, boolean init) { this.player = player; this.skillBar = new PlayerSkillBar(this); + this.castBars = new PlayerCastBars(this); this.combos = new PlayerCombos(this); + this.equips = new PlayerEquips(this); this.init = SkillAPI.isLoaded() && init; this.scheme = "default"; - for (String group : SkillAPI.getGroups()) - { + this.hunger = 1; + for (String group : SkillAPI.getGroups()) { GroupSettings settings = SkillAPI.getSettings().getGroupSettings(group); RPGClass rpgClass = settings.getDefault(); - if (rpgClass != null && settings.getPermission() == null) - { + if (rpgClass != null && settings.getPermission() == null) { setClass(rpgClass); } } @@ -123,9 +132,8 @@ public PlayerData(OfflinePlayer player, boolean init) * * @return Bukkit player object of the owner or null if offline */ - public Player getPlayer() - { - return player.getPlayer(); + public Player getPlayer() { + return new VersionPlayer(player).getPlayer(); } /** @@ -133,36 +141,57 @@ public Player getPlayer() * * @return name of the owner */ - public String getPlayerName() - { + public String getPlayerName() { return player.getName(); } + public UUID getUUID() { + return player.getUniqueId(); + } + /** * Retrieves the skill bar data for the owner * * @return skill bar data of the owner */ - public PlayerSkillBar getSkillBar() - { + public PlayerSkillBar getSkillBar() { return skillBar; } + /** + * @return cast bars data for the player + */ + public PlayerCastBars getCastBars() { + return castBars; + } + /** * Returns the data for the player's combos * * @return combo data for the player */ - public PlayerCombos getComboData() - { + public PlayerCombos getComboData() { return combos; } + /** + * @return extra data attached to the player's account + */ + public DataSection getExtraData() { + return extraData; + } + + /** + * @return equipped item data + */ + public PlayerEquips getEquips() { + return equips; + } + /** * @return health during last logout */ - public double getLastHealth() - { + public double getLastHealth() { return lastHealth; } @@ -171,18 +200,43 @@ public double getLastHealth() * * @param health health logged off with */ - public void setLastHealth(double health) - { + public void setLastHealth(double health) { lastHealth = health; } + /** + * The hunger value here is not representative of the player's total hunger, + * rather the amount left of the next hunger point. This is manipulated by + * attributes were if an attribute says a player has twice as much "hunger" + * as normal, this will go down by decimals to slow the decay of hunger. + * + * @return amount of the next hunger point the player has + */ + public double getHungerValue() { + return hunger; + } + + /** + * @param hungerValue new hunger value + * @see PlayerData#getHungerValue + */ + public void setHungerValue(final double hungerValue) { + this.hunger = hungerValue; + } + + public int subtractHungerValue(final double amount) { + final double scaled = amount / scaleStat(AttributeManager.HUNGER, amount); + final int lost = scaled >= hunger ? (int) (scaled - hunger) + 1 : 0; + this.hunger += lost - amount; + return lost; + } + /** * Ends the initialization flag for the data. Used by the * API to avoid async issues. Do not use this in other * plugins. */ - public void endInit() - { + public void endInit() { init = false; } @@ -191,8 +245,7 @@ public void endInit() * * @return map menu scheme name */ - public String getScheme() - { + public String getScheme() { return scheme; } @@ -201,8 +254,7 @@ public String getScheme() * * @param name name of the scheme */ - public void setScheme(String name) - { + public void setScheme(String name) { scheme = name; } @@ -218,11 +270,9 @@ public void setScheme(String name) * * @return attribute totals */ - public HashMap getAttributes() - { - HashMap map = new HashMap(); - for (String key : SkillAPI.getAttributeManager().getKeys()) - map.put(key, getAttribute(key)); + public HashMap getAttributes() { + HashMap map = new HashMap<>(); + for (String key : SkillAPI.getAttributeManager().getKeys()) { map.put(key, getAttribute(key)); } return map; } @@ -234,9 +284,8 @@ public HashMap getAttributes() * * @return attribute totals */ - public HashMap getInvestedAttributes() - { - return new HashMap(attributes); + public HashMap getInvestedAttributes() { + return new HashMap<>(attributes); } /** @@ -247,17 +296,15 @@ public HashMap getInvestedAttributes() * * @return number of total points */ - public int getAttribute(String key) - { + public int getAttribute(String key) { key = key.toLowerCase(); int total = 0; - if (attributes.containsKey(key)) - total += attributes.get(key); - if (bonusAttrib.containsKey(key)) - total += bonusAttrib.get(key); - for (PlayerClass playerClass : classes.values()) + if (attributes.containsKey(key)) { total += attributes.get(key); } + if (bonusAttrib.containsKey(key)) { total += bonusAttrib.get(key); } + for (PlayerClass playerClass : classes.values()) { total += playerClass.getData().getAttribute(key, playerClass.getLevel()); - return total; + } + return Math.max(0, total); } /** @@ -268,14 +315,8 @@ public int getAttribute(String key) * * @return number of invested points */ - public int getInvestedAttribute(String key) - { - key = key.toLowerCase(); - if (!attributes.containsKey(key)) - { - return 0; - } - return attributes.get(key); + public int getInvestedAttribute(String key) { + return attributes.getOrDefault(key.toLowerCase(), 0); } /** @@ -286,8 +327,7 @@ public int getInvestedAttribute(String key) * * @return true if any points are invested, false otherwise */ - public boolean hasAttribute(String key) - { + public boolean hasAttribute(String key) { return getAttribute(key) > 0; } @@ -297,25 +337,25 @@ public boolean hasAttribute(String key) * has no remaining points, this will do nothing. * * @param key attribute key + * + * @return whether or not it was successfully upgraded */ - public void upAttribute(String key) - { + public boolean upAttribute(String key) { key = key.toLowerCase(); int current = getInvestedAttribute(key); int max = SkillAPI.getAttributeManager().getAttribute(key).getMax(); - if (attribPoints > 0 && current < max) - { + if (attribPoints > 0 && current < max) { attributes.put(key, current + 1); attribPoints--; PlayerUpAttributeEvent event = new PlayerUpAttributeEvent(this, key); Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) - { + if (event.isCancelled()) { attributes.put(key, current); attribPoints++; - } + } else { return true; } } + return false; } /** @@ -325,14 +365,12 @@ public void upAttribute(String key) * @param key attribute to give points for * @param amount amount to give */ - public void giveAttribute(String key, int amount) - { + public void giveAttribute(String key, int amount) { key = key.toLowerCase(); int current = getInvestedAttribute(key); int max = SkillAPI.getAttributeManager().getAttribute(key).getMax(); amount = Math.min(amount + current, max); - if (amount > current) - { + if (amount > current) { attributes.put(key, amount); AttributeListener.updatePlayer(this); } @@ -345,12 +383,10 @@ public void giveAttribute(String key, int amount) * @param key attribute key * @param amount amount to add */ - public void addBonusAttributes(String key, int amount) - { - key = key.toLowerCase(); - if (bonusAttrib.containsKey(key)) - amount += bonusAttrib.get(key); - bonusAttrib.put(key, Math.max(0, amount)); + public void addBonusAttributes(String key, int amount) { + key = SkillAPI.getAttributeManager().normalize(key); + amount += bonusAttrib.getOrDefault(key, 0); + bonusAttrib.put(key, amount); AttributeListener.updatePlayer(this); } @@ -361,28 +397,28 @@ public void addBonusAttributes(String key, int amount) * * @param key attribute key */ - public void refundAttribute(String key) - { + public boolean refundAttribute(String key) { key = key.toLowerCase(); int current = getInvestedAttribute(key); - if (current > 0) - { + if (current > 0) { PlayerRefundAttributeEvent event = new PlayerRefundAttributeEvent(this, key); Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) return; + if (event.isCancelled()) { return false; } attribPoints += 1; attributes.put(key, current - 1); - if (current - 1 <= 0) attributes.remove(key); + if (current - 1 <= 0) { attributes.remove(key); } AttributeListener.updatePlayer(this); + + return true; } + return false; } /** * Refunds all spent attribute points for a specific attribute */ - public void refundAttributes(String key) - { + public void refundAttributes(String key) { key = key.toLowerCase(); attribPoints += getInvestedAttribute(key); attributes.remove(key); @@ -392,11 +428,9 @@ public void refundAttributes(String key) /** * Refunds all spent attribute points */ - public void refundAttributes() - { - ArrayList keys = new ArrayList(attributes.keySet()); - for (String key : keys) - { + public void refundAttributes() { + ArrayList keys = new ArrayList<>(attributes.keySet()); + for (String key : keys) { refundAttributes(key); } } @@ -406,8 +440,7 @@ public void refundAttributes() * * @return attribute point total */ - public int getAttributePoints() - { + public int getAttributePoints() { return attribPoints; } @@ -416,8 +449,7 @@ public int getAttributePoints() * * @param amount amount of attribute points */ - public void giveAttribPoints(int amount) - { + public void giveAttribPoints(int amount) { attribPoints += amount; } @@ -426,8 +458,7 @@ public void giveAttribPoints(int amount) * * @param amount amount of points to have */ - public void setAttribPoints(int amount) - { + public void setAttribPoints(int amount) { attribPoints = amount; } @@ -439,19 +470,21 @@ public void setAttribPoints(int amount) * * @return modified value */ - public double scaleStat(String stat, double value) - { - AttributeManager manager = SkillAPI.getAttributeManager(); - if (manager == null) return value; - for (String key : manager.getKeys()) - { - int amount = getAttribute(key); - if (amount > 0) - { - value = manager.getAttribute(key).modifyStat(stat, value, amount); + public double scaleStat(final String stat, final double value) { + final AttributeManager manager = SkillAPI.getAttributeManager(); + if (manager == null) { return value; } + + final List matches = manager.forStat(stat); + if (matches == null) { return value; } + + double modified = value; + for (final AttributeManager.Attribute attribute : matches) { + int amount = getAttribute(attribute.getKey()); + if (amount > 0) { + modified = attribute.modifyStat(stat, modified, amount); } } - return value; + return modified; } /** @@ -460,67 +493,48 @@ public double scaleStat(String stat, double value) * @param component component holding the value * @param key key of the value * @param value unmodified value - * @param self whether or not the player is the target * * @return the modified value */ - public double scaleDynamic(EffectComponent component, String key, double value, boolean self) - { - AttributeManager manager = SkillAPI.getAttributeManager(); - if (manager == null) return value; - for (String attr : manager.getKeys()) - { - int amount = getAttribute(attr); - if (amount > 0) - { - value = manager.getAttribute(attr).modify(component, key, self, value, amount); + public double scaleDynamic(EffectComponent component, String key, double value) { + final AttributeManager manager = SkillAPI.getAttributeManager(); + if (manager == null) { return value; } + + final List matches = manager.forComponent(component, key); + if (matches == null) { return value; } + + for (final AttributeManager.Attribute attribute : matches) { + int amount = getAttribute(attribute.getKey()); + if (amount > 0) { + value = attribute.modify(component, key, value, amount); } } return value; } /** - * Opens the attribute menu + * Opens the attribute menu for the player + * + * @return true if successfully opened, false if conditions weren't met */ - public void openAttributeMenu() - { + public boolean openAttributeMenu() { Player player = getPlayer(); - if (SkillAPI.getSettings().isAttributesEnabled() && player != null) - { - AttributeManager manager = SkillAPI.getAttributeManager(); - Inventory inv = InventoryManager.createInventory( - AttributeListener.MENU_KEY, - (manager.getKeys().size() + 8) / 9, - SkillAPI.getLanguage().getMessage( - GUINodes.ATTRIB_TITLE, - true, - FilterType.COLOR, - RPGFilter.POINTS.setReplacement(attribPoints + ""), - Filter.PLAYER.setReplacement(player.getName()) - ).get(0) + if (SkillAPI.getSettings().isAttributesEnabled() && player != null) { + GUITool.getAttributesMenu().show( + new AttributeHandler(), + this, + SkillAPI.getLanguage().getMessage( + GUINodes.ATTRIB_TITLE, + true, + FilterType.COLOR, + RPGFilter.POINTS.setReplacement(attribPoints + ""), + Filter.PLAYER.setReplacement(player.getName()) + ).get(0), + SkillAPI.getAttributeManager().getAttributes() ); - int i = 0; - for (String key : manager.getKeys()) - { - ItemStack icon = manager.getAttribute(key).getIcon().clone(); - ItemMeta meta = icon.getItemMeta(); - meta.setDisplayName(filter(meta.getDisplayName(), key)); - List lore = meta.getLore(); - for (int j = 0; j < lore.size(); j++) - lore.set(j, filter(lore.get(j), key)); - - icon.setItemMeta(meta); - inv.setItem(i++, icon); - } - player.openInventory(inv); + return true; } - } - - private String filter(String text, String key) - { - return text - .replace("{amount}", "" + getInvestedAttribute(key)) - .replace("{total}", "" + getAttribute(key)); + return false; } /** @@ -530,8 +544,7 @@ private String filter(String text, String key) * * @return the player's attribute data */ - public HashMap getAttributeData() - { + public HashMap getAttributeData() { return attributes; } @@ -550,8 +563,7 @@ public HashMap getAttributeData() * * @return true if has the skill, false otherwise */ - public boolean hasSkill(String name) - { + public boolean hasSkill(String name) { return name != null && skills.containsKey(name.toLowerCase()); } @@ -562,13 +574,19 @@ public boolean hasSkill(String name) * * @return data for the skill or null if the player doesn't have the skill */ - public PlayerSkill getSkill(String name) - { - if (name == null) - return null; + public PlayerSkill getSkill(String name) { + if (name == null) { return null; } return skills.get(name.toLowerCase()); } + public int getInvestedSkillPoints() { + int total = 0; + for (PlayerSkill playerSkill : skills.values()) { + total += playerSkill.getInvestedCost(); + } + return total; + } + /** * Retrieves all of the skill data the player has. Modifying this * collection will not modify the player's owned skills but modifying @@ -576,8 +594,7 @@ public PlayerSkill getSkill(String name) * * @return collection of skill data for the owner */ - public Collection getSkills() - { + public Collection getSkills() { return skills.values(); } @@ -588,8 +605,7 @@ public Collection getSkills() * * @return level of the skill or 0 if not found */ - public int getSkillLevel(String name) - { + public int getSkillLevel(String name) { PlayerSkill skill = getSkill(name); return skill == null ? 0 : skill.getLevel(); } @@ -600,8 +616,7 @@ public int getSkillLevel(String name) * * @param skill skill to give the player */ - public void giveSkill(Skill skill) - { + public void giveSkill(Skill skill) { giveSkill(skill, null); } @@ -612,11 +627,9 @@ public void giveSkill(Skill skill) * @param skill skill to give the player * @param parent parent class data */ - public void giveSkill(Skill skill, PlayerClass parent) - { + public void giveSkill(Skill skill, PlayerClass parent) { String key = skill.getKey(); - if (!skills.containsKey(key)) - { + if (!skills.containsKey(key)) { PlayerSkill data = new PlayerSkill(this, skill, parent); combos.addSkill(skill); skills.put(key, data); @@ -627,27 +640,29 @@ public void giveSkill(Skill skill, PlayerClass parent) /** * Attempts to auto-level any skills that are able to do so */ - public void autoLevel() - { - if (init) return; + public void autoLevel() { + if (init) { return; } + + final Player player = getPlayer(); + if (player == null) { return; } - for (PlayerSkill skill : skills.values()) - { - autoLevel(skill.getData()); + for (PlayerSkill skill : skills.values()) { + if (skill.getData().isAllowed(player)) { + autoLevel(skill.getData()); + } } } - private void autoLevel(Skill skill) - { + private void autoLevel(Skill skill) { PlayerSkill data = skills.get(skill.getKey()); - if (data == null) return; + if (data == null || getPlayer() == null || !skill.isAllowed(getPlayer())) { return; } int lastLevel = data.getLevel(); - while (data.getData().canAutoLevel() && !data.isMaxed() && data.getLevelReq() <= data.getPlayerClass().getLevel()) - { + while (data.getData().canAutoLevel(lastLevel) + && !data.isMaxed() + && data.getLevelReq() <= data.getPlayerClass().getLevel()) { upgradeSkill(skill); - if (lastLevel == data.getLevel()) - { + if (lastLevel == data.getLevel()) { break; } lastLevel++; @@ -664,77 +679,69 @@ private void autoLevel(Skill skill) * * @return true if successfully was upgraded, false otherwise */ - public boolean upgradeSkill(Skill skill) - { + public boolean upgradeSkill(Skill skill) { // Cannot be null - if (skill == null) - { + if (skill == null) { return false; } // Must be a valid available skill PlayerSkill data = skills.get(skill.getName().toLowerCase()); - if (data == null) - { + if (data == null) { return false; } // Must meet any skill requirements - if (skill.getSkillReq() != null) - { - PlayerSkill req = skills.get(skill.getSkillReq().toLowerCase()); - if (req != null && req.getLevel() < skill.getSkillReqLevel()) - { - return false; - } + if (!skill.isCompatible(this) || !skill.hasInvestedEnough(this) || !skill.hasDependency(this)) { + return false; } int level = data.getPlayerClass().getLevel(); int points = data.getPlayerClass().getPoints(); int cost = data.getCost(); - if (!data.isMaxed() && level >= data.getLevelReq() && points >= cost) - { + if (!data.isMaxed() && level >= data.getLevelReq() && points >= cost) { // Upgrade event PlayerSkillUpgradeEvent event = new PlayerSkillUpgradeEvent(this, data, cost); Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) - { + if (event.isCancelled()) { return false; } // Apply upgrade data.getPlayerClass().usePoints(cost); - data.addLevels(1); - - // Passive calls - if (passive) - { - Player player = getPlayer(); - if (player != null && skill instanceof PassiveSkill) - { - if (data.getLevel() == 1) - { - ((PassiveSkill) skill).initialize(player, data.getLevel()); - } - else - { - ((PassiveSkill) skill).update(player, data.getLevel() - 1, data.getLevel()); - } - } + forceUpSkill(data); + + return true; + } else { + return false; + } + } - // Unlock event - if (data.getLevel() == 1) - { - Bukkit.getPluginManager().callEvent(new PlayerSkillUnlockEvent(this, data)); - this.autoLevel(); + /** + * Forcefully upgrades a skill, not letting other plugins + * cancel it and ignoring any requirements to do so + * + * @param skill skill to forcefully upgrade + */ + public void forceUpSkill(PlayerSkill skill) { + skill.addLevels(1); + + // Passive calls + if (passive) { + Player player = getPlayer(); + if (player != null && skill.getData() instanceof PassiveSkill) { + if (skill.getLevel() == 1) { + ((PassiveSkill) skill.getData()).initialize(player, skill.getLevel()); + } else { + ((PassiveSkill) skill.getData()).update(player, skill.getLevel() - 1, skill.getLevel()); } } - return true; - } - else - { - return false; + // Unlock event + if (skill.getLevel() == 1) { + Bukkit.getPluginManager().callEvent(new PlayerSkillUnlockEvent(this, skill)); + this.autoLevel(); + } } } @@ -747,91 +754,171 @@ public boolean upgradeSkill(Skill skill) * * @return true if successfully downgraded, false otherwise */ - public boolean downgradeSkill(Skill skill) - { + public boolean downgradeSkill(Skill skill) { // Cannot be null - if (skill == null) - { + if (skill == null) { return false; } // Must be a valid available skill PlayerSkill data = skills.get(skill.getName().toLowerCase()); - if (data == null) - { + if (data == null) { return false; } // Must not be a free skill - if (data.getCost() == 0) - { + if (data.getCost() == 0) { return false; } // Must not be required by another skill - for (PlayerSkill s : skills.values()) - { + for (PlayerSkill s : skills.values()) { if (s.getData().getSkillReq() != null - && s.getData().getSkillReq().equalsIgnoreCase(skill.getName()) - && data.getLevel() <= s.getData().getSkillReqLevel() - && s.getLevel() > 0) - { + && s.getData().getSkillReq().equalsIgnoreCase(skill.getName()) + && data.getLevel() <= s.getData().getSkillReqLevel() + && s.getLevel() > 0) { return false; } } int cost = skill.getCost(data.getLevel() - 1); - if (data.getLevel() > 0) - { + if (data.getLevel() > 0) { // Upgrade event PlayerSkillDowngradeEvent event = new PlayerSkillDowngradeEvent(this, data, cost); Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) - { + if (event.isCancelled()) { return false; } // Apply upgrade data.getPlayerClass().givePoints(cost, PointSource.REFUND); - data.addLevels(-1); + forceDownSkill(data); - // Passive calls - Player player = getPlayer(); - if (player != null && skill instanceof PassiveSkill) - { - if (data.getLevel() == 0) - { - ((PassiveSkill) skill).stopEffects(player, 1); - } - else - { - ((PassiveSkill) skill).update(player, data.getLevel() + 1, data.getLevel()); - } - } + return true; + } else { + return false; + } + } + + /** + * Forcefully downgrades a skill, not letting other plugins + * stop it and ignoring any skill requirements to do so. + * + * @param skill skill to forcefully downgrade + */ + public void forceDownSkill(PlayerSkill skill) { + skill.addLevels(-1); - // Clear bindings - if (data.getLevel() == 0) - { - clearBinds(skill); + // Passive calls + Player player = getPlayer(); + if (player != null && skill.getData() instanceof PassiveSkill) { + if (skill.getLevel() == 0) { + ((PassiveSkill) skill.getData()).stopEffects(player, 1); + } else { + ((PassiveSkill) skill.getData()).update(player, skill.getLevel() + 1, skill.getLevel()); } + } - return true; + // Clear bindings + if (skill.getLevel() == 0) { + clearBinds(skill.getData()); } - else - { - return false; + } + + /** + * Refunds a skill for the player, resetting it down + * to level 0 and giving back any invested skill points. + * + * @param skill skill to refund + */ + public void refundSkill(PlayerSkill skill) { + Player player = getPlayer(); + + if (skill.getCost() == 0 || skill.getLevel() == 0) { return; } + + skill.getPlayerClass().givePoints(skill.getInvestedCost(), PointSource.REFUND); + skill.setLevel(0); + + if (player != null && (skill.getData() instanceof PassiveSkill)) { + ((PassiveSkill) skill.getData()).stopEffects(player, 1); } } + /** + * Refunds all skills for the player + */ + public void refundSkills() { + for (PlayerSkill skill : skills.values()) { refundSkill(skill); } + + clearAllBinds(); + } + /** * Shows the skill tree for the player. If the player has multiple trees, * this will show the list of skill trees they can view. */ - public void showSkills() - { + public void showSkills() { showSkills(getPlayer()); } + /** + * Shows the class details for the player + * + * @param player player to show to + * + * @return true if shown, false if nothing to show + */ + public boolean showDetails(Player player) { + if (classes.size() > 0 && player != null) { + HashMap iconMap = new HashMap<>(); + for (Map.Entry entry : classes.entrySet()) { + iconMap.put(entry.getKey().toLowerCase(), entry.getValue().getData()); + } + + GUITool.getDetailsMenu().show( + new DetailsHandler(), + this, + SkillAPI.getLanguage().getMessage( + GUINodes.CLASS_LIST, + true, + FilterType.COLOR, + Filter.PLAYER.setReplacement(player.getName()) + ).get(0), + iconMap + ); + return true; + } else { return false; } + } + + /** + * Shows profession options of the first class group available + * + * @param player player to show profession options for + * + * @return true if shown profession options, false if none available + */ + public boolean showProfession(Player player) { + for (String group : SkillAPI.getGroups()) { + PlayerClass c = getClass(group); + if (c == null || (c.getLevel() == c.getData().getMaxLevel() && c.getData().getOptions().size() > 0)) { + GUITool.getProfessMenu(c == null ? null : c.getData()).show( + new ProfessHandler(), + this, + SkillAPI.getLanguage().getMessage( + GUINodes.PROFESS_TITLE, + true, + FilterType.COLOR, + Filter.PLAYER.setReplacement(player.getName()), + RPGFilter.GROUP.setReplacement(group) + ).get(0), + SkillAPI.getClasses() + ); + return true; + } + } + return false; + } + /** * Shows the skill tree for the player. If the player has multiple trees, * this will show the list of skill trees they can view. @@ -840,37 +927,17 @@ public void showSkills() * * @return true if able to show the player, false otherwise */ - public boolean showSkills(Player player) - { + public boolean showSkills(Player player) { // Cannot show an invalid player, and cannot show no skills - if (player == null || classes.size() == 0 || skills.size() == 0) - { + if (player == null || classes.size() == 0 || skills.size() == 0) { return false; } // Show list of classes that have skill trees - if (classes.size() > 1) - { - Inventory inv = InventoryManager.createInventory( - TreeListener.CLASS_LIST_KEY, - (classes.size() + 8) / 9, - SkillAPI.getLanguage().getMessage( - GUINodes.CLASS_LIST, - true, - FilterType.COLOR, - Filter.PLAYER.setReplacement(player.getName()) - ).get(0) - ); - for (PlayerClass c : classes.values()) - { - inv.addItem(c.getData().getIcon()); - } - player.openInventory(inv); - return true; - } + if (classes.size() > 1) { return showDetails(player); } // Show only class's skill tree otherwise - else return showSkills(player, classes.get(classes.keySet().toArray(new String[1])[0])); + else { return showSkills(player, classes.values().iterator().next()); } } /** @@ -881,17 +948,28 @@ public boolean showSkills(Player player) * * @return true if succeeded, false otherwise */ - public boolean showSkills(Player player, PlayerClass playerClass) - { + public boolean showSkills(Player player, PlayerClass playerClass) { // Cannot show an invalid player, and cannot show no skills - if (player == null || playerClass.getData().getSkills().size() == 0) - { + if (player == null || playerClass.getData().getSkills().size() == 0) { return false; } // Show skill tree of the class this.menuClass = playerClass.getData().getName(); - player.openInventory(((InventoryTree) playerClass.getData().getSkillTree()).getInventory(this)); + GUITool.getSkillTree(playerClass.getData()).show( + new SkillHandler(), + this, + SkillAPI.getLanguage().getMessage( + GUINodes.SKILL_TREE, + true, + FilterType.COLOR, + RPGFilter.POINTS.setReplacement(playerClass.getPoints() + ""), + RPGFilter.LEVEL.setReplacement(playerClass.getLevel() + ""), + RPGFilter.CLASS.setReplacement(playerClass.getData().getName()), + Filter.PLAYER.setReplacement(getPlayerName()) + ).get(0), + playerClass.getData().getSkillMap() + ); return true; } @@ -900,8 +978,7 @@ public boolean showSkills(Player player, PlayerClass playerClass) * * @return class name */ - public String getShownClassName() - { + public String getShownClassName() { return menuClass; } @@ -916,8 +993,7 @@ public String getShownClassName() * * @return true if professed, false otherwise */ - public boolean hasClass() - { + public boolean hasClass() { return classes.size() > 0; } @@ -928,8 +1004,7 @@ public boolean hasClass() * * @return true if has a class in the group, false otherwise */ - public boolean hasClass(String group) - { + public boolean hasClass(String group) { return classes.containsKey(group); } @@ -938,8 +1013,7 @@ public boolean hasClass(String group) * * @return collection of the data for professed classes */ - public Collection getClasses() - { + public Collection getClasses() { return classes.values(); } @@ -951,8 +1025,7 @@ public Collection getClasses() * * @return professed class data or null if not professed for the group */ - public PlayerClass getClass(String group) - { + public PlayerClass getClass(String group) { return classes.get(group); } @@ -962,19 +1035,13 @@ public PlayerClass getClass(String group) * * @return main professed class data or null if not professed for the main group */ - public PlayerClass getMainClass() - { + public PlayerClass getMainClass() { String main = SkillAPI.getSettings().getMainGroup(); - if (classes.containsKey(main)) - { + if (classes.containsKey(main)) { return classes.get(main); - } - else if (classes.size() > 0) - { + } else if (classes.size() > 0) { return classes.values().toArray(new PlayerClass[classes.size()])[0]; - } - else - { + } else { return null; } } @@ -988,25 +1055,20 @@ else if (classes.size() > 0) * * @return the player-specific data for the new class */ - public PlayerClass setClass(RPGClass rpgClass) - { + public PlayerClass setClass(RPGClass rpgClass) { PlayerClass c = classes.remove(rpgClass.getGroup()); - if (c != null) - { - for (Skill skill : c.getData().getSkills()) - { + if (c != null) { + for (Skill skill : c.getData().getSkills()) { skills.remove(skill.getName().toLowerCase()); combos.removeSkill(skill); } - } - else attribPoints += rpgClass.getGroupSettings().getStartingAttribs(); + } else { attribPoints += rpgClass.getGroupSettings().getStartingAttribs(); } PlayerClass classData = new PlayerClass(this, rpgClass); classes.put(rpgClass.getGroup(), classData); // Add in missing skills - for (Skill skill : rpgClass.getSkills()) - { + for (Skill skill : rpgClass.getSkills()) { giveSkill(skill, classData); } @@ -1023,9 +1085,8 @@ public PlayerClass setClass(RPGClass rpgClass) * * @return true if professed as the specific class, false otherwise */ - public boolean isExactClass(RPGClass rpgClass) - { - if (rpgClass == null) return false; + public boolean isExactClass(RPGClass rpgClass) { + if (rpgClass == null) { return false; } PlayerClass c = classes.get(rpgClass.getGroup()); return (c != null) && (c.getData() == rpgClass); } @@ -1038,21 +1099,17 @@ public boolean isExactClass(RPGClass rpgClass) * * @return true if professed as the class or one of its children, false otherwise */ - public boolean isClass(RPGClass rpgClass) - { - if (rpgClass == null) - { + public boolean isClass(RPGClass rpgClass) { + if (rpgClass == null) { return false; } PlayerClass pc = classes.get(rpgClass.getGroup()); - if (pc == null) return false; + if (pc == null) { return false; } RPGClass temp = pc.getData(); - while (temp != null) - { - if (temp == rpgClass) - { + while (temp != null) { + if (temp == rpgClass) { return true; } temp = temp.getParent(); @@ -1070,23 +1127,16 @@ public boolean isClass(RPGClass rpgClass) * * @return true if can profess, false otherwise */ - public boolean canProfess(RPGClass rpgClass) - { - if (rpgClass.isNeedsPermission()) - { - Player p = getPlayer(); - if (p == null || (!p.hasPermission(Permissions.CLASS) && !p.hasPermission(Permissions.CLASS + "." + rpgClass.getName().toLowerCase().replace(" ", "-")))) - { - return false; - } + public boolean canProfess(RPGClass rpgClass) { + Player p = getPlayer(); + if (p == null || !rpgClass.isAllowed(p)) { + return false; } - if (classes.containsKey(rpgClass.getGroup())) - { + + if (classes.containsKey(rpgClass.getGroup())) { PlayerClass current = classes.get(rpgClass.getGroup()); return rpgClass.getParent() == current.getData() && current.getData().getMaxLevel() <= current.getLevel(); - } - else - { + } else { return !rpgClass.hasParent(); } } @@ -1098,22 +1148,19 @@ public boolean canProfess(RPGClass rpgClass) * * @param group group to reset */ - public void reset(String group) - { + public void reset(String group) { GroupSettings settings = SkillAPI.getSettings().getGroupSettings(group); - if (!settings.canReset()) - return; + if (!settings.canReset()) { return; } PlayerClass playerClass = classes.remove(group); - if (playerClass != null) - { + if (playerClass != null) { // Remove skills RPGClass data = playerClass.getData(); - for (Skill skill : data.getSkills()) - { + for (Skill skill : data.getSkills()) { PlayerSkill ps = skills.remove(skill.getName().toLowerCase()); - if (ps != null && ps.isUnlocked() && ps.getData() instanceof PassiveSkill) + if (ps != null && ps.isUnlocked() && ps.getData() instanceof PassiveSkill) { ((PassiveSkill) ps.getData()).stopEffects(getPlayer(), ps.getLevel()); + } combos.removeSkill(skill); } @@ -1126,8 +1173,7 @@ public void reset(String group) // Restore default class if applicable RPGClass rpgClass = settings.getDefault(); - if (rpgClass != null && settings.getPermission() == null) - { + if (rpgClass != null && settings.getPermission() == null) { setClass(rpgClass); } binds.clear(); @@ -1138,27 +1184,23 @@ public void reset(String group) * Resets all profession data for the player. This clears all professions the player * has, leaving no remaining data until the player professes again to a starting class. */ - public void resetAll() - { - ArrayList keys = new ArrayList(classes.keySet()); - for (String key : keys) - reset(key); + public void resetAll() { + ArrayList keys = new ArrayList<>(classes.keySet()); + for (String key : keys) { reset(key); } } /** * Resets attributes for the player */ - public void resetAttribs() - { + public void resetAttribs() { attributes.clear(); attribPoints = 0; - for (PlayerClass c : classes.values()) - { + for (PlayerClass c : classes.values()) { GroupSettings s = c.getData().getGroupSettings(); attribPoints += s.getStartingAttribs() + s.getAttribsForLevels(c.getLevel(), 1); } AttributeListener.updatePlayer(this); - updateHealthAndMana(player.getPlayer()); + updateHealthAndMana(getPlayer()); } /** @@ -1172,37 +1214,42 @@ public void resetAttribs() * * @return true if successfully professed, false otherwise */ - public boolean profess(RPGClass rpgClass) - { - if (rpgClass != null && canProfess(rpgClass)) - { + public boolean profess(RPGClass rpgClass) { + if (rpgClass != null && canProfess(rpgClass)) { + final PlayerClass previousData = classes.get(rpgClass.getGroup()); + final RPGClass previous = previousData == null ? null : previousData.getData(); + + // Pre-class change event in case someone wants to stop it + final PlayerPreClassChangeEvent event = new PlayerPreClassChangeEvent( + this, + previousData, + previous, + rpgClass); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + // Reset data if applicable - if (SkillAPI.getSettings().getGroupSettings(rpgClass.getGroup()).isProfessReset()) - { + final boolean isResetting = SkillAPI.getSettings().getGroupSettings(rpgClass.getGroup()).isProfessReset(); + if (isResetting) { reset(rpgClass.getGroup()); } // Inherit previous class data if any - PlayerClass current = classes.get(rpgClass.getGroup()); - RPGClass previous; - if (current == null) - { - previous = null; + final PlayerClass current; + if (previousData == null || isResetting) { current = new PlayerClass(this, rpgClass); classes.put(rpgClass.getGroup(), current); attribPoints += rpgClass.getGroupSettings().getStartingAttribs(); - } - else - { - previous = current.getData(); - current.setClassData(rpgClass); + } else { + current = previousData; + previousData.setClassData(rpgClass); } // Add skills - for (Skill skill : rpgClass.getSkills()) - { - if (!skills.containsKey(skill.getKey())) - { + for (Skill skill : rpgClass.getSkills()) { + if (!skills.containsKey(skill.getKey())) { skills.put(skill.getKey(), new PlayerSkill(this, skill, current)); combos.addSkill(skill); } @@ -1212,9 +1259,7 @@ public boolean profess(RPGClass rpgClass) resetAttribs(); updateScoreboard(); return true; - } - else - { + } else { return false; } } @@ -1225,24 +1270,30 @@ public boolean profess(RPGClass rpgClass) * @param amount amount of experience to give * @param source source of the experience */ - public void giveExp(double amount, ExpSource source) - { - for (PlayerClass playerClass : classes.values()) - { - playerClass.giveExp(amount, source); + public void giveExp(double amount, ExpSource source) { + giveExp(amount, source, true); + } + + /** + * Gives experience to the player from the given source + * + * @param amount amount of experience to give + * @param source source of the experience + * @param message whether or not to show the configured message if enabled + */ + public void giveExp(double amount, ExpSource source, boolean message) { + for (PlayerClass playerClass : classes.values()) { + playerClass.giveExp(amount, source, message); } } /** * Causes the player to lose experience as a penalty (generally for dying) */ - public void loseExp() - { - for (PlayerClass playerClass : classes.values()) - { + public void loseExp() { + for (PlayerClass playerClass : classes.values()) { double penalty = playerClass.getData().getGroupSettings().getDeathPenalty(); - if (penalty > 0) - { + if (penalty > 0) { playerClass.loseExp(penalty); } } @@ -1254,25 +1305,17 @@ public void loseExp() * @param amount amount of levels to give * @param source source of the levels */ - public void giveLevels(int amount, ExpSource source) - { - for (PlayerClass playerClass : classes.values()) - { + public boolean giveLevels(int amount, ExpSource source) { + boolean success = false; + for (PlayerClass playerClass : classes.values()) { RPGClass data = playerClass.getData(); - if (data.receivesExp(source)) - { - int exp = 0; - int count = 0; - int temp = amount; - while (temp > 0) - { - temp--; - exp += data.getRequiredExp(playerClass.getLevel() + count++); - } - playerClass.giveExp(exp, source); + if (data.receivesExp(source)) { + success = true; + playerClass.giveLevels(amount); } } updateHealthAndMana(getPlayer()); + return success; } /** @@ -1281,12 +1324,9 @@ public void giveLevels(int amount, ExpSource source) * @param amount amount of levels to give * @param source source of the levels */ - public void givePoints(int amount, ExpSource source) - { - for (PlayerClass playerClass : classes.values()) - { - if (playerClass.getData().receivesExp(source)) - { + public void givePoints(int amount, ExpSource source) { + for (PlayerClass playerClass : classes.values()) { + if (playerClass.getData().receivesExp(source)) { playerClass.givePoints(amount); } } @@ -1303,41 +1343,32 @@ public void givePoints(int amount, ExpSource source) * * @param player player to update the health and mana for */ - public void updateHealthAndMana(Player player) - { - if (player == null) - { + public void updateHealthAndMana(Player player) { + if (player == null) { return; } // Update maxes double health = bonusHealth; maxMana = bonusMana; - for (PlayerClass c : classes.values()) - { + for (PlayerClass c : classes.values()) { health += c.getHealth(); maxMana += c.getMana(); } - if (health == bonusHealth) - { + if (health == bonusHealth) { health += SkillAPI.getSettings().getDefaultHealth(); } - if (health == 0) - { - health = 20; + if (health <= 0) { + health = SkillAPI.getSettings().getDefaultHealth(); } - if (SkillAPI.getSettings().isModifyHealth()) - VersionManager.setMaxHealth(player, health); + if (SkillAPI.getSettings().isModifyHealth()) { player.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(health); } mana = Math.min(mana, maxMana); // Health scaling is available starting with 1.6.2 - if (SkillAPI.getSettings().isOldHealth()) - { + if (SkillAPI.getSettings().isOldHealth()) { player.setHealthScaled(true); player.setHealthScale(20); - } - else - { + } else { player.setHealthScaled(false); } } @@ -1349,12 +1380,25 @@ public void updateHealthAndMana(Player player) * * @param amount amount of bonus health to give */ - public void addMaxHealth(double amount) - { + + public void addMaxHealth(double amount) { bonusHealth += amount; - Player player = getPlayer(); - if (player != null) - player.setMaxHealth(player.getMaxHealth() + amount); + final Player player = getPlayer(); + if (player != null) { + if (VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { + final AttributeInstance attribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH); + attribute.setBaseValue(attribute.getBaseValue() + amount); + } else { + final double newHealth = player.getMaxHealth() + amount; + player.setMaxHealth(newHealth); + if (player.getMaxHealth() > newHealth) { + player.setMaxHealth(newHealth * 2 - player.getMaxHealth()); + } + + } + + + } } /** @@ -1364,8 +1408,7 @@ public void addMaxHealth(double amount) * * @param amount amount of bonus mana to give */ - public void addMaxMana(double amount) - { + public void addMaxMana(double amount) { bonusMana += amount; maxMana += amount; mana += amount; @@ -1376,8 +1419,7 @@ public void addMaxMana(double amount) * * @return current player mana */ - public double getMana() - { + public double getMana() { return mana; } @@ -1388,8 +1430,7 @@ public double getMana() * * @return true if has the amount of mana, false otherwise */ - public boolean hasMana(double amount) - { + public boolean hasMana(double amount) { return mana >= amount; } @@ -1398,26 +1439,21 @@ public boolean hasMana(double amount) * * @return max amount of mana the player can have */ - public double getMaxMana() - { + public double getMaxMana() { return maxMana; } /** * Regenerates mana for the player based on the regen amounts of professed classes */ - public void regenMana() - { + public void regenMana() { double amount = 0; - for (PlayerClass c : classes.values()) - { - if (c.getData().hasManaRegen()) - { + for (PlayerClass c : classes.values()) { + if (c.getData().hasManaRegen()) { amount += c.getData().getManaRegen(); } } - if (amount > 0) - { + if (amount > 0) { giveMana(amount, ManaSource.REGEN); } } @@ -1427,8 +1463,7 @@ public void regenMana() * * @param amount current mana */ - public void setMana(double amount) - { + public void setMana(double amount) { this.mana = amount; } @@ -1438,8 +1473,7 @@ public void setMana(double amount) * * @param amount amount of mana to give */ - public void giveMana(double amount) - { + public void giveMana(double amount) { giveMana(amount, ManaSource.SPECIAL); } @@ -1450,26 +1484,24 @@ public void giveMana(double amount) * @param amount amount of mana to give * @param source source of the mana */ - public void giveMana(double amount, ManaSource source) - { + public void giveMana(double amount, ManaSource source) { PlayerManaGainEvent event = new PlayerManaGainEvent(this, amount, source); Bukkit.getPluginManager().callEvent(event); - if (!event.isCancelled()) - { - Logger.log(LogType.MANA, 2, getPlayerName() + " gained " + amount + " mana due to " + event.getSource().name()); + if (!event.isCancelled()) { + Logger.log( + LogType.MANA, + 2, + getPlayerName() + " gained " + amount + " mana due to " + event.getSource().name()); mana += event.getAmount(); - if (mana > maxMana) - { + if (mana > maxMana) { mana = maxMana; } - if (mana < 0) - { + if (mana < 0) { mana = 0; } - } - else Logger.log(LogType.MANA, 2, getPlayerName() + " had their mana gain cancelled"); + } else { Logger.log(LogType.MANA, 2, getPlayerName() + " had their mana gain cancelled"); } } /** @@ -1478,8 +1510,7 @@ public void giveMana(double amount, ManaSource source) * * @param amount amount of mana to take away */ - public void useMana(double amount) - { + public void useMana(double amount) { useMana(amount, ManaCost.SPECIAL); } @@ -1490,18 +1521,18 @@ public void useMana(double amount) * @param amount amount of mana to take away * @param cost source of the mana cost */ - public void useMana(double amount, ManaCost cost) - { + public void useMana(double amount, ManaCost cost) { PlayerManaLossEvent event = new PlayerManaLossEvent(this, amount, cost); Bukkit.getPluginManager().callEvent(event); - if (!event.isCancelled()) - { - Logger.log(LogType.MANA, 2, getPlayerName() + " used " + amount + " mana due to " + event.getSource().name()); + if (!event.isCancelled()) { + Logger.log( + LogType.MANA, + 2, + getPlayerName() + " used " + amount + " mana due to " + event.getSource().name()); mana -= event.getAmount(); - if (mana < 0) - { + if (mana < 0) { mana = 0; } } @@ -1510,10 +1541,11 @@ public void useMana(double amount, ManaCost cost) /** * Clears bonus health/mana */ - public void clearBonuses() - { + public void clearBonuses() { bonusMana = 0; bonusHealth = 0; + bonusAttrib.clear(); + equips = new PlayerEquips(this); } /////////////////////////////////////////////////////// @@ -1529,9 +1561,12 @@ public void clearBonuses() * * @return skill bound to the material or null if none are bound */ - public PlayerSkill getBoundSkill(Material mat) - { - return binds.get(mat); + public PlayerSkill getBoundSkill(Material mat) { + return binds.get(mat.toString()); + } + public PlayerSkill getBoundSkill(String dispname) { + dispname = dispname.replaceAll("§.", "").replace(" ","_").toUpperCase(); + return binds.get(dispname); } /** @@ -1540,8 +1575,7 @@ public PlayerSkill getBoundSkill(Material mat) * * @return the skill binds data for the player */ - public HashMap getBinds() - { + public HashMap getBinds() { return binds; } @@ -1552,11 +1586,13 @@ public HashMap getBinds() * * @return true if a skill is bound to it, false otherwise */ - public boolean isBound(Material mat) - { - return binds.containsKey(mat); + public boolean isBound(Material mat) { + return binds.containsKey(mat.toString()); + } + public boolean isBound(String dispname) { + dispname = dispname.replaceAll("§.", "").replace(" ","_").toUpperCase(); + return binds.containsKey(dispname); } - /** * Binds a skill to a material for the player. The bind will not work if the skill * was already bound to the material. @@ -1566,49 +1602,73 @@ public boolean isBound(Material mat) * * @return true if was able to bind the skill, false otherwise */ - public boolean bind(Material mat, PlayerSkill skill) - { + public boolean bind(Material mat, PlayerSkill skill) { // Special cases - if (mat == null || (skill != null && skill.getPlayerData() != this)) - { + if (mat == null || (skill != null && skill.getPlayerData() != this)) { return false; } PlayerSkill bound = getBoundSkill(mat); - if (bound != skill) - { + if (bound != skill) { // Apply the binding - if (skill == null) - { - binds.remove(mat); - } - else - { - binds.put(mat, skill); + if (skill == null) { + binds.remove(mat.toString()); + } else { + binds.put(mat.toString(), skill); } // Update the old skill's bind - if (bound != null) - { - bound.setBind(null); + if (bound != null) { + bound.setBindMat(null); } // Update the new skill's bind - if (skill != null) - { - skill.setBind(mat); + if (skill != null) { + skill.setBindMat(mat); } return true; } // The skill was already bound - else - { + else { return false; } } + // iomatix: bind by disp name + public boolean bind(String dispname, PlayerSkill skill) { + // Special cases + if (dispname == null || (skill != null && skill.getPlayerData() != this)) { + return false; + } + dispname = dispname.replaceAll("§.", "").replace(" ","_").toUpperCase(); + PlayerSkill bound = getBoundSkill(dispname); + if (bound != skill) { + // Apply the binding + if (skill == null) { + binds.remove(dispname); + } else { + binds.put(dispname, skill); + } + // Update the old skill's bind + if (bound != null) { + bound.setBindDispName(null); + } + + // Update the new skill's bind + if (skill != null) { + skill.setBindDispName(dispname); + } + + return true; + } + + // The skill was already bound + else { + return false; + } + } /** * Clears a skill binding on the material. If there is no binding on the * material, this will do nothing. @@ -1617,9 +1677,9 @@ public boolean bind(Material mat, PlayerSkill skill) * * @return true if a binding was cleared, false otherwise */ - public boolean clearBind(Material mat) - { - return binds.remove(mat) != null; + public boolean clearBind(String dispname) { + dispname = dispname.replaceAll("§.", "").replace(" ","_").toUpperCase(); + return binds.remove(dispname) != null; } /** @@ -1628,14 +1688,11 @@ public boolean clearBind(Material mat) * * @param skill skill to unbind */ - public void clearBinds(Skill skill) - { - ArrayList keys = new ArrayList(binds.keySet()); - for (Material key : keys) - { + public void clearBinds(Skill skill) { + ArrayList keys = new ArrayList<>(binds.keySet()); + for (String key : keys) { PlayerSkill bound = binds.get(key); - if (bound.getData() == skill) - { + if (bound.getData() == skill) { binds.remove(key); } } @@ -1644,8 +1701,7 @@ public void clearBinds(Skill skill) /** * Clears all binds the player currently has */ - public void clearAllBinds() - { + public void clearAllBinds() { binds.clear(); } @@ -1660,8 +1716,7 @@ public void clearAllBinds() * * @param player player to record for */ - public void record(Player player) - { + public void record(Player player) { this.lastHealth = player.getHealth(); } @@ -1670,10 +1725,8 @@ public void record(Player player) * This is already done by the API and doesn't need to be * done by other plugins. */ - public void updateScoreboard() - { - if (SkillAPI.getSettings().isShowScoreboard()) - SkillAPI.schedule(new ScoreboardTask(this), 2); + public void updateScoreboard() { + if (SkillAPI.getSettings().isShowScoreboard()) { SkillAPI.schedule(new ScoreboardTask(this), 2); } } /** @@ -1682,17 +1735,13 @@ public void updateScoreboard() * * @param player player to set the passive skills up for */ - public void startPassives(Player player) - { - if (player == null) - { + public void startPassives(Player player) { + if (player == null) { return; } passive = true; - for (PlayerSkill skill : skills.values()) - { - if (skill.isUnlocked() && (skill.getData() instanceof PassiveSkill)) - { + for (PlayerSkill skill : skills.values()) { + if (skill.isUnlocked() && (skill.getData() instanceof PassiveSkill)) { ((PassiveSkill) skill.getData()).initialize(player, skill.getLevel()); } } @@ -1704,18 +1753,19 @@ public void startPassives(Player player) * * @param player player to stop the passive skills for */ - public void stopPassives(Player player) - { - if (player == null) - { + public void stopPassives(Player player) { + if (player == null) { return; } passive = false; - for (PlayerSkill skill : skills.values()) - { - if (skill.isUnlocked() && (skill.getData() instanceof PassiveSkill)) - { - ((PassiveSkill) skill.getData()).stopEffects(player, skill.getLevel()); + for (PlayerSkill skill : skills.values()) { + if (skill.isUnlocked() && (skill.getData() instanceof PassiveSkill)) { + try { + ((PassiveSkill) skill.getData()).stopEffects(player, skill.getLevel()); + } catch (Exception ex) { + Logger.bug("Failed to stop passive skill " + skill.getData().getName()); + ex.printStackTrace(); + } } } } @@ -1729,8 +1779,7 @@ public void stopPassives(Player player) * * @return true if successfully cast the skill, false otherwise */ - public boolean cast(String skillName) - { + public boolean cast(String skillName) { return cast(skills.get(skillName.toLowerCase())); } @@ -1743,98 +1792,85 @@ public boolean cast(String skillName) * * @return true if successfully cast the skill, false otherwise */ - public boolean cast(PlayerSkill skill) - { + public boolean cast(PlayerSkill skill) { // Invalid skill - if (skill == null) - throw new IllegalArgumentException("Skill cannot be null"); + if (skill == null) { throw new IllegalArgumentException("Skill cannot be null"); } int level = skill.getLevel(); // Not unlocked or on cooldown - if (level <= 0 || !check(skill, true, true)) - return false; + if (!check(skill, true, true)) { return false; } // Dead players can't cast skills Player p = getPlayer(); - if (p.isDead()) - return false; + if (p.isDead()) { return PlayerSkillCastFailedEvent.invoke(skill, CASTER_DEAD); } + + // Disable casting in spectator mode + if (p.getGameMode().name().equals("SPECTATOR")) { return PlayerSkillCastFailedEvent.invoke(skill, SPECTATOR); } // Skill Shots - if (skill.getData() instanceof SkillShot) - { + if (skill.getData() instanceof SkillShot) { PlayerCastSkillEvent event = new PlayerCastSkillEvent(this, skill, p); Bukkit.getPluginManager().callEvent(event); // Make sure it isn't cancelled - if (!event.isCancelled()) - { - try - { - if (((SkillShot) skill.getData()).cast(p, level)) - { - skill.startCooldown(); - if (SkillAPI.getSettings().isShowSkillMessages()) - { - skill.getData().sendMessage(p, SkillAPI.getSettings().getMessageRadius()); - } - if (SkillAPI.getSettings().isManaEnabled()) - { - useMana(skill.getManaCost(), ManaCost.SKILL_CAST); - } - return true; + if (!event.isCancelled()) { + try { + if (((SkillShot) skill.getData()).cast(p, level)) { + return applyUse(p, skill, event.getManaCost()); + } else { + return PlayerSkillCastFailedEvent.invoke(skill, EFFECT_FAILED); } - } - catch (Exception ex) - { + } catch (Exception ex) { Logger.bug("Failed to cast skill - " + skill.getData().getName() + ": Internal skill error"); ex.printStackTrace(); + return PlayerSkillCastFailedEvent.invoke(skill, EFFECT_FAILED); } - } + } else { return PlayerSkillCastFailedEvent.invoke(skill, CANCELED); } } // Target Skills - else if (skill.getData() instanceof TargetSkill) - { + else if (skill.getData() instanceof TargetSkill) { LivingEntity target = TargetHelper.getLivingTarget(p, skill.getData().getRange(level)); // Must have a target - if (target == null) - return false; + if (target == null) { return PlayerSkillCastFailedEvent.invoke(skill, NO_TARGET); } PlayerCastSkillEvent event = new PlayerCastSkillEvent(this, skill, p); Bukkit.getPluginManager().callEvent(event); // Make sure it isn't cancelled - if (!event.isCancelled()) - { - try - { - if (((TargetSkill) skill.getData()).cast(p, target, level, !SkillAPI.getSettings().canAttack(p, target))) - { - skill.startCooldown(); - if (SkillAPI.getSettings().isShowSkillMessages()) - { - skill.getData().sendMessage(p, SkillAPI.getSettings().getMessageRadius()); - } - if (SkillAPI.getSettings().isManaEnabled()) - { - useMana(skill.getManaCost(), ManaCost.SKILL_CAST); - } - return true; + if (!event.isCancelled()) { + try { + final boolean canAttack = !SkillAPI.getSettings().canAttack(p, target); + if (((TargetSkill) skill.getData()).cast(p, target, level, canAttack)) { + return applyUse(p, skill, event.getManaCost()); + } else { + return PlayerSkillCastFailedEvent.invoke(skill, EFFECT_FAILED); } - } - catch (Exception ex) - { + } catch (Exception ex) { Logger.bug("Failed to cast skill - " + skill.getData().getName() + ": Internal skill error"); ex.printStackTrace(); + return PlayerSkillCastFailedEvent.invoke(skill, EFFECT_FAILED); } - } + } else { PlayerSkillCastFailedEvent.invoke(skill, CANCELED); } } return false; } + private boolean applyUse(final Player player, final PlayerSkill skill, final double manaCost) { + skill.startCooldown(); + if (SkillAPI.getSettings().isShowSkillMessages()) { + skill.getData().sendMessage(player, SkillAPI.getSettings().getMessageRadius()); + } + if (SkillAPI.getSettings().isManaEnabled()) { + useMana(manaCost, ManaCost.SKILL_CAST); + } + skillTimer = System.currentTimeMillis() + SkillAPI.getSettings().getCastCooldown(); + return true; + } + /** * Checks the cooldown and mana requirements for a skill * @@ -1844,50 +1880,43 @@ else if (skill.getData() instanceof TargetSkill) * * @return true if can use */ - public boolean check(PlayerSkill skill, boolean cooldown, boolean mana) - { - if (skill == null) - return false; + public boolean check(PlayerSkill skill, boolean cooldown, boolean mana) { + if (skill == null || System.currentTimeMillis() < skillTimer) { return false; } SkillStatus status = skill.getStatus(); int level = skill.getLevel(); double cost = skill.getData().getManaCost(level); // Not unlocked - if (level <= 0) - { - return false; + if (level <= 0) { + return PlayerSkillCastFailedEvent.invoke(skill, NOT_UNLOCKED); } // On Cooldown - if (status == SkillStatus.ON_COOLDOWN && cooldown) - { + if (status == SkillStatus.ON_COOLDOWN && cooldown) { SkillAPI.getLanguage().sendMessage( - ErrorNodes.COOLDOWN, - getPlayer(), - FilterType.COLOR, - RPGFilter.COOLDOWN.setReplacement(skill.getCooldown() + ""), - RPGFilter.SKILL.setReplacement(skill.getData().getName()) + ErrorNodes.COOLDOWN, + getPlayer(), + FilterType.COLOR, + RPGFilter.COOLDOWN.setReplacement(skill.getCooldown() + ""), + RPGFilter.SKILL.setReplacement(skill.getData().getName()) ); - return false; + return PlayerSkillCastFailedEvent.invoke(skill, ON_COOLDOWN); } // Not enough mana - else if (status == SkillStatus.MISSING_MANA && mana) - { + else if (status == SkillStatus.MISSING_MANA && mana) { SkillAPI.getLanguage().sendMessage( - ErrorNodes.MANA, - getPlayer(), - FilterType.COLOR, - RPGFilter.SKILL.setReplacement(skill.getData().getName()), - RPGFilter.MANA.setReplacement(getMana() + ""), - RPGFilter.COST.setReplacement((int) Math.ceil(cost) + ""), - RPGFilter.MISSING.setReplacement((int) Math.ceil(cost - getMana()) + "") + ErrorNodes.MANA, + getPlayer(), + FilterType.COLOR, + RPGFilter.SKILL.setReplacement(skill.getData().getName()), + RPGFilter.MANA.setReplacement(getMana() + ""), + RPGFilter.COST.setReplacement((int) Math.ceil(cost) + ""), + RPGFilter.MISSING.setReplacement((int) Math.ceil(cost - getMana()) + "") ); - return false; - } - - else return true; + return PlayerSkillCastFailedEvent.invoke(skill, NO_MANA); + } else { return true; } } /** @@ -1895,17 +1924,16 @@ else if (status == SkillStatus.MISSING_MANA && mana) * * @param player player to set up for */ - public void init(Player player) - { - if (!SkillAPI.getSettings().isWorldEnabled(player.getWorld())) - return; + public void init(Player player) { + if (!SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { return; } AttributeListener.updatePlayer(this); - InventoryTask.check(player); + getEquips().update(player); this.updateHealthAndMana(player); - if (this.getLastHealth() > 0) - player.setHealth(Math.min(this.getLastHealth(), player.getMaxHealth())); this.startPassives(player); this.updateScoreboard(); + if (this.getLastHealth() > 0 && !player.isDead()) { + player.setHealth(Math.min(this.getLastHealth(), player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue())); + } } } diff --git a/src/com/sucy/skill/api/player/PlayerSkill.java b/src/com/sucy/skill/api/player/PlayerSkill.java index bf4cdd7f..96de54ef 100644 --- a/src/com/sucy/skill/api/player/PlayerSkill.java +++ b/src/com/sucy/skill/api/player/PlayerSkill.java @@ -29,7 +29,14 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.enums.SkillStatus; import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.cast.IIndicator; +import com.sucy.skill.cast.IndicatorSettings; +import com.sucy.skill.manager.AttributeManager; import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; /** * Represents player-specific data for a skill such as the player's @@ -37,10 +44,12 @@ */ public final class PlayerSkill { + protected final List indicators = new ArrayList(); + private Skill skill; private PlayerData player; private PlayerClass parent; - private Material bind; + private String bind; private long cooldown; private int level; private int points; @@ -108,7 +117,7 @@ public PlayerData getPlayerData() * * @return the current material bound to or null if not bound */ - public Material getBind() + public String getBind() { return bind; } @@ -143,6 +152,23 @@ public int getCost() return skill.getCost(level); } + /** + * @return total invested cost in the skill + */ + public int getInvestedCost() + { + int total = 0; + for (int i = 0; i < level; i++) + total += skill.getCost(i); + return total; + + /* Could assume the linearly scaling cost, but API allows overrides + int x0 = skill.getCost(0); + int dx = skill.getCost(1) - x0; + return (x0 - dx) * level + dx * (level - 1) * level / 2; + */ + } + /** * @return mana cost to use the skill */ @@ -278,11 +304,17 @@ public void addPoints(int amount) * * @param mat new bind material */ - public void setBind(Material mat) + public void setBindMat(Material mat) { - this.bind = mat; + this.bind = mat.toString(); getPlayerData().bind(mat, this); } + + public void setBindDispName(String dispname) + { + this.bind = dispname; + getPlayerData().bind(dispname, this); + } /** * Reverts the skill back to level 0, locking it from @@ -300,7 +332,8 @@ public void revert() */ public void startCooldown() { - cooldown = System.currentTimeMillis() + (int) (skill.getCooldown(level) * 1000); + long cd = (long)player.scaleStat(AttributeManager.COOLDOWN, skill.getCooldown(level) * 1000L); + cooldown = System.currentTimeMillis() + cd; } /** @@ -336,4 +369,43 @@ public void addCooldown(double seconds) else cooldown = System.currentTimeMillis() + (int) (seconds * 1000); } + + /** + * Initializes the indicators for the skill + * + * @param player player to base location off of + */ + public void initIndicators(Player player) + { + indicators.clear(); + skill.createPreview(indicators, player, level); + } + + /** + * Updates the preview indicators each interval + * + * @param player player to base location off of + */ + public void updateIndicators(Player player) + { + skill.updateIndicators(indicators, player, level); + } + + /** + * Makes the packets for active indicators + * + * @param step animation step + * + * @return packet list + * + * @throws Exception + */ + public List makePackets(int step) + throws Exception + { + List packets = new ArrayList(); + for (IIndicator indicator : indicators) + indicator.makePackets(packets, IndicatorSettings.particle, step); + return packets; + } } diff --git a/src/com/sucy/skill/api/player/PlayerSkillBar.java b/src/com/sucy/skill/api/player/PlayerSkillBar.java index 772c4373..06dd3399 100644 --- a/src/com/sucy/skill/api/player/PlayerSkillBar.java +++ b/src/com/sucy/skill/api/player/PlayerSkillBar.java @@ -35,6 +35,7 @@ import org.bukkit.inventory.ItemStack; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; /** @@ -46,6 +47,7 @@ public class PlayerSkillBar UNASSIGNED = "e"; private final HashMap slots = new HashMap(); + private final HashSet reserved = new HashSet(); private final PlayerData player; private boolean enabled = true; private boolean setup = false; @@ -57,14 +59,19 @@ public class PlayerSkillBar */ public PlayerSkillBar(PlayerData player) { + if (SkillAPI.getSettings().isUsingCombat()) { + reserve(SkillAPI.getSettings().getCastSlot()); + } + this.player = player; for (int i = 1; i <= 9; i++) - { - if (SkillAPI.getSettings().getDefaultBarLayout()[i - 1]) - { + if (SkillAPI.getSettings().getDefaultBarLayout()[i - 1] && !reserved.contains(i - 1)) slots.put(i, UNASSIGNED); - } - } + } + + public void reserve(int slot) { + this.reserved.add(slot); + this.slots.remove(slot); } /** @@ -111,12 +118,9 @@ public Player getPlayer() public int getFirstWeaponSlot() { for (int i = 0; i < 9; i++) - { if (isWeaponSlot(i)) - { return i; - } - } + return -1; } @@ -131,16 +135,12 @@ public int getItemsInSkillSlots() int count = 0; Player p = player.getPlayer(); if (p == null) - { return -1; - } + for (int slot : slots.keySet()) - { if (slot > 0 && slot < 10 && p.getInventory().getItem(slot - 1) != null) - { count++; - } - } + return count; } @@ -156,17 +156,13 @@ public int countOpenSlots() int count = 0; Player p = player.getPlayer(); if (p == null) - { return -1; - } + ItemStack[] items = p.getInventory().getContents(); for (int i = 0; i < items.length; i++) - { - if (items[i] == null && !slots.containsKey(i + 1)) - { + if ((items[i] == null || items[i].getType() == Material.AIR) && !slots.containsKey(i + 1) && !reserved.contains(i)) count++; - } - } + return count; } @@ -194,35 +190,27 @@ public void toggleEnabled() */ public void toggleSlot(int slot) { - if (!isEnabled() || SkillAPI.getSettings().getLockedSlots()[slot]) - { + if (!isEnabled() || SkillAPI.getSettings().getLockedSlots()[slot] || reserved.contains(slot)) return; - } + slot++; // Make sure there is always at least one weapon slot - if (!slots.containsKey(slot) && (slots.size() == 8 || countOpenSlots() == 0)) - { + if (!slots.containsKey(slot) && (slots.size() == 8 - reserved.size() || countOpenSlots() == 0)) return; - } // Cannot have item in cursor Player p = player.getPlayer(); if (p == null || (p.getItemOnCursor() != null && p.getItemOnCursor().getType() != Material.AIR)) - { return; - } // Toggle the slot clear(p); if (slots.containsKey(slot)) - { slots.remove(slot); - } else - { slots.put(slot, UNASSIGNED); - } + setup(p); } @@ -233,24 +221,12 @@ public void toggleSlot(int slot) */ public void apply(int slot) { - if (getPlayer() == null || getPlayer().getGameMode() == GameMode.CREATIVE) - { + if (getPlayer() == null || getPlayer().getGameMode() == GameMode.CREATIVE || !isEnabled() || isWeaponSlot(slot)) return; - } - if (!isEnabled()) - { - return; - } - if (isWeaponSlot(slot)) - { - return; - } + PlayerSkill skill = player.getSkill(slots.get(slot + 1)); - if (skill == null) - { - return; - } - player.cast(skill); + if (skill != null) + player.cast(skill); } /** @@ -261,15 +237,13 @@ public void apply(int slot) public void clear(HumanEntity player) { if (player == null || !setup) - { return; - } + for (int i = 0; i < 9; i++) { if (isWeaponSlot(i)) - { continue; - } + player.getInventory().setItem(i, null); } setup = false; @@ -283,15 +257,13 @@ public void clear(HumanEntity player) public void clear(PlayerDeathEvent event) { if (event == null || !setup) - { return; - } + for (int i = 0; i < 9; i++) { if (isWeaponSlot(i)) - { continue; - } + event.getDrops().remove(event.getEntity().getInventory().getItem(i)); event.getEntity().getInventory().setItem(i, null); } @@ -306,9 +278,8 @@ public void reset() for (int i = 0; i < 9; i++) { if (isWeaponSlot(i)) - { continue; - } + slots.put(i + 1, UNASSIGNED); } update(getPlayer()); @@ -322,9 +293,7 @@ public void reset() public void setup(HumanEntity player) { if (player == null || !enabled || player.getGameMode() == GameMode.CREATIVE || setup) - { return; - } // Disable the skill bar if there isn't enough space if (countOpenSlots() < getItemsInSkillSlots()) @@ -349,15 +318,12 @@ public void setup(HumanEntity player) for (int i = 0; i < 9; i++) { if (isWeaponSlot(i)) - { continue; - } + ItemStack item = player.getInventory().getItem(i); player.getInventory().setItem(i, SkillAPI.getSettings().getUnassigned()); if (item != null) - { player.getInventory().addItem(item); - } } // Update the slots @@ -392,9 +358,8 @@ public void unlock(PlayerSkill skill) public void assign(PlayerSkill skill, int slot) { if (isWeaponSlot(slot)) - { return; - } + for (Map.Entry entry : slots.entrySet()) { if (entry.getValue().equals(skill.getData().getName())) @@ -421,9 +386,7 @@ public void update(HumanEntity player) { int index = i - 1; if (isWeaponSlot(index)) - { continue; - } PlayerSkill skill = this.player.getSkill(slots.get(i)); if (skill == null || !skill.isUnlocked()) @@ -436,9 +399,7 @@ public void update(HumanEntity player) } } else if (isEnabled() && player != null) - { - player.getInventory().setItem(index, skill.getData().getIndicator(skill)); - } + player.getInventory().setItem(index, skill.getData().getIndicator(skill, true)); } } @@ -515,14 +476,10 @@ public void applySettings() if (layout[i - 1]) { if (!slots.containsKey(i)) - { slots.put(i, UNASSIGNED); - } } else if (slots.containsKey(i)) - { slots.remove(i); - } } } } diff --git a/src/com/sucy/skill/api/player/PlayerSkillSlot.java b/src/com/sucy/skill/api/player/PlayerSkillSlot.java new file mode 100644 index 00000000..98e569f0 --- /dev/null +++ b/src/com/sucy/skill/api/player/PlayerSkillSlot.java @@ -0,0 +1,149 @@ +/** + * SkillAPI + * com.sucy.skill.api.player.PlayerSkillSlot + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.player; + +import com.sucy.skill.SkillAPI; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; + +/** + * Handles the skill slot for casting when bars are disabled + */ +public class PlayerSkillSlot +{ + private ArrayList skills = new ArrayList(); + private int index = 0; + private PlayerData player; + + /** + * Initializes the skill slot for the given player + * + * @param data data of the player + */ + public void init(PlayerData data) + { + this.player = data; + this.index = 0; + this.skills.clear(); + + for (PlayerSkill skill : data.getSkills()) + if (skill.getData().canCast() && skill.isUnlocked()) + skills.add(skill); + } + + /** + * Gets the current item that should be displayed in the skill slot + * + * @return item display + */ + public ItemStack getDisplay() + { + return skills.size() == 0 ? + SkillAPI.getSettings().getCastItem() + : skills.get(index).getData().getIndicator(skills.get(index), true); + } + + /** + * Adds a skill to the available skills, if castable + * + * @param skill skill to add + */ + public void unlock(PlayerSkill skill) + { + if (skill.isUnlocked() && skill.getData().canCast()) + skills.add(skill); + } + + /** + * Clears a specified skill, if available + * + * @param skill skill to clear + */ + public void clear(PlayerSkill skill) + { + if (skill.getData().canCast()) + { + skills.remove(skill); + index = Math.max(Math.min(index, skills.size() - 1), 0); + } + } + + /** + * Clears all available skills + */ + public void clearAll() + { + skills.clear(); + index = 0; + } + + /** + * Updates the displayed item for the player + * + * @param player player to update for + */ + public void updateItem(Player player) + { + if (player != null) + player.getInventory().setItem(SkillAPI.getSettings().getCastSlot(), getDisplay()); + } + + /** + * Activates the skill slot, casting the hovered item + */ + public void activate() + { + if (skills.size() > 0) + player.cast(skills.get(index)); + } + + /** + * Cycles to the next skill + */ + public void next() + { + if (skills.size() > 0) + { + index = (index + 1) % skills.size(); + updateItem(player.getPlayer()); + } + } + + /** + * Cycles to the previous skill + */ + public void prev() + { + if (skills.size() > 0) + { + index = (index + skills.size() - 1) % skills.size(); + updateItem(player.getPlayer()); + } + } +} diff --git a/src/com/sucy/skill/api/projectile/CustomProjectile.java b/src/com/sucy/skill/api/projectile/CustomProjectile.java index ea0fce54..85d7fe41 100644 --- a/src/com/sucy/skill/api/projectile/CustomProjectile.java +++ b/src/com/sucy/skill/api/projectile/CustomProjectile.java @@ -1,21 +1,21 @@ /** * SkillAPI * com.sucy.skill.api.projectile.CustomProjectile - * + *

* The MIT License (MIT) - * + *

* Copyright (c) 2014 Steven Sucy - * + *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software") to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + *

* The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + *

* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,8 +26,10 @@ */ package com.sucy.skill.api.projectile; -import com.rit.sucy.player.Protection; import com.rit.sucy.reflect.Reflection; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.particle.target.Followable; +import com.sucy.skill.log.Logger; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Entity; @@ -43,50 +45,69 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.function.Predicate; /** * Base class for custom projectiles */ -public abstract class CustomProjectile extends BukkitRunnable implements Metadatable -{ +public abstract class CustomProjectile extends BukkitRunnable implements Metadatable, Followable { private static Constructor aabbConstructor; - private static Method getEntities; - private static Method getBukkitEntity; - private static Method getHandle; - - static - { - try - { + private static Method getEntities; + private static Method getBukkitEntity; + private static Method getEntitiesGuava; + private static Method getHandle; + + private static boolean isLivingEntity(Object thing) { + try { + return getBukkitEntity.invoke(thing) instanceof LivingEntity; + } catch (Exception ex) { + return false; + } + } + + private static final Predicate JAVA_PREDICATE = CustomProjectile::isLivingEntity; + private static final com.google.common.base.Predicate GUAVA_PREDICATE = CustomProjectile::isLivingEntity; + + + static { + try { Class aabbClass = Reflection.getNMSClass("AxisAlignedBB"); Class entityClass = Reflection.getNMSClass("Entity"); aabbConstructor = aabbClass.getConstructor(double.class, double.class, double.class, double.class, double.class, double.class); - getEntities = Reflection.getNMSClass("World").getDeclaredMethod("getEntities", entityClass, aabbClass); getBukkitEntity = entityClass.getDeclaredMethod("getBukkitEntity"); getHandle = Reflection.getCraftClass("CraftWorld").getDeclaredMethod("getHandle"); - } - catch (Exception ex) - { + Class worldClass = Reflection.getNMSClass("World"); + try { + getEntities = worldClass.getDeclaredMethod("getEntities", entityClass, aabbClass, Predicate.class); + } catch (Exception e) { + getEntitiesGuava = worldClass.getDeclaredMethod("getEntities", entityClass, aabbClass, com.google.common.base.Predicate.class); + } + } catch (Exception ex) { + Logger.log("Unable to use reflection for accurate collision - will resort to simple radius check"); ex.printStackTrace(); } } private final HashMap> metadata = new HashMap>(); + private final Set hit = new HashSet(); + private ProjectileCallback callback; - private LivingEntity thrower; + private LivingEntity thrower; private boolean enemy = true; - private boolean ally = false; + private boolean ally = false; + private boolean valid = true; /** * Constructs a new custom projectile and starts its timer task * * @param thrower entity firing the projectile */ - CustomProjectile(LivingEntity thrower) - { + public CustomProjectile(LivingEntity thrower) { this.thrower = thrower; runTaskTimer(Bukkit.getPluginManager().getPlugin("SkillAPI"), 1, 1); } @@ -125,79 +146,87 @@ public abstract class CustomProjectile extends BukkitRunnable implements Metadat */ protected abstract double getCollisionRadius(); + protected abstract Vector getVelocity(); + + protected abstract void setVelocity(Vector vel); + /** * Checks whether or not the projectile is still valid. * Invalid would mean landing on the ground or leaving the loaded chunks. */ - protected boolean isTraveling() - { + protected boolean isTraveling() { // Leaving a loaded chunk - if (!getLocation().getChunk().isLoaded()) - { + if (!getLocation().getChunk().isLoaded()) { cancel(); Bukkit.getPluginManager().callEvent(expire()); return false; } // Hitting a solid block - if (landed()) - { + if (landed()) { + this.applyLanded(); + return false; + } + + return true; + } + + public void applyLanded() { + if (valid) { cancel(); Bukkit.getPluginManager().callEvent(land()); if (callback != null) callback.callback(this, null); - return false; } - - return true; } /** * Checks if the projectile collides with a given list of entities */ - protected void checkCollision() - { - for (LivingEntity entity : getColliding()) - { - boolean ally = Protection.isAlly(getShooter(), entity); + protected void checkCollision(final boolean pierce) { + for (LivingEntity entity : getColliding()) { + if (entity == thrower || hit.contains(entity.getEntityId())) { + continue; + } + hit.add(entity.getEntityId()); + + boolean ally = SkillAPI.getSettings().isAlly(getShooter(), entity); if (ally && !this.ally) continue; if (!ally && !this.enemy) continue; + if (!SkillAPI.getSettings().isValidTarget(entity)) continue; - cancel(); Bukkit.getPluginManager().callEvent(hit(entity)); if (callback != null) callback.callback(this, entity); - return; + if (!pierce) { + cancel(); + return; + } } } /** * @return list of entities colliding with the projectile */ - private List getColliding() - { + private List getColliding() { // Reflection for nms collision List result = new ArrayList(1); - try - { + try { Object nmsWorld = getHandle.invoke(getLocation().getWorld()); - Object list = getEntities.invoke(nmsWorld, null, getBoundingBox()); - for (Object item : (List) list) - { - Object bukkit = getBukkitEntity.invoke(item); - if (bukkit instanceof LivingEntity) - result.add((LivingEntity) bukkit); + Object predicate = getEntities == null ? GUAVA_PREDICATE : JAVA_PREDICATE; + Object list = (getEntities == null ? getEntitiesGuava : getEntities) + .invoke(nmsWorld, null, getBoundingBox(), predicate); + for (Object item : (List) list) { + result.add((LivingEntity) getBukkitEntity.invoke(item)); } } // Fallback when reflection fails - catch (Exception ex) - { + catch (Exception ex) { double radiusSq = getCollisionRadius(); radiusSq *= radiusSq; - for (LivingEntity entity : getNearbyEntities()) - { + for (LivingEntity entity : getNearbyEntities()) { if (entity == thrower) continue; @@ -211,21 +240,19 @@ private List getColliding() /** * @return NMS bounding box of the projectile */ - private Object getBoundingBox() throws Exception - { + private Object getBoundingBox() throws Exception { Location loc = getLocation(); double rad = getCollisionRadius(); return aabbConstructor.newInstance( - loc.getX() - rad, loc.getY() - rad, loc.getZ() - rad, - loc.getX() + rad, loc.getY() + rad, loc.getZ() + rad + loc.getX() - rad, loc.getY() - rad, loc.getZ() - rad, + loc.getX() + rad, loc.getY() + rad, loc.getZ() + rad ); } /** * @return list of nearby living entities */ - private List getNearbyEntities() - { + private List getNearbyEntities() { List list = new ArrayList(); Location loc = getLocation(); double radius = getCollisionRadius(); @@ -247,8 +274,7 @@ private List getNearbyEntities() * @param ally whether or not allies can be hit * @param enemy whether or not enemies can be hit */ - public void setAllyEnemy(boolean ally, boolean enemy) - { + public void setAllyEnemy(boolean ally, boolean enemy) { this.ally = ally; this.enemy = enemy; } @@ -258,8 +284,7 @@ public void setAllyEnemy(boolean ally, boolean enemy) * * @return the entity that shot the projectile */ - public LivingEntity getShooter() - { + public LivingEntity getShooter() { return thrower; } @@ -267,9 +292,19 @@ public LivingEntity getShooter() * Marks the projectile as invalid when the associated task is cancelled */ @Override - public void cancel() - { + public void cancel() { super.cancel(); + valid = false; + } + + /** + * Checks whether or not the projectile is still active + * + * @return true if active, false otherwise + */ + @Override + public boolean isValid() { + return valid; } /** @@ -279,13 +314,11 @@ public void cancel() * @param meta the metadata to set */ @Override - public void setMetadata(String key, MetadataValue meta) - { + public void setMetadata(String key, MetadataValue meta) { boolean hasMeta = hasMetadata(key); List list = hasMeta ? getMetadata(key) : new ArrayList(); list.add(meta); - if (!hasMeta) - { + if (!hasMeta) { metadata.put(key, list); } } @@ -299,8 +332,7 @@ public void setMetadata(String key, MetadataValue meta) * @return the metadata value */ @Override - public List getMetadata(String key) - { + public List getMetadata(String key) { return metadata.get(key); } @@ -312,8 +344,7 @@ public List getMetadata(String key) * @return whether or not there is metadata set for the key */ @Override - public boolean hasMetadata(String key) - { + public boolean hasMetadata(String key) { return metadata.containsKey(key); } @@ -325,8 +356,7 @@ public boolean hasMetadata(String key) * @param plugin plugin to remove the metadata for */ @Override - public void removeMetadata(String key, Plugin plugin) - { + public void removeMetadata(String key, Plugin plugin) { metadata.remove(key); } @@ -335,8 +365,7 @@ public void removeMetadata(String key, Plugin plugin) * * @param callback callback handler */ - public void setCallback(ProjectileCallback callback) - { + public void setCallback(ProjectileCallback callback) { this.callback = callback; } @@ -355,25 +384,21 @@ public void setCallback(ProjectileCallback callback) * * @return the list of calculated directions */ - public static ArrayList calcSpread(Vector dir, double angle, int amount) - { + public static ArrayList calcSpread(Vector dir, double angle, int amount) { // Special cases - if (amount <= 0) - { + if (amount <= 0) { return new ArrayList(); } ArrayList list = new ArrayList(); // One goes straight if odd amount - if (amount % 2 == 1) - { + if (amount % 2 == 1) { list.add(dir); amount--; } - if (amount <= 0) - { + if (amount <= 0) { return list; } @@ -387,22 +412,18 @@ public static ArrayList calcSpread(Vector dir, double angle, int amount) // Get the vertical angle double vBaseAngle = Math.acos(Math.max(-1, Math.min(base.dot(dir), 1))); - if (dir.getY() < 0) - { + if (dir.getY() < 0) { vBaseAngle = -vBaseAngle; } double hAngle = Math.acos(Math.max(-1, Math.min(1, base.dot(X_VEC)))) / DEGREE_TO_RAD; - if (dir.getZ() < 0) - { + if (dir.getZ() < 0) { hAngle = -hAngle; } // Calculate directions double angleIncrement = angle / (amount - 1); - for (int i = 0; i < amount / 2; i++) - { - for (int direction = -1; direction <= 1; direction += 2) - { + for (int i = 0; i < amount / 2; i++) { + for (int direction = -1; direction <= 1; direction += 2) { // Initial calculations double bonusAngle = angle / 2 * direction - angleIncrement * i * direction; double totalAngle = hAngle + bonusAngle; @@ -433,11 +454,9 @@ public static ArrayList calcSpread(Vector dir, double angle, int amount) * * @return list of locations to spawn projectiles */ - public static ArrayList calcRain(Location loc, double radius, double height, int amount) - { + public static ArrayList calcRain(Location loc, double radius, double height, int amount) { ArrayList list = new ArrayList(); - if (amount <= 0) - { + if (amount <= 0) { return list; } loc.add(0, height, 0); @@ -448,14 +467,12 @@ public static ArrayList calcRain(Location loc, double radius, double h // Calculate locations int tiers = (amount + 7) / 8; - for (int i = 0; i < tiers; i++) - { + for (int i = 0; i < tiers; i++) { double rad = radius * (tiers - i) / tiers; int tierNum = Math.min(amount, 8); double increment = 360 / tierNum; double angle = (i % 2) * 22.5; - for (int j = 0; j < tierNum; j++) - { + for (int j = 0; j < tierNum; j++) { double dx = Math.cos(angle) * rad; double dz = Math.sin(angle) * rad; Location l = loc.clone(); diff --git a/src/com/sucy/skill/api/projectile/HomingProjectile.java b/src/com/sucy/skill/api/projectile/HomingProjectile.java new file mode 100644 index 00000000..da84d69c --- /dev/null +++ b/src/com/sucy/skill/api/projectile/HomingProjectile.java @@ -0,0 +1,87 @@ +package com.sucy.skill.api.projectile; + +import com.sucy.skill.api.particle.target.EffectTarget; +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; + +/** + * SkillAPI © 2018 + * com.sucy.skill.api.projectile.HomingProjectile + */ +public class HomingProjectile extends CustomProjectile { + private static final Vector UP = new Vector(0, 1, 0); + + private final CustomProjectile baseProjectile; + private final EffectTarget target; + private final double threshold; + private final double angle; + + public HomingProjectile(final CustomProjectile baseProjectile, final EffectTarget target, final double angle) { + super(baseProjectile.getShooter()); + this.baseProjectile = baseProjectile; + this.target = target; + this.angle = angle; + this.threshold = Math.cos(angle); + } + + @Override + public Location getLocation() { + return baseProjectile.getLocation(); + } + + @Override + protected Event expire() { + return baseProjectile.expire(); + } + + @Override + protected Event land() { + return baseProjectile.land(); + } + + @Override + protected Event hit(final LivingEntity entity) { + return baseProjectile.hit(entity); + } + + @Override + protected boolean landed() { + return baseProjectile.landed(); + } + + @Override + protected double getCollisionRadius() { + return baseProjectile.getCollisionRadius(); + } + + @Override + protected Vector getVelocity() { + return baseProjectile.getVelocity(); + } + + @Override + protected void setVelocity(Vector vel) { + baseProjectile.setVelocity(vel); + } + + @Override + public void run() { + final Vector vel = getVelocity(); + final double speed = vel.length(); + final Vector dir = vel.multiply(1 / speed); + final Vector towards = target.getLocation().toVector().subtract(getLocation().toVector()); + final Vector targetDir = towards.normalize(); + final double dot = dir.dot(targetDir); + if (dot >= threshold) { + setVelocity(targetDir.multiply(speed)); + } else { + final double diff = Math.acos(dot); + final double t = angle / diff; + final double sinAngle = Math.sin(diff); + //final double m = Math.sin() + //baseProjectile.setVelocity(result); + } + } +} diff --git a/src/com/sucy/skill/api/projectile/ItemProjectile.java b/src/com/sucy/skill/api/projectile/ItemProjectile.java index 446e333d..372d6716 100644 --- a/src/com/sucy/skill/api/projectile/ItemProjectile.java +++ b/src/com/sucy/skill/api/projectile/ItemProjectile.java @@ -26,9 +26,11 @@ */ package com.sucy.skill.api.projectile; +import com.sucy.skill.SkillAPI; import com.sucy.skill.api.event.ItemProjectileHitEvent; import com.sucy.skill.api.event.ItemProjectileLandEvent; import com.sucy.skill.api.event.ItemProjectileLaunchEvent; +import com.sucy.skill.api.util.DamageLoreRemover; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Item; @@ -40,6 +42,8 @@ import java.util.ArrayList; +import static com.sucy.skill.listener.MechanicListener.ITEM_PROJECTILE; + /** *

Represents a projectile that uses an item as the actual projectile.

*/ @@ -48,7 +52,7 @@ public class ItemProjectile extends CustomProjectile private static final String NAME = "SkillAPI#"; private static int NEXT = 0; - private Item item; + private final Item item; /** *

Constructs a new item projectile.

@@ -65,10 +69,12 @@ public ItemProjectile(LivingEntity thrower, Location loc, ItemStack item, Vector ItemMeta meta = item.getItemMeta(); meta.setDisplayName(NAME + NEXT++); item.setItemMeta(meta); + DamageLoreRemover.removeAttackDmg(item); this.item = thrower.getWorld().dropItem(loc.add(0, 1, 0), item); this.item.setVelocity(vel); this.item.setPickupDelay(999999); + SkillAPI.setMeta(this.item, ITEM_PROJECTILE, this); Bukkit.getPluginManager().callEvent(new ItemProjectileLaunchEvent(this)); } @@ -131,6 +137,16 @@ protected double getCollisionRadius() return item.getVelocity().length() / 2; } + @Override + protected Vector getVelocity() { + return item.getVelocity(); + } + + @Override + protected void setVelocity(final Vector velocity) { + item.setVelocity(velocity); + } + /** *

Updates the projectile's position.

*

This is for the repeating task and if you call it yourself, it @@ -140,7 +156,7 @@ protected double getCollisionRadius() public void run() { if (isTraveling()) - checkCollision(); + checkCollision(false); } /** diff --git a/src/com/sucy/skill/api/projectile/ParticleProjectile.java b/src/com/sucy/skill/api/projectile/ParticleProjectile.java index 90f65eac..d1e6eeef 100644 --- a/src/com/sucy/skill/api/projectile/ParticleProjectile.java +++ b/src/com/sucy/skill/api/projectile/ParticleProjectile.java @@ -48,7 +48,7 @@ public class ParticleProjectile extends CustomProjectile /** * Settings key for the projectile speed */ - private static final String SPEED = "velocity"; + public static final String SPEED = "velocity"; /** * Settings key for the projectile lifespan @@ -60,6 +60,13 @@ public class ParticleProjectile extends CustomProjectile */ private static final String FREQUENCY = "frequency"; + /** + * Settings key for the projectile's effective gravity + */ + private static final String GRAVITY = "gravity"; + + private static final String PIERCE = "pierce"; + private Location loc; private Settings settings; private Vector vel; @@ -67,6 +74,8 @@ public class ParticleProjectile extends CustomProjectile private int count; private int freq; private int life; + private Vector gravity; + private boolean pierce; /** * Constructor @@ -85,9 +94,12 @@ public ParticleProjectile(LivingEntity shooter, int level, Location loc, Setting this.vel = loc.getDirection().multiply(settings.getAttr(SPEED, level, 1.0)); this.freq = (int) (20 * settings.getDouble(FREQUENCY, 0.5)); this.life = (int) (settings.getDouble(LIFESPAN, 2) * 20); + this.gravity = new Vector(0, settings.getDouble(GRAVITY, 0), 0); + this.pierce = settings.getBool(PIERCE, false); steps = (int) Math.ceil(vel.length() * 2); vel.multiply(1.0 / steps); + gravity.multiply(1.0 / steps); Bukkit.getPluginManager().callEvent(new ParticleProjectileLaunchEvent(this)); } @@ -152,6 +164,7 @@ protected double getCollisionRadius() /** * @return velocity of the projectile */ + @Override public Vector getVelocity() { return vel; @@ -172,6 +185,7 @@ public void teleport(Location loc) * * @param vel new velocity */ + @Override public void setVelocity(Vector vel) { this.vel = vel; @@ -187,11 +201,12 @@ public void run() for (int i = 0; i < steps; i++) { loc.add(vel); + vel.add(gravity); if (!isTraveling()) return; - checkCollision(); + checkCollision(pierce); } // Particle along path diff --git a/src/com/sucy/skill/api/skills/Skill.java b/src/com/sucy/skill/api/skills/Skill.java index b5daaf9e..54c1140f 100644 --- a/src/com/sucy/skill/api/skills/Skill.java +++ b/src/com/sucy/skill/api/skills/Skill.java @@ -29,6 +29,7 @@ import com.rit.sucy.config.Filter; import com.rit.sucy.config.FilterType; import com.rit.sucy.config.parse.DataSection; +import com.rit.sucy.config.parse.NumberParser; import com.rit.sucy.text.TextFormatter; import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; @@ -37,11 +38,14 @@ import com.sucy.skill.api.event.SkillDamageEvent; import com.sucy.skill.api.event.TrueDamageEvent; import com.sucy.skill.api.player.PlayerCombos; +import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.api.player.PlayerSkill; import com.sucy.skill.api.util.DamageLoreRemover; import com.sucy.skill.api.util.Data; -import com.sucy.skill.api.util.NumberParser; +import com.sucy.skill.cast.IIndicator; +import com.sucy.skill.data.Permissions; import com.sucy.skill.dynamic.TempEntity; +import com.sucy.skill.gui.tool.IconHolder; import com.sucy.skill.hook.NoCheatHook; import com.sucy.skill.hook.PluginChecker; import com.sucy.skill.language.NotificationNodes; @@ -52,6 +56,7 @@ import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.attribute.Attribute; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -65,11 +70,11 @@ * Represents a template for a skill used in the RPG system. This is * the class to extend when creating your own custom skills. */ -public abstract class Skill +public abstract class Skill implements IconHolder { private static final DecimalFormat FORMAT = new DecimalFormat("#########0.0#"); - private final ArrayList description = new ArrayList(); + private final ArrayList description = new ArrayList<>(); private List iconLore; private ItemStack indicator; @@ -197,12 +202,25 @@ public boolean hasCombo() * level up to the next stage. * * @return true if can level up automatically, false otherwise + * @deprecated use {@link Skill#canAutoLevel(int)} instead */ + @Deprecated public boolean canAutoLevel() { return getCost(0) == 0 && getCost(1) == 0; } + /** + * Checks whether or not the skill can automatically + * level up to the next stage. + * + * @param level - the current level of the skill + * @return true if can level up automatically to the next level, false otherwise + */ + public boolean canAutoLevel(final int level) { + return getCost(level) == 0; + } + /** * Retrieves the configuration key for the skill * @@ -433,6 +451,80 @@ public boolean canCast() return this instanceof SkillShot || this instanceof TargetSkill; } + /** + * Gets the indicator for the skill for the GUI tools + * + * @return GUI tool indicator + */ + public ItemStack getToolIndicator() + { + ItemStack item = indicator.clone(); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(name); + + ArrayList lore = new ArrayList<>(); + lore.add("Level: " + getLevelReq(0)); + lore.add("Cost: " + getCost(0)); + meta.setLore(lore); + + item.setItemMeta(meta); + + return item; + } + + /** + * Fetches the icon for the skill for the player + * + * @param data player to get for + * + * @return the skill icon + */ + @Override + public ItemStack getIcon(PlayerData data) + { + PlayerSkill skill = data.getSkill(name); + if (skill != null) + return getIndicator(skill, false); + else + return getIndicator(); + } + + @Override + public boolean isAllowed(final Player player) { + return !needsPermission() + || player.hasPermission(Permissions.SKILL) + || player.hasPermission(Permissions.SKILL + "." + name.toLowerCase().replace(" ", "-")); + } + + public boolean hasDependency(final PlayerData playerData) { + // Must meet any skill requirements + if (getSkillReq() != null) { + PlayerSkill req = playerData.getSkill(getSkillReq()); + return req == null || req.getLevel() >= getSkillReqLevel(); + } + return true; + } + + public boolean isCompatible(final PlayerData playerData) { + for (final String skillName : settings.getStringList(SkillAttribute.INCOMPATIBLE)) { + final PlayerSkill skill = playerData.getSkill(skillName); + if (skill != null && skill.getLevel() > 0) { + return false; + } + } + return true; + } + + public boolean hasInvestedEnough(final PlayerData playerData) { + final PlayerSkill skill = playerData.getSkill(name); + if (skill == null) { + return false; + } + + final double reqPoints = settings.getAttr(SkillAttribute.POINTS_SPENT_REQ, skill.getLevel(), 0); + return playerData.getInvestedSkillPoints() >= reqPoints; + } + /** * Retrieves the indicator for the skill while applying filters to match * the player-specific data. @@ -441,7 +533,7 @@ public boolean canCast() * * @return filtered skill indicator */ - public ItemStack getIndicator(PlayerSkill skillData) + public ItemStack getIndicator(PlayerSkill skillData, boolean brief) { Player player = skillData.getPlayerData().getPlayer(); @@ -450,10 +542,16 @@ public ItemStack getIndicator(PlayerSkill skillData) ItemMeta meta = item.hasItemMeta() ? item.getItemMeta() : Bukkit.getItemFactory().getItemMeta(item.getType()); ArrayList lore = new ArrayList(); - String lvlReq = SkillAPI.getLanguage().getMessage(skillData.getLevelReq() <= skillData.getPlayerClass().getLevel() ? SkillNodes.REQUIREMENT_MET : SkillNodes.REQUIREMENT_NOT_MET, true, FilterType.COLOR).get(0); - String costReq = SkillAPI.getLanguage().getMessage(skillData.getCost() <= skillData.getPlayerClass().getPoints() ? SkillNodes.REQUIREMENT_MET : SkillNodes.REQUIREMENT_NOT_MET, true, FilterType.COLOR).get(0); - lvlReq = lvlReq.substring(0, lvlReq.length() - 2); - costReq = costReq.substring(0, costReq.length() - 2); + String MET = SkillAPI.getLanguage().getMessage(SkillNodes.REQUIREMENT_MET, true, FilterType.COLOR).get(0); + String NOT_MET = SkillAPI.getLanguage().getMessage(SkillNodes.REQUIREMENT_NOT_MET, true, FilterType.COLOR).get(0); + MET = MET.substring(0, MET.length() - 2); + NOT_MET = NOT_MET.substring(0, NOT_MET.length() - 2); + + final String lvlReq = skillData.getLevelReq() <= skillData.getPlayerClass().getLevel() ? MET : NOT_MET; + final String costReq = skillData.getCost() <= skillData.getPlayerClass().getPoints() ? MET : NOT_MET; + final String spentReq = hasInvestedEnough(skillData.getPlayerData()) ? MET : NOT_MET; + final String branchReq = isCompatible(skillData.getPlayerData()) ? MET : NOT_MET; + final String skillReq = isCompatible(skillData.getPlayerData()) ? MET : NOT_MET; String attrChanging = SkillAPI.getLanguage().getMessage(SkillNodes.ATTRIBUTE_CHANGING, true, FilterType.COLOR).get(0); String attrStatic = SkillAPI.getLanguage().getMessage(SkillNodes.ATTRIBUTE_NOT_CHANGING, true, FilterType.COLOR).get(0); @@ -464,12 +562,15 @@ public ItemStack getIndicator(PlayerSkill skillData) { // General data line = line.replace("{level}", "" + skillData.getLevel()) - .replace("{req:lvl}", lvlReq) - .replace("{req:level}", lvlReq) - .replace("{req:cost}", costReq) - .replace("{max}", "" + maxLevel) - .replace("{name}", name) - .replace("{type}", type); + .replace("{req:lvl}", lvlReq) + .replace("{req:level}", lvlReq) + .replace("{req:cost}", costReq) + .replace("{req:spent}", spentReq) + .replace("{req:branch}", branchReq) + .replace("{req:skill}", skillReq) + .replace("{max}", "" + maxLevel) + .replace("{name}", name) + .replace("{type}", type); // Attributes while (line.contains("{attr:")) @@ -481,11 +582,11 @@ public ItemStack getIndicator(PlayerSkill skillData) Object nextValue = getAttr(player, attr, Math.min(skillData.getLevel() + 1, maxLevel)); if (attr.equals("level") || attr.equals("cost")) { - nextValue = (int) Math.floor(NumberParser.parseDouble(nextValue.toString())); + nextValue = Integer.parseInt(nextValue.toString().replaceAll("\\.(.*)$","").replaceAll(",(.*)$","")); currValue = nextValue; } - if (currValue.equals(nextValue)) + if (currValue.equals(nextValue) || brief) { line = line.replace("{attr:" + attr + "}", attrStatic.replace("{name}", getAttrName(attr)).replace("{value}", currValue.toString())); } @@ -542,6 +643,13 @@ public ItemStack getIndicator(PlayerSkill skillData) } } + // Binds + if (SkillAPI.getSettings().isShowBinds() && skillData.getBind() != null) { + lore.add(""); + final String type = TextFormatter.format(skillData.getBind().replace("LEGACY_", "").replace("_", " ")); + lore.add(SkillAPI.getSettings().getBindText().replace("{material}", type)); + } + if (lore.size() > 0) { meta.setDisplayName(lore.remove(0)); @@ -629,21 +737,32 @@ public void sendMessage(Player player, double radius) * @param damage amount of damage to deal * @param source source of the damage (skill caster) */ - public void damage(LivingEntity target, double damage, LivingEntity source) - { + public void damage(LivingEntity target, double damage, LivingEntity source) { + damage(target, damage, source, "default"); + } + + /** + * Applies skill damage to the target, launching the skill damage event + * + * @param target target to receive the damage + * @param damage amount of damage to deal + * @param source source of the damage (skill caster) + * @param classification type of damage to deal + */ + public void damage(LivingEntity target, double damage, LivingEntity source, String classification) { if (target instanceof TempEntity) return; - SkillDamageEvent event = new SkillDamageEvent(source, target, damage); + SkillDamageEvent event = new SkillDamageEvent(this, source, target, damage, classification); Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { if (source instanceof Player) { - Player player = (Player) source; + final Player player = (Player) source; if (PluginChecker.isNoCheatActive()) NoCheatHook.exempt(player); skillDamage = true; target.setNoDamageTicks(0); - target.damage(event.getDamage(), source); + target.damage(event.getDamage()); skillDamage = false; if (PluginChecker.isNoCheatActive()) NoCheatHook.unexempt(player); } @@ -668,12 +787,32 @@ public void trueDamage(LivingEntity target, double damage, LivingEntity source) { if (target instanceof TempEntity) return; - TrueDamageEvent event = new TrueDamageEvent(source, target, damage); + TrueDamageEvent event = new TrueDamageEvent(this, source, target, damage); Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled() && event.getDamage() != 0) - target.setHealth(Math.max(Math.min(target.getHealth() - event.getDamage(), target.getMaxHealth()), 0)); + target.setHealth(Math.max(Math.min(target.getHealth() - event.getDamage(), target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()), 0)); } + /** + * Initializes the indicators for a skill. + * This should be implemented by each skill. + * + * @param list list to store indicators in + * @param player player to base location on + * @param level the level of the skill to create for + */ + public void createPreview(List list, Player player, int level) { } + + /** + * Updates the positions of indicators for a skill. + * This should be implemented by each skill. + * + * @param list list to update + * @param player player to base location on + * @param level level of the skill updating for + */ + public void updateIndicators(List list, Player player, int level) { } + private static boolean skillDamage = false; /** @@ -698,7 +837,6 @@ public static boolean isSkillDamage() private static final String PERM = "needs-permission"; private static final String DESC = "desc"; private static final String ATTR = "attributes"; - private static final String ATTR_INFO = "attribute-info"; private static final String COMBO = "combo"; /** diff --git a/src/com/sucy/skill/api/skills/SkillAttribute.java b/src/com/sucy/skill/api/skills/SkillAttribute.java index 28efd12a..f6faade5 100644 --- a/src/com/sucy/skill/api/skills/SkillAttribute.java +++ b/src/com/sucy/skill/api/skills/SkillAttribute.java @@ -57,4 +57,8 @@ public final class SkillAttribute * Range of a target skill */ public static final String RANGE = "range"; + + public static final String INCOMPATIBLE = "incompatible"; + + public static final String POINTS_SPENT_REQ = "points-spent-req"; } diff --git a/src/com/sucy/skill/api/util/ActionBar.java b/src/com/sucy/skill/api/util/ActionBar.java index 7fe38134..3839bbcc 100644 --- a/src/com/sucy/skill/api/util/ActionBar.java +++ b/src/com/sucy/skill/api/util/ActionBar.java @@ -28,6 +28,7 @@ import com.rit.sucy.reflect.Reflection; import com.rit.sucy.text.TextFormatter; +import com.rit.sucy.version.VersionManager; import com.sucy.skill.log.Logger; import org.bukkit.entity.Player; @@ -50,6 +51,8 @@ public class ActionBar private static Constructor constructPacket; private static Constructor constructText; + private static Object messageType = (byte)2; + private static boolean initialized = false; private static boolean supported = false; @@ -63,7 +66,14 @@ private static void initialize() packet = Reflection.getNMSClass("Packet"); chatBase = Reflection.getNMSClass("IChatBaseComponent"); chatText = Reflection.getNMSClass("ChatComponentText"); - constructPacket = chatPacket.getConstructor(chatBase, byte.class); + if (VersionManager.isVersionAtLeast(11200)) { + Class chatMessageType = Reflection.getNMSClass("ChatMessageType"); + messageType = chatMessageType.getMethod("a", byte.class).invoke(null, messageType); + constructPacket = chatPacket.getConstructor(chatBase, chatMessageType); + } + else { + constructPacket = chatPacket.getConstructor(chatBase, byte.class); + } constructText = chatText.getConstructor(String.class); getHandle = craftPlayer.getDeclaredMethod("getHandle"); @@ -71,6 +81,7 @@ private static void initialize() } catch (Exception ex) { + ex.printStackTrace(); Logger.invalid("Failed to setup Action Bar utility - not supported on pre-1.8 servers"); } } @@ -100,7 +111,7 @@ public static void show(Player player, String message) try { Object text = constructText.newInstance(TextFormatter.colorString(message)); - Object data = constructPacket.newInstance(text, (byte) 2); + Object data = constructPacket.newInstance(text, messageType); Object handle = getHandle.invoke(player); Object connection = Reflection.getValue(handle, "playerConnection"); Method send = Reflection.getMethod(connection, "sendPacket", packet); diff --git a/src/com/sucy/skill/api/util/Buff.java b/src/com/sucy/skill/api/util/Buff.java index d17cc2d8..b019616b 100644 --- a/src/com/sucy/skill/api/util/Buff.java +++ b/src/com/sucy/skill/api/util/Buff.java @@ -26,17 +26,18 @@ */ package com.sucy.skill.api.util; +import org.bukkit.scheduler.BukkitTask; + /** * Represents a buff given to an entity */ public class Buff { - protected BuffType type; - private double value; private boolean percent; private String key; - private int id; + + BukkitTask task; /** * Constructs a new buff @@ -78,14 +79,4 @@ public boolean isPercent() { return percent; } - - public int getId() - { - return id; - } - - public void setId(int id) - { - id = id; - } } diff --git a/src/com/sucy/skill/api/util/BuffData.java b/src/com/sucy/skill/api/util/BuffData.java index e069d78f..560747e0 100644 --- a/src/com/sucy/skill/api/util/BuffData.java +++ b/src/com/sucy/skill/api/util/BuffData.java @@ -26,33 +26,24 @@ */ package com.sucy.skill.api.util; +import com.sucy.skill.SkillAPI; import com.sucy.skill.log.LogType; import com.sucy.skill.log.Logger; -import org.bukkit.Bukkit; import org.bukkit.entity.LivingEntity; -import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; -import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; /** * Represents buffs set on an entity */ public class BuffData { - private static final int MAX_ID = 9999999; - - private final HashMap damageBuffs = new HashMap(); - private final HashMap defenseBuffs = new HashMap(); - private final HashMap skillDamageBuffs = new HashMap(); - private final HashMap skillDefenseBuffs = new HashMap(); - private final HashMap tasks = new HashMap(); + private final Map> buffs = new HashMap>(); private LivingEntity entity; - private Plugin plugin; - private int nextId = 0; /** * Initializes new buff data for the entity @@ -61,245 +52,213 @@ public class BuffData */ public BuffData(LivingEntity entity) { - this.plugin = Bukkit.getPluginManager().getPlugin("SkillAPI"); this.entity = entity; } + public double getMultiplier(final BuffType buffType, final String category) { + return category == null || category.isEmpty() + ? getMultiplier(buffType.name()) + : getMultiplier(buffType.name(), buffType.name() + category); + } + + public double getFlatBonus(final BuffType buffType, final String category) { + return category == null || category.isEmpty() + ? getFlatBonus(buffType.name()) + : getFlatBonus(buffType.name(), buffType.name() + category); + } + /** - * Adds an offensive buff to the entity + * Adds a buff to the buff collection. If a buff already exists with the same + * key, it will be overwritten. * - * @param buff buff to add - * @param ticks ticks to apply the buff for + * @param type type of buff to add + * @param buff buff details + * @param ticks how long to apply the buff for */ - public void addDamageBuff(Buff buff, int ticks) - { - buff.type = BuffType.DAMAGE; - int id = check(buff, damageBuffs); - if (id == -1) - { - damageBuffs.put(nextId, buff); - tasks.put(nextId, new BuffTask(BuffType.DAMAGE, nextId).runTaskLater(plugin, ticks)); - nextId = (nextId + 1) % MAX_ID; - } - else - { - damageBuffs.put(id, buff); - BukkitTask task = tasks.remove(id); - if (task != null) - task.cancel(); - tasks.put(id, new BuffTask(BuffType.DAMAGE, id).runTaskLater(plugin, ticks)); - } + public void addBuff(final BuffType type, final Buff buff, final int ticks) { + doAddBuff(type.name(), buff, ticks); } /** - * Adds a defensive buff to the entity + * Adds a buff to the buff collection. If a buff already exists with the same + * key, it will be overwritten. * - * @param buff buff to add - * @param ticks ticks to apply the buff for + * @param type type of buff to add + * @param category sub category of the type to apply (e.g. damage classification) + * @param buff buff details + * @param ticks how long to apply the buff for */ - public void addDefenseBuff(Buff buff, int ticks) - { - buff.type = BuffType.DEFENSE; - int id = check(buff, defenseBuffs); - if (id == -1) - { - defenseBuffs.put(nextId, buff); - tasks.put(nextId, new BuffTask(BuffType.DEFENSE, nextId).runTaskLater(plugin, ticks)); - nextId = (nextId + 1) % MAX_ID; - } - else - { - defenseBuffs.put(id, buff); - BukkitTask task = tasks.remove(id); - if (task != null) - task.cancel(); - tasks.put(id, new BuffTask(BuffType.DEFENSE, id).runTaskLater(plugin, ticks)); - } + public void addBuff(final BuffType type, final String category, final Buff buff, final int ticks) { + doAddBuff(type.name() + category, buff, ticks); + } + + private void doAddBuff(final String type, final Buff buff, final int ticks) { + final Map typeBuffs = buffs.computeIfAbsent(type, t -> new HashMap<>()); + final Buff conflict = typeBuffs.remove(buff.getKey()); + if (conflict != null) + conflict.task.cancel(); + + typeBuffs.put(buff.getKey(), buff); + buff.task = SkillAPI.schedule(new BuffTask(type, buff.getKey()), ticks); + } + + /** @deprecated use {@link BuffData#addBuff(BuffType, Buff, int)} instead */ + @Deprecated + public void addDamageBuff(Buff buff, int ticks) { + addBuff(BuffType.DAMAGE, buff, ticks); + } + + /** @deprecated use {@link BuffData#addBuff(BuffType, Buff, int)} instead */ + @Deprecated + public void addDefenseBuff(Buff buff, int ticks) { + addBuff(BuffType.DEFENSE, buff, ticks); + } + + /** @deprecated use {@link BuffData#addBuff(BuffType, Buff, int)} instead */ + @Deprecated + public void addSkillDamageBuff(Buff buff, int ticks) { + addBuff(BuffType.SKILL_DAMAGE, buff, ticks); + } + + /** @deprecated use {@link BuffData#addBuff(BuffType, Buff, int)} instead */ + @Deprecated + public void addSkillDefenseBuff(Buff buff, int ticks) { + addBuff(BuffType.SKILL_DEFENSE, buff, ticks); } /** - * Adds an offensive buff to the entity + * Applies all buffs of the given type to the specified value * - * @param buff buff to add - * @param ticks ticks to apply the buff for + * @param type type of buff to apply + * @param value value to modify + * @return value after all buff applications */ - public void addSkillDamageBuff(Buff buff, int ticks) - { - buff.type = BuffType.SKILL_DAMAGE; - int id = check(buff, skillDamageBuffs); - if (id == -1) - { - skillDamageBuffs.put(nextId, buff); - tasks.put(nextId, new BuffTask(BuffType.SKILL_DAMAGE, nextId).runTaskLater(plugin, ticks)); - nextId = (nextId + 1) % MAX_ID; - } - else - { - skillDamageBuffs.put(id, buff); - BukkitTask task = tasks.remove(id); - if (task != null) - task.cancel(); - tasks.put(id, new BuffTask(BuffType.SKILL_DAMAGE, id).runTaskLater(plugin, ticks)); - } + public double apply(final BuffType type, final double value) { + return doApply(value, type.name()); } /** - * Adds a defensive buff to the entity + * Applies all buffs of the given type to the specified value * - * @param buff buff to add - * @param ticks ticks to apply the buff for + * @param type type of buff to apply + * @param category sub category of the buff type to apply (e.g. damage classification) + * @param value value to modify + * @return value after all buff applications */ - public void addSkillDefenseBuff(Buff buff, int ticks) - { - buff.type = BuffType.SKILL_DEFENSE; - int id = check(buff, skillDefenseBuffs); - if (id == -1) - { - skillDefenseBuffs.put(nextId, buff); - tasks.put(nextId, new BuffTask(BuffType.SKILL_DEFENSE, nextId).runTaskLater(plugin, ticks)); - nextId = (nextId + 1) % MAX_ID; + public double apply(final BuffType type, final String category, final double value) { + return category == null || category.length() == 0 + ? doApply(value, type.name()) + : doApply(value, type.name(), type.name() + category); + } + + private double doApply(final double value, final String... types) { + + // Ignore zeroed out values that shouldn't get buffs + if (value <= 0) return value; + + double multiplier = 1; + double bonus = 0; + Logger.log(LogType.BUFF, 1, "Buffs:"); + for (final String type : types) { + final Map typeBuffs = buffs.get(type); + if (typeBuffs == null) { + continue; + } + + for (final Buff buff : typeBuffs.values()) { + if (buff.isPercent()) { + Logger.log(LogType.BUFF, 1, " - x" + buff.getValue()); + multiplier *= buff.getValue(); + } else { + Logger.log(LogType.BUFF, 1, " - +" + buff.getValue()); + bonus += buff.getValue(); + } + } } - else - { - skillDefenseBuffs.put(id, buff); - BukkitTask task = tasks.remove(id); - if (task != null) - task.cancel(); - tasks.put(id, new BuffTask(BuffType.SKILL_DEFENSE, id).runTaskLater(plugin, ticks)); + Logger.log(LogType.BUFF, 1, "Result: x" + multiplier + ", +" + bonus + ", " + value + " -> " + Math.max(0, value * multiplier + bonus)); + + // Negatives aren't well received by bukkit, so return 0 instead + if (multiplier <= 0) return 0; + + return Math.max(0, value * multiplier + bonus); + } + + private double getFlatBonus(final String... types) { + double bonus = 0; + for (final String type : types) { + for (final Buff buff : buffs.getOrDefault(type, Collections.emptyMap()).values()) { + if (!buff.isPercent()) { + bonus += buff.getValue(); + } + } } + // Negatives aren't well received by bukkit, so return 0 instead + return bonus; } - /** - * Checks for buffs with overlapping keys - * - * @param buff new buff to check against - * @param map map to look through - * - * @return ID of overlapping buff or -1 if no conflict - */ - private int check(Buff buff, HashMap map) - { - for (Buff active : map.values()) - { - if (active.getKey().equals(buff.getKey())) - { - return active.getId(); + private double getMultiplier(final String... types) { + double multiplier = 1; + for (final String type : types) { + for (final Buff buff : buffs.getOrDefault(type, Collections.emptyMap()).values()) { + if (buff.isPercent()) { + multiplier *= buff.getValue(); + } } } - return -1; + // Negatives aren't well received by bukkit, so return 0 instead + return Math.max(0, multiplier); } - /** - * Modifies the amount of dealt damage using damage buff - * multipliers and bonuses. - * - * @param damage base damage amount to modify - * - * @return modified damage amount - */ + /** @deprecated use {@link BuffData#apply(BuffType, double)} instead */ + @Deprecated public double modifyDealtDamage(double damage) { - return modify(damageBuffs.values(), damage); + return apply(BuffType.DAMAGE, damage); } - /** - * Modifies the amount of taken damage using defense buff - * multipliers and bonuses. - * - * @param damage base damage amount to modify - * - * @return modified damage amount - */ + /** @deprecated use {@link BuffData#apply(BuffType, double)} instead */ + @Deprecated public double modifyTakenDamage(double damage) { - return modify(defenseBuffs.values(), damage); + return apply(BuffType.DEFENSE, damage); } - /** - * Modifies the amount of dealt damage using damage buff - * multipliers and bonuses. - * - * @param damage base damage amount to modify - * - * @return modified damage amount - */ + /** @deprecated use {@link BuffData#apply(BuffType, double)} instead */ + @Deprecated public double modifySkillDealtDamage(double damage) { - return modify(skillDamageBuffs.values(), damage); + return apply(BuffType.SKILL_DAMAGE, damage); } - /** - * Modifies the amount of taken damage using defense buff - * multipliers and bonuses. - * - * @param damage base damage amount to modify - * - * @return modified damage amount - */ + /** @deprecated use {@link BuffData#apply(BuffType, double)} instead */ + @Deprecated public double modifySkillTakenDamage(double damage) { - return modify(skillDefenseBuffs.values(), damage); - } - - private double modify(Collection buffs, double value) - { - if (value <= 0) - { - return 0; - } - double multiplier = 1; - double bonus = 0; - Logger.log(LogType.BUFF, 1, "Buffs:"); - for (Buff buff : buffs) - { - if (buff.isPercent()) - { - Logger.log(LogType.BUFF, 1, " - x" + buff.getValue()); - multiplier *= buff.getValue(); - } - else - { - Logger.log(LogType.BUFF, 1, " - +" + buff.getValue()); - bonus += buff.getValue(); - } - } - Logger.log(LogType.BUFF, 1, "Result: x" + multiplier + ", +" + bonus + ", " + value + " -> " + Math.max(0, value * multiplier + bonus)); - if (multiplier <= 0) - { - return 0; - } - else - { - return Math.max(0, value * multiplier + bonus); - } + return apply(BuffType.SKILL_DEFENSE, damage); } /** * Clears all buffs on the entity and stops associated tasks. */ - public void clear() - { - damageBuffs.clear(); - defenseBuffs.clear(); - skillDamageBuffs.clear(); - skillDefenseBuffs.clear(); - for (BukkitTask task : tasks.values()) - { - task.cancel(); + public void clear() { + for (final Map typeBuffs : buffs.values()) { + for (final Buff buff : typeBuffs.values()) { + buff.task.cancel(); + } } - tasks.clear(); + buffs.clear(); BuffManager.clearData(entity); } private class BuffTask extends BukkitRunnable { - private BuffType type; - private int id; + private final String type; + private final String key; - public BuffTask(BuffType type, int id) + BuffTask(final String type, final String key) { this.type = type; - this.id = id; + this.key = key; } @Override @@ -310,24 +269,16 @@ public void run() BuffManager.clearData(entity); return; } - switch (type) - { - case DAMAGE: - damageBuffs.remove(id); - break; - case DEFENSE: - defenseBuffs.remove(id); - break; - case SKILL_DAMAGE: - skillDamageBuffs.remove(id); - break; - case SKILL_DEFENSE: - skillDefenseBuffs.remove(id); - } - tasks.remove(id); - if (damageBuffs.size() + defenseBuffs.size() == 0) - { - BuffManager.clearData(entity); + + final Map typeBuffs = buffs.get(type); + typeBuffs.remove(key); + + // Clean up buff data if the entity doesn't hold onto any buffs + if (typeBuffs.size() == 0) { + buffs.remove(type); + if (buffs.size() == 0) { + BuffManager.clearData(entity); + } } } } diff --git a/src/com/sucy/skill/api/util/BuffManager.java b/src/com/sucy/skill/api/util/BuffManager.java index 18413a25..09fab902 100644 --- a/src/com/sucy/skill/api/util/BuffManager.java +++ b/src/com/sucy/skill/api/util/BuffManager.java @@ -46,8 +46,7 @@ public class BuffManager * * @return the buff data for the entity */ - public static BuffData getBuffData(LivingEntity entity) - { + public static BuffData getBuffData(final LivingEntity entity) { return getBuffData(entity, true); } @@ -61,14 +60,10 @@ public static BuffData getBuffData(LivingEntity entity) * * @return the buff data for an enemy */ - public static BuffData getBuffData(LivingEntity entity, boolean create) - { - if (entity == null) - { - return null; - } - if (!data.containsKey(entity.getUniqueId()) && create) - { + public static BuffData getBuffData(final LivingEntity entity, final boolean create) { + if (entity == null) return null; + + if (!data.containsKey(entity.getUniqueId()) && create) { data.put(entity.getUniqueId(), new BuffData(entity)); } return data.get(entity.getUniqueId()); @@ -79,15 +74,11 @@ public static BuffData getBuffData(LivingEntity entity, boolean create) * * @param entity entity to clear the buffs for */ - public static void clearData(LivingEntity entity) - { - if (entity == null) - { - return; - } - BuffData result = data.remove(entity.getUniqueId()); - if (result != null) - { + public static void clearData(final LivingEntity entity) { + if (entity == null) return; + + final BuffData result = data.remove(entity.getUniqueId()); + if (result != null) { result.clear(); } } @@ -96,108 +87,51 @@ public static void clearData(LivingEntity entity) * Adds an offensive buff to the entity * * @param entity entity to give the buff to + * @param type type of buff to add * @param buff buff to add * @param ticks ticks to apply the buff for */ - public static void addDamageBuff(LivingEntity entity, Buff buff, int ticks) - { - if (entity == null) - { - return; - } - getBuffData(entity, true).addDamageBuff(buff, ticks); + public static void addBuff(final LivingEntity entity, final BuffType type, final Buff buff, final int ticks) { + if (entity == null) return; + getBuffData(entity, true).addBuff(type, buff, ticks); } /** - * Adds a defensive buff to the entity + * Adds an offensive buff to the entity * * @param entity entity to give the buff to + * @param type type of buff to add + * @param category sub category of the buff to add * @param buff buff to add * @param ticks ticks to apply the buff for */ - public static void addDefenseBuff(LivingEntity entity, Buff buff, int ticks) - { - if (entity == null) - { - return; - } - getBuffData(entity, true).addDefenseBuff(buff, ticks); + public static void addBuff(final LivingEntity entity, final BuffType type, final String category, final Buff buff, final int ticks) { + if (entity == null) return; + getBuffData(entity, true).addBuff(type, category, buff, ticks); } - /** - * Adds an offensive buff to the entity - * - * @param entity entity to give the buff to - * @param buff buff to add - * @param ticks ticks to apply the buff for - */ - public static void addSkillDamageBuff(LivingEntity entity, Buff buff, int ticks) - { - if (entity == null) - { - return; - } - getBuffData(entity, true).addSkillDamageBuff(buff, ticks); + /** @deprecated use {@link BuffManager#addBuff(LivingEntity, BuffType, Buff, int)} instead */ + @Deprecated + public static void addDamageBuff(final LivingEntity entity, final Buff buff, final int ticks) { + addBuff(entity, BuffType.DAMAGE, buff, ticks); } - /** - * Adds a defensive buff to the entity - * - * @param entity entity to give the buff to - * @param buff buff to add - * @param ticks ticks to apply the buff for - */ - public static void addSkillDefenseBuff(LivingEntity entity, Buff buff, int ticks) - { - if (entity == null) - { - return; - } - getBuffData(entity, true).addSkillDefenseBuff(buff, ticks); + /** @deprecated use {@link BuffManager#addBuff(LivingEntity, BuffType, Buff, int)} instead */ + @Deprecated + public static void addDefenseBuff(final LivingEntity entity, final Buff buff, final int ticks) { + addBuff(entity, BuffType.DEFENSE, buff, ticks); } - /** - * Modifies the amount of dealt damage using damage buff - * multipliers and bonuses. - * - * @param entity entity to use the data of - * @param damage base damage amount to modify - * - * @return modified damage amount - */ - public static double modifyDealtDamage(LivingEntity entity, double damage) - { - BuffData data = getBuffData(entity, false); - if (data == null) - { - return damage; - } - else - { - return data.modifyDealtDamage(damage); - } + /** @deprecated use {@link BuffManager#addBuff(LivingEntity, BuffType, Buff, int)} instead */ + @Deprecated + public static void addSkillDamageBuff(final LivingEntity entity, final Buff buff, final int ticks) { + addBuff(entity, BuffType.SKILL_DAMAGE, buff, ticks); } - /** - * Modifies the amount of taken damage using defense buff - * multipliers and bonuses. - * - * @param entity entity to use the data of - * @param damage base damage amount to modify - * - * @return modified damage amount - */ - public static double modifyTakenDefense(LivingEntity entity, double damage) - { - BuffData data = getBuffData(entity, false); - if (data == null) - { - return damage; - } - else - { - return data.modifyTakenDamage(damage); - } + /** @deprecated use {@link BuffManager#addBuff(LivingEntity, BuffType, Buff, int)} instead */ + @Deprecated + public static void addSkillDefenseBuff(final LivingEntity entity, final Buff buff, final int ticks) { + addBuff(entity, BuffType.SKILL_DEFENSE, buff, ticks); } /** @@ -205,42 +139,55 @@ public static double modifyTakenDefense(LivingEntity entity, double damage) * multipliers and bonuses. * * @param entity entity to use the data of - * @param damage base damage amount to modify + * @param type type of buffs to apply + * @param amount base amount to modify * - * @return modified damage amount + * @return modified number */ - public static double modifySkillDealtDamage(LivingEntity entity, double damage) + public static double apply(final LivingEntity entity, final BuffType type, final double amount) { - BuffData data = getBuffData(entity, false); - if (data == null) - { - return damage; - } - else - { - return data.modifySkillDealtDamage(damage); - } + final BuffData data = getBuffData(entity, false); + return data == null ? amount : data.apply(type, amount); } /** - * Modifies the amount of taken damage using defense buff + * Modifies the amount of dealt damage using damage buff * multipliers and bonuses. * * @param entity entity to use the data of - * @param damage base damage amount to modify + * @param type type of buffs to apply + * @param category sub category of the buffs to apply + * @param amount base amount to modify * - * @return modified damage amount + * @return modified number */ - public static double modifySkillTakenDefense(LivingEntity entity, double damage) + public static double apply(final LivingEntity entity, final BuffType type, final String category, final double amount) { - BuffData data = getBuffData(entity, false); - if (data == null) - { - return damage; - } - else - { - return data.modifySkillTakenDamage(damage); - } + final BuffData data = getBuffData(entity, false); + return data == null ? amount : data.apply(type, category, amount); + } + + /** @deprecated use {@link BuffManager#apply(LivingEntity, BuffType, double)} instead */ + @Deprecated + public static double modifyDealtDamage(final LivingEntity entity, final double damage) { + return apply(entity, BuffType.DAMAGE, damage); + } + + /** @deprecated use {@link BuffManager#apply(LivingEntity, BuffType, double)} instead */ + @Deprecated + public static double modifyTakenDefense(final LivingEntity entity, final double damage) { + return apply(entity, BuffType.DEFENSE, damage); + } + + /** @deprecated use {@link BuffManager#apply(LivingEntity, BuffType, double)} instead */ + @Deprecated + public static double modifySkillDealtDamage(LivingEntity entity, double damage) { + return apply(entity, BuffType.SKILL_DAMAGE, damage); + } + + /** @deprecated use {@link BuffManager#apply(LivingEntity, BuffType, double)} instead */ + @Deprecated + public static double modifySkillTakenDefense(LivingEntity entity, double damage) { + return apply(entity, BuffType.SKILL_DEFENSE, damage); } } diff --git a/src/com/sucy/skill/api/util/BuffType.java b/src/com/sucy/skill/api/util/BuffType.java index 087a0ab1..af39915a 100644 --- a/src/com/sucy/skill/api/util/BuffType.java +++ b/src/com/sucy/skill/api/util/BuffType.java @@ -31,5 +31,6 @@ public enum BuffType DAMAGE, DEFENSE, SKILL_DAMAGE, - SKILL_DEFENSE + SKILL_DEFENSE, + HEALING } diff --git a/src/com/sucy/skill/api/util/DamageLoreRemover.java b/src/com/sucy/skill/api/util/DamageLoreRemover.java index 0d61eb60..d58c8696 100644 --- a/src/com/sucy/skill/api/util/DamageLoreRemover.java +++ b/src/com/sucy/skill/api/util/DamageLoreRemover.java @@ -105,8 +105,7 @@ public static ItemStack removeAttackDmg(ItemStack item) Object nbtCompound = GET_TAG.invoke(nmsStack); // Disable durability if needed - if (item.getType().getMaxDurability() > 0 - && item.getDurability() > 0) + if (item.getType().getMaxDurability() > 0) { SET_BOOL.invoke(nbtCompound, "Unbreakable", true); SET_INT.invoke(nbtCompound, "HideFlags", 4); diff --git a/src/com/sucy/skill/api/util/Data.java b/src/com/sucy/skill/api/util/Data.java index b1590654..48bd6ed4 100644 --- a/src/com/sucy/skill/api/util/Data.java +++ b/src/com/sucy/skill/api/util/Data.java @@ -26,105 +26,46 @@ */ package com.sucy.skill.api.util; -import com.rit.sucy.config.CustomFilter; import com.rit.sucy.config.parse.DataSection; import com.rit.sucy.text.TextFormatter; -import com.sucy.skill.log.Logger; import org.bukkit.ChatColor; import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +//import org.bukkit.material.MaterialData; +import java.util.ArrayList; import java.util.List; /** * Helper class for managing loading/saving certain data */ -public class Data -{ - private static final String MAT = "icon"; - private static final String DATA = "icon-data"; - private static final String LORE = "icon-lore"; +public class Data { + private static final String MAT = "icon"; + private static final String DATA = "icon-data"; + private static final String DURABILITY = "icon-durability"; + private static final String LORE = "icon-lore"; - /** - * Parses a material from a string - * - * @param name material name string - * - * @return parsed material or null if invalid - */ - public static Material parseMat(String name) - { - try - { - return Material.valueOf(name.toUpperCase().replace(' ', '_')); - } - catch (Exception ex) - { - Logger.invalid("Failed to parse " + name); - return null; - } - } - - /** - * Serializes an item icon into a configuration - * - * @param item item to serialize - * @param config config to serialize into - */ - public static void serializeIcon(ItemStack item, ConfigurationSection config) - { - config.set(MAT, item.getType().name()); - config.set(DATA, item.getData().getData()); - if (item.hasItemMeta() && item.getItemMeta().hasDisplayName() && item.getItemMeta().hasLore()) - { - List lore = item.getItemMeta().getLore(); - lore.add(0, item.getItemMeta().getDisplayName()); - int count = lore.size(); - for (int i = 0; i < count; i++) - { - lore.add(lore.remove(0).replace(ChatColor.COLOR_CHAR, '&')); + private static ItemStack parse(final String mat, final short dur, final byte data, final List lore) { + try { + Material material = Material.matchMaterial(mat); + if (material == null) { + material = Material.JACK_O_LANTERN; } - config.set(LORE, lore); - } - } - /** - * Parses an item icon from a configuration - * - * @param config config to load from - * - * @return parsed item icon or a plain Jack O' Lantern if invalid - */ - public static ItemStack parseIcon(ConfigurationSection config, CustomFilter... filters) - { - if (config == null) - { - return new ItemStack(Material.JACK_O_LANTERN); - } + final ItemStack item = new ItemStack(material); + //item.setData(new MaterialData(material, data)); + item.setDurability(dur); - try - { - ItemStack item = new ItemStack(parseMat(config.getString(MAT, "JACK_O_LANTERN"))); - short value = (short) config.getInt(DATA, 0); - if (config.contains(LORE)) - { - List lore = TextFormatter.colorStringList(config.getStringList(LORE)); - if (lore.size() == 0) - { - return item; - } - ItemMeta meta = item.getItemMeta(); - meta.setDisplayName(lore.remove(0)); - meta.setLore(lore); + if (lore != null && !lore.isEmpty()) { + final List colored = TextFormatter.colorStringList(lore); + final ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(colored.remove(0)); + meta.setLore(colored); item.setItemMeta(meta); } - item.setDurability(value); - return item; - } - catch (Exception ex) - { + return DamageLoreRemover.removeAttackDmg(item); + } catch (final Exception ex) { return new ItemStack(Material.JACK_O_LANTERN); } } @@ -135,19 +76,17 @@ public static ItemStack parseIcon(ConfigurationSection config, CustomFilter... f * @param item item to serialize * @param config config to serialize into */ - public static void serializeIcon(ItemStack item, DataSection config) - { + public static void serializeIcon(ItemStack item, DataSection config) { config.set(MAT, item.getType().name()); - config.set(DATA, item.getData().getData()); - if (item.hasItemMeta() && item.getItemMeta().hasDisplayName() && item.getItemMeta().hasLore()) - { + config.set(DURABILITY, item.getDurability()); + config.set(DATA, item.getData()); + + if (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) { List lore = item.getItemMeta().getLore(); + if (lore == null) { lore = new ArrayList<>(); } lore.add(0, item.getItemMeta().getDisplayName()); int count = lore.size(); - for (int i = 0; i < count; i++) - { - lore.add(lore.remove(0).replace(ChatColor.COLOR_CHAR, '&')); - } + for (int i = 0; i < count; i++) { lore.add(lore.remove(0).replace(ChatColor.COLOR_CHAR, '&')); } config.set(LORE, lore); } } @@ -159,36 +98,16 @@ public static void serializeIcon(ItemStack item, DataSection config) * * @return parsed item icon or a plain Jack O' Lantern if invalid */ - public static ItemStack parseIcon(DataSection config) - { - if (config == null) - { + public static ItemStack parseIcon(DataSection config) { + if (config == null) { return new ItemStack(Material.JACK_O_LANTERN); } - try - { - ItemStack item = new ItemStack(parseMat(config.getString(MAT, "JACK_O_LANTERN"))); - short value = (short) config.getInt(DATA, 0); - if (config.has(LORE)) - { - List lore = TextFormatter.colorStringList(config.getList(LORE)); - if (lore.size() == 0) - { - return item; - } - ItemMeta meta = item.getItemMeta(); - meta.setDisplayName(lore.remove(0)); - meta.setLore(lore); - item.setItemMeta(meta); - } - item.setDurability(value); - return item; - } - catch (Exception ex) - { - ex.printStackTrace(); - return new ItemStack(Material.JACK_O_LANTERN); - } + final int data = config.getInt(DATA, 0); + return parse( + config.getString(MAT, "JACK_O_LANTERN"), + (short) config.getInt(DURABILITY, data), + (byte) data, + config.getList(LORE, null)); } } diff --git a/src/com/sucy/skill/api/util/ItemSerializer.java b/src/com/sucy/skill/api/util/ItemSerializer.java new file mode 100644 index 00000000..d89d9185 --- /dev/null +++ b/src/com/sucy/skill/api/util/ItemSerializer.java @@ -0,0 +1,354 @@ +/** + * SkillAPI + * com.sucy.skill.api.util.ItemSerializer + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.util; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.rit.sucy.reflect.Reflection; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +// Based on the thread https://bukkit.org/threads/help-with-serialized-nbttagcompounds.116335/ +public class ItemSerializer { + + private static boolean initialized = false; + + private static Constructor nbtTagListConstructor; + private static Constructor nbtTagCompoundConstructor; + private static Constructor craftItemConstructor; + private static Constructor craftItemNMSConstructor; + private static Constructor nmsItemConstructor; + + private static Method itemStack_save; + private static Method nbtTagList_add; + private static Method nbtTagList_size; + private static Method nbtTagList_get; + private static Method nbtCompressedStreamTools_write; + private static Method nbtCompressedStreamTools_read; + private static Method nbtTagCompound_set; + private static Method nbtTagCompound_getList; + private static Method nbtTagCompound_isEmpty; + + private static Field craftItemStack_getHandle; + + private static void initialize() { + if (initialized) + return; + + initialized = true; + + try { + String nms = Reflection.getNMSPackage(); + String craft = Reflection.getCraftPackage(); + + Class craftItemStack = Class.forName(craft + "inventory.CraftItemStack"); + Class nmsItemStack = Class.forName(nms + "ItemStack"); + craftItemConstructor = craftItemStack.getDeclaredConstructor(ItemStack.class); + craftItemConstructor.setAccessible(true); + craftItemNMSConstructor = craftItemStack.getDeclaredConstructor(nmsItemStack); + craftItemNMSConstructor.setAccessible(true); + craftItemStack_getHandle = craftItemStack.getDeclaredField("handle"); + craftItemStack_getHandle.setAccessible(true); + + Class nbtTagCompound = Class.forName(nms + "NBTTagCompound"); + Class nbtTagList = Class.forName(nms + "NBTTagList"); + Class nbtBase = Class.forName(nms + "NBTBase"); + Class nbtCompressedStreamTools = Class.forName(nms + "NBTCompressedStreamTools"); + nmsItemConstructor = nmsItemStack.getDeclaredConstructor(nbtTagCompound); + nmsItemConstructor.setAccessible(true); + nbtTagCompoundConstructor = nbtTagCompound.getConstructor(); + nbtTagListConstructor = nbtTagList.getConstructor(); + nbtTagCompound_set = nbtTagCompound.getDeclaredMethod("set", String.class, nbtBase); + nbtTagCompound_getList = nbtTagCompound.getDeclaredMethod("getList", String.class, int.class); + nbtTagCompound_isEmpty = nbtTagCompound.getDeclaredMethod("isEmpty"); + itemStack_save = nmsItemStack.getDeclaredMethod("save", nbtTagCompound); + nbtTagList_add = nbtTagList.getDeclaredMethod("add", nbtBase); + nbtTagList_size = nbtTagList.getDeclaredMethod("size"); + nbtTagList_get = nbtTagList.getDeclaredMethod("get", int.class); + nbtCompressedStreamTools_write = nbtCompressedStreamTools.getDeclaredMethod("a", nbtTagCompound, DataOutput.class); + nbtCompressedStreamTools_read = nbtCompressedStreamTools.getDeclaredMethod("a", DataInputStream.class); + } + catch (Exception ex) { + System.out.println("Server doesn't support NBT serialization - resorting to a less complete implementation"); + } + } + + public static String toBase64(ItemStack[] items) { + if (items == null) return null; + + initialize(); + if (nbtCompressedStreamTools_read == null) { + return basicSerialize(items); + } + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutput = new DataOutputStream(outputStream); + Object itemList = nbtTagListConstructor.newInstance(); + + // Save every element in the list + for (ItemStack item : items) { + Object outputObject = nbtTagCompoundConstructor.newInstance(); + Object craft = getCraftVersion(item); + + // Convert the item stack to a NBT compound + if (craft != null) + itemStack_save.invoke(craftItemStack_getHandle.get(craft), outputObject); + nbtTagList_add.invoke(itemList, outputObject); + } + + Object wrapper = nbtTagCompoundConstructor.newInstance(); + nbtTagCompound_set.invoke(wrapper, "i", itemList); + + nbtCompressedStreamTools_write.invoke(null, wrapper, dataOutput); + + // Serialize that array + return new BigInteger(1, outputStream.toByteArray()).toString(32); + } + catch (Exception ex) { + return null; + } + } + + public static ItemStack[] fromBase64(String data) { + if (data == null) return null; + + initialize(); + if (data.indexOf(';') >= 0) { + return basicDeserialize(data); + } + try { + ByteArrayInputStream inputStream = new ByteArrayInputStream(new BigInteger(data, 32).toByteArray()); + DataInputStream dataInputStream = new DataInputStream(inputStream); + Object wrapper = nbtCompressedStreamTools_read.invoke(null, dataInputStream); + Object itemList = nbtTagCompound_getList.invoke(wrapper, "i", 10); + ItemStack[] items = new ItemStack[(Integer)nbtTagList_size.invoke(itemList)]; + + for (int i = 0; i < items.length; i++) { + Object inputObject = nbtTagList_get.invoke(itemList, i); + + // IsEmpty + if (!(Boolean)nbtTagCompound_isEmpty.invoke(inputObject)) { + items[i] = (ItemStack)craftItemNMSConstructor.newInstance(nmsItemConstructor.newInstance(inputObject)); + } + } + + // Serialize that array + return items; + } + catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + private static Object getCraftVersion(ItemStack stack) throws Exception { + if (stack == null) + return null; + else if (stack.getClass() == ItemStack.class) + return craftItemConstructor.newInstance(stack); + else + return stack; + } + + private static String basicSerialize(ItemStack[] items) + { + StringBuilder builder = new StringBuilder(); + builder.append(items.length); + builder.append(';'); + for (int i = 0; i < items.length; i++) + { + ItemStack is = items[i]; + if (is != null) + { + builder.append(i); + builder.append('#'); + + String isType = String.valueOf(is.getType()); //.getId()); + builder.append("t@"); + builder.append(isType); + + if (is.getDurability() != 0) + { + String isDurability = String.valueOf(is.getDurability()); + builder.append(":d@"); + builder.append(isDurability); + } + + if (is.getAmount() != 1) + { + String isAmount = String.valueOf(is.getAmount()); + builder.append(":a@"); + builder.append(isAmount); + } + + Map isEnch = is.getEnchantments(); + if (isEnch.size() > 0) + { + for (Map.Entry ench : isEnch.entrySet()) + { + builder.append(":e@"); + builder.append(ENCHANT_IDS.get(ench.getKey().getName())); + builder.append('@'); + builder.append(ench.getValue()); + } + } + + ItemMeta meta = is.getItemMeta(); + if (meta.hasDisplayName()) { + builder.append(":n@"); + builder.append(meta.getDisplayName().replaceAll("[:@#;]", "")); + } + + if (meta.hasLore()) { + for (String line : meta.getLore()) { + builder.append(":l@"); + builder.append(line.replaceAll("[:;@#]", "")); + } + } + + builder.append(';'); + } + } + return builder.toString(); + } + + private static ItemStack[] basicDeserialize(String invString) + { + String[] serializedBlocks = invString.split(";"); + if (serializedBlocks.length == 0) + return null; + String invInfo = serializedBlocks[0]; + ItemStack[] deserializedInventory = new ItemStack[Integer.valueOf(invInfo)]; + + for (int i = 1; i <= deserializedInventory.length && i < serializedBlocks.length; i++) + { + String[] serializedBlock = serializedBlocks[i].split("#"); + int stackPosition = Integer.valueOf(serializedBlock[0]); + + if (stackPosition >= deserializedInventory.length) + { + continue; + } + + ItemStack is = null; + Boolean createdItemStack = false; + + String[] serializedItemStack = serializedBlock[1].split(":"); + for (String itemInfo : serializedItemStack) + { + String[] itemAttribute = itemInfo.split("@"); + if (itemAttribute[0].equals("t")) + { + int id = Integer.valueOf(itemAttribute[1]); + if (id >= 2256) id -= 2267 - Material.values().length; + final Material mat = Material.values()[id]; + is = new ItemStack(mat); + createdItemStack = true; + } + else if (itemAttribute[0].equals("d") && createdItemStack) + { + is.setDurability(Short.valueOf(itemAttribute[1])); + } + else if (itemAttribute[0].equals("a") && createdItemStack) + { + is.setAmount(Integer.valueOf(itemAttribute[1])); + } + else if (itemAttribute[0].equals("e") && createdItemStack) + { + final String name = ENCHANT_IDS.inverse().getOrDefault(Integer.valueOf(itemAttribute[1]), "OXYGEN"); + is.addUnsafeEnchantment(Enchantment.getByName(name), Integer.valueOf(itemAttribute[2])); + } + else if (itemAttribute[0].equals("n") && createdItemStack) + { + ItemMeta meta = is.getItemMeta(); + meta.setDisplayName(itemAttribute[1]); + is.setItemMeta(meta); + } + else if (itemAttribute[0].equals("l") && createdItemStack) + { + ItemMeta meta = is.getItemMeta(); + List lore = meta.getLore(); + if (lore == null) lore = new ArrayList<>(); + lore.add(itemAttribute[1]); + meta.setLore(lore); + is.setItemMeta(meta); + } + } + deserializedInventory[stackPosition] = is; + } + + return deserializedInventory; + } + + private static final BiMap ENCHANT_IDS = ImmutableBiMap.builder() + .put("PROTECTION_ENVIRONMENTAL", 0) + .put("PROTECTION_FIRE", 1) + .put("PROTECTION_FALL", 2) + .put("PROTECTION_EXPLOSIONS", 3) + .put("PROTECTION_PROJECTILE", 4) + .put("OXYGEN", 5) + .put("WATER_WORKER", 6) + .put("THORNS", 7) + .put("DEPTH_STRIDER", 8) + .put("FROST_WALKER", 9) + .put("BINDING_CURSE", 10) + .put("DAMAGE_ALL", 16) + .put("DAMAGE_UNDEAD", 17) + .put("DAMAGE_ARTHROPODS", 18) + .put("KNOCKBACK", 19) + .put("FIRE_ASPECT", 20) + .put("LOOT_BONUS_MOBS", 21) + .put("SWEEPING_EDGE", 22) + .put("DIG_SPEED", 32) + .put("SILK_TOUCH", 33) + .put("DURABILITY", 34) + .put("LOOT_BONUS_BLOCKS", 35) + .put("ARROW_DAMAGE", 48) + .put("ARROW_KNOCKBACK", 49) + .put("ARROW_FIRE", 50) + .put("ARROW_INFINITE", 51) + .put("LUCK", 61) + .put("LURE", 62) + .put("MENDING", 70) + .put("VANISHING_CURSE", 71) + .put("LOYALTY", 80) + .put("IMPALING", 81) + .put("RIPTIDE", 82) + .put("CHANNELING", 83) + .build(); +} diff --git a/src/com/sucy/skill/api/util/Nearby.java b/src/com/sucy/skill/api/util/Nearby.java new file mode 100644 index 00000000..4f8a2661 --- /dev/null +++ b/src/com/sucy/skill/api/util/Nearby.java @@ -0,0 +1,170 @@ +/** + * SkillAPI + * com.sucy.skill.api.util.Nearby + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.api.util; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fetches nearby entities by going through possible chunks + * instead of all entities in a world + */ +public class Nearby +{ + /** + * Gets entities nearby a location using a given radius + * + * @param loc location centered around + * @param radius radius to get within + * + * @return nearby entities + */ + public static List getNearby(Location loc, double radius) + { + List result = new ArrayList(); + + int minX = (int) (loc.getX() - radius) >> 4; + int maxX = (int) (loc.getX() + radius) >> 4; + int minZ = (int) (loc.getZ() - radius) >> 4; + int maxZ = (int) (loc.getZ() + radius) >> 4; + + radius *= radius; + + for (int i = minX; i <= maxX; i++) + for (int j = minZ; j <= maxZ; j++) + for (Entity entity : loc.getWorld().getChunkAt(i, j).getEntities()) + if (entity.getLocation().distanceSquared(loc) < radius) + result.add(entity); + + return result; + } + + /** + * Fetches entities nearby a location using a given radius + * + * @param loc location centered around + * @param radius radius to get within + * + * @return nearby entities + */ + public static List getLivingNearby(Location loc, double radius) { + return getLivingNearby(null, loc, radius); + } + + private static List getLivingNearby(Entity source, Location loc, double radius) { + List result = new ArrayList(); + + int minX = (int) (loc.getX() - radius) >> 4; + int maxX = (int) (loc.getX() + radius) >> 4; + int minZ = (int) (loc.getZ() - radius) >> 4; + int maxZ = (int) (loc.getZ() + radius) >> 4; + + radius *= radius; + + for (int i = minX; i <= maxX; i++) + for (int j = minZ; j <= maxZ; j++) + for (Entity entity : loc.getWorld().getChunkAt(i, j).getEntities()) + if (entity != source + && entity instanceof LivingEntity + && entity.getWorld() == loc.getWorld() + && entity.getLocation().distanceSquared(loc) < radius) + result.add((LivingEntity) entity); + + return result; + } + + /** + * Gets entities nearby a location using a given radius + * + * @param entity entity to get nearby ones for + * @param radius radius to get within + * + * @return nearby entities + */ + public static List getNearby(Entity entity, double radius) + { + return getNearby(entity.getLocation(), radius); + } + + /** + * Fetches entities nearby a location using a given radius + * + * @param entity entity to get nearby ones for + * @param radius radius to get within + * + * @return nearby entities + */ + public static List getLivingNearby(Entity entity, double radius) + { + return getLivingNearby(entity, entity.getLocation(), radius); + } + + public static List getNearbyBox(Location loc, double radius) + { + List result = new ArrayList(); + + int minX = (int) (loc.getX() - radius) >> 4; + int maxX = (int) (loc.getX() + radius) >> 4; + int minZ = (int) (loc.getZ() - radius) >> 4; + int maxZ = (int) (loc.getZ() + radius) >> 4; + + for (int i = minX; i <= maxX; i++) + for (int j = minZ; j <= maxZ; j++) + for (Entity entity : loc.getWorld().getChunkAt(i, j).getEntities()) + if (boxDistance(entity.getLocation(), loc) < radius) + result.add(entity); + + return result; + } + + public static List getLivingNearbyBox(Location loc, double radius) + { + List result = new ArrayList(); + + int minX = (int) (loc.getX() - radius) >> 4; + int maxX = (int) (loc.getX() + radius) >> 4; + int minZ = (int) (loc.getZ() - radius) >> 4; + int maxZ = (int) (loc.getZ() + radius) >> 4; + + for (int i = minX; i <= maxX; i++) + for (int j = minZ; j <= maxZ; j++) + for (Entity entity : loc.getWorld().getChunkAt(i, j).getEntities()) + if (entity instanceof LivingEntity && boxDistance(entity.getLocation(), loc) < radius) + result.add((LivingEntity) entity); + + return result; + } + + private static double boxDistance(Location loc1, Location loc2) + { + return Math.max(Math.max(Math.abs(loc1.getX() - loc2.getX()), Math.abs(loc1.getY() - loc2.getY())), Math.abs(loc1.getZ() - loc2.getZ())); + } +} diff --git a/src/com/sucy/skill/api/util/NumberParser.java b/src/com/sucy/skill/api/util/NumberParser.java deleted file mode 100644 index 5fde91c3..00000000 --- a/src/com/sucy/skill/api/util/NumberParser.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * SkillAPI - * com.sucy.skill.api.util.NumberParser - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Steven Sucy - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.sucy.skill.api.util; - -/** - * Handles number parsing for various locales - */ -public class NumberParser -{ - /** - * Parses an integer value from a string - * - * @param value string to parse - * - * @return integer value - */ - public static int parseInt(String value) - { - int n = 0; - int val; - boolean negative = false; - - int i = 0; - char c = value.charAt(i); - - // Negative sign in front - if (c == '-') - { - negative = true; - i++; - } - - // Positive sign in front - else if (c == '+') - { - i++; - } - - // Read in digits - for (; i < value.length(); i++) - { - c = value.charAt(i); - val = (int) c - 48; - if (val < 0 || val > 9) - error(value); - n *= 10; - n += val; - } - return negative ? -n : n; - } - - /** - * Parses a double value from a string - * - * @param value string to parse - * - * @return double value - */ - public static double parseDouble(String value) - { - double n = 0; - double d = 0.1; - int val; - boolean decimal = false; - boolean negative = false; - int i = 0; - char c = value.charAt(i); - - // Negative sign in front - if (c == '-') - { - negative = true; - i++; - } - - // Positive sign in front - else if (c == '+') - { - i++; - } - - for (; i < value.length(); i++) - { - c = value.charAt(i); - switch (c) - { - // Start the decimal portion - case '.': - case ',': - if (decimal) - error(value); - decimal = true; - break; - - // Add digits - default: - val = (int) c - 48; - if (val < 0 || val > 9) - error(value); - if (decimal) - { - n += d * val; - d *= 0.1; - } - else - { - n *= 10; - n += val; - } - break; - } - } - return negative ? -n : n; - } - - /** - * Throws an error when parsing numbers - * - * @param value string being parsed - */ - private static void error(String value) - { - throw new NumberFormatException("Invalid Number: " + value); - } -} diff --git a/src/com/sucy/skill/api/util/ParticleHelper.java b/src/com/sucy/skill/api/util/ParticleHelper.java index a5b567ea..ac443c08 100644 --- a/src/com/sucy/skill/api/util/ParticleHelper.java +++ b/src/com/sucy/skill/api/util/ParticleHelper.java @@ -26,9 +26,12 @@ */ package com.sucy.skill.api.util; +import com.google.common.collect.ImmutableSet; import com.rit.sucy.reflect.Particle; +import com.rit.sucy.version.VersionManager; import com.sucy.skill.api.Settings; import com.sucy.skill.api.enums.Direction; +import com.sucy.skill.api.particle.SpigotParticles; import com.sucy.skill.log.Logger; import org.bukkit.Effect; import org.bukkit.EntityEffect; @@ -41,12 +44,12 @@ import java.util.HashMap; import java.util.Random; +import java.util.Set; /** * Helper class for playing particles via config strings in various ways. */ -public class ParticleHelper -{ +public class ParticleHelper { /** * Settings key for the arrangement type of particles */ @@ -130,8 +133,7 @@ public class ParticleHelper * @param loc location to play the effect * @param effect entity effect to play */ - public static void play(Location loc, EntityEffect effect) - { + public static void play(Location loc, EntityEffect effect) { Wolf wolf = (Wolf) loc.getWorld().spawnEntity(loc, EntityType.WOLF); wolf.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, 100, 100)); wolf.playEffect(effect); @@ -144,46 +146,32 @@ public static void play(Location loc, EntityEffect effect) * @param loc location to center the effect around * @param settings data to play the particles with */ - public static void play(Location loc, Settings settings) - { + public static void play(Location loc, Settings settings) { String particle = settings.getString(PARTICLE_KEY, "invalid"); - if (settings.has(ARRANGEMENT_KEY)) - { + if (settings.has(ARRANGEMENT_KEY)) { int level = settings.getInt(LEVEL, 1); double radius = settings.getAttr(RADIUS_KEY, level, 3.0); int amount = (int) settings.getAttr(PARTICLES_KEY, level, 10); String arrangement = settings.getString(ARRANGEMENT_KEY).toLowerCase(); - if (arrangement.equals("circle")) - { + if (arrangement.equals("circle")) { Direction dir = null; - if (settings.has(DIRECTION_KEY)) - { - try - { + if (settings.has(DIRECTION_KEY)) { + try { dir = Direction.valueOf(settings.getString(DIRECTION_KEY)); - } - catch (Exception ex) - { /* Use default value */ } + } catch (Exception ex) { /* Use default value */ } } - if (dir == null) - { + if (dir == null) { dir = Direction.XZ; } fillCircle(loc, particle, settings, radius, amount, dir); - } - else if (arrangement.equals("sphere")) - { + } else if (arrangement.equals("sphere")) { fillSphere(loc, particle, settings, radius, amount); - } - else if (arrangement.equals("hemisphere")) - { + } else if (arrangement.equals("hemisphere")) { fillHemisphere(loc, particle, settings, radius, amount); } - } - else - { + } else { play(loc, particle, settings); } } @@ -195,62 +183,59 @@ else if (arrangement.equals("hemisphere")) * @param particle particle to play * @param settings data to play the particle with */ - public static void play(Location loc, String particle, Settings settings) - { + public static void play(Location loc, String particle, Settings settings) { particle = particle.toLowerCase().replace("_", " "); + final int rad = settings.getInt(VISIBLE_RADIUS_KEY, 25); + final float dx = (float)settings.getDouble(DX_KEY, 0.0); + final float dy = (float)settings.getDouble(DY_KEY, 0.0); + final float dz = (float)settings.getDouble(DZ_KEY, 0.0); + final int amount = settings.getInt(AMOUNT_KEY, 1); + final float speed = (float) settings.getDouble(SPEED_KEY, 1.0); + final Material mat = Material.valueOf(settings.getString(MATERIAL_KEY, "DIRT").toUpperCase().replace(" ", "_")); + + try { + // Normal bukkit effects + if (BUKKIT_EFFECTS.containsKey(particle)) { + loc.getWorld().playEffect(loc, BUKKIT_EFFECTS.get(particle), settings.getInt(DATA_KEY, 0)); + } - // Normal bukkit effects - if (BUKKIT_EFFECTS.containsKey(particle)) - { - loc.getWorld().playEffect(loc, BUKKIT_EFFECTS.get(particle), settings.getInt(DATA_KEY, 0)); - } - - // Entity effects - else if (ENTITY_EFFECTS.containsKey(particle)) - { - play(loc, ENTITY_EFFECTS.get(particle)); - } - - // Reflection particles - else if (REFLECT_PARTICLES.containsKey(particle)) - { - int num = settings.getInt(AMOUNT_KEY, 1); - Particle.play(REFLECT_PARTICLES.get(particle), loc, settings.getInt(VISIBLE_RADIUS_KEY, 25), (float) settings.getDouble(DX_KEY, 0.0), (float) settings.getDouble(DY_KEY, 0.0), (float) settings.getDouble(DZ_KEY, 0.0), (float) settings.getDouble(SPEED_KEY, 1.0), num); - } + // Entity effects + else if (ENTITY_EFFECTS.contains(particle)) { + play(loc, EntityEffect.valueOf(particle.toUpperCase().replace(' ', '_'))); + } - // Block break particle - else if (particle.equals("block crack")) - { - try - { - Material mat = Material.valueOf(settings.getString(MATERIAL_KEY, "DIRT").toUpperCase().replace(" ", "_")); - Particle.playBlockCrack(mat, (short) settings.getInt(TYPE_KEY, 0), loc, settings.getInt(VISIBLE_RADIUS_KEY, 25), (float) settings.getDouble(SPEED_KEY, 1.0)); + // v1.13 particles + else if (VersionManager.isVersionAtLeast(11300)) { + if (particle.startsWith("block")) { + SpigotParticles.playBlock(loc, particle, dx, dy, dz, amount, speed, rad, mat); + } else if (particle.startsWith("icon")) { + SpigotParticles.playItem(loc, particle, dx, dy, dz, amount, speed, rad, mat); + } else { + SpigotParticles.play(loc, particle, dx, dy, dz, amount, speed, rad); + } } - catch (Exception ex) - { - Logger.invalid(ex.getCause().getMessage()); + + // Reflection particles + else if (REFLECT_PARTICLES.containsKey(particle)) { + Particle.play(REFLECT_PARTICLES.get(particle), loc, rad, dx, dy, dz, speed, amount); } - } - // Icon break particle - else if (particle.equals("icon crack")) - { - try - { - Material mat = Material.valueOf(settings.getString(MATERIAL_KEY, "DIRT").toUpperCase().replace(" ", "_")); - Particle.playIconCrack(mat, (short) settings.getInt(TYPE_KEY, 0), loc, settings.getInt(VISIBLE_RADIUS_KEY, 25), (float) settings.getDouble(SPEED_KEY, 1.0)); + // Block break particle + else if (particle.equals("block crack")) { + Particle.playBlockCrack(mat, (short) settings.getInt(TYPE_KEY, 0), loc, rad, speed); } - catch (Exception ex) - { - Logger.invalid(ex.getCause().getMessage()); + + // Icon break particle + else if (particle.equals("icon crack")) { + Particle.playIconCrack(mat, (short) settings.getInt(TYPE_KEY, 0), loc, rad, speed); } - } - // 1.9+ particles - else - { - int num = settings.getInt(AMOUNT_KEY, 1); - Particle.play(particle, loc, settings.getInt(VISIBLE_RADIUS_KEY, 25), (float) settings.getDouble(DX_KEY, 0.0), (float) settings.getDouble(DY_KEY, 0.0), (float) settings.getDouble(DZ_KEY, 0.0), (float) settings.getDouble(SPEED_KEY, 1.0), num); + // 1.9+ particles + else { + Particle.play(particle, loc, rad, dx, dy, dz, speed, amount); + } + } catch (Exception ex) { + Logger.invalid(ex.getCause().getMessage()); } } @@ -263,31 +248,31 @@ else if (particle.equals("icon crack")) * @param radius radius of the circle * @param amount amount of particles to play */ - public static void fillCircle(Location loc, String particle, Settings settings, double radius, int amount, Direction direction) - { + public static void fillCircle( + Location loc, + String particle, + Settings settings, + double radius, + int amount, + Direction direction) { Location temp = loc.clone(); double rSquared = radius * radius; double twoRadius = radius * 2; int index = 0; // Play the particles - while (index < amount) - { - if (direction == Direction.XY || direction == Direction.XZ) - { + while (index < amount) { + if (direction == Direction.XY || direction == Direction.XZ) { temp.setX(loc.getX() + random.nextDouble() * twoRadius - radius); } - if (direction == Direction.XY || direction == Direction.YZ) - { + if (direction == Direction.XY || direction == Direction.YZ) { temp.setY(loc.getY() + random.nextDouble() * twoRadius - radius); } - if (direction == Direction.XZ || direction == Direction.YZ) - { + if (direction == Direction.XZ || direction == Direction.YZ) { temp.setZ(loc.getZ() + random.nextDouble() * twoRadius - radius); } - if (temp.distanceSquared(loc) > rSquared) - { + if (temp.distanceSquared(loc) > rSquared) { continue; } @@ -305,22 +290,19 @@ public static void fillCircle(Location loc, String particle, Settings settings, * @param radius radius of the sphere * @param amount amount of particles to use */ - public static void fillSphere(Location loc, String particle, Settings settings, double radius, int amount) - { + public static void fillSphere(Location loc, String particle, Settings settings, double radius, int amount) { Location temp = loc.clone(); double rSquared = radius * radius; double twoRadius = radius * 2; int index = 0; // Play the particles - while (index < amount) - { + while (index < amount) { temp.setX(loc.getX() + random.nextDouble() * twoRadius - radius); temp.setY(loc.getY() + random.nextDouble() * twoRadius - radius); temp.setZ(loc.getZ() + random.nextDouble() * twoRadius - radius); - if (temp.distanceSquared(loc) > rSquared) - { + if (temp.distanceSquared(loc) > rSquared) { continue; } @@ -338,22 +320,19 @@ public static void fillSphere(Location loc, String particle, Settings settings, * @param radius radius of the sphere * @param amount amount of particles to use */ - public static void fillHemisphere(Location loc, String particle, Settings settings, double radius, int amount) - { + public static void fillHemisphere(Location loc, String particle, Settings settings, double radius, int amount) { Location temp = loc.clone(); double rSquared = radius * radius; double twoRadius = radius * 2; int index = 0; // Play the particles - while (index < amount) - { + while (index < amount) { temp.setX(loc.getX() + random.nextDouble() * twoRadius - radius); temp.setY(loc.getY() + random.nextDouble() * radius); temp.setZ(loc.getZ() + random.nextDouble() * twoRadius - radius); - if (temp.distanceSquared(loc) > rSquared) - { + if (temp.distanceSquared(loc) > rSquared) { continue; } @@ -362,26 +341,17 @@ public static void fillHemisphere(Location loc, String particle, Settings settin } } - private static final HashMap BUKKIT_EFFECTS = new HashMap() - {{ + private static final HashMap BUKKIT_EFFECTS = new HashMap() {{ put("smoke", Effect.SMOKE); put("ender signal", Effect.ENDER_SIGNAL); put("mobspawner flames", Effect.MOBSPAWNER_FLAMES); put("potion break", Effect.POTION_BREAK); }}; - private static final HashMap ENTITY_EFFECTS = new HashMap() - {{ - put("death", EntityEffect.DEATH); - put("hurt", EntityEffect.HURT); - put("sheep eat", EntityEffect.SHEEP_EAT); - put("wolf hearts", EntityEffect.WOLF_HEARTS); - put("wolf shake", EntityEffect.WOLF_SHAKE); - put("wolf smoke", EntityEffect.WOLF_SMOKE); - }}; + private static final Set ENTITY_EFFECTS = ImmutableSet.of( + "death", "hurt", "sheep eat", "wolf hearts", "wolf shake", "wolf smoke"); - private static final HashMap REFLECT_PARTICLES = new HashMap() - {{ + private static final HashMap REFLECT_PARTICLES = new HashMap() {{ put("angry villager", "angryVillager"); put("bubble", "bubble"); put("cloud", "cloud"); diff --git a/src/com/sucy/skill/cast/CircleIndicator.java b/src/com/sucy/skill/cast/CircleIndicator.java new file mode 100644 index 00000000..b9d956f1 --- /dev/null +++ b/src/com/sucy/skill/cast/CircleIndicator.java @@ -0,0 +1,118 @@ +/** + * SkillAPI + * com.sucy.skill.cast.CircleIndicator + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +import com.sucy.skill.api.particle.ParticleSettings; +import org.bukkit.Location; + +import java.util.List; + +/** + * An indicator for a circular pattern + */ +public class CircleIndicator implements IIndicator +{ + private double x, y, z; + private double radius; + private double sin, cos; + private int particles; + + /** + * @param radius radius of the circle + */ + public CircleIndicator(double radius) + { + if (radius == 0) + throw new IllegalArgumentException("Invalid radius - cannot be 0"); + + this.radius = Math.abs(radius); + particles = (int) (IndicatorSettings.density * radius * 2 * Math.PI); + + double angle = Math.PI * 2 / particles; + sin = Math.sin(angle); + cos = Math.cos(angle); + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param loc location to move to + */ + @Override + public void moveTo(Location loc) + { + this.x = loc.getX(); + this.y = loc.getY(); + this.z = loc.getZ(); + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param x X-axis coordinate + * @param y Y-axis coordinate + * @param z Z-axis coordinate + */ + @Override + public void moveTo(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Creates the packets for the indicator, adding them to the list + * + * @param packets packet list to add to + * @param particle particle type to use + * @param step animation step + * + * @throws Exception + */ + @Override + public void makePackets(List packets, ParticleSettings particle, int step) + throws Exception + { + // Offset angle for animation + double startAngle = step * IndicatorSettings.animation / (20 * radius); + double ii = Math.sin(startAngle) * radius; + double jj = Math.cos(startAngle) * radius; + + // Make the packets + for (int i = 0; i < particles; i++) + { + packets.add(particle.instance(x + ii, y, z + jj)); + + double temp = ii * cos - jj * sin; + jj = ii * sin + jj * cos; + ii = temp; + } + } +} diff --git a/src/com/sucy/skill/cast/ConeIndicator.java b/src/com/sucy/skill/cast/ConeIndicator.java new file mode 100644 index 00000000..55eff657 --- /dev/null +++ b/src/com/sucy/skill/cast/ConeIndicator.java @@ -0,0 +1,156 @@ +/** + * SkillAPI + * com.sucy.skill.cast.ConeIndicator + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +import com.sucy.skill.api.particle.ParticleSettings; +import org.bukkit.Location; + +import java.util.List; + +public class ConeIndicator implements IIndicator +{ + private double x, y, z; + private double fx, fz; + private double arc, radius; + private double sin, cos; + private double rSin, rCos; + private double offset; + private double yaw; + private double angleOffset; + + /** + * @param arc angle of the cone in degrees + * @param radius radius of the cone + */ + public ConeIndicator(double arc, double radius) + { + if (radius == 0) + throw new IllegalArgumentException("Invalid radius - cannot be 0"); + + this.arc = arc * Math.PI / 180; + this.radius = Math.abs(radius); + double perimeter = radius * arc + 2 * radius; + int particles = (int) (IndicatorSettings.density * perimeter); + + offset = perimeter / particles; + angleOffset = offset / radius; + sin = Math.sin(angleOffset); + cos = Math.cos(angleOffset); + rSin = Math.sin(arc / 2); + rCos = Math.cos(arc / 2); + } + + /** + * Sets the direction of the projectile + * + * @param yaw the directional yaw + */ + public void setDirection(float yaw) + { + yaw *= -Math.PI / 180; + fx = Math.sin(yaw); + fz = Math.cos(yaw); + this.yaw = yaw + Math.PI / 4; + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param loc location to move to + */ + @Override + public void moveTo(Location loc) + { + this.x = loc.getX(); + this.y = loc.getY(); + this.z = loc.getZ(); + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param x X-axis coordinate + * @param y Y-axis coordinate + * @param z Z-axis coordinate + */ + @Override + public void moveTo(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Creates the packets for the indicator, adding them to the list + * + * @param packets packet list to add to + * @param particle particle type to use + * @param step animation step + * + * @throws Exception + */ + @Override + public void makePackets(List packets, ParticleSettings particle, int step) + throws Exception + { + double base = (IndicatorSettings.animation * 0.05 * step) % offset; + + // Offset angle for animation + double startAngle = ((radius - base) % offset) / radius; + double ii = Math.sin(startAngle + yaw) * radius; + double jj = Math.cos(startAngle + yaw) * radius; + + // Packets along the edges + make(packets, particle, base, fx * rCos + fz * rSin, fz * rCos - fx * rSin); + make(packets, particle, offset - base, fx * rCos - fz * rSin, fx * rSin + fz * rCos); + + // Packets around the curve + while (startAngle < arc) + { + packets.add(particle.instance(x + ii, y, z + jj)); + + double temp = ii * cos - jj * sin; + jj = ii * sin + jj * cos; + ii = temp; + + startAngle += angleOffset; + } + } + + private void make(List packets, ParticleSettings particle, double pos, double rfx, double rfz) + throws Exception + { + while (pos <= radius) + { + packets.add(particle.instance(x + pos * rfx, y, z + pos * rfz)); + pos += offset; + } + } +} diff --git a/src/com/sucy/skill/cast/CylinderIndicator.java b/src/com/sucy/skill/cast/CylinderIndicator.java new file mode 100644 index 00000000..10881949 --- /dev/null +++ b/src/com/sucy/skill/cast/CylinderIndicator.java @@ -0,0 +1,135 @@ +/** + * SkillAPI + * com.sucy.skill.cast.CylinderIndicator + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +import com.sucy.skill.api.particle.ParticleSettings; +import org.bukkit.Location; + +import java.util.List; + +public class CylinderIndicator implements IIndicator +{ + private double x; + private double y; + private double z; + private double radius; + private double height; + private double sin; + private double cos; + private int particles; + private int vertParticles; + private double vertOffset; + private int vert; + + /** + * @param radius radius of the circle + */ + public CylinderIndicator(double radius, double height) + { + if (radius == 0) + throw new IllegalArgumentException("Invalid radius - cannot be 0"); + + this.radius = Math.abs(radius); + this.height = height; + particles = (int) (IndicatorSettings.density * radius * 2 * Math.PI); + vert = particles / 8; + vertParticles = (int) (IndicatorSettings.density * height); + vertOffset = height / vertParticles; + + double angle = Math.PI * 2 / particles; + sin = Math.sin(angle); + cos = Math.cos(angle); + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param loc location to move to + */ + @Override + public void moveTo(Location loc) + { + this.x = loc.getX(); + this.y = loc.getY(); + this.z = loc.getZ(); + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param x X-axis coordinate + * @param y Y-axis coordinate + * @param z Z-axis coordinate + */ + @Override + public void moveTo(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Creates the packets for the indicator, adding them to the list + * + * @param packets packet list to add to + * @param particle particle type to use + * @param step animation step + * + * @throws Exception + */ + @Override + public void makePackets(List packets, ParticleSettings particle, int step) + throws Exception + { + // Offset angle for animation + double startAngle = step * IndicatorSettings.animation / (20 * radius); + double rSin = Math.sin(startAngle) * radius; + double rCos = Math.cos(startAngle) * radius; + + // Make the packets + for (int i = 0; i < particles; i++) + { + packets.add(particle.instance(x + rSin, y, z + rCos)); + packets.add(particle.instance(x + rSin, y + height, z + rCos)); + + if (i % vert == 0) + { + for (int j = 0; j < vertParticles; j++) + { + packets.add(particle.instance(x + rSin, y + vertOffset * j, z + rCos)); + } + } + + double temp = rSin * cos - rCos * sin; + rCos = rSin * sin + rCos * cos; + rSin = temp; + } + } +} diff --git a/src/com/sucy/skill/cast/IIndicator.java b/src/com/sucy/skill/cast/IIndicator.java new file mode 100644 index 00000000..01dcea42 --- /dev/null +++ b/src/com/sucy/skill/cast/IIndicator.java @@ -0,0 +1,42 @@ +/** + * SkillAPI + * com.sucy.skill.cast.IIndicator + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +import com.sucy.skill.api.particle.ParticleSettings; +import org.bukkit.Location; + +import java.util.List; + +public interface IIndicator +{ + public void moveTo(Location loc); + + public void moveTo(double x, double y, double z); + + public void makePackets(List packets, ParticleSettings particle, int step) + throws Exception; +} diff --git a/src/com/sucy/skill/cast/IndicatorSettings.java b/src/com/sucy/skill/cast/IndicatorSettings.java new file mode 100644 index 00000000..4f6ebee6 --- /dev/null +++ b/src/com/sucy/skill/cast/IndicatorSettings.java @@ -0,0 +1,52 @@ +/** + * SkillAPI + * com.sucy.skill.cast.IndicatorSettings + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.api.particle.ParticleSettings; + +/** + * An indicator for a player's skill + */ +public class IndicatorSettings +{ + public static ParticleSettings particle; + + public static boolean enabled; + public static double density; + public static double animation; + public static int interval; + + public static void load(DataSection data) + { + enabled = data.getBoolean("enabled"); + density = data.getDouble("density"); + animation = data.getDouble("animation"); + interval = (int) Math.ceil(20 / data.getDouble("frequency")); + particle = new ParticleSettings(data.getSection("particle")); + } +} diff --git a/src/com/sucy/skill/cast/IndicatorType.java b/src/com/sucy/skill/cast/IndicatorType.java new file mode 100644 index 00000000..0f466bec --- /dev/null +++ b/src/com/sucy/skill/cast/IndicatorType.java @@ -0,0 +1,71 @@ +/** + * SkillAPI + * com.sucy.skill.cast.IndicatorType + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +/** + * The type of indicator display to use + */ +public enum IndicatorType +{ + // 3-dimensional effect (sphere, cuboid, etc.) + DIM_3("2D"), + + // 2-dimensional effect (circle, rectangle, etc.) + DIM_2("3D"), + + // No effect + NONE("None"); + + private String key; + + IndicatorType(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + /** + * Gets the indicator type by key + * + * @param key key + * + * @return indicator type + */ + public static IndicatorType getByKey(String key) + { + if (key == null) + return NONE; + else if (key.equalsIgnoreCase("3D")) + return DIM_3; + else if (key.equalsIgnoreCase("2D")) + return DIM_2; + else + return NONE; + } +} diff --git a/src/com/sucy/skill/cast/PlayerCastBars.java b/src/com/sucy/skill/cast/PlayerCastBars.java new file mode 100644 index 00000000..f878a438 --- /dev/null +++ b/src/com/sucy/skill/cast/PlayerCastBars.java @@ -0,0 +1,529 @@ +/** + * SkillAPI + * com.sucy.skill.cast.PlayerCastBars + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.player.PlayerSkill; +import com.sucy.skill.task.PreviewTask; +import com.sucy.skill.thread.MainThread; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.*; + +/** + * Representation of cast bar data for a single player + */ +public class PlayerCastBars implements InventoryHolder +{ + private HashMap hoverBar = new HashMap(); + private HashMap instantBar = new HashMap(); + + private HashSet used = new HashSet(); + private HashSet unused = new HashSet(); + + private PlayerView view = PlayerView.INVENTORY; + + private ItemStack[] backup; + + private PlayerData player; + + private PlayerSkill hovered; + + private long cooldown; + + private int oldSlot; + + /** + * @param data player data reference + */ + public PlayerCastBars(PlayerData data) + { + this.player = data; + } + + /** + * Validates added skills, making sure they're still unlocked + */ + public void validate() + { + validate(hoverBar); + validate(instantBar); + } + + /** + * @return true if in the hover view, false otherwise + */ + public boolean isHovering() + { + return view == PlayerView.HOVER_BAR; + } + + public PlayerView getView() { + return view; + } + + /** + * Marks a skill as hovered + * + * @param slot skill slot + */ + private void hoverSkill(Player player, int slot) + { + if (hoverBar.containsKey(slot)) + { + hovered = this.player.getSkill(hoverBar.get(slot)); + hovered.initIndicators(player); + } + else hovered = null; + } + + /** + * Makes the packets for cast previews + * + * @param step animation step + * + * @return packet list or null if nothing is hovered + * + * @throws Exception + */ + public List getHoverPackets(Player player, int step) + throws Exception + { + if (hovered == null) + return null; + + hovered.updateIndicators(player); + return hovered.makePackets(step); + } + + /** + * Checks the skills assigned to a bar to make sure they are still unlocked + * + * @param map data of the bar to validate + */ + private void validate(HashMap map) + { + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) + { + Map.Entry entry = iterator.next(); + if (!player.hasSkill(entry.getValue()) || !player.getSkill(entry.getValue()).isUnlocked()) + { + iterator.remove(); + used.remove(entry.getValue()); + } + } + } + + /** + * Restores the players inventory after + * viewing one of the related views + * + * @param player player to restore + */ + public void restore(Player player) + { + if (view == PlayerView.INVENTORY) + return; + + // Update organizer data + if (view == PlayerView.ORGANIZER) + { + reset(); + + ItemStack[] contents = player.getInventory().getContents(); + update(contents, hoverBar, 0); + update(contents, instantBar, 24); + } + + // Restore player's items + player.getInventory().setContents(backup); + view = PlayerView.INVENTORY; + player.getInventory().setHeldItemSlot(oldSlot); + } + + /** + * Opens the cast bar organizer GUI + * + * @param player player to open for + * + * @return true if opened + */ + public boolean showOrganizer(Player player) + { + if (used.size() + unused.size() == 0 || view != PlayerView.INVENTORY) + return false; + + view = PlayerView.ORGANIZER; + backup = player.getInventory().getContents(); + + // Set up player inventory for the different bars + ItemStack[] playerContents = new ItemStack[36]; + playerContents[8] = SkillAPI.getSettings().getHoverItem(); + playerContents[35] = SkillAPI.getSettings().getInstantItem(); + fill(playerContents, hoverBar, 0); + fill(playerContents, instantBar, 24); + + // Make the inventory for unused skills + int size = Math.min(54, 9 * ((used.size() + unused.size() + 8) / 9)); + Inventory inv = player.getServer().createInventory(this, size); + ItemStack[] contents = new ItemStack[size]; + int i = 0; + int j = 9; + for (String skill : unused) + { + if (i < contents.length) + contents[i++] = makeIndicator(skill); + else if (j < 24) + playerContents[j++] = makeIndicator(skill); + } + + // Apply layouts and open the view + player.getInventory().setContents(playerContents); + inv.setContents(contents); + player.openInventory(inv); + + return true; + } + + /** + * Fills the contents with the skills in a cast bar + * + * @param contents contents to add to + * @param bar cast bar data + * @param index index to start at + */ + private void fill(ItemStack[] contents, HashMap bar, int index) + { + for (Map.Entry entry : bar.entrySet()) + contents[index + entry.getKey()] = makeIndicator(entry.getValue()); + } + + /** + * Updates the layout for a cast bar + * + * @param contents customizer GUI contents + * @param bar bar data to update + * @param index starting index + */ + private void update(ItemStack[] contents, HashMap bar, int index) + { + for (int i = 0; i < 8; i++) + { + if (contents[i + index] != null) + { + List lore = contents[i + index].getItemMeta().getLore(); + String skill = lore.get(lore.size() - 1); + if (unused.contains(skill)) + { + bar.put(i, skill); + used.add(skill); + unused.remove(skill); + } + } + } + } + + /** + * Creates an indicator for use in the skill organize display + * + * @param skill skill to display + * + * @return makes a skill indicator, appending the skill name to the end for identification + */ + private ItemStack makeIndicator(String skill) + { + if (skill == null) { + return null; + } + ItemStack item = SkillAPI.getSkill(skill).getIndicator(this.player.getSkill(skill), true); + ItemMeta meta = item.getItemMeta(); + List lore = meta.getLore(); + lore.add(skill); + meta.setLore(lore); + item.setItemMeta(meta); + return item; + } + + /** + * Shows the hover cast bar to the player + * + * @param player player to show to + */ + public boolean showHoverBar(Player player) + { + boolean result = show(player, PlayerView.HOVER_BAR, hoverBar); + player.getInventory().setHeldItemSlot(0); + hoverSkill(player, 0); + MainThread.register(new PreviewTask(player)); + return result; + } + + /** + * Shows the instant bar to the player + * + * @param player player to show to + */ + public boolean showInstantBar(Player player) + { + return show(player, PlayerView.INSTANT_BAR, instantBar); + } + + /** + * Shows a cast bar to the player if requirements are met + * + * @param player player to show + * @param view view related to the bar + * @param bar bar data + */ + private boolean show(Player player, PlayerView view, HashMap bar) + { + long left = System.currentTimeMillis() - cooldown - SkillAPI.getSettings().getCastCooldown(); + if (this.view != PlayerView.INVENTORY || bar.size() == 0 || left < 0) + return false; + + this.view = view; + backup = player.getInventory().getContents(); + + ItemStack[] contents = new ItemStack[36]; + makeContents(bar, contents, 0); + player.getInventory().setContents(contents); + + return true; + } + + /** + * Handles changing to a different weapon slot + * + * @param event event details + */ + public boolean handle(PlayerItemHeldEvent event) + { + switch (view) + { + case INSTANT_BAR: + if (instantBar.containsKey(event.getNewSlot())) + { + player.cast(instantBar.get(event.getNewSlot())); + cooldown = System.currentTimeMillis(); + } + restore(event.getPlayer()); + event.setCancelled(true); + return true; + + case HOVER_BAR: + hoverSkill(event.getPlayer(), event.getNewSlot()); + return true; + + case INVENTORY: + oldSlot = event.getPreviousSlot(); + return false; + default: + break; + } + + return false; + } + + /** + * Handles a click event when in certain views + * + * @param player the player doing the interaction + */ + public boolean handleInteract(Player player) + { + switch (view) + { + case INSTANT_BAR: + restore(player); + return true; + + case HOVER_BAR: + if (hoverBar.containsKey(player.getInventory().getHeldItemSlot())) + { + this.player.cast(hoverBar.get(player.getInventory().getHeldItemSlot())); + cooldown = System.currentTimeMillis(); + } + restore(player); + return true; + default: + break; + } + return false; + } + + /** + * Handles when the player opens an inventory + * + * @param player player to handle for + */ + public void handleOpen(Player player) + { + if (view == PlayerView.HOVER_BAR || view == PlayerView.INSTANT_BAR) + restore(player); + } + + /** + * Handles clicking in the GUI + * + * @param event event details + */ + public void handle(InventoryClickEvent event) + { + if (event.getInventory() == event.getWhoClicked().getInventory()) + { + if (event.getSlot() == 8 || event.getSlot() == 35) + event.setCancelled(true); + } + else if (event.getSlot() < 0) + event.setCancelled(true); + } + + /** + * Adds an unlocked skill to the skill bars + * + * @param skill skill to add + */ + public void unlock(PlayerSkill skill) + { + if (!addTo(hoverBar, skill)) + addTo(instantBar, skill); + } + + /** + * Adds a skill to the first open slot in the bar + * + * @param bar bar to add to + * @param skill skill to add + * + * @return true if added, false if no room + */ + private boolean addTo(HashMap bar, PlayerSkill skill) + { + for (int i = 0; i < 9; i++) + { + if (!bar.containsKey(i)) + { + add(bar, skill.getData().getName(), i); + return true; + } + } + return false; + } + + /** + * Sets a skill to the bar + * + * @param bar bar to set to + * @param skill skill to set + * @param slot slot to set to + */ + private void add(HashMap bar, String skill, int slot) + { + bar.put(slot, skill); + used.add(skill); + unused.remove(skill); + } + + /** + * Resets the layout and populates the unused list + * with all available skills + */ + public void reset() + { + unused.clear(); + used.clear(); + instantBar.clear(); + hoverBar.clear(); + for (PlayerSkill skill : player.getSkills()) + if (skill.isUnlocked() && skill.getData().canCast()) + unused.add(skill.getData().getName()); + } + + /** + * Makes the contents for one of the views + * + * @param slots slots to use + * @param contents where to store the results + * @param offset starting index to add to + */ + private void makeContents(HashMap slots, ItemStack[] contents, int offset) + { + for (Map.Entry slot : slots.entrySet()) + { + PlayerSkill skill = this.player.getSkill(slot.getValue()); + if (skill != null) { + contents[offset + slot.getKey()] = skill.getData().getIndicator(skill, true); + } + } + } + + /** + * Loads data from the config + * + * @param config config data + * @param hover whether or not it's for the hover bar + */ + public void load(DataSection config, boolean hover) + { + if (config == null) + return; + + HashMap bar = hover ? hoverBar : instantBar; + for (String key : config.keys()) + add(bar, key, config.getInt(key)); + } + + /** + * Saves data to the config + * + * @param config config data + * @param hover whether or not it's for the hover bar + */ + public void save(DataSection config, boolean hover) + { + HashMap bar = hover ? hoverBar : instantBar; + for (Map.Entry entry : bar.entrySet()) + config.set(entry.getValue(), entry.getKey()); + } + + /** + * Added to satisfy InventoryHolder, though doesn't do anything + * + * @return null + */ + @Override + public Inventory getInventory() { return null; } +} diff --git a/src/com/sucy/skill/cast/PlayerView.java b/src/com/sucy/skill/cast/PlayerView.java new file mode 100644 index 00000000..3fabe521 --- /dev/null +++ b/src/com/sucy/skill/cast/PlayerView.java @@ -0,0 +1,45 @@ +/** + * SkillAPI + * com.sucy.skill.cast.PlayerView + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +/** + * Types of views the player can currently be in + */ +public enum PlayerView +{ + // Default view, having the normal inventory items + INVENTORY, + + // Skill bar for hovering over skills + HOVER_BAR, + + // Skill bar for instant-casting skills + INSTANT_BAR, + + // GUI for organizing skill bars + ORGANIZER +} diff --git a/src/com/sucy/skill/cast/ProjectileIndicator.java b/src/com/sucy/skill/cast/ProjectileIndicator.java new file mode 100644 index 00000000..0380b171 --- /dev/null +++ b/src/com/sucy/skill/cast/ProjectileIndicator.java @@ -0,0 +1,132 @@ +/** + * SkillAPI + * com.sucy.skill.cast.ArcIndicator + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +import com.sucy.skill.api.particle.ParticleSettings; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +import java.util.List; + +/** + * Represents a preview indicator for showing the direction of projectiles to fire + */ +public class ProjectileIndicator implements IIndicator +{ + private double x, y, z; + private double velX, velY, velZ; + private double speed; + private double gravity; + private double tBase; + + /** + * @param speed speed of the projectile + * @param gravity gravity of the projectile + */ + public ProjectileIndicator(double speed, double gravity) + { + this.speed = speed; + this.gravity = gravity; + this.tBase = 3 / this.speed; + } + + /** + * Sets the direction of the projectile + * + * @param direction direction of the indicator + */ + public void setDirection(Vector direction) + { + velX = direction.getX() * speed; + velY = direction.getY() * speed; + velZ = direction.getZ() * speed; + } + + /** + * Sets the direction of the projectile + * + * @param x X-Axis value + * @param y Y-Axis value + * @param z Z-Axis value + */ + public void setDirection(double x, double y, double z) + { + velX = x * speed; + velY = y * speed; + velZ = z * speed; + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param loc location to move to + */ + @Override + public void moveTo(Location loc) + { + this.x = loc.getX(); + this.y = loc.getY(); + this.z = loc.getZ(); + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param x X-axis coordinate + * @param y Y-axis coordinate + * @param z Z-axis coordinate + */ + @Override + public void moveTo(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Creates the packets for the indicator, adding them to the list + * + * @param packets packet list to add to + * @param particle particle type to use + * @param step animation step + * + * @throws Exception + */ + @Override + public void makePackets(List packets, ParticleSettings particle, int step) + throws Exception + { + double px = x + velX * tBase; + double py = y + velY * tBase - gravity * tBase * tBase; + double pz = z + velZ * tBase; + + packets.add(particle.instance(px, py, pz)); + } +} diff --git a/src/com/sucy/skill/cast/SphereIndicator.java b/src/com/sucy/skill/cast/SphereIndicator.java new file mode 100644 index 00000000..5d472faa --- /dev/null +++ b/src/com/sucy/skill/cast/SphereIndicator.java @@ -0,0 +1,128 @@ +/** + * SkillAPI + * com.sucy.skill.cast.SphereIndicator + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cast; + +import com.sucy.skill.api.particle.ParticleSettings; +import org.bukkit.Location; + +import java.util.List; + +/** + * A fancier sphere indicator + */ +public class SphereIndicator implements IIndicator +{ + private static final double COS_45 = Math.cos(Math.PI / 4); + + private double x, y, z; + private double radius; + private double sin, cos; + private double angleStep; + private int particles; + + /** + * @param radius radius of the circle + */ + public SphereIndicator(double radius) + { + if (radius == 0) + throw new IllegalArgumentException("Invalid radius - cannot be 0"); + + this.radius = Math.abs(radius); + particles = (int) (IndicatorSettings.density * radius * 2 * Math.PI); + angleStep = IndicatorSettings.animation * IndicatorSettings.interval / (20 * this.radius); + + double angle = Math.PI * 2 / particles; + sin = Math.sin(angle); + cos = Math.cos(angle); + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param loc location to move to + */ + @Override + public void moveTo(Location loc) + { + this.x = loc.getX(); + this.y = loc.getY(); + this.z = loc.getZ(); + } + + /** + * Updates the position of the indicator to be centered + * at the given coordinates + * + * @param x X-axis coordinate + * @param y Y-axis coordinate + * @param z Z-axis coordinate + */ + @Override + public void moveTo(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Creates the packets for the indicator, adding them to the list + * + * @param packets packet list to add to + * @param particle particle type to use + * @param step animation step + * + * @throws Exception + */ + @Override + public void makePackets(List packets, ParticleSettings particle, int step) + throws Exception + { + // Offset angle for animation + double startAngle = step * angleStep; + + double urs = Math.sin(startAngle); + double urc = Math.cos(startAngle); + + double rs = urs * radius; + double rc = urc * radius; + + // Flat circle packets + for (int i = 0; i < particles; i++) + { + packets.add(particle.instance(x + rs, y, z + rc)); + packets.add(particle.instance(x + rs * urc, y + rc, z + rs * urs)); + packets.add(particle.instance(x + (rc - rs * urs) * COS_45, y + rs * urc, z + (rc + rs * urs) * COS_45)); + + double temp = rs * cos - rc * sin; + rc = rs * sin + rc * cos; + rs = temp; + } + } +} diff --git a/src/com/sucy/skill/cmd/CmdAP.java b/src/com/sucy/skill/cmd/CmdAP.java index 471e5bee..de83ca26 100644 --- a/src/com/sucy/skill/cmd/CmdAP.java +++ b/src/com/sucy/skill/cmd/CmdAP.java @@ -43,81 +43,78 @@ /** * A command that gives a player class experience */ -public class CmdAP implements IFunction -{ - private static final String NOT_PLAYER = "not-player"; - private static final String NOT_NUMBER = "not-number"; - private static final String NOT_POSITIVE = "not-positive"; - private static final String GAVE_AP = "gave-points"; - private static final String RECEIVED_AP = "received-points"; - private static final String DISABLED = "world-disabled"; +public class CmdAP implements IFunction { + private static final String NOT_PLAYER = "not-player"; + private static final String NOT_NUMBER = "not-number"; + private static final String NOT_POSITIVE = "not-positive"; + private static final String GAVE_AP = "gave-points"; + private static final String RECEIVED_AP = "received-points"; + private static final String DISABLED = "world-disabled"; - /** - * Runs the command - * - * @param cmd command that was executed - * @param plugin plugin reference - * @param sender sender of the command - * @param args argument list - */ - @Override - public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) - { - // Disabled world - if (sender instanceof Player && !SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld()) && args.length == 1) - { - cmd.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); - } + /** + * Runs the command + * + * @param cmd command that was executed + * @param plugin plugin reference + * @param sender sender of the command + * @param args argument list + */ + @Override + public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) { + // Disabled world + if (sender instanceof Player && !SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld()) + && args.length == 1) { + cmd.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); + } - // Only can show info of a player so console needs to provide a name - else if (args.length >= 1 && (args.length >= 2 || sender instanceof Player)) - { - // Get the player data - OfflinePlayer target = args.length == 1 ? (OfflinePlayer) sender : VersionManager.getOfflinePlayer(args[0], false); - if (target == null) - { - cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "That is not a valid player name"); - return; - } + // Only can show info of a player so console needs to provide a name + else if (args.length >= 1 && (args.length >= 2 || sender instanceof Player)) { + // Get the player data + OfflinePlayer target = args.length == 1 ? (OfflinePlayer) sender + : VersionManager.getOfflinePlayer(args[0], false); + if (target == null) { + cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "That is not a valid player name"); + return; + } - // Parse the skill points - int amount; - try - { - amount = Integer.parseInt(args[args.length == 1 ? 0 : 1]); - } - catch (Exception ex) - { - cmd.sendMessage(sender, NOT_NUMBER, ChatColor.RED + "That is not a valid skill point amount"); - return; - } + // Parse the skill points + int amount; + try { + amount = Integer.parseInt(args[args.length == 1 ? 0 : 1]); + } catch (Exception ex) { + cmd.sendMessage(sender, NOT_NUMBER, ChatColor.RED + "That is not a valid skill point amount"); + return; + } - // Invalid amount of skill points - if (amount <= 0) - { - cmd.sendMessage(sender, NOT_POSITIVE, ChatColor.RED + "You must give a positive amount of skill points"); - return; - } + // Invalid amount of skill points + if (amount <= 0) { + cmd.sendMessage(sender, NOT_POSITIVE, + ChatColor.RED + "You must give a positive amount of skill points"); + return; + } - // Give skill points - PlayerData data = SkillAPI.getPlayerData(target); - data.giveAttribPoints(amount); + // Give skill points + PlayerData data = SkillAPI.getPlayerData(target); + data.giveAttribPoints(amount); - // Messages - if (target != sender) - { - cmd.sendMessage(sender, GAVE_AP, ChatColor.DARK_GREEN + "You have given " + ChatColor.GOLD + "{player} {points} attribute points", Filter.PLAYER.setReplacement(target.getName()), RPGFilter.POINTS.setReplacement("" + amount)); - } - if (target.isOnline()) - { - cmd.sendMessage(target.getPlayer(), RECEIVED_AP, ChatColor.DARK_GREEN + "You have received " + ChatColor.GOLD + "{points} attribute points " + ChatColor.DARK_GREEN + "from " + ChatColor.GOLD + "{player}", Filter.PLAYER.setReplacement(sender.getName()), RPGFilter.POINTS.setReplacement("" + amount)); - } - } + // Messages + if (target != sender) { + cmd.sendMessage(sender, GAVE_AP, + ChatColor.DARK_GREEN + "You have given " + ChatColor.GOLD + + "{player} {points} attribute points", + Filter.PLAYER.setReplacement(target.getName()), RPGFilter.POINTS.setReplacement("" + amount)); + } + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), RECEIVED_AP, + ChatColor.DARK_GREEN + "You have received " + ChatColor.GOLD + "{points} attribute points " + + ChatColor.DARK_GREEN + "from " + ChatColor.GOLD + "{player}", + Filter.PLAYER.setReplacement(sender.getName()), RPGFilter.POINTS.setReplacement("" + amount)); + } + } - // Not enough arguments - else - { - CommandManager.displayUsage(cmd, sender); - } - } -} + // Not enough arguments + else { + CommandManager.displayUsage(cmd, sender); + } + } +} \ No newline at end of file diff --git a/src/com/sucy/skill/cmd/CmdBackup.java b/src/com/sucy/skill/cmd/CmdBackup.java index 006e7db6..9ddf985c 100644 --- a/src/com/sucy/skill/cmd/CmdBackup.java +++ b/src/com/sucy/skill/cmd/CmdBackup.java @@ -29,6 +29,7 @@ import com.rit.sucy.commands.ConfigurableCommand; import com.rit.sucy.commands.IFunction; import com.rit.sucy.config.Filter; +import com.rit.sucy.config.parse.YAMLParser; import com.rit.sucy.sql.direct.SQLDatabase; import com.rit.sucy.sql.direct.SQLTable; import com.sucy.skill.SkillAPI; @@ -104,13 +105,17 @@ public void run() SQLTable table = database.createTable(api, "players"); ResultSet query = table.queryAll(); + final File file = new File(api.getDataFolder(), "players"); + file.mkdir(); + // Go through every entry, saving it to disk while (query.next()) { - String yaml = query.getString(SQLIO.DATA); + String sqlYaml = query.getString(SQLIO.DATA); + String yaml = YAMLParser.parseText(sqlYaml, SQLIO.STRING).toString(); String name = query.getString("Name"); - FileOutputStream out = new FileOutputStream(new File("players/" + name + ".yml")); + FileOutputStream out = new FileOutputStream(new File(file, name + ".yml")); BufferedWriter write = new BufferedWriter(new OutputStreamWriter(out, Encoder.UTF_8)); write.write(yaml); @@ -129,6 +134,7 @@ public void run() "&4SQL database backup failed - backed up {amount} entries", Filter.AMOUNT.setReplacement(count + "") ); + ex.printStackTrace(); } database.closeConnection(); } diff --git a/src/com/sucy/skill/cmd/CmdBind.java b/src/com/sucy/skill/cmd/CmdBind.java index 89483a6b..5204218c 100644 --- a/src/com/sucy/skill/cmd/CmdBind.java +++ b/src/com/sucy/skill/cmd/CmdBind.java @@ -76,7 +76,7 @@ else if (!SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld())) else if (args.length >= 1) { - ItemStack item = ((Player) sender).getItemInHand(); + ItemStack item = ((Player) sender).getInventory().getItemInMainHand(); if (item == null || item.getType() == Material.AIR) { command.sendMessage(sender, NO_ITEM, "&4You are not holding an item"); @@ -103,8 +103,14 @@ else if (skill.getLevel() == 0) } else { + if(item.getItemMeta().hasDisplayName()) { + player.bind(item.getItemMeta().getDisplayName(), skill); + command.sendMessage(sender, SKILL_BOUND, "&6{skill} &2has been bound to &6{item}", RPGFilter.SKILL.setReplacement(skill.getData().getName()), RPGFilter.ITEM.setReplacement(TextFormatter.format(item.getItemMeta().getDisplayName().replaceAll("§.", "")))); + }else { player.bind(item.getType(), skill); command.sendMessage(sender, SKILL_BOUND, "&6{skill} &2has been bound to &6{item}", RPGFilter.SKILL.setReplacement(skill.getData().getName()), RPGFilter.ITEM.setReplacement(TextFormatter.format(item.getType().name()))); + } + } } else diff --git a/src/com/sucy/skill/cmd/CmdCast.java b/src/com/sucy/skill/cmd/CmdCast.java index fa86b8a5..b45d3fa7 100644 --- a/src/com/sucy/skill/cmd/CmdCast.java +++ b/src/com/sucy/skill/cmd/CmdCast.java @@ -37,12 +37,12 @@ import org.bukkit.plugin.Plugin; /** - * Command to bind a skill to an item + * Command to cast a skill */ public class CmdCast implements IFunction { - private static final String NOT_SKILL = "not-skill"; + private static final String NOT_AVAILABLE = "not-available"; private static final String NOT_UNLOCKED = "not-unlocked"; private static final String NOT_PLAYER = "not-player"; private static final String DISABLED = "world-disabled"; @@ -60,52 +60,41 @@ public void execute(ConfigurableCommand command, Plugin plugin, CommandSender se { // Player only command if (!(sender instanceof Player)) - { command.sendMessage(sender, NOT_PLAYER, "&4Only players can use this command"); - } // Disabled world else if (!SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld())) - { command.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); - } // Requires at least one argument else if (args.length >= 1) { - PlayerData player = SkillAPI.getPlayerData((Player) sender); // Get the skill name String skill = args[0]; for (int i = 1; i < args.length; i++) - { skill += " " + args[i]; - } // Invalid skill if (!SkillAPI.isSkillRegistered(skill)) - { command.sendMessage(sender, NOT_SKILL, ChatColor.RED + "That is not a valid skill name"); - } - // Player doesn't have the skill + // Class mismatch + else if (!player.hasSkill(skill)) + command.sendMessage(sender, NOT_AVAILABLE, ChatColor.RED + "That skill is not available for your class"); + + // Not unlocked else if (!player.hasSkill(skill) || player.getSkillLevel(skill) == 0) - { - command.sendMessage(sender, NOT_UNLOCKED, ChatColor.RED + "You cannot cast that skill"); - } + command.sendMessage(sender, NOT_UNLOCKED, ChatColor.RED + "You must level up the skill first"); // Cast the skill else - { player.cast(skill); - } } // Invalid arguments else - { CommandManager.displayUsage(command, sender, 1); - } } } diff --git a/src/com/sucy/skill/cmd/CmdChangeClass.java b/src/com/sucy/skill/cmd/CmdChangeClass.java new file mode 100644 index 00000000..f44b1886 --- /dev/null +++ b/src/com/sucy/skill/cmd/CmdChangeClass.java @@ -0,0 +1,81 @@ +package com.sucy.skill.cmd; + +import com.rit.sucy.commands.ConfigurableCommand; +import com.rit.sucy.commands.IFunction; +import com.rit.sucy.config.Filter; +import com.rit.sucy.version.VersionManager; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.api.player.PlayerClass; +import com.sucy.skill.language.RPGFilter; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * SkillAPI © 2018 + * com.sucy.skill.cmd.CmdChangeClass + */ +public class CmdChangeClass implements IFunction { + private static final String INVALID_GROUP = "invalid-group"; + private static final String INVALID_PLAYER = "invalid-player"; + private static final String INVALID_TARGET = "invalid-class"; + private static final String SUCCESS = "success"; + private static final String NOTIFICATION = "notification"; + + /** + * Executes the command + * + * @param cmd owning command + * @param plugin plugin reference + * @param sender sender of the command + * @param args arguments + */ + @Override + public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) { + if (args.length >= 3) { + final String playerName = args[0]; + final String groupName = args[1]; + String className = args[2]; + for (int i = 3; i < args.length; i++) className += ' ' + args[i]; + + final Player player = VersionManager.getPlayer(playerName); + if (player == null) { + cmd.sendMessage(sender, INVALID_PLAYER, ChatColor.DARK_RED + "{player} is not online", + Filter.PLAYER.setReplacement(playerName)); + return; + } + + final PlayerClass data = SkillAPI.getPlayerData(player).getClass(groupName); + if (data == null) { + cmd.sendMessage(sender, INVALID_GROUP, "{player} does not have a {group}", + Filter.PLAYER.setReplacement(player.getName()), + RPGFilter.CLASS.setReplacement(groupName)); + return; + } + + final String original = data.getData().getName(); + final RPGClass target = SkillAPI.getClass(className); + if (target == null) { + cmd.sendMessage(sender, INVALID_TARGET, "{class} is not a valid class to change to", + RPGFilter.CLASS.setReplacement(className)); + return; + } + + data.setClassData(target); + cmd.sendMessage(sender, SUCCESS, "You have changed {player} from a {name} to a {group}", + Filter.PLAYER.setReplacement(player.getName()), + RPGFilter.CLASS.setReplacement(className), + RPGFilter.NAME.setReplacement(original)); + + if (sender != player) { + cmd.sendMessage(player, NOTIFICATION, "You have changed from a {name} to a {group}", + RPGFilter.CLASS, + RPGFilter.NAME); + } + } else { + cmd.displayHelp(sender); + } + } +} diff --git a/src/com/sucy/skill/cmd/CmdCustomize.java b/src/com/sucy/skill/cmd/CmdCustomize.java new file mode 100644 index 00000000..8d819c16 --- /dev/null +++ b/src/com/sucy/skill/cmd/CmdCustomize.java @@ -0,0 +1,58 @@ +/** + * SkillAPI + * com.sucy.skill.cmd.CmdCustomize + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cmd; + +import com.rit.sucy.commands.ConfigurableCommand; +import com.rit.sucy.commands.IFunction; +import com.sucy.skill.gui.tool.GUITool; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +public class CmdCustomize implements IFunction +{ + public static final String CUSTOMIZE = "sapiCustomGUI"; + + private static final String NOT_PLAYER = "not-player"; + + /** + * Executes the command + * + * @param command owning command + * @param plugin plugin reference + * @param sender sender of the command + * @param args arguments + */ + @Override + public void execute(ConfigurableCommand command, Plugin plugin, CommandSender sender, String[] args) + { + if (!(sender instanceof Player)) + command.sendMessage(sender, NOT_PLAYER, "&4Only players can use this command"); + else + new GUITool((Player) sender).open(); + } +} diff --git a/src/com/sucy/skill/cmd/CmdExp.java b/src/com/sucy/skill/cmd/CmdExp.java index 5c772764..4ec214e3 100644 --- a/src/com/sucy/skill/cmd/CmdExp.java +++ b/src/com/sucy/skill/cmd/CmdExp.java @@ -1,3 +1,25 @@ +package com.sucy.skill.cmd; + +import com.rit.sucy.commands.CommandManager; +import com.rit.sucy.commands.ConfigurableCommand; +import com.rit.sucy.commands.IFunction; +import com.rit.sucy.config.Filter; +import com.rit.sucy.config.parse.NumberParser; +import com.rit.sucy.version.VersionManager; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.enums.ExpSource; +import com.sucy.skill.api.player.PlayerClass; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.language.RPGFilter; +import com.sucy.skill.manager.CmdManager; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.util.regex.Pattern; + /** * SkillAPI * com.sucy.skill.cmd.CmdExp @@ -24,31 +46,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.sucy.skill.cmd; - -import com.rit.sucy.commands.CommandManager; -import com.rit.sucy.commands.ConfigurableCommand; -import com.rit.sucy.commands.IFunction; -import com.rit.sucy.config.Filter; -import com.rit.sucy.version.VersionManager; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.api.enums.ExpSource; -import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.api.util.NumberParser; -import com.sucy.skill.language.RPGFilter; -import org.bukkit.ChatColor; -import org.bukkit.OfflinePlayer; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - -/** - * A command that gives a player class experience - */ public class CmdExp implements IFunction { + private static final Pattern IS_NUMBER = Pattern.compile("[0-9]+"); + private static final Pattern IS_BOOL = Pattern.compile("(true)|(false)"); + private static final String NOT_PLAYER = "not-player"; - private static final String NOT_NUMBER = "not-number"; private static final String NOT_POSITIVE = "not-positive"; private static final String GAVE_EXP = "gave-exp"; private static final String RECEIVED_EXP = "received-exp"; @@ -63,7 +66,7 @@ public class CmdExp implements IFunction * @param args argument list */ @Override - public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) + public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String... args) { // Disabled world if (sender instanceof Player && !SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld()) && args.length == 1) @@ -72,27 +75,23 @@ public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender } // Only can show info of a player so console needs to provide a name - else if (args.length >= 1 && (args.length >= 2 || sender instanceof Player)) + else if ((args.length >= 1 && sender instanceof Player && IS_NUMBER.matcher(args[0]).matches()) || args.length >= 2) { + int numberIndex = IS_NUMBER.matcher(args[0]).matches() ? 0 : 1; + if (args.length > 1 && IS_NUMBER.matcher(args[1]).matches()) numberIndex = 1; + // Get the player data - OfflinePlayer target = args.length == 1 ? (OfflinePlayer) sender : VersionManager.getOfflinePlayer(args[0], false); + OfflinePlayer target = numberIndex == 0 ? (OfflinePlayer) sender : VersionManager.getOfflinePlayer(args[0], false); if (target == null) { cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "That is not a valid player name"); return; } + PlayerData data = SkillAPI.getPlayerData(target); // Parse the experience double amount; - try - { - amount = NumberParser.parseDouble(args[args.length == 1 ? 0 : 1]); - } - catch (Exception ex) - { - cmd.sendMessage(sender, NOT_NUMBER, ChatColor.RED + "That is not a valid experience amount"); - return; - } + amount = NumberParser.parseDouble(args[numberIndex]); // Invalid amount of experience if (amount <= 0) @@ -101,18 +100,44 @@ else if (args.length >= 1 && (args.length >= 2 || sender instanceof Player)) return; } - // Give experience - PlayerData data = SkillAPI.getPlayerData(target); - data.giveExp(amount, ExpSource.COMMAND); + int lastArg = args.length - 1; + boolean message = IS_BOOL.matcher(args[lastArg]).matches(); + boolean showMessage = !message || Boolean.parseBoolean(args[lastArg]); + if (message) lastArg--; - // Messages - if (target != sender) + + // Give experience to a specific class group + if (numberIndex + 1 <= lastArg) { - cmd.sendMessage(sender, GAVE_EXP, ChatColor.DARK_GREEN + "You have given " + ChatColor.GOLD + "{player} {exp} experience", Filter.PLAYER.setReplacement(target.getName()), RPGFilter.EXP.setReplacement("" + amount)); + PlayerClass playerClass = data.getClass(CmdManager.join(args, numberIndex + 1, lastArg)); + if (playerClass == null) + return; + + playerClass.giveExp(amount, ExpSource.COMMAND, showMessage); } - if (target.isOnline()) - { - cmd.sendMessage(target.getPlayer(), RECEIVED_EXP, ChatColor.DARK_GREEN + "You have received " + ChatColor.GOLD + "{exp} experience " + ChatColor.DARK_GREEN + "from " + ChatColor.GOLD + "{player}", Filter.PLAYER.setReplacement(sender.getName()), RPGFilter.EXP.setReplacement("" + amount)); + + // Give experience + else + data.giveExp(amount, ExpSource.COMMAND, showMessage); + + // Messages + if (showMessage) { + if (target != sender) { + cmd.sendMessage( + sender, + GAVE_EXP, + ChatColor.DARK_GREEN + "You have given " + ChatColor.GOLD + "{player} {exp} experience", + Filter.PLAYER.setReplacement(target.getName()), + RPGFilter.EXP.setReplacement("" + amount)); + } + if (target.isOnline()) { + cmd.sendMessage( + target.getPlayer(), + RECEIVED_EXP, + ChatColor.DARK_GREEN + "You have received " + ChatColor.GOLD + "{exp} experience " + ChatColor.DARK_GREEN + "from " + ChatColor.GOLD + "{player}", + Filter.PLAYER.setReplacement(sender.getName()), + RPGFilter.EXP.setReplacement("" + amount)); + } } } @@ -122,4 +147,4 @@ else if (args.length >= 1 && (args.length >= 2 || sender instanceof Player)) CommandManager.displayUsage(cmd, sender); } } -} +} \ No newline at end of file diff --git a/src/com/sucy/skill/cmd/CmdForceReset.java b/src/com/sucy/skill/cmd/CmdForceReset.java index 2ff78c84..14c3e174 100644 --- a/src/com/sucy/skill/cmd/CmdForceReset.java +++ b/src/com/sucy/skill/cmd/CmdForceReset.java @@ -29,10 +29,10 @@ import com.rit.sucy.commands.ConfigurableCommand; import com.rit.sucy.commands.IFunction; import com.rit.sucy.config.Filter; +import com.rit.sucy.config.parse.NumberParser; import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerAccounts; -import com.sucy.skill.api.util.NumberParser; import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; diff --git a/src/com/sucy/skill/cmd/CmdForceSkill.java b/src/com/sucy/skill/cmd/CmdForceSkill.java new file mode 100644 index 00000000..0f5e5648 --- /dev/null +++ b/src/com/sucy/skill/cmd/CmdForceSkill.java @@ -0,0 +1,109 @@ +/** + * SkillAPI + * com.sucy.skill.cmd.CmdForceSkill + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cmd; + +import com.rit.sucy.commands.ConfigurableCommand; +import com.rit.sucy.commands.IFunction; +import com.rit.sucy.version.VersionManager; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.player.PlayerSkill; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +/** + * Command to forcefully modify a skill's level + */ +public class CmdForceSkill implements IFunction +{ + private static final String NOT_PLAYER = "not-player"; + private static final String NOT_SKILL = "not-skill"; + private static final String NOT_FUNCTION = "not-function"; + private static final String UPGRADED = "skill-upped"; + private static final String DOWNGRADED = "skill-downed"; + private static final String RESET = "skill-reset"; + + /** + * Executes the command + * + * @param command owning command + * @param plugin plugin reference + * @param sender sender of the command + * @param args arguments + */ + @Override + public void execute(ConfigurableCommand command, Plugin plugin, CommandSender sender, String[] args) + { + // Needs two arguments + if (args.length < 3) + { + command.displayHelp(sender); + } + + // Switch accounts if valid number + else + { + OfflinePlayer player = VersionManager.getOfflinePlayer(args[0], false); + + if (player == null) + { + command.sendMessage(sender, NOT_PLAYER, "&4That is not a valid player name"); + return; + } + + PlayerData playerData = SkillAPI.getPlayerData(player); + StringBuilder skillName = new StringBuilder(args[2]); + for (int i = 3; i < args.length; i++) skillName.append(args[i]); + PlayerSkill skill = playerData.getSkill(skillName.toString()); + + if (skill == null) + { + command.sendMessage(sender, NOT_SKILL, "&4The player does not have access to that skill"); + return; + } + + if (args[1].equals("up")) + { + playerData.forceUpSkill(skill); + command.sendMessage(sender, UPGRADED, "&6" + skill.getData().getName() + "&2 was upgraded for &6" + player.getName()); + } + else if (args[1].equals("down")) + { + playerData.forceDownSkill(skill); + command.sendMessage(sender, DOWNGRADED, "&6" + skill.getData().getName() + "&2 was downgraded for &6" + player.getName()); + } + else if (args[1].equals("reset")) + { + playerData.refundSkill(skill); + command.sendMessage(sender, RESET, "&6" + skill.getData().getName() + "&2 was reset for &6" + player.getName()); + } + else + command.sendMessage(sender, NOT_FUNCTION, "&4That is not a valid function. Use up, down, or reset."); + } + } +} diff --git a/src/com/sucy/skill/cmd/CmdLevel.java b/src/com/sucy/skill/cmd/CmdLevel.java index d1b43c9f..1a9820df 100644 --- a/src/com/sucy/skill/cmd/CmdLevel.java +++ b/src/com/sucy/skill/cmd/CmdLevel.java @@ -30,95 +30,127 @@ import com.rit.sucy.commands.ConfigurableCommand; import com.rit.sucy.commands.IFunction; import com.rit.sucy.config.Filter; +import com.rit.sucy.config.parse.NumberParser; import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.enums.ExpSource; +import com.sucy.skill.api.player.PlayerClass; import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.language.RPGFilter; +import com.sucy.skill.manager.CmdManager; import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import java.util.regex.Pattern; + /** * A command that gives a player class levels */ -public class CmdLevel implements IFunction -{ - private static final String NOT_PLAYER = "not-player"; - private static final String NOT_NUMBER = "not-number"; - private static final String NOT_POSITIVE = "not-positive"; - private static final String GAVE_LEVEL = "gave-level"; - private static final String RECEIVED_LEVEL = "received-level"; - private static final String DISABLED = "world-disabled"; +public class CmdLevel implements IFunction { + private static final Pattern IS_NUMBER = Pattern.compile("[0-9]+"); + private static final Pattern IS_BOOL = Pattern.compile("(true)|(false)"); + + private static final String NOT_PLAYER = "not-player"; + private static final String NOT_POSITIVE = "not-positive"; + private static final String GAVE_LEVEL = "gave-level"; + private static final String RECEIVED_LEVEL = "received-level"; + private static final String DISABLED = "world-disabled"; + private static final String NO_CLASSES = "no-classes"; + + /** + * Runs the command + * + * @param cmd command that was executed + * @param plugin plugin reference + * @param sender sender of the command + * @param args argument list + */ + @Override + public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) { + // Disabled world + if (sender instanceof Player && !SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld()) + && args.length == 1) { + cmd.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); + } + + // Only can show info of a player so console needs to provide a name + else if ((args.length >= 1 && sender instanceof Player && IS_NUMBER.matcher(args[0]).matches()) + || (args.length >= 2 && !IS_NUMBER.matcher(args[0]).matches())) { + int numberIndex = IS_NUMBER.matcher(args[0]).matches() ? 0 : 1; + if (args.length > 1 && IS_NUMBER.matcher(args[1]).matches()) + numberIndex = 1; + + // Get the player data + OfflinePlayer target = numberIndex == 0 ? (OfflinePlayer) sender + : VersionManager.getOfflinePlayer(args[0], false); + if (target == null) { + cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "That is not a valid player name"); + return; + } + PlayerData data = SkillAPI.getPlayerData(target); + + // Parse the levels + int amount; + amount = NumberParser.parseInt(args[numberIndex]); - /** - * Runs the command - * - * @param cmd command that was executed - * @param plugin plugin reference - * @param sender sender of the command - * @param args argument list - */ - @Override - public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) - { - // Disabled world - if (sender instanceof Player && !SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld()) && args.length == 1) - { - cmd.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); - } + // Invalid amount of levels + if (amount <= 0) { + cmd.sendMessage(sender, NOT_POSITIVE, ChatColor.RED + "You must give a positive amount of levels"); + return; + } - // Only can show info of a player so console needs to provide a name - else if (args.length >= 1 && (args.length >= 2 || sender instanceof Player)) - { - // Get the player data - OfflinePlayer target = args.length == 1 ? (OfflinePlayer) sender : VersionManager.getOfflinePlayer(args[0], false); - if (target == null) - { - cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "That is not a valid player name"); - return; - } + int lastArg = args.length - 1; + boolean message = IS_BOOL.matcher(args[lastArg]).matches(); + boolean showMessage = !message || Boolean.parseBoolean(args[lastArg]); + if (message) + lastArg--; - // Parse the level - int amount; - try - { - amount = Integer.parseInt(args[args.length == 1 ? 0 : 1]); - } - catch (Exception ex) - { - cmd.sendMessage(sender, NOT_NUMBER, ChatColor.RED + "That is not a valid level amount"); - return; - } + // Give levels to a specific class group + boolean success; + if (numberIndex + 1 <= lastArg) { + PlayerClass playerClass = data.getClass(CmdManager.join(args, numberIndex + 1, lastArg)); + if (playerClass == null) { + CommandManager.displayUsage(cmd, sender); + return; + } - // Invalid amount of levels - if (amount <= 0) - { - cmd.sendMessage(sender, NOT_POSITIVE, ChatColor.RED + "You must give a positive amount of levels"); - return; - } + playerClass.giveLevels(amount); + success = true; + } - // Give levels - PlayerData data = SkillAPI.getPlayerData(target); - data.giveLevels(amount, ExpSource.COMMAND); + // Give levels + else + success = data.giveLevels(amount, ExpSource.COMMAND); - // Messages - if (target != sender) - { - cmd.sendMessage(sender, GAVE_LEVEL, ChatColor.DARK_GREEN + "You have given " + ChatColor.GOLD + "{player} {level} levels", Filter.PLAYER.setReplacement(target.getName()), RPGFilter.LEVEL.setReplacement("" + amount)); - } - if (target.isOnline()) - { - cmd.sendMessage(target.getPlayer(), RECEIVED_LEVEL, ChatColor.DARK_GREEN + "You have received " + ChatColor.GOLD + "{level} levels " + ChatColor.DARK_GREEN + "from " + ChatColor.GOLD + "{player}", Filter.PLAYER.setReplacement(sender.getName()), RPGFilter.LEVEL.setReplacement("" + amount)); - } - } + // Messages + if (showMessage) { + if (!success) { + cmd.sendMessage(sender, NO_CLASSES, + ChatColor.RED + "You aren't professed as a class that receives experience from commands", + Filter.PLAYER.setReplacement(target.getName()), + RPGFilter.LEVEL.setReplacement("" + amount)); + } else if (target != sender) { + cmd.sendMessage(sender, GAVE_LEVEL, + ChatColor.DARK_GREEN + "You have given " + ChatColor.GOLD + "{player} {level} levels", + Filter.PLAYER.setReplacement(target.getName()), + RPGFilter.LEVEL.setReplacement("" + amount)); + } + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), RECEIVED_LEVEL, + ChatColor.DARK_GREEN + "You have received " + ChatColor.GOLD + "{level} levels " + + ChatColor.DARK_GREEN + "from " + ChatColor.GOLD + "{player}", + Filter.PLAYER.setReplacement(sender.getName()), + RPGFilter.LEVEL.setReplacement("" + amount)); + } + } + } - // Not enough arguments - else - { - CommandManager.displayUsage(cmd, sender); - } - } -} + // Not enough arguments + else { + CommandManager.displayUsage(cmd, sender); + } + } +} \ No newline at end of file diff --git a/src/com/sucy/skill/cmd/CmdLore.java b/src/com/sucy/skill/cmd/CmdLore.java index 08caa8a8..f4d23066 100644 --- a/src/com/sucy/skill/cmd/CmdLore.java +++ b/src/com/sucy/skill/cmd/CmdLore.java @@ -64,7 +64,7 @@ public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender if (args.length >= 1 && sender instanceof Player) { Player player = (Player) sender; - ItemStack held = player.getInventory().getItemInHand(); + ItemStack held = player.getInventory().getItemInMainHand(); // No held item if (held == null) diff --git a/src/com/sucy/skill/cmd/CmdMana.java b/src/com/sucy/skill/cmd/CmdMana.java index fe10d98c..9254d8e2 100644 --- a/src/com/sucy/skill/cmd/CmdMana.java +++ b/src/com/sucy/skill/cmd/CmdMana.java @@ -30,11 +30,11 @@ import com.rit.sucy.commands.ConfigurableCommand; import com.rit.sucy.commands.IFunction; import com.rit.sucy.config.Filter; +import com.rit.sucy.config.parse.NumberParser; import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.enums.ManaSource; import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.api.util.NumberParser; import com.sucy.skill.language.RPGFilter; import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; diff --git a/src/com/sucy/skill/cmd/CmdPoints.java b/src/com/sucy/skill/cmd/CmdPoints.java index 11248256..aa4d2619 100644 --- a/src/com/sucy/skill/cmd/CmdPoints.java +++ b/src/com/sucy/skill/cmd/CmdPoints.java @@ -44,81 +44,77 @@ /** * A command that gives a player class experience */ -public class CmdPoints implements IFunction -{ - private static final String NOT_PLAYER = "not-player"; - private static final String NOT_NUMBER = "not-number"; - private static final String NOT_POSITIVE = "not-positive"; - private static final String GAVE_SP = "gave-points"; - private static final String RECEIVED_SP = "received-points"; - private static final String DISABLED = "world-disabled"; +public class CmdPoints implements IFunction { + private static final String NOT_PLAYER = "not-player"; + private static final String NOT_NUMBER = "not-number"; + private static final String NOT_POSITIVE = "not-positive"; + private static final String GAVE_SP = "gave-points"; + private static final String RECEIVED_SP = "received-points"; + private static final String DISABLED = "world-disabled"; - /** - * Runs the command - * - * @param cmd command that was executed - * @param plugin plugin reference - * @param sender sender of the command - * @param args argument list - */ - @Override - public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) - { - // Disabled world - if (sender instanceof Player && !SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld()) && args.length == 1) - { - cmd.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); - } + /** + * Runs the command + * + * @param cmd command that was executed + * @param plugin plugin reference + * @param sender sender of the command + * @param args argument list + */ + @Override + public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) { + // Disabled world + if (sender instanceof Player && !SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld()) + && args.length == 1) { + cmd.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); + } - // Only can show info of a player so console needs to provide a name - else if (args.length >= 1 && (args.length >= 2 || sender instanceof Player)) - { - // Get the player data - OfflinePlayer target = args.length == 1 ? (OfflinePlayer) sender : VersionManager.getOfflinePlayer(args[0], false); - if (target == null) - { - cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "That is not a valid player name"); - return; - } + // Only can show info of a player so console needs to provide a name + else if (args.length >= 1 && (args.length >= 2 || sender instanceof Player)) { + // Get the player data + OfflinePlayer target = args.length == 1 ? (OfflinePlayer) sender + : VersionManager.getOfflinePlayer(args[0], false); + if (target == null) { + cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "That is not a valid player name"); + return; + } - // Parse the skill points - int amount; - try - { - amount = Integer.parseInt(args[args.length == 1 ? 0 : 1]); - } - catch (Exception ex) - { - cmd.sendMessage(sender, NOT_NUMBER, ChatColor.RED + "That is not a valid skill point amount"); - return; - } + // Parse the skill points + int amount; + try { + amount = Integer.parseInt(args[args.length == 1 ? 0 : 1]); + } catch (Exception ex) { + cmd.sendMessage(sender, NOT_NUMBER, ChatColor.RED + "That is not a valid skill point amount"); + return; + } - // Invalid amount of skill points - if (amount <= 0) - { - cmd.sendMessage(sender, NOT_POSITIVE, ChatColor.RED + "You must give a positive amount of skill points"); - return; - } + // Invalid amount of skill points + if (amount <= 0) { + cmd.sendMessage(sender, NOT_POSITIVE, + ChatColor.RED + "You must give a positive amount of skill points"); + return; + } - // Give skill points - PlayerData data = SkillAPI.getPlayerData(target); - data.givePoints(amount, ExpSource.COMMAND); + // Give skill points + PlayerData data = SkillAPI.getPlayerData(target); + data.givePoints(amount, ExpSource.COMMAND); - // Messages - if (target != sender) - { - cmd.sendMessage(sender, GAVE_SP, ChatColor.DARK_GREEN + "You have given " + ChatColor.GOLD + "{player} {points} skill points", Filter.PLAYER.setReplacement(target.getName()), RPGFilter.POINTS.setReplacement("" + amount)); - } - if (target.isOnline()) - { - cmd.sendMessage(target.getPlayer(), RECEIVED_SP, ChatColor.DARK_GREEN + "You have received " + ChatColor.GOLD + "{points} skill points " + ChatColor.DARK_GREEN + "from " + ChatColor.GOLD + "{player}", Filter.PLAYER.setReplacement(sender.getName()), RPGFilter.POINTS.setReplacement("" + amount)); - } - } + // Messages + if (target != sender) { + cmd.sendMessage(sender, GAVE_SP, + ChatColor.DARK_GREEN + "You have given " + ChatColor.GOLD + "{player} {points} skill points", + Filter.PLAYER.setReplacement(target.getName()), RPGFilter.POINTS.setReplacement("" + amount)); + } + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), RECEIVED_SP, + ChatColor.DARK_GREEN + "You have received " + ChatColor.GOLD + "{points} skill points " + + ChatColor.DARK_GREEN + "from " + ChatColor.GOLD + "{player}", + Filter.PLAYER.setReplacement(sender.getName()), RPGFilter.POINTS.setReplacement("" + amount)); + } + } - // Not enough arguments - else - { - CommandManager.displayUsage(cmd, sender); - } - } -} + // Not enough arguments + else { + CommandManager.displayUsage(cmd, sender); + } + } +} \ No newline at end of file diff --git a/src/com/sucy/skill/cmd/CmdProfess.java b/src/com/sucy/skill/cmd/CmdProfess.java index 582c38e3..bc0e18f4 100644 --- a/src/com/sucy/skill/cmd/CmdProfess.java +++ b/src/com/sucy/skill/cmd/CmdProfess.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.cmd; -import com.rit.sucy.commands.CommandManager; import com.rit.sucy.commands.ConfigurableCommand; import com.rit.sucy.commands.IFunction; import com.sucy.skill.SkillAPI; @@ -48,6 +47,7 @@ public class CmdProfess implements IFunction private static final String PROFESSED = "professed"; private static final String CANNOT_PROFESS = "cannot-profess"; private static final String DISABLED = "world-disabled"; + private static final String NOT_AVAILABLE = "not-available"; /** * Runs the command @@ -69,16 +69,18 @@ public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender // Only players have profession options else if (sender instanceof Player) { + PlayerData data = SkillAPI.getPlayerData((Player) sender); + if (args.length == 0) { - CommandManager.displayUsage(cmd, sender); + if (!data.showProfession((Player) sender)) + cmd.sendMessage(sender, NOT_AVAILABLE, ChatColor.RED + "There's no profession available at this time"); } else { String name = args[0]; for (int i = 1; i < args.length; i++) name += ' ' + args[i]; - PlayerData data = SkillAPI.getPlayerData((Player) sender); RPGClass target = SkillAPI.getClass(name); // Invalid class diff --git a/src/com/sucy/skill/cmd/CmdRefund.java b/src/com/sucy/skill/cmd/CmdRefund.java new file mode 100644 index 00000000..56766302 --- /dev/null +++ b/src/com/sucy/skill/cmd/CmdRefund.java @@ -0,0 +1,175 @@ +/** + * SkillAPI + * com.sucy.skill.cmd.CmdRefund + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2016 Steven Sucy, 2019 iomatix + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cmd; + +import com.rit.sucy.commands.ConfigurableCommand; +import com.rit.sucy.commands.IFunction; +import com.rit.sucy.version.VersionManager; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.data.Permissions; + +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * Command for refunding invested skill points + */ +public class CmdRefund implements IFunction { + private static final String CANNOT_USE = "cannot-use"; + private static final String NOT_PLAYER = "not-player"; + private static final String NO_CLASS = "no-class"; + private static final String REFUNDED_ALL = "refunded-all"; + private static final String REFUNDED_AP = "refunded-ap"; + private static final String REFUNDED_SP = "refunded-sp"; + private static final String NOT_PLAYER_OR_ARG = "not-player-or-argument"; + private static final String NO_PERMISSION = "no-permission"; + + /** + * Runs the command + * + * @param cmd command that was executed + * @param plugin plugin reference + * @param sender sender of the command + * @param args argument list + */ + @Override + public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) { + + // Players + if (sender instanceof Player) { + // Reset all skills or attr + if (args.length >= 1) { + if (args[0].toLowerCase().equals("attr") || args[0].toLowerCase().equals("points")) { + PlayerData player = SkillAPI.getPlayerData((Player) sender); + // Player must have a class + if (!player.hasClass()) { + cmd.sendMessage(sender, NO_CLASS, "&4You have not professed as any class yet."); + return; + } + if (args[0].toLowerCase().equals("attr")) { + player.refundAttributes(); + cmd.sendMessage(sender, REFUNDED_AP, "&2Your attribute points have been refunded."); + } else if (args[0].toLowerCase().equals("points")) { + player.refundSkills(); + cmd.sendMessage(sender, REFUNDED_SP, "&2Your skill points have been refunded."); + } + } else { + if (!sender.hasPermission(Permissions.REFUND_OTHERS)) { + cmd.sendMessage(sender, NO_PERMISSION, "&cYou are not allowed to run this command."); + return; + } + OfflinePlayer target = VersionManager.getOfflinePlayer(args[0], false); + if (target == null) { + cmd.sendMessage(sender, NOT_PLAYER_OR_ARG, + ChatColor.RED + "That is not a valid player name or argument."); + return; + } + PlayerData player = SkillAPI.getPlayerData(target); + // Player must have a class + if (!player.hasClass()) { + cmd.sendMessage(sender, NO_CLASS, "&4Target has not professed as any class yet."); + return; + } + + if (args.length >= 2) { + if (args[1].toLowerCase().equals("attr")) { + player.refundAttributes(); + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), REFUNDED_AP, + "&2Your attribute points have been refunded."); + } + } else if (args[1].toLowerCase().equals("points")) { + player.refundSkills(); + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), REFUNDED_SP, + "&2Your skill points have been refunded."); + } + } + } else { + player.refundSkills(); + player.refundAttributes(); + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), REFUNDED_ALL, + "&2Your skill points and attribute points have been refunded."); + } + } + } + } else { + PlayerData player = SkillAPI.getPlayerData((Player) sender); + player.refundSkills(); + player.refundAttributes(); + cmd.sendMessage(sender, REFUNDED_ALL, "&2Your skill points and attribute points have been refunded."); + } + + } + + // Console + else { + if (args.length >= 1) { + if (!sender.hasPermission(Permissions.REFUND_OTHERS)) { + cmd.sendMessage(sender, NO_PERMISSION, "&cYou are not allowed to run this command."); + return; + } + + OfflinePlayer target = VersionManager.getOfflinePlayer(args[0], false); + if (target == null) { + cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "That is not a valid player name."); + return; + } + PlayerData player = SkillAPI.getPlayerData(target); + if (args.length >= 2) { + if (args[1].toLowerCase().equals("attr")) { + player.refundAttributes(); + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), REFUNDED_AP, + "&2Your attribute points have been refunded."); + } + }else if (args[1].toLowerCase().equals("points")) { + player.refundSkills(); + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), REFUNDED_SP, "&2Your skill points have been refunded."); + } + } + } else { + player.refundSkills(); + player.refundAttributes(); + if (target.isOnline()) { + cmd.sendMessage(target.getPlayer(), REFUNDED_ALL, + "&2Your skill points and attribute points have been refunded."); + } + } + + } else { + cmd.sendMessage(sender, NOT_PLAYER, ChatColor.RED + "Specify a valid player name."); + } + } + } +} diff --git a/src/com/sucy/skill/cmd/CmdScheme.java b/src/com/sucy/skill/cmd/CmdScheme.java deleted file mode 100644 index d70f3e93..00000000 --- a/src/com/sucy/skill/cmd/CmdScheme.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * SkillAPI - * com.sucy.skill.cmd.CmdScheme - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Steven Sucy - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software") to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.sucy.skill.cmd; - -import com.rit.sucy.commands.ConfigurableCommand; -import com.rit.sucy.commands.IFunction; -import com.rit.sucy.gui.MapScheme; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.language.RPGFilter; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - -import java.util.ArrayList; - -/** - * A command that allows a player to view their skill tree - */ -public class CmdScheme implements IFunction -{ - private static final String CANNOT_USE = "cannot-use"; - private static final String SCHEME_LIST = "scheme-list"; - private static final String DISABLED = "world-disabled"; - private static final String NOT_SCHEME = "not-scheme"; - private static final String SCHEME_SET = "scheme-set"; - - /** - * Runs the command - * - * @param cmd command that was executed - * @param plugin plugin reference - * @param sender sender of the command - * @param args argument list - */ - @Override - public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender, String[] args) - { - // Disabled world - if (!SkillAPI.getSettings().isMapTreeEnabled() || (sender instanceof Player && !SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld()))) - { - cmd.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); - } - - // Only plays have skills to view - else if (sender instanceof Player) - { - Player p = (Player) sender; - - // Scheme list - if (args.length == 0) - { - String list = ""; - ArrayList schemes = MapScheme.list((SkillAPI) plugin); - for (MapScheme scheme : schemes) - { - if (list.length() > 0) list += ", "; - list += scheme.getKey(); - } - cmd.sendMessage(sender, SCHEME_LIST, ChatColor.DARK_GREEN + "Available Schemes: " + ChatColor.GOLD + "{list}", RPGFilter.LIST.setReplacement(list)); - } - - // Choosing a scheme - else - { - String name = args[0]; - for (int i = 1; i < args.length; i++) - { - name += " " + args[i]; - } - Object scheme = MapScheme.get((SkillAPI) plugin, name); - if (scheme == null) - { - cmd.sendMessage(sender, NOT_SCHEME, ChatColor.RED + "That is not a valid scheme"); - } - else - { - SkillAPI.getPlayerData(p).setScheme(name); - cmd.sendMessage(sender, SCHEME_SET, ChatColor.DARK_GREEN + "Your scheme has been set to " + ChatColor.GOLD + "{scheme}", RPGFilter.SCHEME.setReplacement(name)); - } - } - } - - // Console doesn't have profession options - else - { - cmd.sendMessage(sender, CANNOT_USE, ChatColor.RED + "This cannot be used by the console"); - } - } -} diff --git a/src/com/sucy/skill/cmd/CmdSkill.java b/src/com/sucy/skill/cmd/CmdSkill.java index d5b53347..2a9a7415 100644 --- a/src/com/sucy/skill/cmd/CmdSkill.java +++ b/src/com/sucy/skill/cmd/CmdSkill.java @@ -28,15 +28,11 @@ import com.rit.sucy.commands.ConfigurableCommand; import com.rit.sucy.commands.IFunction; -import com.rit.sucy.gui.MapMenuManager; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.gui.Menu; import org.bukkit.ChatColor; -import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; /** @@ -71,27 +67,10 @@ public void execute(ConfigurableCommand cmd, Plugin plugin, CommandSender sender else if (sender instanceof Player) { Player p = (Player) sender; - if (SkillAPI.getSettings().isMapTreeEnabled()) + PlayerData data = SkillAPI.getPlayerData(p); + if (!data.showSkills(p)) { - ItemStack map = MapMenuManager.getData(Menu.SKILL_TREE).getMapItem(); - for (ItemStack i : p.getInventory().getContents()) - { - if (i != null && i.getType() == Material.MAP && i.getDurability() == map.getDurability()) - { - cmd.sendMessage(sender, MAP_OWNED, ChatColor.RED + "You already have the skill tree map"); - return; - } - } - cmd.sendMessage(sender, MAP_GIVEN, ChatColor.DARK_GREEN + "You were given the skill tree map. Hold it in your hand to view skills."); - p.getInventory().addItem(map); - } - else - { - PlayerData data = SkillAPI.getPlayerData(p); - if (!data.showSkills(p)) - { - cmd.sendMessage(sender, NO_SKILLS, ChatColor.RED + "You have no skills to view"); - } + cmd.sendMessage(sender, NO_SKILLS, ChatColor.RED + "You have no skills to view"); } } diff --git a/src/com/sucy/skill/cmd/CmdSkillMap.java b/src/com/sucy/skill/cmd/CmdSkillMap.java index c7da7d82..fc7648c8 100644 --- a/src/com/sucy/skill/cmd/CmdSkillMap.java +++ b/src/com/sucy/skill/cmd/CmdSkillMap.java @@ -30,7 +30,7 @@ import com.rit.sucy.commands.IFunction; import com.rit.sucy.gui.MapMenuManager; import com.sucy.skill.SkillAPI; -import com.sucy.skill.gui.Menu; +import com.sucy.skill.gui.map.Menu; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.command.CommandSender; diff --git a/src/com/sucy/skill/cmd/CmdSwitch.java b/src/com/sucy/skill/cmd/CmdSwitch.java new file mode 100644 index 00000000..0e4db0a7 --- /dev/null +++ b/src/com/sucy/skill/cmd/CmdSwitch.java @@ -0,0 +1,119 @@ +/** + * SkillAPI + * com.sucy.skill.cmd.CmdSwitch + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cmd; + +import com.rit.sucy.commands.ConfigurableCommand; +import com.rit.sucy.commands.IFunction; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.api.player.PlayerAccounts; +import com.sucy.skill.api.player.PlayerClass; +import com.sucy.skill.api.player.PlayerData; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.util.Map; + +public class CmdSwitch implements IFunction { + + private static final String NOT_PLAYER = "not-player"; + private static final String NOT_CLASS = "not-class"; + private static final String CHANGED = "account-changed"; + private static final String DISABLED = "world-disabled"; + + /** + * Executes the command + * + * @param command owning command + * @param plugin plugin reference + * @param sender sender of the command + * @param args arguments + */ + @Override + public void execute(ConfigurableCommand command, Plugin plugin, CommandSender sender, String[] args) + { + // Must be a player + if (!(sender instanceof Player)) + { + command.sendMessage(sender, NOT_PLAYER, "&4Only players can use this command"); + } + + // Disabled world + else if (!SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld())) + { + command.sendMessage(sender, DISABLED, "&4You cannot use this command in this world"); + } + + // Needs an argument + else if (args.length == 0) + { + command.displayHelp(sender); + } + + // Switch accounts if valid number + else + { + PlayerAccounts player = SkillAPI.getPlayerAccountData((Player) sender); + + RPGClass rpgClass = getRoot(SkillAPI.getClass(args[0])); + if (rpgClass != null) + { + boolean done = false; + for (Map.Entry entry : player.getAllData().entrySet()) { + PlayerClass accountClass = entry.getValue().getMainClass(); + if (accountClass != null && getRoot(accountClass.getData()) == rpgClass) { + player.setAccount(entry.getKey()); + done = true; + break; + } + } + if (!done) { + int i = 1; + if (player.getActiveData().getMainClass() != null) + while (player.getData(i) != null) i++; + player.setAccount(i); + player.getActiveData().profess(rpgClass); + } + command.sendMessage(sender, CHANGED, ChatColor.DARK_GREEN + "You have changed classes"); + return; + } + + command.sendMessage(sender, NOT_CLASS, ChatColor.RED + "That is not a valid class"); + } + } + + private RPGClass getRoot(RPGClass rpgClass) { + if (rpgClass == null) + return null; + + while (rpgClass.getParent() != null) + rpgClass = rpgClass.getParent(); + return rpgClass; + } +} diff --git a/src/com/sucy/skill/cmd/CmdUnbind.java b/src/com/sucy/skill/cmd/CmdUnbind.java index 46891b05..c9a1aa50 100644 --- a/src/com/sucy/skill/cmd/CmdUnbind.java +++ b/src/com/sucy/skill/cmd/CmdUnbind.java @@ -74,7 +74,7 @@ else if (!SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld())) else { - ItemStack item = ((Player) sender).getItemInHand(); + ItemStack item = ((Player) sender).getInventory().getItemInMainHand(); if (item == null || item.getType() == Material.AIR) { command.sendMessage(sender, NO_ITEM, "&4You are not holding an item"); @@ -83,15 +83,24 @@ else if (!SkillAPI.getSettings().isWorldEnabled(((Player) sender).getWorld())) PlayerData player = SkillAPI.getPlayerData((Player) sender); - if (!player.isBound(item.getType())) + if (player.isBound(item.getType())) { - command.sendMessage(sender, NOT_BOUND, "&4There are no skills bound to the held item"); - } + PlayerSkill skill = player.getBoundSkill(item.getType()); + player.clearBind(item.getType().toString()); + + command.sendMessage(sender, SKILL_BOUND, "&6{skill} &2has been unbound from &6{item}", RPGFilter.SKILL.setReplacement(skill.getData().getName()), RPGFilter.ITEM.setReplacement(TextFormatter.format(item.getType().name()))); + + }else if (item.getItemMeta().hasDisplayName() && player.isBound(item.getItemMeta().getDisplayName())) + { + PlayerSkill skill = player.getBoundSkill(item.getItemMeta().getDisplayName()); + player.clearBind(item.getItemMeta().getDisplayName()); + command.sendMessage(sender, SKILL_BOUND, "&6{skill} &2has been unbound from &6{item}", RPGFilter.SKILL.setReplacement(skill.getData().getName()), RPGFilter.ITEM.setReplacement(TextFormatter.format(item.getItemMeta().getDisplayName().replaceAll("§.", "")))); + + } else { - PlayerSkill skill = player.getBoundSkill(item.getType()); - player.clearBind(item.getType()); - command.sendMessage(sender, SKILL_BOUND, "&6{skill} &2has been unbound from &6{item}", RPGFilter.SKILL.setReplacement(skill.getData().getName()), RPGFilter.ITEM.setReplacement(TextFormatter.format(item.getType().name()))); + command.sendMessage(sender, NOT_BOUND, "&4There are no skills bound to the held item"); + } } } diff --git a/src/com/sucy/skill/cmd/CmdWorld.java b/src/com/sucy/skill/cmd/CmdWorld.java new file mode 100644 index 00000000..8acd6c55 --- /dev/null +++ b/src/com/sucy/skill/cmd/CmdWorld.java @@ -0,0 +1,64 @@ +/** + * SkillAPI + * com.sucy.skill.cmd.CmdWorld + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.cmd; + +import com.rit.sucy.commands.ConfigurableCommand; +import com.rit.sucy.commands.IFunction; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +public class CmdWorld implements IFunction { + + private static final String PLAYER_ONLY = "must-be-player"; + + @Override + public void execute(final ConfigurableCommand cmd, final Plugin plugin, final CommandSender sender, final String[] args) { + if (!(sender instanceof Player)) { + cmd.sendMessage(sender, PLAYER_ONLY, ChatColor.DARK_RED + "Only players can use this command"); + return; + } else if (args.length < 1) { + cmd.displayHelp(sender); + } + + String worldName = args[0]; + for (int i = 1; i < args.length; i++) { + worldName += " " + args[i]; + } + + World world = Bukkit.getWorld(worldName); + if (world == null) { + world = Bukkit.createWorld(new WorldCreator(worldName)); + } + + ((Player) sender).teleport(world.getSpawnLocation()); + } +} diff --git a/src/com/sucy/skill/data/Click.java b/src/com/sucy/skill/data/Click.java index 241d338f..0ff42df2 100644 --- a/src/com/sucy/skill/data/Click.java +++ b/src/com/sucy/skill/data/Click.java @@ -29,38 +29,52 @@ import com.rit.sucy.text.TextFormatter; import com.sucy.skill.SkillAPI; +import java.util.HashMap; +import java.util.Map; + /** * Represents a single click in a click combination */ public enum Click { - LEFT(1), - RIGHT(2), - SHIFT(3),; + LEFT(1, "L"), + RIGHT(2, "R"), + SHIFT(3, "S"), + LEFT_SHIFT(4, "LS"), + RIGHT_SHIFT(5, "RS"), + SPACE(6, "P"), + Q(7, "Q"); - public static final int BITS = 2; + public static final int BITS = 3; public static final int BIT_MASK = (1 << BITS) - 1; public static final int MAX_COMBO_SIZE = 30 / BITS; - private static final Click[] CLICKS = new Click[] { null, LEFT, RIGHT, SHIFT }; + private static final Click[] CLICKS = new Click[] { null, LEFT, RIGHT, SHIFT, LEFT_SHIFT, RIGHT_SHIFT, SPACE, Q }; private int id; + private String key; - Click(int id) + Click(int id, String key) { this.id = id; + this.key = key; } /** - * Gets the ID of the click type used in compiling combos - * - * @return ID of the click type + * @return numeric ID of the click type */ public int getId() { return id; } + /** + * @return config key for the click + */ + public String getKey() { + return key; + } + /** * Retrieves the formatted name of the click type * @@ -97,11 +111,13 @@ public static Click getByName(String name) { if (name == null) return null; name = name.toLowerCase(); - for (Click click : values()) - { - if (name.equals(click.getName().toLowerCase())) - return click; - } - return null; + return CLICK_MAP.get(name); } + + private static final Map CLICK_MAP = new HashMap() {{ + for (final Click click : Click.values()) { + put(click.name().toLowerCase(), click); + put(click.key.toLowerCase(), click); + } + }}; } diff --git a/src/com/sucy/skill/data/GroupSettings.java b/src/com/sucy/skill/data/GroupSettings.java index 215d595f..f07f45a2 100644 --- a/src/com/sucy/skill/data/GroupSettings.java +++ b/src/com/sucy/skill/data/GroupSettings.java @@ -26,36 +26,57 @@ */ package com.sucy.skill.data; +import com.google.common.collect.ImmutableList; import com.rit.sucy.config.parse.DataSection; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.log.Logger; + +import java.util.ArrayList; +import java.util.List; /** * Settings for class groups */ -public class GroupSettings -{ +public class GroupSettings { + private static final int[] POINTS = new int[] { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; + private static final String PROFESS_RESET = "profess-reset"; private static final String CAN_RESET = "can-reset"; private static final String EXP_LOST = "exp-lost-on-death"; private static final String STARTING_POINTS = "starting-points"; private static final String POINTS_PER_LEVEL = "points-per-level"; private static final String PERMISSION = "permission"; + private static final String FRIENDLY = "friendly"; private static final String DEFAULT = "default"; private static final String STARTING_ATTRIBS = "starting-attribs"; private static final String ATTRIB_PER_LEVEL = "attribs-per-level"; private static final String SCOREBOARD = "show-scoreboard"; + private static final String CUSTOM_POINTS = "use-custom-points"; + private static final String DEFINED_POINTS = "custom-points"; + private static final String CUSTOM_ATTRIBS = "use-custom-attribute-points"; + private static final String DEFINED_ATTRIBS = "custom-attribute-points"; - private String defaultClass; - private String permission; - private boolean professReset; - private boolean showScoreboard; - private boolean canReset; - private double deathPenalty; - private int startingPoints; - private double pointsPerLevel; - private double attribsPerLevel; - private int startingAttribs; + private String defaultClass = "none"; + private String permission = "none"; + private boolean professReset = false; + private boolean showScoreboard = true; + private boolean canReset = true; + private boolean friendly = false; + private boolean useCustomPoints = false; + private boolean useCustomAttribs = false; + private int[] customPoints = POINTS; + private int[] customAttribs = POINTS; + private double deathPenalty = 0; + private int startingPoints = 1; + private double pointsPerLevel = 1; + private double attribsPerLevel = 1; + private int startingAttribs = 0; + + /** + * Initializes group settings with default settings + */ + public GroupSettings() { } /** * Initializes a new set of settings for a class group by @@ -63,39 +84,51 @@ public class GroupSettings * * @param config config to load from */ - public GroupSettings(DataSection config) - { - this(); - + public GroupSettings(DataSection config) { defaultClass = config.getString(DEFAULT, defaultClass); permission = config.getString(PERMISSION, permission); professReset = config.getBoolean(PROFESS_RESET, professReset); showScoreboard = config.getBoolean(SCOREBOARD, showScoreboard); canReset = config.getBoolean(CAN_RESET, canReset); + friendly = config.getBoolean(FRIENDLY, friendly); deathPenalty = config.getDouble(EXP_LOST, deathPenalty); startingPoints = config.getInt(STARTING_POINTS, startingPoints); pointsPerLevel = config.getDouble(POINTS_PER_LEVEL, pointsPerLevel); attribsPerLevel = config.getDouble(ATTRIB_PER_LEVEL, attribsPerLevel); startingAttribs = config.getInt(STARTING_ATTRIBS, startingAttribs); - + useCustomPoints = config.getBoolean(CUSTOM_POINTS, false); + useCustomAttribs = config.getBoolean(CUSTOM_ATTRIBS, false); + customPoints = loadCustomPoints(config.getSection(DEFINED_POINTS)); + customAttribs = loadCustomPoints(config.getSection(DEFINED_ATTRIBS)); save(config); } + private int[] loadCustomPoints(final DataSection data) { + if (data != null) { + final List points = new ArrayList<>(); + for (final String key : data.keys()) { + try { + final int level = Integer.parseInt(key); + if (level < points.size()) { + points.set(level, data.getInt(key, 0)); + } else { + while (level > points.size()) { points.add(0); } + points.add(data.getInt(key, 0)); + } + } catch (final NumberFormatException ex) { + Logger.invalid(key + " is not a valid level for custom skill points"); + } + } + return points.stream().mapToInt(Integer::intValue).toArray(); + } + return POINTS; + } + /** - * Initializes a default collection of group settings + * @return true if players with the same class under this group are allies */ - public GroupSettings() - { - defaultClass = "none"; - permission = "none"; - professReset = false; - showScoreboard = true; - canReset = true; - deathPenalty = 0; - startingPoints = 1; - pointsPerLevel = 1; - attribsPerLevel = 1; - startingAttribs = 0; + public boolean isFriendly() { + return friendly; } /** @@ -103,8 +136,7 @@ public GroupSettings() * * @return default class of the group or null/"none" if none */ - public RPGClass getDefault() - { + public RPGClass getDefault() { return SkillAPI.getClass(defaultClass); } @@ -113,8 +145,7 @@ public RPGClass getDefault() * * @return true if requires a permission, false otherwise */ - public boolean requiresPermission() - { + public boolean requiresPermission() { return !permission.equals("none"); } @@ -123,8 +154,7 @@ public boolean requiresPermission() * * @return required permission or null if none */ - public String getPermission() - { + public String getPermission() { return requiresPermission() ? permission : null; } @@ -133,24 +163,21 @@ public String getPermission() * * @return true if resets upon profession, false otherwise */ - public boolean isProfessReset() - { + public boolean isProfessReset() { return professReset; } /** * @return true if the group is allowed to reset, false otherwise */ - public boolean canReset() - { + public boolean canReset() { return canReset; } /** * @return true if should show the scoreboard, false otherwise */ - public boolean isShowScoreboard() - { + public boolean isShowScoreboard() { return showScoreboard; } @@ -159,8 +186,7 @@ public boolean isShowScoreboard() * * @return death penalty */ - public double getDeathPenalty() - { + public double getDeathPenalty() { return deathPenalty; } @@ -169,8 +195,7 @@ public double getDeathPenalty() * * @return starting skill points */ - public int getStartingPoints() - { + public int getStartingPoints() { return startingPoints; } @@ -179,8 +204,7 @@ public int getStartingPoints() * * @return skill points per level */ - public double getPointsPerLevel() - { + public double getPointsPerLevel() { return pointsPerLevel; } @@ -192,9 +216,8 @@ public double getPointsPerLevel() * * @return gained points */ - public int getPointsForLevels(int newLevel, int oldLevel) - { - return (int) (newLevel * pointsPerLevel) - (int) (oldLevel * pointsPerLevel); + public int getPointsForLevels(int newLevel, int oldLevel) { + return computePoints(newLevel, oldLevel, useCustomPoints, customPoints, pointsPerLevel); } /** @@ -202,8 +225,7 @@ public int getPointsForLevels(int newLevel, int oldLevel) * * @return attribute points gained each level */ - public double getAttribsPerLevel() - { + public double getAttribsPerLevel() { return attribsPerLevel; } @@ -215,35 +237,130 @@ public double getAttribsPerLevel() * * @return gained points */ - public int getAttribsForLevels(int newLevel, int oldLevel) - { - return (int) (newLevel * attribsPerLevel) - (int) (oldLevel * attribsPerLevel); + public int getAttribsForLevels(int newLevel, int oldLevel) { + return computePoints(newLevel, oldLevel, useCustomAttribs, customAttribs, attribsPerLevel); } /** * @return attribute points classes in the group start with */ - public int getStartingAttribs() - { + public int getStartingAttribs() { return startingAttribs; } + private int computePoints(int newLevel, int oldLevel, boolean custom, int[] data, double perLevel) { + if (custom) { + int total = 0; + for (int i = oldLevel + 1; i < data.length && i <= newLevel; i++) { + total += data[i]; + } + return total; + } else { + return (int) (newLevel * perLevel) - (int) (oldLevel * perLevel); + } + } + /** * Saves the group settings to a config * * @param config config to save to */ - public void save(DataSection config) - { + public void save(DataSection config) { + config.setComments(DEFAULT, ImmutableList.of( + "", + " The starting class for all players for this group.", + " \"none\" will result in no starting class")); config.set(DEFAULT, defaultClass); + + config.setComments(PERMISSION, ImmutableList.of( + "", + " The permission required to profess as any class in this group.", + " \"none\" will result in no permissions being required")); config.set(PERMISSION, permission); + + config.setComments(PROFESS_RESET, ImmutableList.of( + "", + " Whether or not to reset a players level and skill points to starting values", + " when professing into a subclass")); config.set(PROFESS_RESET, professReset); + + config.setComments(CAN_RESET, ImmutableList.of( + "", + " Whether or not this class is reset when players use the reset command")); config.set(CAN_RESET, canReset); + + config.setComments(FRIENDLY, ImmutableList.of( + "", + " Whether or not players professed as the same base class in this group", + " are considered allies and cannot attack each other")); + config.set(FRIENDLY, friendly); + + config.setComments(SCOREBOARD, ImmutableList.of( + "", + " Whether or not to show a scoreboard for classes within this group.", + " Scoreboards must be enabled for this to work.")); config.set(SCOREBOARD, showScoreboard); + + config.setComments(EXP_LOST, ImmutableList.of( + "", + " Percentage of experience lost upon dying.", + " This will not cause players to lose levels.")); config.set(EXP_LOST, deathPenalty); + + config.setComments(STARTING_POINTS, ImmutableList.of( + "", + " Number of skill points players start with")); config.set(STARTING_POINTS, startingPoints); + + config.setComments(POINTS_PER_LEVEL, ImmutableList.of( + "", + " How many skill points a player gains every level.", + " You can use decimal values for one point every few levels.", + " 0.2, for instance, would give one point every 5 levels.", + " If use-custom-points is enabled, this is ignored")); config.set(POINTS_PER_LEVEL, pointsPerLevel); + + config.setComments(STARTING_ATTRIBS, ImmutableList.of("", + " Number of attribute points players start with")); config.set(STARTING_ATTRIBS, startingAttribs); + + config.setComments(ATTRIB_PER_LEVEL, ImmutableList.of("", + " How many attribute points a player gains every level.", + " You can use decimal values for one point every few levels.", + " 0.2, for instance, would give one point every 5 levels.", + " If use-custom-attribute-points is enabled, this is ignored.")); config.set(ATTRIB_PER_LEVEL, attribsPerLevel); + + config.setComments(CUSTOM_POINTS, ImmutableList.of("", + " Whether or not to use custom skill point distribution.", + " When enabled, skill points are given based on custom-points", + " instead of points-per-level")); + config.set(CUSTOM_POINTS, useCustomPoints); + + config.setComments(CUSTOM_ATTRIBS, ImmutableList.of("", + " Whether or not to use custom attribute point distribution.", + " When enabled, attribute points are given based on custom-attribute-points", + " instead of attribs-per-level")); + config.set(CUSTOM_ATTRIBS, useCustomAttribs); + + config.setComments(DEFINED_POINTS, ImmutableList.of("", + " Defines how many skill points players get at specific levels.", + " This only applies when use-custom-points is set to \"true\".", + " Numbers on the left are the level the skill points are given.", + " Numbers on the right are how many skill points are given.")); + savePoints(config.createSection(DEFINED_POINTS), customPoints); + + config.setComments(DEFINED_ATTRIBS, ImmutableList.of("", + " Defines how many attribute points players get at specific levels.", + " This only applies when use-custom-attribute-points is set to \"true\".", + " Numbers on the left are the level the attribute points are given.", + " Numbers on the right are how many attribute points are given.")); + savePoints(config.createSection(DEFINED_ATTRIBS), customAttribs); + } + + private void savePoints(final DataSection destination, final int[] points) { + for (int i = 0; i < points.length; i++) { + if (points[i] > 0) { destination.set(Integer.toString(i), points[i]); } + } } } diff --git a/src/com/sucy/skill/data/Permissions.java b/src/com/sucy/skill/data/Permissions.java index a265f169..28b92b84 100644 --- a/src/com/sucy/skill/data/Permissions.java +++ b/src/com/sucy/skill/data/Permissions.java @@ -34,6 +34,8 @@ public class Permissions private static final String ROOT = "skillapi."; public static final String BASIC = ROOT + "basic"; + public static final String REFUND = ROOT + "refund"; + public static final String REFUND_OTHERS = ROOT + "refund.others"; public static final String BACKUP = ROOT + "backup"; public static final String EXP = ROOT + "exp"; public static final String LVL = ROOT + "level"; @@ -45,4 +47,6 @@ public class Permissions public static final String FORCE = ROOT + "force"; public static final String LORE = ROOT + "lore"; public static final String ATTRIB = ROOT + "attrib"; + public static final String GUI = ROOT + "gui"; + public static final String WORLD = ROOT + "world"; } diff --git a/src/com/sucy/skill/data/PlayerEquips.java b/src/com/sucy/skill/data/PlayerEquips.java new file mode 100644 index 00000000..7114d795 --- /dev/null +++ b/src/com/sucy/skill/data/PlayerEquips.java @@ -0,0 +1,404 @@ +/** + * SkillAPI + * com.sucy.skill.data.PlayerEquips + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data; + +import com.google.common.base.Objects; +import com.rit.sucy.config.parse.NumberParser; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.api.player.PlayerClass; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.skills.Skill; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.sucy.skill.listener.ItemListener.ARMOR_TYPES; + +/** + * Handles keeping track of and applying attribute + * bonuses and requirements for items. + */ +public class PlayerEquips +{ + private static final ItemStack TEMP = new ItemStack(Material.BEDROCK); + + private PlayerData player; + + private EquipData empty = new EquipData(); + private EquipData weapon = empty; + private EquipData[] other; + + private int offhand = -1; + + /** + * @param player player data reference + */ + public PlayerEquips(PlayerData player) + { + this.player = player; + other = new EquipData[SkillAPI.getSettings().getSlots().length]; + for (int i = 0; i < other.length; i++) { + other[i] = empty; + if (SkillAPI.getSettings().getSlots()[i] == 40) + offhand = i; + } + } + + /** + * @return true if the player can hit something, false otherwise + */ + public boolean canHit() + { + return weapon.isMet(); + } + + public boolean canBlock() + { + return offhand >= 0 && other[offhand].isMet(); + } + + /** + * Updates all available items for the player + * + * @param player player to update for + */ + public void update(Player player) + { + PlayerInventory inv = player.getInventory(); + weapon = swap(inv, inv.getHeldItemSlot(), weapon, true); + for (int i = 0; i < other.length; i++) + other[i] = swap(inv, SkillAPI.getSettings().getSlots()[i], other[i], i == offhand); + } + + /** + * Handles swapping two items, handling any requirements + * + * @param inv inventory to manage + * @param index related index + * @param from old equip data + * + * @return the used equip data + */ + private EquipData swap(PlayerInventory inv, int index, EquipData from, boolean weapon) + { + EquipData to = make(inv.getItem(index)); + if (Objects.equal(from.item, to.item)) + { + return to; + } + + if (from.isMet() && (!weapon || !from.isArmor)) { + from.revert(); + } + + if (weapon && to.isArmor) { + return to; + } + else if (!to.isMet()) + { + if (SkillAPI.getSettings().isDropWeapon() || !weapon) { + inv.setItem(index, TEMP); + for (ItemStack item : inv.addItem(to.item).values()) + inv.getHolder().getWorld().dropItemNaturally(inv.getHolder().getLocation(), item); + inv.setItem(index, null); + return empty; + } + return to; + } + else + { + to.apply(); + return to; + } + } + + private boolean isArmor(final ItemStack item) { + return item != null && ARMOR_TYPES.contains(item.getType()); + } + + /** + * Clears the weapon slot + */ + public void clearWeapon() + { + if (weapon.isMet() && !weapon.isArmor) { + weapon.revert(); + } + weapon = empty; + } + + /** + * Updates the equipped weapon + * + * @param inv inventory data + */ + public void updateWeapon(PlayerInventory inv) + { + weapon = swap(inv, inv.getHeldItemSlot(), weapon, true); + } + + /** + * Makes data for the ItemStack if needed + * + * @param item item to make for + * + * @return item data + */ + private EquipData make(ItemStack item) + { + if (item == null) + return empty; + else + return new EquipData(item); + } + + /** + * Represents one available item's data + */ + private class EquipData + { + private HashMap skillReq; + private HashMap attrReq; + private HashMap attribs; + + private HashSet classReq; + private HashSet classExc; + + private ItemStack item; + private int levelReq; + private boolean isArmor; + + /** + * Sets up for an empty item slot + */ + EquipData() { } + + /** + * Scans an items for bonuses or requirements + * + * @param item item to grab data from + */ + EquipData(ItemStack item) + { + this.item = item; + this.isArmor = PlayerEquips.this.isArmor(item); + + if (!item.hasItemMeta()) + return; + + List lore = item.getItemMeta().getLore(); + if (lore == null) + return; + + Settings settings = SkillAPI.getSettings(); + String classText = settings.getLoreClassText(); + String excludeText = settings.getLoreExcludeText(); + String levelText = settings.getLoreLevelText(); + boolean skills = settings.isCheckSkillLore(); + boolean attributes = settings.isAttributesEnabled(); + + for (String line : lore) + { + String lower = ChatColor.stripColor(line).toLowerCase(); + + // Level requirements + if (lower.startsWith(levelText)) { + levelReq = NumberParser.parseInt(lower.substring(levelText.length())); + } + + // Class requirements + else if (lower.startsWith(classText)) { + List required = Arrays.asList(lower.substring(classText.length()).split(", ")); + if (classReq == null) + classReq = new HashSet<>(); + classReq.addAll(required); + } + + // Excluded classes + else if (lower.startsWith(excludeText)) { + List excluded = Arrays.asList(lower.substring(excludeText.length()).split(", ")); + if (classExc == null) + classExc = new HashSet<>(); + classExc.addAll(excluded); + } + + else + { + boolean done = false; + + // Skill requirements + if (skills) + { + for (Skill skill : SkillAPI.getSkills().values()) + { + String text = settings.getSkillText(skill.getName()); + if (lower.startsWith(text)) + { + done = true; + if (skillReq == null) + skillReq = new HashMap<>(); + + skillReq.put(skill.getName(), NumberParser.parseInt(lower.substring(text.length()))); + break; + } + } + } + + // Attribute requirements + if (attributes && !done) + { + for (String attr : SkillAPI.getAttributeManager().getLookupKeys()) + { + String text = settings.getAttrReqText(attr); + if (lower.startsWith(text)) + { + if (attrReq == null) + attrReq = new HashMap<>(); + + String normalized = SkillAPI.getAttributeManager().normalize(attr); + attrReq.put(normalized, NumberParser.parseInt(lower.substring(text.length()))); + break; + } + + text = settings.getAttrGiveText(attr); + if (lower.startsWith(text)) + { + if (attribs == null) + attribs = new HashMap<>(); + + String normalized = SkillAPI.getAttributeManager().normalize(attr); + int current = attribs.containsKey(attr) ? attribs.get(attr) : 0; + int extra = NumberParser.parseInt(lower.substring(text.length()).replace("%", "")); + attribs.put(normalized, current + extra); + break; + } + } + } + } + } + } + + /** + * Applies bonuse attributes for the item + */ + public void apply() + { + if (attribs != null) + for (Map.Entry entry : attribs.entrySet()) + player.addBonusAttributes(entry.getKey(), entry.getValue()); + } + + /** + * Reverts bonus attributes for the item + */ + void revert() + { + if (attribs != null) + for (Map.Entry entry : attribs.entrySet()) + player.addBonusAttributes(entry.getKey(), -entry.getValue()); + } + + public boolean isArmor() { + return isArmor; + } + + /** + * Checks for conditions of an item + * + * @return true if conditions are met + */ + boolean isMet() + { + if (item == null) { + return true; + } + + PlayerClass main = player.getMainClass(); + String className = main == null ? "null" : main.getData().getName().toLowerCase(); + if ((levelReq > 0 && (main == null || main.getLevel() < levelReq)) + || (classExc != null && main != null && classExc.contains(className)) + || (classReq != null && (main == null || !classReq.contains(className)))) + return false; + + if (classExc != null) + for (PlayerClass playerClass : player.getClasses()) + if (matches(classExc, playerClass)) + return false; + + if (classReq != null) { + boolean metClassReq = false; + for (PlayerClass playerClass : player.getClasses()) + if (matches(classReq, playerClass)) + metClassReq = true; + + if (!metClassReq) + return false; + } + + + for (PlayerClass playerClass : player.getClasses()) + if (!playerClass.getData().canUse(item.getType())) + return false; + + if (skillReq != null) + for (Map.Entry entry : skillReq.entrySet()) + if (player.getSkillLevel(entry.getKey()) < entry.getValue()) + return false; + + if (attrReq != null) + for (Map.Entry entry : attrReq.entrySet()) + if (player.getAttribute(entry.getKey()) < entry.getValue()) + return false; + + return true; + } + + private boolean matches(final Set names, final PlayerClass playerClass) { + if (playerClass == null) return false; + + RPGClass current = playerClass.getData(); + while (current != null) { + if (names.contains(current.getName().toLowerCase())) { + return true; + } + current = current.getParent(); + } + + return false; + } + } +} diff --git a/src/com/sucy/skill/data/PlayerSettings.java b/src/com/sucy/skill/data/PlayerSettings.java new file mode 100644 index 00000000..35f4a2c4 --- /dev/null +++ b/src/com/sucy/skill/data/PlayerSettings.java @@ -0,0 +1,35 @@ +/** + * SkillAPI + * com.sucy.skill.data.PlayerSettings + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data; + +public class PlayerSettings { + public boolean showCastMessages; + public boolean showLevelUpMessage; + public boolean showExpMessage; + public boolean showParticles; + public boolean oldHealthBar; +} diff --git a/src/com/sucy/skill/data/Point2D.java b/src/com/sucy/skill/data/Point2D.java new file mode 100644 index 00000000..df959ff0 --- /dev/null +++ b/src/com/sucy/skill/data/Point2D.java @@ -0,0 +1,57 @@ +/** + * SkillAPI + * com.sucy.skill.data.Point2D + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data; + +public class Point2D +{ + public double x; + public double y; + + public Point2D() + { + this.x = this.y = 0; + } + + public Point2D(double x, double y) + { + this.x = x; + this.y = y; + } + + public void rotate(double cos, double sin) + { + double temp = x * cos - y * sin; + y = x * sin + y * cos; + x = temp; + } + + @Override + public String toString() + { + return "(" + String.format("%.2f", x) + ", " + String.format("%.2f", y) + ")"; + } +} diff --git a/src/com/sucy/skill/data/Point3D.java b/src/com/sucy/skill/data/Point3D.java new file mode 100644 index 00000000..b64fb3cd --- /dev/null +++ b/src/com/sucy/skill/data/Point3D.java @@ -0,0 +1,52 @@ +/** + * SkillAPI + * com.sucy.skill.data.Point + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data; + +public class Point3D +{ + public double x; + public double y; + public double z; + + public Point3D() + { + this.x = this.y = this.z = 0; + } + + public Point3D(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public String toString() + { + return "(" + String.format("%.2f", x) + ", " + String.format("%.2f", y) + ", " + String.format("%.2f", z) + ")"; + } +} diff --git a/src/com/sucy/skill/data/Settings.java b/src/com/sucy/skill/data/Settings.java index ec2b34bb..578682ad 100644 --- a/src/com/sucy/skill/data/Settings.java +++ b/src/com/sucy/skill/data/Settings.java @@ -26,33 +26,50 @@ */ package com.sucy.skill.data; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.rit.sucy.config.CommentedConfig; import com.rit.sucy.config.parse.DataSection; -import com.rit.sucy.player.Protection; +import com.rit.sucy.config.parse.NumberParser; import com.rit.sucy.text.TextFormatter; +import com.rit.sucy.version.VersionManager; +//import com.sucy.party.Parties; +//import com.sucy.party.Party; +import iomatix.spigot.RPGParty.Party; +import iomatix.spigot.RPGParty.Parties; import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.CombatProtection; +import com.sucy.skill.api.DefaultCombatProtection; +import com.sucy.skill.api.player.PlayerClass; import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.cast.IndicatorSettings; +import com.sucy.skill.data.formula.Formula; +import com.sucy.skill.data.formula.value.CustomValue; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.hook.NoCheatHook; -import com.sucy.skill.hook.PluginChecker; +import com.sucy.skill.gui.tool.GUITool; import com.sucy.skill.log.Logger; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.entity.*; +import org.bukkit.entity.Animals; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.material.MaterialData; +//import org.bukkit.material.MaterialData; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** *

The management class for SkillAPI's config.yml settings.

*/ -public class Settings -{ +public class Settings { private HashMap groups = new HashMap(); @@ -67,12 +84,10 @@ public class Settings * * @param plugin SkillAPI plugin reference */ - public Settings(SkillAPI plugin) - { + public Settings(SkillAPI plugin) { this.plugin = plugin; CommentedConfig file = new CommentedConfig(plugin, "config"); file.checkDefaults(); - file.trim(); file.save(); config = file.getConfig(); reload(); @@ -83,14 +98,15 @@ public Settings(SkillAPI plugin) *

This will fill in any missing values with default values * and trim any values that aren't supposed to be there.

*/ - public void reload() - { + public void reload() { + loadExperienceSettings(); loadAccountSettings(); loadClassSettings(); loadManaSettings(); loadSkillSettings(); loadItemSettings(); loadGUISettings(); + loadCastSettings(); loadComboSettings(); loadExpSettings(); loadSkillBarSettings(); @@ -98,6 +114,78 @@ public void reload() loadWorldSettings(); loadSaveSettings(); loadTargetingSettings(); + loadWorldGuardSettings(); + } + + /////////////////////////////////////////////////////// + // // + // Experience Settings // + // // + /////////////////////////////////////////////////////// + + private static final String DEFAULT_YIELD = "default"; + + private Map> breakYields; + private Map> placeYields; + private Map> craftYields; + + private boolean trackBreak; + private boolean yieldsEnabled; + + public void loadExperienceSettings() { + CommentedConfig file = new CommentedConfig(plugin, "exp"); + file.checkDefaults(); + file.save(); + DataSection config = file.getConfig(); + + DataSection breakData = config.getSection("break"); + yieldsEnabled = config.getBoolean("enabled", false); + trackBreak = breakData.getBoolean("allow-replace", true); + breakYields = loadYields(breakData.getSection("types")); + placeYields = loadYields(config.getSection("place")); + craftYields = loadYields(config.getSection("craft")); + } + + private Map> loadYields(DataSection config) { + Map> yields = new HashMap>(); + for (String className : config.keys()) { + HashMap map = new HashMap(); + DataSection classYields = config.getSection(className); + for (String type : classYields.keys()) { + map.put(type.toUpperCase().replace(" ", "_"), classYields.getDouble(type)); + } + yields.put(className, map); + } + return yields; + } + + public boolean trackBreaks() { + return trackBreak; + } + + public boolean yieldsEnabled() { + return yieldsEnabled; + } + + public double getBreakYield(PlayerClass playerClass, Material mat) { + return getYield(breakYields, playerClass, mat.name()); + } + + public double getPlaceYield(PlayerClass playerClass, Material mat) { + return getYield(placeYields, playerClass, mat.name()); + } + + public double getCraftYield(PlayerClass playerClass, Material mat) { + return getYield(craftYields, playerClass, mat.name()); + } + + private double getYield(Map> yields, PlayerClass playerClass, String key) { + double yield = getYield(yields.get(playerClass.getData().getName()), key); + return yield > 0 ? yield : getYield(yields.get(DEFAULT_YIELD), key); + } + + private double getYield(Map yields, String key) { + return yields == null ? 0 : (yields.containsKey(key) ? yields.get(key) : 0); } /////////////////////////////////////////////////////// @@ -106,24 +194,25 @@ public void reload() // // /////////////////////////////////////////////////////// - public void loadGroupSettings() - { + public void loadGroupSettings() { CommentedConfig file = new CommentedConfig(plugin, "groups"); DataSection config = file.getConfig(); groups.clear(); - for (String key : config.keys()) - { + for (String key : config.keys()) { groups.put(key.toLowerCase(), new GroupSettings(config.getSection(key))); } - for (String group : SkillAPI.getGroups()) - { - if (!groups.containsKey(group.toLowerCase())) - { + for (String group : SkillAPI.getGroups()) { + if (!groups.containsKey(group.toLowerCase())) { GroupSettings settings = new GroupSettings(); groups.put(group.toLowerCase(), settings); settings.save(config.createSection(group.toLowerCase())); } + config.setComments(group.toLowerCase(), ImmutableList.of( + "", + " Settings for classes with the group " + group, + " If new classes are loaded with different groups,", + " the new groups will show up in this file after the first load.")); } file.save(); @@ -136,10 +225,8 @@ public void loadGroupSettings() * * @return settings for the class group */ - public GroupSettings getGroupSettings(String group) - { - if (!groups.containsKey(group.toLowerCase())) - { + public GroupSettings getGroupSettings(String group) { + if (!groups.containsKey(group.toLowerCase())) { return new GroupSettings(); } return groups.get(group.toLowerCase()); @@ -169,8 +256,7 @@ public GroupSettings getGroupSettings(String group) * * @return main class group */ - public String getMainGroup() - { + public String getMainGroup() { return mainGroup; } @@ -180,8 +266,7 @@ public String getMainGroup() * * @return true if enabled, false otherwise */ - public boolean isOnePerClass() - { + public boolean isOnePerClass() { return onePerClass; } @@ -190,8 +275,7 @@ public boolean isOnePerClass() * * @return max accounts allowed for most players */ - public int getMaxAccounts() - { + public int getMaxAccounts() { return maxAccounts; } @@ -203,50 +287,39 @@ public int getMaxAccounts() * * @return number of allowed accounts */ - public int getMaxAccounts(Player player) - { - if (player == null) - { + public int getMaxAccounts(Player player) { + if (player == null) { return maxAccounts; } int max = maxAccounts; - for (Map.Entry entry : permAccounts.entrySet()) - { - if (player.hasPermission(entry.getKey())) - { + for (Map.Entry entry : permAccounts.entrySet()) { + if (player.hasPermission(entry.getKey())) { max = Math.max(max, entry.getValue()); } } return max; } - private void loadAccountSettings() - { + private void loadAccountSettings() { mainGroup = config.getString(ACCOUNT_MAIN); onePerClass = config.getBoolean(ACCOUNT_EACH); maxAccounts = config.getInt(ACCOUNT_MAX); // Permission account amounts List list = config.getList(ACCOUNT_PERM); - for (String item : list) - { - if (!item.contains(":")) - { + for (String item : list) { + if (!item.contains(":")) { continue; } String[] pieces = item.split(":"); - if (pieces.length != 2) - { + if (pieces.length != 2) { continue; } - try - { + try { permAccounts.put(pieces[0], Integer.parseInt(pieces[1])); - } - catch (Exception ex) - { + } catch (Exception ex) { // Invalid setting value } } @@ -262,6 +335,9 @@ private void loadAccountSettings() private static final String TARGET_MONSTER = TARGET_BASE + "monsters-enemy"; private static final String TARGET_PASSIVE = TARGET_BASE + "passive-ally"; private static final String TARGET_PLAYER = TARGET_BASE + "player-ally"; + private static final String TARGET_PARTIES = TARGET_BASE + "parties-ally"; + private static final String TARGET_NPC = TARGET_BASE + "affect-npcs"; + private static final String TARGET_STANDS = TARGET_BASE + "affect-armor-stands"; private ArrayList monsterWorlds = new ArrayList(); private ArrayList passiveWorlds = new ArrayList(); @@ -270,6 +346,11 @@ private void loadAccountSettings() private boolean monsterEnemy; private boolean passiveAlly; private boolean playerAlly; + private boolean partiesAlly; + private boolean affectNpcs; + private boolean affectArmorStands; + + private CombatProtection combatProtection = new DefaultCombatProtection(); /** * Checks whether or not something can be attacked @@ -279,46 +360,34 @@ private void loadAccountSettings() * * @return true if can be attacked, false otherwise */ - public boolean canAttack(LivingEntity attacker, LivingEntity target) - { - if (attacker instanceof Player) - { - if (target instanceof Animals && !(target instanceof Tameable)) - { - if (passiveAlly || passiveWorlds.contains(attacker.getWorld().getName())) - return false; - } - else if (target instanceof Monster) - { - if (monsterEnemy || monsterWorlds.contains(attacker.getWorld().getName())) - return true; - } - else if (target instanceof Player) - { - if (playerAlly || playerWorlds.contains(attacker.getWorld().getName())) - return false; + public boolean canAttack(LivingEntity attacker, LivingEntity target) { + if (attacker instanceof Player) { + final Player player = (Player) attacker; + if (target instanceof Animals && !(target instanceof Tameable)) { + if (passiveAlly || passiveWorlds.contains(attacker.getWorld().getName())) { return false; } + } else if (target instanceof Monster) { + if (monsterEnemy || monsterWorlds.contains(attacker.getWorld().getName())) { return true; } + } else if (target instanceof Player) { + if (playerAlly || playerWorlds.contains(attacker.getWorld().getName())) { return false; } + + if (partiesAlly) { + final Parties parties = Parties.getPlugin(Parties.class); + final Party p1 = parties.getJoinedParty(player); + final Party p2 = parties.getJoinedParty((Player) target); + return p1 == null || p1 != p2; + } + return combatProtection.canAttack(player, (Player) target); } - } - else if (attacker instanceof Tameable) - { + return combatProtection.canAttack(player, target); + } else if (attacker instanceof Tameable) { Tameable tameable = (Tameable) attacker; - if (tameable.isTamed() && (tameable.getOwner() instanceof LivingEntity)) - { + if (tameable.isTamed() && (tameable.getOwner() instanceof LivingEntity)) { return (tameable.getOwner() != target) - && canAttack((LivingEntity) tameable.getOwner(), target); + && canAttack((LivingEntity) tameable.getOwner(), target); } - } - boolean canAttack; - if (PluginChecker.isNoCheatActive() && attacker instanceof Player) - { - Player player = (Player) attacker; - NoCheatHook.exempt(player); - canAttack = Protection.canAttack(attacker, target); - NoCheatHook.unexempt(player); - } - else canAttack = Protection.canAttack(attacker, target); + } else { return !(target instanceof Monster); } - return canAttack; + return combatProtection.canAttack(attacker, target); } /** @@ -329,33 +398,49 @@ else if (attacker instanceof Tameable) * * @return true if an ally, false otherwise */ - public boolean isAlly(LivingEntity attacker, LivingEntity target) - { + public boolean isAlly(LivingEntity attacker, LivingEntity target) { return !canAttack(attacker, target); } - private void loadTargetingSettings() - { - if (config.isList(TARGET_MONSTER)) - { + /** + * Checks whether or not a target is a valid target. + * + * @param target target to check + * @return true if a valid target, false otherwise + */ + public boolean isValidTarget(final LivingEntity target) { + return (!target.hasMetadata("NPC") || affectNpcs) + && (!target.getType().name().equals("ARMOR_STAND") || affectArmorStands); + } + + /** + * Swaps out the default combat protection for a custom one + * + * @param combatProtection combat protection to use + */ + public void setCombatProtection(final CombatProtection combatProtection) { + this.combatProtection = combatProtection; + } + + private void loadTargetingSettings() { + if (config.isList(TARGET_MONSTER)) { monsterWorlds.addAll(config.getList(TARGET_MONSTER)); monsterEnemy = false; - } - else monsterEnemy = config.getBoolean(TARGET_MONSTER); + } else { monsterEnemy = config.getBoolean(TARGET_MONSTER); } - if (config.isList(TARGET_PASSIVE)) - { + if (config.isList(TARGET_PASSIVE)) { passiveWorlds.addAll(config.getList(TARGET_PASSIVE)); passiveAlly = false; - } - else passiveAlly = config.getBoolean(TARGET_PASSIVE); + } else { passiveAlly = config.getBoolean(TARGET_PASSIVE); } - if (config.isList(TARGET_PLAYER)) - { + if (config.isList(TARGET_PLAYER)) { playerWorlds.addAll(config.getList(TARGET_PLAYER)); playerAlly = false; - } - else playerAlly = config.getBoolean(TARGET_PLAYER); + } else { playerAlly = config.getBoolean(TARGET_PLAYER); } + + partiesAlly = config.getBoolean(TARGET_PARTIES); + affectArmorStands = config.getBoolean(TARGET_STANDS); + affectNpcs = config.getBoolean(TARGET_NPC); } /////////////////////////////////////////////////////// @@ -373,6 +458,7 @@ private void loadTargetingSettings() private boolean auto; private boolean useSql; private int minutes; + private int sqlDelay; private String sqlHost; private String sqlPort; @@ -385,8 +471,7 @@ private void loadTargetingSettings() * * @return true if enabled, false otherwise */ - public boolean isAutoSave() - { + public boolean isAutoSave() { return auto; } @@ -395,8 +480,7 @@ public boolean isAutoSave() * * @return frequency of saves */ - public int getSaveFreq() - { + public int getSaveFreq() { return minutes * 60 * 20; } @@ -405,8 +489,7 @@ public int getSaveFreq() * * @return true if enabled, false otherwise */ - public boolean isUseSql() - { + public boolean isUseSql() { return useSql; } @@ -415,8 +498,7 @@ public boolean isUseSql() * * @return host IP for SQL database */ - public String getSQLHost() - { + public String getSQLHost() { return sqlHost; } @@ -425,8 +507,7 @@ public String getSQLHost() * * @return host port for SQL database */ - public String getSQLPort() - { + public String getSQLPort() { return sqlPort; } @@ -435,8 +516,7 @@ public String getSQLPort() * * @return SQL database name */ - public String getSQLDatabase() - { + public String getSQLDatabase() { return sqlDatabase; } @@ -445,8 +525,7 @@ public String getSQLDatabase() * * @return SQL database username */ - public String getSQLUser() - { + public String getSQLUser() { return sqlUser; } @@ -455,20 +534,26 @@ public String getSQLUser() * * @return SQL database password */ - public String getSQLPass() - { + public String getSQLPass() { return sqlPass; } - private void loadSaveSettings() - { + /** + * @return time in milliseconds to wait before loading SQL data + */ + public int getSqlDelay() { + return sqlDelay; + } + + private void loadSaveSettings() { auto = config.getBoolean(SAVE_AUTO); minutes = config.getInt(SAVE_MINS); useSql = config.getBoolean(SAVE_SQL); - if (useSql) - { - DataSection details = config.getSection(SAVE_SQLD); + DataSection details = config.getSection(SAVE_SQLD); + sqlDelay = details.getInt("delay"); + + if (useSql) { sqlHost = details.getString("host"); sqlPort = details.getString("port"); sqlDatabase = details.getString("database"); @@ -503,8 +588,7 @@ private void loadSaveSettings() * * @return true if enabled, false otherwise */ - public boolean isModifyHealth() - { + public boolean isModifyHealth() { return modifyHealth; } @@ -513,8 +597,7 @@ public boolean isModifyHealth() * * @return default health for classless players */ - public int getDefaultHealth() - { + public int getDefaultHealth() { return defaultHealth; } @@ -523,8 +606,7 @@ public int getDefaultHealth() * * @return true if shown, false otherwise */ - public boolean isShowingAutoSkills() - { + public boolean isShowingAutoSkills() { return showAutoSkills; } @@ -533,8 +615,7 @@ public boolean isShowingAutoSkills() * * @return true if enabled, false otherwise */ - public boolean isAttributesEnabled() - { + public boolean isAttributesEnabled() { return attributesEnabled; } @@ -543,8 +624,7 @@ public boolean isAttributesEnabled() * * @return true if can refund, false otherwise */ - public boolean isAttributesDowngrade() - { + public boolean isAttributesDowngrade() { return attributesDowngrade; } @@ -554,8 +634,7 @@ public boolean isAttributesDowngrade() * * @return true if one is available, false otherwise */ - public boolean hasLevelUpEffect() - { + public boolean hasLevelUpEffect() { return getLevelUpSkill() != null; } @@ -564,14 +643,12 @@ public boolean hasLevelUpEffect() * * @return skill for level up effects */ - public DynamicSkill getLevelUpSkill() - { + public DynamicSkill getLevelUpSkill() { Skill skill = SkillAPI.getSkill(levelUpSkill); return (skill instanceof DynamicSkill) ? (DynamicSkill) skill : null; } - private void loadClassSettings() - { + private void loadClassSettings() { modifyHealth = config.getBoolean(CLASS_MODIFY); defaultHealth = config.getInt(CLASS_HP); showAutoSkills = config.getBoolean(CLASS_SHOW); @@ -598,8 +675,7 @@ private void loadClassSettings() * * @return true if enabled, false otherwise */ - public boolean isManaEnabled() - { + public boolean isManaEnabled() { return manaEnabled; } @@ -608,13 +684,11 @@ public boolean isManaEnabled() * * @return the frequency of mana gain */ - public int getGainFreq() - { + public int getGainFreq() { return gainFreq; } - private void loadManaSettings() - { + private void loadManaSettings() { manaEnabled = config.getBoolean(MANA_ENABLED); gainFreq = (int) (config.getDouble(MANA_FREQ) * 20); } @@ -644,8 +718,7 @@ private void loadManaSettings() * * @return true if allowed, false otherwise */ - public boolean isAllowDowngrade() - { + public boolean isAllowDowngrade() { return allowDowngrade; } @@ -654,16 +727,14 @@ public boolean isAllowDowngrade() * * @return true if enabled, false otherwise */ - public boolean isShowSkillMessages() - { + public boolean isShowSkillMessages() { return showSkillMessages; } /** * @return whether or not knockback should be applied when dealing 0 damage */ - public boolean isKnockback() - { + public boolean isKnockback() { return knockback; } @@ -672,8 +743,7 @@ public boolean isKnockback() * * @return skill message radius */ - public int getMessageRadius() - { + public int getMessageRadius() { return messageRadius; } @@ -682,13 +752,11 @@ public int getMessageRadius() * * @return list of blocks */ - public List getFilteredBlocks() - { + public List getFilteredBlocks() { return filteredBlocks; } - private void loadSkillSettings() - { + private void loadSkillSettings() { allowDowngrade = config.getBoolean(SKILL_DOWNGRADE); showSkillMessages = config.getBoolean(SKILL_MESSAGE); messageRadius = config.getInt(SKILL_RADIUS); @@ -696,29 +764,20 @@ private void loadSkillSettings() filteredBlocks = new ArrayList(); List list = config.getList(SKILL_BLOCKS); - for (String item : list) - { + for (String item : list) { item = item.toUpperCase().replace(' ', '_'); - if (item.endsWith("*")) - { + if (item.endsWith("*")) { item = item.substring(0, item.length() - 1); - for (Material mat : Material.values()) - { - if (mat.name().contains(item)) - { + for (Material mat : Material.values()) { + if (mat.name().contains(item)) { filteredBlocks.add(mat); } } - } - else - { - try - { + } else { + try { Material mat = Material.valueOf(item); filteredBlocks.add(mat); - } - catch (Exception ex) - { + } catch (Exception ex) { Logger.invalid("Invalid block type \"" + item + "\""); } } @@ -742,60 +801,56 @@ private void loadSkillSettings() private static final String ITEM_EXCLUDE = ITEM_BASE + "lore-exclude-text"; private static final String ITEM_ATTR = ITEM_BASE + "lore-attribute-text"; private static final String ITEM_STATS = ITEM_BASE + "attribute-text"; - private static final String ITEM_CHECK = ITEM_BASE + "players-per-check"; + private static final String ITEM_SLOTS = ITEM_BASE + "slots"; private boolean checkLore; private boolean checkAttribs; private boolean checkSkills; private boolean dropWeapon; private String loreClassText; - private String loreSkillText; private String loreLevelText; private String loreExcludeText; - private String reqAttrText; - private String giveAttrText; - private int playersPerCheck; + private int[] slots; + + private String skillPre, skillPost; + private String attrReqPre, attrReqPost; + private String attrPre, attrPost; /** * Checks whether or not lore requirements are enabled * * @return true if enabled, false otherwise */ - public boolean isCheckLore() - { + public boolean isCheckLore() { return checkLore; } /** * @return true if should check for skill requirements */ - public boolean isCheckSkillLore() - { + public boolean isCheckSkillLore() { return checkSkills; } /** * @return true if should check for attribute bonuses */ - public boolean isCheckAttributes() - { + public boolean isCheckAttributes() { return checkAttribs; } /** * @return checks if weapons are dropped when hovered */ - public boolean isDropWeapon() - { + public boolean isDropWeapon() { return dropWeapon; } /** * @return lore for skill requirements */ - public String getSkillText() - { - return loreSkillText; + public String getSkillText(String skill) { + return skillPre + skill + skillPost; } /** @@ -803,8 +858,7 @@ public String getSkillText() * * @return lore text for class requirements */ - public String getLoreClassText() - { + public String getLoreClassText() { return loreClassText; } @@ -813,8 +867,7 @@ public String getLoreClassText() * * @return lore text for level requirements */ - public String getLoreLevelText() - { + public String getLoreLevelText() { return loreLevelText; } @@ -823,8 +876,7 @@ public String getLoreLevelText() * * @return lore text for excluded classes */ - public String getLoreExcludeText() - { + public String getLoreExcludeText() { return loreExcludeText; } @@ -833,42 +885,52 @@ public String getLoreExcludeText() * * @return lore text for attributes */ - public String getAttrReqText() - { - return reqAttrText; + public String getAttrReqText(String attr) { + return attrReqPre + attr + attrReqPost; } /** * @return lore text for giving attributes */ - public String getAttrGiveText() - { - return giveAttrText; + public String getAttrGiveText(String attr) { + return attrPre + attr + attrPost; } /** - * Retrieves the number of players checked each update - * - * @return number of players checked each update + * @return slots checked for requirements and attributes */ - public int getPlayersPerCheck() - { - return playersPerCheck; + public int[] getSlots() { + return slots; } - private void loadItemSettings() - { + private void loadItemSettings() { checkLore = config.getBoolean(ITEM_LORE); dropWeapon = config.getBoolean(ITEM_DROP); checkSkills = config.getBoolean(ITEM_SKILLS); checkAttribs = config.getBoolean(ITEM_ATTRIBS); - loreClassText = config.getString(ITEM_CLASS); - loreSkillText = config.getString(ITEM_SKILL); - loreLevelText = config.getString(ITEM_LEVEL); - loreExcludeText = config.getString(ITEM_EXCLUDE); - reqAttrText = config.getString(ITEM_ATTR); - giveAttrText = config.getString(ITEM_STATS); - playersPerCheck = config.getInt(ITEM_CHECK); + loreClassText = config.getString(ITEM_CLASS).toLowerCase(); + loreLevelText = config.getString(ITEM_LEVEL).toLowerCase(); + loreExcludeText = config.getString(ITEM_EXCLUDE).toLowerCase(); + + String temp = config.getString(ITEM_SKILL).toLowerCase(); + int index = temp.indexOf('{'); + skillPre = temp.substring(0, index); + skillPost = temp.substring(index + 7); + + temp = config.getString(ITEM_ATTR).toLowerCase(); + index = temp.indexOf('{'); + attrReqPre = temp.substring(0, index); + attrReqPost = temp.substring(index + 6); + + temp = config.getString(ITEM_STATS).toLowerCase(); + index = temp.indexOf('{'); + attrPre = temp.substring(0, index); + attrPost = temp.substring(index + 6); + + List slotList = config.getList(ITEM_SLOTS); + if (!VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { slotList.remove("40"); } + slots = new int[slotList.size()]; + for (int i = 0; i < slots.length; i++) { slots[i] = NumberParser.parseInt(slotList.get(i)); } } /////////////////////////////////////////////////////// @@ -878,23 +940,24 @@ private void loadItemSettings() /////////////////////////////////////////////////////// private static final String - GUI_BASE = "GUI.", - GUI_OLD = GUI_BASE + "old-health-bar", - GUI_FORCE = GUI_BASE + "force-scaling", - GUI_LVLBAR = GUI_BASE + "level-bar", - GUI_FOOD = GUI_BASE + "food-bar", - GUI_ACTION = GUI_BASE + "use-action-bar", - GUI_TEXT = GUI_BASE + "action-bar-text", - GUI_BOARD = GUI_BASE + "scoreboard-enabled", - GUI_NAME = GUI_BASE + "show-class-name", - GUI_LEVEL = GUI_BASE + "show-class-level", - GUI_LVLTXT = GUI_BASE + "class-level-text", - GUI_MAP = GUI_BASE + "map-tree-enabled", - GUI_TITLE = GUI_BASE + "title-enabled", - GUI_DUR = GUI_BASE + "title-duration", - GUI_FADEI = GUI_BASE + "title-fade-in", - GUI_FADEO = GUI_BASE + "title-fade-out", - GUI_LIST = GUI_BASE + "title-messages"; + GUI_BASE = "GUI.", + GUI_OLD = GUI_BASE + "old-health-bar", + GUI_FORCE = GUI_BASE + "force-scaling", + GUI_LVLBAR = GUI_BASE + "level-bar", + GUI_FOOD = GUI_BASE + "food-bar", + GUI_ACTION = GUI_BASE + "use-action-bar", + GUI_TEXT = GUI_BASE + "action-bar-text", + GUI_BOARD = GUI_BASE + "scoreboard-enabled", + GUI_NAME = GUI_BASE + "show-class-name", + GUI_LEVEL = GUI_BASE + "show-class-level", + GUI_BINDS = GUI_BASE + "show-binds", + GUI_BIND_TEXT = GUI_BASE + "show-bind-text", + GUI_LVLTXT = GUI_BASE + "class-level-text", + GUI_TITLE = GUI_BASE + "title-enabled", + GUI_DUR = GUI_BASE + "title-duration", + GUI_FADEI = GUI_BASE + "title-fade-in", + GUI_FADEO = GUI_BASE + "title-fade-out", + GUI_LIST = GUI_BASE + "title-messages"; private List titleMessages; @@ -908,8 +971,8 @@ private void loadItemSettings() private boolean showScoreboard; private boolean showClassName; private boolean showClassLevel; - private boolean showMapTree; - private boolean showTree; + private boolean showBinds; + private String bindText; private boolean useTitle; private int titleDuration; private int titleFadeIn; @@ -920,16 +983,14 @@ private void loadItemSettings() * * @return true if enabled, false otherwise */ - public boolean isOldHealth() - { + public boolean isOldHealth() { return oldHealth; } /** * @return true if forces the SkillAPI health scaling, false otherwise */ - public boolean isForceScaling() - { + public boolean isForceScaling() { return forceScaling; } @@ -938,8 +999,7 @@ public boolean isForceScaling() * * @return level bar setting */ - public String getLevelBar() - { + public String getLevelBar() { return levelBar; } @@ -948,8 +1008,7 @@ public String getLevelBar() * * @return food bar setting */ - public String getFoodBar() - { + public String getFoodBar() { return foodBar; } @@ -958,8 +1017,7 @@ public String getFoodBar() * * @return true if used, false otherwise */ - public boolean isUseActionBar() - { + public boolean isUseActionBar() { return useActionBar; } @@ -968,8 +1026,7 @@ public boolean isUseActionBar() * * @return action bar text */ - public String getActionText() - { + public String getActionText() { return actionText; } @@ -978,8 +1035,7 @@ public String getActionText() * * @return true if shown, false otherwise */ - public boolean isShowScoreboard() - { + public boolean isShowScoreboard() { return showScoreboard; } @@ -989,8 +1045,7 @@ public boolean isShowScoreboard() * * @return true if shown, false otherwise */ - public boolean isShowClassName() - { + public boolean isShowClassName() { return showClassName; } @@ -1000,37 +1055,23 @@ public boolean isShowClassName() * * @return true if shown, false otherwise */ - public boolean isShowClassLevel() - { + public boolean isShowClassLevel() { return showClassLevel; } - /** - * @return text shown alongside the class level - */ - public String getLevelText() - { - return levelText; + public boolean isShowBinds() { + return showBinds; } - /** - * Checks whether or not map trees are enabled on the server - * - * @return true if enabled, false otherwise - */ - public boolean isMapTreeEnabled() - { - return showMapTree; + public String getBindText() { + return bindText; } /** - * Checks whether or not the map tree is available in some way - * - * @return true if available + * @return text shown alongside the class level */ - public boolean isMapTreeAvailable() - { - return showTree; + public String getLevelText() { + return levelText; } /** @@ -1041,37 +1082,32 @@ public boolean isMapTreeAvailable() * * @return true if should use title display, false otherwise */ - public boolean useTitle(TitleType type) - { + public boolean useTitle(TitleType type) { return useTitle && type != null && titleMessages.contains(type.name().toLowerCase()); } /** * @return duration of the title display in ticks */ - public int getTitleDuration() - { + public int getTitleDuration() { return titleDuration; } /** * @return fade in time of the title display in ticks */ - public int getTitleFadeIn() - { + public int getTitleFadeIn() { return titleFadeIn; } /** * @return fade out time of the title display in ticks */ - public int getTitleFadeOut() - { + public int getTitleFadeOut() { return titleFadeOut; } - private void loadGUISettings() - { + private void loadGUISettings() { oldHealth = config.getBoolean(GUI_OLD); forceScaling = config.getBoolean(GUI_FORCE); levelBar = config.getString(GUI_LVLBAR); @@ -1082,8 +1118,8 @@ private void loadGUISettings() showScoreboard = config.getBoolean(GUI_BOARD); showClassName = config.getBoolean(GUI_NAME); showClassLevel = config.getBoolean(GUI_LEVEL); - showMapTree = config.getString(GUI_MAP).equalsIgnoreCase("TRUE"); - showTree = showMapTree || config.getString(GUI_MAP).equalsIgnoreCase("PARTIAL"); + showBinds = config.getBoolean(GUI_BINDS); + bindText = config.getString(GUI_BIND_TEXT); useTitle = config.getBoolean(GUI_TITLE); titleDuration = (int) (20 * config.getFloat(GUI_DUR)); titleFadeIn = (int) (20 * config.getFloat(GUI_FADEI)); @@ -1091,6 +1127,96 @@ private void loadGUISettings() titleMessages = config.getList(GUI_LIST); } + /////////////////////////////////////////////////////// + // // + // Cast Settings // + // // + /////////////////////////////////////////////////////// + + private static final String CAST_BASE = "Casting."; + private static final String CAST_ENABLED = CAST_BASE + "enabled"; + private static final String CAST_BARS = CAST_BASE + "bars"; + private static final String CAST_COMBAT = CAST_BASE + "combat"; + private static final String CAST_INDICATOR = CAST_BASE + "cast-indicator"; + private static final String CAST_SLOT = CAST_BASE + "slot"; + private static final String CAST_ITEM = CAST_BASE + "item"; + private static final String CAST_COOLDOWN = CAST_BASE + "cooldown"; + private static final String CAST_HOVER = CAST_BASE + "hover-item"; + private static final String CAST_INSTANT = CAST_BASE + "instant-item"; + + private boolean castEnabled; + private boolean castBars; + private boolean combatEnabled; + private int castSlot; + private long castCooldown; + private ItemStack castItem; + private ItemStack hoverItem; + private ItemStack instantItem; + + /** + * @return true if default casting is enabled + */ + public boolean isCastEnabled() { + return castEnabled; + } + + /** + * @return true if using bar format, false otherwise + */ + public boolean isUsingBars() { + return castEnabled && castBars && !combatEnabled; + } + + public boolean isUsingWand() { + return castEnabled && !castBars && !combatEnabled; + } + + public boolean isUsingCombat() { + return castEnabled && combatEnabled; + } + + /** + * @return slot the cast item is stored in + */ + public int getCastSlot() { + return castSlot; + } + + /** + * @return global cooldown for casting + */ + public long getCastCooldown() { + return castCooldown; + } + + /** + * @return cast item to use in the slot + */ + public ItemStack getCastItem() { + return castItem; + } + + public ItemStack getHoverItem() { + return hoverItem; + } + + public ItemStack getInstantItem() { + return instantItem; + } + + private void loadCastSettings() { + castEnabled = config.getBoolean(CAST_ENABLED); + castBars = config.getBoolean(CAST_BARS); + combatEnabled = config.getBoolean(CAST_COMBAT); + castSlot = config.getInt(CAST_SLOT) - 1; + castCooldown = (long) (config.getDouble(CAST_COOLDOWN) * 1000); + castItem = GUITool.parseItem(config.getSection(CAST_ITEM)); + hoverItem = GUITool.parseItem(config.getSection(CAST_HOVER)); + instantItem = GUITool.parseItem(config.getSection(CAST_INSTANT)); + castEnabled = castEnabled && castItem != null; + IndicatorSettings.load(config.getSection(CAST_INDICATOR)); + } + /////////////////////////////////////////////////////// // // // Click Combo Settings // @@ -1100,27 +1226,24 @@ private void loadGUISettings() private static final String COMBO_BASE = "Click Combos."; private static final String COMBO_ENABLED = COMBO_BASE + "enabled"; private static final String COMBO_CUSTOM = COMBO_BASE + "allow-custom"; - private static final String COMBO_LEFT = COMBO_BASE + "use-click-left"; - private static final String COMBO_RIGHT = COMBO_BASE + "use-click-right"; - private static final String COMBO_SHIFT = COMBO_BASE + "use-click-shift"; + private static final String COMBO_CLICK = COMBO_BASE + "use-click-"; private static final String COMBO_SIZE = COMBO_BASE + "combo-size"; private static final String COMBO_TIME = COMBO_BASE + "click-time"; + private static final String COMBO_AUTO = COMBO_BASE + "auto-assign"; - private boolean combosEnabled; - private boolean customCombos; - private boolean comboLeft; - private boolean comboRight; - private boolean comboShift; - private int comboSize; - private int clickTime; + private boolean[] clicks; + private boolean combosEnabled; + private boolean customCombos; + private boolean autoAssignCombos; + private int comboSize; + private int clickTime; /** * Checks whether or not click combos are enabled * * @return true if enabled, false otherwise */ - public boolean isCombosEnabled() - { + public boolean isCombosEnabled() { return combosEnabled; } @@ -1129,39 +1252,19 @@ public boolean isCombosEnabled() * * @return true if can customize them, false otherwise */ - public boolean isCustomCombosAllowed() - { + public boolean isCustomCombosAllowed() { return customCombos; } - /** - * Checks whether or not left clicks are enabled for combos - * - * @return true if enabled, false otherwise - */ - public boolean isComboLeft() - { - return comboLeft; + public boolean shouldAutoAssignCombos() { + return autoAssignCombos; } /** - * Checks whether or not right clicks are enabled for combos - * - * @return true if enabled, false otherwise + * @return enabled clicks as an array of booleans indexed by click ID */ - public boolean isComboRight() - { - return comboRight; - } - - /** - * Checks whether or not shift clicks are enabled for combos - * - * @return true if enabled, false othewise - */ - public boolean isComboShift() - { - return comboShift; + public boolean[] getEnabledClicks() { + return clicks; } /** @@ -1169,8 +1272,7 @@ public boolean isComboShift() * * @return max length of combos to be used */ - public int getComboSize() - { + public int getComboSize() { return comboSize; } @@ -1179,20 +1281,25 @@ public int getComboSize() * * @return number of seconds before a click combo resets */ - public int getClickTime() - { + public int getClickTime() { return clickTime; } - private void loadComboSettings() - { + private void loadComboSettings() { combosEnabled = config.getBoolean(COMBO_ENABLED); customCombos = combosEnabled && config.getBoolean(COMBO_CUSTOM); - comboLeft = config.getBoolean(COMBO_LEFT); - comboRight = config.getBoolean(COMBO_RIGHT); - comboShift = config.getBoolean(COMBO_SHIFT); + autoAssignCombos = combosEnabled && config.getBoolean(COMBO_AUTO, true); comboSize = config.getInt(COMBO_SIZE); clickTime = (int) (1000 * config.getDouble(COMBO_TIME)); + + clicks = new boolean[Click.values().length + 1]; + for (int i = 1; i <= Click.values().length; i++) { + final String key = COMBO_CLICK + Click.getById(i).name().toLowerCase().replace('_', '-'); + clicks[i] = config.getBoolean(key); + } + if (clicks[Click.RIGHT_SHIFT.getId()] || clicks[Click.LEFT_SHIFT.getId()]) { + clicks[Click.SHIFT.getId()] = false; + } } /////////////////////////////////////////////////////// @@ -1213,6 +1320,7 @@ private void loadComboSettings() private boolean showExpMessages; private boolean showLevelMessages; private boolean showLossMessages; + private Set expLostBlacklist; /** * Gets the required amount of experience at a given level @@ -1221,10 +1329,8 @@ private void loadComboSettings() * * @return required experience to gain a level */ - public int getRequiredExp(int level) - { - if (useCustomExp) return (int) expCustom.compute(level, 0); - else return expFormula.calculate(level); + public int getRequiredExp(int level) { + if (useCustomExp) { return (int) expCustom.compute(level, 0); } else { return expFormula.calculate(level); } } /** @@ -1234,15 +1340,11 @@ public int getRequiredExp(int level) * * @return experience yield */ - public double getYield(String mob) - { + public double getYield(String mob) { mob = mob.toLowerCase(); - if (!yields.containsKey(mob)) - { + if (!yields.containsKey(mob)) { return 0; - } - else - { + } else { return yields.get(mob); } } @@ -1253,8 +1355,7 @@ public double getYield(String mob) * * @return true if enabled, false otherwise */ - public boolean isUseOrbs() - { + public boolean isUseOrbs() { return useOrbs; } @@ -1264,8 +1365,7 @@ public boolean isUseOrbs() * * @return true if blocked, false otherwise */ - public boolean isBlockSpawner() - { + public boolean isBlockSpawner() { return blockSpawner; } @@ -1275,8 +1375,7 @@ public boolean isBlockSpawner() * * @return true if blocked, false otherwise */ - public boolean isBlockEgg() - { + public boolean isBlockEgg() { return blockEgg; } @@ -1286,8 +1385,7 @@ public boolean isBlockEgg() * * @return true if blocked, false otherwise */ - public boolean isBlockCreative() - { + public boolean isBlockCreative() { return blockCreative; } @@ -1297,8 +1395,7 @@ public boolean isBlockCreative() * * @return true if enabled, false otherwise */ - public boolean isShowExpMessages() - { + public boolean isShowExpMessages() { return showExpMessages; } @@ -1308,8 +1405,7 @@ public boolean isShowExpMessages() * * @return true if enabled, false otherwise */ - public boolean isShowLevelMessages() - { + public boolean isShowLevelMessages() { return showLevelMessages; } @@ -1319,15 +1415,21 @@ public boolean isShowLevelMessages() * * @return true if enabled, false otherwise */ - public boolean isShowLossMessages() - { + public boolean isShowLossMessages() { return showLossMessages; } + /** + * @param world world a player died in + * @return true if the world is blacklisted for losing experience + */ + public boolean shouldIgnoreExpLoss(final World world) { + return expLostBlacklist.contains(world.getName()); + } + private static final String EXP_BASE = "Experience."; - private void loadExpSettings() - { + private void loadExpSettings() { this.useOrbs = config.getBoolean(EXP_BASE + "use-exp-orbs"); this.blockSpawner = config.getBoolean(EXP_BASE + "block-mob-spawner"); this.blockEgg = config.getBoolean(EXP_BASE + "block-mob-egg"); @@ -1335,6 +1437,7 @@ private void loadExpSettings() this.showExpMessages = config.getBoolean(EXP_BASE + "exp-message-enabled"); this.showLevelMessages = config.getBoolean(EXP_BASE + "level-message-enabled"); this.showLossMessages = config.getBoolean(EXP_BASE + "lose-exp-message"); + this.expLostBlacklist = new HashSet<>(config.getList(EXP_BASE + "lose-exp-blacklist")); DataSection formula = config.getSection(EXP_BASE + "formula"); int x = formula.getInt("x"); @@ -1342,13 +1445,12 @@ private void loadExpSettings() int z = formula.getInt("z"); expFormula = new ExpFormula(x, y, z); - expCustom = new Formula(config.getString(EXP_BASE + "custom-formula").replace("lvl", "v")); + expCustom = new Formula(config.getString(EXP_BASE + "custom-formula"), new CustomValue("lvl")); useCustomExp = config.getBoolean(EXP_BASE + "use-custom") && expCustom.isValid(); DataSection yields = config.getSection(EXP_BASE + "yields"); this.yields.clear(); - for (String key : yields.keys()) - { + for (String key : yields.keys()) { this.yields.put(key, yields.getDouble(key)); } } @@ -1370,8 +1472,7 @@ private void loadExpSettings() * * @return true if enabled, false otherwise */ - public boolean isSkillBarEnabled() - { + public boolean isSkillBarEnabled() { return skillBarEnabled; } @@ -1380,8 +1481,7 @@ public boolean isSkillBarEnabled() * * @return true if enabled, false otherwise */ - public boolean isSkillBarCooldowns() - { + public boolean isSkillBarCooldowns() { return skillBarCooldowns; } @@ -1390,8 +1490,7 @@ public boolean isSkillBarCooldowns() * * @return unassigned indicator */ - public ItemStack getUnassigned() - { + public ItemStack getUnassigned() { return unassigned; } @@ -1400,8 +1499,7 @@ public ItemStack getUnassigned() * * @return default skill bar layout */ - public boolean[] getDefaultBarLayout() - { + public boolean[] getDefaultBarLayout() { return defaultBarLayout; } @@ -1410,48 +1508,47 @@ public boolean[] getDefaultBarLayout() * * @return list of locked skill bar slots */ - public boolean[] getLockedSlots() - { + public boolean[] getLockedSlots() { return lockedSlots; } - private void loadSkillBarSettings() - { + private void loadSkillBarSettings() { DataSection bar = config.getSection("Skill Bar"); - skillBarEnabled = bar.getBoolean("enabled", false); + skillBarEnabled = bar.getBoolean("enabled", false) && !castEnabled; skillBarCooldowns = bar.getBoolean("show-cooldown", true); DataSection icon = bar.getSection("empty-icon"); - Material mat; - try - { - mat = Material.valueOf(icon.getString("material", "PUMPKIN_SEEDS").toUpperCase().replace(' ', '_')); - } - catch (Exception ex) - { - mat = Material.PUMPKIN_SEEDS; - } + Material mat = Material.matchMaterial(icon.getString("material", "PUMPKIN_SEEDS")); + if (mat == null) { mat = Material.PUMPKIN_SEEDS; } unassigned = new ItemStack(mat); - unassigned.setData(new MaterialData(mat, (byte) icon.getInt("data", 0))); + + final int data = icon.getInt("data", 0); + unassigned.setDurability((short) data); + // unassigned.setData(new MaterialData(mat, (byte) data)); ItemMeta meta = unassigned.getItemMeta(); - meta.setDisplayName(TextFormatter.colorString(icon.getString("text", "&7Unassigned"))); + if (icon.isList("text")) { + List format = TextFormatter.colorStringList(icon.getList("text")); + meta.setDisplayName(format.remove(0)); + meta.setLore(format); + } else { meta.setDisplayName(TextFormatter.colorString(icon.getString("text", "&7Unassigned"))); } unassigned.setItemMeta(meta); DataSection layout = bar.getSection("layout"); int skillCount = 0; - for (int i = 0; i < 9; i++) - { + for (int i = 0; i < 9; i++) { DataSection slot = layout.getSection((i + 1) + ""); defaultBarLayout[i] = slot.getBoolean("skill", i <= 5); lockedSlots[i] = slot.getBoolean("locked", false); - if (defaultBarLayout[i]) - { + if (isUsingCombat() && i == castSlot) { + lockedSlots[i] = true; + defaultBarLayout[i] = false; + } + if (defaultBarLayout[i]) { skillCount++; } } - if (skillCount == 9) - { + if (skillCount == 9) { Logger.invalid("Invalid Skill Bar Setup - Cannot have all 9 skill slots!"); Logger.invalid(" -> Setting last slot to be a weapon slot"); defaultBarLayout[8] = false; @@ -1464,8 +1561,7 @@ private void loadSkillBarSettings() // // /////////////////////////////////////////////////////// - private void loadLoggingSettings() - { + private void loadLoggingSettings() { Logger.loadLevels(config.getSection("Logging")); } @@ -1491,8 +1587,7 @@ private void loadLoggingSettings() * * @return true if active, false otherwise */ - public boolean isWorldEnabled(World world) - { + public boolean isWorldEnabled(World world) { return isWorldEnabled(world.getName()); } @@ -1504,15 +1599,44 @@ public boolean isWorldEnabled(World world) * * @return true if active, false otherwise */ - public boolean isWorldEnabled(String world) - { + public boolean isWorldEnabled(String world) { return !worldEnabled || (worldEnableList == worlds.contains(world)); } - private void loadWorldSettings() - { + private void loadWorldSettings() { worldEnabled = config.getBoolean(WORLD_ENABLE); worldEnableList = config.getBoolean(WORLD_TYPE); worlds = config.getList(WORLD_LIST); } + + /////////////////////////////////////////////////////// + // // + // WorldGuard Settings // + // // + /////////////////////////////////////////////////////// + + private static final String WG_SKILLS = "disable-skills"; + private static final String WG_EXP = "disable-exp"; + + private Set skillDisabledRegions; + private Set expDisabledRegions; + + public boolean areSkillsDisabledForRegion(final String region) { + return skillDisabledRegions.contains(region); + } + + public boolean isExpDisabledForRegion(final String region) { + return expDisabledRegions.contains(region); + } + + private void loadWorldGuardSettings() { + final CommentedConfig config = new CommentedConfig(plugin, "worldGuard"); + config.checkDefaults(); + config.trim(); + config.save(); + final DataSection data = config.getConfig(); + + skillDisabledRegions = ImmutableSet.copyOf(data.getList(WG_SKILLS)); + expDisabledRegions = ImmutableSet.copyOf(data.getList(WG_EXP)); + } } diff --git a/src/com/sucy/skill/data/Formula.java b/src/com/sucy/skill/data/formula/Formula.java similarity index 52% rename from src/com/sucy/skill/data/Formula.java rename to src/com/sucy/skill/data/formula/Formula.java index d6af5f56..943cb0f6 100644 --- a/src/com/sucy/skill/data/Formula.java +++ b/src/com/sucy/skill/data/formula/Formula.java @@ -1,6 +1,6 @@ /** * SkillAPI - * com.sucy.skill.data.Formula + * com.sucy.skill.data.formula.Formula * * The MIT License (MIT) * @@ -24,26 +24,52 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.sucy.skill.data; +package com.sucy.skill.data.formula; -import com.sucy.skill.api.util.NumberParser; +import com.sucy.skill.data.formula.func.*; +import com.sucy.skill.data.formula.operator.*; +import com.sucy.skill.data.formula.value.CustomValue; +import com.sucy.skill.data.formula.value.ValueNum; import com.sucy.skill.log.Logger; import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.HashMap; /** * Represents a basic math equation read from left to right, ignoring * order of operations. Currently this only supports addition, subtraction, * multiplication, and division. */ -public class Formula +public class Formula implements IValue { - private static final List OPS = Arrays.asList('+', '-', '*', '/', '^'); + public static final double DEG_TO_RAD = Math.PI / 180; - private Object[] values; - private Character[] operations; + private static final HashMap OPS = new HashMap() + {{ + put('+', new Addition()); + put('-', new Subtraction()); + put('*', new Multiplication()); + put('/', new Division()); + put('%', new Modulo()); + put('^', new Exponent()); + put('_', new Log()); + }}; + + private static final HashMap> FUNCS = new HashMap>() + {{ + put("abs", Abs.class); + put("ceil", Ceil.class); + put("cos", Cos.class); + put("floor", Floor.class); + put("sqrt", Root.class); + put("sign", Sign.class); + put("sin", Sin.class); + put("sq", Square.class); + put("tan", Tan.class); + }}; + + private IValue[] values; + private IOperator[] operations; private boolean valid; private boolean negative; private String equation; @@ -53,16 +79,18 @@ public class Formula * * @param equation equation string */ - public Formula(String equation) + public Formula(String equation, CustomValue... defined) { + int i = 0; + for (CustomValue value : defined) + value.setIndex(i++); + negative = false; // Empty formulas if (equation == null || equation.length() == 0) { - values = new Object[] { 'v' }; - operations = new Character[0]; - this.equation = "v"; + invalidate(defined); return; } @@ -71,10 +99,11 @@ public Formula(String equation) this.equation = equation; // Parse the formula - ArrayList vals = new ArrayList(); - ArrayList ops = new ArrayList(); + ArrayList vals = new ArrayList(); + ArrayList ops = new ArrayList(); int parens = 0, l = equation.length(), valStart = 0, lastOp = -1; - for (int i = 0; i < l; i++) + Class func = null; + for (i = 0; i < l; i++) { char c = equation.charAt(i); @@ -85,10 +114,17 @@ public Formula(String equation) { if (valStart != i) { - vals.add(makeVal(equation.substring(valStart, i))); - ops.add('*'); + String val = equation.substring(valStart, i); + if (FUNCS.containsKey(val)) + func = FUNCS.get(val); + else + { + vals.add(makeVal(val, defined)); + ops.add(OPS.get('*')); + } } valStart = i + 1; + lastOp = i; } parens++; } @@ -99,13 +135,31 @@ else if (c == ')') parens--; if (parens == 0) { - vals.add(makeVal(new Formula(equation.substring(valStart, i)))); + if (func == null) + vals.add(makeVal(new Formula(equation.substring(valStart, i), defined))); + else + { + try + { + vals.add( + func.getConstructor(IValue.class).newInstance( + makeVal(new Formula(equation.substring(valStart, i), defined)) + ) + ); + } + catch (Exception ex) + { + ex.printStackTrace(); + invalidate(defined); + return; + } + } valStart = i + 1; } } // Operators - else if (parens == 0 && OPS.contains(c)) + else if (parens == 0 && OPS.containsKey(c)) { if (c == '-' && lastOp == i - 1) { @@ -117,9 +171,9 @@ else if (parens == 0 && OPS.contains(c)) { if (valStart != i) { - vals.add(makeVal(equation.substring(valStart, i))); + vals.add(makeVal(equation.substring(valStart, i), defined)); } - ops.add(c); + ops.add(OPS.get(c)); lastOp = i; valStart = i + 1; } @@ -129,46 +183,67 @@ else if (parens == 0 && OPS.contains(c)) // End any lingering values if (valStart != l) { - vals.add(makeVal(equation.substring(valStart, equation.length()))); + vals.add(makeVal(equation.substring(valStart, equation.length()), defined)); } negative = false; // Convert to arrays - values = vals.toArray(); - operations = ops.toArray(new Character[ops.size()]); + values = vals.toArray(new IValue[vals.size()]); + operations = ops.toArray(new IOperator[ops.size()]); if (!validate()) - { - Logger.invalid("Invalid equation: " + equation); - values = new Object[] { 'v' }; - operations = new Character[0]; - valid = false; - } + invalidate(defined); else - { valid = true; - } } - private Object makeVal(String val) + /** + * Invalidates the equation + * + * @param defined defined inputs + */ + private void invalidate(CustomValue... defined) + { + Logger.invalid("Invalid equation: " + equation); + equation = defined[0].getToken(); + values = new IValue[] { defined[0] }; + operations = new IOperator[0]; + valid = false; + } + + private IValue makeVal(String val, CustomValue... defined) { if (negative) { negative = false; - return new Formula(val).negate(); + return new Formula(val, defined).negate(); + } + else + { + for (CustomValue value : defined) + { + if (value.getToken().equals(val)) + return value; + } + return new ValueNum(val); } - else return val; } - private Object makeVal(Formula val) + private IValue makeVal(Formula val) { - if (negative) + if (negative && val.operations.length == 0 && val.values[0] instanceof ValueNum) + return new ValueNum(-val.values[0].compute()); + else if (negative) { val.negate(); negative = false; + return val; } - return val; + else if (val.operations.length == 0) + return val.values[0]; + else + return val; } /** @@ -206,30 +281,10 @@ private boolean validate() return false; } - // Parse each value to make sure it's a valid equation - for (int i = 0; i < values.length; i++) - { - // Sub-equations - if (values[i] instanceof Formula) - { - if (!((Formula) values[i]).validate()) - return false; - } - - // Numbers - else if (!values[i].toString().equals("v") && !values[i].toString().equals("a")) - { - // Parse the number - try - { - values[i] = NumberParser.parseDouble(values[i].toString()); - } - catch (NumberFormatException ex) - { - return false; - } - } - } + // Ensure valid sub equations + for (IValue value : values) + if (value instanceof Formula && !((Formula) value).validate()) + return false; // Nothing went wrong return true; @@ -239,50 +294,24 @@ else if (!values[i].toString().equals("v") && !values[i].toString().equals("a")) * Calculates the formula using the given base value and attribute. * If the formula is invalid, this returns the value. * - * @param value base value - * @param attr attribute + * @param input the input data * * @return computed value */ - public double compute(double value, double attr) + public double compute(double... input) { - double result = getValue(0, value, attr); + double result = values[0].compute(input); int i; for (i = 1; i < values.length; i++) { - double val = getValue(i, value, attr); - switch (operations[i - 1]) - { - case '+': - result += val; - break; - case '-': - result -= val; - break; - case '*': - result *= val; - break; - case '/': - result /= val; - break; - case '^': - result = Math.pow(result, val); - break; - } + double val = values[i].compute(input); + result = operations[i - 1].compute(result, val); } if (negative) result = -result; return result; } - private double getValue(int index, double value, double attr) - { - if (values[index] instanceof Formula) return ((Formula) values[index]).compute(value, attr); - if (values[index].toString().equals("v")) return value; - if (values[index].toString().equals("a")) return attr; - return (Double) values[index]; - } - /** * Returns the equation string for toString * diff --git a/src/com/sucy/skill/data/formula/IOperator.java b/src/com/sucy/skill/data/formula/IOperator.java new file mode 100644 index 00000000..eb2dd6b1 --- /dev/null +++ b/src/com/sucy/skill/data/formula/IOperator.java @@ -0,0 +1,43 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.IOperator + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula; + +/** + * A single operation used in formulas + */ +public interface IOperator +{ + /** + * Performs the operation between the two values + * + * @param a first value + * @param b second value + * + * @return result + */ + public double compute(double a, double b); +} diff --git a/src/com/sucy/skill/data/formula/IValue.java b/src/com/sucy/skill/data/formula/IValue.java new file mode 100644 index 00000000..96a3e51c --- /dev/null +++ b/src/com/sucy/skill/data/formula/IValue.java @@ -0,0 +1,42 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.IValue + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula; + +/** + * Represents a value in a formula + */ +public interface IValue +{ + /** + * Gets the value using the two inputs + * + * @param input the input data + * + * @return result value + */ + public double compute(double... input); +} diff --git a/src/com/sucy/skill/data/formula/func/Abs.java b/src/com/sucy/skill/data/formula/func/Abs.java new file mode 100644 index 00000000..2c46111f --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Abs.java @@ -0,0 +1,55 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.func.Abs + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.IValue; + +public class Abs implements IValue +{ + private IValue formula; + + /** + * @param formula wrapped formula + */ + public Abs(IValue formula) + { + this.formula = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return Math.abs(formula.compute(input)); + } +} diff --git a/src/com/sucy/skill/data/formula/func/Ceil.java b/src/com/sucy/skill/data/formula/func/Ceil.java new file mode 100644 index 00000000..1c4653ca --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Ceil.java @@ -0,0 +1,58 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.func.Ceil + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.IValue; + +/** + * Ceilings a number + */ +public class Ceil implements IValue +{ + private IValue formula; + + /** + * @param formula wrapped formula + */ + public Ceil(IValue formula) + { + this.formula = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return Math.ceil(formula.compute(input)); + } +} diff --git a/src/com/sucy/skill/data/formula/func/Cos.java b/src/com/sucy/skill/data/formula/func/Cos.java new file mode 100644 index 00000000..a58c2152 --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Cos.java @@ -0,0 +1,59 @@ +/** + * SkillAPI + * com.sucy.skill.data.value.func.Cos + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.Formula; +import com.sucy.skill.data.formula.IValue; + +/** + * Cosine function + */ +public class Cos implements IValue +{ + private IValue value; + + /** + * @param formula wrapped value + */ + public Cos(IValue formula) + { + this.value = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return Math.cos(value.compute(input) * Formula.DEG_TO_RAD); + } +} diff --git a/src/com/sucy/skill/data/formula/func/Floor.java b/src/com/sucy/skill/data/formula/func/Floor.java new file mode 100644 index 00000000..80a22872 --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Floor.java @@ -0,0 +1,58 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.func.Floor + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.IValue; + +/** + * Floors a number + */ +public class Floor implements IValue +{ + private IValue formula; + + /** + * @param formula wrapped formula + */ + public Floor(IValue formula) + { + this.formula = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return Math.floor(formula.compute(input)); + } +} diff --git a/src/com/sucy/skill/data/formula/func/Root.java b/src/com/sucy/skill/data/formula/func/Root.java new file mode 100644 index 00000000..3d3e89f7 --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Root.java @@ -0,0 +1,58 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.func.Root + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.IValue; + +/** + * Square root of a number + */ +public class Root implements IValue +{ + private IValue formula; + + /** + * @param formula wrapped formula + */ + public Root(IValue formula) + { + this.formula = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return Math.sqrt(formula.compute(input)); + } +} diff --git a/src/com/sucy/skill/data/formula/func/Sign.java b/src/com/sucy/skill/data/formula/func/Sign.java new file mode 100644 index 00000000..240cf84a --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Sign.java @@ -0,0 +1,64 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.func.Sign + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.IValue; + +/** + * Sign function + */ +public class Sign implements IValue +{ + private IValue formula; + + /** + * @param formula wrapped formula + */ + public Sign(IValue formula) + { + this.formula = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + double val = formula.compute(input); + if (val == 0) + return 0.0; + else if (val < 0) + return -1.0; + else + return 1.0; + } +} diff --git a/src/com/sucy/skill/data/formula/func/Sin.java b/src/com/sucy/skill/data/formula/func/Sin.java new file mode 100644 index 00000000..de8130fb --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Sin.java @@ -0,0 +1,59 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.func.Sin + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.Formula; +import com.sucy.skill.data.formula.IValue; + +/** + * Sine function + */ +public class Sin implements IValue +{ + private IValue formula; + + /** + * @param formula wrapped formula + */ + public Sin(IValue formula) + { + this.formula = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return Math.sin(formula.compute(input) * Formula.DEG_TO_RAD); + } +} diff --git a/src/com/sucy/skill/data/formula/func/Square.java b/src/com/sucy/skill/data/formula/func/Square.java new file mode 100644 index 00000000..559ebc08 --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Square.java @@ -0,0 +1,59 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.func.Square + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.IValue; + +/** + * Squares a number + */ +public class Square implements IValue +{ + private IValue formula; + + /** + * @param formula wrapped formula + */ + public Square(IValue formula) + { + this.formula = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + double val = formula.compute(input); + return val * val; + } +} diff --git a/src/com/sucy/skill/data/formula/func/Tan.java b/src/com/sucy/skill/data/formula/func/Tan.java new file mode 100644 index 00000000..5b90461a --- /dev/null +++ b/src/com/sucy/skill/data/formula/func/Tan.java @@ -0,0 +1,59 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.func.Tan + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.func; + +import com.sucy.skill.data.formula.Formula; +import com.sucy.skill.data.formula.IValue; + +/** + * Tangent function + */ +public class Tan implements IValue +{ + private IValue formula; + + /** + * @param formula wrapped formula + */ + public Tan(IValue formula) + { + this.formula = formula; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return Math.tan(formula.compute(input) * Formula.DEG_TO_RAD); + } +} diff --git a/src/com/sucy/skill/data/formula/operator/Addition.java b/src/com/sucy/skill/data/formula/operator/Addition.java new file mode 100644 index 00000000..61184f30 --- /dev/null +++ b/src/com/sucy/skill/data/formula/operator/Addition.java @@ -0,0 +1,48 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.operator.Addition + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.operator; + +import com.sucy.skill.data.formula.IOperator; + +/** + * The addition operation used in formulas + */ +public class Addition implements IOperator +{ + /** + * Performs the operation between the two values + * + * @param a first value + * @param b second value + * + * @return result + */ + public double compute(double a, double b) + { + return a + b; + } +} diff --git a/src/com/sucy/skill/data/formula/operator/Division.java b/src/com/sucy/skill/data/formula/operator/Division.java new file mode 100644 index 00000000..72407c91 --- /dev/null +++ b/src/com/sucy/skill/data/formula/operator/Division.java @@ -0,0 +1,48 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.operator.Division + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.operator; + +import com.sucy.skill.data.formula.IOperator; + +/** + * The division operation used in formulas + */ +public class Division implements IOperator +{ + /** + * Performs the operation between the two values + * + * @param a first value + * @param b second value + * + * @return result + */ + public double compute(double a, double b) + { + return b == 0 ? 0 : a / b; + } +} diff --git a/src/com/sucy/skill/data/formula/operator/Exponent.java b/src/com/sucy/skill/data/formula/operator/Exponent.java new file mode 100644 index 00000000..c90e3c44 --- /dev/null +++ b/src/com/sucy/skill/data/formula/operator/Exponent.java @@ -0,0 +1,48 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.operator.Exponent + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.operator; + +import com.sucy.skill.data.formula.IOperator; + +/** + * The exponent operation used in formulas + */ +public class Exponent implements IOperator +{ + /** + * Performs the operation between the two values + * + * @param a first value + * @param b second value + * + * @return result + */ + public double compute(double a, double b) + { + return Math.pow(a, b); + } +} diff --git a/src/com/sucy/skill/data/formula/operator/Log.java b/src/com/sucy/skill/data/formula/operator/Log.java new file mode 100644 index 00000000..e083c9f8 --- /dev/null +++ b/src/com/sucy/skill/data/formula/operator/Log.java @@ -0,0 +1,48 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.operator.Log + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.operator; + +import com.sucy.skill.data.formula.IOperator; + +/** + * The exponent operation used in formulas + */ +public class Log implements IOperator +{ + /** + * Performs the operation between the two values + * + * @param a first value + * @param b second value + * + * @return result + */ + public double compute(double a, double b) + { + return a <= 0 || b <= 0 ? 0 : Math.log(a) / Math.log(b); + } +} diff --git a/src/com/sucy/skill/data/formula/operator/Modulo.java b/src/com/sucy/skill/data/formula/operator/Modulo.java new file mode 100644 index 00000000..0274166a --- /dev/null +++ b/src/com/sucy/skill/data/formula/operator/Modulo.java @@ -0,0 +1,48 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.operator.Modulo + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.operator; + +import com.sucy.skill.data.formula.IOperator; + +/** + * Modulo operator + */ +public class Modulo implements IOperator +{ + /** + * Performs the operation between the two values + * + * @param a first value + * @param b second value + * + * @return result + */ + public double compute(double a, double b) + { + return a % b; + } +} diff --git a/src/com/sucy/skill/data/formula/operator/Multiplication.java b/src/com/sucy/skill/data/formula/operator/Multiplication.java new file mode 100644 index 00000000..15e916fb --- /dev/null +++ b/src/com/sucy/skill/data/formula/operator/Multiplication.java @@ -0,0 +1,48 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.operator.Multiplication + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.operator; + +import com.sucy.skill.data.formula.IOperator; + +/** + * The multiplication operation used in formulas + */ +public class Multiplication implements IOperator +{ + /** + * Performs the operation between the two values + * + * @param a first value + * @param b second value + * + * @return result + */ + public double compute(double a, double b) + { + return a * b; + } +} diff --git a/src/com/sucy/skill/data/formula/operator/Subtraction.java b/src/com/sucy/skill/data/formula/operator/Subtraction.java new file mode 100644 index 00000000..d734ce6c --- /dev/null +++ b/src/com/sucy/skill/data/formula/operator/Subtraction.java @@ -0,0 +1,48 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.operator.Subtraction + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.operator; + +import com.sucy.skill.data.formula.IOperator; + +/** + * The subtraction operation used in formulas + */ +public class Subtraction implements IOperator +{ + /** + * Performs the operation between the two values + * + * @param a first value + * @param b second value + * + * @return result + */ + public double compute(double a, double b) + { + return a - b; + } +} diff --git a/src/com/sucy/skill/data/formula/value/CustomValue.java b/src/com/sucy/skill/data/formula/value/CustomValue.java new file mode 100644 index 00000000..e833f1a3 --- /dev/null +++ b/src/com/sucy/skill/data/formula/value/CustomValue.java @@ -0,0 +1,81 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.value.CustomValue + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.value; + +import com.sucy.skill.data.formula.IValue; + +/** + * A custom defined value for a formula + */ +public class CustomValue implements IValue +{ + private String token; + private int index; + + /** + * A defined value used in formulas + * + * @param token equation token + */ + public CustomValue(String token) + { + this.token = token; + } + + /** + * Sets the argument index for the value. + * This is handled by formulas so you shouldn't + * need to use this. + * + * @param index argument index + */ + public void setIndex(int index) + { + this.index = index; + } + + /** + * @return defining token + */ + public String getToken() + { + return token; + } + + /** + * Gets the value using the inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return input[index]; + } +} diff --git a/src/com/sucy/skill/data/formula/value/ValueNum.java b/src/com/sucy/skill/data/formula/value/ValueNum.java new file mode 100644 index 00000000..57985948 --- /dev/null +++ b/src/com/sucy/skill/data/formula/value/ValueNum.java @@ -0,0 +1,67 @@ +/** + * SkillAPI + * com.sucy.skill.data.formula.value.ValueNum + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.data.formula.value; + +import com.rit.sucy.config.parse.NumberParser; +import com.sucy.skill.data.formula.IValue; + +/** + * The attribute value + */ +public class ValueNum implements IValue +{ + private double value; + + /** + * @param value value to use + */ + public ValueNum(String value) + { + this.value = NumberParser.parseDouble(value); + } + + /** + * @param value value to use + */ + public ValueNum(double value) + { + this.value = value; + } + + /** + * Gets the value using the two inputs + * + * @param input the input data + * + * @return result value + */ + @Override + public double compute(double... input) + { + return value; + } +} diff --git a/src/com/sucy/skill/data/io/ConfigIO.java b/src/com/sucy/skill/data/io/ConfigIO.java index b945a7b6..0f510df6 100644 --- a/src/com/sucy/skill/data/io/ConfigIO.java +++ b/src/com/sucy/skill/data/io/ConfigIO.java @@ -28,11 +28,13 @@ import com.rit.sucy.config.CommentedConfig; import com.rit.sucy.config.parse.DataSection; +import com.rit.sucy.version.VersionManager; import com.rit.sucy.version.VersionPlayer; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerAccounts; import com.sucy.skill.log.Logger; import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.HashMap; @@ -52,6 +54,14 @@ public ConfigIO(SkillAPI plugin) super(plugin); } + public HashMap loadAll() { + HashMap result = new HashMap(); + for (Player player : VersionManager.getOnlinePlayers()) { + result.put(new VersionPlayer(player).getIdString(), loadData(player)); + } + return result; + } + /** * Loads data for the given player * @@ -69,9 +79,7 @@ public PlayerAccounts loadData(OfflinePlayer player) { DataSection old = nameConfig.getConfig(); for (String key : old.keys()) - { config.getConfig().set(key, old.get(key)); - } nameConfig.getConfigFile().delete(); } DataSection file = config.getConfig(); @@ -112,8 +120,6 @@ public void saveAll() HashMap data = SkillAPI.getPlayerAccountData(); ArrayList keys = new ArrayList(data.keySet()); for (String key : keys) - { saveData(data.get(key)); - } } } diff --git a/src/com/sucy/skill/data/io/IOManager.java b/src/com/sucy/skill/data/io/IOManager.java index bb2462ee..7884458e 100644 --- a/src/com/sucy/skill/data/io/IOManager.java +++ b/src/com/sucy/skill/data/io/IOManager.java @@ -31,9 +31,10 @@ import com.sucy.skill.api.classes.RPGClass; import com.sucy.skill.api.player.*; import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.listener.MainListener; import com.sucy.skill.log.Logger; import com.sucy.skill.manager.ComboManager; -import org.bukkit.Material; +//import org.bukkit.Material; import org.bukkit.OfflinePlayer; import java.util.ArrayList; @@ -57,16 +58,19 @@ public abstract class IOManager SKILLS = "skills", BINDS = "binds", LEVEL = "level", - SCHEME = "scheme", EXP = "exp", POINTS = "points", SKILL_BAR = "bar", + HOVER = "hover", + EXTRA = "extra", + INSTANT = "instant", ENABLED = "enabled", SLOTS = "slots", UNASSIGNED = "e", COMBOS = "combos", ATTRIBS = "attribs", COOLDOWN = "cd", + HUNGER = "hunger", ATTRIB_POINTS = "attrib-points"; /** @@ -79,11 +83,18 @@ public abstract class IOManager * * @param api SkillAPI reference */ - protected IOManager(SkillAPI api) + IOManager(SkillAPI api) { this.api = api; } + /** + * Loads player data for all online players + * + * @return loaded player data + */ + public abstract HashMap loadAll(); + /** * Loads data for the player * @@ -107,7 +118,9 @@ public void saveAll() { for (PlayerAccounts data : SkillAPI.getPlayerAccountData().values()) { - saveData(data); + if (!MainListener.loadingPlayers.containsKey(data.getOfflinePlayer().getUniqueId())) { + saveData(data); + } } } @@ -133,10 +146,6 @@ protected PlayerAccounts load(OfflinePlayer player, DataSection file) DataSection account = accounts.getSection(accountKey); PlayerData acc = data.getData(Integer.parseInt(accountKey.replace(ACCOUNT_PREFIX, "")), player, true); - // Load scheme - if (SkillAPI.getSettings().isMapTreeAvailable()) - acc.setScheme(account.getString(SCHEME, "default")); - // Load classes DataSection classes = account.getSection(CLASSES); if (classes != null) @@ -170,52 +179,40 @@ protected PlayerAccounts load(OfflinePlayer player, DataSection file) PlayerSkill skillData = acc.getSkill(skillKey); if (skillData != null) { - skillData.addLevels(skill.getInt(LEVEL)); - skillData.addPoints(skill.getInt(POINTS)); + skillData.setLevel(skill.getInt(LEVEL)); + skillData.setPoints(skill.getInt(POINTS)); skillData.addCooldown(skill.getInt(COOLDOWN, 0)); } } } - // Load binds - DataSection binds = account.getSection(BINDS); - if (binds != null) - { - for (String bindKey : binds.keys()) - { - acc.bind(Material.valueOf(bindKey), acc.getSkill(binds.getString(bindKey))); - } - } - // Load skill bar - if (SkillAPI.getSettings().isSkillBarEnabled()) + if (SkillAPI.getSettings().isSkillBarEnabled() || SkillAPI.getSettings().isUsingCombat()) { - DataSection skillBar = account.getSection(SKILL_BAR); - PlayerSkillBar bar = acc.getSkillBar(); + final DataSection skillBar = account.getSection(SKILL_BAR); + final PlayerSkillBar bar = acc.getSkillBar(); if (skillBar != null && bar != null) { - for (String key : skillBar.keys()) + boolean enabled = skillBar.getBoolean(ENABLED, true); + for (final String key : skillBar.keys()) { - if (key.equals(ENABLED)) - { - if (bar.isEnabled() != skillBar.getBoolean(key)) - { - bar.toggleEnabled(); - } - } - else if (key.equals(SLOTS)) - { - List slots = skillBar.getList(SLOTS); - for (String i : slots) - { - bar.getData().put(Integer.parseInt(i), UNASSIGNED); + final boolean[] locked = SkillAPI.getSettings().getLockedSlots(); + if (key.equals(SLOTS)) { + for (int i = 0; i < 9; i++) + if (!bar.isWeaponSlot(i) && !locked[i]) + bar.getData().remove(i + 1); + + final List slots = skillBar.getList(SLOTS); + for (final String slot : slots) { + int i = Integer.parseInt(slot); + if (!locked[i - 1]) + bar.getData().put(i, UNASSIGNED); } } else if (SkillAPI.getSkill(key) != null) - { bar.getData().put(skillBar.getInt(key), key); - } } + bar.applySettings(); } } @@ -255,8 +252,32 @@ else if (SkillAPI.getSkill(key) != null) } } + // Load cast bars + if (SkillAPI.getSettings().isCastEnabled()) + { + acc.getCastBars().reset(); + acc.getCastBars().load(account.getSection(HOVER), true); + acc.getCastBars().load(account.getSection(INSTANT), false); + } + + acc.setHungerValue(account.getDouble(HUNGER, 1)); + + // Extra data + if (account.has(EXTRA)) { + acc.getExtraData().applyDefaults(account.getSection(EXTRA)); + } + acc.endInit(); - acc.autoLevel(); + + // Load binds + DataSection binds = account.getSection(BINDS); + if (binds != null) + { + for (String bindKey : binds.keys()) + { + acc.bind(bindKey, acc.getSkill(binds.getString(bindKey))); + } + } } data.setAccount(file.getInt(ACTIVE, data.getActiveId()), false); data.getActiveData().setLastHealth(file.getDouble(HEALTH)); @@ -280,10 +301,6 @@ protected DataSection save(PlayerAccounts data) DataSection account = accounts.createSection(ACCOUNT_PREFIX + entry.getKey()); PlayerData acc = entry.getValue(); - // Save scheme - if (SkillAPI.getSettings().isMapTreeAvailable()) - account.set(SCHEME, acc.getScheme()); - // Save classes DataSection classes = account.createSection(CLASSES); for (PlayerClass c : acc.getClasses()) @@ -307,19 +324,20 @@ protected DataSection save(PlayerAccounts data) // Save binds DataSection binds = account.createSection(BINDS); - for (Map.Entry bind : acc.getBinds().entrySet()) + for (Map.Entry bind : acc.getBinds().entrySet()) { if (bind.getKey() == null || bind.getValue() == null) continue; - binds.set(bind.getKey().name(), bind.getValue().getData().getName()); + binds.set(bind.getKey().replaceAll("§.", "").replace(" ","_").toUpperCase(), bind.getValue().getData().getName()); } // Save skill bar - if (SkillAPI.getSettings().isSkillBarEnabled() && acc.getSkillBar() != null) + if ((SkillAPI.getSettings().isSkillBarEnabled() || SkillAPI.getSettings().isUsingCombat()) + && acc.getSkillBar() != null) { DataSection skillBar = account.createSection(SKILL_BAR); PlayerSkillBar bar = acc.getSkillBar(); skillBar.set(ENABLED, bar.isEnabled()); - skillBar.set(SLOTS, new ArrayList(bar.getData().keySet())); + skillBar.set(SLOTS, new ArrayList<>(bar.getData().keySet())); for (Map.Entry slotEntry : bar.getData().entrySet()) { if (slotEntry.getValue().equals(UNASSIGNED)) @@ -356,6 +374,20 @@ protected DataSection save(PlayerAccounts data) attribs.set(key, acc.getAttributeData().get(key)); } } + + // Save cast bars + if (SkillAPI.getSettings().isCastEnabled()) + { + acc.getCastBars().save(account.createSection(HOVER), true); + acc.getCastBars().save(account.createSection(INSTANT), false); + } + + account.set(HUNGER, acc.getHungerValue()); + + // Extra data + if (acc.getExtraData().size() > 0) { + account.set(EXTRA, acc.getExtraData()); + } } return file; } diff --git a/src/com/sucy/skill/data/io/SQLIO.java b/src/com/sucy/skill/data/io/SQLIO.java index 902756ca..acc19ee5 100644 --- a/src/com/sucy/skill/data/io/SQLIO.java +++ b/src/com/sucy/skill/data/io/SQLIO.java @@ -31,12 +31,14 @@ import com.rit.sucy.sql.ColumnType; import com.rit.sucy.sql.direct.SQLDatabase; import com.rit.sucy.sql.direct.SQLTable; +import com.rit.sucy.version.VersionManager; import com.rit.sucy.version.VersionPlayer; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerAccounts; import com.sucy.skill.data.Settings; import com.sucy.skill.log.Logger; import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.HashMap; @@ -50,10 +52,6 @@ public class SQLIO extends IOManager public static final String DATA = "data"; public static final char STRING = '√'; - private boolean startup; - private SQLDatabase database; - private SQLTable table; - /** * Initializes the SQL IO Manager * @@ -62,37 +60,33 @@ public class SQLIO extends IOManager public SQLIO(SkillAPI api) { super(api); - startup = true; } - /** - * Connects to the database - */ - private void init() - { - if (database == null) - { - Settings settings = SkillAPI.getSettings(); - database = new SQLDatabase(api, settings.getSQLHost(), settings.getSQLPort(), settings.getSQLDatabase(), settings.getSQLUser(), settings.getSQLPass()); - database.openConnection(); - table = database.createTable(api, "players"); + private SQLConnection openConnection() { + SQLConnection connection = new SQLConnection(); - table.createColumn(ID, ColumnType.INCREMENT); - table.createColumn(DATA, ColumnType.TEXT); - } + Settings settings = SkillAPI.getSettings(); + connection.database = new SQLDatabase(api, settings.getSQLHost(), settings.getSQLPort(), settings.getSQLDatabase(), settings.getSQLUser(), settings.getSQLPass()); + connection.database.openConnection(); + connection.table = connection.database.createTable(api, "players"); + + connection.table.createColumn(ID, ColumnType.INCREMENT); + connection.table.createColumn(DATA, ColumnType.TEXT); + return connection; } - /** - * Closes the database connection - */ - public void cleanup() - { - startup = false; - if (database != null) - { - database.closeConnection(); - database = null; + @Override + public HashMap loadAll() { + SQLConnection connection = openConnection(); + + HashMap result = new HashMap(); + for (Player player : VersionManager.getOnlinePlayers()) { + result.put(new VersionPlayer(player).getIdString(), load(connection, player)); } + + connection.database.closeConnection(); + + return result; } @Override @@ -100,59 +94,67 @@ public PlayerAccounts loadData(OfflinePlayer player) { if (player == null) return null; - init(); + SQLConnection connection = openConnection(); - PlayerAccounts result = null; + PlayerAccounts result = load(connection, player); + + connection.database.closeConnection(); + + return result; + } + private PlayerAccounts load(SQLConnection connection, OfflinePlayer player) { try { String playerKey = new VersionPlayer(player).getIdString(); - DataSection file = YAMLParser.parseText(table.createEntry(playerKey).getString(DATA), STRING); - result = load(player, file); + DataSection file = YAMLParser.parseText(connection.table.createEntry(playerKey).getString(DATA), STRING); + return load(player, file); } catch (Exception ex) { Logger.bug("Failed to load data from the SQL Database - " + ex.getMessage()); + return null; } - - if (!startup) cleanup(); - - return result; } @Override public void saveData(PlayerAccounts data) { - init(); - saveSingle(data); - cleanup(); + SQLConnection connection = openConnection(); + saveSingle(connection, data); + connection.database.closeConnection(); } @Override public void saveAll() { - init(); + SQLConnection connection = openConnection(); HashMap data = SkillAPI.getPlayerAccountData(); ArrayList keys = new ArrayList(data.keySet()); for (String key : keys) { - saveSingle(data.get(key)); + saveSingle(connection, data.get(key)); } - cleanup(); + connection.database.closeConnection(); } - private void saveSingle(PlayerAccounts data) + private void saveSingle(SQLConnection connection, PlayerAccounts data) { DataSection file = save(data); try { String playerKey = new VersionPlayer(data.getOfflinePlayer()).getIdString(); - table.createEntry(playerKey).set(DATA, file.toString(STRING)); + connection.table.createEntry(playerKey).set(DATA, file.toString(STRING)); } catch (Exception ex) { Logger.bug("Failed to save data for invalid player"); } } + + private class SQLConnection { + private SQLDatabase database; + private SQLTable table; + } } diff --git a/src/com/sucy/skill/dynamic/ComponentRegistry.java b/src/com/sucy/skill/dynamic/ComponentRegistry.java new file mode 100644 index 00000000..88ac5ec6 --- /dev/null +++ b/src/com/sucy/skill/dynamic/ComponentRegistry.java @@ -0,0 +1,398 @@ +package com.sucy.skill.dynamic; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.dynamic.condition.ArmorCondition; +import com.sucy.skill.dynamic.condition.AttributeCondition; +import com.sucy.skill.dynamic.condition.BiomeCondition; +import com.sucy.skill.dynamic.condition.BlockCondition; +import com.sucy.skill.dynamic.condition.CastLevelCondition; +import com.sucy.skill.dynamic.condition.CeilingCondition; +import com.sucy.skill.dynamic.condition.ChanceCondition; +import com.sucy.skill.dynamic.condition.ClassCondition; +import com.sucy.skill.dynamic.condition.ClassLevelCondition; +import com.sucy.skill.dynamic.condition.CombatCondition; +import com.sucy.skill.dynamic.condition.CrouchCondition; +import com.sucy.skill.dynamic.condition.DirectionCondition; +import com.sucy.skill.dynamic.condition.ElevationCondition; +import com.sucy.skill.dynamic.condition.ElseCondition; +import com.sucy.skill.dynamic.condition.EntityTypeCondition; +import com.sucy.skill.dynamic.condition.FireCondition; +import com.sucy.skill.dynamic.condition.FlagCondition; +import com.sucy.skill.dynamic.condition.GroundCondition; +import com.sucy.skill.dynamic.condition.HealthCondition; +import com.sucy.skill.dynamic.condition.InventoryCondition; +import com.sucy.skill.dynamic.condition.ItemCondition; +import com.sucy.skill.dynamic.condition.LightCondition; +import com.sucy.skill.dynamic.condition.LoreCondition; +import com.sucy.skill.dynamic.condition.ManaCondition; +import com.sucy.skill.dynamic.condition.NameCondition; +import com.sucy.skill.dynamic.condition.OffhandCondition; +import com.sucy.skill.dynamic.condition.PermissionCondition; +import com.sucy.skill.dynamic.condition.PotionCondition; +import com.sucy.skill.dynamic.condition.SkillLevelCondition; +import com.sucy.skill.dynamic.condition.SlotCondition; +import com.sucy.skill.dynamic.condition.StatusCondition; +import com.sucy.skill.dynamic.condition.TimeCondition; +import com.sucy.skill.dynamic.condition.ToolCondition; +import com.sucy.skill.dynamic.condition.ValueCondition; +import com.sucy.skill.dynamic.condition.WaterCondition; +import com.sucy.skill.dynamic.condition.WeatherCondition; +import com.sucy.skill.dynamic.custom.CustomComponent; +import com.sucy.skill.dynamic.custom.CustomEffectComponent; +import com.sucy.skill.dynamic.custom.EditorOption; +import com.sucy.skill.dynamic.mechanic.AttributeMechanic; +import com.sucy.skill.dynamic.mechanic.BlockMechanic; +import com.sucy.skill.dynamic.mechanic.BuffMechanic; +import com.sucy.skill.dynamic.mechanic.CancelEffectMechanic; +import com.sucy.skill.dynamic.mechanic.CancelMechanic; +import com.sucy.skill.dynamic.mechanic.ChannelMechanic; +import com.sucy.skill.dynamic.mechanic.CleanseMechanic; +import com.sucy.skill.dynamic.mechanic.CommandMechanic; +import com.sucy.skill.dynamic.mechanic.CooldownMechanic; +import com.sucy.skill.dynamic.mechanic.DamageBuffMechanic; +import com.sucy.skill.dynamic.mechanic.DamageLoreMechanic; +import com.sucy.skill.dynamic.mechanic.DamageMechanic; +import com.sucy.skill.dynamic.mechanic.DefenseBuffMechanic; +import com.sucy.skill.dynamic.mechanic.DelayMechanic; +import com.sucy.skill.dynamic.mechanic.DisguiseMechanic; +import com.sucy.skill.dynamic.mechanic.DurabilityMechanic; +import com.sucy.skill.dynamic.mechanic.ExplosionMechanic; +import com.sucy.skill.dynamic.mechanic.FireMechanic; +import com.sucy.skill.dynamic.mechanic.FlagClearMechanic; +import com.sucy.skill.dynamic.mechanic.FlagMechanic; +import com.sucy.skill.dynamic.mechanic.FlagToggleMechanic; +import com.sucy.skill.dynamic.mechanic.FoodMechanic; +import com.sucy.skill.dynamic.mechanic.ForgetTargetsMechanic; +import com.sucy.skill.dynamic.mechanic.HealMechanic; +import com.sucy.skill.dynamic.mechanic.HealthSetMechanic; +import com.sucy.skill.dynamic.mechanic.HeldItemMechanic; +import com.sucy.skill.dynamic.mechanic.ImmunityMechanic; +import com.sucy.skill.dynamic.mechanic.InterruptMechanic; +import com.sucy.skill.dynamic.mechanic.ItemMechanic; +import com.sucy.skill.dynamic.mechanic.ItemProjectileMechanic; +import com.sucy.skill.dynamic.mechanic.ItemRemoveMechanic; +import com.sucy.skill.dynamic.mechanic.LaunchMechanic; +import com.sucy.skill.dynamic.mechanic.LightningMechanic; +import com.sucy.skill.dynamic.mechanic.ManaMechanic; +import com.sucy.skill.dynamic.mechanic.MessageMechanic; +import com.sucy.skill.dynamic.mechanic.ParticleAnimationMechanic; +import com.sucy.skill.dynamic.mechanic.ParticleEffectMechanic; +import com.sucy.skill.dynamic.mechanic.ParticleMechanic; +import com.sucy.skill.dynamic.mechanic.ParticleProjectileMechanic; +import com.sucy.skill.dynamic.mechanic.PassiveMechanic; +import com.sucy.skill.dynamic.mechanic.PermissionMechanic; +import com.sucy.skill.dynamic.mechanic.PotionMechanic; +import com.sucy.skill.dynamic.mechanic.PotionProjectileMechanic; +import com.sucy.skill.dynamic.mechanic.ProjectileMechanic; +import com.sucy.skill.dynamic.mechanic.PurgeMechanic; +import com.sucy.skill.dynamic.mechanic.PushMechanic; +import com.sucy.skill.dynamic.mechanic.RememberTargetsMechanic; +import com.sucy.skill.dynamic.mechanic.RepeatMechanic; +import com.sucy.skill.dynamic.mechanic.SoundMechanic; +import com.sucy.skill.dynamic.mechanic.SpeedMechanic; +import com.sucy.skill.dynamic.mechanic.StatusMechanic; +import com.sucy.skill.dynamic.mechanic.TauntMechanic; +import com.sucy.skill.dynamic.mechanic.TriggerMechanic; +import com.sucy.skill.dynamic.mechanic.ValueAddMechanic; +import com.sucy.skill.dynamic.mechanic.ValueAttributeMechanic; +import com.sucy.skill.dynamic.mechanic.ValueCopyMechanic; +import com.sucy.skill.dynamic.mechanic.ValueDistanceMechanic; +import com.sucy.skill.dynamic.mechanic.ValueHealthMechanic; +import com.sucy.skill.dynamic.mechanic.ValueLocationMechanic; +import com.sucy.skill.dynamic.mechanic.ValueLoreMechanic; +import com.sucy.skill.dynamic.mechanic.ValueLoreSlotMechanic; +import com.sucy.skill.dynamic.mechanic.ValueManaMechanic; +import com.sucy.skill.dynamic.mechanic.ValueMultiplyMechanic; +import com.sucy.skill.dynamic.mechanic.ValuePlaceholderMechanic; +import com.sucy.skill.dynamic.mechanic.ValueRandomMechanic; +import com.sucy.skill.dynamic.mechanic.ValueSetMechanic; +import com.sucy.skill.dynamic.mechanic.WarpLocMechanic; +import com.sucy.skill.dynamic.mechanic.WarpMechanic; +import com.sucy.skill.dynamic.mechanic.WarpRandomMechanic; +import com.sucy.skill.dynamic.mechanic.WarpSwapMechanic; +import com.sucy.skill.dynamic.mechanic.WarpTargetMechanic; +import com.sucy.skill.dynamic.mechanic.WarpValueMechanic; +import com.sucy.skill.dynamic.mechanic.WolfMechanic; +import com.sucy.skill.dynamic.target.AreaTarget; +import com.sucy.skill.dynamic.target.ConeTarget; +import com.sucy.skill.dynamic.target.LinearTarget; +import com.sucy.skill.dynamic.target.LocationTarget; +import com.sucy.skill.dynamic.target.NearestTarget; +import com.sucy.skill.dynamic.target.OffsetTarget; +import com.sucy.skill.dynamic.target.RememberTarget; +import com.sucy.skill.dynamic.target.SelfTarget; +import com.sucy.skill.dynamic.target.SingleTarget; +import com.sucy.skill.dynamic.trigger.BlockBreakTrigger; +import com.sucy.skill.dynamic.trigger.BlockPlaceTrigger; +import com.sucy.skill.dynamic.trigger.CrouchTrigger; +import com.sucy.skill.dynamic.trigger.DeathTrigger; +import com.sucy.skill.dynamic.trigger.EnvironmentalTrigger; +import com.sucy.skill.dynamic.trigger.KillTrigger; +import com.sucy.skill.dynamic.trigger.LandTrigger; +import com.sucy.skill.dynamic.trigger.LaunchTrigger; +import com.sucy.skill.dynamic.trigger.MoveTrigger; +import com.sucy.skill.dynamic.trigger.PhysicalDealtTrigger; +import com.sucy.skill.dynamic.trigger.PhysicalTakenTrigger; +import com.sucy.skill.dynamic.trigger.SkillDealtTrigger; +import com.sucy.skill.dynamic.trigger.SkillTakenTrigger; +import com.sucy.skill.dynamic.trigger.Trigger; +import org.bukkit.event.Event; +import org.bukkit.plugin.EventExecutor; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.ComponentRegistry + */ +public class ComponentRegistry { + + static final Map>> COMPONENTS = new EnumMap<>(ComponentType.class); + + static final Map> TRIGGERS = new HashMap<>(); + private static final Map, EventExecutor> EXECUTORS = new HashMap<>(); + + public static Trigger getTrigger(final String key) { + return TRIGGERS.get(key.toUpperCase().replace(' ', '_')); + } + + static EffectComponent getComponent(final ComponentType type, final String key) { + final Class componentClass = COMPONENTS.get(type).get(key.toLowerCase()); + if (componentClass == null) { + throw new IllegalArgumentException("Invalid component key - " + key); + } + try { + return (EffectComponent) componentClass.newInstance(); + } catch (final Exception ex) { + throw new IllegalArgumentException("Invalid component - does not have a default constructor"); + } + } + + static EventExecutor getExecutor(final Trigger trigger) { + return EXECUTORS.get(trigger); + } + + @SuppressWarnings("unchecked") + public static void register(final Trigger trigger) { + if (getTrigger(trigger.getKey()) != null) { + throw new IllegalArgumentException("Trigger with key " + trigger.getKey() + " already exists"); + } else if (trigger.getKey().contains("-")) { + throw new IllegalArgumentException(trigger.getKey() + " is not a valid key: must not contain dashes"); + } + + TRIGGERS.put(trigger.getKey(), trigger); + EXECUTORS.put(trigger, (listener, event) -> { + if (!trigger.getEvent().isInstance(event)) return; + ((TriggerHandler) listener).apply((T) event, trigger); + }); + } + + public static void register(final CustomEffectComponent component) { + register((EffectComponent) component); + } + + public static void save() { + final StringBuilder builder = new StringBuilder("["); + TRIGGERS.values().forEach(trigger -> append(trigger, builder)); + COMPONENTS.forEach((type, map) -> map.keySet().forEach(key -> append(getComponent(type, key), builder))); + if (builder.length() > 2) { + builder.replace(builder.length() - 1, builder.length(), "]"); + } else { + builder.append(']'); + } + + final File file = new File(SkillAPI.getPlugin(SkillAPI.class).getDataFolder(), "tool-config.json"); + try (final FileOutputStream out = new FileOutputStream(file)) { + final BufferedWriter write = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")); + write.write(builder.toString()); + write.close(); + } catch (Exception var4) { + var4.printStackTrace(); + } + } + + private static void append(final Object obj, final StringBuilder builder) { + if (!(obj instanceof CustomComponent)) { + return; + } + + final CustomComponent component = (CustomComponent) obj; + builder.append("{\"type\":\"").append(component.getType().name()) + .append("\",\"key\":\"").append(component.getKey()) + .append("\",\"display\":\"").append(component.getDisplayName()) + .append("\",\"container\":\"").append(component.isContainer()) + .append("\",\"description\":\"").append(component.getDescription()) + .append("\",\"options\":["); + + boolean first = true; + for (EditorOption option : component.getOptions()) { + if (!first) { + builder.append(','); + } + first = false; + + builder.append("{\"type\":\"").append(option.type) + .append("\",\"key\":\"").append(option.key) + .append("\",\"display\":\"").append(option.name) + .append("\",\"description\":\"").append(option.description) + .append("\""); + option.extra.forEach((key, value) -> builder.append(",\"").append(key).append("\":").append(value)); + builder.append("}"); + } + + builder.append("]},"); + } + + private static void register(final EffectComponent component) { + COMPONENTS.computeIfAbsent(component.getType(), t -> new HashMap<>()) + .put(component.getKey().toLowerCase(), component.getClass()); + } + + static { + + // Triggers + register(new BlockBreakTrigger()); + register(new BlockPlaceTrigger()); + register(new CrouchTrigger()); + register(new DeathTrigger()); + register(new EnvironmentalTrigger()); + register(new KillTrigger()); + register(new LandTrigger()); + register(new LaunchTrigger()); + register(new MoveTrigger()); + register(new PhysicalDealtTrigger()); + register(new PhysicalTakenTrigger()); + register(new SkillDealtTrigger()); + register(new SkillTakenTrigger()); + + // Targets + register(new AreaTarget()); + register(new ConeTarget()); + register(new LinearTarget()); + register(new LocationTarget()); + register(new NearestTarget()); + register(new OffsetTarget()); + register(new RememberTarget()); + register(new SelfTarget()); + register(new SingleTarget()); + + // Conditions + register(new ArmorCondition()); + register(new AttributeCondition()); + register(new BiomeCondition()); + register(new BlockCondition()); + register(new CastLevelCondition()); + register(new CeilingCondition()); + register(new ChanceCondition()); + register(new ClassCondition()); + register(new ClassLevelCondition()); + register(new CombatCondition()); + register(new CrouchCondition()); + register(new DirectionCondition()); + register(new ElevationCondition()); + register(new ElseCondition()); + register(new EntityTypeCondition()); + register(new FireCondition()); + register(new FlagCondition()); + register(new GroundCondition()); + register(new HealthCondition()); + register(new InventoryCondition()); + register(new ItemCondition()); + register(new LightCondition()); + register(new LoreCondition()); + register(new ManaCondition()); + register(new NameCondition()); + register(new OffhandCondition()); + register(new PermissionCondition()); + register(new PotionCondition()); + register(new SkillLevelCondition()); + register(new SlotCondition()); + register(new StatusCondition()); + register(new TimeCondition()); + register(new ToolCondition()); + register(new ValueCondition()); + register(new WaterCondition()); + register(new WeatherCondition()); + + // Mechanics + register(new AttributeMechanic()); + register(new BlockMechanic()); + register(new BuffMechanic()); + register(new CancelEffectMechanic()); + register(new CancelMechanic()); + register(new ChannelMechanic()); + register(new CleanseMechanic()); + register(new CommandMechanic()); + register(new CooldownMechanic()); + register(new DamageMechanic()); + register(new DamageBuffMechanic()); + register(new DamageLoreMechanic()); + register(new DefenseBuffMechanic()); + register(new DelayMechanic()); + register(new DisguiseMechanic()); + register(new DurabilityMechanic()); + register(new ExplosionMechanic()); + register(new FireMechanic()); + register(new FlagMechanic()); + register(new FlagClearMechanic()); + register(new FlagToggleMechanic()); + register(new FoodMechanic()); + register(new ForgetTargetsMechanic()); + register(new HealMechanic()); + register(new HealthSetMechanic()); + register(new HeldItemMechanic()); + register(new ImmunityMechanic()); + register(new InterruptMechanic()); + register(new ItemMechanic()); + register(new ItemProjectileMechanic()); + register(new ItemRemoveMechanic()); + register(new LaunchMechanic()); + register(new LightningMechanic()); + register(new ManaMechanic()); + register(new MessageMechanic()); + register(new ParticleMechanic()); + register(new ParticleAnimationMechanic()); + register(new ParticleEffectMechanic()); + register(new ParticleProjectileMechanic()); + register(new PassiveMechanic()); + register(new PermissionMechanic()); + register(new PotionMechanic()); + register(new PotionProjectileMechanic()); + register(new ProjectileMechanic()); + register(new PurgeMechanic()); + register(new PushMechanic()); + register(new RememberTargetsMechanic()); + register(new RepeatMechanic()); + register(new SpeedMechanic()); + register(new SoundMechanic()); + register(new StatusMechanic()); + register(new TauntMechanic()); + register(new TriggerMechanic()); + register(new ValueAddMechanic()); + register(new ValueAttributeMechanic()); + register(new ValueCopyMechanic()); + register(new ValueDistanceMechanic()); + register(new ValueHealthMechanic()); + register(new ValueLocationMechanic()); + register(new ValueLoreMechanic()); + register(new ValueLoreSlotMechanic()); + register(new ValueManaMechanic()); + register(new ValueMultiplyMechanic()); + register(new ValuePlaceholderMechanic()); + register(new ValueRandomMechanic()); + register(new ValueSetMechanic()); + register(new WarpMechanic()); + register(new WarpLocMechanic()); + register(new WarpRandomMechanic()); + register(new WarpSwapMechanic()); + register(new WarpTargetMechanic()); + register(new WarpValueMechanic()); + register(new WolfMechanic()); + } +} diff --git a/src/com/sucy/skill/dynamic/ComponentType.java b/src/com/sucy/skill/dynamic/ComponentType.java new file mode 100644 index 00000000..34aa6d4d --- /dev/null +++ b/src/com/sucy/skill/dynamic/ComponentType.java @@ -0,0 +1,12 @@ +package com.sucy.skill.dynamic; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.ComponentType + */ +public enum ComponentType { + CONDITION, + MECHANIC, + TARGET, + TRIGGER +} diff --git a/src/com/sucy/skill/dynamic/DynamicSkill.java b/src/com/sucy/skill/dynamic/DynamicSkill.java index 7e82a49c..c6e81996 100644 --- a/src/com/sucy/skill/dynamic/DynamicSkill.java +++ b/src/com/sucy/skill/dynamic/DynamicSkill.java @@ -26,56 +26,53 @@ */ package com.sucy.skill.dynamic; +import com.google.common.collect.ImmutableList; import com.rit.sucy.config.parse.DataSection; import com.rit.sucy.text.TextFormatter; import com.sucy.skill.SkillAPI; -import com.sucy.skill.api.enums.ManaCost; -import com.sucy.skill.api.event.PhysicalDamageEvent; -import com.sucy.skill.api.event.PlayerLandEvent; -import com.sucy.skill.api.event.SkillDamageEvent; -import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.api.player.PlayerSkill; import com.sucy.skill.api.skills.PassiveSkill; import com.sucy.skill.api.skills.Skill; import com.sucy.skill.api.skills.SkillShot; -import com.sucy.skill.dynamic.mechanic.PassiveMechanic; -import com.sucy.skill.dynamic.mechanic.RepeatMechanic; +import com.sucy.skill.cast.IIndicator; +import com.sucy.skill.dynamic.trigger.TriggerComponent; import com.sucy.skill.log.Logger; import org.bukkit.Material; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; +import org.bukkit.event.Cancellable; import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.sucy.skill.dynamic.ComponentRegistry.getTrigger; /** * A skill implementation for the Dynamic system */ -public class DynamicSkill extends Skill implements SkillShot, PassiveSkill, Listener -{ - private final HashMap components = new HashMap(); - private final HashMap attribKeys = new HashMap(); - private final HashMap active = new HashMap(); +public class DynamicSkill extends Skill implements SkillShot, PassiveSkill, Listener { + private final List triggers = new ArrayList<>(); + private final Map attribKeys = new HashMap<>(); + private final Map active = new HashMap<>(); + + private static final HashMap> castData = new HashMap<>(); - private static final HashMap> castData = new HashMap>(); + private TriggerComponent castTrigger; + private TriggerComponent initializeTrigger; + private TriggerComponent cleanupTrigger; - private boolean cancel = false; - private boolean running = false; + private boolean cancel = false; + private double multiplier = 1; + private double bonus = 0; /** * Initializes a new dynamic skill * * @param name name of the skill */ - public DynamicSkill(String name) - { + public DynamicSkill(final String name) { super(name, "Dynamic", Material.JACK_O_LANTERN, 1); } @@ -84,9 +81,8 @@ public DynamicSkill(String name) * * @return true if can cast, false otherwise */ - public boolean canCast() - { - return components.containsKey(Trigger.CAST); + public boolean canCast() { + return castTrigger != null; } /** @@ -96,8 +92,7 @@ public boolean canCast() * * @return true if active, false otherwise */ - public boolean isActive(LivingEntity caster) - { + public boolean isActive(final LivingEntity caster) { return active.containsKey(caster.getEntityId()); } @@ -108,8 +103,7 @@ public boolean isActive(LivingEntity caster) * * @return active level of the skill */ - public int getActiveLevel(LivingEntity caster) - { + public int getActiveLevel(final LivingEntity caster) { return active.get(caster.getEntityId()); } @@ -120,19 +114,44 @@ public int getActiveLevel(LivingEntity caster) * @param key key string * @param component component to grab attributes from */ - public void setAttribKey(String key, EffectComponent component) - { + void setAttribKey(final String key, final EffectComponent component) { attribKeys.put(key, component); } /** * Cancels the event causing a trigger to go off */ - public void cancelTrigger() - { + public void cancelTrigger() { cancel = true; } + void applyCancelled(final Cancellable event) { + if (checkCancelled()) { + event.setCancelled(true); + } + } + + public boolean checkCancelled() { + final boolean result = cancel; + cancel = false; + return result; + } + + public void setImmediateBuff(final double value, final boolean flat) { + if (flat) { + this.bonus = value; + } else { + this.multiplier = value; + } + } + + public double applyImmediateBuff(final double damage) { + final double result = damage * multiplier + bonus; + multiplier = 1; + bonus = 0; + return result; + } + /** * Retrieves the cast data for the caster * @@ -140,13 +159,11 @@ public void cancelTrigger() * * @return cast data for the caster */ - public static HashMap getCastData(LivingEntity caster) - { - if (caster == null) return null; + public static HashMap getCastData(final LivingEntity caster) { + if (caster == null) { return null; } HashMap map = castData.get(caster.getEntityId()); - if (map == null) - { - map = new HashMap(); + if (map == null) { + map = new HashMap<>(); map.put("caster", caster); castData.put(caster.getEntityId(), map); } @@ -158,11 +175,21 @@ public static HashMap getCastData(LivingEntity caster) * * @param entity entity to clear cast data for */ - public static void clearCastData(LivingEntity entity) - { + public static void clearCastData(final LivingEntity entity) { castData.remove(entity.getEntityId()); } + /** + * Registers needed events for the skill, ignoring any unused events for efficiency + * + * @param plugin plugin reference + */ + public void registerEvents(final SkillAPI plugin) { + for (final TriggerHandler triggerHandler : triggers) { + triggerHandler.register(plugin); + } + } + /** * Updates the skill effects * @@ -171,9 +198,11 @@ public static void clearCastData(LivingEntity entity) * @param newLevel new skill level */ @Override - public void update(LivingEntity user, int prevLevel, int newLevel) - { + public void update(final LivingEntity user, final int prevLevel, final int newLevel) { active.put(user.getEntityId(), newLevel); + for (final TriggerHandler triggerHandler : triggers) { + triggerHandler.init(user, newLevel); + } } /** @@ -183,10 +212,12 @@ public void update(LivingEntity user, int prevLevel, int newLevel) * @param level skill level */ @Override - public void initialize(LivingEntity user, int level) - { - trigger(user, user, level, Trigger.INITIALIZE); + public void initialize(final LivingEntity user, final int level) { + trigger(user, user, level, initializeTrigger); active.put(user.getEntityId(), level); + for (final TriggerHandler triggerHandler : triggers) { + triggerHandler.init(user, level); + } } /** @@ -196,13 +227,19 @@ public void initialize(LivingEntity user, int level) * @param level skill level */ @Override - public void stopEffects(LivingEntity user, int level) - { - RepeatMechanic.stopTasks(user, getName()); - PassiveMechanic.stopTasks(user, getName()); + public void stopEffects(final LivingEntity user, final int level) { active.remove(user.getEntityId()); + for (final TriggerHandler triggerHandler : triggers) { + triggerHandler.cleanup(user); + } + cleanup(user, castTrigger); + cleanup(user, initializeTrigger); + + trigger(user, user, 1, cleanupTrigger); + } - trigger(user, user, 1, Trigger.CLEANUP); + private void cleanup(final LivingEntity user, final TriggerComponent component) { + if (component != null) component.cleanUp(user); } /** @@ -214,9 +251,35 @@ public void stopEffects(LivingEntity user, int level) * @return true if casted successfully, false if conditions weren't met or no effects are using the cast trigger */ @Override - public boolean cast(LivingEntity user, int level) - { - return trigger(user, user, level, Trigger.CAST); + public boolean cast(final LivingEntity user, final int level) { + return trigger(user, user, level, castTrigger); + } + + /** + * Initializes the indicators for a skill. + * + * @param list list to store indicators in + * @param player player to base location on + * @param level the level of the skill to create for + */ + @Override + public void createPreview(final List list, final Player player, final int level) { + list.clear(); + if (castTrigger != null) { + castTrigger.makeIndicators(list, player, ImmutableList.of(player), level); + } + } + + /** + * Updates the positions of indicators for a skill. + * + * @param list list to store indicators in + * @param player player to base location on + * @param level the level of the skill to create for + */ + @Override + public void updateIndicators(final List list, final Player player, final int level) { + createPreview(list, player, level); } /** @@ -228,16 +291,10 @@ public boolean cast(LivingEntity user, int level) * @return formatted attribute name */ @Override - protected String getAttrName(String key) - { - if (key.contains(".")) - { + protected String getAttrName(String key) { + if (key.contains(".")) { return TextFormatter.format(key.substring(key.lastIndexOf('.') + 1)); - } - else - { - return super.getAttrName(key); - } + } else { return super.getAttrName(key); } } /** @@ -253,319 +310,26 @@ protected String getAttrName(String key) * @return attribute value or 0 if invalid dynamic path */ @Override - protected Object getAttr(LivingEntity caster, String key, int level) - { + protected Object getAttr(final LivingEntity caster, final String key, final int level) { // Dynamic attribute paths use periods - if (key.contains(".")) - { - String[] path = key.split("\\."); - String attr = path[1].toLowerCase(); - if (attribKeys.containsKey(path[0]) && attribKeys.get(path[0]).settings.has(attr)) - { - return format(attribKeys.get(path[0]).attr(caster, attr, level, 0, true)); - } - else - { - return 0; - } + if (key.contains(".")) { + final String[] path = key.split("\\."); + final String attr = path[1].toLowerCase(); + if (attribKeys.containsKey(path[0]) && attribKeys.get(path[0]).settings.has(attr)) { + return format(attribKeys.get(path[0]).parseValues(caster, attr, level, 0)); + } else { return 0; } } // Otherwise get the attribute normally - else - { - return super.getAttr(caster, key, level); - } - } - - /** - * Cancels firing projectiles when the launcher is stunned or disarmed. - * - * @param event event details - */ - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onLaunch(ProjectileLaunchEvent event) - { - if (running) return; - - EffectComponent component = components.get(Trigger.LAUNCH); - if (component != null && event.getEntity().getShooter() instanceof LivingEntity) - { - LivingEntity shooter = (LivingEntity) event.getEntity().getShooter(); - if (!active.containsKey(shooter.getEntityId())) - return; - - String type = component.getSettings().getString("type", "any").toUpperCase().replace(" ", "_"); - int level = active.get(shooter.getEntityId()); - if (active.containsKey(shooter.getEntityId()) - && (type.equals("ANY") || type.equals(event.getEntity().getType().name()))) - { - getCastData(shooter).put("api-velocity", event.getEntity().getVelocity().length()); - trigger(shooter, shooter, level, Trigger.LAUNCH); - if (cancel) - { - event.setCancelled(true); - cancel = false; - } - } - } - } - - /** - * Applies the death/kill trigger effects - * - * @param event event details - */ - @EventHandler - public void onDeath(EntityDeathEvent event) - { - if (running) return; - - // Death trigger - EffectComponent component = components.get(Trigger.DEATH); - if (active.containsKey(event.getEntity().getEntityId()) && component != null) - { - boolean killer = component.getSettings().getString("killer", "false").equalsIgnoreCase("true"); - if (!killer || event.getEntity().getKiller() != null) - { - trigger(event.getEntity(), killer ? event.getEntity().getKiller() : event.getEntity(), active.get(event.getEntity().getEntityId()), Trigger.DEATH); - cancel = false; - } - } - - // Kill trigger - Player player = event.getEntity().getKiller(); - if (player != null && active.containsKey(player.getEntityId())) - { - trigger(player, player, active.get(player.getEntityId()), Trigger.KILL); - cancel = false; - } - } - - /** - * Environmental damage trigger - * - * @param event event details - */ - @EventHandler - public void onEnvironmental(EntityDamageEvent event) - { - if (running) return; - if (!(event.getEntity() instanceof LivingEntity)) return; - - EffectComponent component = components.get(Trigger.ENVIRONMENT_DAMAGE); - LivingEntity target = (LivingEntity) event.getEntity(); - if (component != null && active.containsKey(target.getEntityId())) - { - String name = component.getSettings().getString("type", "").toUpperCase().replace(' ', '_'); - if (event.getCause().name().equals(name)) - { - getCastData(target).put("api-taken", event.getDamage()); - trigger(target, target, active.get(target.getEntityId()), Trigger.ENVIRONMENT_DAMAGE); - if (cancel) - { - event.setCancelled(true); - cancel = false; - } - } - } - + else { return super.getAttr(caster, key, level); } } - /** - * Applies physical damage triggers - * - * @param event event details - */ - @EventHandler - public void onPhysical(PhysicalDamageEvent event) - { - if (running) return; - - LivingEntity damager = event.getDamager(); - LivingEntity target = event.getTarget(); - boolean projectile = event.isProjectile(); - - // Can't be null - if (damager == null || target == null) - { - return; - } - - EffectComponent component; - - // Physical receieved - component = components.get(Trigger.TOOK_PHYSICAL_DAMAGE); - if (component != null && active.containsKey(target.getEntityId())) - { - String type = component.settings.getString("type", "both").toLowerCase(); - boolean caster = !component.settings.getString("target", "true").toLowerCase().equals("false"); - double min = component.settings.getDouble("dmg-min"); - double max = component.settings.getDouble("dmg-max"); - - if (event.getDamage() >= min && event.getDamage() <= max - && (type.equals("both") || (type.equals("projectile") == projectile))) - { - getCastData(target).put("api-taken", event.getDamage()); - trigger(target, caster ? target : damager, active.get(target.getEntityId()), Trigger.TOOK_PHYSICAL_DAMAGE); - if (cancel) - { - event.setCancelled(true); - cancel = false; - } - } - } - - // Physical dealt - component = components.get(Trigger.PHYSICAL_DAMAGE); - if (component != null && active.containsKey(damager.getEntityId())) - { - String type = component.settings.getString("type", "both").toLowerCase(); - boolean caster = !component.settings.getString("target", "true").toLowerCase().equals("false"); - double min = component.settings.getDouble("dmg-min"); - double max = component.settings.getDouble("dmg-max"); - - if (event.getDamage() >= min && event.getDamage() <= max - && (type.equals("both") || type.equals("projectile") == projectile)) - { - getCastData(damager).put("api-dealt", event.getDamage()); - trigger(damager, caster ? damager : target, active.get(damager.getEntityId()), Trigger.PHYSICAL_DAMAGE); - if (cancel) - { - event.setCancelled(true); - cancel = false; - } - } - } - } - - /** - * Applies skill damage triggers - * - * @param event event details - */ - @EventHandler - public void onSkillDamage(SkillDamageEvent event) - { - if (running) return; - - LivingEntity damager = event.getDamager(); - LivingEntity target = event.getTarget(); - - // Skill received - EffectComponent component = components.get(Trigger.TOOK_SKILL_DAMAGE); - if (component != null && active.containsKey(target.getEntityId())) - { - boolean caster = !component.settings.getString("target", "true").toLowerCase().equals("false"); - double min = component.settings.getDouble("dmg-min"); - double max = component.settings.getDouble("dmg-max"); - - if (event.getDamage() >= min && event.getDamage() <= max) - { - getCastData(target).put("api-taken", event.getDamage()); - trigger(target, caster ? target : damager, active.get(event.getTarget().getEntityId()), Trigger.TOOK_SKILL_DAMAGE); - if (cancel) - { - event.setCancelled(true); - cancel = false; - } - } - } - - // Skill dealt - component = components.get(Trigger.SKILL_DAMAGE); - if (component != null && active.containsKey(damager.getEntityId())) - { - boolean caster = !component.settings.getString("target", "true").toLowerCase().equals("false"); - double min = component.settings.getDouble("dmg-min"); - double max = component.settings.getDouble("dmg-max"); - - if (event.getDamage() >= min && event.getDamage() <= max) - { - getCastData(damager).put("api-dealt", event.getDamage()); - trigger(damager, caster ? damager : target, active.get(damager.getEntityId()), Trigger.SKILL_DAMAGE); - if (cancel) - { - event.setCancelled(true); - cancel = false; - } - } - } - } - - /** - * Applies crouch triggers - * - * @param event event details - */ - @EventHandler - public void onCrouch(PlayerToggleSneakEvent event) - { - if (running) return; - - EffectComponent component = components.get(Trigger.CROUCH); - if (component != null && active.containsKey(event.getPlayer().getEntityId())) - { - String type = component.settings.getString("type", "start crouching"); - if (type.equalsIgnoreCase("both") || event.isSneaking() != type.equalsIgnoreCase("stop crouching")) - { - trigger(event.getPlayer(), event.getPlayer(), active.get(event.getPlayer().getEntityId()), Trigger.CROUCH); - cancel = false; - } - } - } - - /** - * Land trigger - * - * @param event event details - */ - @EventHandler - public void onLand(PlayerLandEvent event) - { - if (running) return; - - EffectComponent component = components.get(Trigger.LAND); - if (active.containsKey(event.getPlayer().getEntityId()) && component != null) - { - double minDistance = component.settings.getDouble("min-distance", 0); - if (event.getDistance() >= minDistance) - { - getCastData(event.getPlayer()).put("api-distance", event.getDistance()); - trigger(event.getPlayer(), event.getPlayer(), active.get(event.getPlayer().getEntityId()), Trigger.LAND); - cancel = false; - } - } - } - - private boolean trigger(LivingEntity user, LivingEntity target, int level, Trigger trigger) - { - if (user != null && components.containsKey(trigger)) - { - EffectComponent component = components.get(trigger); - if (user instanceof Player) - { - PlayerData data = SkillAPI.getPlayerData((Player) user); - PlayerSkill skill = data.getSkill(getName()); - boolean cd = component.getSettings().getBool("cooldown", false); - boolean mana = component.getSettings().getBool("mana", false); - if ((cd || mana) && !data.check(skill, cd, mana)) - return false; - - if (cd) - skill.startCooldown(); - if (mana) - data.useMana(skill.getManaCost(), ManaCost.SKILL_CAST); - } - - ArrayList targets = new ArrayList(); - targets.add(target); - - running = true; - boolean result = component.execute(user, level, targets); - running = false; - return result; - } - return false; + private boolean trigger( + final LivingEntity user, + final LivingEntity target, + final int level, + final TriggerComponent component) { + return component != null && component.trigger(user, target, level); } /** @@ -574,29 +338,37 @@ private boolean trigger(LivingEntity user, LivingEntity target, int level, Trigg * @param config config data to load from */ @Override - public void load(DataSection config) - { - DataSection triggers = config.getSection("components"); - if (triggers != null) - { - for (String key : triggers.keys()) - { - try - { - Trigger trigger = Trigger.valueOf(key.toUpperCase().replace(' ', '_').replaceAll("-.+", "")); - EffectComponent component = trigger.getComponent(); - component.load(this, triggers.getSection(key)); - components.put(trigger, component); - } - catch (Exception ex) - { - // Invalid trigger - Logger.invalid("Invalid trigger for the skill \"" + getName() + "\" - \"" + key + "\""); + public void load(final DataSection config) { + super.load(config); + + final DataSection triggers = config.getSection("components"); + if (triggers == null) { return; } + + for (final String key : triggers.keys()) { + final String modified = key.replaceAll("-.+", ""); + try { + final DataSection settings = triggers.getSection(key); + if (modified.equalsIgnoreCase("CAST")) { + castTrigger = loadComponent(settings); + } else if (modified.equalsIgnoreCase("INITIALIZE")) { + initializeTrigger = loadComponent(settings); + } else if (modified.equalsIgnoreCase("CLEANUP")) { + cleanupTrigger = loadComponent(settings); + } else { + this.triggers.add(new TriggerHandler(this, key, getTrigger(modified), loadComponent(settings))); } + } catch (final Exception ex) { + // Invalid trigger + ex.printStackTrace(); + Logger.invalid("Invalid trigger for the skill \"" + getName() + "\" - \"" + key + "\""); } } + } - super.load(config); + private TriggerComponent loadComponent(final DataSection data) { + final TriggerComponent component = new TriggerComponent(); + component.load(this, data); + return component; } /** @@ -606,13 +378,19 @@ public void load(DataSection config) * @param config config to save to */ @Override - public void save(DataSection config) - { + public void save(final DataSection config) { super.save(config); - DataSection triggers = config.createSection("components"); - for (Trigger trigger : components.keySet()) - { - components.get(trigger).save(triggers.createSection(TextFormatter.format(trigger.name()))); + final DataSection triggers = config.createSection("components"); + for (final TriggerHandler triggerHandler : this.triggers) { + triggerHandler.getComponent() + .save(triggers.createSection(TextFormatter.format(triggerHandler.getKey()))); } + save(triggers, castTrigger, "Cast"); + save(triggers, initializeTrigger, "Initialize"); + save(triggers, cleanupTrigger, "Cleanup"); + } + + private void save(final DataSection triggers, final TriggerComponent component, final String key) { + if (component != null) component.save(triggers.createSection(TextFormatter.format(key))); } } diff --git a/src/com/sucy/skill/dynamic/EffectComponent.java b/src/com/sucy/skill/dynamic/EffectComponent.java index 85d0895f..11a9502f 100644 --- a/src/com/sucy/skill/dynamic/EffectComponent.java +++ b/src/com/sucy/skill/dynamic/EffectComponent.java @@ -27,14 +27,13 @@ package com.sucy.skill.dynamic; import com.rit.sucy.config.parse.DataSection; +import com.rit.sucy.mobs.MobManager; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.Settings; import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.api.player.PlayerSkill; -import com.sucy.skill.api.util.NumberParser; -import com.sucy.skill.dynamic.condition.*; -import com.sucy.skill.dynamic.mechanic.*; -import com.sucy.skill.dynamic.target.*; +import com.sucy.skill.cast.IIndicator; +import com.sucy.skill.cast.IndicatorType; import com.sucy.skill.log.Logger; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -42,19 +41,21 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * A component for dynamic skills which takes care of one effect */ -public abstract class EffectComponent -{ - //public static final Pattern NUMBER = Pattern.compile("(-?[0-9]+([,.][0-9]+)?)|(-?[,.][0-9]+)"); - //public static final Pattern RANGE = Pattern.compile("((-?[0-9]+([,.][0-9]+)?)|(-?[,.][0-9]+))-((-?[0-9]+([,.][0-9]+)?)|(-?[,.][0-9]+))"); - +public abstract class EffectComponent { private static final String ICON_KEY = "icon-key"; private static final String COUNTS_KEY = "counts"; - public final ArrayList children = new ArrayList(); + private static boolean passed; + + /** + * Child components + */ + public final ArrayList children = new ArrayList<>(); /** * The settings for the component @@ -67,45 +68,57 @@ public abstract class EffectComponent protected DynamicSkill skill; /** - * Key of the component for the config + * Type of indicators to show */ - private String key; + protected IndicatorType indicatorType; /** - * Type of the component + * Whether or not the component has preview effects */ - private String type = "trigger"; + public boolean hasEffect; + + private String instanceKey; /** * Retrieves the config key for the component * * @return config key of the component */ - public String getKey() - { - return key; + public String getInstanceKey() { + return instanceKey; } + public abstract String getKey(); + /** * Retrieves the type of the component * * @return component type */ - public String getType() - { - return type; - } + public abstract ComponentType getType(); /** * Retrieves the settings of the dynamic component * * @return settings of the dynamic component */ - public Settings getSettings() - { + public Settings getSettings() { return settings; } + /** + * Checks whether or not the component or its children have an effect + * + * @return true if has an effect, false otherwise + */ + private boolean hasEffect() { + if (indicatorType != IndicatorType.NONE) { return true; } + + for (EffectComponent child : children) { if (child.hasEffect()) { return true; } } + + return false; + } + /** * Retrieves an attribute value while applying attribute * data if enabled and a player is using the skill @@ -114,21 +127,18 @@ public Settings getSettings() * @param key key of the value to grab * @param level level of the skill * @param fallback default value for the attribute - * @param self whether or not the skill is targeting the caster * * @return the value with attribute modifications if applicable */ - protected double attr(LivingEntity caster, String key, int level, double fallback, boolean self) - { + protected double parseValues(LivingEntity caster, String key, int level, double fallback) { double base = getNum(caster, key + "-base", fallback); double scale = getNum(caster, key + "-scale", 0); double value = base + (level - 1) * scale; // Apply global modifiers - if (SkillAPI.getSettings().isAttributesEnabled() && caster instanceof Player) - { + if (SkillAPI.getSettings().isAttributesEnabled() && caster instanceof Player) { PlayerData data = SkillAPI.getPlayerData((Player) caster); - value = data.scaleDynamic(this, key, value, self); + value = data.scaleDynamic(this, key, value); } return value; @@ -147,42 +157,41 @@ protected double attr(LivingEntity caster, String key, int level, double fallbac * * @return the settings value or, if not a number, the cast data value */ - protected double getNum(LivingEntity caster, String key, double fallback) - { + protected double getNum(LivingEntity caster, String key, double fallback) { String val = settings.getString(key); - if (val == null) - { - return fallback; - } - try - { - return NumberParser.parseDouble(val); - } - catch (Exception ex) { /* Not a number */ } - - HashMap map = DynamicSkill.getCastData(caster); - if (map.containsKey(val)) - { - String mapVal = map.get(val).toString(); - try - { - return NumberParser.parseDouble(mapVal); - } - catch (Exception ex) { /* Not a number */ } + if (val == null) { return fallback; } + + try { + return Double.parseDouble(val); + } catch (Exception ex) { /* Not a number */ } + + final Map map = DynamicSkill.getCastData(caster); + if (map.containsKey(val)) { + final String mapVal = map.get(val).toString(); + try { + return Double.parseDouble(mapVal); + } catch (Exception ex) { /* Not a number */ } } - try - { - int mid = val.indexOf('-', 1); - double min = NumberParser.parseDouble(val.substring(0, mid)); - double max = NumberParser.parseDouble(val.substring(mid + 1)); + try { + final int mid = val.indexOf('-', 1); + final double min = Double.parseDouble(val.substring(0, mid)); + final double max = Double.parseDouble(val.substring(mid + 1)); return Math.random() * (max - min) + min; - } - catch (Exception ex) { /* Not a range */ } + } catch (Exception ex) { /* Not a range */ } return 0; } + /** + * Checks whether or not the last component passed or not + * + * @return true if passed, false otherwise + */ + protected boolean lastPassed() { + return passed; + } + /** * Executes the children of the component using the given targets * @@ -192,17 +201,27 @@ protected double getNum(LivingEntity caster, String key, double fallback) * * @return true if executed, false if conditions not met */ - protected boolean executeChildren(LivingEntity caster, int level, List targets) - { + protected boolean executeChildren(LivingEntity caster, int level, List targets) { + if (targets.isEmpty()) { + return false; + } + boolean worked = false; - for (EffectComponent child : children) - { + for (EffectComponent child : children) { boolean counts = !child.settings.getString(COUNTS_KEY, "true").toLowerCase().equals("false"); - worked = (child.execute(caster, level, targets) && counts) || worked; + passed = child.execute(caster, level, targets); + worked = (passed && counts) || worked; } return worked; } + public void cleanUp(final LivingEntity caster) { + doCleanUp(caster); + children.forEach(child -> child.cleanUp(caster)); + } + + protected void doCleanUp(final LivingEntity caster) { } + /** * Gets the skill data for the caster * @@ -210,18 +229,55 @@ protected boolean executeChildren(LivingEntity caster, int level, List data = DynamicSkill.getCastData(caster); + + int k = 0; + while (i >= 0 && j > i) { + String key = text.substring(i + 1, j); + if (data.containsKey(key)) { + Object obj = data.get(key); + if (obj instanceof Player) { obj = ((Player) obj).getName(); } else if (obj instanceof LivingEntity) { + obj = MobManager.getName((LivingEntity) obj); + } + builder.append(text.substring(k, i)); + builder.append(obj); + + k = j + 1; + } else if (key.equals("player")) { + builder.append(text.substring(k, i)); + builder.append(caster.getName()); + + k = j + 1; + } else if (key.equals("target")) { + builder.append(text.substring(k, i)); + builder.append(target.getName()); + + k = j + 1; + } + i = text.indexOf('{', j); + j = text.indexOf('}', i); + } + builder.append(text.substring(k)); + return builder.toString(); + } + /** * Executes the component (to be implemented) * @@ -233,21 +289,35 @@ protected PlayerSkill getSkillData(LivingEntity caster) */ public abstract boolean execute(LivingEntity caster, int level, List targets); - private static final String TYPE = "type"; + /** + * Creates the list of indicators for the skill + * + * @param list list to store indicators in + * @param caster caster reference + * @param targets location to base location on + * @param level the level of the skill to create for + */ + public void makeIndicators(List list, Player caster, List targets, int level) { + if (hasEffect) { + for (EffectComponent component : children) { component.makeIndicators(list, caster, targets, level); } + } + } + + private static final String TYPE = "type"; + private static final String INDICATOR = "indicator"; /** * Saves the component and its children to the config * * @param config config to save to */ - public void save(DataSection config) - { - config.set(TYPE, type); + public void save(DataSection config) { + config.set(TYPE, getType().name().toLowerCase()); + config.set(INDICATOR, indicatorType.getKey()); settings.save(config.createSection("data")); DataSection children = config.createSection("children"); - for (EffectComponent child : this.children) - { - child.save(children.createSection(child.key)); + for (EffectComponent child : this.children) { + child.save(children.createSection(child.instanceKey)); } } @@ -257,176 +327,42 @@ public void save(DataSection config) * @param skill owning skill of the component * @param config config data to load from */ - public void load(DynamicSkill skill, DataSection config) - { + public void load(DynamicSkill skill, DataSection config) { this.skill = skill; - if (config == null) - { + if (config == null) { return; } settings.load(config.getSection("data")); - if (settings.has(ICON_KEY)) - { + if (settings.has(ICON_KEY)) { String key = settings.getString(ICON_KEY); - if (!key.equals("")) - { + if (!key.equals("")) { skill.setAttribKey(key, this); } } + indicatorType = IndicatorType.getByKey(settings.getString(INDICATOR, "2D")); DataSection children = config.getSection("children"); - if (children != null) - { - for (String key : children.keys()) - { - String type = children.getSection(key).getString(TYPE, "missing").toLowerCase(); - HashMap> map; - if (type.equals("target")) - { - map = targets; - } - else if (type.equals("condition")) - { - map = conditions; - } - else if (type.equals("mechanic")) - { - map = mechanics; - } - else - { - Logger.invalid("Invalid component type - " + type); - continue; - } - String mkey = key.toLowerCase().replaceAll("-.+", ""); - if (map.containsKey(mkey)) - { - try - { - EffectComponent child = map.get(mkey).newInstance(); - child.key = key; - child.type = type; + if (children != null) { + for (String key : children.keys()) { + final String typeName = children.getSection(key).getString(TYPE, "missing").toUpperCase(); + final ComponentType type = ComponentType.valueOf(typeName); + final String mkey = key.replaceAll("-.+", ""); + try { + final EffectComponent child = ComponentRegistry.getComponent(type, mkey); + if (child != null) { + child.instanceKey = key; child.load(skill, children.getSection(key)); this.children.add(child); + } else { + Logger.invalid("Invalid " + type + " component: " + mkey); } - catch (Exception ex) - { - // Failed to create the component, just don't add it - Logger.bug("Failed to create " + type + " component: " + key); - } - } - else - { - Logger.invalid("Invalid " + type + " component: " + key); + } catch (Exception ex) { + // Failed to create the component, just don't add it + Logger.bug("Failed to create " + type + " component: " + key); } } } - } - private static final HashMap> targets = new HashMap>() - {{ - put("area", AreaTarget.class); - put("cone", ConeTarget.class); - put("linear", LinearTarget.class); - put("location", LocationTarget.class); - put("nearest", NearestTarget.class); - put("offset", OffsetTarget.class); - put("remember", RememberTarget.class); - put("self", SelfTarget.class); - put("single", SingleTarget.class); - }}; - - private static final HashMap> conditions = new HashMap>() - {{ - put("armor", ArmorCondition.class); - put("attribute", AttributeCondition.class); - put("biome", BiomeCondition.class); - put("block", BlockCondition.class); - put("chance", ChanceCondition.class); - put("class", ClassCondition.class); - put("class level", ClassLevelCondition.class); - put("combat", CombatCondition.class); - put("crouch", CrouchCondition.class); - put("direction", DirectionCondition.class); - put("elevation", ElevationCondition.class); - put("fire", FireCondition.class); - put("flag", FlagCondition.class); - put("health", HealthCondition.class); - put("inventory", InventoryCondition.class); - put("item", ItemCondition.class); - put("light", LightCondition.class); - put("lore", LoreCondition.class); - put("mana", ManaCondition.class); - put("name", NameCondition.class); - put("offhand", OffhandCondition.class); - put("potion", PotionCondition.class); - put("skill level", SkillLevelCondition.class); - put("status", StatusCondition.class); - put("time", TimeCondition.class); - put("tool", ToolCondition.class); - put("value", ValueCondition.class); - put("water", WaterCondition.class); - }}; - - private static final HashMap> mechanics = new HashMap>() - {{ - put("attribute", AttributeMechanic.class); - put("block", BlockMechanic.class); - put("cancel", CancelMechanic.class); - put("channel", ChannelMechanic.class); - put("cleanse", CleanseMechanic.class); - put("command", CommandMechanic.class); - put("cooldown", CooldownMechanic.class); - put("damage", DamageMechanic.class); - put("damage buff", DamageBuffMechanic.class); - put("damage lore", DamageLoreMechanic.class); - put("defense buff", DefenseBuffMechanic.class); - put("delay", DelayMechanic.class); - put("disguise", DisguiseMechanic.class); - put("explosion", ExplosionMechanic.class); - put("fire", FireMechanic.class); - put("flag", FlagMechanic.class); - put("flag clear", FlagClearMechanic.class); - put("flag toggle", FlagToggleMechanic.class); - put("heal", HealMechanic.class); - put("immunity", ImmunityMechanic.class); - put("interrupt", InterruptMechanic.class); - put("item", ItemMechanic.class); - put("item projectile", ItemProjectileMechanic.class); - put("item remove", ItemRemoveMechanic.class); - put("launch", LaunchMechanic.class); - put("lightning", LightningMechanic.class); - put("mana", ManaMechanic.class); - put("message", MessageMechanic.class); - put("particle", ParticleMechanic.class); - put("particle animation", ParticleAnimationMechanic.class); - put("particle projectile", ParticleProjectileMechanic.class); - put("passive", PassiveMechanic.class); - put("permission", PermissionMechanic.class); - put("potion", PotionMechanic.class); - put("potion projectile", PotionProjectileMechanic.class); - put("projectile", ProjectileMechanic.class); - put("purge", PurgeMechanic.class); - put("push", PushMechanic.class); - put("remember targets", RememberTargetsMechanic.class); - put("repeat", RepeatMechanic.class); - put("speed", SpeedMechanic.class); - put("sound", SoundMechanic.class); - put("status", StatusMechanic.class); - put("taunt", TauntMechanic.class); - put("value add", ValueAddMechanic.class); - put("value attribute", ValueAttributeMechanic.class); - put("value location", ValueLocationMechanic.class); - put("value lore", ValueLoreMechanic.class); - put("value multiply", ValueMultiplyMechanic.class); - put("value random", ValueRandomMechanic.class); - put("value set", ValueSetMechanic.class); - put("warp", WarpMechanic.class); - put("warp location", WarpLocMechanic.class); - put("warp random", WarpRandomMechanic.class); - put("warp swap", WarpSwapMechanic.class); - put("warp target", WarpTargetMechanic.class); - put("warp value", WarpValueMechanic.class); - put("wolf", WolfMechanic.class); - }}; + hasEffect = hasEffect(); + } } diff --git a/src/com/sucy/skill/dynamic/ItemChecker.java b/src/com/sucy/skill/dynamic/ItemChecker.java index a1fb76bd..692d2a7f 100644 --- a/src/com/sucy/skill/dynamic/ItemChecker.java +++ b/src/com/sucy/skill/dynamic/ItemChecker.java @@ -26,13 +26,16 @@ */ package com.sucy.skill.dynamic; +import com.rit.sucy.config.parse.NumberParser; import com.sucy.skill.api.Settings; import org.bukkit.ChatColor; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import java.util.List; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -54,16 +57,18 @@ public class ItemChecker /** * Checks the player inventory for items matching the settings * - * @param player player to check - * @param level level of the effect - * @param settings settings to apply - * @param remove whether or not to remove matching items + * @param player player to check + * @param level level of the effect + * @param component effect component checking for + * @param remove whether or not to remove matching items * * @return true if all conditions met, false otherwise */ - public static boolean check(Player player, int level, Settings settings, boolean remove) + public static boolean check(Player player, int level, EffectComponent component, boolean remove) { - int count = (int) settings.getAttr(AMOUNT, level, 1); + final Settings settings = component.getSettings(); + + int count = (int) component.parseValues(player, AMOUNT, level, 1); // Checks to do boolean mat = settings.getBool(CHECK_MAT, true); @@ -207,4 +212,35 @@ else if (!regex && line.contains(target)) return false; } + + public static boolean findLore(LivingEntity caster, ItemStack item, String regex, String key, double multiplier) + { + Pattern pattern = Pattern.compile(regex.replace("{value}", "([+-]?[0-9]+([.,][0-9]+)?)")); + + if (item == null || !item.hasItemMeta() || !item.getItemMeta().hasLore()) + return false; + + List lore = item.getItemMeta().getLore(); + for (String line : lore) + { + line = ChatColor.stripColor(line); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) + { + String value = matcher.group(1); + try + { + double base = NumberParser.parseDouble(value); + DynamicSkill.getCastData(caster).put(key, base * multiplier); + break; + } + catch (Exception ex) + { + // Not a valid value + } + } + } + + return true; + } } diff --git a/src/com/sucy/skill/dynamic/TempEntity.java b/src/com/sucy/skill/dynamic/TempEntity.java index 85ed5c77..d365f325 100644 --- a/src/com/sucy/skill/dynamic/TempEntity.java +++ b/src/com/sucy/skill/dynamic/TempEntity.java @@ -26,11 +26,29 @@ */ package com.sucy.skill.dynamic; -import org.bukkit.*; +import com.google.common.collect.ImmutableList; +import com.sucy.skill.api.particle.target.EffectTarget; +import com.sucy.skill.api.particle.target.EntityTarget; +import com.sucy.skill.api.particle.target.FixedTarget; +import com.sucy.skill.api.util.Nearby; +import org.bukkit.Bukkit; +import org.bukkit.EntityEffect; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.World; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; import org.bukkit.block.Block; -import org.bukkit.entity.*; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Egg; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Snowball; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.EntityEquipment; @@ -43,390 +61,352 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.util.Vector; -import java.util.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; /** * Temporary dummy entity used for targeting a location in the dynamic system */ -public class TempEntity implements LivingEntity -{ +public class TempEntity implements LivingEntity { - private Location loc; + private EffectTarget target; /** * Sets up a new dummy entity * * @param loc location to represent */ - public TempEntity(Location loc) - { - this.loc = loc; + public TempEntity(Location loc) { + this(new FixedTarget(loc)); } - @Override - public double getEyeHeight() - { - return 0; + public TempEntity(final EffectTarget target) { + this.target = target; } - @Override - public double getEyeHeight(boolean b) - { - return 0; + public double getEyeHeight() { + return 0.2; } - @Override - public Location getEyeLocation() - { - return null; + public double getEyeHeight(boolean b) { + return 0.2; + } + + public Location getEyeLocation() { + return getLocation().add(0, 1, 0); } - @Override - public List getLineOfSight(HashSet hashSet, int i) - { + public List getLineOfSight(HashSet hashSet, int i) { return null; } - @Override - public List getLineOfSight(Set set, int i) - { + public List getLineOfSight(Set set, int i) { return null; } - @Override - public Block getTargetBlock(HashSet hashSet, int i) - { + public Block getTargetBlock(HashSet hashSet, int i) { return null; } - @Override - public Block getTargetBlock(Set set, int i) - { + public Block getTargetBlock(Set set, int i) { return null; } - @Override - public List getLastTwoTargetBlocks(HashSet hashSet, int i) - { + public List getLastTwoTargetBlocks(HashSet hashSet, int i) { return null; } - @Override - public List getLastTwoTargetBlocks(Set set, int i) - { + public List getLastTwoTargetBlocks(Set set, int i) { return null; } - public Egg throwEgg() - { + public Egg throwEgg() { return null; } - public Snowball throwSnowball() - { + public Snowball throwSnowball() { return null; } - public Arrow shootArrow() - { + public Arrow shootArrow() { return null; } - @Override - public int getRemainingAir() - { + public int getRemainingAir() { return 0; } - @Override - public void setRemainingAir(int i) - { + public void setRemainingAir(int i) { } - @Override - public int getMaximumAir() - { + public int getMaximumAir() { return 0; } - @Override - public void setMaximumAir(int i) - { + public void setMaximumAir(int i) { } - @Override - public int getMaximumNoDamageTicks() - { + public int getMaximumNoDamageTicks() { return 0; } - @Override - public void setMaximumNoDamageTicks(int i) - { + public void setMaximumNoDamageTicks(int i) { } - @Override - public double getLastDamage() - { + public double getLastDamage() { return 0; } - @Override - public int _INVALID_getLastDamage() - { + public int _INVALID_getLastDamage() { return 0; } - @Override - public void setLastDamage(double v) - { + public void setLastDamage(double v) { } - @Override - public void _INVALID_setLastDamage(int i) - { + public void _INVALID_setLastDamage(int i) { } - @Override - public int getNoDamageTicks() - { + public int getNoDamageTicks() { return 0; } - @Override - public void setNoDamageTicks(int i) - { + public void setNoDamageTicks(int i) { } - @Override - public Player getKiller() - { + public Player getKiller() { return null; } - @Override - public boolean addPotionEffect(PotionEffect potionEffect) - { + public boolean addPotionEffect(PotionEffect potionEffect) { return false; } - @Override - public boolean addPotionEffect(PotionEffect potionEffect, boolean b) - { + public boolean addPotionEffect(PotionEffect potionEffect, boolean b) { return false; } - @Override - public boolean addPotionEffects(Collection collection) - { + public boolean addPotionEffects(Collection collection) { return false; } - @Override - public boolean hasPotionEffect(PotionEffectType potionEffectType) - { + public boolean hasPotionEffect(PotionEffectType potionEffectType) { return false; } - @Override - public void removePotionEffect(PotionEffectType potionEffectType) - { + public PotionEffect getPotionEffect(PotionEffectType potionEffectType) { + return null; + } + + public void removePotionEffect(PotionEffectType potionEffectType) { } - @Override - public Collection getActivePotionEffects() - { - return null; + public Collection getActivePotionEffects() { + return ImmutableList.of(); } - @Override - public boolean hasLineOfSight(Entity entity) - { + public boolean hasLineOfSight(Entity entity) { return false; } - @Override - public boolean getRemoveWhenFarAway() - { + public boolean getRemoveWhenFarAway() { return false; } - @Override - public void setRemoveWhenFarAway(boolean b) - { + public void setRemoveWhenFarAway(boolean b) { } - @Override - public EntityEquipment getEquipment() - { + public EntityEquipment getEquipment() { return null; } - @Override - public void setCanPickupItems(boolean b) - { + public void setCanPickupItems(boolean b) { } - @Override - public boolean getCanPickupItems() - { + public boolean getCanPickupItems() { return false; } - @Override - public void setCustomName(String s) - { + public void setCustomName(String s) { } - @Override - public String getCustomName() - { + public String getCustomName() { return null; } - @Override - public void setCustomNameVisible(boolean b) - { + public void setCustomNameVisible(boolean b) { + + } + + public boolean isCustomNameVisible() { + return false; + } + + public void setGlowing(boolean b) { + + } + + public boolean isGlowing() { + return false; + } + + public void setInvulnerable(boolean b) { } - @Override - public boolean isCustomNameVisible() - { + public boolean isInvulnerable() { + return false; + } + + public boolean isSilent() { return false; } - public void setGlowing(boolean b) - { + public void setSilent(boolean b) { } - public boolean isGlowing() - { + public boolean hasGravity() { return false; } - @Override - public Spigot spigot() - { + public void setGravity(boolean b) { + + } + + public int getPortalCooldown() { + return 0; + } + + public void setPortalCooldown(int i) { + + } + + public Set getScoreboardTags() { return null; } - @Override - public boolean isLeashed() - { + public boolean addScoreboardTag(String s) { return false; } - @Override - public Entity getLeashHolder() throws IllegalStateException - { + public boolean removeScoreboardTag(String s) { + return false; + } + + public PistonMoveReaction getPistonMoveReaction() { return null; } - @Override - public boolean setLeashHolder(Entity entity) - { + public boolean isLeashed() { return false; } - @Override - public void damage(double v) - { + public Entity getLeashHolder() throws IllegalStateException { + return null; + } + public boolean setLeashHolder(Entity entity) { + return false; + } + + public boolean isGliding() { + return false; + } + + public void setGliding(boolean b) { + + } + + public boolean isSwimming() { return false; } + + public void setSwimming(final boolean b) { } + + public void setAI(boolean b) { } + + public boolean hasAI() { + return false; + } + + public void setCollidable(boolean b) { + + } + + public boolean isCollidable() { + return false; } - @Override - public void _INVALID_damage(int i) - { + public void damage(double v) { } - @Override - public void damage(double v, Entity entity) - { + public void _INVALID_damage(int i) { } - @Override - public void _INVALID_damage(int i, Entity entity) - { + public void damage(double v, Entity entity) { } - @Override - public double getHealth() - { + public void _INVALID_damage(int i, Entity entity) { + + } + + public double getHealth() { return 1; } - @Override - public int _INVALID_getHealth() - { + public int _INVALID_getHealth() { return 0; } - @Override - public void setHealth(double v) - { + public void setHealth(double v) { } - @Override - public void _INVALID_setHealth(int i) - { + public void _INVALID_setHealth(int i) { } - @Override - public double getMaxHealth() - { + public double getMaxHealth() { return 1; } - @Override - public int _INVALID_getMaxHealth() - { + public int _INVALID_getMaxHealth() { return 0; } - @Override - public void setMaxHealth(double v) - { + public void setMaxHealth(double v) { } - @Override - public void _INVALID_setMaxHealth(int i) - { + public void _INVALID_setMaxHealth(int i) { } - @Override - public void resetMaxHealth() - { + public void resetMaxHealth() { } - @Override - public Location getLocation() - { - return loc.clone(); + public Location getLocation() { + return target.getLocation().clone(); } - @Override - public Location getLocation(Location location) - { + public Location getLocation(final Location location) { + final Location loc = target.getLocation(); location.setX(loc.getX()); location.setY(loc.getY()); location.setZ(loc.getZ()); @@ -436,350 +416,250 @@ public Location getLocation(Location location) return location; } - @Override - public void setVelocity(Vector vector) - { + public void setVelocity(Vector vector) { } - @Override - public Vector getVelocity() - { + public Vector getVelocity() { return new Vector(0, 0, 0); } - @Override - public boolean isOnGround() - { + public double getHeight() { + return 0; + } + + public double getWidth() { + return 0; + } + + public boolean isOnGround() { return true; } - @Override - public World getWorld() - { - return loc.getWorld(); + public World getWorld() { + return target.getLocation().getWorld(); } - @Override - public boolean teleport(Location location) - { - loc = location; + public boolean teleport(Location location) { + target = new FixedTarget(location); return true; } - @Override - public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause teleportCause) - { - loc = location; + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause teleportCause) { + target = new FixedTarget(location); return true; } - @Override - public boolean teleport(Entity entity) - { - loc = entity.getLocation(); + public boolean teleport(Entity entity) { + target = new EntityTarget(entity); return true; } - @Override - public boolean teleport(Entity entity, PlayerTeleportEvent.TeleportCause teleportCause) - { - loc = entity.getLocation(); + public boolean teleport(Entity entity, PlayerTeleportEvent.TeleportCause teleportCause) { + target = new EntityTarget(entity); return true; } - @Override - public List getNearbyEntities(double x, double y, double z) - { - ArrayList list = new ArrayList(); - for (Entity entity : loc.getWorld().getEntities()) - { - if (entity.getLocation().distanceSquared(loc) < x * x) - { - list.add(entity); - } - } - return list; + public List getNearbyEntities(double x, double y, double z) { + return Nearby.getNearby(target.getLocation(), x); } - @Override - public int getEntityId() - { + public int getEntityId() { return 0; } - @Override - public int getFireTicks() - { + public int getFireTicks() { return 0; } - @Override - public int getMaxFireTicks() - { + public int getMaxFireTicks() { return 0; } - @Override - public void setFireTicks(int i) - { + public void setFireTicks(int i) { } - @Override - public void remove() - { + public void remove() { } - @Override - public boolean isDead() - { + public boolean isDead() { return false; } - @Override - public boolean isValid() - { + public boolean isValid() { return true; } - @Override - public void sendMessage(String s) - { + public void sendMessage(String s) { } - @Override - public void sendMessage(String[] strings) - { + public void sendMessage(String[] strings) { } - @Override - public Server getServer() - { + public Server getServer() { return Bukkit.getServer(); } - @Override - public String getName() - { + public String getName() { return "Location"; } - @Override - public Entity getPassenger() - { + public Entity getPassenger() { return null; } - @Override - public boolean setPassenger(Entity entity) - { + public boolean setPassenger(Entity entity) { + return false; + } + + public List getPassengers() { + return null; + } + + public boolean addPassenger(final Entity entity) { + return false; + } + + public boolean removePassenger(final Entity entity) { return false; } - @Override - public boolean isEmpty() - { + public boolean isEmpty() { return false; } - @Override - public boolean eject() - { + public boolean eject() { return false; } - @Override - public float getFallDistance() - { + public float getFallDistance() { return 0; } - @Override - public void setFallDistance(float v) - { + public void setFallDistance(float v) { } - @Override - public void setLastDamageCause(EntityDamageEvent entityDamageEvent) - { + public void setLastDamageCause(EntityDamageEvent entityDamageEvent) { } - @Override - public EntityDamageEvent getLastDamageCause() - { + public EntityDamageEvent getLastDamageCause() { return null; } - @Override - public UUID getUniqueId() - { + public UUID getUniqueId() { return null; } - @Override - public int getTicksLived() - { + public int getTicksLived() { return 0; } - @Override - public void setTicksLived(int i) - { + public void setTicksLived(int i) { } - @Override - public void playEffect(EntityEffect entityEffect) - { + public void playEffect(EntityEffect entityEffect) { } - @Override - public EntityType getType() - { + public EntityType getType() { return EntityType.CHICKEN; } - @Override - public boolean isInsideVehicle() - { + public boolean isInsideVehicle() { return false; } - @Override - public boolean leaveVehicle() - { + public boolean leaveVehicle() { return false; } - @Override - public Entity getVehicle() - { + public Entity getVehicle() { return null; } - @Override - public void setMetadata(String s, MetadataValue metadataValue) - { + public void setMetadata(String s, MetadataValue metadataValue) { } - @Override - public List getMetadata(String s) - { + public List getMetadata(String s) { return null; } - @Override - public boolean hasMetadata(String s) - { + public boolean hasMetadata(String s) { return false; } - @Override - public void removeMetadata(String s, Plugin plugin) - { + public void removeMetadata(String s, Plugin plugin) { } - @Override - public T launchProjectile(Class aClass) - { + public T launchProjectile(Class aClass) { return null; } - @Override - public T launchProjectile(Class aClass, Vector vector) - { + public T launchProjectile(Class aClass, Vector vector) { return null; } - @Override - public boolean isPermissionSet(String s) - { + public boolean isPermissionSet(String s) { return false; } - @Override - public boolean isPermissionSet(Permission permission) - { + public boolean isPermissionSet(Permission permission) { return false; } - @Override - public boolean hasPermission(String s) - { + public boolean hasPermission(String s) { return false; } - @Override - public boolean hasPermission(Permission permission) - { + public boolean hasPermission(Permission permission) { return false; } - @Override - public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b) - { + public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b) { return null; } - @Override - public PermissionAttachment addAttachment(Plugin plugin) - { + public PermissionAttachment addAttachment(Plugin plugin) { return null; } - @Override - public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b, int i) - { + public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b, int i) { return null; } - @Override - public PermissionAttachment addAttachment(Plugin plugin, int i) - { + public PermissionAttachment addAttachment(Plugin plugin, int i) { return null; } - @Override - public void removeAttachment(PermissionAttachment permissionAttachment) - { + public void removeAttachment(PermissionAttachment permissionAttachment) { } - @Override - public void recalculatePermissions() - { + public void recalculatePermissions() { } - @Override - public Set getEffectivePermissions() - { + public Set getEffectivePermissions() { return null; } - @Override - public boolean isOp() - { + public boolean isOp() { return false; } - @Override - public void setOp(boolean b) - { + public void setOp(boolean b) { } - @Override - public AttributeInstance getAttribute(Attribute attribute) - { + public AttributeInstance getAttribute(Attribute attribute) { return null; } } diff --git a/src/com/sucy/skill/dynamic/Trigger.java b/src/com/sucy/skill/dynamic/Trigger.java deleted file mode 100644 index a7133637..00000000 --- a/src/com/sucy/skill/dynamic/Trigger.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * SkillAPI - * com.sucy.skill.dynamic.Trigger - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Steven Sucy - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software") to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.sucy.skill.dynamic; - -import org.bukkit.entity.LivingEntity; - -import java.util.List; - -/** - * Possible triggers for dynamic skill effects - */ -public enum Trigger -{ - /** - * Trigger effects when a player casts the skill - */ - CAST, - - /** - * Trigger effects when the player crouches - */ - CROUCH, - - /** - * Trigger effects when the player takes environmental damage - */ - ENVIRONMENT_DAMAGE, - - /** - * Trigger effects when the player inflicts non-skill damage - */ - PHYSICAL_DAMAGE, - - /** - * Trigger effects when the player inflicts skill damage - */ - SKILL_DAMAGE, - - /** - * Trigger effects when the player dies - */ - DEATH, - - /** - * Trigger effects when the player falls to a certain health percentage - */ - //HEALTH, - - /** - * Trigger effects when launching a projectile - */ - LAUNCH, - - /** - * Trigger effects when the skill is available - */ - INITIALIZE, - - /** - * Trigger effects upon killing something - */ - KILL, - - /** - * Trigger effects upon hitting the ground - */ - LAND, - - /** - * Trigger effects when taking non-skill damage - */ - TOOK_PHYSICAL_DAMAGE, - - /** - * Trigger effects when taking skill damage - */ - TOOK_SKILL_DAMAGE, - - /** - * Trigger effects when the player quits or unlearns the skill - */ - CLEANUP; - - /** - * Retrieves a new component for the trigger - * - * @return the component for the trigger - */ - public TriggerComponent getComponent() - { - return new TriggerComponent(); - } - - /** - * Component for triggers that can contain needed data and child components - */ - public class TriggerComponent extends EffectComponent - { - @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - return executeChildren(caster, level, targets); - } - } -} diff --git a/src/com/sucy/skill/dynamic/TriggerHandler.java b/src/com/sucy/skill/dynamic/TriggerHandler.java new file mode 100644 index 00000000..75edc087 --- /dev/null +++ b/src/com/sucy/skill/dynamic/TriggerHandler.java @@ -0,0 +1,122 @@ +package com.sucy.skill.dynamic; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.enums.ManaCost; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.player.PlayerSkill; +import com.sucy.skill.dynamic.trigger.Trigger; +import com.sucy.skill.dynamic.trigger.TriggerComponent; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import java.util.HashMap; +import java.util.Objects; + +import static com.sucy.skill.dynamic.ComponentRegistry.getExecutor; + +/** + * SkillAPI © 2017 + * com.sucy.skill.dynamic.TriggerHandler + */ +public class TriggerHandler implements Listener { + + private final HashMap active = new HashMap<>(); + + private final DynamicSkill skill; + private final String key; + private final Trigger trigger; + private final TriggerComponent component; + + public TriggerHandler( + final DynamicSkill skill, + final String key, + final Trigger trigger, + final TriggerComponent component) { + + Objects.requireNonNull(skill, "Must provide a skill"); + Objects.requireNonNull(key, "Must provide a key"); + Objects.requireNonNull(trigger, "Must provide a trigger"); + Objects.requireNonNull(component, "Must provide a component"); + + this.skill = skill; + this.key = key; + this.trigger = trigger; + this.component = component; + } + + public String getKey() { + return key; + } + + public Trigger getTrigger() { + return trigger; + } + + public EffectComponent getComponent() { + return component; + } + + public void init(final LivingEntity entity, final int level) { + active.put(entity.getEntityId(), level); + } + + public void cleanup(final LivingEntity entity) { + active.remove(entity.getEntityId()); + component.cleanUp(entity); + } + + /** + * Registers needed events for the skill, ignoring any unused events for efficiency + * + * @param plugin plugin reference + */ + public void register(final SkillAPI plugin) { + plugin.getServer().getPluginManager().registerEvent( + trigger.getEvent(), this, EventPriority.HIGHEST, getExecutor(trigger), plugin, true); + } + + void apply(final T event, final Trigger trigger) { + final LivingEntity caster = trigger.getCaster(event); + if (caster == null || !active.containsKey(caster.getEntityId())) { return; } + + final int level = active.get(caster.getEntityId()); + if (!trigger.shouldTrigger(event, level, component.settings)) { return; } + + final LivingEntity target = trigger.getTarget(event, component.settings); + trigger.setValues(event, DynamicSkill.getCastData(caster)); + trigger(caster, target, level); + + if (event instanceof Cancellable) { skill.applyCancelled((Cancellable) event); } + trigger.postProcess(event, skill); + } + + boolean trigger(final LivingEntity user, final LivingEntity target, final int level) { + if (user == null || target == null || component.isRunning() || !SkillAPI.getSettings().isValidTarget(target)) { + return false; + } + + if (user instanceof Player) { + final PlayerData data = SkillAPI.getPlayerData((Player) user); + final PlayerSkill skill = data.getSkill(this.skill.getName()); + final boolean cd = component.getSettings().getBool("cooldown", false); + final boolean mana = component.getSettings().getBool("mana", false); + + if ((cd || mana) && !data.check(skill, cd, mana)) { return false; } + + if (component.trigger(user, target, level)) { + if (cd) { skill.startCooldown(); } + if (mana) { data.useMana(skill.getManaCost(), ManaCost.SKILL_CAST); } + + return true; + } else { + return false; + } + } else { + return component.trigger(user, target, level); + } + } +} diff --git a/src/com/sucy/skill/dynamic/condition/ArmorCondition.java b/src/com/sucy/skill/dynamic/condition/ArmorCondition.java index 7580dadc..a1169f97 100644 --- a/src/com/sucy/skill/dynamic/condition/ArmorCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ArmorCondition.java @@ -26,58 +26,57 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; +import com.google.common.collect.ImmutableList; +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.dynamic.DynamicSkill; import com.sucy.skill.dynamic.ItemChecker; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; -import java.util.ArrayList; import java.util.List; +import java.util.function.Function; -/** - * Checks the player's armor for matching items - */ -public class ArmorCondition extends EffectComponent -{ +public class ArmorCondition extends ConditionComponent { private static final String ARMOR = "armor"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + private List> getters; + @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - ArrayList list = new ArrayList(); - String armor = settings.getString(ARMOR).toLowerCase(); - boolean helmet = armor.equals("helmet"); - boolean chestplate = armor.equals("chestplate"); - boolean leggings = armor.equals("leggings"); - boolean boots = armor.equals("boots"); - if (!helmet && !chestplate && !leggings && !boots) - helmet = chestplate = leggings = boots = true; + public String getKey() { + return "armor"; + } - for (LivingEntity target : targets) - { - if (!(target instanceof Player)) - continue; + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + getters = determineGetters(); + } - PlayerInventory inv = ((Player) target).getInventory(); - if ((helmet && ItemChecker.check(inv.getHelmet(), level, settings)) - || (chestplate && ItemChecker.check(inv.getChestplate(), level, settings)) - || (leggings && ItemChecker.check(inv.getLeggings(), level, settings)) - || (boots && ItemChecker.check(inv.getBoots(), level, settings))) - { - list.add(target); - } + private List> determineGetters() { + final String type = settings.getString(ARMOR).toLowerCase(); + switch (type) { + case "helmet": + return ImmutableList.of(EntityEquipment::getHelmet); + case "chestplate": + return ImmutableList.of(EntityEquipment::getChestplate); + case "leggings": + return ImmutableList.of(EntityEquipment::getLeggings); + case "boots": + return ImmutableList.of(EntityEquipment::getBoots); + default: // All + return ImmutableList.of( + EntityEquipment::getHelmet, + EntityEquipment::getChestplate, + EntityEquipment::getLeggings, + EntityEquipment::getBoots); } + } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final EntityEquipment equipment = target.getEquipment(); + return equipment != null && getters.stream().anyMatch( + getter -> ItemChecker.check(getter.apply(equipment), level, settings)); } } diff --git a/src/com/sucy/skill/dynamic/condition/AttributeCondition.java b/src/com/sucy/skill/dynamic/condition/AttributeCondition.java index e07c7e3e..b76eeb77 100644 --- a/src/com/sucy/skill/dynamic/condition/AttributeCondition.java +++ b/src/com/sucy/skill/dynamic/condition/AttributeCondition.java @@ -28,53 +28,30 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.List; - -/** - * A condition for dynamic skills that requires the target to be a player who is given class - */ -public class AttributeCondition extends EffectComponent +public class AttributeCondition extends ConditionComponent { private static final String ATTR = "attribute"; private static final String MIN = "min"; private static final String MAX = "max"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - String attr = settings.getString(ATTR, null); - int min = (int) attr(caster, MIN, level, 0, true); - int max = (int) attr(caster, MAX, level, 999, true); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + if (!(target instanceof Player)) return false; - if (attr == null) return false; + final String attr = settings.getString(ATTR, null); + final int min = (int) parseValues(caster, MIN, level, 0); + final int max = (int) parseValues(caster, MAX, level, 999); - List list = new ArrayList(); - for (LivingEntity target : targets) - { - if (target instanceof Player) - { - Player player = (Player) target; - PlayerData data = SkillAPI.getPlayerData(player); + final PlayerData data = SkillAPI.getPlayerData((Player) target); + final int value = data.getAttribute(attr); + return value >= min && value <= max; + } - int num = data.getAttribute(attr); - if (num >= min && num <= max) list.add(player); - } - } - return list.size() > 0 - && executeChildren(caster, level, list); + @Override + public String getKey() { + return "attribute"; } } diff --git a/src/com/sucy/skill/dynamic/condition/BiomeCondition.java b/src/com/sucy/skill/dynamic/condition/BiomeCondition.java index 0aedc184..741c77a0 100644 --- a/src/com/sucy/skill/dynamic/condition/BiomeCondition.java +++ b/src/com/sucy/skill/dynamic/condition/BiomeCondition.java @@ -26,76 +26,37 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.dynamic.DynamicSkill; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; -/** - * A condition for dynamic skills that requires the target to be in a specified biome - */ -public class BiomeCondition extends EffectComponent +public class BiomeCondition extends ConditionComponent { private static final String TYPE = "type"; private static final String BIOME = "biome"; - private static final String[] BIOMES = { - "BEACH", - "DESERT", - "FOREST", - "FROZEN", - "HELL", - "HILLS", - "ICE", - "JUNGLE", - "MESA", - "MOUNTAINS", - "MUSHROOM", - "OCEAN", - "PLAINS", - "PLATEAU", - "RIVER", - "SAVANNA", - "SHORE", - "SKY", - "SWAMPLAND", - "TAIGA" - }; + private Set biomes; + private boolean requiresIn; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - int biomes = settings.getInt(BIOME, 0); - boolean inBiome = !settings.getString(TYPE, "in biome").toLowerCase().equals("not in biome"); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - String biome = target.getLocation().getBlock().getBiome().name(); - boolean any = false; - for (int i = 0; i < BIOMES.length; i++) - { - if ((biomes & (1 << i)) != 0 && biome.contains(BIOMES[i])) - { - any = true; - break; - } - } - if (any == inBiome) - { - list.add(target); - } - } - return list.size() > 0 && executeChildren(caster, level, list); + public String getKey() { + return "biome"; + } + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + requiresIn = !settings.getString(TYPE, "in biome").toLowerCase().equals("not in biome"); + biomes = settings.getStringList(BIOME).stream() + .map(s -> s.toUpperCase().replace(' ', '_')) + .collect(Collectors.toSet()); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return biomes.contains(target.getLocation().getBlock().getBiome().name()) == requiresIn; } } diff --git a/src/com/sucy/skill/dynamic/condition/BlockCondition.java b/src/com/sucy/skill/dynamic/condition/BlockCondition.java index 3f582029..46ab7446 100644 --- a/src/com/sucy/skill/dynamic/condition/BlockCondition.java +++ b/src/com/sucy/skill/dynamic/condition/BlockCondition.java @@ -26,45 +26,43 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; -/** - * A condition for dynamic skills that requires the target to have a specified held item - */ -public class BlockCondition extends EffectComponent -{ +public class BlockCondition extends ConditionComponent { private static final String MATERIAL = "material"; private static final String STANDING = "standing"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + private Set types; + private boolean negated; + private boolean in; + @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - ArrayList list = new ArrayList(); - String block = settings.getString(MATERIAL, "").toUpperCase().replace(" ", "_"); - boolean standing = !settings.getString(STANDING, "on block").equalsIgnoreCase("not on block"); + public String getKey() { + return "block"; + } - for (LivingEntity target : targets) - { - if (target.getLocation().getBlock().getRelative(BlockFace.DOWN).getType().name().equals(block) == standing) - { - list.add(target); - } - } + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + final String type = settings.getString(STANDING).toLowerCase(); + negated = type.startsWith("not"); + in = type.endsWith("in block"); + types = settings.getStringList(MATERIAL).stream() + .map(s -> s.toUpperCase().replace(' ', '_')) + .collect(Collectors.toSet()); + } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final Block in = target.getLocation().getBlock(); + final Block tested = this.in ? in : in.getRelative(BlockFace.DOWN); + return negated != types.contains(tested.getType().name()); } } diff --git a/src/com/sucy/skill/dynamic/condition/CastLevelCondition.java b/src/com/sucy/skill/dynamic/condition/CastLevelCondition.java new file mode 100644 index 00000000..1a68807c --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/CastLevelCondition.java @@ -0,0 +1,62 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.condition.CastLevelCondition + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.condition; + +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +public class CastLevelCondition extends ConditionComponent { + private static final String MIN_LEVEL = "min-level"; + private static final String MAX_LEVEL = "max-level"; + + private int min, max; + + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + min = settings.getInt(MIN_LEVEL, 1); + max = settings.getInt(MAX_LEVEL, 99); + } + + @Override + public String getKey() { + return "cast level"; + } + + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + return test(caster, level, null) && executeChildren(caster, level, targets); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return level >= min && level <= max; + } +} diff --git a/src/com/sucy/skill/dynamic/condition/CeilingCondition.java b/src/com/sucy/skill/dynamic/condition/CeilingCondition.java new file mode 100644 index 00000000..eb6a56f9 --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/CeilingCondition.java @@ -0,0 +1,31 @@ +package com.sucy.skill.dynamic.condition; + +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; + +public class CeilingCondition extends ConditionComponent +{ + private static final String DISTANCE = "distance"; + private static final String AT_LEAST = "at-least"; + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final boolean atLeast = settings.getBool(AT_LEAST, true); + final int distance = (int) parseValues(caster, DISTANCE, level, 5); + + final Block block = target.getLocation().getBlock(); + boolean ceiling = false; + for (int i = 2; i < distance; i++) { + if (block.getRelative(0, i, 0).getType().isSolid()) { + ceiling = true; + break; + } + } + return ceiling != atLeast; + } + + @Override + public String getKey() { + return "ceiling"; + } +} diff --git a/src/com/sucy/skill/dynamic/condition/ChanceCondition.java b/src/com/sucy/skill/dynamic/condition/ChanceCondition.java index dcbd981c..0d631e5e 100644 --- a/src/com/sucy/skill/dynamic/condition/ChanceCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ChanceCondition.java @@ -26,45 +26,23 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; import java.util.Random; -/** - * A condition for dynamic skills that requires a chance to succeed - */ -public class ChanceCondition extends EffectComponent +public class ChanceCondition extends ConditionComponent { private static final String CHANCE = "chance"; private static final Random random = new Random(); - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double chance = attr(caster, CHANCE, level, 25, isSelf) / 100.0; + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final double chance = parseValues(caster, CHANCE, level, 25) / 100.0; + return random.nextDouble() < chance; + } - // Check the biomes of the targets - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if (random.nextDouble() < chance) - { - list.add(target); - } - } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + public String getKey() { + return "chance"; } } diff --git a/src/com/sucy/skill/dynamic/condition/ClassCondition.java b/src/com/sucy/skill/dynamic/condition/ClassCondition.java index bbe04bb0..d0832305 100644 --- a/src/com/sucy/skill/dynamic/condition/ClassCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ClassCondition.java @@ -29,50 +29,26 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.classes.RPGClass; import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.List; - -/** - * A condition for dynamic skills that requires the target to be a player who is given class - */ -public class ClassCondition extends EffectComponent -{ +public class ClassCondition extends ConditionComponent { private static final String CLASS = "class"; private static final String EXACT = "exact"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - RPGClass c = SkillAPI.getClass(settings.getString(CLASS)); - boolean exact = settings.getBool(EXACT, false); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + if (!(target instanceof Player)) return false; - if (c == null) return false; + final RPGClass rpgClass = SkillAPI.getClass(settings.getString(CLASS)); + final boolean exact = settings.getBool(EXACT, false); - List list = new ArrayList(); - for (LivingEntity target : targets) - { - if (target instanceof Player) - { - Player player = (Player) target; - PlayerData data = SkillAPI.getPlayerData(player); - if (!exact && data.isClass(c)) list.add(player); - else if (exact && data.isExactClass(c)) list.add(player); - } - } - return list.size() > 0 - && executeChildren(caster, level, list); + final PlayerData data = SkillAPI.getPlayerData((Player) target); + return exact ? data.isExactClass(rpgClass) : data.isClass(rpgClass); + } + + @Override + public String getKey() { + return "class"; } } diff --git a/src/com/sucy/skill/dynamic/condition/ClassLevelCondition.java b/src/com/sucy/skill/dynamic/condition/ClassLevelCondition.java index 457c8af8..4e55a9e4 100644 --- a/src/com/sucy/skill/dynamic/condition/ClassLevelCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ClassLevelCondition.java @@ -26,47 +26,37 @@ */ package com.sucy.skill.dynamic.condition; +import com.rit.sucy.config.parse.DataSection; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerClass; -import com.sucy.skill.api.player.PlayerSkill; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.dynamic.DynamicSkill; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import java.util.List; - -/** - * A condition for dynamic skills that requires the caster's class level to be within a range - */ -public class ClassLevelCondition extends EffectComponent +public class ClassLevelCondition extends ConditionComponent { private static final String MIN_LEVEL = "min-level"; private static final String MAX_LEVEL = "max-level"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + private int min, max; + @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (!(caster instanceof Player)) - return false; + public String getKey() { + return "class level"; + } - int min = settings.getInt(MIN_LEVEL); - int max = settings.getInt(MAX_LEVEL); + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + min = settings.getInt(MIN_LEVEL); + max = settings.getInt(MAX_LEVEL); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + if (!(target instanceof Player)) return false; - PlayerSkill skill = getSkillData(caster); - PlayerClass data = skill == null - ? SkillAPI.getPlayerData((Player) caster).getMainClass() - : skill.getPlayerClass(); - return data.getLevel() >= min - && data.getLevel() <= max - && executeChildren(caster, level, targets); + final PlayerClass playerClass = SkillAPI.getPlayerData((Player) target).getMainClass(); + return playerClass != null && playerClass.getLevel() >= min && playerClass.getLevel() <= max; } } diff --git a/src/com/sucy/skill/dynamic/condition/CombatCondition.java b/src/com/sucy/skill/dynamic/condition/CombatCondition.java index 2a4c195e..6fbb2395 100644 --- a/src/com/sucy/skill/dynamic/condition/CombatCondition.java +++ b/src/com/sucy/skill/dynamic/condition/CombatCondition.java @@ -26,51 +26,38 @@ */ package com.sucy.skill.dynamic.condition; +import com.rit.sucy.config.parse.DataSection; import com.sucy.skill.api.util.Combat; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.dynamic.DynamicSkill; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.List; - /** * A condition for dynamic skills that requires the target to be a player * who's combat status matches the settings */ -public class CombatCondition extends EffectComponent +public class CombatCondition extends ConditionComponent { private static final String COMBAT = "combat"; private static final String SECONDS = "seconds"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + private boolean combat; + private double seconds; + @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean combat = !settings.getString(COMBAT, "true").toLowerCase().equals("false"); - double seconds = settings.getDouble(SECONDS, 10); + public String getKey() { + return "combat"; + } - List list = new ArrayList(); - for (LivingEntity target : targets) - { - if (target instanceof Player) - { - Player player = (Player) target; - if (Combat.isInCombat(player, seconds) == combat) - { - list.add(player); - } - } - } - return list.size() > 0 - && executeChildren(caster, level, list); + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + combat = !settings.getString(COMBAT, "true").toLowerCase().equals("false"); + seconds = settings.getDouble(SECONDS, 10); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return target instanceof Player && Combat.isInCombat((Player) target, seconds) == combat; } } diff --git a/src/com/sucy/skill/dynamic/condition/ConditionComponent.java b/src/com/sucy/skill/dynamic/condition/ConditionComponent.java new file mode 100644 index 00000000..ecd440d9 --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/ConditionComponent.java @@ -0,0 +1,35 @@ +package com.sucy.skill.dynamic.condition; + +import com.sucy.skill.dynamic.ComponentType; +import com.sucy.skill.dynamic.EffectComponent; +import org.bukkit.entity.LivingEntity; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.condition.ConditionComponent + */ +public abstract class ConditionComponent extends EffectComponent { + + /** {@inheritDoc} */ + @Override + public ComponentType getType() { + return ComponentType.CONDITION; + } + + /** {@inheritDoc} */ + @Override + public boolean execute( + final LivingEntity caster, final int level, final List targets) { + + final List filtered = targets.stream() + .filter(t -> test(caster, level, t)) + .collect(Collectors.toList()); + + return filtered.size() > 0 && executeChildren(caster, level, filtered); + } + + abstract boolean test(final LivingEntity caster, final int level, final LivingEntity target); +} diff --git a/src/com/sucy/skill/dynamic/condition/CrouchCondition.java b/src/com/sucy/skill/dynamic/condition/CrouchCondition.java index 1a92eb9d..ec4436bd 100644 --- a/src/com/sucy/skill/dynamic/condition/CrouchCondition.java +++ b/src/com/sucy/skill/dynamic/condition/CrouchCondition.java @@ -26,37 +26,29 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.dynamic.DynamicSkill; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.List; - -public class CrouchCondition extends EffectComponent -{ +public class CrouchCondition extends ConditionComponent { private static final String CROUCH = "crouch"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + private boolean crouch; + @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean crouch = !settings.getString(CROUCH, "true").toLowerCase().equals("false"); + public String getKey() { + return "crouch"; + } - List list = new ArrayList(); - for (LivingEntity target : targets) - { - if (target instanceof Player && ((Player) target).isSneaking() == crouch) - list.add(target); - } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + crouch = !settings.getString(CROUCH, "true").toLowerCase().equals("false"); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return target instanceof Player && ((Player) target).isSneaking() == crouch; } } diff --git a/src/com/sucy/skill/dynamic/condition/DirectionCondition.java b/src/com/sucy/skill/dynamic/condition/DirectionCondition.java index 59f01733..53d71bf1 100644 --- a/src/com/sucy/skill/dynamic/condition/DirectionCondition.java +++ b/src/com/sucy/skill/dynamic/condition/DirectionCondition.java @@ -26,55 +26,40 @@ */ package com.sucy.skill.dynamic.condition; +import com.rit.sucy.config.parse.DataSection; import com.rit.sucy.player.TargetHelper; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.dynamic.DynamicSkill; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; +import java.util.function.BiPredicate; /** * A condition for dynamic skills that requires the target or caster to be facing a direction relative to the other */ -public class DirectionCondition extends EffectComponent +public class DirectionCondition extends ConditionComponent { private static final String TYPE = "type"; private static final String DIRECTION = "direction"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + private BiPredicate test; + private boolean towards; + + @Override + public String getKey() { + return "direction"; + } + @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - String type = settings.getString(TYPE).toLowerCase(); - String dir = settings.getString(DIRECTION).toLowerCase(); - boolean towards = dir.equals("towards"); + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + towards = settings.getString(DIRECTION).equalsIgnoreCase("towards"); + test = settings.getString(TYPE).equalsIgnoreCase("target") + ? (caster, target) -> TargetHelper.isInFront(target, caster) + : TargetHelper::isInFront; + } - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if (type.equals("target")) - { - if (TargetHelper.isInFront(target, caster) == towards) - { - list.add(target); - } - } - else // type.equals("normal") - { - if (TargetHelper.isInFront(caster, target) == towards) - { - list.add(target); - } - } - } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return test.test(caster, target) == towards; } } diff --git a/src/com/sucy/skill/dynamic/condition/ElevationCondition.java b/src/com/sucy/skill/dynamic/condition/ElevationCondition.java index 0750126b..2df55a50 100644 --- a/src/com/sucy/skill/dynamic/condition/ElevationCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ElevationCondition.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.ArrayList; @@ -35,12 +34,16 @@ /** * A condition for dynamic skills that requires the target to fit the elevation requirement */ -public class ElevationCondition extends EffectComponent -{ +public class ElevationCondition extends ConditionComponent { private static final String TYPE = "type"; private static final String MIN = "min-value"; private static final String MAX = "max-value"; + @Override + public String getKey() { + return "elevation"; + } + /** * Executes the component * @@ -51,30 +54,38 @@ public class ElevationCondition extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; + public boolean execute(LivingEntity caster, int level, List targets) { String type = settings.getString(TYPE).toLowerCase(); - double min = attr(caster, MIN, level, 0, isSelf); - double max = attr(caster, MAX, level, 255, isSelf); + double min = parseValues(caster, MIN, level, 0); + double max = parseValues(caster, MAX, level, 255); ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { double value; - if (type.equals("difference")) - { + if (type.equals("difference")) { value = target.getLocation().getY() - caster.getLocation().getY(); - } - else - { + } else { value = target.getLocation().getY(); } - if (value >= min && value <= max) - { + if (value >= min && value <= max) { list.add(target); } } return list.size() > 0 && executeChildren(caster, level, list); } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final String type = settings.getString(TYPE); + final double min = parseValues(caster, MIN, level, 0); + final double max = parseValues(caster, MAX, level, 255); + + double value; + if (type.equalsIgnoreCase("difference")) { + value = target.getLocation().getY() - caster.getLocation().getY(); + } else { + value = target.getLocation().getY(); + } + return value >= min && value <= max; + } } diff --git a/src/com/sucy/skill/dynamic/condition/ElseCondition.java b/src/com/sucy/skill/dynamic/condition/ElseCondition.java new file mode 100644 index 00000000..07d3e849 --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/ElseCondition.java @@ -0,0 +1,52 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.condition.ElseCondition + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.condition; + +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +/** + * A simple "else" condition, applying if the previous component failed + */ +public class ElseCondition extends ConditionComponent { + + @Override + public String getKey() { + return "else"; + } + + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + return test(caster, level, null) && executeChildren(caster, level, targets); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return !lastPassed(); + } +} diff --git a/src/com/sucy/skill/dynamic/condition/EntityTypeCondition.java b/src/com/sucy/skill/dynamic/condition/EntityTypeCondition.java new file mode 100644 index 00000000..843e3501 --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/EntityTypeCondition.java @@ -0,0 +1,59 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.condition.EntityTypeCondition + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.condition; + +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.entity.LivingEntity; + +import java.util.Set; +import java.util.stream.Collectors; + +public class EntityTypeCondition extends ConditionComponent { + + private static final String TYPE = "types"; + + private Set types; + + @Override + public String getKey() { + return "entity type"; + } + + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + types = settings.getStringList(TYPE).stream() + .map(s -> s.toUpperCase().replace(' ', '_')) + .collect(Collectors.toSet()); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return types.contains(target.getType().name()); + } +} diff --git a/src/com/sucy/skill/dynamic/condition/FireCondition.java b/src/com/sucy/skill/dynamic/condition/FireCondition.java index d275a3f2..7d31eb56 100644 --- a/src/com/sucy/skill/dynamic/condition/FireCondition.java +++ b/src/com/sucy/skill/dynamic/condition/FireCondition.java @@ -26,40 +26,22 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; - /** * A condition for dynamic skills that requires the target to be on fire */ -public class FireCondition extends EffectComponent -{ +public class FireCondition extends ConditionComponent { private static final String TYPE = "type"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean onFire = !settings.getString(TYPE, "on fire").toLowerCase().equals("not on fire"); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if ((target.getFireTicks() > 0) == onFire) - { - list.add(target); - } - } - return list.size() > 0 && executeChildren(caster, level, list); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final boolean onFire = !settings.getString(TYPE, "on fire").toLowerCase().equals("not on fire"); + return (target.getFireTicks() > 0) == onFire; + } + + @Override + public String getKey() { + return "fire"; } } diff --git a/src/com/sucy/skill/dynamic/condition/FlagCondition.java b/src/com/sucy/skill/dynamic/condition/FlagCondition.java index 17bee281..c5d03241 100644 --- a/src/com/sucy/skill/dynamic/condition/FlagCondition.java +++ b/src/com/sucy/skill/dynamic/condition/FlagCondition.java @@ -27,43 +27,24 @@ package com.sucy.skill.dynamic.condition; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; - /** * A condition for dynamic skills that requires the target to have a specified flag active */ -public class FlagCondition extends EffectComponent -{ +public class FlagCondition extends ConditionComponent { private static final String TYPE = "type"; private static final String KEY = "key"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - String flag = settings.getString(KEY); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final String flag = settings.getString(KEY); + final boolean set = !settings.getString(TYPE, "set").toLowerCase().equals("not set"); + return FlagManager.hasFlag(target, flag) == set; + } - boolean set = !settings.getString(TYPE, "set").toLowerCase().equals("not set"); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if (FlagManager.hasFlag(target, flag) == set) - { - list.add(target); - } - } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + public String getKey() { + return "flag"; } } diff --git a/src/com/sucy/skill/dynamic/condition/GroundCondition.java b/src/com/sucy/skill/dynamic/condition/GroundCondition.java new file mode 100644 index 00000000..adf9f222 --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/GroundCondition.java @@ -0,0 +1,23 @@ +package com.sucy.skill.dynamic.condition; + +import org.bukkit.entity.LivingEntity; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.condition.Ground + */ +public class GroundCondition extends ConditionComponent { + private static final String type = "type"; + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final boolean onGround = target.isOnGround(); + final boolean wantOnGround = settings.getString(type, "on ground").equalsIgnoreCase("on ground"); + return onGround == wantOnGround; + } + + @Override + public String getKey() { + return "ground"; + } +} diff --git a/src/com/sucy/skill/dynamic/condition/HealthCondition.java b/src/com/sucy/skill/dynamic/condition/HealthCondition.java index bdb636ec..ddf431c9 100644 --- a/src/com/sucy/skill/dynamic/condition/HealthCondition.java +++ b/src/com/sucy/skill/dynamic/condition/HealthCondition.java @@ -26,63 +26,41 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; - /** * A condition for dynamic skills that requires the target's health to fit the requirement */ -public class HealthCondition extends EffectComponent -{ +public class HealthCondition extends ConditionComponent { private static final String TYPE = "type"; private static final String MIN = "min-value"; private static final String MAX = "max-value"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - String type = settings.getString(TYPE).toLowerCase(); - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double min = attr(caster, MIN, level, 0, isSelf); - double max = attr(caster, MAX, level, 999, isSelf); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final String type = settings.getString(TYPE).toLowerCase(); + final double min = parseValues(caster, MIN, level, 0); + final double max = parseValues(caster, MAX, level, 999); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - double value; - if (type.equals("difference percent")) - { + double value; + switch (type) { + case "difference percent": value = (target.getHealth() - caster.getHealth()) * 100 / caster.getHealth(); - } - else if (type.equals("difference")) - { + break; + case "difference": value = target.getHealth() - caster.getHealth(); - } - else if (type.equals("percent")) - { + break; + case "percent": value = target.getHealth() * 100 / target.getMaxHealth(); - } - else // type.equals("health") - { + break; + default: value = target.getHealth(); - } - if (value >= min && value <= max) - { - list.add(target); - } } - return list.size() > 0 && executeChildren(caster, level, list); + return value >= min && value <= max; + } + + @Override + public String getKey() { + return "health"; } } diff --git a/src/com/sucy/skill/dynamic/condition/InventoryCondition.java b/src/com/sucy/skill/dynamic/condition/InventoryCondition.java index 2bcbd7c4..ca32b2d9 100644 --- a/src/com/sucy/skill/dynamic/condition/InventoryCondition.java +++ b/src/com/sucy/skill/dynamic/condition/InventoryCondition.java @@ -26,43 +26,21 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.dynamic.ItemChecker; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.List; - /** * A condition for dynamic skills that requires the target to have a specified item */ -public class InventoryCondition extends EffectComponent -{ - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ +public class InventoryCondition extends ConditionComponent { @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - ArrayList list = new ArrayList(); - - for (LivingEntity target : targets) - { - if (!(target instanceof Player)) continue; - - if (ItemChecker.check((Player) target, level, settings, false)) - { - list.add(target); - } - } + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return target instanceof Player && ItemChecker.check((Player) target, level, this, false); + } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + public String getKey() { + return "inventory"; } } diff --git a/src/com/sucy/skill/dynamic/condition/ItemCondition.java b/src/com/sucy/skill/dynamic/condition/ItemCondition.java index 4ef7f7b6..47877141 100644 --- a/src/com/sucy/skill/dynamic/condition/ItemCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ItemCondition.java @@ -26,40 +26,23 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.dynamic.ItemChecker; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; - /** * A condition for dynamic skills that requires the target to have a specified held item */ -public class ItemCondition extends EffectComponent -{ - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ +public class ItemCondition extends ConditionComponent { @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - ArrayList list = new ArrayList(); - - for (LivingEntity target : targets) - { - if (target.getEquipment() != null && ItemChecker.check(target.getEquipment().getItemInHand(), level, settings)) - { - list.add(target); - } - } + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return target.getEquipment() != null && ItemChecker.check( + target.getEquipment().getItemInHand(), + level, + settings); + } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + public String getKey() { + return "item"; } } diff --git a/src/com/sucy/skill/dynamic/condition/LightCondition.java b/src/com/sucy/skill/dynamic/condition/LightCondition.java index fb97e545..db9c455e 100644 --- a/src/com/sucy/skill/dynamic/condition/LightCondition.java +++ b/src/com/sucy/skill/dynamic/condition/LightCondition.java @@ -26,45 +26,25 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; - /** * A condition for dynamic skills that requires the lighting at the target's location to be within a range */ -public class LightCondition extends EffectComponent -{ +public class LightCondition extends ConditionComponent { private static final String MIN = "min-light"; private static final String MAX = "max-light"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double min = attr(caster, MIN, level, 0, isSelf); - double max = attr(caster, MAX, level, 0, isSelf); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final double min = parseValues(caster, MIN, level, 0); + final double max = parseValues(caster, MAX, level, 0); + final double light = target.getLocation().getBlock().getLightLevel(); + return light >= min && light <= max; + } - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if (target.getLocation().getBlock().getLightLevel() >= min - && target.getLocation().getBlock().getLightLevel() <= max) - { - list.add(target); - } - } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + public String getKey() { + return "light"; } } diff --git a/src/com/sucy/skill/dynamic/condition/LoreCondition.java b/src/com/sucy/skill/dynamic/condition/LoreCondition.java index 641b0d33..440fcf99 100644 --- a/src/com/sucy/skill/dynamic/condition/LoreCondition.java +++ b/src/com/sucy/skill/dynamic/condition/LoreCondition.java @@ -26,57 +26,45 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.dynamic.DynamicSkill; import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.EntityEquipment; -import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; import java.util.regex.Pattern; -/** - * A condition for dynamic skills that requires the target to have a specified potion effect - */ -public class LoreCondition extends EffectComponent -{ +public class LoreCondition extends ConditionComponent { private static final String REGEX = "regex"; private static final String STRING = "str"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + private Predicate test; + + @Override + public String getKey() { + return "lore"; + } + @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean regex = settings.getString(REGEX, "false").toLowerCase().equals("true"); - String str = settings.getString(STRING, ""); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if (target.getEquipment() == null || target.getEquipment().getItemInHand() == null - || !target.getEquipment().getItemInHand().hasItemMeta() - || !target.getEquipment().getItemInHand().getItemMeta().hasLore()) - { - continue; - } - List lore = target.getEquipment().getItemInHand().getItemMeta().getLore(); - for (String line : lore) - { - if (regex && Pattern.compile(str).matcher(line).find()) - { - list.add(target); - } - else if (!regex && line.contains(str)) - { - list.add(target); - } - } + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + final boolean regex = settings.getString(REGEX, "false").toLowerCase().equals("true"); + final String str = settings.getString(STRING, ""); + if (regex) { + final Pattern pattern = Pattern.compile(str); + test = line -> pattern.matcher(line).find(); + } else { + test = line -> line.contains(str); } - return list.size() > 0 && executeChildren(caster, level, list); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final EntityEquipment items = target.getEquipment(); + if (items == null || items.getItemInHand() == null || !items.getItemInHand().hasItemMeta()) { return false; } + + final List lore = items.getItemInHand().getItemMeta().getLore(); + return lore != null && lore.stream().anyMatch(test); } } diff --git a/src/com/sucy/skill/dynamic/condition/ManaCondition.java b/src/com/sucy/skill/dynamic/condition/ManaCondition.java index 7f96e3a3..68090f22 100644 --- a/src/com/sucy/skill/dynamic/condition/ManaCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ManaCondition.java @@ -29,71 +29,47 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.api.player.PlayerSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.List; - -/** - * A condition for dynamic skills that requires the target's mana to fit the requirement - */ -public class ManaCondition extends EffectComponent -{ +public class ManaCondition extends ConditionComponent { private static final String TYPE = "type"; private static final String MIN = "min-value"; private static final String MAX = "max-value"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - String type = settings.getString(TYPE).toLowerCase(); - double min = attr(caster, MIN, level, 0, isSelf); - double max = attr(caster, MAX, level, 99, isSelf); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + if (!(target instanceof Player)) { + return false; + } - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if (!(target instanceof Player)) - { - continue; - } - double value; - PlayerData data = SkillAPI.getPlayerData((Player) target); - PlayerSkill skill = getSkillData(caster); - double mana = data.getMana(); - if (type.equals("difference percent")) - { + final String type = settings.getString(TYPE).toLowerCase(); + final double min = parseValues(caster, MIN, level, 0); + final double max = parseValues(caster, MAX, level, 99); + final PlayerData data = SkillAPI.getPlayerData((Player) target); + final PlayerSkill skill = getSkillData(caster); + final double mana = data.getMana(); + + double value; + switch (type) { + case "difference percent": value = (mana - skill.getPlayerData().getMana()) * 100 / skill.getPlayerData().getMana(); - } - else if (type.equals("difference")) - { + break; + case "difference": value = mana - skill.getPlayerData().getMana(); - } - else if (type.equals("percent")) - { + break; + case "percent": value = mana * 100 / data.getMaxMana(); - } - else // type.equals("health") - { + break; + default: value = mana; - } - if (value >= min && value <= max) - { - list.add(target); - } + break; } - return list.size() > 0 && executeChildren(caster, level, list); + return value >= min && value <= max; + } + + @Override + public String getKey() { + return "mana"; } } diff --git a/src/com/sucy/skill/dynamic/condition/NameCondition.java b/src/com/sucy/skill/dynamic/condition/NameCondition.java index 4db31ce1..6652f026 100644 --- a/src/com/sucy/skill/dynamic/condition/NameCondition.java +++ b/src/com/sucy/skill/dynamic/condition/NameCondition.java @@ -26,54 +26,32 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; import java.util.regex.Pattern; /** * A condition for dynamic skills that requires the target to have a specified potion effect */ -public class NameCondition extends EffectComponent -{ +public class NameCondition extends ConditionComponent { private static final String CONTAINS = "contains"; private static final String REGEX = "regex"; private static final String STRING = "str"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { boolean contains = !settings.getString(CONTAINS, "true").toLowerCase().equals("false"); boolean regex = settings.getString(REGEX, "false").toLowerCase().equals("true"); String str = settings.getString(STRING, ""); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - String name = target.getCustomName(); - if (name == null) - { - continue; - } - if (regex && (Pattern.compile(str).matcher(name).find() == contains)) - { - list.add(target); - } - else if (!regex && (name.contains(str) == contains)) - { - list.add(target); - } - } - return list.size() > 0 && executeChildren(caster, level, list); + + String name = target.getCustomName(); + return name != null && (regex + ? Pattern.compile(str).matcher(name).find() == contains + : name.contains(str) == contains); + } + + @Override + public String getKey() { + return "name"; } } diff --git a/src/com/sucy/skill/dynamic/condition/OffhandCondition.java b/src/com/sucy/skill/dynamic/condition/OffhandCondition.java index 21e7b589..9d059410 100644 --- a/src/com/sucy/skill/dynamic/condition/OffhandCondition.java +++ b/src/com/sucy/skill/dynamic/condition/OffhandCondition.java @@ -26,40 +26,22 @@ */ package com.sucy.skill.dynamic.condition; -import com.rit.sucy.version.VersionManager; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.dynamic.ItemChecker; import org.bukkit.entity.LivingEntity; - -import java.util.ArrayList; -import java.util.List; +import org.bukkit.inventory.EntityEquipment; /** * Item condition for a player's off hand */ -public class OffhandCondition extends EffectComponent -{ - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ +public class OffhandCondition extends ConditionComponent { @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (!VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) - return false; - - ArrayList list = new ArrayList(); - - for (LivingEntity target : targets) - if (target.getEquipment() != null && ItemChecker.check(target.getEquipment().getItemInOffHand(), level, settings)) - list.add(target); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final EntityEquipment equipment = target.getEquipment(); + return equipment != null && ItemChecker.check(target.getEquipment().getItemInOffHand(), level, settings); + } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + public String getKey() { + return "offhand"; } } diff --git a/src/com/sucy/skill/dynamic/condition/PermissionCondition.java b/src/com/sucy/skill/dynamic/condition/PermissionCondition.java new file mode 100644 index 00000000..e82a4e69 --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/PermissionCondition.java @@ -0,0 +1,50 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.condition.PermissionCondition + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.condition; + +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +public class PermissionCondition extends ConditionComponent { + private static final String PERM = "perm"; + + @Override + public String getKey() { + return "permission"; + } + + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + return test(caster, level, null) && executeChildren(caster, level, targets); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + return caster.hasPermission(settings.getString(PERM)); + } +} diff --git a/src/com/sucy/skill/dynamic/condition/PotionCondition.java b/src/com/sucy/skill/dynamic/condition/PotionCondition.java index 83775eea..51934524 100644 --- a/src/com/sucy/skill/dynamic/condition/PotionCondition.java +++ b/src/com/sucy/skill/dynamic/condition/PotionCondition.java @@ -1,21 +1,21 @@ /** * SkillAPI * com.sucy.skill.dynamic.condition.PotionCondition - * + *

* The MIT License (MIT) - * + *

* Copyright (c) 2014 Steven Sucy - * + *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software") to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + *

* The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + *

* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,67 +26,64 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; +import com.rit.sucy.version.VersionManager; import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; /** * A condition for dynamic skills that requires the target to have a specified potion effect */ -public class PotionCondition extends EffectComponent -{ - private static final String TYPE = "type"; +public class PotionCondition extends ConditionComponent { + private static final String TYPE = "type"; private static final String POTION = "potion"; + private static final String MIN_RANK = "min-rank"; + private static final String MAX_RANK = "max-rank"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean active = !settings.getString(TYPE, "active").toLowerCase().equals("not active"); - String potion = settings.getString(POTION, "").toUpperCase().replace(' ', '_'); - PotionEffectType type; - ArrayList list = new ArrayList(); - try - { - type = PotionEffectType.getByName(potion); - for (LivingEntity target : targets) - { - if (target.hasPotionEffect(type) == active) - { - list.add(target); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final boolean active = !settings.getString(TYPE, "active").toLowerCase().equals("not active"); + final Collection effects = target.getActivePotionEffects(); + if (effects.isEmpty()) return !active; + + final String potion = settings.getString(POTION, "").toUpperCase().replace(' ', '_'); + final int minRank = (int) parseValues(caster, MIN_RANK, level, 0); + final int maxRank = (int) parseValues(caster, MAX_RANK, level, 999); + try { + final PotionEffectType type = PotionEffectType.getByName(potion); + return has(target, type, minRank, maxRank) == active; + } catch (Exception ex) { + for (final PotionEffect check : effects) { + if (check.getAmplifier() >= minRank && check.getAmplifier() <= maxRank) { + return true; } } } - catch (Exception ex) - { - for (LivingEntity target : targets) - { - boolean has = false; - for (PotionEffectType check : PotionEffectType.values()) - { - if (check != null && target.hasPotionEffect(check)) - { - has = true; - break; - } - } - if (has == active) - { - list.add(target); - } + return false; + } + + private boolean has(LivingEntity target, PotionEffectType type, int min, int max) { + int rank; + if (VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { + rank = target.getPotionEffect(type).getAmplifier(); + if (!target.hasPotionEffect(type)) { + return false; } + } else { + rank = target.getActivePotionEffects().stream() + .filter(effect -> effect.getType() == type) + .findFirst() + .map(PotionEffect::getAmplifier) + .orElse(-1); + if (rank == -1) return false; } - return list.size() > 0 && executeChildren(caster, level, list); + return rank >= min && rank <= max; + } + + @Override + public String getKey() { + return "potion"; } } diff --git a/src/com/sucy/skill/dynamic/condition/SkillLevelCondition.java b/src/com/sucy/skill/dynamic/condition/SkillLevelCondition.java index 2caeb97a..d4a607ad 100644 --- a/src/com/sucy/skill/dynamic/condition/SkillLevelCondition.java +++ b/src/com/sucy/skill/dynamic/condition/SkillLevelCondition.java @@ -27,41 +27,36 @@ package com.sucy.skill.dynamic.condition; import com.sucy.skill.api.player.PlayerSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; -/** - * A condition for dynamic skills that requires the caster's skill level to be within a range - */ -public class SkillLevelCondition extends EffectComponent -{ +public class SkillLevelCondition extends ConditionComponent { private static final String SKILL = "skill"; private static final String MIN_LEVEL = "min-level"; private static final String MAX_LEVEL = "max-level"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - int min = settings.getInt(MIN_LEVEL, 1); - int max = settings.getInt(MAX_LEVEL, 99); + public String getKey() { + return "skill level"; + } + + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + return test(caster, level, null) && executeChildren(caster, level, targets); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final int min = settings.getInt(MIN_LEVEL, 1); + final int max = settings.getInt(MAX_LEVEL, 99); - String skill = settings.getString(SKILL, ""); - PlayerSkill data = getSkillData(caster).getPlayerData().getSkill(skill); - if (data == null) data = getSkillData(caster); + final String skill = settings.getString(SKILL, ""); + final PlayerSkill triggeredSkill = getSkillData(caster); + if (triggeredSkill == null) { return false; } + PlayerSkill data = triggeredSkill.getPlayerData().getSkill(skill); + if (data == null) { data = triggeredSkill; } - return data.getLevel() >= min - && data.getLevel() <= max - && executeChildren(caster, level, targets); + return data.getLevel() >= min && data.getLevel() <= max; } } diff --git a/src/com/sucy/skill/dynamic/condition/SlotCondition.java b/src/com/sucy/skill/dynamic/condition/SlotCondition.java new file mode 100644 index 00000000..38309d82 --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/SlotCondition.java @@ -0,0 +1,51 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.condition.SlotCondition + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.condition; + +import com.sucy.skill.dynamic.ItemChecker; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.PlayerInventory; + +public class SlotCondition extends ConditionComponent +{ + private static final String SLOT = "slot"; + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + if (!(target instanceof Player)) return false; + + final PlayerInventory inventory = ((Player) target).getInventory(); + return settings.getStringList(SLOT).stream().anyMatch( + slot -> ItemChecker.check(inventory.getItem(Integer.parseInt(slot)), level, settings)); + } + + @Override + public String getKey() { + return "slot"; + } +} diff --git a/src/com/sucy/skill/dynamic/condition/StatusCondition.java b/src/com/sucy/skill/dynamic/condition/StatusCondition.java index 4c3626a2..9da98331 100644 --- a/src/com/sucy/skill/dynamic/condition/StatusCondition.java +++ b/src/com/sucy/skill/dynamic/condition/StatusCondition.java @@ -28,58 +28,31 @@ import com.sucy.skill.api.util.FlagManager; import com.sucy.skill.api.util.StatusFlag; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * A condition for dynamic skills that requires the target to have a status condition */ -public class StatusCondition extends EffectComponent -{ +public class StatusCondition extends ConditionComponent { private static final String TYPE = "type"; private static final String STATUS = "status"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean active = !settings.getString(TYPE, "active").equals("not active"); - String status = settings.getString(STATUS).toLowerCase(); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if (status.equals("any")) - { - boolean has = false; - for (String flag : StatusFlag.ALL) - { - if (FlagManager.hasFlag(target, flag)) - { - has = true; - break; - } - } - if (has == active) - { - list.add(target); - } - } - else if (FlagManager.hasFlag(target, status) == active) - { - list.add(target); - } + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final boolean active = !settings.getString(TYPE, "active").equals("not active"); + final String status = settings.getString(STATUS).toLowerCase(); + + if (status.equals("any")) { + return active == Arrays.stream(StatusFlag.ALL).anyMatch(flag -> FlagManager.hasFlag(target, flag)); + } else { + return FlagManager.hasFlag(target, status) == active; } - return list.size() > 0 && executeChildren(caster, level, list); + } + + @Override + public String getKey() { + return "status"; } } diff --git a/src/com/sucy/skill/dynamic/condition/TimeCondition.java b/src/com/sucy/skill/dynamic/condition/TimeCondition.java index 5c94b2b2..e1f0f2d1 100644 --- a/src/com/sucy/skill/dynamic/condition/TimeCondition.java +++ b/src/com/sucy/skill/dynamic/condition/TimeCondition.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -34,24 +33,22 @@ /** * A condition for dynamic skills that requires the game time to match the settings */ -public class TimeCondition extends EffectComponent -{ +public class TimeCondition extends ConditionComponent { private static final String TIME = "time"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean night = settings.getString(TIME).toLowerCase().equals("night"); - return night == (caster.getWorld().getTime() >= 12300 && caster.getWorld().getTime() <= 23850) - && executeChildren(caster, level, targets); + public String getKey() { + return "time"; + } + + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + return test(caster, level, null) && executeChildren(caster, level, targets); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final boolean night = settings.getString(TIME).toLowerCase().equals("night"); + return night == (caster.getWorld().getTime() >= 12300 && caster.getWorld().getTime() <= 23850); } } diff --git a/src/com/sucy/skill/dynamic/condition/ToolCondition.java b/src/com/sucy/skill/dynamic/condition/ToolCondition.java index fea00996..e45c044e 100644 --- a/src/com/sucy/skill/dynamic/condition/ToolCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ToolCondition.java @@ -26,47 +26,30 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; - -import java.util.ArrayList; -import java.util.List; +import org.bukkit.inventory.EntityEquipment; /** * A condition for dynamic skills that requires the target to have a specified potion effect */ -public class ToolCondition extends EffectComponent -{ +public class ToolCondition extends ConditionComponent { private static final String MATERIAL = "material"; private static final String TOOL = "tool"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - String material = settings.getString(MATERIAL, "").toUpperCase(); - String tool = "_" + settings.getString(TOOL, "").toUpperCase().replace("SHOVEL", "SPADE"); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - if (target.getEquipment() == null || target.getEquipment().getItemInHand() == null) - { - continue; - } - String hand = target.getEquipment().getItemInHand().getType().name(); - if ((material.equals("ANY") || hand.contains(material)) && (tool.equals("_ANY") || hand.contains(tool))) - { - list.add(target); - } - } - return list.size() > 0 && executeChildren(caster, level, list); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final String material = settings.getString(MATERIAL, "").toUpperCase(); + final String tool = "_" + settings.getString(TOOL, "").toUpperCase().replace("SHOVEL", "SPADE"); + + final EntityEquipment equipment = target.getEquipment(); + if (equipment == null || equipment.getItemInHand() == null) return false; + + final String hand = equipment.getItemInHand().getType().name(); + return (material.equals("ANY") || hand.contains(material)) && (tool.equals("_ANY") || hand.contains(tool)); + } + + @Override + public String getKey() { + return "tool"; } } diff --git a/src/com/sucy/skill/dynamic/condition/ValueCondition.java b/src/com/sucy/skill/dynamic/condition/ValueCondition.java index 71a2e472..1306da17 100644 --- a/src/com/sucy/skill/dynamic/condition/ValueCondition.java +++ b/src/com/sucy/skill/dynamic/condition/ValueCondition.java @@ -27,41 +27,35 @@ package com.sucy.skill.dynamic.condition; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; -/** - * A condition for dynamic skills that requires the target to have a specified flag active - */ -public class ValueCondition extends EffectComponent -{ +public class ValueCondition extends ConditionComponent { private static final String KEY = "key"; private static final String MIN = "min-value"; private static final String MAX = "max-value"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - String key = settings.getString(KEY); - double min = attr(caster, MIN, level, 1, true); - double max = attr(caster, MAX, level, 999, true); - Object data = DynamicSkill.getCastData(caster).get(key); + public String getKey() { + return "value"; + } + + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + return test(caster, level, null) && executeChildren(caster, level, targets); + } + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final String key = settings.getString(KEY); + final double min = parseValues(caster, MIN, level, 1); + final double max = parseValues(caster, MAX, level, 999); + final Object data = DynamicSkill.getCastData(caster).get(key); - if (data != null) - { + if (data != null) { double value = (Double) data; - return value >= min && value <= max && executeChildren(caster, level, targets); + return value >= min && value <= max; } return false; diff --git a/src/com/sucy/skill/dynamic/condition/WaterCondition.java b/src/com/sucy/skill/dynamic/condition/WaterCondition.java index 28de584a..4c94b499 100644 --- a/src/com/sucy/skill/dynamic/condition/WaterCondition.java +++ b/src/com/sucy/skill/dynamic/condition/WaterCondition.java @@ -26,42 +26,24 @@ */ package com.sucy.skill.dynamic.condition; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Material; import org.bukkit.entity.LivingEntity; -import java.util.ArrayList; -import java.util.List; - /** * A condition for dynamic skills that requires the target to have a specified potion effect */ -public class WaterCondition extends EffectComponent -{ +public class WaterCondition extends ConditionComponent { private static final String STATE = "state"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean out = settings.getString(STATE).toLowerCase().equals("out of water"); - ArrayList list = new ArrayList(); - for (LivingEntity target : targets) - { - Material block = target.getLocation().getBlock().getType(); - if (out != (block == Material.WATER || block == Material.STATIONARY_WATER)) - { - list.add(target); - } - } - return list.size() > 0 && executeChildren(caster, level, list); + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final boolean out = settings.getString(STATE).toLowerCase().equals("out of water"); + final Material block = target.getLocation().getBlock().getType(); + return out != (block.name().contains("WATER")); + } + + @Override + public String getKey() { + return "water"; } } diff --git a/src/com/sucy/skill/dynamic/condition/WeatherCondition.java b/src/com/sucy/skill/dynamic/condition/WeatherCondition.java new file mode 100644 index 00000000..88d0125f --- /dev/null +++ b/src/com/sucy/skill/dynamic/condition/WeatherCondition.java @@ -0,0 +1,37 @@ +package com.sucy.skill.dynamic.condition; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.LivingEntity; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.condition.Weather + */ +public class WeatherCondition extends ConditionComponent { + private String TYPE = "type"; + + @Override + boolean test(final LivingEntity caster, final int level, final LivingEntity target) { + final String type = settings.getString(TYPE).toLowerCase(); + final World world = target.getWorld(); + final Location loc = target.getLocation(); + final double temperature = loc.getBlock().getTemperature(); + + switch (type) { + case "thunder": + return world.isThundering() && temperature <= 1; + case "rain": + return world.hasStorm() && temperature > 0.15 && temperature <= 1; + case "snow": + return world.hasStorm() && temperature <= 0.15; + default: + return !world.hasStorm() || temperature > 1; + } + } + + @Override + public String getKey() { + return "weather"; + } +} diff --git a/src/com/sucy/skill/dynamic/custom/CustomComponent.java b/src/com/sucy/skill/dynamic/custom/CustomComponent.java new file mode 100644 index 00000000..5f3ef55e --- /dev/null +++ b/src/com/sucy/skill/dynamic/custom/CustomComponent.java @@ -0,0 +1,41 @@ +package com.sucy.skill.dynamic.custom; + +import com.sucy.skill.dynamic.ComponentType; + +import java.util.List; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.custom.CustomComponent + */ +public interface CustomComponent { + + /** + * @return unique key for your component (what is used in skill .yml files) + */ + String getKey(); + + /** + * @return type of the component, describing it's general purpose + * @see ComponentType + */ + ComponentType getType(); + + /** + * @return A description for your trigger that's shown in the editor + */ + String getDescription(); + + /** + * @return settings to show in the editor + */ + List getOptions(); + + default String getDisplayName() { + return getKey(); + } + + default boolean isContainer() { + return getType() != ComponentType.MECHANIC; + } +} diff --git a/src/com/sucy/skill/dynamic/custom/CustomEffectComponent.java b/src/com/sucy/skill/dynamic/custom/CustomEffectComponent.java new file mode 100644 index 00000000..b71a8e1b --- /dev/null +++ b/src/com/sucy/skill/dynamic/custom/CustomEffectComponent.java @@ -0,0 +1,9 @@ +package com.sucy.skill.dynamic.custom; + +import com.sucy.skill.dynamic.EffectComponent; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.custom.CustomEffect + */ +public abstract class CustomEffectComponent extends EffectComponent implements CustomComponent { } diff --git a/src/com/sucy/skill/dynamic/custom/CustomTrigger.java b/src/com/sucy/skill/dynamic/custom/CustomTrigger.java new file mode 100644 index 00000000..a7c4c8fe --- /dev/null +++ b/src/com/sucy/skill/dynamic/custom/CustomTrigger.java @@ -0,0 +1,10 @@ +package com.sucy.skill.dynamic.custom; + +import com.sucy.skill.dynamic.trigger.Trigger; +import org.bukkit.event.Event; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.custom.CustomTrigger + */ +public interface CustomTrigger extends Trigger, CustomComponent { } diff --git a/src/com/sucy/skill/dynamic/custom/EditorOption.java b/src/com/sucy/skill/dynamic/custom/EditorOption.java new file mode 100644 index 00000000..a0d4556b --- /dev/null +++ b/src/com/sucy/skill/dynamic/custom/EditorOption.java @@ -0,0 +1,61 @@ +package com.sucy.skill.dynamic.custom; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.custom.EditorOption + */ +public class EditorOption { + public final Type type; + public final String key; + public final String name; + public final String description; + public final Map extra = new HashMap<>(); + + public static EditorOption number(final String key, final String name, final String description, final double base, final double scale) { + final EditorOption option = new EditorOption(Type.NUMBER, key, name, description); + option.extra.put("base", Double.toString(base)); + option.extra.put("scale", Double.toString(scale)); + return option; + } + + public static EditorOption text(final String key, final String name, final String description, final String initial) { + final EditorOption option = new EditorOption(Type.TEXT, key, name, description); + option.extra.put("default", "\"" + initial + "\""); + return option; + } + + public static EditorOption dropdown(final String key, final String name, final String description, final List options) { + final EditorOption option = new EditorOption(Type.DROPDOWN, key, name, description); + option.extra.put("options", format(options)); + return option; + } + + public static EditorOption list(final String key, final String name, final String description, final List options) { + final EditorOption option = new EditorOption(Type.LIST, key, name, description); + option.extra.put("options", format(options)); + return option; + } + + private static String format(final List list) { + return "[\"" + list.stream().collect(Collectors.joining("\",\"")) + "\"]"; + } + + private EditorOption(final Type type, final String key, final String name, final String description) { + this.type = type; + this.key = key; + this.name = name; + this.description = description; + } + + private enum Type { + NUMBER, + TEXT, + DROPDOWN, + LIST + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/AttributeMechanic.java b/src/com/sucy/skill/dynamic/mechanic/AttributeMechanic.java index a38ce78b..fdf78390 100644 --- a/src/com/sucy/skill/dynamic/mechanic/AttributeMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/AttributeMechanic.java @@ -28,24 +28,26 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Applies a flag to each target */ -public class AttributeMechanic extends EffectComponent -{ - private HashMap tasks = new HashMap(); +public class AttributeMechanic extends MechanicComponent { + private static final String KEY = "key"; + private static final String AMOUNT = "amount"; + private static final String SECONDS = "seconds"; + private static final String STACKABLE = "stackable"; - private static final String KEY = "key"; - private static final String AMOUNT = "amount"; - private static final String SECONDS = "seconds"; + private final Map> tasks = new HashMap<>(); /** * Executes the component @@ -57,62 +59,91 @@ public class AttributeMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { + public boolean execute(LivingEntity caster, int level, List targets) { String key = settings.getString(KEY, ""); - if (targets.size() == 0 || SkillAPI.getAttributeManager().getAttribute(key) == null) - { + if (targets.size() == 0 || SkillAPI.getAttributeManager().getAttribute(key) == null) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - int amount = (int) attr(caster, AMOUNT, level, 5, isSelf); - double seconds = attr(caster, SECONDS, level, 3.0, isSelf); - int ticks = (int) (seconds * 20); + final Map casterTasks = tasks.computeIfAbsent(caster.getEntityId(), HashMap::new); + final int amount = (int) parseValues(caster, AMOUNT, level, 5); + final double seconds = parseValues(caster, SECONDS, level, 3.0); + final boolean stackable = settings.getString(STACKABLE, "false").equalsIgnoreCase("true"); + final int ticks = (int) (seconds * 20); + boolean worked = false; - for (LivingEntity target : targets) - { - if (target instanceof Player) - { + for (LivingEntity target : targets) { + if (target instanceof Player) { worked = true; - PlayerData data = SkillAPI.getPlayerData((Player) target); + final PlayerData data = SkillAPI.getPlayerData((Player) target); - if (tasks.containsKey(data.getPlayerName())) - { - AttribTask old = tasks.remove(data.getPlayerName()); - if (amount != old.amount) - data.addBonusAttributes(key, amount - old.amount); + if (casterTasks.containsKey(data.getPlayerName()) && !stackable) { + final AttribTask old = casterTasks.remove(data.getPlayerName()); + if (amount != old.amount) { data.addBonusAttributes(key, amount - old.amount); } old.cancel(); - } - else - data.addBonusAttributes(key, amount); + } else { data.addBonusAttributes(key, amount); } - AttribTask task = new AttribTask(data, key, amount); - tasks.put(data.getPlayerName(), task); - SkillAPI.schedule(task, ticks); + final AttribTask task = new AttribTask(caster.getEntityId(), data, key, amount); + casterTasks.put(data.getPlayerName(), task); + if (ticks >= 0) { + SkillAPI.schedule(task, ticks); + } } } return worked; } - private class AttribTask extends BukkitRunnable - { + @Override + public String getKey() { + return "attribute"; + } + + @Override + protected void doCleanUp(final LivingEntity user) { + final Map casterTasks = tasks.remove(user.getEntityId()); + if (casterTasks != null) { + casterTasks.values().forEach(AttribTask::stop); + } + } + + private class AttribTask extends BukkitRunnable { private PlayerData data; private String attrib; private int amount; + private int id; + private boolean running = false; + private boolean stopped = false; - public AttribTask(PlayerData data, String attrib, int amount) - { + AttribTask(int id, PlayerData data, String attrib, int amount) { + this.id = id; this.data = data; this.attrib = attrib; this.amount = amount; } + public void stop() { + if (!stopped) { + stopped = true; + run(); + if (running) { + cancel(); + } + } + } + @Override - public void run() - { + public BukkitTask runTaskLater(final Plugin plugin, final long delay) { + running = true; + return super.runTaskLater(plugin, delay); + } + + @Override + public void run() { data.addBonusAttributes(attrib, -amount); - tasks.remove(data.getPlayerName()); + if (tasks.containsKey(id)) { + tasks.get(id).remove(data.getPlayerName()); + } + running = false; } } } diff --git a/src/com/sucy/skill/dynamic/mechanic/BlockMechanic.java b/src/com/sucy/skill/dynamic/mechanic/BlockMechanic.java index 4512b80c..3d1a9af4 100644 --- a/src/com/sucy/skill/dynamic/mechanic/BlockMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/BlockMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.SkillAPI; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -42,15 +41,14 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Mechanic that changes blocks for a duration before * returning them to what they were */ -public class BlockMechanic extends EffectComponent -{ - - private static final Vector up = new Vector(0, 1, 0); +public class BlockMechanic extends MechanicComponent { + private static final Vector UP = new Vector(0, 1, 0); private static final String SHAPE = "shape"; private static final String TYPE = "type"; @@ -65,6 +63,11 @@ public class BlockMechanic extends EffectComponent private static final String UPWARD = "upward"; private static final String RIGHT = "right"; + private static final HashMap pending = new HashMap(); + private static final HashMap original = new HashMap(); + + private final Map> tasks = new HashMap<>(); + /** * Executes the component * @@ -75,48 +78,41 @@ public class BlockMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) return false; + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } Material block = Material.ICE; - try - { + try { block = Material.valueOf(settings.getString(BLOCK, "ICE").toUpperCase().replace(' ', '_')); - } - catch (Exception ex) - { + } catch (Exception ex) { // Use default } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; boolean sphere = settings.getString(SHAPE, "sphere").toLowerCase().equals("sphere"); - int ticks = (int) (20 * attr(caster, SECONDS, level, 5, isSelf)); + int ticks = (int) (20 * parseValues(caster, SECONDS, level, 5)); byte data = (byte) settings.getInt(DATA, 0); String type = settings.getString(TYPE, "solid").toLowerCase(); boolean solid = type.equals("solid"); boolean air = type.equals("air"); - double forward = attr(caster, FORWARD, level, 0, isSelf); - double upward = attr(caster, UPWARD, level, 0, isSelf); - double right = attr(caster, RIGHT, level, 0, isSelf); + double forward = parseValues(caster, FORWARD, level, 0); + double upward = parseValues(caster, UPWARD, level, 0); + double right = parseValues(caster, RIGHT, level, 0); List blocks = new ArrayList(); World w = caster.getWorld(); // Grab blocks in a sphere - if (sphere) - { - double radius = attr(caster, RADIUS, level, 3, isSelf); + if (sphere) { + double radius = parseValues(caster, RADIUS, level, 3); double x, y, z, dx, dy, dz; double rSq = radius * radius; - for (LivingEntity t : targets) - { + for (LivingEntity t : targets) { // Get the center with offsets included Location loc = t.getLocation(); Vector dir = t.getLocation().getDirection().setY(0).normalize(); - Vector nor = dir.clone().crossProduct(up); + Vector nor = dir.clone().crossProduct(UP); loc.add(dir.multiply(forward).add(nor.multiply(right))); loc.add(0, upward, 0); @@ -125,22 +121,17 @@ public boolean execute(LivingEntity caster, int level, List target z = loc.getBlockZ(); // Get all blocks within the radius of the center - for (int i = (int) (x - radius) + 1; i < (int) (x + radius); i++) - { - for (int j = (int) (y - radius) + 1; j < (int) (y + radius); j++) - { - for (int k = (int) (z - radius) + 1; k < (int) (z + radius); k++) - { + for (int i = (int) (x - radius) + 1; i < (int) (x + radius); i++) { + for (int j = (int) (y - radius) + 1; j < (int) (y + radius); j++) { + for (int k = (int) (z - radius) + 1; k < (int) (z + radius); k++) { dx = x - i; dy = y - j; dz = z - k; - if (dx * dx + dy * dy + dz * dz < rSq) - { + if (dx * dx + dy * dy + dz * dz < rSq) { Block b = w.getBlockAt(i, j, k); if ((!solid || b.getType().isSolid()) - && (!air || b.getType() == Material.AIR) - && !SkillAPI.getSettings().getFilteredBlocks().contains(b.getType())) - { + && (!air || b.getType() == Material.AIR) + && !SkillAPI.getSettings().getFilteredBlocks().contains(b.getType())) { blocks.add(b); } } @@ -151,20 +142,18 @@ public boolean execute(LivingEntity caster, int level, List target } // Grab blocks in a cuboid - else - { + else { // Cuboid options - double width = (attr(caster, WIDTH, level, 5, isSelf) - 1) / 2; - double height = (attr(caster, HEIGHT, level, 5, isSelf) - 1) / 2; - double depth = (attr(caster, DEPTH, level, 5, isSelf) - 1) / 2; + double width = (parseValues(caster, WIDTH, level, 5) - 1) / 2; + double height = (parseValues(caster, HEIGHT, level, 5) - 1) / 2; + double depth = (parseValues(caster, DEPTH, level, 5) - 1) / 2; double x, y, z; - for (LivingEntity t : targets) - { + for (LivingEntity t : targets) { // Get the location with offsets included Location loc = t.getLocation(); Vector dir = t.getLocation().getDirection().setY(0).normalize(); - Vector nor = dir.clone().crossProduct(up); + Vector nor = dir.clone().crossProduct(UP); loc.add(dir.multiply(forward).add(nor.multiply(right))); loc.add(0, upward, 0); @@ -173,17 +162,13 @@ public boolean execute(LivingEntity caster, int level, List target z = loc.getZ(); // Get all blocks in the area - for (double i = x - width; i <= x + width + 0.01; i++) - { - for (double j = y - height; j <= y + height + 0.01; j++) - { - for (double k = z - depth; k <= z + depth + 0.01; k++) - { + for (double i = x - width; i <= x + width + 0.01; i++) { + for (double j = y - height; j <= y + height + 0.01; j++) { + for (double k = z - depth; k <= z + depth + 0.01; k++) { Block b = w.getBlockAt((int) i, (int) j, (int) k); if ((!solid || b.getType().isSolid()) - && (!air || b.getType() == Material.AIR) - && !SkillAPI.getSettings().getFilteredBlocks().contains(b.getType())) - { + && (!air || b.getType() == Material.AIR) + && !SkillAPI.getSettings().getFilteredBlocks().contains(b.getType())) { blocks.add(b); } } @@ -194,16 +179,12 @@ public boolean execute(LivingEntity caster, int level, List target // Change blocks ArrayList states = new ArrayList(); - for (Block b : blocks) - { + for (Block b : blocks) { // Increment the counter Location loc = b.getLocation(); - if (pending.containsKey(loc)) - { + if (pending.containsKey(loc)) { pending.put(loc, pending.get(loc) + 1); - } - else - { + } else { pending.put(loc, 1); original.put(loc, b.getState()); } @@ -216,34 +197,27 @@ public boolean execute(LivingEntity caster, int level, List target } // Revert after duration - RevertTask task = new RevertTask(states); + final RevertTask task = new RevertTask(caster, states); task.runTaskLater(Bukkit.getPluginManager().getPlugin("SkillAPI"), ticks); - tasks.add(task); + tasks.computeIfAbsent(caster.getEntityId(), ArrayList::new).add(task); return true; } - private static final HashMap pending = new HashMap(); - private static final HashMap original = new HashMap(); - - private static final ArrayList tasks = new ArrayList(); + @Override + public String getKey() { + return "block"; + } - /** - * Reverts all block changes - */ - public static void revertAll() - { - for (Location loc : pending.keySet()) - { - original.get(loc).update(true, false); - } - for (RevertTask task : tasks) - { - task.cancel(); + @Override + protected void doCleanUp(final LivingEntity caster) { + final List casterTasks = tasks.remove(caster.getEntityId()); + if (casterTasks != null) { + casterTasks.forEach(task -> { + task.revert(); + task.cancel(); + }); } - pending.clear(); - original.clear(); - tasks.clear(); } /** @@ -253,40 +227,38 @@ public static void revertAll() * * @return true if modified, false otherwise */ - public static boolean isPending(Location loc) - { + public static boolean isPending(Location loc) { return pending.containsKey(loc); } /** * Reverts block changes after a duration */ - private class RevertTask extends BukkitRunnable - { + private class RevertTask extends BukkitRunnable { private ArrayList locs; + private LivingEntity caster; - RevertTask(ArrayList locs) - { + RevertTask(final LivingEntity caster, final ArrayList locs) { + this.caster = caster; this.locs = locs; } @Override - public void run() - { - for (Location loc : locs) - { + public void run() { + revert(); + tasks.get(caster.getEntityId()).remove(this); + } + + private void revert() { + for (Location loc : locs) { int count = pending.remove(loc); - if (count == 1) - { + if (count == 1) { original.remove(loc).update(true, false); - } - else - { + } else { pending.put(loc, count - 1); } } - tasks.remove(this); } } } diff --git a/src/com/sucy/skill/dynamic/mechanic/BuffMechanic.java b/src/com/sucy/skill/dynamic/mechanic/BuffMechanic.java new file mode 100644 index 00000000..38f00f57 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/BuffMechanic.java @@ -0,0 +1,63 @@ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.api.util.Buff; +import com.sucy.skill.api.util.BuffManager; +import com.sucy.skill.api.util.BuffType; +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +/** + * SkillAPI © 2017 + * com.sucy.skill.dynamic.mechanic.BuffMechanic + */ +public class BuffMechanic extends MechanicComponent { + + private static final String MODIFIER = "modifier"; + private static final String CATEGORY = "category"; + private static final String TYPE = "type"; + private static final String VALUE = "value"; + private static final String SECONDS = "seconds"; + private static final String IMMEDIATE = "immediate"; + + @Override + public String getKey() { + return "buff"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) return false; + + boolean immediate = settings.getString(IMMEDIATE, "false").equalsIgnoreCase("true"); + double value = parseValues(caster, VALUE, level, 1.0); + boolean percent = settings.getString(MODIFIER, "flat").equalsIgnoreCase("multiplier"); + + if (immediate) { + skill.setImmediateBuff(value, !percent); + return true; + } + + BuffType buffType = BuffType.valueOf(settings.getString(TYPE, "DAMAGE")); + double seconds = parseValues(caster, SECONDS, level, 3.0); + String category = settings.getString(CATEGORY, null); + int ticks = (int) (seconds * 20); + for (LivingEntity target : targets) { + BuffManager.getBuffData(target).addBuff( + buffType, + category, + new Buff(this.skill.getName() + "-" + caster.getName(), value, percent), + ticks); + } + return targets.size() > 0; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/CancelEffectMechanic.java b/src/com/sucy/skill/dynamic/mechanic/CancelEffectMechanic.java new file mode 100644 index 00000000..72246c8f --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/CancelEffectMechanic.java @@ -0,0 +1,56 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.mechanic.ParticleEffectCancel + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.api.particle.EffectData; +import com.sucy.skill.api.particle.EffectManager; +import com.sucy.skill.api.particle.target.EntityTarget; +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +public class CancelEffectMechanic extends MechanicComponent { + + private static final String KEY = "effect-key"; + + @Override + public String getKey() { + return "cancel effect"; + } + + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + String key = settings.getString(KEY, skill.getName()); + + for (LivingEntity target : targets) { + EffectData effectData = EffectManager.getEffectData(new EntityTarget(target)); + if (effectData != null) { effectData.cancel(key); } + } + + return targets.size() > 0; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/CancelMechanic.java b/src/com/sucy/skill/dynamic/mechanic/CancelMechanic.java index 67f311cc..b8e47a93 100644 --- a/src/com/sucy/skill/dynamic/mechanic/CancelMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/CancelMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -34,8 +33,12 @@ /** * Cancels the event that caused the trigger to go off */ -public class CancelMechanic extends EffectComponent -{ +public class CancelMechanic extends MechanicComponent { + @Override + public String getKey() { + return "cancel"; + } + /** * Executes the component * @@ -46,8 +49,7 @@ public class CancelMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { + public boolean execute(LivingEntity caster, int level, List targets) { skill.cancelTrigger(); return true; } diff --git a/src/com/sucy/skill/dynamic/mechanic/ChannelMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ChannelMechanic.java index a6f0dd05..82682946 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ChannelMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ChannelMechanic.java @@ -28,7 +28,6 @@ import com.sucy.skill.api.util.FlagManager; import com.sucy.skill.api.util.StatusFlag; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Bukkit; import org.bukkit.entity.LivingEntity; @@ -37,11 +36,15 @@ /** * Executes child components after a delay, applying "channeling" rules */ -public class ChannelMechanic extends EffectComponent -{ +public class ChannelMechanic extends MechanicComponent { private static final String SECONDS = "time"; private static final String STILL = "still"; + @Override + public String getKey() { + return "channel"; + } + /** * Executes the component * @@ -52,31 +55,21 @@ public class ChannelMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(final LivingEntity caster, final int level, final List targets) - { - if (targets.size() == 0) - { + public boolean execute(final LivingEntity caster, final int level, final List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; boolean still = settings.getBool(STILL); - int ticks = (int) (20 * attr(caster, SECONDS, level, 2.0, isSelf)); - if (still) - FlagManager.addFlag(caster, StatusFlag.CHANNELING, ticks + 2); + int ticks = (int) (20 * parseValues(caster, SECONDS, level, 2.0)); + if (still) { FlagManager.addFlag(caster, StatusFlag.CHANNELING, ticks + 2); } Bukkit.getScheduler().runTaskLater( - Bukkit.getPluginManager().getPlugin("SkillAPI"), new Runnable() - { - @Override - public void run() - { - if (FlagManager.hasFlag(caster, StatusFlag.CHANNEL)) - { - executeChildren(caster, level, targets); + Bukkit.getPluginManager().getPlugin("SkillAPI"), () -> { + if (FlagManager.hasFlag(caster, StatusFlag.CHANNEL)) { FlagManager.removeFlag(caster, StatusFlag.CHANNEL); FlagManager.removeFlag(caster, StatusFlag.CHANNELING); + executeChildren(caster, level, targets); } - } - }, ticks + }, ticks ); FlagManager.addFlag(caster, StatusFlag.CHANNEL, ticks + 2); return true; diff --git a/src/com/sucy/skill/dynamic/mechanic/CleanseMechanic.java b/src/com/sucy/skill/dynamic/mechanic/CleanseMechanic.java index e1a38dd9..74e62f52 100644 --- a/src/com/sucy/skill/dynamic/mechanic/CleanseMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/CleanseMechanic.java @@ -26,28 +26,33 @@ */ package com.sucy.skill.dynamic.mechanic; +import com.google.common.collect.ImmutableSet; import com.sucy.skill.api.util.FlagManager; import com.sucy.skill.api.util.StatusFlag; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import java.util.List; +import java.util.Set; /** * Cleanses a target of negative potion or status effects */ -public class CleanseMechanic extends EffectComponent -{ - private static final PotionEffectType[] POTIONS = new PotionEffectType[] { - PotionEffectType.BLINDNESS, PotionEffectType.CONFUSION, PotionEffectType.HUNGER, - PotionEffectType.LEVITATION, PotionEffectType.POISON, PotionEffectType.SLOW, - PotionEffectType.SLOW_DIGGING, PotionEffectType.WEAKNESS, PotionEffectType.WITHER - }; +public class CleanseMechanic extends MechanicComponent { + private static final Set POTIONS = ImmutableSet.of( + "BLINDNESS", "CONFUSION", "HUNGER", "LEVITATION", "POISON", + "SLOW", "SLOW_DIGGING", "WEAKNESS", "WITHER" + ); private static final String STATUS = "status"; private static final String POTION = "potion"; + @Override + public String getKey() { + return "cleanse"; + } + /** * Executes the component * @@ -58,53 +63,38 @@ public class CleanseMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { + public boolean execute(LivingEntity caster, int level, List targets) { boolean worked = false; String status = settings.getString(STATUS, "None").toLowerCase(); String potion = settings.getString(POTION).toUpperCase().replace(' ', '_'); PotionEffectType type = null; - try - { + try { type = PotionEffectType.getByName(potion); - } - catch (Exception ex) - { + } catch (Exception ex) { // Invalid potion type } - for (LivingEntity target : targets) - { - if (status.equals("all")) - { - for (String flag : StatusFlag.NEGATIVE) - { - if (FlagManager.hasFlag(target, flag)) - { + for (LivingEntity target : targets) { + if (status.equals("all")) { + for (String flag : StatusFlag.NEGATIVE) { + if (FlagManager.hasFlag(target, flag)) { FlagManager.removeFlag(target, flag); worked = true; } } - } - else if (FlagManager.hasFlag(target, status)) - { + } else if (FlagManager.hasFlag(target, status)) { FlagManager.removeFlag(target, status); worked = true; } - if (potion.equals("ALL")) - { - for (PotionEffectType p : POTIONS) - { - if (target.hasPotionEffect(p)) - { - target.removePotionEffect(p); + if (potion.equals("ALL")) { + for (PotionEffect p : target.getActivePotionEffects()) { + if (POTIONS.contains(p.getType().getName())) { + target.removePotionEffect(p.getType()); worked = true; } } - } - else if (type != null && target.hasPotionEffect(type)) - { + } else if (type != null && target.hasPotionEffect(type)) { target.removePotionEffect(type); worked = true; } diff --git a/src/com/sucy/skill/dynamic/mechanic/CommandMechanic.java b/src/com/sucy/skill/dynamic/mechanic/CommandMechanic.java index a2b618d3..0d898c4e 100644 --- a/src/com/sucy/skill/dynamic/mechanic/CommandMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/CommandMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Bukkit; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -36,11 +35,15 @@ /** * Executes a command for each target */ -public class CommandMechanic extends EffectComponent -{ +public class CommandMechanic extends MechanicComponent { private static final String COMMAND = "command"; private static final String TYPE = "type"; + @Override + public String getKey() { + return "command"; + } + /** * Executes the component * @@ -51,34 +54,26 @@ public class CommandMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(COMMAND)) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(COMMAND)) { return false; } String command = settings.getString(COMMAND); String type = settings.getString(TYPE).toLowerCase(); boolean worked = false; - for (LivingEntity t : targets) - { - if (t instanceof Player) - { + for (LivingEntity t : targets) { + if (t instanceof Player) { Player p = (Player) t; worked = true; - String filtered = command.replace("{player}", p.getName()); - if (type.equals("op")) - { + + command = filter(caster, p, command); + if (type.equals("op")) { boolean op = p.isOp(); p.setOp(true); - Bukkit.getServer().dispatchCommand(p, filtered); + Bukkit.getServer().dispatchCommand(p, command); p.setOp(op); - } - else - { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), filtered); - } + } else { Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command); } } } diff --git a/src/com/sucy/skill/dynamic/mechanic/CooldownMechanic.java b/src/com/sucy/skill/dynamic/mechanic/CooldownMechanic.java index a20bfdba..5df863b3 100644 --- a/src/com/sucy/skill/dynamic/mechanic/CooldownMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/CooldownMechanic.java @@ -29,7 +29,6 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.api.player.PlayerSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -38,12 +37,16 @@ /** * Lowers the cooldowns of the caster's skills */ -public class CooldownMechanic extends EffectComponent -{ +public class CooldownMechanic extends MechanicComponent { private static final String SKILL = "skill"; private static final String TYPE = "type"; private static final String VALUE = "value"; + @Override + public String getKey() { + return "cooldown"; + } + /** * Executes the component * @@ -54,51 +57,34 @@ public class CooldownMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (!(caster instanceof Player)) return false; + public boolean execute(LivingEntity caster, int level, List targets) { + if (!(caster instanceof Player)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String skill = settings.getString(SKILL, ""); String type = settings.getString(TYPE, "all").toLowerCase(); - double value = attr(caster, VALUE, level, 0, isSelf); + double value = parseValues(caster, VALUE, level, 0); PlayerData playerData = SkillAPI.getPlayerData((Player) caster); PlayerSkill skillData = playerData.getSkill(skill); - if (skillData == null && !skill.equals("all")) - { + if (skillData == null && !skill.equals("all")) { skillData = playerData.getSkill(this.skill.getName()); } boolean worked = false; - if (skill.equals("all")) - { - for (PlayerSkill data : playerData.getSkills()) - { - if (data.isOnCooldown() == (value < 0)) - { - continue; - } - if (type.equals("percent")) - { + if (skill.equals("all")) { + for (PlayerSkill data : playerData.getSkills()) { + if (type.equals("percent")) { data.subtractCooldown(value * data.getCooldown() / 100); - } - else - { + } else { data.subtractCooldown(value); } worked = true; } - } - else if (skillData != null) - { - if (type.equals("percent")) - { + } else if (skillData != null) { + if (type.equals("percent")) { skillData.subtractCooldown(value * skillData.getCooldown() / 100); - } - else - { + } else { skillData.subtractCooldown(value); } worked = true; diff --git a/src/com/sucy/skill/dynamic/mechanic/DamageBuffMechanic.java b/src/com/sucy/skill/dynamic/mechanic/DamageBuffMechanic.java index 03954856..3358e365 100644 --- a/src/com/sucy/skill/dynamic/mechanic/DamageBuffMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/DamageBuffMechanic.java @@ -28,7 +28,7 @@ import com.sucy.skill.api.util.Buff; import com.sucy.skill.api.util.BuffManager; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.api.util.BuffType; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -36,13 +36,17 @@ /** * Applies a flag to each target */ -public class DamageBuffMechanic extends EffectComponent -{ +public class DamageBuffMechanic extends MechanicComponent { private static final String TYPE = "type"; private static final String SKILL = "skill"; private static final String VALUE = "value"; private static final String SECONDS = "seconds"; + @Override + public String getKey() { + return "damage buff"; + } + /** * Executes the component * @@ -53,25 +57,20 @@ public class DamageBuffMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { - return false; - } + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; boolean skill = settings.getString(SKILL, "false").equalsIgnoreCase("true"); boolean percent = settings.getString(TYPE, "flat").toLowerCase().equals("multiplier"); - double value = attr(caster, VALUE, level, 1.0, isSelf); - double seconds = attr(caster, SECONDS, level, 3.0, isSelf); + double value = parseValues(caster, VALUE, level, 1.0); + double seconds = parseValues(caster, SECONDS, level, 3.0); int ticks = (int) (seconds * 20); - for (LivingEntity target : targets) - { - if (skill) - BuffManager.addSkillDamageBuff(target, new Buff(this.skill.getName(), value, percent), ticks); - else - BuffManager.addDamageBuff(target, new Buff(this.skill.getName(), value, percent), ticks); + for (LivingEntity target : targets) { + BuffManager.addBuff( + target, + skill ? BuffType.SKILL_DAMAGE : BuffType.DAMAGE, + new Buff(this.skill.getName(), value, percent), + ticks); } return targets.size() > 0; } diff --git a/src/com/sucy/skill/dynamic/mechanic/DamageLoreMechanic.java b/src/com/sucy/skill/dynamic/mechanic/DamageLoreMechanic.java index a5165733..b82bef09 100644 --- a/src/com/sucy/skill/dynamic/mechanic/DamageLoreMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/DamageLoreMechanic.java @@ -26,9 +26,8 @@ */ package com.sucy.skill.dynamic.mechanic; +import com.rit.sucy.config.parse.NumberParser; import com.rit.sucy.version.VersionManager; -import com.sucy.skill.api.util.NumberParser; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.ChatColor; import org.bukkit.entity.LivingEntity; import org.bukkit.inventory.ItemStack; @@ -40,12 +39,17 @@ /** * Deals damage based on a held item's lore to each target */ -public class DamageLoreMechanic extends EffectComponent -{ +public class DamageLoreMechanic extends MechanicComponent { private static final String REGEX = "regex"; private static final String MULTIPLIER = "multiplier"; private static final String HAND = "hand"; private static final String TRUE = "true"; + private static final String CLASSIFIER = "classifier"; + + @Override + public String getKey() { + return "damage lore"; + } /** * Executes the component @@ -57,55 +61,46 @@ public class DamageLoreMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; + public boolean execute(LivingEntity caster, int level, List targets) { String regex = settings.getString(REGEX, "Damage: {value}"); regex = regex.replace("{value}", "([0-9]+)"); Pattern pattern = Pattern.compile(regex); - double m = attr(caster, MULTIPLIER, level, 1.0, isSelf); + double m = parseValues(caster, MULTIPLIER, level, 1.0); boolean worked = false; boolean offhand = VersionManager.isVersionAtLeast(VersionManager.V1_9_0) - && settings.getString(HAND).equalsIgnoreCase("offhand"); + && settings.getString(HAND).equalsIgnoreCase("offhand"); boolean trueDmg = settings.getBool(TRUE, false); + String classification = settings.getString(CLASSIFIER, "default"); - if (caster.getEquipment() == null) - return false; + if (caster.getEquipment() == null) { return false; } ItemStack hand; - if (offhand) - hand = caster.getEquipment().getItemInOffHand(); - else hand = caster.getEquipment().getItemInHand(); + if (offhand) { hand = caster.getEquipment().getItemInOffHand(); } else { + hand = caster.getEquipment().getItemInMainHand(); + } - if (hand == null || !hand.hasItemMeta() || !hand.getItemMeta().hasLore()) - return false; + if (hand == null || !hand.hasItemMeta() || !hand.getItemMeta().hasLore()) { return false; } List lore = hand.getItemMeta().getLore(); - for (String line : lore) - { + for (String line : lore) { line = ChatColor.stripColor(line); Matcher matcher = pattern.matcher(line); - if (matcher.find()) - { + if (matcher.find()) { String value = matcher.group(1); - try - { + try { double base = NumberParser.parseDouble(value); - if (base * m > 0) - { - for (LivingEntity target : targets) - { - if (trueDmg) - skill.trueDamage(target, base * m, caster); - else - skill.damage(target, base * m, caster); + if (base * m > 0) { + for (LivingEntity target : targets) { + if (target.isDead()) { continue; } + + if (trueDmg) { skill.trueDamage(target, base * m, caster); } else { + skill.damage(target, base * m, caster, classification); + } } worked = targets.size() > 0; break; } - } - catch (Exception ex) - { + } catch (Exception ex) { // Not a valid value } } diff --git a/src/com/sucy/skill/dynamic/mechanic/DamageMechanic.java b/src/com/sucy/skill/dynamic/mechanic/DamageMechanic.java index 68b3e526..6b52c547 100644 --- a/src/com/sucy/skill/dynamic/mechanic/DamageMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/DamageMechanic.java @@ -26,7 +26,7 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; +import org.bukkit.attribute.Attribute; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -34,11 +34,16 @@ /** * Deals damage to each target */ -public class DamageMechanic extends EffectComponent -{ - private static final String TYPE = "type"; - private static final String DAMAGE = "value"; - private static final String TRUE = "true"; +public class DamageMechanic extends MechanicComponent { + private static final String TYPE = "type"; + private static final String DAMAGE = "value"; + private static final String TRUE = "true"; + private static final String CLASSIFIER = "classifier"; + + @Override + public String getKey() { + return "damage"; + } /** * Executes the component @@ -50,35 +55,32 @@ public class DamageMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; + public boolean execute(LivingEntity caster, int level, List targets) { String pString = settings.getString(TYPE, "damage").toLowerCase(); boolean percent = pString.equals("multiplier") || pString.equals("percent"); boolean missing = pString.equals("percent missing"); boolean left = pString.equals("percent left"); boolean trueDmg = settings.getBool(TRUE, false); - double damage = attr(caster, DAMAGE, level, 1.0, isSelf); - if (damage < 0) return false; - for (LivingEntity target : targets) - { - double amount = damage; - if (percent) - { - amount = damage * target.getMaxHealth() / 100; + double damage = parseValues(caster, DAMAGE, level, 1.0); + String classification = settings.getString(CLASSIFIER, "default"); + if (damage < 0) { return false; } + for (LivingEntity target : targets) { + if (target.isDead()) { + continue; } - else if (missing) - { - amount = damage * (target.getMaxHealth() - target.getHealth()) / 100; - } - else if (left) - { + + double amount = damage; + if (percent) { + amount = damage * target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() / 100; + } else if (missing) { + amount = damage * (target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() - target.getHealth()) / 100; + } else if (left) { amount = damage * target.getHealth() / 100; } - if (trueDmg) - skill.trueDamage(target, amount, caster); - else - skill.damage(target, amount, caster); + if (trueDmg) { skill.trueDamage(target, amount, caster); } + else { + skill.damage(target, amount, caster, classification); + } } return targets.size() > 0; } diff --git a/src/com/sucy/skill/dynamic/mechanic/DefenseBuffMechanic.java b/src/com/sucy/skill/dynamic/mechanic/DefenseBuffMechanic.java index b6871438..89f016ad 100644 --- a/src/com/sucy/skill/dynamic/mechanic/DefenseBuffMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/DefenseBuffMechanic.java @@ -28,7 +28,7 @@ import com.sucy.skill.api.util.Buff; import com.sucy.skill.api.util.BuffManager; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.api.util.BuffType; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -36,13 +36,17 @@ /** * Applies a flag to each target */ -public class DefenseBuffMechanic extends EffectComponent -{ +public class DefenseBuffMechanic extends MechanicComponent { private static final String TYPE = "type"; private static final String SKILL = "skill"; private static final String VALUE = "value"; private static final String SECONDS = "seconds"; + @Override + public String getKey() { + return "defense buff"; + } + /** * Executes the component * @@ -53,25 +57,20 @@ public class DefenseBuffMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { - return false; - } + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; boolean skill = settings.getString(SKILL, "false").equalsIgnoreCase("true"); boolean percent = settings.getString(TYPE, "flat").toLowerCase().equals("multiplier"); - double value = attr(caster, VALUE, level, 1.0, isSelf); - double seconds = attr(caster, SECONDS, level, 3.0, isSelf); + double value = parseValues(caster, VALUE, level, 1.0); + double seconds = parseValues(caster, SECONDS, level, 3.0); int ticks = (int) (seconds * 20); - for (LivingEntity target : targets) - { - if (skill) - BuffManager.addSkillDefenseBuff(target, new Buff(this.skill.getName(), value, percent), ticks); - else - BuffManager.addDefenseBuff(target, new Buff(this.skill.getName(), value, percent), ticks); + for (LivingEntity target : targets) { + BuffManager.addBuff( + target, + skill ? BuffType.SKILL_DEFENSE : BuffType.DEFENSE, + new Buff(this.skill.getName(), value, percent), + ticks); } return targets.size() > 0; } diff --git a/src/com/sucy/skill/dynamic/mechanic/DelayMechanic.java b/src/com/sucy/skill/dynamic/mechanic/DelayMechanic.java index b9ad03d5..9296bfae 100644 --- a/src/com/sucy/skill/dynamic/mechanic/DelayMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/DelayMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Bukkit; import org.bukkit.entity.LivingEntity; @@ -35,10 +34,14 @@ /** * Executes child components after a delay */ -public class DelayMechanic extends EffectComponent -{ +public class DelayMechanic extends MechanicComponent { private static final String SECONDS = "delay"; + @Override + public String getKey() { + return "delay"; + } + /** * Executes the component * @@ -49,23 +52,15 @@ public class DelayMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(final LivingEntity caster, final int level, final List targets) - { - if (targets.size() == 0) - { + public boolean execute(final LivingEntity caster, final int level, final List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double seconds = attr(caster, SECONDS, level, 2.0, isSelf); + double seconds = parseValues(caster, SECONDS, level, 2.0); Bukkit.getScheduler().runTaskLater( - Bukkit.getPluginManager().getPlugin("SkillAPI"), new Runnable() - { - @Override - public void run() - { - executeChildren(caster, level, targets); - } - }, (long) (seconds * 20) + Bukkit.getPluginManager().getPlugin("SkillAPI"), + () -> executeChildren(caster, level, targets), + (long) (seconds * 20) ); return true; } diff --git a/src/com/sucy/skill/dynamic/mechanic/DisguiseMechanic.java b/src/com/sucy/skill/dynamic/mechanic/DisguiseMechanic.java index a1284a16..836b2420 100644 --- a/src/com/sucy/skill/dynamic/mechanic/DisguiseMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/DisguiseMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.dynamic.TempEntity; import com.sucy.skill.hook.DisguiseHook; import com.sucy.skill.hook.PluginChecker; @@ -39,8 +38,7 @@ /** * Disguises each target */ -public class DisguiseMechanic extends EffectComponent -{ +public class DisguiseMechanic extends MechanicComponent { private static final String TYPE = "type"; private static final String MOB = "mob"; private static final String ADULT = "adult"; @@ -49,6 +47,11 @@ public class DisguiseMechanic extends EffectComponent private static final String DATA = "data"; private static final String DURATION = "duration"; + @Override + public String getKey() { + return "disguise"; + } + /** * Executes the component * @@ -59,46 +62,48 @@ public class DisguiseMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (!PluginChecker.isDisguiseActive()) - return false; + public boolean execute(LivingEntity caster, int level, List targets) { + if (!PluginChecker.isDisguiseActive()) { return false; } String type = settings.getString(TYPE); // Mob disguises - if (type.equalsIgnoreCase("mob")) - { - for (LivingEntity target : targets) - if (!(target instanceof TempEntity)) + if (type.equalsIgnoreCase("mob")) { + for (LivingEntity target : targets) { + if (!(target instanceof TempEntity)) { DisguiseHook.disguiseMob(target, settings.getString(MOB, "Zombie"), settings.getBool(ADULT, true)); + } + } } // Player disguises - else if (type.equalsIgnoreCase("player")) - { - for (LivingEntity target : targets) - if (!(target instanceof TempEntity)) - DisguiseHook.disguisePlayer(target, settings.getString(PLAYER, "Eniripsa96").replace("{player}", caster.getName())); + else if (type.equalsIgnoreCase("player")) { + for (LivingEntity target : targets) { + if (!(target instanceof TempEntity)) { + DisguiseHook.disguisePlayer( + target, + settings.getString(PLAYER, "Eniripsa96").replace("{player}", caster.getName())); + } + } } // Miscellaneous disguises - else if (type.equalsIgnoreCase("misc")) - { - for (LivingEntity target : targets) - if (!(target instanceof TempEntity)) + else if (type.equalsIgnoreCase("misc")) { + for (LivingEntity target : targets) { + if (!(target instanceof TempEntity)) { DisguiseHook.disguiseMisc(target, settings.getString(MISC, "Painting"), settings.getInt(DATA, 0)); + } + } } // Invalid type - else - return false; + else { return false; } // Apply Flag duration - int ticks = (int) (attr(caster, DURATION, level, -1, false) * 20); - for (LivingEntity target : targets) - if (!(target instanceof TempEntity)) - FlagManager.addFlag(target, MechanicListener.DISGUISE_KEY, ticks); + int ticks = (int) (parseValues(caster, DURATION, level, -1) * 20); + for (LivingEntity target : targets) { + if (!(target instanceof TempEntity)) { FlagManager.addFlag(target, MechanicListener.DISGUISE_KEY, ticks); } + } return targets.size() > 0; } diff --git a/src/com/sucy/skill/dynamic/mechanic/DurabilityMechanic.java b/src/com/sucy/skill/dynamic/mechanic/DurabilityMechanic.java new file mode 100644 index 00000000..b4238c64 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/DurabilityMechanic.java @@ -0,0 +1,58 @@ +package com.sucy.skill.dynamic.mechanic; + +import com.rit.sucy.version.VersionManager; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +/** + * SkillAPI © 2017 + * com.sucy.skill.dynamic.mechanic.DurabilityMechanic + */ +public class DurabilityMechanic extends MechanicComponent { + + private static final String AMOUNT = "amount"; + private static final String OFFHAND = "offhand"; + + @Override + public String getKey() { + return "durability"; + } + + @Override + public boolean execute( + final LivingEntity caster, final int level, final List targets) { + + if (!(caster instanceof Player)) { + return false; + } + + final Player player = (Player) caster; + final boolean offhand = settings.getBool(OFFHAND, false); + final short amount = (short) (parseValues(caster, AMOUNT, level, 1) * targets.size()); + + final ItemStack item; + if (offhand && VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { + item = player.getInventory().getItemInOffHand(); + } else { item = player.getInventory().getItemInMainHand(); } + + if (item == null || item.getType().getMaxDurability() == 0) { + return false; + } + + int durability = item.getType().getMaxDurability() - item.getDurability(); + if (durability <= -amount) { + if (offhand && VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { + player.getInventory().setItemInOffHand(null); + } else { + player.getInventory().setItemInMainHand(null); + } + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1, 1); + } + item.setDurability((short) (item.getDurability() - amount)); + return true; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ExplosionMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ExplosionMechanic.java index c4f14644..88fc8d45 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ExplosionMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ExplosionMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; @@ -35,12 +34,16 @@ /** * Creates an explosion at the target's location */ -public class ExplosionMechanic extends EffectComponent -{ +public class ExplosionMechanic extends MechanicComponent { private static final String POWER = "power"; private static final String DAMAGE = "damage"; private static final String FIRE = "fire"; + @Override + public String getKey() { + return "explosion"; + } + /** * Executes the component * @@ -51,18 +54,14 @@ public class ExplosionMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double power = attr(caster, POWER, level, 4, isSelf); + double power = parseValues(caster, POWER, level, 4); boolean fire = settings.getBool(FIRE, false); boolean damage = settings.getBool(DAMAGE, false); - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { Location loc = target.getLocation(); target.getWorld().createExplosion(loc.getX(), loc.getY(), loc.getZ(), (float) power, fire, damage); } diff --git a/src/com/sucy/skill/dynamic/mechanic/FireMechanic.java b/src/com/sucy/skill/dynamic/mechanic/FireMechanic.java index fb55c089..f5faf27d 100644 --- a/src/com/sucy/skill/dynamic/mechanic/FireMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/FireMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -34,10 +33,14 @@ /** * Executes child components after a delay */ -public class FireMechanic extends EffectComponent -{ +public class FireMechanic extends MechanicComponent { private static final String SECONDS = "seconds"; + @Override + public String getKey() { + return "fire"; + } + /** * Executes the component * @@ -48,18 +51,15 @@ public class FireMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double seconds = attr(caster, SECONDS, level, 3.0, isSelf); + double seconds = parseValues(caster, SECONDS, level, 3.0); int ticks = (int) (seconds * 20); - for (LivingEntity target : targets) - { - target.setFireTicks(Math.max(ticks, target.getFireTicks())); + for (LivingEntity target : targets) { + int newTicks = ticks <= 0 ? 0 : Math.max(ticks, target.getFireTicks()); + target.setFireTicks(newTicks); } return targets.size() > 0; } diff --git a/src/com/sucy/skill/dynamic/mechanic/FlagClearMechanic.java b/src/com/sucy/skill/dynamic/mechanic/FlagClearMechanic.java index 8de0dd3c..da45260d 100644 --- a/src/com/sucy/skill/dynamic/mechanic/FlagClearMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/FlagClearMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -35,10 +34,15 @@ /** * Applies a flag to each target */ -public class FlagClearMechanic extends EffectComponent +public class FlagClearMechanic extends MechanicComponent { private static final String KEY = "key"; + @Override + public String getKey() { + return "flag clear"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/FlagMechanic.java b/src/com/sucy/skill/dynamic/mechanic/FlagMechanic.java index 10ecdc2d..05743c5c 100644 --- a/src/com/sucy/skill/dynamic/mechanic/FlagMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/FlagMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -35,11 +34,16 @@ /** * Applies a flag to each target */ -public class FlagMechanic extends EffectComponent +public class FlagMechanic extends MechanicComponent { private static final String KEY = "key"; private static final String SECONDS = "seconds"; + @Override + public String getKey() { + return "flag"; + } + /** * Executes the component * @@ -57,9 +61,8 @@ public boolean execute(LivingEntity caster, int level, List target return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(KEY); - double seconds = attr(caster, SECONDS, level, 3.0, isSelf); + double seconds = parseValues(caster, SECONDS, level, 3.0); int ticks = (int) (seconds * 20); for (LivingEntity target : targets) { diff --git a/src/com/sucy/skill/dynamic/mechanic/FlagToggleMechanic.java b/src/com/sucy/skill/dynamic/mechanic/FlagToggleMechanic.java index 69c32866..fec6d1ec 100644 --- a/src/com/sucy/skill/dynamic/mechanic/FlagToggleMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/FlagToggleMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -35,10 +34,15 @@ /** * Applies a flag to each target */ -public class FlagToggleMechanic extends EffectComponent +public class FlagToggleMechanic extends MechanicComponent { private static final String KEY = "key"; + @Override + public String getKey() { + return "flag toggle"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/FoodMechanic.java b/src/com/sucy/skill/dynamic/mechanic/FoodMechanic.java new file mode 100644 index 00000000..6611fbb2 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/FoodMechanic.java @@ -0,0 +1,45 @@ +package com.sucy.skill.dynamic.mechanic; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * SkillAPI © 2017 + * com.sucy.skill.dynamic.mechanic.FoodMechanic + */ +public class FoodMechanic extends MechanicComponent { + private static final String FOOD = "food"; + private static final String SATURATION = "saturation"; + + @Override + public String getKey() { + return "food"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + double food = parseValues(caster, FOOD, level, 1.0); + double saturation = parseValues(caster, SATURATION, level, 1.0); + for (LivingEntity target : targets) { + if (target instanceof Player) { + Player player = (Player) target; + player.setFoodLevel(Math.min(20, Math.max(0, (int) food + player.getFoodLevel()))); + player.setSaturation(Math.min( + player.getFoodLevel(), + Math.max(0, player.getSaturation() + (float) saturation))); + } + } + return targets.size() > 0; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ForgetTargetsMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ForgetTargetsMechanic.java new file mode 100644 index 00000000..672c8bf2 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/ForgetTargetsMechanic.java @@ -0,0 +1,59 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.mechanic.ForgetTargets + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +public class ForgetTargetsMechanic extends MechanicComponent +{ + private static final String KEY = "key"; + + @Override + public String getKey() { + return "forget targets"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) + { + String key = settings.getString(KEY, ""); + DynamicSkill.getCastData(caster).remove(key); + return true; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/HealMechanic.java b/src/com/sucy/skill/dynamic/mechanic/HealMechanic.java index c5023344..7de848be 100644 --- a/src/com/sucy/skill/dynamic/mechanic/HealMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/HealMechanic.java @@ -28,8 +28,8 @@ import com.rit.sucy.version.VersionManager; import com.sucy.skill.api.event.SkillHealEvent; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Bukkit; +import org.bukkit.attribute.Attribute; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -37,11 +37,15 @@ /** * Heals each target */ -public class HealMechanic extends EffectComponent -{ +public class HealMechanic extends MechanicComponent { private static final String TYPE = "type"; private static final String VALUE = "value"; + @Override + public String getKey() { + return "heal"; + } + /** * Executes the component * @@ -52,24 +56,21 @@ public class HealMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; + public boolean execute(LivingEntity caster, int level, List targets) { boolean percent = settings.getString(TYPE, "health").toLowerCase().equals("percent"); - double value = attr(caster, VALUE, level, 1.0, isSelf); - if (value < 0) return false; - for (LivingEntity target : targets) - { + double value = parseValues(caster, VALUE, level, 1.0); + if (value < 0) { return false; } + for (LivingEntity target : targets) { + if (target.isDead()) { continue; } + double amount = value; - if (percent) - { - amount = target.getMaxHealth() * value / 100; + if (percent) { + amount = target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() * value / 100; } SkillHealEvent event = new SkillHealEvent(caster, target, amount); Bukkit.getPluginManager().callEvent(event); - if (!event.isCancelled()) - { + if (!event.isCancelled()) { VersionManager.heal(target, event.getAmount()); } } diff --git a/src/com/sucy/skill/dynamic/mechanic/HealthSetMechanic.java b/src/com/sucy/skill/dynamic/mechanic/HealthSetMechanic.java new file mode 100644 index 00000000..98922739 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/HealthSetMechanic.java @@ -0,0 +1,31 @@ +package com.sucy.skill.dynamic.mechanic; + +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +/** + * SkillAPI © 2017 + * com.sucy.skill.dynamic.mechanic.HealthSetMechanic + */ +public class HealthSetMechanic extends MechanicComponent { + + private static final String HEALTH = "health"; + + @Override + public String getKey() { + return "health set"; + } + + @Override + public boolean execute(final LivingEntity caster, final int level, final List targets) { + final double health = Math.max(1, parseValues(caster, HEALTH, level, 1)); + + for (final LivingEntity target : targets) { + target.setHealth(Math.min(health, target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue())); + } + + return true; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/HeldItemMechanic.java b/src/com/sucy/skill/dynamic/mechanic/HeldItemMechanic.java new file mode 100644 index 00000000..69ecfc40 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/HeldItemMechanic.java @@ -0,0 +1,72 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.mechanic.HeldItemMechanic + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.SkillAPI; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.List; + +public class HeldItemMechanic extends MechanicComponent +{ + private static final String SLOT = "slot"; + + @Override + public String getKey() { + return "held item"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) + { + int slot = (int) parseValues(caster, SLOT, level, 0); + + boolean worked = false; + for (LivingEntity target : targets) + { + if (!(target instanceof Player)) + continue; + + worked = true; + Player player = (Player) target; + if (SkillAPI.getSettings().isSkillBarEnabled() && SkillAPI.getPlayerData(player).getSkillBar().isWeaponSlot(slot)) + player.getInventory().setHeldItemSlot(slot); + } + + return worked; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ImmunityMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ImmunityMechanic.java index 880a70aa..835209a0 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ImmunityMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ImmunityMechanic.java @@ -26,8 +26,8 @@ */ package com.sucy.skill.dynamic.mechanic; +import com.sucy.skill.SkillAPI; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -35,10 +35,17 @@ /** * Applies a damage immunity flag to each target */ -public class ImmunityMechanic extends EffectComponent -{ - private static final String TYPE = "type"; - private static final String SECONDS = "seconds"; +public class ImmunityMechanic extends MechanicComponent { + public static final String META_KEY = "sapi_immunity"; + + private static final String TYPE = "type"; + private static final String SECONDS = "seconds"; + private static final String MULTIPLIER = "multiplier"; + + @Override + public String getKey() { + return "immunity"; + } /** * Executes the component @@ -50,20 +57,18 @@ public class ImmunityMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(TYPE)) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(TYPE)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(TYPE); - double seconds = attr(caster, SECONDS, level, 3.0, isSelf); + double seconds = parseValues(caster, SECONDS, level, 3.0); + double multiplier = parseValues(caster, MULTIPLIER, level, 0); int ticks = (int) (seconds * 20); - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { FlagManager.addFlag(target, "immune:" + key.toUpperCase().replace(" ", "_"), ticks); + SkillAPI.setMeta(target, META_KEY, multiplier); } return targets.size() > 0; } diff --git a/src/com/sucy/skill/dynamic/mechanic/InterruptMechanic.java b/src/com/sucy/skill/dynamic/mechanic/InterruptMechanic.java index fa944ad7..980f181b 100644 --- a/src/com/sucy/skill/dynamic/mechanic/InterruptMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/InterruptMechanic.java @@ -28,7 +28,6 @@ import com.sucy.skill.api.util.FlagManager; import com.sucy.skill.api.util.StatusFlag; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -36,8 +35,13 @@ /** * Interrupts any channels that may be being performed by the targets */ -public class InterruptMechanic extends EffectComponent +public class InterruptMechanic extends MechanicComponent { + @Override + public String getKey() { + return "interrupt"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/ItemMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ItemMechanic.java index c744b117..3726f287 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ItemMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ItemMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.rit.sucy.text.TextFormatter; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Material; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -39,16 +38,21 @@ /** * Gives an item to each player target */ -public class ItemMechanic extends EffectComponent +public class ItemMechanic extends MechanicComponent { private static final String MATERIAL = "material"; private static final String AMOUNT = "amount"; - private static final String DATA = "data"; - private static final String BYTE = "byte"; + // private static final String DATA = "data"; + // private static final String BYTE = "byte"; private static final String CUSTOM = "custom"; private static final String NAME = "name"; private static final String LORE = "lore"; + @Override + public String getKey() { + return "item"; + } + /** * Executes the component * @@ -72,10 +76,11 @@ public boolean execute(LivingEntity caster, int level, List target return false; } int amount = settings.getInt(AMOUNT, 1); - int durability = settings.getInt(DATA, 0); - int data = settings.getInt(BYTE, 0); - ItemStack item = new ItemStack(material, amount, (short) durability, (byte) data); + // int durability = settings.getInt(DATA, 0); + // int data = settings.getInt(BYTE, 0); + ItemStack item = new ItemStack(material, amount); + boolean custom = settings.getString(CUSTOM, "false").toLowerCase().equals("true"); if (custom) { @@ -90,11 +95,12 @@ public boolean execute(LivingEntity caster, int level, List target item.setItemMeta(meta); } + boolean worked = false; for (LivingEntity target : targets) { if (target instanceof Player) { - ((Player) target).getInventory().addItem(item); + worked = ((Player) target).getInventory().addItem(item).isEmpty() || worked; } } return targets.size() > 0; diff --git a/src/com/sucy/skill/dynamic/mechanic/ItemProjectileMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ItemProjectileMechanic.java index c247d019..c3142b07 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ItemProjectileMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ItemProjectileMechanic.java @@ -27,14 +27,21 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.particle.EffectPlayer; +import com.sucy.skill.api.particle.target.FollowTarget; import com.sucy.skill.api.projectile.CustomProjectile; import com.sucy.skill.api.projectile.ItemProjectile; import com.sucy.skill.api.projectile.ProjectileCallback; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.cast.CircleIndicator; +import com.sucy.skill.cast.CylinderIndicator; +import com.sucy.skill.cast.IIndicator; +import com.sucy.skill.cast.IndicatorType; +import com.sucy.skill.cast.ProjectileIndicator; import com.sucy.skill.dynamic.TempEntity; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; @@ -44,8 +51,7 @@ /** * Heals each target */ -public class ItemProjectileMechanic extends EffectComponent implements ProjectileCallback -{ +public class ItemProjectileMechanic extends MechanicComponent implements ProjectileCallback { private static final Vector UP = new Vector(0, 1, 0); private static final String ITEM = "item"; @@ -62,6 +68,63 @@ public class ItemProjectileMechanic extends EffectComponent implements Projectil private static final String UPWARD = "upward"; private static final String FORWARD = "forward"; + private static final String USE_EFFECT = "use-effect"; + private static final String EFFECT_KEY = "effect-key"; + + /** + * Creates the list of indicators for the skill + * + * @param list list to store indicators in + * @param caster caster reference + * @param targets location to base location on + * @param level the level of the skill to create for + */ + @Override + public void makeIndicators(List list, Player caster, List targets, int level) { + targets.forEach(target -> { + // Get common values + int amount = (int) parseValues(caster, AMOUNT, level, 1.0); + double speed = parseValues(caster, "velocity", level, 1); + String spread = settings.getString(SPREAD, "cone").toLowerCase(); + + // Apply the spread type + if (spread.equals("rain")) { + double radius = parseValues(caster, RADIUS, level, 2.0); + + if (indicatorType == IndicatorType.DIM_2) { + IIndicator indicator = new CircleIndicator(radius); + indicator.moveTo(target.getLocation().add(0, 0.1, 0)); + list.add(indicator); + } else { + double height = parseValues(caster, HEIGHT, level, 8.0); + IIndicator indicator = new CylinderIndicator(radius, height); + indicator.moveTo(target.getLocation()); + list.add(indicator); + } + } else { + Vector dir = target.getLocation().getDirection(); + if (spread.equals("horizontal cone")) { + dir.setY(0); + dir.normalize(); + } + double angle = parseValues(caster, ANGLE, level, 30.0); + ArrayList dirs = CustomProjectile.calcSpread(dir, angle, amount); + Location loc = caster.getLocation().add(0, caster.getEyeHeight(), 0); + for (Vector d : dirs) { + ProjectileIndicator indicator = new ProjectileIndicator(speed, 0.04); + indicator.setDirection(d); + indicator.moveTo(loc); + list.add(indicator); + } + } + }); + } + + @Override + public String getKey() { + return "item projectile"; + } + /** * Executes the component * @@ -72,75 +135,77 @@ public class ItemProjectileMechanic extends EffectComponent implements Projectil * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { + public boolean execute(LivingEntity caster, int level, List targets) { Material mat = Material.JACK_O_LANTERN; - try - { + try { mat = Material.valueOf(settings.getString(ITEM).toUpperCase().replace(" ", "_")); - } - catch (Exception ex) - { + } catch (Exception ex) { // Invalid or missing item material } ItemStack item = new ItemStack(mat); item.setDurability((short) settings.getInt(DATA, 0)); // Get other common values - double speed = attr(caster, SPEED, level, 3.0, true); - int amount = (int) attr(caster, AMOUNT, level, 1.0, true); + double speed = parseValues(caster, SPEED, level, 3.0); + int amount = (int) parseValues(caster, AMOUNT, level, 1.0); String spread = settings.getString(SPREAD, "cone").toLowerCase(); boolean ally = settings.getString(ALLY, "enemy").toLowerCase().equals("ally"); // Fire from each target - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { Location loc = target.getLocation(); // Apply the spread type ArrayList list; - if (spread.equals("rain")) - { - double radius = attr(caster, RADIUS, level, 2.0, true); - double height = attr(caster, HEIGHT, level, 8.0, true); + if (spread.equals("rain")) { + double radius = parseValues(caster, RADIUS, level, 2.0); + double height = parseValues(caster, HEIGHT, level, 8.0); list = ItemProjectile.rain(caster, loc, item, radius, height, speed, amount, this); - } - else - { + } else { Vector dir = target.getLocation().getDirection(); - double right = attr(caster, RIGHT, level, 0, true); - double upward = attr(caster, UPWARD, level, 0, true); - double forward = attr(caster, FORWARD, level, 0, true); + double right = parseValues(caster, RIGHT, level, 0); + double upward = parseValues(caster, UPWARD, level, 0); + double forward = parseValues(caster, FORWARD, level, 0); Vector looking = dir.clone().setY(0).normalize(); Vector normal = looking.clone().crossProduct(UP); looking.multiply(forward).add(normal.multiply(right)); - if (spread.equals("horizontal cone")) - { + if (spread.equals("horizontal cone")) { dir.setY(0); dir.normalize(); } dir.multiply(speed); - double angle = attr(caster, ANGLE, level, 30.0, true); + double angle = parseValues(caster, ANGLE, level, 30.0); list = ItemProjectile.spread( - caster, - dir, - loc.add(looking).add(0, 0.5 + upward, 0), - item, - angle, - amount, - this + caster, + dir, + loc.add(looking).add(0, 0.5 + upward, 0), + item, + angle, + amount, + this ); } // Set metadata for when the callback happens - for (ItemProjectile p : list) - { + for (ItemProjectile p : list) { SkillAPI.setMeta(p, LEVEL, level); p.setAllyEnemy(ally, !ally); } + + if (settings.getBool(USE_EFFECT, false)) { + EffectPlayer player = new EffectPlayer(settings); + for (CustomProjectile p : list) { + player.start( + new FollowTarget(p), + settings.getString(EFFECT_KEY, skill.getName()), + 9999, + level, + true); + } + } } return targets.size() > 0; @@ -153,10 +218,8 @@ public boolean execute(LivingEntity caster, int level, List target * @param hit the entity hit by the projectile, if any */ @Override - public void callback(CustomProjectile projectile, LivingEntity hit) - { - if (hit == null) - { + public void callback(CustomProjectile projectile, LivingEntity hit) { + if (hit == null) { hit = new TempEntity(projectile.getLocation()); } ArrayList targets = new ArrayList(); diff --git a/src/com/sucy/skill/dynamic/mechanic/ItemRemoveMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ItemRemoveMechanic.java index 21881d67..9fc1b2ed 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ItemRemoveMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ItemRemoveMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.dynamic.ItemChecker; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -36,9 +35,12 @@ /** * Removes an item to from each player target */ -public class ItemRemoveMechanic extends EffectComponent +public class ItemRemoveMechanic extends MechanicComponent { - + @Override + public String getKey() { + return "item remove"; + } /** * Executes the component @@ -58,7 +60,7 @@ public boolean execute(LivingEntity caster, int level, List target if (target instanceof Player) { players = true; - ItemChecker.check((Player) target, level, settings, true); + ItemChecker.check((Player) target, level, this, true); } } return players; diff --git a/src/com/sucy/skill/dynamic/mechanic/LaunchMechanic.java b/src/com/sucy/skill/dynamic/mechanic/LaunchMechanic.java index e98ccd99..e1064394 100644 --- a/src/com/sucy/skill/dynamic/mechanic/LaunchMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/LaunchMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.util.Vector; @@ -35,14 +34,20 @@ /** * Launches the target in a given direction relative to their forward direction */ -public class LaunchMechanic extends EffectComponent -{ +public class LaunchMechanic extends MechanicComponent { private Vector up = new Vector(0, 1, 0); private static final String FORWARD = "forward"; private static final String UPWARD = "upward"; private static final String RIGHT = "right"; + private static final String RELATIVE = "relative"; + + @Override + public String getKey() { + return "launch"; + } + /** * Executes the component * @@ -53,21 +58,26 @@ public class LaunchMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double forward = attr(caster, FORWARD, level, 0, isSelf); - double upward = attr(caster, UPWARD, level, 0, isSelf); - double right = attr(caster, RIGHT, level, 0, isSelf); - for (LivingEntity target : targets) - { - Vector dir = target.getLocation().getDirection().setY(0).normalize(); - Vector nor = dir.clone().crossProduct(up); + double forward = parseValues(caster, FORWARD, level, 0); + double upward = parseValues(caster, UPWARD, level, 0); + double right = parseValues(caster, RIGHT, level, 0); + String relative = settings.getString(RELATIVE, "target").toLowerCase(); + for (LivingEntity target : targets) { + final Vector dir; + if (relative.equals("caster")) { + dir = caster.getLocation().getDirection().setY(0).normalize(); + } else if (relative.equals("between")) { + dir = target.getLocation().toVector().subtract(caster.getLocation().toVector()).setY(0).normalize(); + } else { + dir = target.getLocation().getDirection().setY(0).normalize(); + } + + final Vector nor = dir.clone().crossProduct(up); dir.multiply(forward); dir.add(nor.multiply(right)).setY(upward); diff --git a/src/com/sucy/skill/dynamic/mechanic/LightningMechanic.java b/src/com/sucy/skill/dynamic/mechanic/LightningMechanic.java index b2c67539..7d2dba13 100644 --- a/src/com/sucy/skill/dynamic/mechanic/LightningMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/LightningMechanic.java @@ -26,7 +26,7 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; +import org.bukkit.Location; import org.bukkit.entity.LivingEntity; import org.bukkit.util.Vector; @@ -35,12 +35,17 @@ /** * Strikes lightning about each target with an offset */ -public class LightningMechanic extends EffectComponent -{ +public class LightningMechanic extends MechanicComponent { private static final Vector up = new Vector(0, 1, 0); private static final String FORWARD = "forward"; private static final String RIGHT = "right"; + private static final String DAMAGE = "damage"; + + @Override + public String getKey() { + return "lightning"; + } /** * Executes the component @@ -52,20 +57,20 @@ public class LightningMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double forward = attr(caster, FORWARD, level, 0, isSelf); - double right = attr(caster, RIGHT, level, 0, isSelf); - for (LivingEntity target : targets) - { + boolean damage = settings.getBool(DAMAGE, true); + double forward = parseValues(caster, FORWARD, level, 0); + double right = parseValues(caster, RIGHT, level, 0); + for (LivingEntity target : targets) { Vector dir = target.getLocation().getDirection().setY(0).normalize(); Vector nor = dir.clone().crossProduct(up); - target.getWorld().strikeLightning(target.getLocation().add(dir.multiply(forward).add(nor.multiply(right)))); + Location loc = target.getLocation().add(dir.multiply(forward).add(nor.multiply(right))); + if (damage) { target.getWorld().strikeLightning(loc); } else { + target.getWorld().strikeLightningEffect(loc); + } } return targets.size() > 0; } diff --git a/src/com/sucy/skill/dynamic/mechanic/ManaMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ManaMechanic.java index ce56e24a..8fa5047a 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ManaMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ManaMechanic.java @@ -30,7 +30,6 @@ import com.sucy.skill.api.enums.ManaCost; import com.sucy.skill.api.enums.ManaSource; import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -39,11 +38,15 @@ /** * Gives mana to each target */ -public class ManaMechanic extends EffectComponent -{ +public class ManaMechanic extends MechanicComponent { private static final String TYPE = "type"; private static final String VALUE = "value"; + @Override + public String getKey() { + return "mana"; + } + /** * Executes the component * @@ -54,17 +57,13 @@ public class ManaMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; + public boolean execute(LivingEntity caster, int level, List targets) { boolean percent = settings.getString(TYPE, "mana").toLowerCase().equals("percent"); - double value = attr(caster, VALUE, level, 1.0, isSelf); + double value = parseValues(caster, VALUE, level, 1.0); boolean worked = false; - for (LivingEntity target : targets) - { - if (!(target instanceof Player)) - { + for (LivingEntity target : targets) { + if (!(target instanceof Player)) { continue; } @@ -72,21 +71,15 @@ public boolean execute(LivingEntity caster, int level, List target PlayerData data = SkillAPI.getPlayerData((Player) target); double amount; - if (percent) - { + if (percent) { amount = data.getMaxMana() * value / 100; - } - else - { + } else { amount = value; } - if (amount > 0) - { + if (amount > 0) { data.giveMana(amount, ManaSource.SKILL); - } - else - { + } else { data.useMana(-amount, ManaCost.SKILL_EFFECT); } } diff --git a/src/com/sucy/skill/dynamic/mechanic/MechanicComponent.java b/src/com/sucy/skill/dynamic/mechanic/MechanicComponent.java new file mode 100644 index 00000000..ca7b8dab --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/MechanicComponent.java @@ -0,0 +1,15 @@ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.dynamic.ComponentType; +import com.sucy.skill.dynamic.EffectComponent; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.mechanic.MechanicComponent + */ +public abstract class MechanicComponent extends EffectComponent { + @Override + public ComponentType getType() { + return ComponentType.MECHANIC; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/MessageMechanic.java b/src/com/sucy/skill/dynamic/mechanic/MessageMechanic.java index b3d06620..2f209003 100644 --- a/src/com/sucy/skill/dynamic/mechanic/MessageMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/MessageMechanic.java @@ -26,23 +26,24 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.rit.sucy.mobs.MobManager; import com.rit.sucy.text.TextFormatter; -import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import java.util.HashMap; import java.util.List; /** * Sends a message to each player target */ -public class MessageMechanic extends EffectComponent +public class MessageMechanic extends MechanicComponent { private static final String MESSAGE = "message"; + @Override + public String getKey() { + return "message"; + } + /** * Executes the component * @@ -56,39 +57,19 @@ public class MessageMechanic extends EffectComponent public boolean execute(LivingEntity caster, int level, List targets) { if (targets.size() == 0 || !settings.has(MESSAGE)) - { return false; - } String message = TextFormatter.colorString(settings.getString(MESSAGE)); if (message == null) return false; - // Grab values - HashMap data = DynamicSkill.getCastData(caster); - int i = message.indexOf('{'); - while (i >= 0) - { - int j = message.indexOf('}', i); - String key = message.substring(i + 1, j); - if (data.containsKey(key)) - { - Object obj = data.get(key); - if (obj instanceof Player) - obj = ((Player) obj).getName(); - else if (obj instanceof LivingEntity) - obj = MobManager.getName((LivingEntity) obj); - message = message.substring(0, i) + obj + message.substring(j + 1); - } - i = message.indexOf('{', j); - } - // Display message boolean worked = false; for (LivingEntity target : targets) { if (target instanceof Player) { - target.sendMessage(message); + Player player = (Player) target; + player.sendMessage(filter(caster, target, message)); worked = true; } } diff --git a/src/com/sucy/skill/dynamic/mechanic/ParticleAnimationMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ParticleAnimationMechanic.java index 1c264b24..05facba2 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ParticleAnimationMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ParticleAnimationMechanic.java @@ -27,8 +27,8 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.Settings; import com.sucy.skill.api.util.ParticleHelper; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; import org.bukkit.scheduler.BukkitRunnable; @@ -39,10 +39,8 @@ /** * Plays a particle effect */ -public class ParticleAnimationMechanic extends EffectComponent +public class ParticleAnimationMechanic extends MechanicComponent { - private static final Vector UP = new Vector(0, 1, 0); - private static final String FORWARD = "forward"; private static final String UPWARD = "upward"; private static final String RIGHT = "right"; @@ -56,6 +54,11 @@ public class ParticleAnimationMechanic extends EffectComponent private static final String H_CYCLES = "h-cycles"; private static final String V_CYCLES = "v-cycles"; + @Override + public String getKey() { + return "particle animation"; + } + /** * Executes the component * @@ -73,8 +76,11 @@ public boolean execute(LivingEntity caster, int level, List target return false; } - settings.set("level", level); - new ParticleTask(caster, targets, level); + final Settings copy = new Settings(settings); + copy.set(ParticleHelper.PARTICLES_KEY, parseValues(caster, ParticleHelper.PARTICLES_KEY, level, 1), 0); + copy.set(ParticleHelper.RADIUS_KEY, parseValues(caster, ParticleHelper.RADIUS_KEY, level, 0), 0); + copy.set("level", level); + new ParticleTask(caster, targets, level, copy); return targets.size() > 0; } @@ -102,13 +108,15 @@ private class ParticleTask extends BukkitRunnable private int vl; private double ht; private double vt; - private double radius; private double cos; private double sin; - public ParticleTask(LivingEntity caster, List targets, int level) + private Settings settings; + + ParticleTask(LivingEntity caster, List targets, int level, Settings settings) { this.targets = targets; + this.settings = settings; this.forward = settings.getDouble(FORWARD, 0); this.upward = settings.getDouble(UPWARD, 0); @@ -118,10 +126,10 @@ public ParticleTask(LivingEntity caster, List targets, int level) this.freq = (int) (settings.getDouble(FREQ, 1.0) * 20); this.angle = settings.getInt(ANGLE, 0); this.startAngle = settings.getInt(START, 0); - this.duration = steps * (int) (20 * attr(caster, DURATION, level, 3.0, true)); + this.duration = steps * (int) (20 * parseValues(caster, DURATION, level, 3.0)); this.life = 0; - this.ht = attr(caster, H_TRANS, level, 0, true); - this.vt = attr(caster, V_TRANS, level, 0, true); + this.ht = parseValues(caster, H_TRANS, level, 0); + this.vt = parseValues(caster, V_TRANS, level, 0); this.hc = settings.getInt(H_CYCLES, 1); this.vc = settings.getInt(V_CYCLES, 1); this.hl = duration / hc; diff --git a/src/com/sucy/skill/dynamic/mechanic/ParticleEffectMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ParticleEffectMechanic.java new file mode 100644 index 00000000..f965bc2c --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/ParticleEffectMechanic.java @@ -0,0 +1,57 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.mechanic.ParticleEffectMechanic + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2016 Steven Sucy + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.api.particle.EffectPlayer; +import com.sucy.skill.api.particle.target.EntityTarget; +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +public class ParticleEffectMechanic extends MechanicComponent +{ + private static final String DURATION = "duration"; + private static final String KEY = "effect-key"; + + @Override + public String getKey() { + return "particle effect"; + } + + @Override + public boolean execute(LivingEntity caster, int level, List targets) + { + String key = settings.getString(KEY, skill.getName()); + int duration = (int) (20 * parseValues(caster, DURATION, level, 5)); + + EffectPlayer player = new EffectPlayer(settings); + for (LivingEntity target : targets) + player.start(new EntityTarget(target), key, duration, level, true); + + return targets.size() > 0; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ParticleMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ParticleMechanic.java index 36a3d839..da2dade0 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ParticleMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ParticleMechanic.java @@ -26,8 +26,8 @@ */ package com.sucy.skill.dynamic.mechanic; +import com.sucy.skill.api.Settings; import com.sucy.skill.api.util.ParticleHelper; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; import org.bukkit.util.Vector; @@ -37,7 +37,7 @@ /** * Plays a particle effect */ -public class ParticleMechanic extends EffectComponent +public class ParticleMechanic extends MechanicComponent { private static final Vector UP = new Vector(0, 1, 0); @@ -45,6 +45,11 @@ public class ParticleMechanic extends EffectComponent private static final String UPWARD = "upward"; private static final String RIGHT = "right"; + @Override + public String getKey() { + return "particle"; + } + /** * Executes the component * @@ -65,7 +70,11 @@ public boolean execute(LivingEntity caster, int level, List target double forward = settings.getDouble(FORWARD, 0); double upward = settings.getDouble(UPWARD, 0); double right = settings.getDouble(RIGHT, 0); - settings.set("level", level); + + final Settings copy = new Settings(settings); + copy.set(ParticleHelper.PARTICLES_KEY, parseValues(caster, ParticleHelper.PARTICLES_KEY, level, 1), 0); + copy.set(ParticleHelper.RADIUS_KEY, parseValues(caster, ParticleHelper.RADIUS_KEY, level, 0), 0); + copy.set("level", level); for (LivingEntity target : targets) { @@ -74,7 +83,7 @@ public boolean execute(LivingEntity caster, int level, List target Vector side = dir.clone().crossProduct(UP); loc.add(dir.multiply(forward)).add(0, upward, 0).add(side.multiply(right)); - ParticleHelper.play(loc, settings); + ParticleHelper.play(loc, copy); } return targets.size() > 0; diff --git a/src/com/sucy/skill/dynamic/mechanic/ParticleProjectileMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ParticleProjectileMechanic.java index f357088f..5015fcdb 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ParticleProjectileMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ParticleProjectileMechanic.java @@ -27,13 +27,22 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.particle.EffectPlayer; +import com.sucy.skill.api.particle.target.FollowTarget; import com.sucy.skill.api.projectile.CustomProjectile; import com.sucy.skill.api.projectile.ParticleProjectile; import com.sucy.skill.api.projectile.ProjectileCallback; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.api.util.ParticleHelper; +import com.sucy.skill.cast.CircleIndicator; +import com.sucy.skill.cast.CylinderIndicator; +import com.sucy.skill.cast.IIndicator; +import com.sucy.skill.cast.IndicatorType; +import com.sucy.skill.cast.ProjectileIndicator; import com.sucy.skill.dynamic.TempEntity; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import org.bukkit.util.Vector; import java.util.ArrayList; @@ -42,8 +51,7 @@ /** * Heals each target */ -public class ParticleProjectileMechanic extends EffectComponent implements ProjectileCallback -{ +public class ParticleProjectileMechanic extends MechanicComponent implements ProjectileCallback { private static final Vector UP = new Vector(0, 1, 0); private static final String POSITION = "position"; @@ -58,6 +66,63 @@ public class ParticleProjectileMechanic extends EffectComponent implements Proje private static final String UPWARD = "upward"; private static final String FORWARD = "forward"; + private static final String USE_EFFECT = "use-effect"; + private static final String EFFECT_KEY = "effect-key"; + + /** + * Creates the list of indicators for the skill + * + * @param list list to store indicators in + * @param caster caster reference + * @param targets location to base location on + * @param level the level of the skill to create for + */ + @Override + public void makeIndicators(List list, Player caster, List targets, int level) { + targets.forEach(target -> { + // Get common values + int amount = (int) parseValues(caster, AMOUNT, level, 1.0); + double speed = parseValues(caster, "velocity", level, 1); + String spread = settings.getString(SPREAD, "cone").toLowerCase(); + + // Apply the spread type + if (spread.equals("rain")) { + double radius = parseValues(caster, RADIUS, level, 2.0); + + if (indicatorType == IndicatorType.DIM_2) { + IIndicator indicator = new CircleIndicator(radius); + indicator.moveTo(target.getLocation().add(0, 0.1, 0)); + list.add(indicator); + } else { + double height = parseValues(caster, HEIGHT, level, 8.0); + IIndicator indicator = new CylinderIndicator(radius, height); + indicator.moveTo(target.getLocation()); + list.add(indicator); + } + } else { + Vector dir = target.getLocation().getDirection(); + if (spread.equals("horizontal cone")) { + dir.setY(0); + dir.normalize(); + } + double angle = parseValues(caster, ANGLE, level, 30.0); + ArrayList dirs = CustomProjectile.calcSpread(dir, angle, amount); + Location loc = caster.getLocation().add(0, caster.getEyeHeight(), 0); + for (Vector d : dirs) { + ProjectileIndicator indicator = new ProjectileIndicator(speed, 0); + indicator.setDirection(d); + indicator.moveTo(loc); + list.add(indicator); + } + } + }); + } + + @Override + public String getKey() { + return "particle projectile"; + } + /** * Executes the component * @@ -68,65 +133,73 @@ public class ParticleProjectileMechanic extends EffectComponent implements Proje * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { + public boolean execute(LivingEntity caster, int level, List targets) { // Get common values - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - int amount = (int) attr(caster, AMOUNT, level, 1.0, isSelf); + int amount = (int) parseValues(caster, AMOUNT, level, 1.0); String spread = settings.getString(SPREAD, "cone").toLowerCase(); boolean ally = settings.getString(ALLY, "enemy").toLowerCase().equals("ally"); settings.set("level", level); - double position = settings.getDouble(POSITION); + + final Settings copy = new Settings(settings); + copy.set(ParticleProjectile.SPEED, parseValues(caster, ParticleProjectile.SPEED, level, 1), 0); + copy.set(ParticleHelper.PARTICLES_KEY, parseValues(caster, ParticleHelper.PARTICLES_KEY, level, 1), 0); + copy.set(ParticleHelper.RADIUS_KEY, parseValues(caster, ParticleHelper.RADIUS_KEY, level, 0), 0); // Fire from each target - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { Location loc = target.getLocation(); // Apply the spread type ArrayList list; - if (spread.equals("rain")) - { - double radius = attr(caster, RADIUS, level, 2.0, isSelf); - double height = attr(caster, HEIGHT, level, 8.0, isSelf); - list = ParticleProjectile.rain(caster, level, loc, settings, radius, height, amount, this); - } - else - { + if (spread.equals("rain")) { + double radius = parseValues(caster, RADIUS, level, 2.0); + double height = parseValues(caster, HEIGHT, level, 8.0); + list = ParticleProjectile.rain(caster, level, loc, copy, radius, height, amount, this); + } else { Vector dir = target.getLocation().getDirection(); - double right = attr(caster, RIGHT, level, 0, true); - double upward = attr(caster, UPWARD, level, 0, true); - double forward = attr(caster, FORWARD, level, 0, true); + double right = parseValues(caster, RIGHT, level, 0); + double upward = parseValues(caster, UPWARD, level, 0); + double forward = parseValues(caster, FORWARD, level, 0); Vector looking = dir.clone().setY(0).normalize(); Vector normal = looking.clone().crossProduct(UP); looking.multiply(forward).add(normal.multiply(right)); - if (spread.equals("horizontal cone")) - { + if (spread.equals("horizontal cone")) { dir.setY(0); dir.normalize(); } - double angle = attr(caster, ANGLE, level, 30.0, isSelf); + double angle = parseValues(caster, ANGLE, level, 30.0); list = ParticleProjectile.spread( - caster, - level, - dir, - loc.add(looking).add(0, upward + 0.5, 0), - settings, - angle, - amount, - this + caster, + level, + dir, + loc.add(looking).add(0, upward + 0.5, 0), + copy, + angle, + amount, + this ); } // Set metadata for when the callback happens - for (ParticleProjectile p : list) - { + for (ParticleProjectile p : list) { SkillAPI.setMeta(p, LEVEL, level); p.setAllyEnemy(ally, !ally); } + + if (settings.getBool(USE_EFFECT, false)) { + EffectPlayer player = new EffectPlayer(settings); + for (CustomProjectile p : list) { + player.start( + new FollowTarget(p), + settings.getString(EFFECT_KEY, skill.getName()), + 9999, + level, + true); + } + } } return targets.size() > 0; @@ -139,15 +212,12 @@ public boolean execute(LivingEntity caster, int level, List target * @param hit the entity hit by the projectile, if any */ @Override - public void callback(CustomProjectile projectile, LivingEntity hit) - { - if (hit == null) - { + public void callback(CustomProjectile projectile, LivingEntity hit) { + if (hit == null) { hit = new TempEntity(projectile.getLocation()); } ArrayList targets = new ArrayList(); targets.add(hit); executeChildren(projectile.getShooter(), SkillAPI.getMetaInt(projectile, LEVEL), targets); - projectile.setCallback(null); } } diff --git a/src/com/sucy/skill/dynamic/mechanic/PassiveMechanic.java b/src/com/sucy/skill/dynamic/mechanic/PassiveMechanic.java index de4cb16b..37437127 100644 --- a/src/com/sucy/skill/dynamic/mechanic/PassiveMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/PassiveMechanic.java @@ -28,7 +28,6 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; @@ -36,15 +35,15 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Executes child components continuously */ -public class PassiveMechanic extends EffectComponent -{ +public class PassiveMechanic extends MechanicComponent { private static final String PERIOD = "seconds"; - private static final HashMap> TASKS = new HashMap>(); + private final Map tasks = new HashMap<>(); /** * Executes the component @@ -56,63 +55,39 @@ public class PassiveMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() > 0) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - int period = (int) (attr(caster, PERIOD, level, 1.0, isSelf) * 20); - PassiveTask task = new PassiveTask(caster, level, targets, period); + public boolean execute(LivingEntity caster, int level, List targets) { + if (tasks.containsKey(caster.getEntityId())) { return false; } - if (!TASKS.containsKey(skill.getName())) TASKS.put(skill.getName(), new ArrayList()); - TASKS.get(skill.getName()).add(task); + if (targets.size() > 0) { + final int period = (int) (parseValues(caster, PERIOD, level, 1.0) * 20); + final PassiveTask task = new PassiveTask(caster, level, targets, period); + tasks.put(caster.getEntityId(), task); return true; } return false; } - /** - * Stops all passive tasks for the player - * - * @param player player to cancel tasks for - * @param skill skill to cancel - */ - public static void stopTasks(LivingEntity player, String skill) - { - ArrayList tasks = TASKS.get(skill); - if (tasks == null) return; - for (int i = 0; i < tasks.size(); i++) - { - if (tasks.get(i).caster == player) - { - tasks.get(i).cancel(); - tasks.remove(i); - i--; - } - } + @Override + public String getKey() { + return "passive"; } - /** - * Stops all passive tasks - */ - public static void stopAll() - { - for (ArrayList list : TASKS.values()) - for (PassiveTask task : list) - task.cancel(); - TASKS.clear(); + @Override + protected void doCleanUp(final LivingEntity caster) { + final PassiveTask task = tasks.remove(caster.getEntityId()); + if (task != null) { + task.cancel(); + } } - private class PassiveTask extends BukkitRunnable - { + private class PassiveTask extends BukkitRunnable { private List targets; private LivingEntity caster; private int level; - public PassiveTask(LivingEntity caster, int level, List targets, int period) - { - this.targets = targets; + PassiveTask(LivingEntity caster, int level, List targets, int period) { + this.targets = new ArrayList<>(targets); this.caster = caster; this.level = level; @@ -120,33 +95,34 @@ public PassiveTask(LivingEntity caster, int level, List targets, i } @Override - public void run() - { - for (int i = 0; i < targets.size(); i++) - { - if (targets.get(i).isDead() || !targets.get(i).isValid()) - { + public void cancel() { + super.cancel(); + tasks.remove(caster.getEntityId()); + } + + @Override + public void run() { + for (int i = 0; i < targets.size(); i++) { + if (targets.get(i).isDead() || !targets.get(i).isValid()) { targets.remove(i); } } - if (!skill.isActive(caster) || targets.size() == 0) - { + if (!skill.isActive(caster) || targets.size() == 0) { cancel(); - TASKS.get(skill.getName()).remove(this); return; - } - else if (caster instanceof Player) - { + } else if (caster instanceof Player) { PlayerSkill data = getSkillData(caster); - if (data == null || !data.isUnlocked() || !((Player) caster).isOnline()) - { + if (data == null || !data.isUnlocked() || !((Player) caster).isOnline()) { cancel(); - TASKS.get(skill.getName()).remove(this); return; } } level = skill.getActiveLevel(caster); executeChildren(caster, level, targets); + + if (skill.checkCancelled()) { + cancel(); + } } } } diff --git a/src/com/sucy/skill/dynamic/mechanic/PermissionMechanic.java b/src/com/sucy/skill/dynamic/mechanic/PermissionMechanic.java index 68481766..4d715e9c 100644 --- a/src/com/sucy/skill/dynamic/mechanic/PermissionMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/PermissionMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.hook.PluginChecker; import org.bukkit.entity.LivingEntity; @@ -36,11 +35,15 @@ /** * Applies a flag to each target */ -public class PermissionMechanic extends EffectComponent -{ +public class PermissionMechanic extends MechanicComponent { private static final String PERM = "perm"; private static final String SECONDS = "seconds"; + @Override + public String getKey() { + return "permission"; + } + /** * Executes the component * @@ -51,20 +54,18 @@ public class PermissionMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(PERM) || !PluginChecker.isVaultActive()) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(PERM) || !PluginChecker.isVaultActive()) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(PERM); - double seconds = attr(caster, SECONDS, level, 3.0, isSelf); + double seconds = parseValues(caster, SECONDS, level, 3.0); int ticks = (int) (seconds * 20); - for (LivingEntity target : targets) - { - FlagManager.addFlag(target, "perm:" + key, ticks); + for (LivingEntity target : targets) { + if (!target.hasPermission(key)) { + FlagManager.addFlag(target, "perm:" + key, ticks); + } } return targets.size() > 0; } diff --git a/src/com/sucy/skill/dynamic/mechanic/PotionMechanic.java b/src/com/sucy/skill/dynamic/mechanic/PotionMechanic.java index 217c49f5..b0ea7a03 100644 --- a/src/com/sucy/skill/dynamic/mechanic/PotionMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/PotionMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; @@ -36,13 +35,17 @@ /** * Executes child components after a delay */ -public class PotionMechanic extends EffectComponent -{ +public class PotionMechanic extends MechanicComponent { private static final String POTION = "potion"; private static final String AMBIENT = "ambient"; private static final String TIER = "tier"; private static final String SECONDS = "seconds"; + @Override + public String getKey() { + return "potion"; + } + /** * Executes the component * @@ -53,29 +56,24 @@ public class PotionMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } - try - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - PotionEffectType potion = PotionEffectType.getByName(settings.getString(POTION, "Absorption").toUpperCase().replace(' ', '_')); - int tier = (int) attr(caster, TIER, level, 1, isSelf) - 1; - double seconds = attr(caster, SECONDS, level, 3.0, isSelf); + try { + PotionEffectType potion = PotionEffectType.getByName(settings.getString(POTION, "Absorption") + .toUpperCase() + .replace(' ', '_')); + int tier = (int) parseValues(caster, TIER, level, 1) - 1; + double seconds = parseValues(caster, SECONDS, level, 3.0); boolean ambient = !settings.getString(AMBIENT, "true").equals("false"); int ticks = (int) (seconds * 20); - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { target.addPotionEffect(new PotionEffect(potion, ticks, tier, ambient), true); } return targets.size() > 0; - } - catch (Exception ex) - { + } catch (Exception ex) { return false; } } diff --git a/src/com/sucy/skill/dynamic/mechanic/PotionProjectileMechanic.java b/src/com/sucy/skill/dynamic/mechanic/PotionProjectileMechanic.java index cb0a7a29..d15ae205 100644 --- a/src/com/sucy/skill/dynamic/mechanic/PotionProjectileMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/PotionProjectileMechanic.java @@ -26,11 +26,13 @@ */ package com.sucy.skill.dynamic.mechanic; +import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.dynamic.TempEntity; -import com.sucy.skill.listener.MechanicListener; +import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LingeringPotion; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.ThrownPotion; import org.bukkit.inventory.ItemStack; @@ -43,16 +45,24 @@ import java.util.Collection; import java.util.List; +import static com.sucy.skill.listener.MechanicListener.POTION_PROJECTILE; +import static com.sucy.skill.listener.MechanicListener.SKILL_CASTER; +import static com.sucy.skill.listener.MechanicListener.SKILL_LEVEL; + /** * Heals each target */ -public class PotionProjectileMechanic extends EffectComponent +public class PotionProjectileMechanic extends MechanicComponent { private static final String POTION = "type"; private static final String ALLY = "group"; - private static final String LEVEL = "skill_level"; private static final String LINGER = "linger"; + @Override + public String getKey() { + return "potion projectile"; + } + /** * Executes the component * @@ -67,7 +77,7 @@ public boolean execute(LivingEntity caster, int level, List target { // Get common values String potion = settings.getString(POTION, "slowness").toUpperCase().replace(" ", "_"); - boolean linger = settings.getString(LINGER, "false").toLowerCase().equals("true"); + boolean linger = settings.getString(LINGER, "false").toLowerCase().equals("true") && VersionManager.isVersionAtLeast(VersionManager.V1_9_0); PotionType type; try { @@ -78,7 +88,7 @@ public boolean execute(LivingEntity caster, int level, List target return false; } - Potion p = new Potion(type, 1); + Potion p = new Potion(type, 1); ItemStack item; try { @@ -93,14 +103,16 @@ public boolean execute(LivingEntity caster, int level, List target { item = new ItemStack(Material.POTION); } + p.apply(item); // Fire from each target for (LivingEntity target : targets) { - ThrownPotion thrown = caster.launchProjectile(ThrownPotion.class); - SkillAPI.setMeta(thrown, LEVEL, level); - SkillAPI.setMeta(thrown, MechanicListener.POTION_PROJECTILE, this); + ThrownPotion thrown = target.launchProjectile(linger ? LingeringPotion.class : ThrownPotion.class); + SkillAPI.setMeta(thrown, SKILL_LEVEL, level); + SkillAPI.setMeta(thrown, SKILL_CASTER, caster); + SkillAPI.setMeta(thrown, POTION_PROJECTILE, this); thrown.setItem(item); } @@ -110,16 +122,18 @@ public boolean execute(LivingEntity caster, int level, List target /** * The callback for the projectiles that applies child components * - * @param projectile projectile calling back for - * @param hit the entity hit by the projectile, if any + * @param entity potion effect + * @param hit the entity hit by the projectile, if any */ - public void callback(ThrownPotion projectile, Collection hit) + public void callback(Entity entity, Collection hit) { ArrayList targets = new ArrayList(hit); String group = settings.getString(ALLY, "enemy").toLowerCase(); boolean both = group.equals("both"); boolean ally = group.equals("ally"); - LivingEntity caster = (LivingEntity) projectile.getShooter(); + LivingEntity caster = (LivingEntity) SkillAPI.getMeta(entity, SKILL_CASTER); + int level = SkillAPI.getMetaInt(entity, SKILL_LEVEL); + Location loc = entity.getLocation(); for (int i = 0; i < targets.size(); i++) { if (!both && SkillAPI.getSettings().canAttack(caster, targets.get(i)) == ally) @@ -130,9 +144,9 @@ public void callback(ThrownPotion projectile, Collection hit) } if (targets.size() == 0) { - LivingEntity loc = new TempEntity(projectile.getLocation()); - targets.add(loc); + LivingEntity locTarget = new TempEntity(loc); + targets.add(locTarget); } - executeChildren((LivingEntity) projectile.getShooter(), SkillAPI.getMetaInt(projectile, LEVEL), targets); + executeChildren(caster, level, targets); } } diff --git a/src/com/sucy/skill/dynamic/mechanic/ProjectileMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ProjectileMechanic.java index 3ad0fc31..ed0e0f00 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ProjectileMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ProjectileMechanic.java @@ -28,13 +28,19 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.projectile.CustomProjectile; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.dynamic.TempEntity; import com.sucy.skill.listener.MechanicListener; import com.sucy.skill.task.RemoveTask; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.entity.*; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Egg; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LargeFireball; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Snowball; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; @@ -45,7 +51,7 @@ /** * Heals each target */ -public class ProjectileMechanic extends EffectComponent +public class ProjectileMechanic extends MechanicComponent { private static final Vector UP = new Vector(0, 1, 0); @@ -64,6 +70,11 @@ public class ProjectileMechanic extends EffectComponent private static final String UPWARD = "upward"; private static final String FORWARD = "forward"; + @Override + public String getKey() { + return "projectile"; + } + /** * Executes the component * @@ -77,9 +88,9 @@ public class ProjectileMechanic extends EffectComponent public boolean execute(LivingEntity caster, int level, List targets) { // Get common values - int amount = (int) attr(caster, AMOUNT, level, 1.0, true); - double speed = attr(caster, SPEED, level, 2.0, true); - double range = attr(caster, RANGE, level, 999, true); + int amount = (int) parseValues(caster, AMOUNT, level, 1.0); + double speed = parseValues(caster, SPEED, level, 2.0); + double range = parseValues(caster, RANGE, level, 999); boolean flaming = settings.getString(FLAMING, "false").equalsIgnoreCase("true"); String spread = settings.getString(SPREAD, "cone").toLowerCase(); String projectile = settings.getString(PROJECTILE, "arrow").toLowerCase(); @@ -118,8 +129,8 @@ public boolean execute(LivingEntity caster, int level, List target // Apply the spread type if (spread.equals("rain")) { - double radius = attr(caster, RADIUS, level, 2.0, true); - double height = attr(caster, HEIGHT, level, 8.0, true); + double radius = parseValues(caster, RADIUS, level, 2.0); + double height = parseValues(caster, HEIGHT, level, 8.0); ArrayList locs = CustomProjectile.calcRain(target.getLocation(), radius, height, amount); for (Location loc : locs) @@ -140,10 +151,10 @@ public boolean execute(LivingEntity caster, int level, List target dir.setY(0); dir.normalize(); } - double angle = attr(caster, ANGLE, level, 30.0, true); - double right = attr(caster, RIGHT, level, 0, true); - double upward = attr(caster, UPWARD, level, 0, true); - double forward = attr(caster, FORWARD, level, 0, true); + double angle = parseValues(caster, ANGLE, level, 30.0); + double right = parseValues(caster, RIGHT, level, 0); + double upward = parseValues(caster, UPWARD, level, 0); + double forward = parseValues(caster, FORWARD, level, 0); Vector looking = target.getLocation().getDirection().setY(0).normalize(); Vector normal = looking.clone().crossProduct(UP); @@ -178,6 +189,7 @@ public boolean execute(LivingEntity caster, int level, List target */ public void callback(Projectile projectile, LivingEntity hit) { + if (hit == null) hit = new TempEntity(projectile.getLocation()); @@ -200,6 +212,15 @@ public void callback(Projectile projectile, LivingEntity hit) {{ put("arrow", Material.ARROW); put("egg", Material.EGG); - put("snowball", Material.SNOW_BALL); + put("snowball", snowBall()); }}; + + private static Material snowBall() { + for (Material material : Material.values()) { + if (material.name().startsWith("SNOW") && material.name().endsWith("BALL")) { + return material; + } + } + return Material.SNOW; + } } diff --git a/src/com/sucy/skill/dynamic/mechanic/PurgeMechanic.java b/src/com/sucy/skill/dynamic/mechanic/PurgeMechanic.java index 99e6eef7..74b216f0 100644 --- a/src/com/sucy/skill/dynamic/mechanic/PurgeMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/PurgeMechanic.java @@ -26,30 +26,34 @@ */ package com.sucy.skill.dynamic.mechanic; +import com.google.common.collect.ImmutableSet; import com.sucy.skill.api.util.FlagManager; import com.sucy.skill.api.util.StatusFlag; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.potion.PotionEffectType; import java.util.List; +import java.util.Set; /** * Purges a target of positive potion or status effects */ -public class PurgeMechanic extends EffectComponent +public class PurgeMechanic extends MechanicComponent { - private static final PotionEffectType[] POTIONS = new PotionEffectType[] { - PotionEffectType.ABSORPTION, PotionEffectType.DAMAGE_RESISTANCE, PotionEffectType.FAST_DIGGING, - PotionEffectType.FIRE_RESISTANCE, PotionEffectType.HEALTH_BOOST, PotionEffectType.INCREASE_DAMAGE, - PotionEffectType.INVISIBILITY, PotionEffectType.JUMP, PotionEffectType.NIGHT_VISION, - PotionEffectType.REGENERATION, PotionEffectType.SATURATION, PotionEffectType.SPEED, - PotionEffectType.WATER_BREATHING - }; + private static final Set POTIONS = ImmutableSet.of( + "ABSORPTION", "DAMAGE_RESISTANCE", "FAST_DIGGING", "FIRE_RESISTANCE", "HEALTH_BOOST", + "INCREASE_DAMAGE", "INVISIBILITY", "JUMP", "NIGHT_VISION", "REGENERATION", + "SATURATION", "SPEED", "WATER_BREATHING" + ); private static final String STATUS = "status"; private static final String POTION = "potion"; + @Override + public String getKey() { + return "purge"; + } + /** * Executes the component * @@ -96,9 +100,9 @@ else if (FlagManager.hasFlag(target, status)) if (potion.equals("ALL")) { - for (PotionEffectType p : POTIONS) + for (PotionEffectType p : PotionEffectType.values()) { - if (target.hasPotionEffect(p)) + if (target.hasPotionEffect(p) && POTIONS.contains(p.getName())) { target.removePotionEffect(p); worked = true; diff --git a/src/com/sucy/skill/dynamic/mechanic/PushMechanic.java b/src/com/sucy/skill/dynamic/mechanic/PushMechanic.java index 86fe00ce..133c2c9d 100644 --- a/src/com/sucy/skill/dynamic/mechanic/PushMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/PushMechanic.java @@ -26,7 +26,8 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.dynamic.target.RememberTarget; +import org.bukkit.Location; import org.bukkit.entity.LivingEntity; import org.bukkit.util.Vector; @@ -35,9 +36,14 @@ /** * Launches the target in a given direction relative to their forward direction */ -public class PushMechanic extends EffectComponent -{ - private static final String SPEED = "speed"; +public class PushMechanic extends MechanicComponent { + private static final String SPEED = "speed"; + private static final String SOURCE = "source"; + + @Override + public String getKey() { + return "push"; + } /** * Executes the component @@ -49,30 +55,27 @@ public class PushMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double speed = attr(caster, SPEED, level, 3.0, isSelf); + final double speed = parseValues(caster, SPEED, level, 3.0); + final String type = settings.getString("type", "scaled").toLowerCase(); + + final List sources = RememberTarget.remember(caster, settings.getString(SOURCE, "_none")); + final Location center = sources.isEmpty() ? caster.getLocation() : sources.get(0).getLocation(); + boolean worked = false; - String type = settings.getString("type", "scaled").toLowerCase(); - for (LivingEntity target : targets) - { - Vector vel = target.getLocation().subtract(caster.getLocation()).toVector(); - if (vel.lengthSquared() == 0) - { + for (LivingEntity target : targets) { + final Vector vel = target.getLocation().subtract(center).toVector(); + if (vel.lengthSquared() == 0) { continue; - } - if (type.equals("inverse")) - vel.multiply(speed); - else if (type.equals("fixed")) + } else if (type.equals("inverse")) { vel.multiply(speed); } else if (type.equals("fixed")) { vel.multiply(speed / vel.length()); - else // "scaled" + } else { // "scaled" vel.multiply(speed / vel.lengthSquared()); + } vel.setY(vel.getY() / 5 + 0.5); target.setVelocity(vel); worked = true; diff --git a/src/com/sucy/skill/dynamic/mechanic/RememberTargetsMechanic.java b/src/com/sucy/skill/dynamic/mechanic/RememberTargetsMechanic.java index 390ea3d0..1437692a 100644 --- a/src/com/sucy/skill/dynamic/mechanic/RememberTargetsMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/RememberTargetsMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -35,10 +34,15 @@ /** * Applies a flag to each target */ -public class RememberTargetsMechanic extends EffectComponent +public class RememberTargetsMechanic extends MechanicComponent { private static final String KEY = "key"; + @Override + public String getKey() { + return "remember targets"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/RepeatMechanic.java b/src/com/sucy/skill/dynamic/mechanic/RepeatMechanic.java index d628334d..dba3b7cb 100644 --- a/src/com/sucy/skill/dynamic/mechanic/RepeatMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/RepeatMechanic.java @@ -27,24 +27,24 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.SkillAPI; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.scheduler.BukkitRunnable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Executes child components multiple times */ -public class RepeatMechanic extends EffectComponent -{ - private static final String REPETITIONS = "repetitions"; - private static final String DELAY = "delay"; - private static final String PERIOD = "period"; +public class RepeatMechanic extends MechanicComponent { + private static final String REPETITIONS = "repetitions"; + private static final String DELAY = "delay"; + private static final String PERIOD = "period"; + private static final String STOP_ON_FAIL = "stop-on-fail"; - private static final HashMap> TASKS = new HashMap>(); + private final Map> tasks = new HashMap<>(); /** * Executes the component @@ -56,102 +56,86 @@ public class RepeatMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() > 0) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - int count = (int) attr(caster, REPETITIONS, level, 3.0, isSelf); - if (count <= 0) - { - return false; - } - int delay = (int) (settings.getDouble(DELAY, 0.0) * 20); - int period = (int) (settings.getDouble(PERIOD, 1.0) * 20); - RepeatTask task = new RepeatTask(caster, level, targets, count, delay, period); + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() > 0) { + final int count = (int) parseValues(caster, REPETITIONS, level, 3.0); + if (count <= 0) { return false; } - if (!TASKS.containsKey(skill.getName())) TASKS.put(skill.getName(), new ArrayList()); - TASKS.get(skill.getName()).add(task); + final int delay = (int) (settings.getDouble(DELAY, 0.0) * 20); + final int period = (int) (settings.getDouble(PERIOD, 1.0) * 20); + final boolean stopOnFail = settings.getBool(STOP_ON_FAIL, false); + final RepeatTask task = new RepeatTask(caster, targets, count, delay, period, stopOnFail); + tasks.computeIfAbsent(caster.getEntityId(), ArrayList::new).add(task); return true; } return false; } - /** - * Stops all repeat tasks for the player - * - * @param player player to cancel tasks for - * @param skill skill to cancel - */ - public static void stopTasks(LivingEntity player, String skill) - { - ArrayList tasks = TASKS.get(skill); - if (tasks == null) return; - for (int i = 0; i < tasks.size(); i++) - { - if (tasks.get(i).caster == player) - { - tasks.get(i).cancel(); - tasks.remove(i); - i--; - } - } + @Override + public String getKey() { + return "repeat"; } - /** - * Stops all passive tasks - */ - public static void stopAll() - { - for (ArrayList list : TASKS.values()) - for (RepeatTask task : list) - task.cancel(); - TASKS.clear(); + @Override + protected void doCleanUp(final LivingEntity caster) { + final List casterTasks = tasks.remove(caster.getEntityId()); + if (casterTasks != null) { + casterTasks.forEach(RepeatTask::cancel); + } } - private class RepeatTask extends BukkitRunnable - { - private List targets; - private LivingEntity caster; - private int level; - private int count; - - public RepeatTask(LivingEntity caster, int level, List targets, int count, int delay, int period) - { - this.targets = targets; + private class RepeatTask extends BukkitRunnable { + private final List targets; + private final LivingEntity caster; + private final boolean stopOnFail; + + private int count; + + RepeatTask( + LivingEntity caster, + List targets, + int count, + int delay, + int period, + boolean stopOnFail) { + this.targets = new ArrayList<>(targets); this.caster = caster; - this.level = level; this.count = count; + this.stopOnFail = stopOnFail; SkillAPI.schedule(this, delay, period); } @Override - public void run() - { - for (int i = 0; i < targets.size(); i++) - { - if (targets.get(i).isDead() || !targets.get(i).isValid()) - { - targets.remove(i); - } + public void cancel() { + super.cancel(); + final List casterTasks = tasks.get(caster.getEntityId()); + if (casterTasks != null) { + casterTasks.remove(this); } + } - if (!skill.isActive(caster) || targets.size() == 0) - { + @Override + public void run() { + for (int i = 0; i < targets.size(); i++) { + if (targets.get(i).isDead() || !targets.get(i).isValid()) { targets.remove(i); } + } + + if (!skill.isActive(caster) || targets.size() == 0) { cancel(); - TASKS.get(skill.getName()).remove(this); return; } - level = skill.getActiveLevel(caster); + final int level = skill.getActiveLevel(caster); + boolean success = executeChildren(caster, level, targets); + + if (--count <= 0 || (!success && stopOnFail)) { + cancel(); + } - executeChildren(caster, level, targets); - if (--count <= 0) - { + if (skill.checkCancelled()) { cancel(); - TASKS.get(skill.getName()).remove(this); } } } diff --git a/src/com/sucy/skill/dynamic/mechanic/SoundMechanic.java b/src/com/sucy/skill/dynamic/mechanic/SoundMechanic.java index 86deda14..ecaf0a9e 100644 --- a/src/com/sucy/skill/dynamic/mechanic/SoundMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/SoundMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.log.Logger; import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; @@ -36,13 +35,18 @@ /** * Plays a particle effect */ -public class SoundMechanic extends EffectComponent +public class SoundMechanic extends MechanicComponent { private static final String SOUND = "sound"; private static final String SOUND2 = "newsound"; private static final String VOLUME = "volume"; private static final String PITCH = "pitch"; + @Override + public String getKey() { + return "sound"; + } + /** * Executes the component * @@ -64,8 +68,8 @@ public boolean execute(LivingEntity caster, int level, List target try { Sound sound = Sound.valueOf(type); - float volume = (float) attr(caster, VOLUME, level, 100.0, true) / 100; - float pitch = (float) attr(caster, PITCH, level, 0.0, true); + float volume = (float) parseValues(caster, VOLUME, level, 100.0) / 100; + float pitch = (float) parseValues(caster, PITCH, level, 0.0); volume = Math.max(0, volume); pitch = Math.min(2, Math.max(0.5f, pitch)); diff --git a/src/com/sucy/skill/dynamic/mechanic/SpeedMechanic.java b/src/com/sucy/skill/dynamic/mechanic/SpeedMechanic.java index c29ecff7..bf476df9 100644 --- a/src/com/sucy/skill/dynamic/mechanic/SpeedMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/SpeedMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.listener.AttributeListener; import com.sucy.skill.listener.MechanicListener; import org.bukkit.entity.LivingEntity; @@ -38,11 +37,15 @@ /** * Applies a flag to each target */ -public class SpeedMechanic extends EffectComponent -{ +public class SpeedMechanic extends MechanicComponent { private static final String MULTIPLIER = "multiplier"; private static final String DURATION = "duration"; + @Override + public String getKey() { + return "speed"; + } + /** * Executes the component * @@ -53,16 +56,13 @@ public class SpeedMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - float multiplier = (float) attr(caster, MULTIPLIER, level, 1.2, isSelf); - double seconds = attr(caster, DURATION, level, 3.0, isSelf); + public boolean execute(LivingEntity caster, int level, List targets) { + float multiplier = (float) parseValues(caster, MULTIPLIER, level, 1.2); + double seconds = parseValues(caster, DURATION, level, 3.0); int ticks = (int) (seconds * 20); boolean worked = false; - for (LivingEntity target : targets) - { - if (!(target instanceof Player)) continue; + for (LivingEntity target : targets) { + if (!(target instanceof Player)) { continue; } AttributeListener.refreshSpeed((Player) target); FlagManager.addFlag(target, MechanicListener.SPEED_KEY, ticks); diff --git a/src/com/sucy/skill/dynamic/mechanic/StatusMechanic.java b/src/com/sucy/skill/dynamic/mechanic/StatusMechanic.java index c503cd69..c28a11c0 100644 --- a/src/com/sucy/skill/dynamic/mechanic/StatusMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/StatusMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.api.util.FlagManager; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -35,11 +34,15 @@ /** * Applies a flag to each target */ -public class StatusMechanic extends EffectComponent -{ +public class StatusMechanic extends MechanicComponent { private static final String KEY = "status"; private static final String DURATION = "duration"; + @Override + public String getKey() { + return "status"; + } + /** * Executes the component * @@ -50,19 +53,15 @@ public class StatusMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(KEY)) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(KEY)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(KEY, "stun").toLowerCase(); - double seconds = attr(caster, DURATION, level, 3.0, isSelf); + double seconds = parseValues(caster, DURATION, level, 3.0); int ticks = (int) (seconds * 20); - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { FlagManager.addFlag(target, key, ticks); } return targets.size() > 0; diff --git a/src/com/sucy/skill/dynamic/mechanic/TauntMechanic.java b/src/com/sucy/skill/dynamic/mechanic/TauntMechanic.java index aa5377ed..79ab390b 100644 --- a/src/com/sucy/skill/dynamic/mechanic/TauntMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/TauntMechanic.java @@ -26,7 +26,8 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.hook.MythicMobsHook; +import com.sucy.skill.hook.PluginChecker; import org.bukkit.entity.Creature; import org.bukkit.entity.LivingEntity; @@ -35,8 +36,15 @@ /** * Mechanic for taunting mobs */ -public class TauntMechanic extends EffectComponent +public class TauntMechanic extends MechanicComponent { + private static final String AMOUNT = "amount"; + + @Override + public String getKey() { + return "taunt"; + } + /** * Executes the component * @@ -49,12 +57,18 @@ public class TauntMechanic extends EffectComponent @Override public boolean execute(LivingEntity caster, int level, List targets) { + double amount = parseValues(caster, AMOUNT, level, 1); boolean taunted = false; for (LivingEntity entity : targets) { if (entity instanceof Creature && entity != caster) { - ((Creature) entity).setTarget(caster); + if (PluginChecker.isMythicMobsActive() && MythicMobsHook.isMonster(entity)) { + MythicMobsHook.taunt(entity, caster, amount); + } + else { + ((Creature) entity).setTarget(caster); + } taunted = true; } } diff --git a/src/com/sucy/skill/dynamic/mechanic/TriggerMechanic.java b/src/com/sucy/skill/dynamic/mechanic/TriggerMechanic.java new file mode 100644 index 00000000..47f4d113 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/TriggerMechanic.java @@ -0,0 +1,161 @@ +package com.sucy.skill.dynamic.mechanic; + +import com.google.common.base.Objects; +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.dynamic.ComponentRegistry; +import com.sucy.skill.dynamic.DynamicSkill; +import com.sucy.skill.dynamic.TriggerHandler; +import com.sucy.skill.dynamic.trigger.Trigger; +import com.sucy.skill.dynamic.trigger.TriggerComponent; +import org.bukkit.entity.LivingEntity; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * SkillAPI © 2017 + * com.sucy.skill.dynamic.mechanic.TriggerMechanic + */ +public class TriggerMechanic extends MechanicComponent { + + private static final String TRIGGER = "trigger"; + private static final String DURATION = "duration"; + private static final String STACKABLE = "stackable"; + private static final String ONCE = "once"; + + private final Map> CASTER_MAP = new HashMap>(); + + private TriggerHandler triggerHandler; + private boolean once; + private boolean stackable; + + @Override + public void load(final DynamicSkill skill, final DataSection dataSection) { + super.load(skill, dataSection); + + final String name = settings.getString(TRIGGER, "DEATH"); + final Trigger trigger = ComponentRegistry.getTrigger(name); + if (trigger == null) { + throw new IllegalArgumentException("Skill is using invalid trigger for mechanic: " + name); + } + + final Receiver receiver = new Receiver(); + triggerHandler = new TriggerHandler(skill, "fake", trigger, receiver); + triggerHandler.register(JavaPlugin.getPlugin(SkillAPI.class)); + once = settings.getBool(ONCE, true); + stackable = settings.getBool(STACKABLE, true); + } + + @Override + public String getKey() { + return "trigger"; + } + + @Override + public boolean execute( + final LivingEntity caster, final int level, final List targets) { + + final int ticks = (int)(20 * parseValues(caster, DURATION, level, 5)); + + boolean worked = false; + for (final LivingEntity target : targets) { + if (!stackable && CASTER_MAP.containsKey(target.getEntityId())) + return false; + + if (!CASTER_MAP.containsKey(target.getEntityId())) { + CASTER_MAP.put(target.getEntityId(), new ArrayList<>()); + } + triggerHandler.init(target, level); + + final Context context = new Context(caster, level); + CASTER_MAP.get(target.getEntityId()).add(context); + SkillAPI.schedule(new StopTask(target, context), ticks); + worked = true; + } + return worked; + } + + private void remove(final LivingEntity target, final Context context) { + final List contexts = CASTER_MAP.get(target.getEntityId()); + if (contexts == null) return; + + contexts.remove(context); + if (contexts.isEmpty()) { + CASTER_MAP.remove(target.getEntityId()); + triggerHandler.cleanup(target); + } + } + + private class StopTask implements Runnable { + + private final LivingEntity target; + private final Context context; + + public StopTask(final LivingEntity target, final Context context) { + this.target = target; + this.context = context; + } + + @Override + public void run() { + remove(target, context); + } + } + + private class Receiver extends TriggerComponent { + + private Receiver() { + final DataSection data = new DataSection(); + TriggerMechanic.this.settings.save(data); + this.settings.load(data); + } + + @Override + public boolean execute(final LivingEntity target, final int level, final List targets) { + if (!CASTER_MAP.containsKey(target.getEntityId())) return false; + + final List contexts; + if (once) + contexts = CASTER_MAP.remove(target.getEntityId()); + else + contexts = CASTER_MAP.get(target.getEntityId()); + + final List targetList = new ArrayList(); + targetList.add(target); + + for (final Context context : contexts) { + DynamicSkill.getCastData(context.caster).put("listen-target", targetList); + TriggerMechanic.this.executeChildren(context.caster, context.level, targets); + } + + return true; + } + } + + private static class Context { + public final LivingEntity caster; + public final int level; + + public Context(final LivingEntity caster, final int level) { + this.caster = caster; + this.level = level; + } + + @Override + public boolean equals(final Object other) { + if (other == this) return true; + if (!(other instanceof Context)) return false; + final Context context = (Context) other; + return context.caster == caster && context.level == level; + } + + @Override + public int hashCode() { + return Objects.hashCode(caster, level); + } + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueAddMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueAddMechanic.java index 58edbf62..8f3ab376 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ValueAddMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ValueAddMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.HashMap; @@ -36,11 +35,15 @@ /** * Adds to a cast data value */ -public class ValueAddMechanic extends EffectComponent -{ +public class ValueAddMechanic extends MechanicComponent { private static final String KEY = "key"; private static final String AMOUNT = "amount"; + @Override + public String getKey() { + return "value add"; + } + /** * Executes the component * @@ -51,19 +54,15 @@ public class ValueAddMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(KEY)) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(KEY)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(KEY); - double amount = attr(caster, AMOUNT, level, 1, isSelf); + double amount = parseValues(caster, AMOUNT, level, 1) * targets.size(); HashMap data = DynamicSkill.getCastData(caster); - if (!data.containsKey(key)) data.put(key, amount); - else data.put(key, amount + (Double) data.get(key)); + if (!data.containsKey(key)) { data.put(key, amount); } else { data.put(key, amount + (Double) data.get(key)); } return true; } } diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueAttributeMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueAttributeMechanic.java index 9875996e..6c6d77ed 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ValueAttributeMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ValueAttributeMechanic.java @@ -28,7 +28,6 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -38,11 +37,16 @@ /** * Adds to a cast data value */ -public class ValueAttributeMechanic extends EffectComponent +public class ValueAttributeMechanic extends MechanicComponent { private static final String KEY = "key"; private static final String ATTR = "attribute"; + @Override + public String getKey() { + return "value attribute"; + } + /** * Executes the component * @@ -55,7 +59,7 @@ public class ValueAttributeMechanic extends EffectComponent @Override public boolean execute(LivingEntity caster, int level, List targets) { - if (!settings.has(KEY) || !settings.has(ATTR) || !(caster instanceof Player)) + if (!settings.has(KEY) || !settings.has(ATTR) || !(targets.get(0) instanceof Player)) { return false; } @@ -63,7 +67,7 @@ public boolean execute(LivingEntity caster, int level, List target String key = settings.getString(KEY); String attr = settings.getString(ATTR); HashMap data = DynamicSkill.getCastData(caster); - data.put(key, (double) SkillAPI.getPlayerData((Player) caster).getAttribute(attr)); + data.put(key, (double) SkillAPI.getPlayerData((Player) targets.get(0)).getAttribute(attr)); return true; } } diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueCopyMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueCopyMechanic.java new file mode 100644 index 00000000..7bea3b83 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/ValueCopyMechanic.java @@ -0,0 +1,49 @@ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.mechanic.ValueCopyMechanic + */ +public class ValueCopyMechanic extends MechanicComponent { + private static final String KEY = "key"; + private static final String TARGET = "destination"; + private static final String TO_TARGET = "to-target"; + + @Override + public String getKey() { + return "value copy"; + } + + @Override + public boolean execute( + final LivingEntity caster, final int level, final List targets) { + + if (targets.size() == 0 || !settings.has(KEY)) { + return false; + } + + final String key = settings.getString(KEY); + final String destination = settings.getString(TARGET, key); + final boolean toTarget = settings.getString(TO_TARGET, "true").equalsIgnoreCase("true"); + + if (toTarget) { + targets.forEach(target -> apply(caster, target, key, destination)); + } else { + apply(targets.get(0), caster, key, destination); + } + + return true; + } + + private boolean apply(final LivingEntity from, final LivingEntity to, final String key, final String destination) { + final Object value = DynamicSkill.getCastData(from).get(key); + if (value == null) return false; + DynamicSkill.getCastData(to).put(destination, value); + return true; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueDistanceMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueDistanceMechanic.java new file mode 100644 index 00000000..42109e67 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/ValueDistanceMechanic.java @@ -0,0 +1,45 @@ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.List; + +/** + * SkillAPI © 2017 + * com.sucy.skill.dynamic.mechanic.ValueDistanceMechanic + */ +public class ValueDistanceMechanic extends MechanicComponent +{ + private static final String KEY = "key"; + + @Override + public String getKey() { + return "value distance"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(final LivingEntity caster, final int level, final List targets) + { + if (!settings.has(KEY) || !(caster instanceof Player)) + { + return false; + } + + final String key = settings.getString(KEY); + final HashMap data = DynamicSkill.getCastData(caster); + data.put(key, targets.get(0).getLocation().distance(caster.getLocation())); + return true; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueHealthMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueHealthMechanic.java new file mode 100644 index 00000000..e81f30af --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/ValueHealthMechanic.java @@ -0,0 +1,77 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.mechanic.ValueHealth + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.dynamic.DynamicSkill; + +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.LivingEntity; + +import java.util.HashMap; +import java.util.List; + +public class ValueHealthMechanic extends MechanicComponent { + private static final String KEY = "key"; + private static final String TYPE = "type"; + + @Override + public String getKey() { + return "value health"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + final String key = settings.getString(KEY); + final String type = settings.getString(TYPE, "current").toLowerCase(); + final HashMap data = DynamicSkill.getCastData(caster); + + final LivingEntity target = targets.get(0); + switch (type) { + case "max": + data.put(key, target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + break; + case "percent": + data.put(key, target.getHealth() / target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + break; + case "missing": + data.put(key, target.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() - target.getHealth()); + break; + default: // current + data.put(key, target.getHealth()); + } + return true; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueLocationMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueLocationMechanic.java index 39001cd1..90a6aec1 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ValueLocationMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ValueLocationMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.HashMap; @@ -36,10 +35,15 @@ /** * Adds to a cast data value */ -public class ValueLocationMechanic extends EffectComponent +public class ValueLocationMechanic extends MechanicComponent { private static final String KEY = "key"; + @Override + public String getKey() { + return "value location"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueLoreMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueLoreMechanic.java index 6d4811fd..b3386e1c 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ValueLoreMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ValueLoreMechanic.java @@ -27,28 +27,26 @@ package com.sucy.skill.dynamic.mechanic; import com.rit.sucy.version.VersionManager; -import com.sucy.skill.api.util.NumberParser; -import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; -import org.bukkit.ChatColor; +import com.sucy.skill.dynamic.ItemChecker; import org.bukkit.entity.LivingEntity; import org.bukkit.inventory.ItemStack; -import java.util.HashMap; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Adds to a cast data value */ -public class ValueLoreMechanic extends EffectComponent -{ +public class ValueLoreMechanic extends MechanicComponent { private static final String KEY = "key"; private static final String REGEX = "regex"; private static final String MULTIPLIER = "multiplier"; private static final String HAND = "hand"; + @Override + public String getKey() { + return "value lore"; + } + /** * Executes the component * @@ -59,55 +57,21 @@ public class ValueLoreMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(KEY)) - { - return false; - } + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(KEY)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(KEY); - HashMap data = DynamicSkill.getCastData(caster); - double multiplier = attr(caster, MULTIPLIER, level, 1, isSelf); - boolean offhand = VersionManager.isVersionAtLeast(VersionManager.V1_9_0) - && settings.getString(HAND).equalsIgnoreCase("offhand"); - + double multiplier = parseValues(caster, MULTIPLIER, level, 1); + boolean offhand = settings.getString(HAND, "").equalsIgnoreCase("offhand"); String regex = settings.getString(REGEX, "Damage: {value}"); - regex = regex.replace("{value}", "([0-9]+)"); - Pattern pattern = Pattern.compile(regex); - if (caster.getEquipment() == null) - return false; + if (caster.getEquipment() == null) { return false; } ItemStack hand; - if (offhand) + if (offhand && VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { hand = caster.getEquipment().getItemInOffHand(); - else hand = caster.getEquipment().getItemInHand(); - - if (hand == null || !hand.hasItemMeta() || !hand.getItemMeta().hasLore()) - return false; - - List lore = hand.getItemMeta().getLore(); - for (String line : lore) - { - line = ChatColor.stripColor(line); - Matcher matcher = pattern.matcher(line); - if (matcher.find()) - { - String value = matcher.group(1); - try - { - double base = NumberParser.parseDouble(value); - data.put(key, base * multiplier); - } - catch (Exception ex) - { - // Not a valid value - } - } - } + } else { hand = caster.getEquipment().getItemInMainHand(); } - return true; + return ItemChecker.findLore(caster, hand, regex, key, multiplier); } } diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueLoreSlotMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueLoreSlotMechanic.java new file mode 100644 index 00000000..2179eae0 --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/ValueLoreSlotMechanic.java @@ -0,0 +1,69 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.mechanic.ValueLoreSlot + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.dynamic.ItemChecker; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class ValueLoreSlotMechanic extends MechanicComponent { + private static final String KEY = "key"; + private static final String REGEX = "regex"; + private static final String MULTIPLIER = "multiplier"; + private static final String SLOT = "slot"; + + @Override + public String getKey() { + return "value lore slot"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(KEY) || !(caster instanceof Player)) { return false; } + + String key = settings.getString(KEY); + double multiplier = parseValues(caster, MULTIPLIER, level, 1); + int slot = settings.getInt(SLOT); + String regex = settings.getString(REGEX, "Damage: {value}"); + + ItemStack item = ((Player) caster).getInventory().getItem(slot); + + return ItemChecker.findLore(caster, item, regex, key, multiplier); + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueManaMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueManaMechanic.java new file mode 100644 index 00000000..b34ee25a --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/ValueManaMechanic.java @@ -0,0 +1,78 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.mechanic.ValueMana + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.List; + +public class ValueManaMechanic extends MechanicComponent +{ + private static final String KEY = "key"; + private static final String TYPE = "type"; + + @Override + public String getKey() { + return "value mana"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + if (!(targets.get(0) instanceof Player)) return false; + + final PlayerData player = SkillAPI.getPlayerData((Player)targets.get(0)); + final String key = settings.getString(KEY); + final String type = settings.getString(TYPE, "current").toLowerCase(); + final HashMap data = DynamicSkill.getCastData(caster); + + switch (type) { + case "max": + data.put(key, player.getMaxMana()); + case "percent": + data.put(key, player.getMana() / player.getMaxMana()); + case "missing": + data.put(key, player.getMaxMana() - player.getMana()); + default: // current + data.put(key, player.getMana()); + } + return true; + } +} \ No newline at end of file diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueMultiplyMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueMultiplyMechanic.java index b2f82eb4..f9fec5db 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ValueMultiplyMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ValueMultiplyMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.HashMap; @@ -36,11 +35,15 @@ /** * Adds to a cast data value */ -public class ValueMultiplyMechanic extends EffectComponent -{ +public class ValueMultiplyMechanic extends MechanicComponent { private static final String KEY = "key"; private static final String MULTIPLIER = "multiplier"; + @Override + public String getKey() { + return "value multiply"; + } + /** * Executes the component * @@ -51,19 +54,15 @@ public class ValueMultiplyMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(KEY)) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(KEY)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(KEY); - double multiplier = attr(caster, MULTIPLIER, level, 1, isSelf); + double multiplier = parseValues(caster, MULTIPLIER, level, 1); HashMap data = DynamicSkill.getCastData(caster); - if (data.containsKey(key)) - data.put(key, multiplier * (Double) data.get(key)); + if (data.containsKey(key)) { data.put(key, multiplier * (Double) data.get(key)); } return true; } } diff --git a/src/com/sucy/skill/dynamic/mechanic/ValuePlaceholderMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValuePlaceholderMechanic.java new file mode 100644 index 00000000..ce417aea --- /dev/null +++ b/src/com/sucy/skill/dynamic/mechanic/ValuePlaceholderMechanic.java @@ -0,0 +1,67 @@ +package com.sucy.skill.dynamic.mechanic; + +import com.sucy.skill.dynamic.DynamicSkill; +import com.sucy.skill.hook.PlaceholderAPIHook; +import com.sucy.skill.hook.PluginChecker; +import com.sucy.skill.log.Logger; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.mechanic.ValuePlaceholderMechanic + */ +public class ValuePlaceholderMechanic extends MechanicComponent +{ + private static final String KEY = "key"; + private static final String TYPE = "type"; + private static final String PLACEHOLDER = "placeholder"; + + @Override + public String getKey() { + return "value placeholder"; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) + { + if (!PluginChecker.isPlaceholderAPIActive()) { + return false; + } + + if (targets.get(0) instanceof Player) + { + final String key = settings.getString(KEY); + final String placeholder = settings.getString(PLACEHOLDER); + final String type = settings.getString(TYPE).toUpperCase(); + + final String value = PlaceholderAPIHook.format(placeholder, (Player)targets.get(0)); + + switch (type.charAt(0)) { + case 'S': // STRING + DynamicSkill.getCastData(caster).put(key, value); + break; + default: // NUMBER + try { + DynamicSkill.getCastData(caster).put(key, Double.parseDouble(value)); + } catch (final Exception ex) { + Logger.invalid(placeholder + " is not a valid numeric placeholder - PlaceholderAPI returned " + value); + return false; + } + } + return true; + } + return false; + } +} diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueRandomMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueRandomMechanic.java index 3f055269..d575935b 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ValueRandomMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ValueRandomMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.HashMap; @@ -36,13 +35,17 @@ /** * Adds to a cast data value */ -public class ValueRandomMechanic extends EffectComponent -{ +public class ValueRandomMechanic extends MechanicComponent { private static final String KEY = "key"; private static final String TYPE = "type"; private static final String MIN = "min"; private static final String MAX = "max"; + @Override + public String getKey() { + return "value random"; + } + /** * Executes the component * @@ -53,18 +56,15 @@ public class ValueRandomMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(KEY)) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(KEY)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(KEY); boolean triangular = settings.getString(TYPE).toUpperCase().equals("triangular"); - double min = attr(caster, MIN, level, 1, isSelf); - double max = attr(caster, MAX, level, 1, isSelf); + double min = parseValues(caster, MIN, level, 1); + double max = parseValues(caster, MAX, level, 1); HashMap data = DynamicSkill.getCastData(caster); double rand = triangular ? 0.5 * (Math.random() + Math.random()) : Math.random(); diff --git a/src/com/sucy/skill/dynamic/mechanic/ValueSetMechanic.java b/src/com/sucy/skill/dynamic/mechanic/ValueSetMechanic.java index 0d0a13f0..4f1c2152 100644 --- a/src/com/sucy/skill/dynamic/mechanic/ValueSetMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/ValueSetMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.HashMap; @@ -36,11 +35,15 @@ /** * Adds to a cast data value */ -public class ValueSetMechanic extends EffectComponent -{ +public class ValueSetMechanic extends MechanicComponent { private static final String KEY = "key"; private static final String VALUE = "value"; + @Override + public String getKey() { + return "value set"; + } + /** * Executes the component * @@ -51,16 +54,13 @@ public class ValueSetMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0 || !settings.has(KEY)) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0 || !settings.has(KEY)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; String key = settings.getString(KEY); - double value = attr(caster, VALUE, level, 1, isSelf); + double value = parseValues(caster, VALUE, level, 1); HashMap data = DynamicSkill.getCastData(caster); data.put(key, value); return true; diff --git a/src/com/sucy/skill/dynamic/mechanic/WarpLocMechanic.java b/src/com/sucy/skill/dynamic/mechanic/WarpLocMechanic.java index cc5114af..ff4bbdc9 100644 --- a/src/com/sucy/skill/dynamic/mechanic/WarpLocMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/WarpLocMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -37,7 +36,7 @@ /** * Strikes lightning about each target with an offset */ -public class WarpLocMechanic extends EffectComponent +public class WarpLocMechanic extends MechanicComponent { private static final String WORLD = "world"; private static final String X = "x"; @@ -46,6 +45,11 @@ public class WarpLocMechanic extends EffectComponent private static final String YAW = "yaw"; private static final String PITCH = "pitch"; + @Override + public String getKey() { + return "warp location"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/WarpMechanic.java b/src/com/sucy/skill/dynamic/mechanic/WarpMechanic.java index 30b7d418..173541ab 100644 --- a/src/com/sucy/skill/dynamic/mechanic/WarpMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/WarpMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.rit.sucy.player.TargetHelper; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Location; import org.bukkit.block.BlockFace; import org.bukkit.entity.LivingEntity; @@ -38,8 +37,7 @@ /** * Strikes lightning about each target with an offset */ -public class WarpMechanic extends EffectComponent -{ +public class WarpMechanic extends MechanicComponent { private static final Vector UP = new Vector(0, 1, 0); private static final String WALL = "walls"; @@ -47,6 +45,11 @@ public class WarpMechanic extends EffectComponent private static final String UPWARD = "upward"; private static final String RIGHT = "right"; + @Override + public String getKey() { + return "warp"; + } + /** * Executes the component * @@ -57,28 +60,23 @@ public class WarpMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } // Get the world - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; boolean throughWalls = settings.getString(WALL, "false").toLowerCase().equals("true"); - double forward = attr(caster, FORWARD, level, 0.0, isSelf); - double upward = attr(caster, UPWARD, level, 0.0, isSelf); - double right = attr(caster, RIGHT, level, 0.0, isSelf); + double forward = parseValues(caster, FORWARD, level, 0.0); + double upward = parseValues(caster, UPWARD, level, 0.0); + double right = parseValues(caster, RIGHT, level, 0.0); - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { Vector dir = target.getLocation().getDirection(); Vector side = dir.clone().crossProduct(UP).multiply(right); Location loc = target.getLocation().add(dir.multiply(forward)).add(side).add(0, upward, 0).add(0, 1, 0); loc = TargetHelper.getOpenLocation(target.getLocation().add(0, 1, 0), loc, throughWalls); - if (!loc.getBlock().getType().isSolid() && loc.getBlock().getRelative(BlockFace.DOWN).getType().isSolid()) - { + if (!loc.getBlock().getType().isSolid() && loc.getBlock().getRelative(BlockFace.DOWN).getType().isSolid()) { loc.add(0, 1, 0); } target.teleport(loc.subtract(0, 1, 0)); diff --git a/src/com/sucy/skill/dynamic/mechanic/WarpRandomMechanic.java b/src/com/sucy/skill/dynamic/mechanic/WarpRandomMechanic.java index 269020cf..b6173160 100644 --- a/src/com/sucy/skill/dynamic/mechanic/WarpRandomMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/WarpRandomMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.rit.sucy.player.TargetHelper; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Location; import org.bukkit.block.BlockFace; import org.bukkit.entity.LivingEntity; @@ -38,14 +37,18 @@ /** * Strikes lightning about each target with an offset */ -public class WarpRandomMechanic extends EffectComponent -{ +public class WarpRandomMechanic extends MechanicComponent { private static final Random random = new Random(); private static final String WALL = "walls"; private static final String HORIZONTAL = "horizontal"; private static final String DISTANCE = "distance"; + @Override + public String getKey() { + return "warp random"; + } + /** * Executes the component * @@ -56,35 +59,28 @@ public class WarpRandomMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (targets.size() == 0) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (targets.size() == 0) { return false; } // Get the world - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; boolean throughWalls = settings.getString(WALL, "false").toLowerCase().equals("true"); boolean horizontal = !settings.getString(HORIZONTAL, "true").toLowerCase().equals("false"); - double distance = attr(caster, DISTANCE, level, 3.0, isSelf); + double distance = parseValues(caster, DISTANCE, level, 3.0); - for (LivingEntity target : targets) - { + for (LivingEntity target : targets) { Location loc; Location temp = target.getLocation(); - do - { + do { loc = temp.clone().add(rand(distance), 0, rand(distance)); - if (!horizontal) - { + if (!horizontal) { loc.add(0, rand(distance), 0); } } while (temp.distanceSquared(loc) > distance * distance); loc = TargetHelper.getOpenLocation(target.getLocation().add(0, 1, 0), loc, throughWalls); - if (!loc.getBlock().getType().isSolid() && loc.getBlock().getRelative(BlockFace.DOWN).getType().isSolid()) - { + if (!loc.getBlock().getType().isSolid() && loc.getBlock().getRelative(BlockFace.DOWN).getType().isSolid()) { loc.add(0, 1, 0); } target.teleport(loc.subtract(0, 1, 0)); @@ -92,8 +88,7 @@ public boolean execute(LivingEntity caster, int level, List target return targets.size() > 0; } - private double rand(double distance) - { + private double rand(double distance) { return random.nextDouble() * distance * 2 - distance; } } diff --git a/src/com/sucy/skill/dynamic/mechanic/WarpSwapMechanic.java b/src/com/sucy/skill/dynamic/mechanic/WarpSwapMechanic.java index cd13d143..5e8f0232 100644 --- a/src/com/sucy/skill/dynamic/mechanic/WarpSwapMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/WarpSwapMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; @@ -35,8 +34,13 @@ /** * Strikes lightning about each target with an offset */ -public class WarpSwapMechanic extends EffectComponent +public class WarpSwapMechanic extends MechanicComponent { + @Override + public String getKey() { + return "warp swap"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/WarpTargetMechanic.java b/src/com/sucy/skill/dynamic/mechanic/WarpTargetMechanic.java index adeb49ea..2e7cffde 100644 --- a/src/com/sucy/skill/dynamic/mechanic/WarpTargetMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/WarpTargetMechanic.java @@ -26,7 +26,6 @@ */ package com.sucy.skill.dynamic.mechanic; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; import java.util.List; @@ -34,10 +33,15 @@ /** * Strikes lightning about each target with an offset */ -public class WarpTargetMechanic extends EffectComponent +public class WarpTargetMechanic extends MechanicComponent { private static final String TYPE = "type"; + @Override + public String getKey() { + return "warp target"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/WarpValueMechanic.java b/src/com/sucy/skill/dynamic/mechanic/WarpValueMechanic.java index 5ef64a21..7c593267 100644 --- a/src/com/sucy/skill/dynamic/mechanic/WarpValueMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/WarpValueMechanic.java @@ -27,7 +27,6 @@ package com.sucy.skill.dynamic.mechanic; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; @@ -37,10 +36,15 @@ /** * Applies a flag to each target */ -public class WarpValueMechanic extends EffectComponent +public class WarpValueMechanic extends MechanicComponent { private static final String KEY = "key"; + @Override + public String getKey() { + return "warp value"; + } + /** * Executes the component * diff --git a/src/com/sucy/skill/dynamic/mechanic/WolfMechanic.java b/src/com/sucy/skill/dynamic/mechanic/WolfMechanic.java index 14e89665..f700c11c 100644 --- a/src/com/sucy/skill/dynamic/mechanic/WolfMechanic.java +++ b/src/com/sucy/skill/dynamic/mechanic/WolfMechanic.java @@ -31,33 +31,35 @@ import com.sucy.skill.api.skills.PassiveSkill; import com.sucy.skill.api.skills.Skill; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import com.sucy.skill.listener.MechanicListener; import com.sucy.skill.task.RemoveTask; import org.bukkit.DyeColor; +import org.bukkit.attribute.Attribute; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Wolf; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Applies a flag to each target */ -public class WolfMechanic extends EffectComponent -{ - public static final String SKILL_META = "sapi_wolf_skills"; - public static final String LEVEL = "sapi_wolf_level"; - - private static final ArrayList tasks = new ArrayList(); - - private static final String COLOR = "color"; - private static final String HEALTH = "health"; - private static final String SECONDS = "seconds"; - private static final String NAME = "name"; - private static final String DAMAGE = "damage"; - private static final String SKILLS = "skills"; +public class WolfMechanic extends MechanicComponent { + public static final String SKILL_META = "sapi_wolf_skills"; + public static final String LEVEL = "sapi_wolf_level"; + private static final String COLOR = "color"; + private static final String HEALTH = "health"; + private static final String SECONDS = "seconds"; + private static final String NAME = "name"; + private static final String DAMAGE = "damage"; + private static final String SKILLS = "skills"; + private static final String AMOUNT = "amount"; + private static final String SITTING = "sitting"; + + private final Map tasks = new HashMap<>(); /** * Executes the component @@ -69,111 +71,90 @@ public class WolfMechanic extends EffectComponent * @return true if applied to something, false otherwise */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - if (!(caster instanceof Player)) - { + public boolean execute(LivingEntity caster, int level, List targets) { + if (!(caster instanceof Player)) { return false; } - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; + cleanUp(caster); + + final Player player = (Player) caster; + String color = settings.getString(COLOR); - double health = attr(caster, HEALTH, level, 10.0, isSelf); - String name = TextFormatter.colorString(settings.getString(NAME, "").replace("{player}", caster.getName())); - double damage = attr(caster, DAMAGE, level, 3.0, isSelf); + double health = parseValues(player, HEALTH, level, 10.0); + String name = TextFormatter.colorString(settings.getString(NAME, "").replace("{player}", player.getName())); + double damage = parseValues(player, DAMAGE, level, 3.0); + double amount = parseValues(player, AMOUNT, level, 1.0); + boolean sitting = settings.getString(SITTING, "false").equalsIgnoreCase("true"); List skills = settings.getStringList(SKILLS); DyeColor dye = null; - if (color != null) - { - try - { + if (color != null) { + try { dye = DyeColor.valueOf(color); - } - catch (Exception ex) - { /* Invalid color */ } + } catch (Exception ex) { /* Invalid color */ } } - double seconds = attr(caster, SECONDS, level, 10.0, isSelf); + double seconds = parseValues(player, SECONDS, level, 10.0); int ticks = (int) (seconds * 20); - ArrayList wolves = new ArrayList(); - for (LivingEntity target : targets) - { - Wolf wolf = target.getWorld().spawn(target.getLocation(), Wolf.class); - wolf.setOwner((Player) caster); - wolf.setMaxHealth(health); - wolf.setHealth(health); - SkillAPI.setMeta(wolf, MechanicListener.SUMMON_DAMAGE, damage); - - List owner = new ArrayList(1); - owner.add(caster); - DynamicSkill.getCastData(wolf).put("api-owner", owner); - - if (dye != null) - { - wolf.setCollarColor(dye); - } - if (name.length() > 0) - { - wolf.setCustomName(name); - wolf.setCustomNameVisible(true); - } + List wolves = new ArrayList<>(); + for (LivingEntity target : targets) { + for (int i = 0; i < amount; i++) { + Wolf wolf = target.getWorld().spawn(target.getLocation(), Wolf.class); + wolf.setOwner(player); + wolf.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(health); + wolf.setHealth(health); + wolf.setSitting(sitting); + SkillAPI.setMeta(wolf, MechanicListener.SUMMON_DAMAGE, damage); + + List owner = new ArrayList<>(1); + owner.add(player); + DynamicSkill.getCastData(wolf).put("api-owner", owner); + + if (dye != null) { + wolf.setCollarColor(dye); + } + if (name.length() > 0) { + wolf.setCustomName(name); + wolf.setCustomNameVisible(true); + } - // Setup skills - for (String skillName : skills) - { - Skill skill = SkillAPI.getSkill(skillName); - if (skill instanceof PassiveSkill) - { - ((PassiveSkill) skill).initialize(wolf, level); + // Setup skills + for (String skillName : skills) { + Skill skill = SkillAPI.getSkill(skillName); + if (skill instanceof PassiveSkill) { + ((PassiveSkill) skill).initialize(wolf, level); + } } - } - SkillAPI.setMeta(wolf, SKILL_META, skills); - SkillAPI.setMeta(wolf, LEVEL, level); + SkillAPI.setMeta(wolf, SKILL_META, skills); + SkillAPI.setMeta(wolf, LEVEL, level); - RemoveTask task = new RemoveTask(wolf, ticks); - tasks.add(task); - wolves.add(wolf); + wolves.add(wolf); + } } + final RemoveTask task = new RemoveTask(wolves, ticks); + tasks.put(caster.getEntityId(), task); + // Apply children to the wolves - if (wolves.size() > 0) - { - executeChildren(caster, level, wolves); + if (wolves.size() > 0) { + executeChildren(player, level, wolves); return true; } return false; } - /** - * Removes all of the currently summoned wolves. This would be used - * to clean up before the plugin is disabled or the server is shut down. - */ - public static void removeWolves() - { - for (RemoveTask task : tasks) - { - task.run(); - task.cancel(); - } - tasks.clear(); + @Override + public String getKey() { + return "wolf"; } - /** - * Removes any wolves summoned by the given player - * - * @param player player to desummon wolves for - */ - public static void removeWolves(Player player) - { - for (RemoveTask task : tasks) - { - if (task.isOwnedBy(player)) - { - task.run(); - task.cancel(); - tasks.remove(task); - } + @Override + public void cleanUp(final LivingEntity caster) { + final RemoveTask task = tasks.remove(caster.getEntityId()); + if (task != null) { + task.cancel(); + task.run(); } } } diff --git a/src/com/sucy/skill/dynamic/target/AreaTarget.java b/src/com/sucy/skill/dynamic/target/AreaTarget.java index 2234b4bc..c52b8d72 100644 --- a/src/com/sucy/skill/dynamic/target/AreaTarget.java +++ b/src/com/sucy/skill/dynamic/target/AreaTarget.java @@ -26,87 +26,53 @@ */ package com.sucy.skill.dynamic.target; -import com.rit.sucy.player.TargetHelper; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.dynamic.EffectComponent; -import com.sucy.skill.dynamic.TempEntity; -import org.bukkit.Location; -import org.bukkit.entity.Entity; +import com.sucy.skill.api.util.Nearby; +import com.sucy.skill.cast.IIndicator; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; +import java.util.Random; /** * Applies child components to the closest all nearby entities around * each of the current targets. */ -public class AreaTarget extends EffectComponent -{ +public class AreaTarget extends TargetComponent { private static final String RADIUS = "radius"; - private static final String ALLY = "group"; - private static final String MAX = "max"; - private static final String WALL = "wall"; - private static final String CASTER = "caster"; + private static final String RANDOM = "random"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + private final Random random = new Random(); + + /** {@inheritDoc} */ + @Override + List getTargets( + final LivingEntity caster, final int level, final List targets) { + + final double radius = parseValues(caster, RADIUS, level, 3.0); + final boolean random = settings.getBool(RANDOM, false); + return determineTargets(caster, level, targets, t -> shuffle(Nearby.getLivingNearby(t.getLocation(), radius), random)); + } + + /** {@inheritDoc} */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double radius = attr(caster, RADIUS, level, 3.0, isSelf); - String group = settings.getString(ALLY, "enemy").toLowerCase(); - boolean both = group.equals("both"); - boolean ally = group.equals("ally"); - boolean throughWall = settings.getString(WALL, "false").toLowerCase().equals("true"); - boolean self = settings.getString(CASTER, "false").toLowerCase().equals("true"); - double max = attr(caster, MAX, level, 99, isSelf); - double radSq = radius * radius; + void makeIndicators(final List list, final Player caster, final LivingEntity target, final int level) { + makeCircleIndicator(list, target, parseValues(caster, RADIUS, level, 3.0)); + } + + @Override + public String getKey() { + return "area"; + } - ArrayList list = new ArrayList(); - for (LivingEntity t : targets) - { - List entities = t.getNearbyEntities(radius, radius, radius); - if (t != caster && !(t instanceof TempEntity) && (both || SkillAPI.getSettings().isAlly(caster, t) == ally)) - { - list.add(t); - } - if (self) - { - list.add(caster); - } + private List shuffle(final List targets, final boolean random) { + if (!random) return targets; - Location wallCheckLoc = t.getLocation().add(0, 0.5, 0); - for (int i = 0; i < entities.size() && list.size() < max; i++) - { - if (entities.get(i) instanceof LivingEntity) - { - LivingEntity target = (LivingEntity) entities.get(i); - Location loc = target.getLocation().add(0, 0.5, 0); - if (loc.distanceSquared(wallCheckLoc) > radSq - || (!throughWall && TargetHelper.isObstructed(wallCheckLoc, loc))) - { - continue; - } - if (both || ally == SkillAPI.getSettings().isAlly(caster, target)) - { - list.add(target); - if (list.size() >= max) - { - break; - } - } - } - } + final List list = new ArrayList<>(); + while (!targets.isEmpty()) { + list.add(targets.remove(this.random.nextInt(targets.size()))); } - return list.size() > 0 && executeChildren(caster, level, list); + return list; } } diff --git a/src/com/sucy/skill/dynamic/target/ConeTarget.java b/src/com/sucy/skill/dynamic/target/ConeTarget.java index c9b51545..36df9826 100644 --- a/src/com/sucy/skill/dynamic/target/ConeTarget.java +++ b/src/com/sucy/skill/dynamic/target/ConeTarget.java @@ -27,10 +27,9 @@ package com.sucy.skill.dynamic.target; import com.rit.sucy.player.TargetHelper; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.dynamic.EffectComponent; -import org.bukkit.Location; +import com.sucy.skill.cast.IIndicator; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import java.util.List; @@ -38,57 +37,28 @@ * Applies child components to the closest all nearby entities around * each of the current targets. */ -public class ConeTarget extends EffectComponent -{ +public class ConeTarget extends TargetComponent { private static final String ANGLE = "angle"; private static final String RANGE = "range"; - private static final String ALLY = "group"; - private static final String MAX = "max"; - private static final String WALL = "wall"; - private static final String CASTER = "caster"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + /** {@inheritDoc} */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean worked = false; - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double range = attr(caster, RANGE, level, 3.0, isSelf); - double angle = attr(caster, ANGLE, level, 90.0, isSelf); - boolean both = settings.getString(ALLY, "enemy").toLowerCase().equals("both"); - boolean ally = settings.getString(ALLY, "enemy").toLowerCase().equals("ally"); - boolean throughWall = settings.getString(WALL, "false").toLowerCase().equals("true"); - boolean self = settings.getString(CASTER, "false").toLowerCase().equals("true"); - int max = settings.getInt(MAX, 99); - for (LivingEntity t : targets) - { - List list = TargetHelper.getConeTargets(caster, angle, range); - if (self) - { - list.add(caster); - } - Location wallCheckLoc = t.getLocation().add(0, 0.5, 0); - for (int i = 0; i < list.size(); i++) - { - LivingEntity target = list.get(i); - if (i >= max - || (!throughWall && TargetHelper.isObstructed(wallCheckLoc, target.getLocation().add(0, 0.5, 0))) - || (!both && ally != SkillAPI.getSettings().isAlly(caster, target))) - { - list.remove(i); - i--; - } - } - worked = executeChildren(caster, level, list) || worked; - } - return worked; + void makeIndicators(List list, Player caster, LivingEntity target, int level) { + double range = parseValues(caster, RANGE, level, 3.0); + double angle = parseValues(caster, ANGLE, level, 90.0); + makeConeIndicator(list, target, range, angle); + } + + /** {@inheritDoc} */ + @Override + List getTargets(LivingEntity caster, int level, List targets) { + double range = parseValues(caster, RANGE, level, 3.0); + double angle = parseValues(caster, ANGLE, level, 90.0); + return determineTargets(caster, level, targets, t -> TargetHelper.getConeTargets(t, angle, range)); + } + + @Override + public String getKey() { + return "cone"; } } diff --git a/src/com/sucy/skill/dynamic/target/LinearTarget.java b/src/com/sucy/skill/dynamic/target/LinearTarget.java index c4720b0f..142f7fe2 100644 --- a/src/com/sucy/skill/dynamic/target/LinearTarget.java +++ b/src/com/sucy/skill/dynamic/target/LinearTarget.java @@ -27,76 +27,38 @@ package com.sucy.skill.dynamic.target; import com.rit.sucy.player.TargetHelper; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.dynamic.EffectComponent; -import org.bukkit.Location; +import com.sucy.skill.cast.IIndicator; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; -import java.util.ArrayList; import java.util.List; /** * Applies child components to the entities in a line in front of each of the * provided targets. */ -public class LinearTarget extends EffectComponent -{ +public class LinearTarget extends TargetComponent { private static final String RANGE = "range"; private static final String TOLERANCE = "tolerance"; - private static final String ALLY = "group"; - private static final String MAX = "max"; - private static final String WALL = "wall"; - private static final String CASTER = "caster"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + /** {@inheritDoc} */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean worked = false; - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double tolerance = attr(caster, TOLERANCE, level, 4.0, isSelf); - double range = attr(caster, RANGE, level, 5.0, isSelf); - boolean both = settings.getString(ALLY, "enemy").toLowerCase().equals("both"); - boolean ally = settings.getString(ALLY, "enemy").toLowerCase().equals("ally"); - boolean throughWall = settings.getString(WALL, "false").toLowerCase().equals("true"); - boolean self = settings.getString(CASTER, "false").toLowerCase().equals("true"); - - int max = (int) attr(caster, MAX, level, 99, isSelf); - ArrayList list = new ArrayList(); - for (LivingEntity t : targets) - { - if (self) - { - list.add(caster); - } - Location wallCheckLoc = t.getLocation().add(0, 0.5, 0); + List getTargets( + final LivingEntity caster, final int level, final List targets) { + final double tolerance = parseValues(caster, TOLERANCE, level, 4.0); + final double range = parseValues(caster, RANGE, level, 5.0); + return determineTargets(caster, level, targets, t -> TargetHelper.getLivingTargets(t, range, tolerance)); + } - List result = TargetHelper.getLivingTargets(t, range, tolerance); - for (LivingEntity target : result) - { - if (!throughWall && TargetHelper.isObstructed(wallCheckLoc, target.getLocation().add(0, 0.5, 0))) - { - continue; - } - if (both || ally != SkillAPI.getSettings().canAttack(caster, target)) - { - list.add(target); - if (list.size() >= max) - { - break; - } - } - } + /** {@inheritDoc} */ + @Override + void makeIndicators( + final List list, final Player caster, final LivingEntity target, final int level) { + // TODO - add indicators for linear targeting + } - } - return list.size() > 0 && executeChildren(caster, level, list); + @Override + public String getKey() { + return "linear"; } } diff --git a/src/com/sucy/skill/dynamic/target/LocationTarget.java b/src/com/sucy/skill/dynamic/target/LocationTarget.java index 30a8d45a..bb3f9cab 100644 --- a/src/com/sucy/skill/dynamic/target/LocationTarget.java +++ b/src/com/sucy/skill/dynamic/target/LocationTarget.java @@ -26,63 +26,103 @@ */ package com.sucy.skill.dynamic.target; -import com.sucy.skill.dynamic.EffectComponent; +import com.google.common.collect.ImmutableList; +import com.rit.sucy.player.TargetHelper; +import com.sucy.skill.cast.IIndicator; import com.sucy.skill.dynamic.TempEntity; import org.bukkit.Location; -import org.bukkit.block.Block; +import org.bukkit.Material; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Applies child components to a location using the caster's faced direction */ -public class LocationTarget extends EffectComponent -{ - private static final String RANGE = "range"; +public class LocationTarget extends TargetComponent { + private static final String RANGE = "range"; private static final String GROUND = "ground"; - private static final HashSet NULL_SET = null; - - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + /** {@inheritDoc} */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean worked = false; - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double range = attr(caster, RANGE, level, 5.0, isSelf); - boolean groundOnly = !settings.getString(GROUND, "true").toLowerCase().equals("false"); - - ArrayList list = new ArrayList(); - for (LivingEntity t : targets) - { - Location loc; - Block b = t.getTargetBlock(NULL_SET, (int) Math.ceil(range)); - if (b == null && !groundOnly) - { - loc = t.getLocation().add(t.getLocation().getDirection().multiply(range)); - } - else if (b != null) - { - loc = b.getLocation(); - } - else - { - continue; + public void makeIndicators(List list, Player caster, LivingEntity target, int level) { + final List locs = getTargets(caster, level, ImmutableList.of(target)); + if (!locs.isEmpty()) makeCircleIndicator(list, locs.get(0), 0.5); + } + + /** {@inheritDoc} */ + @Override + List getTargets( + final LivingEntity caster, final int level, final List targets) { + final double range = parseValues(caster, RANGE, level, 5.0); + final boolean groundOnly = !settings.getString(GROUND, "true").toLowerCase().equals("false"); + return determineTargets(caster, level, targets, t -> getTargetLoc(caster, t, range, groundOnly)); + } + + private List getTargetLoc( + LivingEntity caster, + LivingEntity t, + final double range, + final boolean groundOnly) { + Location loc = calcTargetLoc(t, range); + if (groundOnly && AIR_BLOCKS.contains(loc.getBlock().getType())) { + return ImmutableList.of(); + } + + if (!groundOnly) { + final LivingEntity target = TargetHelper.getLivingTarget(t, range, 4); + final Location casterLoc = caster.getLocation(); + if (target != null && target.getLocation().distanceSquared(casterLoc) < loc.distanceSquared(casterLoc)) { + loc = target.getLocation(); } + } + + return ImmutableList.of(new TempEntity(loc)); + } + + private Location calcTargetLoc(LivingEntity entity, double maxRange) { + Location start = entity.getEyeLocation(); + Vector dir = entity.getLocation().getDirection(); + if (dir.getX() == 0) { dir.setX(Double.MIN_NORMAL); } + if (dir.getY() == 0) { dir.setY(Double.MIN_NORMAL); } + if (dir.getZ() == 0) { dir.setY(Double.MIN_NORMAL); } + int ox = dir.getX() > 0 ? 1 : 0; + int oy = dir.getY() > 0 ? 1 : 0; + int oz = dir.getZ() > 0 ? 1 : 0; - list.add(new TempEntity(loc)); + while (AIR_BLOCKS.contains(start.getBlock().getType()) && maxRange > 0) { + double dxt = computeWeight(start.getX(), dir.getX(), ox); + double dyt = computeWeight(start.getY(), dir.getY(), oy); + double dzt = computeWeight(start.getZ(), dir.getZ(), oz); + double t = Math.min(dxt, Math.min(dyt, Math.min(dzt, maxRange))) + 1E-5; + + start.add(dir.clone().multiply(t)); + maxRange -= t; + } + + return start; + } + + private double computeWeight(double val, double dir, int offset) { + return (Math.floor(val + offset) - val) / dir; + } + + private static final Set AIR_BLOCKS = new HashSet(); + + static { + for (Material material : Material.values()) { + if (material.isTransparent()) { + AIR_BLOCKS.add(material); + } } - return executeChildren(caster, level, list); + } + + @Override + public String getKey() { + return "location"; } } diff --git a/src/com/sucy/skill/dynamic/target/NearestTarget.java b/src/com/sucy/skill/dynamic/target/NearestTarget.java index 83bed3c5..8cc602d6 100644 --- a/src/com/sucy/skill/dynamic/target/NearestTarget.java +++ b/src/com/sucy/skill/dynamic/target/NearestTarget.java @@ -26,97 +26,61 @@ */ package com.sucy.skill.dynamic.target; -import com.rit.sucy.player.TargetHelper; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.api.util.Nearby; +import com.sucy.skill.cast.IIndicator; import org.bukkit.Location; -import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; /** - * Applies child components to the closest entities to the caster within a given range + * Applies child components to the closest all nearby entities around + * each of the current targets. */ -public class NearestTarget extends EffectComponent -{ +public class NearestTarget extends TargetComponent { private static final String RADIUS = "radius"; - private static final String ALLY = "group"; - private static final String MAX = "max"; - private static final String WALL = "wall"; - private static final String CASTER = "caster"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + /** {@inheritDoc} */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double radius = attr(caster, RADIUS, level, 3.0, isSelf); - boolean both = settings.getString(ALLY, "enemy").toLowerCase().equals("both"); - boolean ally = settings.getString(ALLY, "enemy").toLowerCase().equals("ally"); - boolean throughWall = settings.getString(WALL, "false").toLowerCase().equals("true"); - boolean self = settings.getString(CASTER, "false").toLowerCase().equals("true"); - double max = attr(caster, MAX, level, 99, isSelf); - ArrayList list = new ArrayList(); - for (LivingEntity t : targets) - { - int prevSize = list.size(); + List getTargets( + final LivingEntity caster, final int level, final List targets) { - List entities = t.getNearbyEntities(radius, radius, radius); - if (self) - { - list.add(caster); - } + final double radius = parseValues(caster, RADIUS, level, 3.0); + final List result = new ArrayList<>(); + for (LivingEntity target : targets) { + final Comparator comparator = new DistanceComparator(target.getLocation()); + Nearby.getLivingNearby(target, radius).stream() + .min(comparator) + .ifPresent(result::add); - Location wallCheckLoc = t.getLocation().add(0, 0.5, 0); + } + return result; + } - // Grab nearby targets - for (Entity entity : entities) - { - if (entity instanceof LivingEntity) - { - LivingEntity target = (LivingEntity) entity; - if (!throughWall && TargetHelper.isObstructed(wallCheckLoc, target.getLocation().add(0, 0.5, 0))) - { - continue; - } - if (both || ally == SkillAPI.getSettings().isAlly(caster, target)) - { - list.add(target); - } - } - } + /** {@inheritDoc} */ + @Override + void makeIndicators(final List list, final Player caster, final LivingEntity target, final int level) { + makeCircleIndicator(list, target, parseValues(caster, RADIUS, level, 3.0)); + } - // Take only the closest - // TODO optimize getting the nearest ones - Location casterLoc = caster.getLocation(); - while (list.size() > max && list.size() > 0) - { - double dSq = -1; - int index = 0; - for (int i = 0; i < list.size(); i++) - { - double d = list.get(i).getLocation().distanceSquared(casterLoc); - if (d > dSq) - { - dSq = d; - index = i; - } - } - list.remove(index); - } + @Override + public String getKey() { + return "nearest"; + } + + private static class DistanceComparator implements Comparator { + private Location loc; + + private DistanceComparator(final Location loc) { + this.loc = loc; + } - max += list.size() - prevSize; + @Override + public int compare(final LivingEntity o1, final LivingEntity o2) { + return Double.compare(o1.getLocation().distanceSquared(loc), o2.getLocation().distanceSquared(loc)); } - return list.size() > 0 && executeChildren(caster, level, list); } } diff --git a/src/com/sucy/skill/dynamic/target/OffsetTarget.java b/src/com/sucy/skill/dynamic/target/OffsetTarget.java index b254bfbd..1ec94e5f 100644 --- a/src/com/sucy/skill/dynamic/target/OffsetTarget.java +++ b/src/com/sucy/skill/dynamic/target/OffsetTarget.java @@ -26,52 +26,57 @@ */ package com.sucy.skill.dynamic.target; -import com.sucy.skill.dynamic.EffectComponent; +import com.google.common.collect.ImmutableList; +import com.sucy.skill.cast.IIndicator; import com.sucy.skill.dynamic.TempEntity; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import org.bukkit.util.Vector; -import java.util.ArrayList; import java.util.List; /** * Applies child effects to a location offset from the current targets */ -public class OffsetTarget extends EffectComponent -{ - private static Vector up = new Vector(0, 1, 0); +public class OffsetTarget extends TargetComponent { + private static final Vector UP = new Vector(0, 1, 0); - private static final String FORWARD = "forward"; - private static final String UPWARD = "upward"; - private static final String RIGHT = "right"; + private static final String FORWARD = "forward"; + private static final String UPWARD = "upward"; + private static final String RIGHT = "right"; + private static final String HORIZONTAL = "horizontal"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + /** {@inheritDoc} */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double forward = attr(caster, FORWARD, level, 0, isSelf); - double upward = attr(caster, UPWARD, level, 0, isSelf); - double right = attr(caster, RIGHT, level, 0, isSelf); + public void makeIndicators(List list, Player caster, LivingEntity target, int level) { + makeCircleIndicator(list, getTargetLoc(caster, level, target), 0.5); + } + + /** {@inheritDoc} */ + @Override + List getTargets( + final LivingEntity caster, final int level, final List targets) { + return determineTargets(caster, level, targets, t -> ImmutableList.of(getTargetLoc(caster, level, t))); + } + + private TempEntity getTargetLoc(LivingEntity caster, int level, LivingEntity t) { + final boolean horizontal = settings.getBool(HORIZONTAL, false); + final double forward = parseValues(caster, FORWARD, level, 0); + final double upward = parseValues(caster, UPWARD, level, 0); + final double right = parseValues(caster, RIGHT, level, 0); - ArrayList list = new ArrayList(); - for (LivingEntity t : targets) - { - Vector dir = t.getLocation().getDirection().setY(0).normalize(); - Vector nor = dir.clone().crossProduct(up); - dir.multiply(forward); - dir.add(nor.multiply(right)).setY(upward); + final Vector dir = t.getLocation().getDirection().setY(0).normalize(); + if (horizontal) { dir.setY(0).normalize(); } - list.add(new TempEntity(t.getLocation().add(dir))); - } - return executeChildren(caster, level, list); + final Vector nor = dir.clone().crossProduct(UP); + dir.multiply(forward); + dir.add(nor.multiply(right)).setY(upward); + + return new TempEntity(t.getLocation().add(dir)); + } + + @Override + public String getKey() { + return "offset"; } } diff --git a/src/com/sucy/skill/dynamic/target/RememberTarget.java b/src/com/sucy/skill/dynamic/target/RememberTarget.java index c60946b9..9113d37d 100644 --- a/src/com/sucy/skill/dynamic/target/RememberTarget.java +++ b/src/com/sucy/skill/dynamic/target/RememberTarget.java @@ -26,55 +26,42 @@ */ package com.sucy.skill.dynamic.target; +import com.google.common.collect.ImmutableList; +import com.sucy.skill.cast.IIndicator; import com.sucy.skill.dynamic.DynamicSkill; -import com.sucy.skill.dynamic.EffectComponent; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import java.util.List; /** * Applies a flag to each target */ -public class RememberTarget extends EffectComponent -{ +public class RememberTarget extends TargetComponent { private static final String KEY = "key"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + /** {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") - public boolean execute(final LivingEntity caster, final int level, final List targets) - { - if (!settings.has(KEY)) - { - return false; - } + public void makeIndicators(List list, Player caster, LivingEntity target, int level) { + final List targets = getTargets(caster, level, null); + if (!targets.isEmpty()) { makeCircleIndicator(list, targets.get(0), 0.5); } + } + + /** {@inheritDoc} */ + @Override + List getTargets( + final LivingEntity caster, final int level, final List targets) { + return remember(caster, settings.getString(KEY)); + } - String key = settings.getString(KEY); - Object data = DynamicSkill.getCastData(caster).get(key); - try - { - List remembered = (List) data; - for (int i = 0; i < remembered.size(); i++) - { - if (remembered.get(i).isDead() || !remembered.get(i).isValid()) - { - remembered.remove(i); - i--; - } - } - return targets.size() > 0 && executeChildren(caster, level, remembered); - } - catch (Exception ex) - { - return false; - } + public static List remember(final LivingEntity caster, final String key) { + final Object data = DynamicSkill.getCastData(caster).get(key); + //noinspection unchecked - proper skill setup should cause this to work + return data == null ? ImmutableList.of() : (List) data; + } + + @Override + public String getKey() { + return "remember"; } } diff --git a/src/com/sucy/skill/dynamic/target/SelfTarget.java b/src/com/sucy/skill/dynamic/target/SelfTarget.java index 6240afad..6a2372e5 100644 --- a/src/com/sucy/skill/dynamic/target/SelfTarget.java +++ b/src/com/sucy/skill/dynamic/target/SelfTarget.java @@ -26,43 +26,33 @@ */ package com.sucy.skill.dynamic.target; -import com.sucy.skill.dynamic.EffectComponent; +import com.google.common.collect.ImmutableList; +import com.sucy.skill.cast.IIndicator; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; -import java.util.ArrayList; import java.util.List; /** * Applies child components to the caster */ -public class SelfTarget extends EffectComponent -{ - private static final String REPEATED = "repeated"; +public class SelfTarget extends TargetComponent { - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + /** {@inheritDoc} */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean worked = false; - ArrayList list = new ArrayList(); - list.add(caster); - for (LivingEntity t : targets) - { - worked = executeChildren(caster, level, list) || worked; + public void makeIndicators(List list, Player caster, LivingEntity target, int level) { + makeCircleIndicator(list, caster, 0.5); + } - if (!settings.getBool(REPEATED)) - { - break; - } - } - return worked; + /** {@inheritDoc} */ + @Override + List getTargets( + final LivingEntity caster, final int level, final List targets) { + return ImmutableList.of(caster); + } + + @Override + public String getKey() { + return "self"; } } diff --git a/src/com/sucy/skill/dynamic/target/SingleTarget.java b/src/com/sucy/skill/dynamic/target/SingleTarget.java index 430fc812..76d295a8 100644 --- a/src/com/sucy/skill/dynamic/target/SingleTarget.java +++ b/src/com/sucy/skill/dynamic/target/SingleTarget.java @@ -26,62 +26,45 @@ */ package com.sucy.skill.dynamic.target; +import com.google.common.collect.ImmutableList; import com.rit.sucy.player.TargetHelper; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.dynamic.EffectComponent; -import org.bukkit.Location; +import com.sucy.skill.cast.IIndicator; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; -import java.util.ArrayList; import java.util.List; /** * Applies child components to the closest linear entity of each of the * provided targets. */ -public class SingleTarget extends EffectComponent -{ +public class SingleTarget extends TargetComponent { private static final String RANGE = "range"; private static final String TOLERANCE = "tolerance"; - private static final String ALLY = "group"; - private static final String WALL = "wall"; - /** - * Executes the component - * - * @param caster caster of the skill - * @param level level of the skill - * @param targets targets to apply to - * - * @return true if applied to something, false otherwise - */ + /** {@inheritDoc} */ @Override - public boolean execute(LivingEntity caster, int level, List targets) - { - boolean isSelf = targets.size() == 1 && targets.get(0) == caster; - double range = attr(caster, RANGE, level, 5.0, isSelf); - double tolerance = attr(caster, TOLERANCE, level, 4.0, isSelf); - boolean both = settings.getString(ALLY, "enemy").toLowerCase().equals("both"); - boolean ally = settings.getString(ALLY, "enemy").toLowerCase().equals("ally"); - boolean throughWall = settings.getString(WALL, "false").toLowerCase().equals("true"); + public void makeIndicators(List list, Player caster, LivingEntity target, int level) { + double range = parseValues(caster, RANGE, level, 3.0); + double angle = parseValues(caster, TOLERANCE, level, 4.0); + makeConeIndicator(list, target, range, angle); + } + + /** {@inheritDoc} */ + @Override + List getTargets( + final LivingEntity caster, final int level, final List targets) { - ArrayList list = new ArrayList(); - for (LivingEntity t : targets) - { - Location wallCheckLoc = t.getLocation().add(0, 0.5, 0); - LivingEntity target = TargetHelper.getLivingTarget(t, range, tolerance); - if (target != null) - { - if (!throughWall && TargetHelper.isObstructed(wallCheckLoc, target.getLocation().add(0, 0.5, 0))) - { - continue; - } - if (both || ally != SkillAPI.getSettings().canAttack(caster, target)) - { - list.add(target); - } - } - } - return list.size() > 0 && executeChildren(caster, level, list); + double range = parseValues(caster, RANGE, level, 5.0); + double tolerance = parseValues(caster, TOLERANCE, level, 4.0); + return determineTargets(caster, level, targets, t -> { + final LivingEntity target = TargetHelper.getLivingTarget(t, range, tolerance); + return target == null ? ImmutableList.of() : ImmutableList.of(target); + }); + } + + @Override + public String getKey() { + return "single"; } } diff --git a/src/com/sucy/skill/dynamic/target/TargetComponent.java b/src/com/sucy/skill/dynamic/target/TargetComponent.java new file mode 100644 index 00000000..1de78db1 --- /dev/null +++ b/src/com/sucy/skill/dynamic/target/TargetComponent.java @@ -0,0 +1,160 @@ +package com.sucy.skill.dynamic.target; + +import com.rit.sucy.config.parse.DataSection; +import com.rit.sucy.player.TargetHelper; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.cast.CircleIndicator; +import com.sucy.skill.cast.ConeIndicator; +import com.sucy.skill.cast.IIndicator; +import com.sucy.skill.cast.IndicatorType; +import com.sucy.skill.cast.SphereIndicator; +import com.sucy.skill.dynamic.ComponentType; +import com.sucy.skill.dynamic.DynamicSkill; +import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.dynamic.TempEntity; +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.target.TargetComponent + */ +public abstract class TargetComponent extends EffectComponent { + + private static final String ALLY = "group"; + private static final String WALL = "wall"; + private static final String CASTER = "caster"; + private static final String MAX = "max"; + + boolean everyone; + boolean allies; + boolean throughWall; + boolean self; + + @Override + public ComponentType getType() { + return ComponentType.TARGET; + } + + /** + * Executes the component + * + * @param caster caster of the skill + * @param level level of the skill + * @param targets targets to apply to + * + * @return true if applied to something, false otherwise + */ + @Override + public boolean execute(LivingEntity caster, int level, List targets) { + final List list = getTargets(caster, level, targets); + return (!list.isEmpty() && executeChildren(caster, level, list)); + } + + @Override + public void load(DynamicSkill skill, DataSection config) { + super.load(skill, config); + + final String group = settings.getString(ALLY, "enemy").toLowerCase(); + everyone = group.equals("both"); + allies = group.equals("ally"); + throughWall = settings.getString(WALL, "false").equalsIgnoreCase("true"); + self = settings.getString(CASTER, "false").equalsIgnoreCase("true"); + } + + abstract List getTargets( + final LivingEntity caster, + final int level, + final List targets); + + abstract void makeIndicators(final List list, final Player caster, final LivingEntity target, final int level); + + /** + * Creates the list of indicators for the skill + * + * @param list list to store indicators in + * @param caster caster reference + * @param targets location to base location on + * @param level the level of the skill to create for + */ + @Override + public void makeIndicators(List list, Player caster, List targets, int level) { + targets.forEach(target -> makeIndicators(list, caster, target, level)); + + List childTargets = null; + for (final EffectComponent component : children) { + if (component.hasEffect) { + if (childTargets == null) { childTargets = getTargets(caster, level, targets); } + + component.makeIndicators(list, caster, childTargets, level); + } + } + } + + void makeConeIndicator(final List list, final LivingEntity target, final double range, final double angle) { + if (indicatorType != IndicatorType.NONE) { + Location loc = target.getLocation(); + ConeIndicator indicator = new ConeIndicator(angle, range); + indicator.moveTo(loc.getX(), loc.getY() + 0.1, loc.getZ()); + indicator.setDirection(loc.getYaw()); + list.add(indicator); + } + } + + void makeCircleIndicator(final List list, final LivingEntity source, final double radius) { + if (indicatorType == IndicatorType.DIM_3) { + final Location loc = source.getLocation(); + IIndicator indicator = new SphereIndicator(radius); + indicator.moveTo(loc.getX(), loc.getY() + 0.1, loc.getZ()); + list.add(indicator); + } else if (indicatorType == IndicatorType.DIM_2) { + final Location loc = source.getLocation(); + IIndicator indicator = new CircleIndicator(radius); + indicator.moveTo(loc.getX(), loc.getY() + 0.1, loc.getZ()); + list.add(indicator); + } + } + + List determineTargets( + final LivingEntity caster, + final int level, + final List from, + final Function> conversion) { + + final double max = parseValues(caster, MAX, level, 99); + + final List list = new ArrayList<>(); + from.forEach(target -> { + final int count = list.size(); + final List found = conversion.apply(target); + for (LivingEntity entity : found) { + if (isValidTarget(caster, target, entity)) { + list.add(entity); + if (list.size() - count >= max) { + break; + } + } + } + }); + if (self) { + list.add(caster); + } + + return list; + } + + boolean isValidTarget(final LivingEntity caster, final LivingEntity from, final LivingEntity target) { + if (target instanceof TempEntity) { + return true; + } + + return target != caster && SkillAPI.getSettings().isValidTarget(target) + && (throughWall || !TargetHelper.isObstructed(from.getEyeLocation(), target.getEyeLocation())) + && (everyone || allies == SkillAPI.getSettings().isAlly(caster, target)); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/BlockBreakTrigger.java b/src/com/sucy/skill/dynamic/trigger/BlockBreakTrigger.java new file mode 100644 index 00000000..60be8ffe --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/BlockBreakTrigger.java @@ -0,0 +1,54 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.block.BlockBreakEvent; + +import java.util.List; +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class BlockBreakTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "BLOCK_BREAK"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return BlockBreakEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final BlockBreakEvent event, final int level, final Settings settings) { + final List types = settings.getStringList("material"); + return types.isEmpty() || (types.size() == 1 && types.get(0).equalsIgnoreCase("ANY")) + || types.stream().anyMatch(mat -> event.getBlock().getType().name().equalsIgnoreCase(mat)); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final BlockBreakEvent event, final Map data) { + data.put("api-block-type", event.getBlock().getType().name()); + data.put("api-block-loc", event.getBlock().getLocation()); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final BlockBreakEvent event) { + return event.getPlayer(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final BlockBreakEvent event, final Settings settings) { + return event.getPlayer(); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/BlockPlaceTrigger.java b/src/com/sucy/skill/dynamic/trigger/BlockPlaceTrigger.java new file mode 100644 index 00000000..72b5f6a1 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/BlockPlaceTrigger.java @@ -0,0 +1,54 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.block.BlockPlaceEvent; + +import java.util.List; +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class BlockPlaceTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "BLOCK_PLACE"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return BlockPlaceEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final BlockPlaceEvent event, final int level, final Settings settings) { + final List types = settings.getStringList("material"); + return types.isEmpty() || (types.size() == 1 && types.get(0).equalsIgnoreCase("ANY")) + || types.stream().anyMatch(mat -> event.getBlock().getType().name().equalsIgnoreCase(mat)); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final BlockPlaceEvent event, final Map data) { + data.put("api-block-type", event.getBlock().getType().name()); + data.put("api-block-loc", event.getBlock().getLocation()); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final BlockPlaceEvent event) { + return event.getPlayer(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final BlockPlaceEvent event, final Settings settings) { + return event.getPlayer(); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/CrouchTrigger.java b/src/com/sucy/skill/dynamic/trigger/CrouchTrigger.java new file mode 100644 index 00000000..6a45ecb4 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/CrouchTrigger.java @@ -0,0 +1,49 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.player.PlayerToggleSneakEvent; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class CrouchTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "CROUCH"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return PlayerToggleSneakEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final PlayerToggleSneakEvent event, final int level, final Settings settings) { + final String type = settings.getString("type", "start crouching"); + return type.equalsIgnoreCase("both") || event.isSneaking() != type.equalsIgnoreCase("stop crouching"); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final PlayerToggleSneakEvent event, final Map data) { } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final PlayerToggleSneakEvent event) { + return event.getPlayer(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final PlayerToggleSneakEvent event, final Settings settings) { + return event.getPlayer(); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/DeathTrigger.java b/src/com/sucy/skill/dynamic/trigger/DeathTrigger.java new file mode 100644 index 00000000..2889ef9f --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/DeathTrigger.java @@ -0,0 +1,52 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class DeathTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "DEATH"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return EntityDeathEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final EntityDeathEvent event, final int level, final Settings settings) { + return !isTargetingKiller(settings) || event.getEntity().getKiller() != null; + } + + /** {@inheritDoc} */ + @Override + public void setValues(final EntityDeathEvent event, final Map data) { } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final EntityDeathEvent event) { + return event.getEntity(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final EntityDeathEvent event, final Settings settings) { + return isTargetingKiller(settings) ? event.getEntity().getKiller() : event.getEntity(); + } + + private boolean isTargetingKiller(final Settings settings) { + return settings.getString("killer", "false").equalsIgnoreCase("true"); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/EnvironmentalTrigger.java b/src/com/sucy/skill/dynamic/trigger/EnvironmentalTrigger.java new file mode 100644 index 00000000..00804d41 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/EnvironmentalTrigger.java @@ -0,0 +1,68 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.entity.EntityDamageEvent; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class EnvironmentalTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "ENVIRONMENT_DAMAGE"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return EntityDamageEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final EntityDamageEvent event, final int level, final Settings settings) { + final String type = settings.getString("type", "any").replace(' ', '_').toUpperCase(); + return type.equalsIgnoreCase("ANY") || type.equalsIgnoreCase(event.getCause().name()); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final EntityDamageEvent event, final Map data) { + data.put("api-taken", event.getDamage()); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final EntityDamageEvent event) { + if (event.getEntity() instanceof LivingEntity) { + return (LivingEntity) event.getEntity(); + } else { + return null; + } + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final EntityDamageEvent event, final Settings settings) { + return getCaster(event); + } + + /** + * Handles applying other effects after the skill resolves + * + * @param event event details + * @param skill skill to resolve + */ + @Override + public void postProcess(final EntityDamageEvent event, final DynamicSkill skill) { + final double damage = skill.applyImmediateBuff(event.getDamage()); + event.setDamage(damage); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/KillTrigger.java b/src/com/sucy/skill/dynamic/trigger/KillTrigger.java new file mode 100644 index 00000000..03e6969d --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/KillTrigger.java @@ -0,0 +1,48 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class KillTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "KILL"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return EntityDeathEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final EntityDeathEvent event, final int level, final Settings settings) { + return true; + } + + /** {@inheritDoc} */ + @Override + public void setValues(final EntityDeathEvent event, final Map data) { } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final EntityDeathEvent event) { + return event.getEntity().getKiller(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final EntityDeathEvent event, final Settings settings) { + return event.getEntity().getKiller(); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/LandTrigger.java b/src/com/sucy/skill/dynamic/trigger/LandTrigger.java new file mode 100644 index 00000000..88a8d4dc --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/LandTrigger.java @@ -0,0 +1,51 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.event.PlayerLandEvent; +import org.bukkit.entity.LivingEntity; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class LandTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "LAND"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return PlayerLandEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final PlayerLandEvent event, final int level, final Settings settings) { + final double minDistance = settings.getDouble("min-distance", 0); + return event.getDistance() >= minDistance; + } + + /** {@inheritDoc} */ + @Override + public void setValues(final PlayerLandEvent event, final Map data) { + data.put("api-distance", event.getDistance()); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final PlayerLandEvent event) { + return event.getPlayer(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final PlayerLandEvent event, final Settings settings) { + return event.getPlayer(); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/LaunchTrigger.java b/src/com/sucy/skill/dynamic/trigger/LaunchTrigger.java new file mode 100644 index 00000000..3eea9bcb --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/LaunchTrigger.java @@ -0,0 +1,55 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.entity.ProjectileLaunchEvent; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class LaunchTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "LAUNCH"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return ProjectileLaunchEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final ProjectileLaunchEvent event, final int level, final Settings settings) { + final String type = settings.getString("type", "any"); + return type.equalsIgnoreCase("ANY") || type.equalsIgnoreCase(event.getEntity().getType().name()); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final ProjectileLaunchEvent event, final Map data) { + data.put("api-velocity", event.getEntity().getVelocity().length()); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final ProjectileLaunchEvent event) { + if (event.getEntity().getShooter() instanceof LivingEntity) { + return (LivingEntity) event.getEntity().getShooter(); + } else { + return null; + } + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final ProjectileLaunchEvent event, final Settings settings) { + return getCaster(event); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/MoveTrigger.java b/src/com/sucy/skill/dynamic/trigger/MoveTrigger.java new file mode 100644 index 00000000..bb16f482 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/MoveTrigger.java @@ -0,0 +1,51 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.player.PlayerMoveEvent; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class MoveTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "MOVE"; + } + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return PlayerMoveEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final PlayerMoveEvent event, final int level, final Settings settings) { + return event.getFrom().getWorld() == event.getTo().getWorld(); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final PlayerMoveEvent event, final Map data) { + final double distance = event.getTo().distance(event.getFrom()); + data.put("api-distance", distance); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final PlayerMoveEvent event) { + return event.getPlayer(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final PlayerMoveEvent event, final Settings settings) { + return event.getPlayer(); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/PhysicalDealtTrigger.java b/src/com/sucy/skill/dynamic/trigger/PhysicalDealtTrigger.java new file mode 100644 index 00000000..b9970976 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/PhysicalDealtTrigger.java @@ -0,0 +1,38 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.event.PhysicalDamageEvent; +import org.bukkit.entity.LivingEntity; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class PhysicalDealtTrigger extends PhysicalTrigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "PHYSICAL_DAMAGE"; + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final PhysicalDamageEvent event) { + return event.getDamager(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final PhysicalDamageEvent event, final Settings settings) { + return isUsingTarget(settings) ? event.getTarget() : event.getDamager(); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final PhysicalDamageEvent event, final Map data) { + data.put("api-dealt", event.getDamage()); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/PhysicalTakenTrigger.java b/src/com/sucy/skill/dynamic/trigger/PhysicalTakenTrigger.java new file mode 100644 index 00000000..8dc9abe7 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/PhysicalTakenTrigger.java @@ -0,0 +1,38 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.event.PhysicalDamageEvent; +import org.bukkit.entity.LivingEntity; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class PhysicalTakenTrigger extends PhysicalTrigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "TOOK_PHYSICAL_DAMAGE"; + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final PhysicalDamageEvent event) { + return event.getTarget(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final PhysicalDamageEvent event, final Settings settings) { + return isUsingTarget(settings) ? event.getDamager() : event.getTarget(); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final PhysicalDamageEvent event, final Map data) { + data.put("api-taken", event.getDamage()); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/PhysicalTrigger.java b/src/com/sucy/skill/dynamic/trigger/PhysicalTrigger.java new file mode 100644 index 00000000..bd1de7ea --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/PhysicalTrigger.java @@ -0,0 +1,45 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.event.PhysicalDamageEvent; +import com.sucy.skill.dynamic.DynamicSkill; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public abstract class PhysicalTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return PhysicalDamageEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final PhysicalDamageEvent event, final int level, final Settings settings) { + final String type = settings.getString("type", "both"); + final double min = settings.getDouble("dmg-min"); + final double max = settings.getDouble("dmg-max"); + final boolean projectile = event.isProjectile(); + return event.getDamage() >= min && event.getDamage() <= max && + (type.equalsIgnoreCase("both") || type.equalsIgnoreCase("projectile") == projectile); + } + + /** + * Handles applying other effects after the skill resolves + * + * @param event event details + * @param skill skill to resolve + */ + @Override + public void postProcess(final PhysicalDamageEvent event, final DynamicSkill skill) { + final double damage = skill.applyImmediateBuff(event.getDamage()); + event.setDamage(damage); + } + + boolean isUsingTarget(final Settings settings) { + return settings.getString("target", "true").equalsIgnoreCase("false"); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/SkillDealtTrigger.java b/src/com/sucy/skill/dynamic/trigger/SkillDealtTrigger.java new file mode 100644 index 00000000..1e0e0ad3 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/SkillDealtTrigger.java @@ -0,0 +1,38 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.event.SkillDamageEvent; +import org.bukkit.entity.LivingEntity; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class SkillDealtTrigger extends SkillTrigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "SKILL_DAMAGE"; + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final SkillDamageEvent event) { + return event.getDamager(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final SkillDamageEvent event, final Settings settings) { + return isUsingTarget(settings) ? event.getTarget() : event.getDamager(); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final SkillDamageEvent event, final Map data) { + data.put("api-dealt", event.getDamage()); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/SkillTakenTrigger.java b/src/com/sucy/skill/dynamic/trigger/SkillTakenTrigger.java new file mode 100644 index 00000000..10f9a9a7 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/SkillTakenTrigger.java @@ -0,0 +1,38 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.event.SkillDamageEvent; +import org.bukkit.entity.LivingEntity; + +import java.util.Map; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public class SkillTakenTrigger extends SkillTrigger { + + /** {@inheritDoc} */ + @Override + public String getKey() { + return "TOOK_SKILL_DAMAGE"; + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getCaster(final SkillDamageEvent event) { + return event.getTarget(); + } + + /** {@inheritDoc} */ + @Override + public LivingEntity getTarget(final SkillDamageEvent event, final Settings settings) { + return isUsingTarget(settings) ? event.getDamager() : event.getTarget(); + } + + /** {@inheritDoc} */ + @Override + public void setValues(final SkillDamageEvent event, final Map data) { + data.put("api-taken", event.getDamage()); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/SkillTrigger.java b/src/com/sucy/skill/dynamic/trigger/SkillTrigger.java new file mode 100644 index 00000000..c4f0918f --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/SkillTrigger.java @@ -0,0 +1,47 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.api.event.SkillDamageEvent; +import com.sucy.skill.dynamic.DynamicSkill; + +import java.util.List; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.BlockBreakTrigger + */ +public abstract class SkillTrigger implements Trigger { + + /** {@inheritDoc} */ + @Override + public Class getEvent() { + return SkillDamageEvent.class; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldTrigger(final SkillDamageEvent event, final int level, final Settings settings) { + final double min = settings.getDouble("dmg-min"); + final double max = settings.getDouble("dmg-max"); + final List types = settings.getStringList("category"); + final boolean empty = types.isEmpty() || types.get(0).isEmpty(); + return event.getDamage() >= min && event.getDamage() <= max && + (empty || types.contains(event.getClassification())); + } + + /** + * Handles applying other effects after the skill resolves + * + * @param event event details + * @param skill skill to resolve + */ + @Override + public void postProcess(final SkillDamageEvent event, final DynamicSkill skill) { + final double damage = skill.applyImmediateBuff(event.getDamage()); + event.setDamage(damage); + } + + boolean isUsingTarget(final Settings settings) { + return settings.getString("target", "true").equalsIgnoreCase("false"); + } +} diff --git a/src/com/sucy/skill/dynamic/trigger/Trigger.java b/src/com/sucy/skill/dynamic/trigger/Trigger.java new file mode 100644 index 00000000..fc317a68 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/Trigger.java @@ -0,0 +1,93 @@ +/** + * SkillAPI + * com.sucy.skill.dynamic.Trigger + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software") to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.api.Settings; +import com.sucy.skill.dynamic.DynamicSkill; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; + +import java.util.Map; + +/** + * Possible triggers for dynamic skill effects + */ +public interface Trigger { + + /** + * @return unique key for the trigger + */ + String getKey(); + + /** + * @return class of the event related to the trigger + */ + Class getEvent(); + + /** + * @param event event details + * @param level the level of the owning skill + * @param settings skill settings + * @return true if the skill should activate, false otherwise + */ + boolean shouldTrigger(final E event, final int level, final Settings settings); + + /** + * Reads data from the event and provides values to the caster's value data. This can be used within + * skills for more flexible effects. An example of this in base triggers is the Launch trigger providing + * the speed a projectile was launched so mechanics can replace it with equally-fast projectiles. + * + * @param event event details + * @param data caster's value data to populate + */ + void setValues(final E event, final Map data); + + /** + * Fetches the caster as determined by the triggering event. + * + * @param event event details + * @return the one to apply the trigger for + */ + LivingEntity getCaster(final E event); + + /** + * Fetches the target as determined by the triggering event. This can be the same as the caster. + * + * @param event event details + * @param settings skill settings + * @return the one being affected by the trigger (initial target) + */ + LivingEntity getTarget(final E event, final Settings settings); + + /** + * Handles applying other effects after the skill resolves + * + * @param event event details + * @param skill skill to resolve + */ + default void postProcess(final E event, final DynamicSkill skill) { } +} diff --git a/src/com/sucy/skill/dynamic/trigger/TriggerComponent.java b/src/com/sucy/skill/dynamic/trigger/TriggerComponent.java new file mode 100644 index 00000000..d8123e78 --- /dev/null +++ b/src/com/sucy/skill/dynamic/trigger/TriggerComponent.java @@ -0,0 +1,45 @@ +package com.sucy.skill.dynamic.trigger; + +import com.sucy.skill.dynamic.ComponentType; +import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.util.Lists; +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +/** + * SkillAPI © 2018 + * com.sucy.skill.dynamic.trigger.TriggerComponent + */ +public class TriggerComponent extends EffectComponent { + + private boolean running = false; + + public boolean isRunning() { + return running; + } + + public boolean trigger(final LivingEntity caster, final LivingEntity target, final int level) { + return execute(caster, level, Lists.asList(target)); + } + + @Override + public String getKey() { + return "trigger"; + } + + @Override + public ComponentType getType() { + return ComponentType.TRIGGER; + } + + @Override + public boolean execute(final LivingEntity caster, final int level, final List targets) { + try { + running = true; + return executeChildren(caster, level, targets); + } finally { + running = false; + } + } +} diff --git a/src/com/sucy/skill/gui/handlers/AttributeHandler.java b/src/com/sucy/skill/gui/handlers/AttributeHandler.java new file mode 100644 index 00000000..9df1f44c --- /dev/null +++ b/src/com/sucy/skill/gui/handlers/AttributeHandler.java @@ -0,0 +1,54 @@ +/** + * SkillAPI + * com.sucy.skill.gui.handlers.AttributeHandler + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2016 Steven Sucy + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.handlers; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.gui.tool.GUIHolder; +import com.sucy.skill.manager.AttributeManager; + +import java.util.HashMap; + +public class AttributeHandler extends GUIHolder { + private HashMap start = new HashMap(); + + @Override + protected void onSetup() { + AttributeManager manager = SkillAPI.getAttributeManager(); + for (String key : manager.getKeys()) { start.put(key, player.getAttribute(key)); } + } + + @Override + public void onClick(AttributeManager.Attribute type, int slot, boolean left, boolean shift) { + if (left) { + if (player.upAttribute(type.getKey())) { setPage(page); } + } else if (SkillAPI.getSettings().isAttributesDowngrade() || player.getAttribute(type.getKey()) > start.get(type.getKey())) { + if (player.refundAttribute(type.getKey())) { + setPage(page); + } + } + } +} diff --git a/src/com/sucy/skill/gui/handlers/DetailsHandler.java b/src/com/sucy/skill/gui/handlers/DetailsHandler.java new file mode 100644 index 00000000..bec706ee --- /dev/null +++ b/src/com/sucy/skill/gui/handlers/DetailsHandler.java @@ -0,0 +1,48 @@ +/** + * SkillAPI + * com.sucy.skill.gui.handlers.DetailsHandler + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2016 Steven Sucy + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.handlers; + +import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.gui.tool.GUIHolder; + +/** + * Handles interactions with the class details menu + */ +public class DetailsHandler extends GUIHolder +{ + /** + * Shows class details when clicked + * + * @param type player class clicked on + * @param slot slot number + */ + @Override + protected void onClick(RPGClass type, int slot, boolean left, boolean shift) + { + player.showSkills(player.getPlayer(), player.getClass(type.getGroup())); + } +} diff --git a/src/com/sucy/skill/gui/handlers/ProfessHandler.java b/src/com/sucy/skill/gui/handlers/ProfessHandler.java new file mode 100644 index 00000000..1ee89af1 --- /dev/null +++ b/src/com/sucy/skill/gui/handlers/ProfessHandler.java @@ -0,0 +1,47 @@ +/** + * SkillAPI + * com.sucy.skill.gui.handlers.ProfessHandler + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2016 Steven Sucy + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.handlers; + +import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.manager.CmdManager; +import com.sucy.skill.gui.tool.GUIHolder; + +public class ProfessHandler extends GUIHolder +{ + /** + * Professes as the clicked class + * + * @param type player class clicked on + * @param slot slot number + */ + @Override + protected void onClick(RPGClass type, int slot, boolean left, boolean shift) + { + player.getPlayer().closeInventory(); + CmdManager.PROFESS_COMMAND.execute(player.getPlayer(), new String[] { type.getName() }); + } +} diff --git a/src/com/sucy/skill/gui/handlers/SkillHandler.java b/src/com/sucy/skill/gui/handlers/SkillHandler.java new file mode 100644 index 00000000..9249836d --- /dev/null +++ b/src/com/sucy/skill/gui/handlers/SkillHandler.java @@ -0,0 +1,58 @@ +/** + * SkillAPI + * com.sucy.skill.gui.handlers.SkillHandler + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2016 Steven Sucy + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.handlers; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.gui.tool.GUIHolder; + +import java.util.HashMap; + +public class SkillHandler extends GUIHolder { + private HashMap start = new HashMap(); + + @Override + public void onSetup() { + for (String key : data.keySet()) { start.put(key, player.getSkillLevel(key)); } + } + + @Override + public void onClick(Skill type, int slot, boolean left, boolean shift) { + if (left) { + if (player.upgradeSkill(type)) { setPage(page); } + } else if ((SkillAPI.getSettings() + .isAllowDowngrade() || player.getSkillLevel(type.getKey()) > start.get(type.getKey())) + && player.downgradeSkill(type)) { setPage(page); } + } + + @Override + public void onHotBar(Skill type, int from, int to) { + if (player.getSkillBar().isSetup() && type.canCast() && player.hasSkill(type.getName())) { + player.getSkillBar().assign(player.getSkill(type.getName()), to); + } + } +} diff --git a/src/com/sucy/skill/gui/Menu.java b/src/com/sucy/skill/gui/map/Menu.java similarity index 75% rename from src/com/sucy/skill/gui/Menu.java rename to src/com/sucy/skill/gui/map/Menu.java index fd5064f3..aacdb6e1 100644 --- a/src/com/sucy/skill/gui/Menu.java +++ b/src/com/sucy/skill/gui/map/Menu.java @@ -1,6 +1,6 @@ /** * SkillAPI - * com.sucy.skill.gui.Menu + * com.sucy.skill.gui.map.Menu * * The MIT License (MIT) * @@ -24,7 +24,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.sucy.skill.gui; +package com.sucy.skill.gui.map; import com.rit.sucy.gui.MapFont; import com.rit.sucy.gui.MapMenuManager; @@ -43,29 +43,29 @@ public class Menu public static final String SKILL_TREE = "sapiSkills"; // Menus - public static SkillListMenu LIST_MENU; - public static SkillDetailMenu DETAIL_MENU; + static SkillListMenu LIST_MENU; + static SkillDetailMenu DETAIL_MENU; // Images - public static final String BACKGROUND = "background"; - public static final String TITLE = "title"; - public static final String NAMEPLATE = "nameplate"; - public static final String SELECTOR = "selector"; - public static final String UP_0 = "up0"; - public static final String UP_1 = "up1"; - public static final String DOWN_0 = "down0"; - public static final String DOWN_1 = "down1"; - public static final String MORE_0 = "more0"; - public static final String MORE_1 = "more1"; - public static final String BACK_0 = "back0"; - public static final String BACK_1 = "back1"; + static final String BACKGROUND = "background"; + static final String TITLE = "title"; + static final String NAMEPLATE = "nameplate"; + static final String SELECTOR = "selector"; + static final String UP_0 = "up0"; + static final String UP_1 = "up1"; + static final String DOWN_0 = "down0"; + static final String DOWN_1 = "down1"; + static final String MORE_0 = "more0"; + static final String MORE_1 = "more1"; + static final String BACK_0 = "back0"; + static final String BACK_1 = "back1"; // Fonts - public static final String LIST = "list"; - public static final String DETAIL = "detail"; + static final String LIST = "list"; + static final String DETAIL = "detail"; // Colors - public static final String FONT = "font"; + static final String FONT = "font"; /** * Sets up the schemes for SkillAPI diff --git a/src/com/sucy/skill/gui/SkillDetailMenu.java b/src/com/sucy/skill/gui/map/SkillDetailMenu.java similarity index 98% rename from src/com/sucy/skill/gui/SkillDetailMenu.java rename to src/com/sucy/skill/gui/map/SkillDetailMenu.java index 064be2ed..bc891e24 100644 --- a/src/com/sucy/skill/gui/SkillDetailMenu.java +++ b/src/com/sucy/skill/gui/map/SkillDetailMenu.java @@ -1,6 +1,6 @@ /** * SkillAPI - * com.sucy.skill.gui.SkillDetailMenu + * com.sucy.skill.gui.map.SkillDetailMenu * * The MIT License (MIT) * @@ -24,7 +24,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.sucy.skill.gui; +package com.sucy.skill.gui.map; import com.rit.sucy.gui.*; import com.sucy.skill.SkillAPI; @@ -170,7 +170,7 @@ public void setup(Player player) // Get text to draw PlayerSkill skill = SkillListMenu.getSkill(player); - ItemStack icon = skill.getData().getIndicator(skill); + ItemStack icon = skill.getData().getIndicator(skill, true); List lore = icon.getItemMeta().getLore(); lore.add(0, icon.getItemMeta().getDisplayName()); diff --git a/src/com/sucy/skill/gui/SkillListMenu.java b/src/com/sucy/skill/gui/map/SkillListMenu.java similarity index 98% rename from src/com/sucy/skill/gui/SkillListMenu.java rename to src/com/sucy/skill/gui/map/SkillListMenu.java index e72bbdde..4b21278e 100644 --- a/src/com/sucy/skill/gui/SkillListMenu.java +++ b/src/com/sucy/skill/gui/map/SkillListMenu.java @@ -1,6 +1,6 @@ /** * SkillAPI - * com.sucy.skill.gui.SkillListMenu + * com.sucy.skill.gui.map.SkillListMenu * * The MIT License (MIT) * @@ -24,7 +24,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.sucy.skill.gui; +package com.sucy.skill.gui.map; import com.rit.sucy.gui.*; import com.sucy.skill.SkillAPI; diff --git a/src/com/sucy/skill/gui/tool/GUIData.java b/src/com/sucy/skill/gui/tool/GUIData.java new file mode 100644 index 00000000..87cf37b3 --- /dev/null +++ b/src/com/sucy/skill/gui/tool/GUIData.java @@ -0,0 +1,206 @@ +/** + * SkillAPI + * com.sucy.skill.gui.tool.GUIData + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.tool; + +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.tree.basic.InventoryTree; +import org.bukkit.Bukkit; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.HashMap; + +public class GUIData +{ + private static final String + ROWS = "rows", + PAGES = "pages", + SLOTS = "slots"; + + private final ArrayList pageMap = new ArrayList(); + + private int rows = 3; + private int pages = 1; + private int nav = 0; + + GUIData() + { + pageMap.add(new GUIPage(this)); + } + + GUIData(InventoryTree skillTree) + { + rows = skillTree.getHeight(); + this.pageMap.add(new GUIPage(this, skillTree.getSkillSlots())); + } + + GUIData(DataSection data) + { + if (data != null) + { + rows = data.getInt(ROWS, rows); + this.pages = data.getInt(PAGES, this.pages); + DataSection pages = data.getSection(SLOTS); + if (pages != null) + for (String page : pages.keys()) + if (pages.isSection(page)) + this.pageMap.add(new GUIPage(this, pages.getSection(page))); + } + while (pageMap.size() < pages) + pageMap.add(new GUIPage(this)); + } + + public void show(GUIHolder handler, PlayerData player, String title, HashMap data) + { + Inventory inv = Bukkit.getServer().createInventory(handler, rows * 9, title); + ItemStack[] contents = pageMap.get(0).instance(player, data); + if (pages > 1) + GUITool.addPageButtons(contents); + inv.setContents(contents); + handler.set(this, player, inv, data); + player.getPlayer().openInventory(inv); + } + + public GUIPage getPage(int page) + { + return pageMap.get(page % pages); + } + + public GUIPage getPage() + { + return pageMap.get(nav); + } + + public int getSize() + { + return rows * 9; + } + + public void init(ItemStack[] contents) + { + nav = 0; + fill(contents); + } + + public void load(ItemStack[] contents) + { + pageMap.get(nav).load(contents); + } + + public void fill(ItemStack[] contents) + { + pageMap.get(nav).fill(contents); + } + + public void next() + { + nav = (nav + 1) % pages; + } + + public void prev() + { + nav = (nav + pages - 1) % pages; + } + + public int getPages() + { + return pages; + } + + public void addPage() + { + pageMap.add(new GUIPage(this)); + pages += 1; + nav++; + if (pages == 2) + for (GUIPage page : pageMap) + page.clearRight(); + } + + public void removePage() + { + pageMap.remove(nav); + pages--; + nav = Math.min(nav, pages - 1); + + if (pages == 0) { + addPage(); + } + } + + public void resize(int rows) + { + this.rows = Math.max(Math.min(rows, 6), 1); + } + + public void shrink() + { + if (rows > 1) + rows--; + + for (GUIPage page : pageMap) + page.remove(getSize(), getSize() + 9); + } + + public void grow() + { + if (rows < 6) + rows++; + } + + public boolean isValid() + { + for (GUIPage page : pageMap) + if (page.isValid()) + return true; + + return false; + } + + public boolean has(String item) + { + for (GUIPage page : pageMap) + if (page.getIndex(item) != -1) + return true; + + return false; + } + + public void save(DataSection data) + { + data.set(ROWS, rows); + data.set(PAGES, pages); + DataSection slots = data.createSection(SLOTS); + int i = 0; + for (GUIPage page : pageMap) + { + page.save(slots.createSection((++i) + "")); + } + } +} diff --git a/src/com/sucy/skill/gui/tool/GUIHolder.java b/src/com/sucy/skill/gui/tool/GUIHolder.java new file mode 100644 index 00000000..3606d292 --- /dev/null +++ b/src/com/sucy/skill/gui/tool/GUIHolder.java @@ -0,0 +1,134 @@ +/** + * SkillAPI + * com.sucy.skill.gui.tool.GUIHolder + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.tool; + +import com.sucy.skill.api.player.PlayerData; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; + +public abstract class GUIHolder implements InventoryHolder +{ + protected HashMap data = new HashMap(); + + protected GUIData gui; + protected PlayerData player; + protected Inventory inventory; + protected int page; + + public void set(GUIData gui, PlayerData player, Inventory inv, HashMap data) + { + this.gui = gui; + this.player = player; + this.inventory = inv; + this.data = data; + + onSetup(); + } + + public T get(int slot) { + String identifier = gui.getPage(page).get(slot); + return identifier == null ? null : data.get(identifier); + } + + public void next() + { + setPage((page + 1) % gui.getPages()); + } + + public void prev() + { + setPage((page + gui.getPages() - 1) % gui.getPages()); + } + + public void setPage(int page) + { + this.page = page; + ItemStack[] contents = gui.getPage(page).instance(player, data); + if (gui.getPages() > 1) + GUITool.addPageButtons(contents); + inventory.setContents(contents); + } + + public void handleDrag(InventoryDragEvent event) + { + event.setCancelled(true); + } + + @SuppressWarnings("unchecked") + public void handleClick(InventoryClickEvent event) + { + event.setCancelled(true); + boolean top = event.getRawSlot() < event.getView().getTopInventory().getSize(); + T result = get(event.getSlot()); + if (top && result != null && result.isAllowed((Player) event.getWhoClicked())) + { + if (event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD || event.getAction() == InventoryAction.HOTBAR_SWAP) + onHotBar(result, event.getSlot(), event.getHotbarButton()); + else + onClick(result, event.getSlot(), event.isLeftClick(), event.isShiftClick()); + } + else if (top && gui.getPages() > 1) { + if (gui.getSize() == 9) { + if (event.getSlot() == 7) + prev(); + if (event.getSlot() == 8) + next(); + } + else if (event.getSlot() == 8) + prev(); + else if (event.getSlot() == 17) + next(); + } + } + + public void handleClose(InventoryCloseEvent event) + { + onClose((Player) event.getPlayer()); + } + + protected abstract void onClick(T type, int slot, boolean left, boolean shift); + + protected void onHotBar(T type, int from, int to) { } + + protected void onSetup() { } + + protected void onClose(Player player) { } + + @Override + public Inventory getInventory() + { + return inventory; + } +} diff --git a/src/com/sucy/skill/gui/tool/GUIPage.java b/src/com/sucy/skill/gui/tool/GUIPage.java new file mode 100644 index 00000000..97abf5f0 --- /dev/null +++ b/src/com/sucy/skill/gui/tool/GUIPage.java @@ -0,0 +1,174 @@ +/** + * SkillAPI + * com.sucy.skill.gui.tool.GUIPage + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.tool; + +import com.rit.sucy.config.parse.DataSection; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.skills.Skill; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; + +public class GUIPage +{ + private HashMap slots = new HashMap(); + private HashMap lookup = new HashMap(); + + private GUIData parent; + + GUIPage(GUIData parent) + { + this.parent = parent; + } + + GUIPage(GUIData parent, Map skillSlots) + { + this.parent = parent; + for (Map.Entry entry : skillSlots.entrySet()) + { + slots.put(entry.getValue().getName().toLowerCase(), entry.getKey()); + lookup.put(entry.getKey(), entry.getValue().getName().toLowerCase()); + } + } + + GUIPage(GUIData parent, DataSection data) + { + this(parent); + + for (String key : data.keys()) + { + slots.put(key, data.getInt(key)); + lookup.put(data.getInt(key), key); + } + } + + public String get(int index) + { + return lookup.get(index); + } + + public void set(int index, String value) + { + slots.put(value, index); + lookup.put(index, value); + } + + public int getIndex(String item) + { + item = item.toLowerCase(); + if (!slots.containsKey(item)) + return -1; + else + return slots.get(item); + } + + public void fill(ItemStack[] data) + { + for (Map.Entry entry : slots.entrySet()) + data[entry.getValue()] = make(entry.getKey()); + } + + public void clearRight() + { + for (int i = 8; i < 54; i += 9) + if (lookup.containsKey(i)) + slots.remove(lookup.remove(i)); + } + + public void remove(int min, int max) + { + for (int i = min; i < max; i++) + if (lookup.containsKey(i)) + slots.remove(lookup.remove(i)); + } + + private ItemStack make(String key) + { + ItemStack item; + if (SkillAPI.isSkillRegistered(key)) + item = SkillAPI.getSkill(key).getToolIndicator(); + else if (SkillAPI.isClassRegistered(key)) + item = SkillAPI.getClass(key).getToolIcon(); + else + item = GUITool.getIcon(key); + + return item; + } + + public void load(ItemStack[] data) + { + slots.clear(); + lookup.clear(); + for (int i = 0; i < data.length; i++) + { + ItemStack item = data[i]; + if (item == null) + continue; + + String key = ChatColor.stripColor(data[i].getItemMeta().getDisplayName()).toLowerCase(); + if (key.equals("next page") || key.equals("prev page")) + continue; + + slots.put(key.toLowerCase(), i); + lookup.put(i, key.toLowerCase()); + } + } + + public ItemStack[] instance(PlayerData player, HashMap data) + { + ItemStack[] contents = new ItemStack[parent.getSize()]; + + Player bukkitPlayer = player.getPlayer(); + for (Map.Entry entry : lookup.entrySet()) + { + IconHolder holder = data.get(entry.getValue()); + if (holder != null && holder.isAllowed(bukkitPlayer)) + contents[entry.getKey()] = holder.getIcon(player); + else + contents[entry.getKey()] = GUITool.getIcon(entry.getValue()); + } + + return contents; + } + + public void save(DataSection data) + { + for (Map.Entry entry : slots.entrySet()) + { + data.set(entry.getKey(), entry.getValue()); + } + } + + public boolean isValid() + { + return slots.size() > 0; + } +} diff --git a/src/com/sucy/skill/gui/tool/GUITool.java b/src/com/sucy/skill/gui/tool/GUITool.java new file mode 100644 index 00000000..1b07ddb5 --- /dev/null +++ b/src/com/sucy/skill/gui/tool/GUITool.java @@ -0,0 +1,623 @@ +/** + * SkillAPI + * com.sucy.skill.gui.tool.GUITool + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.tool; + +import com.rit.sucy.config.CommentedConfig; +import com.rit.sucy.config.parse.DataSection; +import com.rit.sucy.text.TextFormatter; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.classes.RPGClass; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.api.util.DamageLoreRemover; +import com.sucy.skill.log.Logger; +import com.sucy.skill.manager.AttributeManager; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +public class GUITool implements ToolMenu +{ + // Page buttons + private static final String NEXT_PAGE = "NEXT_PAGE"; + private static final String PREV_PAGE = "PREV_PAGE"; + + private static boolean inUse = false; + + public static boolean isInUse() + { + return inUse; + } + + private static HashMap setups = new HashMap(); + private static HashMap items = new HashMap(); + + private static CommentedConfig config; + + private static ItemStack + NEXT, + PREV, + SHRINK, + GROW, + ADD_PAGE, + DEL_PAGE, + NEXT_CLASS, + PREV_CLASS, + NEXT_PROFESSION, + PREV_PROFESSION; + + private static RPGClass[] availableClasses; + private static RPGClass[] availableProfesses; + private static String[] availableGroups; + + public static void init() + { + if (NEXT != null) + return; + + NEXT = make(Material.BOOK, ChatColor.GOLD + "Next Menu"); + PREV = make(Material.BOOK, ChatColor.GOLD + "Previous Menu"); + SHRINK = make(Material.MELON_SEEDS, ChatColor.GOLD + "Shrink", "", "Removes a row from the GUI"); + GROW = make(Material.MELON, ChatColor.GOLD + "Grow", "", "Adds a row to the GUI"); + ADD_PAGE = make(Material.PAPER, ChatColor.GOLD + "Add Page", "", "Adds another page to the GUI", "right after the current one"); + DEL_PAGE = make(Material.PAPER, ChatColor.GOLD + "Delete Page", "", "Deletes the currently", "viewed page"); + NEXT_CLASS = make(Material.DIAMOND_SWORD, ChatColor.GOLD + "Next Class"); + PREV_CLASS = make(Material.IRON_SWORD, ChatColor.GOLD + "Previous Class"); + NEXT_PROFESSION = make(Material.DIAMOND_HOE, ChatColor.GOLD + "Next Profession"); + PREV_PROFESSION = make(Material.IRON_HOE, ChatColor.GOLD + "Previous Profession"); + + availableClasses = SkillAPI.getClasses().values().toArray(new RPGClass[SkillAPI.getClasses().size()]); + HashSet professes = new HashSet(); + HashSet groups = new HashSet(); + professes.add(null); + for (RPGClass c : availableClasses) + { + setups.put(GUIType.SKILL_TREE.getPrefix() + c.getName(), new GUIData(c.getSkillTree())); + if (c.hasParent()) + professes.add(c.getParent()); + groups.add(c.getGroup()); + } + availableGroups = groups.toArray(new String[groups.size()]); + availableProfesses = professes.toArray(new RPGClass[professes.size()]); + + config = SkillAPI.getConfig("gui"); + DataSection data = config.getConfig(); + for (String key : data.keys()) { + GUIData loaded = new GUIData(data.getSection(key)); + if (loaded.isValid()) + setups.put(key, loaded); + } + + CommentedConfig itemFile = SkillAPI.getConfig("tool"); + itemFile.checkDefaults(); + itemFile.save(); + DataSection custom = itemFile.getConfig(); + for (String key : custom.keys()) + { + try + { + ItemStack item = parseItem(custom.getSection(key)); + items.put(key.toUpperCase(), item); + } + catch (Exception ex) + { + Logger.invalid("Bad custom tool item: " + key); + } + } + } + + public static ItemStack parseItem(DataSection data) + { + ItemStack item; + try { + item = new ItemStack( + Material.valueOf(data.getString("type").toUpperCase().replace(" ", "_")), + 1, + data.getShort("durability"), + data.getByte("data") + ); + } catch (Exception ex) { + item = new ItemStack( + Material.matchMaterial(data.getString("type")), + 1, + data.getShort("durability") + ); + } + + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(TextFormatter.colorString(data.getString("name"))); + meta.setLore(TextFormatter.colorStringList(data.getList("lore"))); + item.setItemMeta(meta); + return DamageLoreRemover.removeAttackDmg(item); + } + + public static void cleanUp() + { + if (config != null) { + config.clear(); + DataSection data = config.getConfig(); + for (Map.Entry entry : setups.entrySet()) + if (entry.getValue().isValid()) + entry.getValue().save(data.createSection(entry.getKey())); + config.save(); + } + + setups.clear(); + items.clear(); + config = null; + NEXT = null; + PREV = null; + SHRINK = null; + GROW = null; + ADD_PAGE = null; + DEL_PAGE = null; + NEXT_CLASS = null; + PREV_CLASS = null; + NEXT_PROFESSION = null; + PREV_PROFESSION = null; + + availableClasses = null; + availableProfesses = null; + availableGroups = null; + } + + public static ItemStack getIcon(final String key) { + return items.get(key.toUpperCase()); + } + + public static boolean hasData(String key) + { + return setups.containsKey(key) && setups.get(key).isValid(); + } + + public static GUIData getSkillTree(RPGClass rpgClass) + { + return get(GUIType.SKILL_TREE.getPrefix() + rpgClass.getName()); + } + + public static GUIData getProfessMenu(RPGClass current) + { + return get(current == null ? GUIType.CLASS_SELECTION.name() : GUIType.CLASS_SELECTION.getPrefix() + current.getName()); + } + + public static GUIData getDetailsMenu() + { + return get(GUIType.CLASS_DETAILS.name()); + } + + public static GUIData getAttributesMenu() + { + return get(GUIType.ATTRIBUTES.name()); + } + + private static GUIData get(String key) + { + if (!setups.containsKey(key)) + { + setups.put(key, new GUIData()); + } + return setups.get(key); + } + + public static GUIData getActiveData() + { + String key = type.getPrefix(); + switch (type) + { + case CLASS_SELECTION: + if (professId == 0) + key = type.name(); + else + key += availableProfesses[professId].getName(); + break; + case CLASS_DETAILS: + key = type.name(); + break; + case SKILL_TREE: + key += availableClasses[classId].getName(); + break; + case ATTRIBUTES: + key = type.name(); + } + + return get(key); + } + + private static ItemStack make(Material mat, String name, String... lore) + { + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(name); + meta.setLore(Arrays.asList(lore)); + item.setItemMeta(meta); + return item; + } + + private static GUIType type; + private static int classId; + private static int professId; + + private final Player player; + + private InventoryData data; + + private Inventory inventory; + + private RPGClass rpgClass; + private Skill skill; + private GUIData guiData; + + private ItemStack[] playerContents; + private ItemStack[] inventoryContents; + + private int i; + + private boolean switching = false; + + public GUITool(Player player) + { + this.player = player; + } + + public void open() + { + if (!inUse) + { + PlayerData data = SkillAPI.getPlayerData(player); + if (data.hasClass() && SkillAPI.getSettings().isSkillBarEnabled()) + data.getSkillBar().clear(player); + + this.data = new InventoryData(player); + setType(GUIType.CLASS_SELECTION); + inUse = true; + } + } + + public void setType(GUIType type) + { + GUITool.type = type; + guiData = getActiveData(); + inventoryContents = new ItemStack[guiData.getSize()]; + String title = populate(); + inventory = player.getServer().createInventory(this, guiData.getSize(), title); + inventory.setContents(inventoryContents); + player.getInventory().setContents(playerContents); + + switching = true; + player.openInventory(inventory); + switching = false; + } + + private void update() + { + inventoryContents = inventory.getContents(); + guiData.load(inventoryContents); + } + + private String populate() + { + playerContents = new ItemStack[36]; + + playerContents[0] = PREV; + playerContents[1] = NEXT; + playerContents[2] = SHRINK; + playerContents[3] = GROW; + playerContents[4] = ADD_PAGE; + playerContents[5] = DEL_PAGE; + + String name = null; + + switch (type) + { + case CLASS_DETAILS: + name = populateClassDetails(); + break; + case SKILL_TREE: + name = populateSkillTree(); + break; + case CLASS_SELECTION: + name = populateClassSelection(); + break; + case ATTRIBUTES: + name = populateAttributes(); + break; + } + + GUIPage page = guiData.getPage(); + for (Map.Entry entry : items.entrySet()) + { + if (entry.getKey().equals(NEXT_PAGE) || entry.getKey().equals(PREV_PAGE)) + continue; + + if (i < playerContents.length) + playerContents[i++] = toPlaceholder(entry.getKey(), entry.getValue()); + int index = page.getIndex(entry.getKey()); + if (index >= 0) + inventoryContents[index] = toPlaceholder(entry.getKey(), entry.getValue()); + } + + // Page buttons + if (guiData.getPages() > 1) + addPageButtons(inventoryContents); + + return name; + } + + public static void addPageButtons(ItemStack[] contents) { + if (contents.length > 9) + { + contents[8] = items.get(PREV_PAGE); + contents[17] = items.get(NEXT_PAGE); + } + else + { + contents[7] = items.get(PREV_PAGE); + contents[8] = items.get(NEXT_PAGE); + } + } + + private ItemStack toPlaceholder(String key, ItemStack custom) + { + ItemStack copy = custom.clone(); + ItemMeta meta = copy.getItemMeta(); + meta.setDisplayName(key); + meta.setLore(new ArrayList<>()); + copy.setItemMeta(meta); + return copy; + } + + private String populateClassSelection() + { + playerContents[7] = PREV_PROFESSION; + playerContents[8] = NEXT_PROFESSION; + + GUIPage page = guiData.getPage(); + RPGClass profession = availableProfesses[professId]; + i = 9; + for (RPGClass c : availableClasses) + { + if (c.getParent() != profession) + continue; + + int index = page.getIndex(c.getName()); + if (index != -1) + inventoryContents[index] = c.getToolIcon(); + else if (!guiData.has(c.getName()) && i < playerContents.length) + playerContents[i++] = c.getToolIcon(); + } + + if (profession == null) + return "Class Selection"; + else + return limit(profession.getName() + " / Sub-profession"); + } + + private String limit(String text) + { + return text.substring(0, Math.min(text.length(), 32)); + } + + private String populateClassDetails() + { + i = 9; + GUIPage page = guiData.getPage(); + for (String group : availableGroups) + { + ItemStack item = make(Material.DRAGON_EGG, group, "", "Spot for the player's current", "class in the group should", "be placed in the GUI"); + int index = page.getIndex(group); + if (index != -1) + inventoryContents[index] = item; + else if (!guiData.has(group) && i < playerContents.length) + playerContents[i++] = item; + } + + return "GUI Editor - Class Details"; + } + + private String populateSkillTree() + { + playerContents[7] = PREV_CLASS; + playerContents[8] = NEXT_CLASS; + + RPGClass current = availableClasses[classId]; + GUIPage page = guiData.getPage(); + i = 9; + while (current != null) + { + for (Skill skill : current.getSkills()) + { + int index = page.getIndex(skill.getName()); + if (index != -1) + inventoryContents[index] = skill.getToolIndicator(); + else if (!guiData.has(skill.getName()) && i < playerContents.length) + { + playerContents[i++] = skill.getToolIndicator(); + } + } + current = current.getParent(); + } + + return limit(availableClasses[classId].getName() + " / Skill Tree"); + } + + private String populateAttributes() + { + i = 9; + GUIPage page = guiData.getPage(); + for (String key : SkillAPI.getAttributeManager().getKeys()) + { + AttributeManager.Attribute attr = SkillAPI.getAttributeManager().getAttribute(key); + int index = page.getIndex(attr.getKey()); + if (index != -1) + inventoryContents[index] = attr.getToolIcon(); + else if (!guiData.has(attr.getKey()) && i < playerContents.length) + { + playerContents[i++] = attr.getToolIcon(); + } + } + + return "GUI Editor - Attributes"; + } + + @Override + public void handleClick(InventoryClickEvent event) + { + if (event.getAction() == InventoryAction.HOTBAR_SWAP + || event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD) + event.setCancelled(true); + else if (event.getRawSlot() < event.getView().getTopInventory().getSize()) + { + if (guiData.getPages() == 1) + return; + + if (guiData.getSize() > 9) + { + switch (event.getSlot()) + { + case 8: + update(); + guiData.prev(); + setType(type); + event.setCancelled(true); + break; + case 17: + update(); + guiData.next(); + setType(type); + event.setCancelled(true); + break; + } + } + else + { + switch (event.getSlot()) + { + case 7: + update(); + guiData.prev(); + setType(type); + event.setCancelled(true); + break; + case 8: + update(); + guiData.next(); + setType(type); + event.setCancelled(true); + break; + } + } + } + else + { + if (event.getSlot() < 9) + { + update(); + event.setCancelled(true); + } + switch (event.getSlot()) + { + case 0: + setType(type.prev()); + break; + case 1: + setType(type.next()); + break; + case 2: + guiData.shrink(); + setType(type); + break; + case 3: + guiData.grow(); + setType(type); + break; + case 4: + guiData.addPage(); + setType(type); + break; + case 5: + guiData.removePage(); + setType(type); + break; + case 7: + switch (type) + { + case CLASS_SELECTION: + professId = (professId + 1) % availableProfesses.length; + setType(type); + break; + case SKILL_TREE: + classId = (classId + 1) % availableClasses.length; + setType(type); + break; + } + break; + case 8: + switch (type) + { + case CLASS_SELECTION: + professId = (professId + availableProfesses.length - 1) % availableProfesses.length; + setType(type); + break; + case SKILL_TREE: + classId = (classId + availableClasses.length - 1) % availableClasses.length; + setType(type); + break; + } + break; + } + } + } + + @Override + public void restore() + { + if (data != null && !switching) + { + update(); + data.restore(player); + data = null; + inUse = false; + } + } + + @Override + public Inventory getInventory() + { + return inventory; + } +} diff --git a/src/com/sucy/skill/gui/tool/GUIType.java b/src/com/sucy/skill/gui/tool/GUIType.java new file mode 100644 index 00000000..d892d020 --- /dev/null +++ b/src/com/sucy/skill/gui/tool/GUIType.java @@ -0,0 +1,77 @@ +/** + * SkillAPI + * com.sucy.skill.gui.tool.GUIType + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.tool; + +import com.sucy.skill.SkillAPI; + +public enum GUIType +{ + CLASS_SELECTION(0, "cs"), + CLASS_DETAILS(1, "cd"), + SKILL_TREE(2, "st"), + ATTRIBUTES(3, "a"); + + private int id; + private String prefix; + + GUIType(int id, String prefix) + { + this.id = id; + this.prefix = prefix; + } + + public GUIType next() + { + return cycle(1); + } + + public GUIType prev() + { + return cycle(-1); + } + + private GUIType cycle(int direction) + { + GUIType type = ORDERED[(id + ORDERED.length + direction) % ORDERED.length]; + if (type == ATTRIBUTES && !SkillAPI.getSettings().isAttributesEnabled()) + return ORDERED[(id + ORDERED.length + 2 * direction) % ORDERED.length]; + return type; + } + + public String getPrefix() + { + return prefix; + } + + private static final GUIType[] ORDERED = new GUIType[] + { + CLASS_SELECTION, + CLASS_DETAILS, + SKILL_TREE, + ATTRIBUTES + }; +} diff --git a/src/com/sucy/skill/gui/tool/IconHolder.java b/src/com/sucy/skill/gui/tool/IconHolder.java new file mode 100644 index 00000000..e14eb6d4 --- /dev/null +++ b/src/com/sucy/skill/gui/tool/IconHolder.java @@ -0,0 +1,38 @@ +/** + * SkillAPI + * com.sucy.skill.gui.tool.IconHolder + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.tool; + +import com.sucy.skill.api.player.PlayerData; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public interface IconHolder +{ + ItemStack getIcon(PlayerData player); + + boolean isAllowed(Player player); +} diff --git a/src/com/sucy/skill/gui/tool/InventoryData.java b/src/com/sucy/skill/gui/tool/InventoryData.java new file mode 100644 index 00000000..73b742fc --- /dev/null +++ b/src/com/sucy/skill/gui/tool/InventoryData.java @@ -0,0 +1,68 @@ +/** + * SkillAPI + * com.sucy.skill.gui.tool.InventoryData + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.tool; + +import com.rit.sucy.version.VersionManager; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +/** + * Handles keeping track of player inventory data when overwriting it + * for tool GUIs, allowing the plugin to restore it as they close the menu. + */ +public class InventoryData +{ + private ItemStack[] main; + private ItemStack[] armor; + private ItemStack sidearm; + + /** + * Creates a backup of the player's inventory contents + * + * @param player player to make a backup for + */ + public InventoryData(Player player) + { + main = player.getInventory().getContents(); + armor = player.getInventory().getArmorContents(); + if (VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) + sidearm = player.getInventory().getItemInOffHand(); + } + + /** + * Restores the player's inventory contents + * + * @param player player to restore for + */ + public void restore(Player player) + { + player.getInventory().setContents(main); + player.getInventory().setArmorContents(armor); + if (VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) + player.getInventory().setItemInOffHand(sidearm); + } +} diff --git a/src/com/sucy/skill/gui/tool/ToolMenu.java b/src/com/sucy/skill/gui/tool/ToolMenu.java new file mode 100644 index 00000000..acf766d9 --- /dev/null +++ b/src/com/sucy/skill/gui/tool/ToolMenu.java @@ -0,0 +1,37 @@ +/** + * SkillAPI + * com.sucy.skill.gui.tool.ToolMenu + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.gui.tool; + +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.InventoryHolder; + +public interface ToolMenu extends InventoryHolder +{ + void handleClick(InventoryClickEvent event); + + void restore(); +} diff --git a/src/com/sucy/skill/hook/BungeeHook.java b/src/com/sucy/skill/hook/BungeeHook.java new file mode 100644 index 00000000..761cf753 --- /dev/null +++ b/src/com/sucy/skill/hook/BungeeHook.java @@ -0,0 +1,63 @@ +/** + * SkillAPI + * com.sucy.skill.hook.BungeeHook + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.hook; + +import com.rit.sucy.version.VersionManager; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.listener.MainListener; +import net.md_5.bungee.api.event.ServerConnectEvent; +import net.md_5.bungee.api.event.ServerKickEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +/** + * Handles getting the server kick event over to the + * quit event functions to save player data + */ +public class BungeeHook implements Listener +{ + /** + * Initializes the hook into Bungee + * + * @param api api reference + */ + public static void init(SkillAPI api) + { + api.getServer().getPluginManager().registerEvents(new BungeeHook(), api); + } + + @EventHandler + public void onKick(ServerKickEvent event) + { + MainListener.unload(VersionManager.getPlayer(event.getPlayer().getName())); + } + + @EventHandler + public void serverConnect(ServerConnectEvent event) { + MainListener.unload(VersionManager.getPlayer(event.getPlayer().getName())); + } +} diff --git a/src/com/sucy/skill/hook/CitizensHook.java b/src/com/sucy/skill/hook/CitizensHook.java new file mode 100644 index 00000000..f99691d5 --- /dev/null +++ b/src/com/sucy/skill/hook/CitizensHook.java @@ -0,0 +1,12 @@ +package com.sucy.skill.hook; + +import org.bukkit.entity.Entity; + +/** + * Handles checking whether or not an entity is an NPC + */ +public class CitizensHook { + public static boolean isNPC(Entity entity) { + return entity.getClass().getName().equals("PlayerNPC"); + } +} diff --git a/src/com/sucy/skill/hook/MythicMobsHook.java b/src/com/sucy/skill/hook/MythicMobsHook.java new file mode 100644 index 00000000..87a6dccf --- /dev/null +++ b/src/com/sucy/skill/hook/MythicMobsHook.java @@ -0,0 +1,24 @@ +package com.sucy.skill.hook; + +import io.lumine.xikage.mythicmobs.MythicMobs; +import org.bukkit.entity.LivingEntity; + +/** + * SkillAPI © 2017 + * com.sucy.skill.hook.MythicMobsHook + */ +public class MythicMobsHook { + + public static void taunt(final LivingEntity target, final LivingEntity source, final double amount) { + if (amount > 0) { + MythicMobs.inst().getAPIHelper().addThreat(target, source, amount); + } + else if (amount < 0) { + MythicMobs.inst().getAPIHelper().reduceThreat(target, source, -amount); + } + } + + public static boolean isMonster(final LivingEntity target) { + return MythicMobs.inst().getAPIHelper().isMythicMob(target); + } +} diff --git a/src/com/sucy/skill/hook/PlaceholderAPIHook.java b/src/com/sucy/skill/hook/PlaceholderAPIHook.java new file mode 100644 index 00000000..7f56b202 --- /dev/null +++ b/src/com/sucy/skill/hook/PlaceholderAPIHook.java @@ -0,0 +1,15 @@ +package com.sucy.skill.hook; + +import me.clip.placeholderapi.PlaceholderAPI; +import org.bukkit.entity.Player; + +/** + * SkillAPI © 2018 + * com.sucy.skill.hook.PlaceholderAPIHook + */ +public class PlaceholderAPIHook { + + public static String format(final String message, final Player player) { + return PlaceholderAPI.setPlaceholders(player, message); + } +} diff --git a/src/com/sucy/skill/hook/PluginChecker.java b/src/com/sucy/skill/hook/PluginChecker.java index 008497b6..0792067d 100644 --- a/src/com/sucy/skill/hook/PluginChecker.java +++ b/src/com/sucy/skill/hook/PluginChecker.java @@ -41,7 +41,7 @@ public class PluginChecker */ public static boolean isVaultActive() { - return Bukkit.getPluginManager().getPlugin("Vault") != null && VaultHook.isValid(); + return Bukkit.getPluginManager().isPluginEnabled("Vault") && VaultHook.isValid(); } /** @@ -51,7 +51,7 @@ public static boolean isVaultActive() */ public static boolean isDisguiseActive() { - return Bukkit.getPluginManager().getPlugin("LibsDisguises") != null; + return Bukkit.getPluginManager().isPluginEnabled("LibsDisguises"); } /** @@ -61,6 +61,46 @@ public static boolean isDisguiseActive() */ public static boolean isNoCheatActive() { - return Bukkit.getPluginManager().getPlugin("NoCheatPlus") != null; + return Bukkit.getPluginManager().isPluginEnabled("NoCheatPlus"); + } + + /** + * Checks whether or not RPGInventory is active on the server + * + * @return true if active, false otherwise + */ + public static boolean isRPGInventoryActive() + { + return Bukkit.getPluginManager().isPluginEnabled("RPGInventory"); + } + + public static boolean isPlaceholderAPIActive() { + return Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); + } + + /** + * Checks whether or not bungee is present + * + * @return true if present, false otherwise + */ + public static boolean isBungeeActive() + { + try + { + Class.forName("net.md_5.bungee.Util"); + return true; + } + catch (Exception ex) + { + return false; + } + } + + public static boolean isMythicMobsActive() { + return Bukkit.getPluginManager().isPluginEnabled("MythicMobs"); + } + + public static boolean isWorldGuardActive() { + return Bukkit.getPluginManager().isPluginEnabled("WorldGuard"); } } diff --git a/src/com/sucy/skill/hook/RPGInventoryHook.java b/src/com/sucy/skill/hook/RPGInventoryHook.java new file mode 100644 index 00000000..b829b3e6 --- /dev/null +++ b/src/com/sucy/skill/hook/RPGInventoryHook.java @@ -0,0 +1,49 @@ +/** + * SkillAPI + * com.sucy.skill.hook.RPGInventoryHook + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.hook; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import ru.endlesscode.rpginventory.api.InventoryAPI; + +import java.util.List; + +public class RPGInventoryHook +{ + public static boolean isRPGInventory(Inventory inv) + { + return InventoryAPI.isRPGInventory(inv); + } + + public static List getCustomEquips(Player player) + { + List active = InventoryAPI.getActiveItems(player); + active.addAll(InventoryAPI.getPassiveItems(player)); + return active; + } +} diff --git a/src/com/sucy/skill/hook/WorldGuardHook.java b/src/com/sucy/skill/hook/WorldGuardHook.java new file mode 100644 index 00000000..a6e368f4 --- /dev/null +++ b/src/com/sucy/skill/hook/WorldGuardHook.java @@ -0,0 +1,59 @@ +package com.sucy.skill.hook; + +import com.google.common.collect.ImmutableList; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sucy.skill.SkillAPI; +import org.bukkit.Location; +import org.bukkit.World; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * SkillAPI © 2018 + * com.sucy.skill.hook.WorldGuardHook + */ +public class WorldGuardHook { + + private static Method regionMethod; + + /** + * Fetches the list of region IDs applicable to a given location + * + * @param loc location to get region ids for + * @return region IDs for the location + */ + public static List getRegionIds(final Location loc) { + try { + return WorldGuard.getInstance() + .getPlatform() + .getRegionContainer() + .get(BukkitAdapter.adapt(loc.getWorld())) + .getApplicableRegionsIDs(asVector(loc)); + } catch (NoClassDefFoundError ex) { + try { + final WorldGuardPlugin plugin = SkillAPI.getPlugin(WorldGuardPlugin.class); + return ((RegionManager) getRegionMethod().invoke(plugin, loc.getWorld())) + .getApplicableRegionsIDs(asVector(loc)); + } catch (final Exception e) { + // Cannot handle world guard + return ImmutableList.of(); + } + } + } + + private static Vector asVector(final Location location) { + return new Vector(location.getX(), location.getY(), location.getZ()); + } + + private static Method getRegionMethod() throws Exception { + if (regionMethod == null) { + regionMethod = WorldGuardPlugin.class.getDeclaredMethod("getRegionManager", World.class); + } + return regionMethod; + } +} diff --git a/src/com/sucy/skill/language/GUINodes.java b/src/com/sucy/skill/language/GUINodes.java index f491d900..1a2fd51c 100644 --- a/src/com/sucy/skill/language/GUINodes.java +++ b/src/com/sucy/skill/language/GUINodes.java @@ -29,8 +29,9 @@ public class GUINodes { public static final String - BASE = "GUI.", - ATTRIB_TITLE = BASE + "attribute-title", - CLASS_LIST = BASE + "skill-class-list", - SKILL_TREE = BASE + "skill-tree"; + BASE = "GUI.", + ATTRIB_TITLE = BASE + "attribute-title", + CLASS_LIST = BASE + "skill-class-list", + SKILL_TREE = BASE + "skill-tree", + PROFESS_TITLE = BASE + "profess-title"; } diff --git a/src/com/sucy/skill/listener/AddonListener.java b/src/com/sucy/skill/listener/AddonListener.java new file mode 100644 index 00000000..a7f093eb --- /dev/null +++ b/src/com/sucy/skill/listener/AddonListener.java @@ -0,0 +1,112 @@ +package com.sucy.skill.listener; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.event.PlayerCastSkillEvent; +import com.sucy.skill.api.event.PlayerExperienceGainEvent; +import com.sucy.skill.api.player.PlayerClass; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.hook.PluginChecker; +import com.sucy.skill.hook.WorldGuardHook; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Series of fixes/improvements created by EvrimSen and touched up by Eniripsa96 + * + * See https://www.spigotmc.org/resources/addonforskillapi.55857/ for extra features + */ +public class AddonListener extends SkillAPIListener { + private static final Set IGNORE_CASTING = new HashSet<>(); + + /** + * Cancels damage between friendly classes + * + * @param event damage event + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPlayerHit(final EntityDamageByEntityEvent event) { + if (!SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) { + return; + } + + final LivingEntity damager = ListenerUtil.getDamager(event); + if (event.getEntity() instanceof Player && damager instanceof Player) { + final PlayerData attackerData = SkillAPI.getPlayerData((Player) damager); + final PlayerData defenderData = SkillAPI.getPlayerData((Player) event.getEntity()); + + for (final String group : SkillAPI.getGroups()) { + final boolean friendly = SkillAPI.getSettings().getGroupSettings(group).isFriendly(); + final PlayerClass attacker = attackerData.getClass(group); + final PlayerClass defender = defenderData.getClass(group); + if (friendly && attacker != null && defender != null && attacker.getData().getRoot() == defender.getData().getRoot()) { + event.setCancelled(true); + } + } + } + } + + /** + * Tracks when a player changes worlds for avoiding accidental skill casts + * @see AddonListener#onSkillUse(PlayerCastSkillEvent) + */ + @EventHandler + public void onChangeWorld(PlayerChangedWorldEvent e) { + startIgnoring(e.getPlayer()); + } + + /** + * Tracks when a player joins for avoiding accidental skill casts + * @see AddonListener#onSkillUse(PlayerCastSkillEvent) + */ + @EventHandler + public void onPlayerJoin(PlayerJoinEvent e) { + startIgnoring(e.getPlayer()); + } + + private void startIgnoring(final Player player) { + if (!SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { + return; + } + + final UUID uuid = player.getUniqueId(); + IGNORE_CASTING.add(uuid); + SkillAPI.schedule(() -> IGNORE_CASTING.remove(uuid), 40); + } + + /** + * Cancels skill casts shortly after changing worlds or joining the server. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onSkillUse(final PlayerCastSkillEvent e) { + if (IGNORE_CASTING.contains(e.getPlayer().getUniqueId())) { + e.setCancelled(true); + } else if (PluginChecker.isWorldGuardActive()) { + if (WorldGuardHook.getRegionIds(e.getPlayer().getLocation()).stream() + .anyMatch(id -> SkillAPI.getSettings().areSkillsDisabledForRegion(id))) { + e.setCancelled(true); + } + } + } + + /** + * Cancels skill casts shortly after changing worlds or joining the server. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onExpGain(final PlayerExperienceGainEvent e) { + if (PluginChecker.isWorldGuardActive()) { + if (WorldGuardHook.getRegionIds(e.getPlayerData().getPlayer().getLocation()).stream() + .anyMatch(id -> SkillAPI.getSettings().isExpDisabledForRegion(id))) { + e.setCancelled(true); + } + } + } +} diff --git a/src/com/sucy/skill/listener/AttributeListener.java b/src/com/sucy/skill/listener/AttributeListener.java index 57a9df84..4f8cd8f2 100644 --- a/src/com/sucy/skill/listener/AttributeListener.java +++ b/src/com/sucy/skill/listener/AttributeListener.java @@ -26,20 +26,26 @@ */ package com.sucy.skill.listener; -import com.rit.sucy.items.InventoryManager; +import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.enums.ExpSource; import com.sucy.skill.api.enums.ManaSource; import com.sucy.skill.api.event.*; import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.hook.CitizensHook; import com.sucy.skill.log.LogType; import com.sucy.skill.log.Logger; import com.sucy.skill.manager.AttributeManager; +import org.bukkit.Bukkit; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; @@ -48,18 +54,25 @@ /** * Listener for managing applying attribute bonuses for players */ -public class AttributeListener implements Listener +public class AttributeListener extends SkillAPIListener { - public static final String MENU_KEY = "skillAPIAttrMenu"; + public static final String PHYSICAL = "physical"; + private static HashMap BONUSES = new HashMap<>(); - private static HashMap bonuses = new HashMap(); + @Override + public void init() { + MainListener.registerJoin(this::onJoin); + } /** * Cleans up the listener on shutdown */ - public static void cleanup() - { - bonuses.clear(); + @Override + public void cleanup() { + for (Player player : Bukkit.getOnlinePlayers()) { + clearBonuses(player); + } + BONUSES.clear(); } /** @@ -69,19 +82,43 @@ public static void cleanup() */ public static void clearBonuses(Player player) { - bonuses.remove(player.getName() + ":" + AttributeManager.MOVE_SPEED); - bonuses.remove(player.getName() + ":" + AttributeManager.HEALTH); - bonuses.remove(player.getName() + ":" + AttributeManager.MANA); + clearLocalBonuses(player); + BONUSES.remove(player.getName() + ":" + AttributeManager.HEALTH); + BONUSES.remove(player.getName() + ":" + AttributeManager.MANA); + } + + private static void clearLocalBonuses(Player player) + { + BONUSES.remove(player.getName() + ":" + AttributeManager.MOVE_SPEED); player.setWalkSpeed(0.2f); + + if (VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { + clear(player, Attribute.GENERIC_ATTACK_SPEED, AttributeManager.ATTACK_SPEED); + clear(player, Attribute.GENERIC_ARMOR, AttributeManager.ARMOR); + clear(player, Attribute.GENERIC_LUCK, AttributeManager.LUCK); + clear(player, Attribute.GENERIC_KNOCKBACK_RESISTANCE, AttributeManager.LUCK); + } + if (VersionManager.isVersionAtLeast(11200)) { + clear(player, Attribute.GENERIC_ARMOR_TOUGHNESS, AttributeManager.ARMOR_TOUGHNESS); + } + } + + private static void clear(Player player, Object attribute, String attribKey) { + if (!BONUSES.containsKey(attribKey)) { + return; + } + + double bonus = BONUSES.remove(attribKey); + AttributeInstance instance = player.getAttribute((Attribute) attribute); + instance.setBaseValue(instance.getBaseValue() - bonus); } /** * Gives players bonus stats on login */ - @EventHandler(priority = EventPriority.HIGH) - public void onJoin(PlayerJoinEvent event) + public void onJoin(final Player player) { - updatePlayer(SkillAPI.getPlayerData(event.getPlayer())); + updatePlayer(SkillAPI.getPlayerData(player)); } /** @@ -92,6 +129,9 @@ public void onJoin(PlayerJoinEvent event) @EventHandler(priority = EventPriority.HIGH) public void onRespawn(PlayerRespawnEvent event) { + if (event.getPlayer().hasMetadata("NPC")) + return; + updatePlayer(SkillAPI.getPlayerData(event.getPlayer())); } @@ -103,6 +143,9 @@ public void onRespawn(PlayerRespawnEvent event) @EventHandler public void onQuit(PlayerQuitEvent event) { + if (event.getPlayer().hasMetadata("NPC")) + return; + clearBonuses(event.getPlayer()); } @@ -157,9 +200,17 @@ public void onPhysicalDamage(PhysicalDamageEvent event) if (event.getDamager() instanceof Player) { Player player = (Player) event.getDamager(); + if (CitizensHook.isNPC(player)) + return; + PlayerData data = SkillAPI.getPlayerData(player); double newAmount = data.scaleStat(AttributeManager.PHYSICAL_DAMAGE, event.getDamage()); + if (event.isProjectile()) { + newAmount = data.scaleStat(AttributeManager.PROJECTILE_DAMAGE, newAmount); + } else { + newAmount = data.scaleStat(AttributeManager.MELEE_DAMAGE, newAmount); + } event.setDamage(newAmount); } @@ -167,9 +218,17 @@ public void onPhysicalDamage(PhysicalDamageEvent event) if (event.getTarget() instanceof Player) { Player player = (Player) event.getTarget(); + if (CitizensHook.isNPC(player)) + return; + PlayerData data = SkillAPI.getPlayerData(player); double newAmount = data.scaleStat(AttributeManager.PHYSICAL_DEFENSE, event.getDamage()); + if (event.isProjectile()) { + newAmount = data.scaleStat(AttributeManager.PROJECTILE_DEFENSE, newAmount); + } else { + newAmount = data.scaleStat(AttributeManager.MELEE_DEFENSE, newAmount); + } event.setDamage(newAmount); } } @@ -180,26 +239,98 @@ public void onPhysicalDamage(PhysicalDamageEvent event) * @param event event details */ @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void onSkillDamage(SkillDamageEvent event) + public void onSkillDamage(final SkillDamageEvent event) { // Skill Damage if (event.getDamager() instanceof Player) { - Player player = (Player) event.getDamager(); - PlayerData data = SkillAPI.getPlayerData(player); + final Player player = (Player) event.getDamager(); + if (CitizensHook.isNPC(player)) + return; - double newAmount = data.scaleStat(AttributeManager.SKILL_DAMAGE, event.getDamage()); - event.setDamage(newAmount); + final PlayerData data = SkillAPI.getPlayerData(player); + + if (event.getClassification().equalsIgnoreCase(PHYSICAL)) { + event.setDamage(data.scaleStat(AttributeManager.PHYSICAL_DAMAGE, event.getDamage())); + } else { + final String classified = AttributeManager.SKILL_DAMAGE + "-" + event.getClassification(); + final double firstPass = data.scaleStat(classified, event.getDamage()); + final double newAmount = data.scaleStat(AttributeManager.SKILL_DAMAGE, firstPass); + event.setDamage(newAmount); + } } // Skill Defense if (event.getTarget() instanceof Player) { - Player player = (Player) event.getTarget(); - PlayerData data = SkillAPI.getPlayerData(player); + final Player player = (Player) event.getTarget(); + if (CitizensHook.isNPC(player)) + return; - double newAmount = data.scaleStat(AttributeManager.SKILL_DEFENSE, event.getDamage()); - event.setDamage(newAmount); + final PlayerData data = SkillAPI.getPlayerData(player); + + if (event.getClassification().equalsIgnoreCase(PHYSICAL)) { + event.setDamage(data.scaleStat(AttributeManager.PHYSICAL_DEFENSE, event.getDamage())); + } else { + final String classified = AttributeManager.SKILL_DEFENSE + "-" + event.getClassification(); + final double firstPass = data.scaleStat(classified, event.getDamage()); + final double newAmount = data.scaleStat(AttributeManager.SKILL_DEFENSE, firstPass); + event.setDamage(newAmount); + } + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onDamage(final EntityDamageEvent event) { + if (!(event.getEntity() instanceof Player)) + return; + + final Player player = (Player)event.getEntity(); + if (CitizensHook.isNPC(player)) { + return; + } + + final PlayerData data = SkillAPI.getPlayerData(player); + event.setDamage(data.scaleStat("defense-" + event.getCause().name().toLowerCase(), event.getDamage())); + } + + @EventHandler(priority = EventPriority.HIGH) + public void onExp(final PlayerExperienceGainEvent event) { + if (event.getSource() != ExpSource.COMMAND) { + final double newExp = event.getPlayerData().scaleStat(AttributeManager.EXPERIENCE, event.getExp()); + event.setExp(newExp); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onWorldChange(PlayerChangedWorldEvent event) { + boolean oldEnabled = SkillAPI.getSettings().isWorldEnabled(event.getFrom()); + boolean newEnabled = SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld()); + if (oldEnabled && !newEnabled) { + clearBonuses(event.getPlayer()); + } else if (!oldEnabled && newEnabled) { + updatePlayer(SkillAPI.getPlayerData(event.getPlayer())); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onHungerChange(final FoodLevelChangeEvent event) { + final Player player = (Player)event.getEntity(); + if (event.getFoodLevel() < player.getFoodLevel()) { + final PlayerData data = SkillAPI.getPlayerData(player); + final int lost = data.subtractHungerValue(player.getFoodLevel() - event.getFoodLevel()); + event.setFoodLevel(player.getFoodLevel() - lost); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) + public void onHungerHeal(final EntityRegainHealthEvent event) { + if (event.getRegainReason() == EntityRegainHealthEvent.RegainReason.SATIATED + && event.getEntity() instanceof Player) { + final Player player = (Player)event.getEntity(); + final PlayerData data = SkillAPI.getPlayerData(player); + final double scaled = data.scaleStat(AttributeManager.HUNGER_HEAL, event.getAmount()); + event.setAmount(scaled); } } @@ -211,29 +342,42 @@ public void onSkillDamage(SkillDamageEvent event) public static void updatePlayer(PlayerData data) { Player player = data.getPlayer(); - if (player != null) + if (player != null && SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { - double change = updateStat(data, AttributeManager.HEALTH, player.getMaxHealth()); + double change = updateStat(data, AttributeManager.HEALTH, player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue(), 0, Double.MAX_VALUE); data.addMaxHealth(change); - change = updateStat(data, AttributeManager.MANA, data.getMaxMana()); + change = updateStat(data, AttributeManager.MANA, data.getMaxMana(), 0, Double.MAX_VALUE); data.addMaxMana(change); - change = updateStat(data, AttributeManager.MOVE_SPEED, player.getWalkSpeed()); - if (change + player.getWalkSpeed() > 1) - { - bonuses.put(AttributeManager.MOVE_SPEED, 0.8); - change = 1 - player.getWalkSpeed(); + change = updateStat(data, AttributeManager.MOVE_SPEED, player.getWalkSpeed(), -2, 1); + player.setWalkSpeed(player.getWalkSpeed() + (float) change); + + if (VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { + update(data, player, Attribute.GENERIC_ATTACK_SPEED, AttributeManager.ATTACK_SPEED, 0, 1024); + update(data, player, Attribute.GENERIC_ARMOR, AttributeManager.ARMOR, 0, 30); + update(data, player, Attribute.GENERIC_LUCK, AttributeManager.LUCK, -1024, 1024); + update(data, player, Attribute.GENERIC_KNOCKBACK_RESISTANCE, AttributeManager.KNOCKBACK_RESIST, 0, 1.0); } - else if (change + player.getWalkSpeed() < -1) - { - bonuses.put(AttributeManager.MOVE_SPEED, -1.8); - change = -1 - player.getWalkSpeed(); + if (VersionManager.isVersionAtLeast(110200)) { + update(data, player, Attribute.GENERIC_ARMOR_TOUGHNESS, AttributeManager.ARMOR_TOUGHNESS, 0, 20); } - player.setWalkSpeed(player.getWalkSpeed() + (float) change); } } + private static void update( + final PlayerData data, + final Player player, + final Object attribute, + final String attribKey, + final double min, + final double max) { + + final AttributeInstance instance = player.getAttribute((Attribute) attribute); + final double change = updateStat(data, attribKey, instance.getBaseValue(), min, max); + instance.setBaseValue(instance.getBaseValue() + change); + } + /** * Refreshes player speed after buffs expire * @@ -241,8 +385,8 @@ else if (change + player.getWalkSpeed() < -1) */ public static void refreshSpeed(Player player) { - bonuses.remove(player.getName() + ":" + AttributeManager.MOVE_SPEED); - double speed = updateStat(SkillAPI.getPlayerData(player), AttributeManager.MOVE_SPEED, 0.2); + BONUSES.remove(player.getName() + ":" + AttributeManager.MOVE_SPEED); + double speed = updateStat(SkillAPI.getPlayerData(player), AttributeManager.MOVE_SPEED, 0.2, -2, 1); player.setWalkSpeed((float) (0.2 + speed)); } @@ -255,67 +399,17 @@ public static void refreshSpeed(Player player) * * @return change in the stat based on current attributes */ - private static double updateStat(PlayerData data, String key, double value) + private static double updateStat(PlayerData data, String key, double value, double min, double max) { Player player = data.getPlayer(); if (player != null) { String mapKey = player.getName() + ":" + key; - double current = bonuses.containsKey(mapKey) ? bonuses.remove(mapKey) : 0; - double updated = data.scaleStat(key, value - current) - value + current; - bonuses.put(mapKey, updated); + double current = BONUSES.containsKey(mapKey) ? BONUSES.remove(mapKey) : 0; + double updated = Math.max(min, Math.min(max, data.scaleStat(key, value - current) - value + current)); + BONUSES.put(mapKey, updated); return updated - current; } return 0; } - - /** - * Handles attribute menu interaction - * - * @param event event details - */ - @EventHandler - public void onClick(InventoryClickEvent event) - { - // Class selection - if (InventoryManager.isMatching(event.getInventory(), MENU_KEY)) - { - // Do nothing when clicking outside the inventory - if (event.getSlot() == -999) - { - return; - } - - boolean top = event.getRawSlot() < event.getView().getTopInventory().getSize(); - AttributeManager manager = SkillAPI.getAttributeManager(); - - // Interact with the skill tree when clicking in the top region - if (top) - { - if (event.getSlot() < manager.getKeys().size() || event.getCursor() != null) - { - event.setCancelled(true); - - PlayerData data = SkillAPI.getPlayerData((Player) event.getWhoClicked()); - if (event.isRightClick() && SkillAPI.getSettings().isAttributesDowngrade()) - { - data.refundAttribute(manager.getKeys().toArray()[event.getSlot()].toString()); - } - else if (event.isLeftClick()) - { - - Object[] keys = manager.getKeys().toArray(); - data.upAttribute(keys[event.getSlot()].toString()); - } - data.openAttributeMenu(); - } - } - - // Do not allow shift clicking items into the inventory - else if (event.isShiftClick()) - { - event.setCancelled(true); - } - } - } } diff --git a/src/com/sucy/skill/listener/BarListener.java b/src/com/sucy/skill/listener/BarListener.java index bcae4e86..3e353f91 100644 --- a/src/com/sucy/skill/listener/BarListener.java +++ b/src/com/sucy/skill/listener/BarListener.java @@ -31,15 +31,20 @@ import com.rit.sucy.gui.MapMenuManager; import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.event.PlayerAccountChangeEvent; import com.sucy.skill.api.event.PlayerClassChangeEvent; import com.sucy.skill.api.event.PlayerSkillDowngradeEvent; import com.sucy.skill.api.event.PlayerSkillUnlockEvent; import com.sucy.skill.api.event.PlayerSkillUpgradeEvent; import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.api.player.PlayerSkillBar; -import com.sucy.skill.gui.SkillDetailMenu; -import com.sucy.skill.gui.SkillListMenu; -import org.bukkit.*; +import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.gui.handlers.SkillHandler; +import com.sucy.skill.gui.map.SkillDetailMenu; +import com.sucy.skill.gui.map.SkillListMenu; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -47,8 +52,11 @@ import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.event.player.*; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; import java.util.HashSet; import java.util.UUID; @@ -57,12 +65,15 @@ * Handles interactions with skill bars. This shouldn't be * use by other plugins as it is handled by the API. */ -public class BarListener +public class BarListener extends SkillAPIListener { private final HashSet ignored = new HashSet(); - public BarListener() + @Override + public void init() { + MainListener.registerJoin(this::onJoin); + MainListener.registerClear(this::handleClear); for (Player player : VersionManager.getOnlinePlayers()) { if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { @@ -73,18 +84,33 @@ public BarListener() } } + @Override + public void cleanup() { + for (Player player : Bukkit.getOnlinePlayers()) { + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { + cleanup(player); + } + } + } + + private void cleanup(Player player) { + PlayerData data = SkillAPI.getPlayerData(player); + if (data.getSkillBar().isSetup()) + { + data.getSkillBar().clear(player); + } + ignored.remove(player.getUniqueId()); + } + /** * Sets up skill bars on joining - * - * @param event event details */ - @EventHandler - public void onJoin(PlayerJoinEvent event) + public void onJoin(final Player player) { - if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) { - PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { + final PlayerData data = SkillAPI.getPlayerData(player); if (data.hasClass()) { - data.getSkillBar().setup(event.getPlayer()); + data.getSkillBar().setup(player); } } } @@ -97,11 +123,7 @@ public void onJoin(PlayerJoinEvent event) @EventHandler public void onQuit(PlayerQuitEvent event) { - PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); - if (data.hasClass()) - { - data.getSkillBar().clear(event.getPlayer()); - } + cleanup(event.getPlayer()); } /** @@ -200,6 +222,9 @@ public void run() { @EventHandler(priority = EventPriority.LOWEST) public void onDeath(PlayerDeathEvent event) { + if (!SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) + return; + PlayerData data = SkillAPI.getPlayerData(event.getEntity()); if (data.getSkillBar().isSetup()) { for (int i = 0; i < 9; i++) { @@ -227,12 +252,31 @@ public void onRespawn(PlayerRespawnEvent event) data.getSkillBar().setup(event.getPlayer()); data.getSkillBar().update(event.getPlayer()); if (data.getSkillBar().isSetup() - && SkillAPI.getSettings().isWorldEnabled(event.getRespawnLocation().getWorld()) - && !data.getSkillBar().isWeaponSlot(0)) + && SkillAPI.getSettings().isWorldEnabled(event.getRespawnLocation().getWorld()) + && !data.getSkillBar().isWeaponSlot(0)) ignored.add(event.getPlayer().getUniqueId()); } } + /** + * Handles assigning skills to the skill bar + * + * @param event event details + */ + @EventHandler + public void onAssign(final InventoryClickEvent event) { + if (event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD && event.getInventory().getHolder() instanceof SkillHandler) { + final PlayerData data = SkillAPI.getPlayerData((Player) event.getWhoClicked()); + if (data.getSkillBar().isSetup() && !data.getSkillBar().isWeaponSlot(event.getHotbarButton())) { + final SkillHandler handler = (SkillHandler) event.getInventory().getHolder(); + final Skill skill = handler.get(event.getSlot()); + if (skill != null && skill.canCast()) { + data.getSkillBar().assign(data.getSkill(skill.getName()), event.getHotbarButton()); + } + } + } + } + /** * Event for assigning skills to the skill bar * @@ -247,8 +291,8 @@ public void onToggle(InventoryClickEvent event) if (!skillBar.isSetup()) return; - if (event.getAction() == InventoryAction.HOTBAR_SWAP - && !skillBar.isWeaponSlot(event.getHotbarButton())) + if ((event.getAction() == InventoryAction.HOTBAR_SWAP || event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD) + && (!skillBar.isWeaponSlot(event.getHotbarButton()) || !skillBar.isWeaponSlot(event.getSlot()))) { event.setCancelled(true); return; @@ -256,12 +300,12 @@ public void onToggle(InventoryClickEvent event) // Prevent moving skill icons int slot = event.getSlot(); - if (event.getSlot() < 9 && event.getSlotType() == InventoryType.SlotType.QUICKBAR) + if (event.getSlot() < 9 && event.getRawSlot() > event.getView().getTopInventory().getSize()) { if (event.getClick() == ClickType.LEFT || event.getClick() == ClickType.SHIFT_LEFT) event.setCancelled(!skillBar.isWeaponSlot(slot)); else if ((event.getClick() == ClickType.RIGHT || event.getClick() == ClickType.SHIFT_RIGHT) - && (!skillBar.isWeaponSlot(slot) || (skillBar.isWeaponSlot(slot) && (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR)))) + && (!skillBar.isWeaponSlot(slot) || (skillBar.isWeaponSlot(slot) && (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR)))) { event.setCancelled(true); skillBar.toggleSlot(slot); @@ -324,6 +368,13 @@ public void onCast(PlayerItemHeldEvent event) } } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onChangeAccount(PlayerAccountChangeEvent event) { + if (event.getPreviousAccount().getSkillBar().isSetup()) { + event.getPreviousAccount().getSkillBar().clear(event.getPreviousAccount().getPlayer()); + } + } + /** * Clears or sets up the skill bar upon changing from or to creative mode * @@ -337,18 +388,17 @@ public void onChangeMode(PlayerGameModeChangeEvent event) if (event.getNewGameMode() == GameMode.CREATIVE && data.hasClass()) data.getSkillBar().clear(event.getPlayer()); - // Setup on leaving creative mode + // Setup on leaving creative mode else if (event.getPlayer().getGameMode() == GameMode.CREATIVE && data.hasClass()) { final Player player = event.getPlayer(); - SkillAPI.schedule(new Runnable() - { - @Override - public void run() - { - SkillAPI.getPlayerData(player).getSkillBar().setup(player); - } - }, 0); + SkillAPI.schedule(() -> SkillAPI.getPlayerData(player).getSkillBar().setup(player), 0); } } + + private void handleClear(final Player player) { + final PlayerSkillBar skillBar = SkillAPI.getPlayerData(player).getSkillBar(); + if (skillBar.isSetup()) + skillBar.update(player); + } } diff --git a/src/com/sucy/skill/listener/BindListener.java b/src/com/sucy/skill/listener/BindListener.java new file mode 100644 index 00000000..3b916854 --- /dev/null +++ b/src/com/sucy/skill/listener/BindListener.java @@ -0,0 +1,81 @@ +/** + * SkillAPI + * com.sucy.skill.listener.BindListener + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software") to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.listener; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.event.KeyPressEvent; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.player.PlayerSkill; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; + +/** + * A listener that handles casting skills through binds. This shouldn't be + * use by other plugins as it is handled by the API. + */ +public class BindListener extends SkillAPIListener { + /** + * Handles interact events to check when a player right clicks with + * a bound item to cast a skill. + * + * @param event event details + */ + @EventHandler(priority = EventPriority.HIGHEST) + public void onInteract(KeyPressEvent event) { + Player player = event.getPlayer(); + if (!SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { + return; + } + + PlayerData data = SkillAPI.getPlayerData(player); + String dispname = null; + Material heldItem = player.getInventory().getItemInMainHand().getType(); + // Must be right clicking with an item + if (event.getKey() != KeyPressEvent.Key.RIGHT || heldItem == null) { + return; + } + //disp name + if(player.getInventory().getItemInMainHand().hasItemMeta() && player.getInventory().getItemInMainHand().getItemMeta().hasDisplayName()) { + dispname = player.getInventory().getItemInMainHand().getItemMeta().getDisplayName(); + } + // Must have a valid item + final PlayerSkill skill; + if(dispname != null) { + skill = data.getBoundSkill(dispname); + }else { + skill = data.getBoundSkill(heldItem); + } + if (skill == null || !SkillAPI.isSkillRegistered(skill.getData().getName())) { + return; + } + + // Cast the skill + data.cast(skill); + } +} diff --git a/src/com/sucy/skill/listener/BuffListener.java b/src/com/sucy/skill/listener/BuffListener.java new file mode 100644 index 00000000..c406f7b3 --- /dev/null +++ b/src/com/sucy/skill/listener/BuffListener.java @@ -0,0 +1,83 @@ +package com.sucy.skill.listener; + +import com.sucy.skill.api.event.PhysicalDamageEvent; +import com.sucy.skill.api.event.SkillDamageEvent; +import com.sucy.skill.api.event.SkillHealEvent; +import com.sucy.skill.api.util.BuffManager; +import com.sucy.skill.api.util.BuffType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; + +import static com.sucy.skill.listener.AttributeListener.PHYSICAL; + +/** + * SkillAPI © 2017 + * com.sucy.skill.listener.BuffListener + */ +public class BuffListener extends SkillAPIListener { + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onPhysical(final PhysicalDamageEvent event) { + final double withDamageBuffs = BuffManager.apply( + event.getDamager(), + BuffType.DAMAGE, + event.getDamage()); + final double withDefenseBuffs = BuffManager.apply( + event.getTarget(), + BuffType.DEFENSE, + withDamageBuffs); + + event.setDamage(withDefenseBuffs); + if (withDefenseBuffs <= 0) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onSkill(final SkillDamageEvent event) { + if (event.getClassification().equalsIgnoreCase(PHYSICAL)) { + final double withDamageBuffs = BuffManager.apply( + event.getDamager(), + BuffType.DAMAGE, + event.getDamage()); + final double withDefenseBuffs = BuffManager.apply( + event.getTarget(), + BuffType.DEFENSE, + withDamageBuffs); + + event.setDamage(withDefenseBuffs); + if (withDefenseBuffs <= 0) { + event.setCancelled(true); + } + } else { + final double withDamageBuffs = BuffManager.apply( + event.getDamager(), + BuffType.SKILL_DAMAGE, + event.getClassification(), + event.getDamage()); + final double withDefenseBuffs = BuffManager.apply( + event.getTarget(), + BuffType.SKILL_DEFENSE, + event.getClassification(), + withDamageBuffs); + + event.setDamage(withDefenseBuffs); + if (withDefenseBuffs <= 0) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onHeal(final SkillHealEvent event) { + final double withBuff = BuffManager.apply( + event.getTarget(), + BuffType.HEALING, + event.getAmount()); + + event.setAmount(withBuff); + if (withBuff <= 0) { + event.setCancelled(true); + } + } +} diff --git a/src/com/sucy/skill/listener/CastCombatListener.java b/src/com/sucy/skill/listener/CastCombatListener.java new file mode 100644 index 00000000..3b41a5a5 --- /dev/null +++ b/src/com/sucy/skill/listener/CastCombatListener.java @@ -0,0 +1,451 @@ +/** + * SkillAPI + * com.sucy.skill.listener.BarListener + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software") to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.listener; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.event.PlayerAccountChangeEvent; +import com.sucy.skill.api.event.PlayerClassChangeEvent; +import com.sucy.skill.api.event.PlayerSkillDowngradeEvent; +import com.sucy.skill.api.event.PlayerSkillUnlockEvent; +import com.sucy.skill.api.event.PlayerSkillUpgradeEvent; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.player.PlayerSkillBar; +import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.api.util.ItemSerializer; +import com.sucy.skill.gui.handlers.SkillHandler; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +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.InventoryType; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.UUID; + +/** + * Handles interactions with skill bars. This shouldn't be + * use by other plugins as it is handled by the API. + */ +public class CastCombatListener extends SkillAPIListener +{ + private final String ITEM_SAVE_KEY = "combatItems"; + + private final HashMap backup = new HashMap(); + + private final HashSet ignored = new HashSet(); + + private int slot = SkillAPI.getSettings().getCastSlot(); + + @Override + public void init() + { + MainListener.registerJoin(this::init); + MainListener.registerClear(this::handleClear); + for (Player player : Bukkit.getOnlinePlayers()) + init(player); + } + + private void init(Player player) + { + if (!SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + return; + + PlayerData data = SkillAPI.getPlayerData(player); + if (data.getExtraData().has(ITEM_SAVE_KEY)) { + ItemStack[] items = ItemSerializer.fromBase64(data.getExtraData().getString(ITEM_SAVE_KEY)); + if (items != null) + backup.put(player.getUniqueId(), items); + else + backup.put(player.getUniqueId(), new ItemStack[9]); + } + else + backup.put(player.getUniqueId(), new ItemStack[9]); + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + { + PlayerInventory inv = player.getInventory(); + ItemStack item = inv.getItem(slot); + inv.setItem(slot, SkillAPI.getSettings().getCastItem()); + if (item != null && item.getType() != Material.AIR) { + for (ItemStack overflow : inv.addItem(item).values()) + player.getWorld().dropItemNaturally(player.getLocation(), overflow); + } + inv.getItem(slot).setAmount(1); + + int playerSlot = player.getInventory().getHeldItemSlot(); + while (!data.getSkillBar().isWeaponSlot(playerSlot) || slot == playerSlot) { + playerSlot = (playerSlot + 1) % 9; + } + if (playerSlot != player.getInventory().getHeldItemSlot()) { + player.getInventory().setHeldItemSlot(playerSlot); + } + } + } + + @Override + public void cleanup() { + for (Player player : Bukkit.getOnlinePlayers()) { + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { + cleanup(player); + } + } + } + + private void cleanup(Player player) + { + ignored.remove(player.getUniqueId()); + PlayerData data = SkillAPI.getPlayerData(player); + PlayerSkillBar bar = data.getSkillBar(); + if (bar.isSetup()) + toggle(player); + player.getInventory().setItem(slot, null); + ItemStack[] restore = backup.remove(player.getUniqueId()); + data.getExtraData().set(ITEM_SAVE_KEY, ItemSerializer.toBase64(restore)); + } + + private void toggle(Player player) + { + ItemStack[] items = backup.get(player.getUniqueId()); + if (items == null) { + items = new ItemStack[9]; + } + ItemStack[] temp = new ItemStack[9]; + PlayerData data = SkillAPI.getPlayerData(player); + for (int i = 0; i < items.length; i++) { + if (i == slot) + continue; + if (data.getSkillBar().isSetup() && !data.getSkillBar().isWeaponSlot(i)) + continue; + + temp[i] = player.getInventory().getItem(i); + player.getInventory().setItem(i, null); + } + backup.put(player.getUniqueId(), temp); + if (data.getSkillBar().isSetup()) + data.getSkillBar().clear(player); + else + data.getSkillBar().setup(player); + for (int i = 0; i < 9; i++) { + if (items[i] != null) + player.getInventory().setItem(i, items[i]); + } + } + + /** + * Clears skill bars upon quitting the game + * + * @param event event details + */ + @EventHandler(priority = EventPriority.MONITOR) + public void onQuit(PlayerQuitEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) + cleanup(event.getPlayer()); + } + + /** + * Manages setting up and clearing the skill bar when a player changes professions + * + * @param event event details + */ + @EventHandler + public void onProfess(PlayerClassChangeEvent event) + { + Player p = event.getPlayerData().getPlayer(); + + if (event.getPreviousClass() != null && event.getNewClass() == null) + { + PlayerSkillBar bar = event.getPlayerData().getSkillBar(); + bar.reset(); + bar.clear(p); + } + } + + /** + * Adds unlocked skills to the skill bar if applicable + * + * @param event event details + */ + @EventHandler + public void onUnlock(PlayerSkillUnlockEvent event) + { + if (!event.getUnlockedSkill().getData().canCast() || event.getPlayerData().getPlayer() == null) + { + return; + } + event.getPlayerData().getSkillBar().unlock(event.getUnlockedSkill()); + } + + /** + * Updates the skill bar when a skill is upgraded + * + * @param event event details + */ + @EventHandler + public void onUpgrade(final PlayerSkillUpgradeEvent event) + { + final Player player = event.getPlayerData().getPlayer(); + if (player != null && event.getPlayerData().getSkillBar().isSetup()) + { + SkillAPI.schedule(new Runnable() + { + @Override + public void run() + { + event.getPlayerData().getSkillBar().update(player); + } + }, 0); + } + } + + /** + * Updates a player's skill bar when downgrading a skill to level 0 + * + * @param event event details + */ + @EventHandler + public void onDowngrade(final PlayerSkillDowngradeEvent event) + { + if (event.getPlayerData().getSkillBar().isSetup()) { + SkillAPI.schedule(new Runnable() { + @Override + public void run() { + SkillAPI.getPlayerData(event.getPlayerData().getPlayer()) + .getSkillBar() + .update(event.getPlayerData().getPlayer()); + } + }, 1); + } + } + + /** + * Handles assigning skills to the skill bar + * + * @param event event details + */ + @EventHandler + public void onAssign(final InventoryClickEvent event) { + if (event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD && event.getInventory().getHolder() instanceof SkillHandler) { + final PlayerData data = SkillAPI.getPlayerData((Player) event.getWhoClicked()); + if (data.getSkillBar().isSetup() && !data.getSkillBar().isWeaponSlot(event.getHotbarButton())) { + final SkillHandler handler = (SkillHandler) event.getInventory().getHolder(); + final Skill skill = handler.get(event.getSlot()); + if (skill != null && skill.canCast()) { + data.getSkillBar().assign(data.getSkill(skill.getName()), event.getHotbarButton()); + } + } + } + } + + /** + * Clears the skill bar on death + * + * @param event event details + */ + @EventHandler(priority = EventPriority.LOWEST) + public void onDeath(PlayerDeathEvent event) + { + if (!SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) + return; + if (event.getEntity().getWorld().getGameRuleValue("keepInventory").equals("true")) + return; + + PlayerData data = SkillAPI.getPlayerData(event.getEntity()); + if (data.getSkillBar().isSetup()) { + for (int i = 0; i < 9; i++) { + if (!data.getSkillBar().isWeaponSlot(i)) + event.getDrops().remove(event.getEntity().getInventory().getItem(i)); + } + data.getSkillBar().clear(event.getEntity()); + } + event.getDrops().remove(event.getEntity().getInventory().getItem(slot)); + event.getEntity().getInventory().setItem(slot, null); + + ItemStack[] hidden = backup.get(event.getEntity().getUniqueId()); + for (ItemStack item : hidden) { + if (item != null) { + event.getDrops().add(item); + } + } + backup.put(event.getEntity().getUniqueId(), new ItemStack[9]); + } + + /** + * Sets the skill bar back up on respawn + * + * @param event event details + */ + @EventHandler(priority = EventPriority.MONITOR) + public void onRespawn(PlayerRespawnEvent event) + { + if (!event.getPlayer().getWorld().getGameRuleValue("keepInventory").equals("true")) + init(event.getPlayer()); + } + + @EventHandler + public void onDupe(InventoryClickEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getWhoClicked().getWorld())) { + if (event.getSlot() == slot && event.getSlotType() == InventoryType.SlotType.QUICKBAR) + event.setCancelled(true); + else if ((event.getAction() == InventoryAction.HOTBAR_SWAP || event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD) + && event.getHotbarButton() == slot) + event.setCancelled(true); + } + } + + /** + * Event for assigning skills to the skill bar + * + * @param event event details + */ + @EventHandler(priority = EventPriority.HIGHEST) + public void onToggle(InventoryClickEvent event) + { + // Must click on an active skill bar + PlayerData data = SkillAPI.getPlayerData((Player) event.getWhoClicked()); + final PlayerSkillBar skillBar = data.getSkillBar(); + if (!skillBar.isSetup()) + return; + + if ((event.getAction() == InventoryAction.HOTBAR_SWAP || event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD) + && (!skillBar.isWeaponSlot(event.getHotbarButton()) || !skillBar.isWeaponSlot(event.getSlot()))) + { + event.setCancelled(true); + return; + } + + + // Prevent moving skill icons + int slot = event.getSlot(); + if (event.getSlot() < 9 && event.getRawSlot() > event.getView().getTopInventory().getSize()) + { + if (event.getClick() == ClickType.LEFT || event.getClick() == ClickType.SHIFT_LEFT) + event.setCancelled(!skillBar.isWeaponSlot(slot)); + else if ((event.getClick() == ClickType.RIGHT || event.getClick() == ClickType.SHIFT_RIGHT) + && (!skillBar.isWeaponSlot(slot) || (skillBar.isWeaponSlot(slot) && (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR)))) + { + event.setCancelled(true); + skillBar.toggleSlot(slot); + } + else if (event.getAction().name().startsWith("DROP")) + event.setCancelled(!skillBar.isWeaponSlot(slot)); + } + } + + /** + * Ignores the next cast upon changing worlds due to the forced slot + * + * @param event event details + */ + @EventHandler(priority = EventPriority.LOWEST) + public void onChangeWorld(PlayerChangedWorldEvent event) + { + PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); + boolean enabled = SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld()); + boolean wasEnabled = SkillAPI.getSettings().isWorldEnabled(event.getFrom()); + if (data.hasClass() && data.getSkillBar().isSetup() && enabled) + ignored.add(event.getPlayer().getUniqueId()); + if (enabled && !wasEnabled) + init(event.getPlayer()); + else if (!enabled && wasEnabled) + cleanup(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onChangeAccount(PlayerAccountChangeEvent event) { + if (event.getPreviousAccount().getSkillBar().isSetup()) { + toggle(event.getPreviousAccount().getPlayer()); + } + } + + /** + * Applies skill bar actions when pressing the number keys + * + * @param event event details + */ + @EventHandler + public void onCast(PlayerItemHeldEvent event) + { + if (!SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) + return; + + if (event.getNewSlot() == slot) + { + event.setCancelled(true); + if (event.getPlayer().getGameMode() != GameMode.CREATIVE) + toggle(event.getPlayer()); + return; + } + + // Must be a skill slot when the bar is set up + PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); + PlayerSkillBar bar = data.getSkillBar(); + if (!bar.isWeaponSlot(event.getNewSlot()) && bar.isSetup()) + { + event.setCancelled(true); + if (ignored.remove(event.getPlayer().getUniqueId())) + return; + bar.apply(event.getNewSlot()); + } + } + + /** + * Clears or sets up the skill bar upon changing from or to creative mode + * + * @param event event details + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onChangeMode(PlayerGameModeChangeEvent event) + { + // Clear on entering creative mode + final PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); + if (event.getNewGameMode() == GameMode.CREATIVE && data.getSkillBar().isSetup()) + toggle(data.getPlayer()); + } + + private void handleClear(final Player player) { + backup.put(player.getUniqueId(), new ItemStack[9]); + PlayerSkillBar skillBar = SkillAPI.getPlayerData(player).getSkillBar(); + if (skillBar.isSetup()) + skillBar.update(player); + player.getInventory().setItem(slot, SkillAPI.getSettings().getCastItem()); + } +} diff --git a/src/com/sucy/skill/listener/CastItemListener.java b/src/com/sucy/skill/listener/CastItemListener.java new file mode 100644 index 00000000..f6d197a7 --- /dev/null +++ b/src/com/sucy/skill/listener/CastItemListener.java @@ -0,0 +1,247 @@ +/** + * SkillAPI + * com.sucy.skill.listener.CastItemListener + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.listener; + +import com.rit.sucy.player.PlayerUUIDs; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.event.PlayerClassChangeEvent; +import com.sucy.skill.api.event.PlayerSkillUnlockEvent; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.player.PlayerSkillSlot; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +import java.util.HashMap; +import java.util.UUID; + +/** + * Handles the alternate casting option for casting via a cycling slot + */ +public class CastItemListener extends SkillAPIListener +{ + private static HashMap data = new HashMap(); + + private static int slot; + + @Override + public void init() + { + slot = SkillAPI.getSettings().getCastSlot(); + MainListener.registerJoin(this::init); + MainListener.registerClear(this::handleClear); + for (Player player : Bukkit.getOnlinePlayers()) + init(player); + } + + /** + * Cleans up the listener functions + */ + @Override + public void cleanup() + { + if (slot == -1) + return; + + for (Player player : Bukkit.getOnlinePlayers()) + cleanup(player); + slot = -1; + } + + private static void cleanup(Player player) + { + data.remove(player.getUniqueId()); + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + player.getInventory().setItem(slot, null); + } + + /** + * Re-initializes cast data on class change + * + * @param event event details + */ + @EventHandler + public void onClassChange(PlayerClassChangeEvent event) + { + data.get(event.getPlayerData().getPlayer().getUniqueId()).init(event.getPlayerData()); + } + + /** + * Enables/disables cast when changing worlds + * + * @param event event details + */ + @EventHandler + public void onWorldChange(PlayerChangedWorldEvent event) + { + boolean from = SkillAPI.getSettings().isWorldEnabled(event.getFrom()); + boolean to = SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld()); + if (from && !to) + event.getPlayer().getInventory().setItem(SkillAPI.getSettings().getCastSlot(), null); + else + init(event.getPlayer()); + } + + private PlayerSkillSlot get(Player player) + { + return data.get(player.getUniqueId()); + } + + private PlayerSkillSlot get(PlayerData dataE) + { + return data.get(PlayerUUIDs.getUUID(dataE.getPlayerName())); + } + + /** + * Gives the player the cast item + * + * @param player player to give to + */ + private void init(Player player) + { + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + { + PlayerSkillSlot slotData = new PlayerSkillSlot(); + data.put(player.getUniqueId(), slotData); + slotData.init(SkillAPI.getPlayerData(player)); + + PlayerInventory inv = player.getInventory(); + int slot = SkillAPI.getSettings().getCastSlot(); + ItemStack item = inv.getItem(slot); + slotData.updateItem(player); + if (item != null && item.getType() != Material.AIR) + inv.addItem(item); + } + } + + /** + * Removes the cast item on quit + * + * @param event event details + */ + + @EventHandler + public void onQuit(PlayerQuitEvent event) + { + cleanup(event.getPlayer()); + } + + /** + * Adds unlocked skills to the skill bar if applicable + * + * @param event event details + */ + @EventHandler + public void onUnlock(PlayerSkillUnlockEvent event) + { + get(event.getPlayerData()).unlock(event.getUnlockedSkill()); + } + + /** + * Prevents moving the cast item + * + * @param event event details + */ + @EventHandler + public void onClick(InventoryClickEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getWhoClicked().getWorld())) { + if (event.getSlot() == slot && event.getSlotType() == InventoryType.SlotType.QUICKBAR) + event.setCancelled(true); + else if (event.getAction() == InventoryAction.HOTBAR_SWAP + && event.getHotbarButton() == slot) + event.setCancelled(true); + } + } + + /** + * Casts a skill when dropping the cast item + * + * @param event event details + */ + @EventHandler + public void onDrop(PlayerDropItemEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld()) + && event.getPlayer().getInventory().getHeldItemSlot() == slot) + { + event.setCancelled(true); + get(event.getPlayer()).activate(); + } + } + + @EventHandler + public void onDeath(PlayerDeathEvent event) { + if (SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) { + event.getDrops().remove(event.getEntity().getInventory().getItem(slot)); + } + cleanup(event.getEntity()); + + } + + @EventHandler + public void onRespawn(PlayerRespawnEvent event) { + + init(event.getPlayer()); + + } + + /** + * Cycles through skills upon interact + * + * @param event event details + */ + @EventHandler + public void onInteract(PlayerInteractEvent event) + { + // Cycling skills + if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld()) + && event.getPlayer().getInventory().getHeldItemSlot() == slot) + { + event.setCancelled(true); + if (event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) get(event.getPlayer()).next(); + else if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) get(event.getPlayer()).prev(); + } + } + + private void handleClear(final Player player) { + player.getInventory().setItem(slot, SkillAPI.getSettings().getCastItem()); + } +} diff --git a/src/com/sucy/skill/listener/CastListener.java b/src/com/sucy/skill/listener/CastListener.java index 538ea3fb..e67de762 100644 --- a/src/com/sucy/skill/listener/CastListener.java +++ b/src/com/sucy/skill/listener/CastListener.java @@ -4,10 +4,10 @@ * * The MIT License (MIT) * - * Copyright (c) 2014 Steven Sucy + * Copyright (c) 2016 Steven Sucy * * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software") to deal + * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is @@ -27,58 +27,225 @@ package com.sucy.skill.listener; import com.sucy.skill.SkillAPI; -import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.event.PlayerClassChangeEvent; +import com.sucy.skill.api.event.PlayerSkillUnlockEvent; +import com.sucy.skill.cast.PlayerCastBars; +import com.sucy.skill.thread.MainThread; +import com.sucy.skill.thread.ThreadTask; +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.entity.EntityDamageEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; /** - * A listener that handles casting skills through binds. This shouldn't be - * use by other plugins as it is handled by the API. + * Listener for the main casting system */ -public class CastListener implements Listener +public class CastListener extends SkillAPIListener { + private static int slot = SkillAPI.getSettings().getCastSlot(); + + @Override + public void init() + { + MainListener.registerJoin(this::init); + MainListener.registerClear(this::handleClear); + for (Player player : Bukkit.getOnlinePlayers()) + init(player); + } + /** - * Handles interact events to check when a player right clicks with - * a bound item to cast a skill. - * - * @param event event details + * Cleans up */ - @EventHandler(priority = EventPriority.HIGHEST) - public void onInteract(PlayerInteractEvent event) + @Override + public void cleanup() { - Player player = event.getPlayer(); - if (!SkillAPI.getSettings().isWorldEnabled(player.getWorld())) - { + if (slot == -1) return; + + for (Player player : Bukkit.getOnlinePlayers()) + cleanup(player); + slot = -1; + } + + private static void cleanup(Player player) + { + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + forceCleanup(player); + } + + private static void forceCleanup(Player player) { + SkillAPI.getPlayerData(player).getCastBars().restore(player); + player.getInventory().setItem(slot, null); + } + + @EventHandler + public void onDamaged(EntityDamageEvent event) { + if (event.getEntity() instanceof Player) { + Player player = (Player) event.getEntity(); + SkillAPI.getPlayerData(player).getCastBars().restore(player); } + } + + @EventHandler + public void onClassChange(PlayerClassChangeEvent event) + { + event.getPlayerData().getCastBars().reset(); + } - PlayerData data = SkillAPI.getPlayerData(player); - Material heldItem = player.getItemInHand().getType(); + @EventHandler + public void onWorldChange(PlayerChangedWorldEvent event) + { + boolean from = SkillAPI.getSettings().isWorldEnabled(event.getFrom()); + boolean to = SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld()); + if (from && !to) + forceCleanup(event.getPlayer()); + else + init(event.getPlayer()); + } - // Must be on right click - if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) + @EventHandler + public void onDeath(PlayerDeathEvent event) { + if (SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) { + event.getDrops().remove(event.getEntity().getInventory().getItem(slot)); + } + } + + private void init(Player player) + { + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) { + PlayerInventory inv = player.getInventory(); + int slot = SkillAPI.getSettings().getCastSlot(); + ItemStack item = inv.getItem(slot); + inv.setItem(slot, SkillAPI.getSettings().getCastItem()); + if (item != null && item.getType() != Material.AIR) + inv.addItem(item); + inv.getItem(slot).setAmount(1); + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) + { + cleanup(event.getPlayer()); + } + + @EventHandler + public void onOpen(InventoryOpenEvent event) + { + SkillAPI.getPlayerData((Player) event.getPlayer()).getCastBars().handleOpen((Player) event.getPlayer()); + } + + @EventHandler + public void onClose(InventoryCloseEvent event) + { + SkillAPI.getPlayerData((Player) event.getPlayer()).getCastBars().restore((Player) event.getPlayer()); + init((Player) event.getPlayer()); + } + + /** + * Adds unlocked skills to the skill bar if applicable + * + * @param event event details + */ + @EventHandler + public void onUnlock(PlayerSkillUnlockEvent event) + { + if (event.getUnlockedSkill().getData().canCast() && event.getPlayerData().getPlayer() != null) + event.getPlayerData().getCastBars().unlock(event.getUnlockedSkill()); + } + + @EventHandler + public void onClick(InventoryClickEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getWhoClicked().getWorld())) { + if (event.getSlot() == slot && event.getSlotType() == InventoryType.SlotType.QUICKBAR) + event.setCancelled(true); + else if ((event.getAction() == InventoryAction.HOTBAR_SWAP || event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD) + && event.getHotbarButton() == slot) + event.setCancelled(true); + } + } + + @EventHandler + public void onDrop(PlayerDropItemEvent event) + { + if (!SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) return; + + if (SkillAPI.getPlayerData(event.getPlayer()).getCastBars().handleInteract(event.getPlayer())) + { + event.getItemDrop().remove(); } - // Cannot be cancelled if clicking on a block - if (event.isCancelled() && event.getAction() == Action.RIGHT_CLICK_BLOCK) + else if (event.getPlayer().getInventory().getHeldItemSlot() == slot) { + event.getItemDrop().remove(); + MainThread.register(new OrganizerTask(event.getPlayer())); + } + } + + @EventHandler + public void onInteract(PlayerInteractEvent event) + { + if (!SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) return; + + PlayerCastBars bars = SkillAPI.getPlayerData(event.getPlayer()).getCastBars(); + + // Interaction while in a view + if (bars.isHovering()) + event.setCancelled(true); + + // Entering a view + else if (event.getPlayer().getInventory().getHeldItemSlot() == slot) + { + event.setCancelled(true); + if (event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) + bars.showHoverBar(event.getPlayer()); + else if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) + bars.showInstantBar(event.getPlayer()); } + } + + @EventHandler + public void onHeld(PlayerItemHeldEvent event) + { + SkillAPI.getPlayerData(event.getPlayer()).getCastBars().handle(event); + } - // Must have a valid item - if (heldItem == null || data.getBoundSkill(heldItem) == null || !SkillAPI.isSkillRegistered(data.getBoundSkill(heldItem).getData().getName())) + private void handleClear(final Player player) { + player.getInventory().setItem(slot, SkillAPI.getSettings().getCastItem()); + } + + private class OrganizerTask extends ThreadTask + { + private Player player; + + public OrganizerTask(Player player) { - return; + this.player = player; } - // Cast the skill - data.cast(data.getBoundSkill(heldItem)); + @Override + public void run() + { + SkillAPI.getPlayerData(player).getCastBars().showOrganizer(player); + } } } diff --git a/src/com/sucy/skill/listener/CastOffhandListener.java b/src/com/sucy/skill/listener/CastOffhandListener.java new file mode 100644 index 00000000..efb981fc --- /dev/null +++ b/src/com/sucy/skill/listener/CastOffhandListener.java @@ -0,0 +1,42 @@ +/** + * SkillAPI + * com.sucy.skill.listener.CastOffhandListener + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.listener; + +import com.sucy.skill.SkillAPI; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; + +public class CastOffhandListener extends SkillAPIListener { + private static int slot = SkillAPI.getSettings().getCastSlot(); + + @EventHandler + public void handleOffhandDupe(PlayerSwapHandItemsEvent event) + { + if (event.getOffHandItem() != null && event.getPlayer().getInventory().getHeldItemSlot() == slot) + event.setCancelled(true); + } +} diff --git a/src/com/sucy/skill/listener/ClickListener.java b/src/com/sucy/skill/listener/ClickListener.java index 15bd6f89..d704560d 100644 --- a/src/com/sucy/skill/listener/ClickListener.java +++ b/src/com/sucy/skill/listener/ClickListener.java @@ -26,57 +26,39 @@ */ package com.sucy.skill.listener; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.api.player.PlayerCombos; -import com.sucy.skill.data.Click; +import com.sucy.skill.api.event.KeyPressEvent; +import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; /** * Handles transferring click actions by the player to * combos that cast skills. */ -public class ClickListener implements Listener -{ +public class ClickListener extends SkillAPIListener { + /** * Registers clicks as they happen * * @param event event details */ @EventHandler - public void onClick(PlayerInteractEvent event) - { - - // Get the history - PlayerCombos combo = SkillAPI.getPlayerData(event.getPlayer()).getComboData(); - + public void onClick(PlayerInteractEvent event) { // Left clicks - if (event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) - { - combo.applyClick(Click.LEFT); + if (event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) { + Bukkit.getServer().getPluginManager().callEvent(new KeyPressEvent(event.getPlayer(), KeyPressEvent.Key.LEFT)); } // Right clicks - else if (event.getAction() == Action.RIGHT_CLICK_BLOCK || event.getAction() == Action.RIGHT_CLICK_AIR) - { - combo.applyClick(Click.RIGHT); + else if (event.getAction() == Action.RIGHT_CLICK_BLOCK || event.getAction() == Action.RIGHT_CLICK_AIR) { + Bukkit.getServer().getPluginManager().callEvent(new KeyPressEvent(event.getPlayer(), KeyPressEvent.Key.RIGHT)); } } - /** - * Registers shift clicks as they happen - * - * @param event event details - */ @EventHandler - public void onShiftClick(PlayerToggleSneakEvent event) - { - if (event.isSneaking()) - { - SkillAPI.getPlayerData(event.getPlayer()).getComboData().applyClick(Click.SHIFT); - } + public void onDrop(final PlayerDropItemEvent event) { + Bukkit.getServer().getPluginManager().callEvent(new KeyPressEvent(event.getPlayer(), KeyPressEvent.Key.Q)); } } diff --git a/src/com/sucy/skill/listener/ComboListener.java b/src/com/sucy/skill/listener/ComboListener.java new file mode 100644 index 00000000..3b060a8a --- /dev/null +++ b/src/com/sucy/skill/listener/ComboListener.java @@ -0,0 +1,135 @@ +/** + * SkillAPI + * com.sucy.skill.listener.ClickListener + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software") to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.listener; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.event.KeyPressEvent; +import com.sucy.skill.api.player.PlayerCombos; +import com.sucy.skill.data.Click; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.UUID; + +/** + * Handles transferring click actions by the player to + * combos that cast skills. + */ +public class ComboListener extends SkillAPIListener { + private HashMap lastClick = new HashMap(); + + @Override + public void cleanup() { + lastClick.clear(); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + lastClick.remove(event.getPlayer().getUniqueId()); + onGround.remove(event.getPlayer().getUniqueId()); + } + + /** + * Registers clicks as they happen + * + * @param event event details + */ + @EventHandler + public void onClick(final KeyPressEvent event) { + final Long time = lastClick.get(event.getPlayer().getUniqueId()); + if (time != null && time > System.currentTimeMillis()) { + return; + } + + // Get the history + PlayerCombos combo = SkillAPI.getPlayerData(event.getPlayer()).getComboData(); + + switch (event.getKey()) { + case Q: + combo.applyClick(Click.Q); + break; + case LEFT: + if (event.getPlayer().isSneaking() && SkillAPI.getComboManager().isClickEnabled(Click.LEFT_SHIFT.getId())) { + combo.applyClick(Click.LEFT_SHIFT); + } else { + combo.applyClick(Click.LEFT); + } + break; + case RIGHT: + if (event.getPlayer().isSneaking() && SkillAPI.getComboManager().isClickEnabled(Click.RIGHT_SHIFT.getId())) { + combo.applyClick(Click.RIGHT_SHIFT); + } else { + combo.applyClick(Click.RIGHT); + } + break; + default: + return; + } + + lastClick.put(event.getPlayer().getUniqueId(), System.currentTimeMillis() + 40); + } + + @EventHandler + public void onDrop(final PlayerDropItemEvent event) { + if (SkillAPI.getComboManager().isClickEnabled(Click.Q.getId())) { + event.setCancelled(true); + } + } + + /** + * Registers shift clicks as they happen + * + * @param event event details + */ + @EventHandler + public void onShiftClick(PlayerToggleSneakEvent event) { + if (event.isSneaking()) { + SkillAPI.getPlayerData(event.getPlayer()).getComboData().applyClick(Click.SHIFT); + } + } + + @EventHandler + public void onJump(final PlayerMoveEvent event) { + if (event.getTo().getY() > event.getFrom().getY() + && event.getPlayer().getNoDamageTicks() == 0 + && onGround.contains(event.getPlayer().getUniqueId())) { + SkillAPI.getPlayerData(event.getPlayer()).getComboData().applyClick(Click.SPACE); + } + if (event.getPlayer().isOnGround()) { + onGround.add(event.getPlayer().getUniqueId()); + } else { + onGround.remove(event.getPlayer().getUniqueId()); + } + } + + private HashSet onGround = new HashSet(); +} diff --git a/src/com/sucy/skill/listener/DeathListener.java b/src/com/sucy/skill/listener/DeathListener.java index 0eb9429e..b094a349 100644 --- a/src/com/sucy/skill/listener/DeathListener.java +++ b/src/com/sucy/skill/listener/DeathListener.java @@ -26,41 +26,19 @@ */ package com.sucy.skill.listener; -import com.rit.sucy.reflect.Reflection; +import com.sucy.skill.SkillAPI; import com.sucy.skill.api.event.SkillDamageEvent; import com.sucy.skill.api.event.TrueDamageEvent; -import org.bukkit.entity.HumanEntity; +import com.sucy.skill.api.particle.EffectManager; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDeathEvent; -import java.lang.reflect.Method; - -public class DeathListener implements Listener +public class DeathListener extends SkillAPIListener { - private Method handle; - private Method playerAttack; - private Method mobAttack; - private Method die; - - public DeathListener() - { - try - { - Class entityLiving = Reflection.getNMSClass("EntityLiving"); - Class source = Reflection.getNMSClass("DamageSource"); - playerAttack = source.getDeclaredMethod("playerAttack", Reflection.getNMSClass("EntityHuman")); - mobAttack = source.getDeclaredMethod("mobAttack", entityLiving); - die = entityLiving.getDeclaredMethod("die", source); - handle = Reflection.getCraftClass("entity.CraftEntity") - .getDeclaredMethod("getHandle"); - } - catch (Exception ex) - { - ex.printStackTrace(); - } - } + private final String KILLER = "sapiKiller"; /** * Launches our own death event for when entities are killed via skills @@ -84,25 +62,24 @@ public void onTrue(TrueDamageEvent event) handle(event.getTarget(), event.getDamager(), event.getDamage()); } - private void handle(LivingEntity entity, LivingEntity damager, double damage) + private void handle(final LivingEntity entity, final LivingEntity damager, final double damage) { - if (entity.getHealth() <= damage) - { - try - { - Object nmsEntity = handle.invoke(entity); - Object nmsAttacker = handle.invoke(damager); - Object damageSource; - if (damager instanceof HumanEntity) - damageSource = playerAttack.invoke(null, nmsAttacker); - else - damageSource = mobAttack.invoke(null, nmsAttacker); - die.invoke(nmsEntity, damageSource); - } - catch (Exception ex) - { - ex.printStackTrace(); - } + SkillAPI.setMeta(entity, KILLER, damager); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onDeath(EntityDeathEvent event) { + EffectManager.clear(event.getEntity()); + Object killer = SkillAPI.getMeta(event.getEntity(), KILLER); + if (killer != null && event.getEntity().getKiller() == null) { + applyDeath(event.getEntity(), (LivingEntity)killer, event.getDroppedExp()); } } + + private void applyDeath(LivingEntity entity, LivingEntity damager, int exp) { + if (!entity.isDead() || entity.getKiller() != null || !(damager instanceof Player)) + return; + + KillListener.giveExp(entity, (Player)damager, exp); + } } diff --git a/src/com/sucy/skill/listener/ExperienceListener.java b/src/com/sucy/skill/listener/ExperienceListener.java new file mode 100644 index 00000000..3e46e408 --- /dev/null +++ b/src/com/sucy/skill/listener/ExperienceListener.java @@ -0,0 +1,93 @@ +package com.sucy.skill.listener; + +import com.rit.sucy.config.CommentedConfig; +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.enums.ExpSource; +import com.sucy.skill.api.player.PlayerClass; +import com.sucy.skill.api.player.PlayerData; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.inventory.CraftItemEvent; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * SkillAPI © 2017 + * com.sucy.skill.listener.ExperienceListener + */ +public class ExperienceListener extends SkillAPIListener { + + private static final String CONFIG_KEY = "unnatural"; + + boolean track; + HashSet unnatural = new HashSet(); + + public ExperienceListener() { + track = SkillAPI.getSettings().trackBreaks(); + if (track) { + CommentedConfig data = SkillAPI.getConfig("data/placed"); + unnatural = new HashSet(data.getConfig().getList(CONFIG_KEY)); + } + } + + @Override + public void cleanup() { + if (track) { + CommentedConfig config = SkillAPI.getConfig("data/placed"); + config.getConfig().set(CONFIG_KEY, new ArrayList(unnatural)); + config.save(); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBreak(BlockBreakEvent event) { + if (track && unnatural.contains(format(event.getBlock()))) { + return; + } + + PlayerData playerData = SkillAPI.getPlayerData(event.getPlayer()); + for (PlayerClass playerClass : playerData.getClasses()) { + double yield = SkillAPI.getSettings().getBreakYield(playerClass, event.getBlock().getType()); + if (yield > 0) { + playerClass.giveExp(yield, ExpSource.BLOCK_BREAK); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlace(BlockPlaceEvent event) { + if (track) { + unnatural.add(format(event.getBlock())); + } + + PlayerData playerData = SkillAPI.getPlayerData(event.getPlayer()); + for (PlayerClass playerClass : playerData.getClasses()) { + double yield = SkillAPI.getSettings().getPlaceYield(playerClass, event.getBlock().getType()); + if (yield > 0) { + playerClass.giveExp(yield, ExpSource.BLOCK_PLACE); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onCraft(CraftItemEvent event) { + PlayerData playerData = SkillAPI.getPlayerData((Player) event.getWhoClicked()); + for (PlayerClass playerClass : playerData.getClasses()) { + double yield = SkillAPI.getSettings().getCraftYield(playerClass, event.getRecipe().getResult().getType()); + if (yield > 0) { + playerClass.giveExp(yield, ExpSource.CRAFT); + } + } + } + + private String format(Block block) { + Location loc = block.getLocation(); + return loc.getWorld().getName() + "|" + loc.getBlockX() + "|" + loc.getBlockY() + "|" + loc.getBlockZ(); + } +} diff --git a/src/com/sucy/skill/listener/ItemListener.java b/src/com/sucy/skill/listener/ItemListener.java index c969ee4d..7e1b9550 100644 --- a/src/com/sucy/skill/listener/ItemListener.java +++ b/src/com/sucy/skill/listener/ItemListener.java @@ -26,34 +26,137 @@ */ package com.sucy.skill.listener; +import com.google.common.collect.ImmutableSet; import com.rit.sucy.config.FilterType; import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.event.PlayerClassChangeEvent; +import com.sucy.skill.data.PlayerEquips; import com.sucy.skill.language.ErrorNodes; -import com.sucy.skill.task.InventoryTask; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; 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.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; +//import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityShootBowEvent; -import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemBreakEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Set; /** * Listener that handles weapon item lore requirements */ -public class ItemListener implements Listener +public class ItemListener extends SkillAPIListener { + @Override + public void init() { + MainListener.registerJoin(this::onJoin); + } + + /** + * Removes weapon bonuses when dropped + * + * @param event event details + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDrop(PlayerDropItemEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) + SkillAPI.getPlayerData(event.getPlayer()).getEquips().clearWeapon(); + } + + /** + * Updates player equips when an item breaks + * + * @param event event details + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBreak(PlayerItemBreakEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) + SkillAPI.schedule(new UpdateTask(event.getPlayer(), false), 1); + } + /** + * Updates equipment data on join + */ + public void onJoin(final Player player) + { + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + SkillAPI.getPlayerData(player).getEquips().update(player); + } + + @EventHandler + public void onProfess(PlayerClassChangeEvent event) { + final Player player = event.getPlayerData().getPlayer(); + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + event.getPlayerData().getEquips().update(player); + } + + /** + * Updates weapon on pickup * Clear attribute buff data on quit * * @param event event details */ @EventHandler - public void onQuit(PlayerQuitEvent event) + public void onPickup(EntityPickupItemEvent event) { - InventoryTask.clear(event.getPlayer().getUniqueId()); + if( event.getEntityType() == EntityType.PLAYER) { + if (SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) + SkillAPI.schedule(new UpdateTask((Player)event.getEntity(), false), 1); + } + } + + /** + * Update equips on world change into an active world + * + * @param event event details + */ + @EventHandler(priority = EventPriority.MONITOR) + public void onWorld(PlayerChangedWorldEvent event) + { + if (!SkillAPI.getSettings().isWorldEnabled(event.getFrom()) + && SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) + SkillAPI.getPlayerData(event.getPlayer()).getEquips().update(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onHeld(PlayerItemHeldEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) + SkillAPI.schedule(new UpdateTask(event.getPlayer(), true), 1); + } + + @EventHandler + public void onClose(InventoryCloseEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) + SkillAPI.schedule(new UpdateTask((Player) event.getPlayer(), false), 1); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onInteract(PlayerInteractEvent event) + { + if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld()) + && (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) + && event.getPlayer().getInventory().getItemInMainHand() != null + && ARMOR_TYPES.contains(event.getPlayer().getInventory().getItemInMainHand().getType())) + { + SkillAPI.schedule(new UpdateTask(event.getPlayer(), false), 1); + } } /** @@ -64,10 +167,13 @@ public void onQuit(PlayerQuitEvent event) @EventHandler(priority = EventPriority.LOWEST) public void onAttack(EntityDamageByEntityEvent event) { + if (!SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) + return; + if (event.getDamager() instanceof Player) { Player player = (Player) event.getDamager(); - if (InventoryTask.cannotUse(SkillAPI.getPlayerData(player), player.getItemInHand())) + if (!SkillAPI.getPlayerData(player).getEquips().canHit()) { SkillAPI.getLanguage().sendMessage(ErrorNodes.CANNOT_USE, player, FilterType.COLOR); event.setCancelled(true); @@ -76,11 +182,11 @@ public void onAttack(EntityDamageByEntityEvent event) if (event.getEntity() instanceof Player && VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { Player player = (Player) event.getEntity(); - if (event.getDamage(EntityDamageEvent.DamageModifier.BLOCKING) < 0 - && InventoryTask.cannotUse(SkillAPI.getPlayerData(player), player.getInventory().getItemInMainHand())) + final boolean blocking = event.getDamage() < 0; + if (blocking && !SkillAPI.getPlayerData(player).getEquips().canBlock()) { SkillAPI.getLanguage().sendMessage(ErrorNodes.CANNOT_USE, event.getEntity(), FilterType.COLOR); - event.setDamage(EntityDamageEvent.DamageModifier.BLOCKING, 0); + event.setDamage(0); } } } @@ -90,16 +196,74 @@ public void onAttack(EntityDamageByEntityEvent event) * * @param event event details */ - @EventHandler + @EventHandler(priority = EventPriority.LOWEST) public void onShoot(EntityShootBowEvent event) { if (event.getEntity() instanceof Player) { - if (InventoryTask.cannotUse(SkillAPI.getPlayerData((Player) event.getEntity()), event.getBow())) + final PlayerEquips equips = SkillAPI.getPlayerData((Player) event.getEntity()).getEquips(); + if (isMainhand(event.getBow(), event.getEntity())) { + if (!equips.canHit()) { + SkillAPI.getLanguage().sendMessage(ErrorNodes.CANNOT_USE, event.getEntity(), FilterType.COLOR); + event.setCancelled(true); + } + } + else if (!equips.canBlock()) { SkillAPI.getLanguage().sendMessage(ErrorNodes.CANNOT_USE, event.getEntity(), FilterType.COLOR); event.setCancelled(true); } } } + + private boolean isMainhand(final ItemStack bow, final LivingEntity entity) { + return !VersionManager.isVersionAtLeast(VersionManager.V1_9_0) + || bow == entity.getEquipment().getItemInMainHand(); + } + + public static final Set ARMOR_TYPES = getArmorMaterials(); + + private static Set getArmorMaterials() { + final Set armorSuffixes = ImmutableSet.of("BOOTS", "LEGGINGS", "CHESTPLATE", "HELMET"); + final ImmutableSet.Builder builder = ImmutableSet.builder(); + for (Material material : Material.values()) { + final int index = material.name().lastIndexOf('_') + 1; + final String suffix = material.name().substring(index); + if (armorSuffixes.contains(suffix)) { + builder.add(material); + } + } + return builder.build(); + } + + /** + * Handles updating equipped armor + */ + private class UpdateTask extends BukkitRunnable + { + private Player player; + + private boolean weaponOnly; + + /** + * @param player player reference + */ + UpdateTask(Player player, boolean weaponOnly) + { + this.player = player; + this.weaponOnly = weaponOnly; + } + + /** + * Applies the update + */ + @Override + public void run() + { + if (weaponOnly) + SkillAPI.getPlayerData(player).getEquips().updateWeapon(player.getInventory()); + else + SkillAPI.getPlayerData(player).getEquips().update(player); + } + } } diff --git a/src/com/sucy/skill/listener/KillListener.java b/src/com/sucy/skill/listener/KillListener.java index 253361bb..0f68fb37 100644 --- a/src/com/sucy/skill/listener/KillListener.java +++ b/src/com/sucy/skill/listener/KillListener.java @@ -41,7 +41,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.EntityDeathEvent; @@ -51,7 +50,7 @@ /** * Tracks who kills what entities and awards experience accordingly */ -public class KillListener implements Listener +public class KillListener extends SkillAPIListener { private static final String S_TYPE = "sType"; private static final int SPAWNER = 0, EGG = 1; @@ -88,55 +87,49 @@ public void onKill(EntityDeathEvent event) FlagManager.clearFlags(event.getEntity()); BuffManager.clearData(event.getEntity()); + giveExp(event.getEntity(), event.getEntity().getKiller(), event.getDroppedExp()); + } + + public static void giveExp(LivingEntity entity, Player killer, int exp) { + // Disabled world - if (!SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) + if (!SkillAPI.getSettings().isWorldEnabled(entity.getWorld())) return; // Cancel experience when applicable - if (event.getEntity().hasMetadata(S_TYPE)) + if (entity.hasMetadata(S_TYPE)) { - int value = SkillAPI.getMetaInt(event.getEntity(), S_TYPE); + int value = SkillAPI.getMetaInt(entity, S_TYPE); // Block spawner mob experience if (value == SPAWNER && SkillAPI.getSettings().isBlockSpawner()) - { return; - } - // Block egg mob experience + // Block egg mob experience else if (value == EGG && SkillAPI.getSettings().isBlockEgg()) - { return; - } } // Summons don't give experience - if (event.getEntity().hasMetadata(MechanicListener.SUMMON_DAMAGE)) - { + if (entity.hasMetadata(MechanicListener.SUMMON_DAMAGE)) return; - } - Player killer = event.getEntity().getKiller(); if (killer != null && killer.hasPermission(Permissions.EXP)) { // Block creative experience if (killer.getGameMode() == GameMode.CREATIVE && SkillAPI.getSettings().isBlockCreative()) - { return; - } PlayerData player = SkillAPI.getPlayerData(killer); // Give experience based on orbs when enabled if (SkillAPI.getSettings().isUseOrbs()) - { - player.giveExp(event.getDroppedExp(), ExpSource.MOB); - } + player.giveExp(exp, ExpSource.MOB); - // Give experience based on config when not using orbs + // Give experience based on config when not using orbs else { - String name = ListenerUtil.getName(event.getEntity()); + String name = ListenerUtil.getName(entity); double yield = SkillAPI.getSettings().getYield(name); player.giveExp(yield, ExpSource.MOB); } @@ -152,13 +145,9 @@ else if (value == EGG && SkillAPI.getSettings().isBlockEgg()) public void onSpawn(CreatureSpawnEvent event) { if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.SPAWNER) - { SkillAPI.setMeta(event.getEntity(), S_TYPE, SPAWNER); - } else if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.SPAWNER_EGG) - { SkillAPI.setMeta(event.getEntity(), S_TYPE, EGG); - } } /** @@ -206,9 +195,6 @@ private void setKiller(LivingEntity entity, Player player) killer.set(hit, source); damageTime.set(hit, 100); } - catch (Exception ex) - { - ex.printStackTrace(); - } + catch (Exception ex) { /* */ } } } diff --git a/src/com/sucy/skill/listener/LingeringPotionListener.java b/src/com/sucy/skill/listener/LingeringPotionListener.java new file mode 100644 index 00000000..ed6b21fe --- /dev/null +++ b/src/com/sucy/skill/listener/LingeringPotionListener.java @@ -0,0 +1,37 @@ +package com.sucy.skill.listener; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.dynamic.mechanic.PotionProjectileMechanic; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.AreaEffectCloudApplyEvent; +import org.bukkit.event.entity.LingeringPotionSplashEvent; + +import static com.sucy.skill.listener.MechanicListener.POTION_PROJECTILE; +import static com.sucy.skill.listener.MechanicListener.SKILL_CASTER; +import static com.sucy.skill.listener.MechanicListener.SKILL_LEVEL; + +/** + * SkillAPI © 2017 + * com.sucy.skill.listener.LingeringPotionListener + */ +public class LingeringPotionListener extends SkillAPIListener { + + @EventHandler + public void onLingerSplash(LingeringPotionSplashEvent event) { + PotionProjectileMechanic mechanic = (PotionProjectileMechanic) SkillAPI.getMeta(event.getEntity(), POTION_PROJECTILE); + if (mechanic != null) { + SkillAPI.setMeta(event.getAreaEffectCloud(), POTION_PROJECTILE, mechanic); + event.getAreaEffectCloud().setMetadata(SKILL_LEVEL, event.getEntity().getMetadata(SKILL_LEVEL).get(0)); + event.getAreaEffectCloud().setMetadata(SKILL_CASTER, event.getEntity().getMetadata(SKILL_CASTER).get(0)); + } + } + + @EventHandler + public void onLinger(AreaEffectCloudApplyEvent event) { + PotionProjectileMechanic mechanic = (PotionProjectileMechanic) SkillAPI.getMeta(event.getEntity(), POTION_PROJECTILE); + if (mechanic != null) { + mechanic.callback(event.getEntity(), event.getAffectedEntities()); + event.getAffectedEntities().clear(); + } + } +} diff --git a/src/com/sucy/skill/listener/ListenerUtil.java b/src/com/sucy/skill/listener/ListenerUtil.java index 3d3807dc..881785f7 100644 --- a/src/com/sucy/skill/listener/ListenerUtil.java +++ b/src/com/sucy/skill/listener/ListenerUtil.java @@ -31,6 +31,9 @@ import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Projectile; import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; /** * Helper class for listeners @@ -63,6 +66,18 @@ else if (event.getDamager() instanceof Projectile) return null; } + public static Inventory getClickedInventory(InventoryClickEvent event) { + int slot = event.getRawSlot(); + InventoryView view = event.getView(); + if (slot < 0) { + return null; + } else if ((view.getTopInventory() != null) && (slot < view.getTopInventory().getSize())) { + return view.getTopInventory(); + } else { + return view.getBottomInventory(); + } + } + /** * Gets a simple name of the entity * diff --git a/src/com/sucy/skill/listener/MainListener.java b/src/com/sucy/skill/listener/MainListener.java index b9298bb2..406b2b71 100644 --- a/src/com/sucy/skill/listener/MainListener.java +++ b/src/com/sucy/skill/listener/MainListener.java @@ -31,6 +31,7 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.enums.ExpSource; import com.sucy.skill.api.event.PhysicalDamageEvent; +import com.sucy.skill.api.event.PlayerLevelUpEvent; import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.api.skills.Skill; import com.sucy.skill.api.util.BuffManager; @@ -38,54 +39,104 @@ import com.sucy.skill.api.util.FlagManager; import com.sucy.skill.data.Permissions; import com.sucy.skill.dynamic.DynamicSkill; +import com.sucy.skill.dynamic.mechanic.ImmunityMechanic; +import com.sucy.skill.hook.CitizensHook; import com.sucy.skill.manager.ClassBoardManager; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.attribute.Attribute; +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.block.BlockBreakEvent; import org.bukkit.event.entity.*; import org.bukkit.event.inventory.FurnaceExtractEvent; import org.bukkit.event.player.*; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; +import java.util.function.Consumer; /** * The main listener for SkillAPI that handles general mechanics * such as loading/clearing data, controlling experience gains, and * enabling/disabling passive abilities. */ -public class MainListener implements Listener +public class MainListener extends SkillAPIListener { + private static final List> JOIN_HANDLERS = new ArrayList<>(); + private static final List> CLEAR_HANDLERS = new ArrayList<>(); + + public static final Map loadingPlayers = new HashMap<>(); + + public static void registerJoin(final Consumer joinHandler) { + JOIN_HANDLERS.add(joinHandler); + } + + public static void registerClear(final Consumer joinHandler) { + CLEAR_HANDLERS.add(joinHandler); + } + + @Override + public void cleanup() { + JOIN_HANDLERS.clear(); + } + /** * Loads player data asynchronously when a player tries to log in * * @param event event details */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onLogin(AsyncPlayerPreLoginEvent event) - { + public void onLogin(AsyncPlayerPreLoginEvent event) { + final OfflinePlayer player; if (VersionManager.isVersionAtLeast(VersionManager.V1_7_5)) - { - SkillAPI.loadPlayerData(Bukkit.getOfflinePlayer(event.getUniqueId())); - } + player = Bukkit.getOfflinePlayer(event.getUniqueId()); else - { - SkillAPI.loadPlayerData(VersionManager.getOfflinePlayer(event.getName())); - } + player = VersionManager.getOfflinePlayer(event.getName()); + + if (SkillAPI.getSettings().isUseSql() && SkillAPI.getSettings().getSqlDelay() > 0) + SkillAPI.initFakeData(player); + else + SkillAPI.loadPlayerData(player); } /** * Starts passives and applies class data when a player logs in. - * - * @param event event details */ @EventHandler - public void onJoin(PlayerJoinEvent event) - { - PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); - data.init(event.getPlayer()); + public void onJoin(final PlayerJoinEvent event) { + final Player player = event.getPlayer(); + if (player.hasMetadata("NPC") || !SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + return; + + final int delay = SkillAPI.getSettings().getSqlDelay(); + if (SkillAPI.getSettings().isUseSql() && delay > 0) { + final BukkitTask task = SkillAPI.schedule(() -> { + try { + SkillAPI.reloadPlayerData(player); + init(player); + } finally { + loadingPlayers.remove(event.getPlayer().getUniqueId()); + } + }, delay); + loadingPlayers.put(event.getPlayer().getUniqueId(), task); + } else { + init(player); + } + } + + private void init(final Player player) { + final PlayerData data = SkillAPI.getPlayerData(player); + data.init(player); + data.autoLevel(); + data.updateScoreboard(); + JOIN_HANDLERS.forEach(handler -> handler.accept(player)); } /** @@ -96,21 +147,46 @@ public void onJoin(PlayerJoinEvent event) @EventHandler(priority = EventPriority.MONITOR) public void onQuit(PlayerQuitEvent event) { - PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); - if (SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) - { - data.stopPassives(event.getPlayer()); - data.record(event.getPlayer()); + unload(event.getPlayer()); + } + + /** + * Unloads a player's data from the server + * + * @param player player to unload + */ + public static void unload(Player player) + { + if (CitizensHook.isNPC(player)) + return; + + boolean skipSaving = false; + if (loadingPlayers.containsKey(player.getUniqueId())) { + loadingPlayers.remove(player.getUniqueId()).cancel(); + skipSaving = true; } - FlagManager.clearFlags(event.getPlayer()); - BuffManager.clearData(event.getPlayer()); - Combat.clearData(event.getPlayer()); - DynamicSkill.clearCastData(event.getPlayer()); + PlayerData data = SkillAPI.getPlayerData(player); + if (SkillAPI.getSettings().isWorldEnabled(player.getWorld())) + { + data.record(player); + data.stopPassives(player); + } - event.getPlayer().setDisplayName(event.getPlayer().getName()); - event.getPlayer().setMaxHealth(20); - SkillAPI.unloadPlayerData(event.getPlayer()); + FlagManager.clearFlags(player); + BuffManager.clearData(player); + Combat.clearData(player); + DynamicSkill.clearCastData(player); + + player.setDisplayName(player.getName()); + if (VersionManager.isVersionAtLeast(VersionManager.V1_9_0)) { + player.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(20); + } + //else { + // player.setMaxHealth(20); + // } + player.setWalkSpeed(0.2f); + SkillAPI.unloadPlayerData(player, skipSaving); } /** @@ -125,11 +201,35 @@ public void onDeath(PlayerDeathEvent event) BuffManager.clearData(event.getEntity()); DynamicSkill.clearCastData(event.getEntity()); + if (event.getEntity().hasMetadata("NPC")) + return; + PlayerData data = SkillAPI.getPlayerData(event.getEntity()); if (data.hasClass() && SkillAPI.getSettings().isWorldEnabled(event.getEntity().getWorld())) { data.stopPassives(event.getEntity()); - data.loseExp(); + if (!SkillAPI.getSettings().shouldIgnoreExpLoss(event.getEntity().getWorld())) { + data.loseExp(); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onDeath(final EntityDeathEvent event) { + DynamicSkill.clearCastData(event.getEntity()); + FlagManager.clearFlags(event.getEntity()); + BuffManager.clearData(event.getEntity()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onUnload(final ChunkUnloadEvent event) { + for (final Entity entity : event.getChunk().getEntities()) { + if (entity instanceof LivingEntity && !(entity instanceof Player)) { + final LivingEntity livingEntity = (LivingEntity) entity; + DynamicSkill.clearCastData(livingEntity); + FlagManager.clearFlags(livingEntity); + BuffManager.clearData(livingEntity); + } } } @@ -141,11 +241,12 @@ public void onDeath(PlayerDeathEvent event) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onBreak(BlockBreakEvent event) { + if (event.getPlayer().hasMetadata("NPC")) + return; + Player player = event.getPlayer(); if (SkillAPI.getSettings().isUseOrbs() && player != null && SkillAPI.getSettings().isWorldEnabled(player.getWorld())) - { SkillAPI.getPlayerData(player).giveExp(event.getExpToDrop(), ExpSource.BLOCK_BREAK); - } } /** @@ -158,9 +259,7 @@ public void onSmelt(FurnaceExtractEvent event) { Player player = event.getPlayer(); if (SkillAPI.getSettings().isUseOrbs() && player != null && SkillAPI.getSettings().isWorldEnabled(player.getWorld())) - { SkillAPI.getPlayerData(player).giveExp(event.getExpToDrop(), ExpSource.SMELT); - } } /** @@ -172,14 +271,14 @@ public void onSmelt(FurnaceExtractEvent event) public void onExpBottleBreak(ExpBottleEvent event) { if (!(event.getEntity().getShooter() instanceof Player) || !SkillAPI.getSettings().isWorldEnabled(((Player) event.getEntity().getShooter()).getWorld())) - { return; - } + Player player = (Player) event.getEntity().getShooter(); + if (CitizensHook.isNPC(player)) + return; + if (SkillAPI.getSettings().isUseOrbs()) - { SkillAPI.getPlayerData(player).giveExp(event.getExperience(), ExpSource.EXP_BOTTLE); - } } /** @@ -200,6 +299,18 @@ public void onExpChange(PlayerExpChangeEvent event) } } + /** + * Handles updating level displays for players + * + * @param event event details + */ + @EventHandler + public void onLevelUp(final PlayerLevelUpEvent event) { + if (SkillAPI.getSettings().isShowClassLevel()) { + ClassBoardManager.updateLevel(event.getPlayerData()); + } + } + /** * Starts passive abilities again after respawning * @@ -208,6 +319,9 @@ public void onExpChange(PlayerExpChangeEvent event) @EventHandler public void onRespawn(PlayerRespawnEvent event) { + if (event.getPlayer().hasMetadata("NPC")) + return; + PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); if (data.hasClass() && SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) { @@ -224,9 +338,12 @@ public void onRespawn(PlayerRespawnEvent event) @EventHandler(ignoreCancelled = true) public void onDamage(EntityDamageEvent event) { - if (event.getEntity() instanceof LivingEntity && FlagManager.hasFlag((LivingEntity) event.getEntity(), "immune:" + event.getCause().name())) - { - event.setCancelled(true); + if (event.getEntity() instanceof LivingEntity && FlagManager.hasFlag((LivingEntity) event.getEntity(), "immune:" + event.getCause().name())) { + double multiplier = SkillAPI.getMetaDouble(event.getEntity(), ImmunityMechanic.META_KEY); + if (multiplier <= 0) + event.setCancelled(true); + else + event.setDamage(event.getDamage() * multiplier); } } @@ -262,84 +379,18 @@ public void onSaturationHeal(EntityRegainHealthEvent event) } } - /** - * Applies damage and defense buffs when something takes or deals - * damage to something else. - * - * @param event event details - */ - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onDamage(EntityDamageByEntityEvent event) - { - if (event.getCause() == EntityDamageEvent.DamageCause.CUSTOM - || !(event.getEntity() instanceof LivingEntity)) return; - - // Ignore no-damage events (for CrackShot) - if (event.getDamage() == 0) - return; - - LivingEntity damager = ListenerUtil.getDamager(event); - - if (Skill.isSkillDamage()) - { - event.setDamage(BuffManager.modifySkillDealtDamage(damager, event.getDamage())); - - // Cancel event if no damage - if (event.getDamage() <= 0) - { - if (!SkillAPI.getSettings().isKnockback()) - event.setCancelled(true); - return; - } - - if (!(event.getEntity() instanceof LivingEntity)) - return; - - // Defense buff application - LivingEntity damaged = (LivingEntity) event.getEntity(); - event.setDamage(BuffManager.modifySkillTakenDefense(damaged, event.getDamage())); - - // Cancel event if no damage - if (event.getDamage() <= 0 && !SkillAPI.getSettings().isKnockback()) - event.setCancelled(true); - - return; - } - - // Damage buff application - event.setDamage(BuffManager.modifyDealtDamage(damager, event.getDamage())); - - // Cancel event if no damage - if (event.getDamage() <= 0) - { - if (!SkillAPI.getSettings().isKnockback()) - event.setCancelled(true); - return; - } - - if (!(event.getEntity() instanceof LivingEntity)) - return; - - // Defense buff application - LivingEntity damaged = (LivingEntity) event.getEntity(); - event.setDamage(BuffManager.modifyTakenDefense(damaged, event.getDamage())); - - // Cancel event if no damage - if (event.getDamage() <= 0 && !SkillAPI.getSettings().isKnockback()) - event.setCancelled(true); - } - /** * Launches physical damage events to differentiate skill damage from physical damage * * @param event event details */ - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onPhysicalDamage(EntityDamageByEntityEvent event) { if (Skill.isSkillDamage() || event.getCause() == EntityDamageEvent.DamageCause.CUSTOM - || !(event.getEntity() instanceof LivingEntity)) + || !(event.getEntity() instanceof LivingEntity) + || event.getDamage() <= 0) { return; } @@ -381,16 +432,19 @@ public void onCombat(EntityDamageByEntityEvent event) @EventHandler public void onWorldChange(PlayerChangedWorldEvent event) { + if (event.getPlayer().hasMetadata("NPC")) + return; + boolean oldEnabled = SkillAPI.getSettings().isWorldEnabled(event.getFrom()); boolean newEnabled = SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld()); if (oldEnabled && !newEnabled) { PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); + data.clearBonuses(); data.stopPassives(event.getPlayer()); - if (SkillAPI.getSettings().isSkillBarEnabled()) - data.getSkillBar().clear(event.getPlayer()); ClassBoardManager.clear(new VersionPlayer(event.getPlayer())); - event.getPlayer().setHealth(20); + event.getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(SkillAPI.getSettings().getDefaultHealth()); + event.getPlayer().setHealth(SkillAPI.getSettings().getDefaultHealth()); if (!SkillAPI.getSettings().getLevelBar().equalsIgnoreCase("none")) { event.getPlayer().setLevel(0); @@ -401,14 +455,39 @@ public void onWorldChange(PlayerChangedWorldEvent event) event.getPlayer().setFoodLevel(20); } } - else if (!oldEnabled && newEnabled) - { - PlayerData data = SkillAPI.getPlayerData(event.getPlayer()); - data.startPassives(event.getPlayer()); - if (SkillAPI.getSettings().isSkillBarEnabled()) - data.getSkillBar().setup(event.getPlayer()); - data.updateHealthAndMana(event.getPlayer()); - data.updateScoreboard(); + else if (!oldEnabled && newEnabled) { + init(event.getPlayer()); + } + } + + @EventHandler(ignoreCancelled = true) + public void onCommand(final PlayerCommandPreprocessEvent event) { + if (!SkillAPI.getSettings().isWorldEnabled(event.getPlayer().getWorld())) + return; + + if (event.getMessage().equals("/clear")) { + handleClear(event.getPlayer()); + } + else if (event.getMessage().startsWith("/clear ")) { + handleClear(VersionManager.getPlayer(event.getMessage().substring(7))); + } + } + + @EventHandler + public void onCommand(final ServerCommandEvent event) { + if (event.getCommand().startsWith("clear ")) { + handleClear(VersionManager.getPlayer(event.getCommand().substring(6))); + } + } + + private void handleClear(final Player player) { + if (player != null) { + SkillAPI.schedule(() -> { + final PlayerData data = SkillAPI.getPlayerData(player); + data.getEquips().update(player); + + CLEAR_HANDLERS.forEach(handler -> handler.accept(player)); + }, 1); } } } diff --git a/src/com/sucy/skill/listener/MechanicListener.java b/src/com/sucy/skill/listener/MechanicListener.java index 92761b67..460429f2 100644 --- a/src/com/sucy/skill/listener/MechanicListener.java +++ b/src/com/sucy/skill/listener/MechanicListener.java @@ -31,10 +31,10 @@ import com.sucy.skill.api.event.FlagApplyEvent; import com.sucy.skill.api.event.FlagExpireEvent; import com.sucy.skill.api.event.PlayerLandEvent; +import com.sucy.skill.api.projectile.ItemProjectile; import com.sucy.skill.dynamic.mechanic.BlockMechanic; import com.sucy.skill.dynamic.mechanic.PotionProjectileMechanic; import com.sucy.skill.dynamic.mechanic.ProjectileMechanic; -import com.sucy.skill.dynamic.mechanic.WolfMechanic; import com.sucy.skill.hook.DisguiseHook; import com.sucy.skill.hook.PluginChecker; import com.sucy.skill.hook.VaultHook; @@ -45,10 +45,12 @@ import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.PotionSplashEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; @@ -58,11 +60,14 @@ /** * The listener for handling events related to dynamic mechanics */ -public class MechanicListener implements Listener +public class MechanicListener extends SkillAPIListener { public static final String SUMMON_DAMAGE = "sapiSumDamage"; public static final String P_CALL = "pmCallback"; public static final String POTION_PROJECTILE = "potionProjectile"; + public static final String ITEM_PROJECTILE = "itemProjectile"; + public static final String SKILL_LEVEL = "skill_level"; + public static final String SKILL_CASTER = "caster"; public static final String SPEED_KEY = "sapiSpeedKey"; public static final String DISGUISE_KEY = "sapiDisguiseKey"; @@ -71,7 +76,8 @@ public class MechanicListener implements Listener /** * Cleans up listener data on shutdown */ - public static void cleanup() + @Override + public void cleanup() { flying.clear(); } @@ -84,6 +90,9 @@ public static void cleanup() @EventHandler public void onMove(PlayerMoveEvent event) { + if (event.getPlayer().hasMetadata("NPC")) + return; + boolean inMap = flying.containsKey(event.getPlayer().getUniqueId()); if (inMap == ((Entity) event.getPlayer()).isOnGround()) { @@ -110,33 +119,10 @@ else if (inMap) @EventHandler public void onQuit(PlayerQuitEvent event) { - WolfMechanic.removeWolves(event.getPlayer()); flying.remove(event.getPlayer().getUniqueId()); event.getPlayer().setWalkSpeed(0.2f); } - /** - * Remove wolves of a player upon changing worlds - * - * @param event event details - */ - @EventHandler - public void onChangeWorld(PlayerChangedWorldEvent event) - { - WolfMechanic.removeWolves(event.getPlayer()); - } - - /** - * Remove wolves of a dead player - * - * @param event event details - */ - @EventHandler - public void onDeath(PlayerDeathEvent event) - { - WolfMechanic.removeWolves(event.getEntity()); - } - /** * Applies effects when specific flag keys are set * @@ -171,9 +157,9 @@ else if (event.getFlag().equals(SPEED_KEY)) else ((Player) event.getEntity()).setWalkSpeed(0.2f); } - else if (event.getFlag().equals(DISGUISE_KEY)) - DisguiseHook.removeDisguise(event.getEntity()); } + if (event.getFlag().equals(DISGUISE_KEY)) + DisguiseHook.removeDisguise(event.getEntity()); } /** @@ -182,10 +168,28 @@ else if (event.getFlag().equals(DISGUISE_KEY)) * @param event event details */ @EventHandler - public void onLand(ProjectileHitEvent event) + public void onLand(final ProjectileHitEvent event) { if (event.getEntity().hasMetadata(P_CALL)) - ((ProjectileMechanic) SkillAPI.getMeta(event.getEntity(), P_CALL)).callback(event.getEntity(), null); + SkillAPI.schedule(() -> { + final Object obj = SkillAPI.getMeta(event.getEntity(), P_CALL); + if (obj != null) + ((ProjectileMechanic) obj).callback(event.getEntity(), null); + }, 1); + } + + /** + * Prevent item projectiles from being absorbed by hoppers + * + * @param event event details + */ + @EventHandler + public void onItemLand(final InventoryPickupItemEvent event) { + final Object meta = SkillAPI.getMeta(event.getItem(), ITEM_PROJECTILE); + if (meta != null) { + event.setCancelled(true); + ((ItemProjectile) meta).applyLanded(); + } } /** diff --git a/src/com/sucy/skill/listener/PacketListener.java b/src/com/sucy/skill/listener/PacketListener.java new file mode 100644 index 00000000..a74ade29 --- /dev/null +++ b/src/com/sucy/skill/listener/PacketListener.java @@ -0,0 +1,34 @@ +package com.sucy.skill.listener; + +import com.sucy.skill.packet.PacketInjector; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +/** + * SkillAPI © 2018 + * com.sucy.skill.listener.PacketListener + */ +public class PacketListener extends SkillAPIListener { + private PacketInjector injector; + + public PacketListener(final PacketInjector injector) { + this.injector = injector; + } + + @Override + public void init() { + Bukkit.getServer().getOnlinePlayers().forEach(injector::addPlayer); + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + injector.addPlayer(event.getPlayer()); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + injector.removePlayer(event.getPlayer()); + } +} diff --git a/src/com/sucy/skill/listener/SkillAPIListener.java b/src/com/sucy/skill/listener/SkillAPIListener.java new file mode 100644 index 00000000..8d06e0a4 --- /dev/null +++ b/src/com/sucy/skill/listener/SkillAPIListener.java @@ -0,0 +1,39 @@ +/** + * SkillAPI + * com.sucy.skill.listener.SkillAPIListener + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.listener; + +import org.bukkit.event.Listener; + +/** + * Interface for SkillAPI listeners that lets them be initialized + */ +public abstract class SkillAPIListener implements Listener +{ + public void init() { } + + public void cleanup() { } +} diff --git a/src/com/sucy/skill/listener/StatusListener.java b/src/com/sucy/skill/listener/StatusListener.java index 3ced79fd..b558b68e 100644 --- a/src/com/sucy/skill/listener/StatusListener.java +++ b/src/com/sucy/skill/listener/StatusListener.java @@ -28,6 +28,7 @@ import com.rit.sucy.version.VersionManager; import com.sucy.skill.api.event.FlagApplyEvent; +import com.sucy.skill.api.event.PhysicalDamageEvent; import com.sucy.skill.api.event.PlayerCastSkillEvent; import com.sucy.skill.api.event.TrueDamageEvent; import com.sucy.skill.api.util.FlagManager; @@ -40,7 +41,6 @@ import org.bukkit.event.Cancellable; 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.entity.EntityDamageEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; @@ -57,7 +57,7 @@ * Listener for applying default status flags for the API. You should * not use this class as it is already set up by the API. */ -public class StatusListener implements Listener +public class StatusListener extends SkillAPIListener { private static final HashMap messageTimers = new HashMap(); @@ -84,7 +84,8 @@ public class StatusListener implements Listener /** * Cleans up the listener data on shutdown */ - public static void cleanup() + @Override + public void cleanup() { messageTimers.clear(); } @@ -108,7 +109,7 @@ public void onQuit(PlayerQuitEvent event) @EventHandler(priority = EventPriority.HIGHEST) public void onMove(PlayerMoveEvent event) { - if ((((LivingEntity) event.getPlayer()).isOnGround() || event.getTo().getY() > event.getFrom().getY()) && check(event, event.getPlayer(), event.getPlayer(), StatusFlag.STUN, StatusFlag.ROOT, StatusFlag.CHANNELING)) + if (((event.getPlayer()).isOnGround() || event.getTo().getY() > event.getFrom().getY()) && check(event, event.getPlayer(), event.getPlayer(), StatusFlag.STUN, StatusFlag.ROOT, StatusFlag.CHANNELING)) { event.getPlayer().setVelocity(ZERO); } @@ -161,7 +162,12 @@ public void onDamage(EntityDamageByEntityEvent event) return; LivingEntity damager = ListenerUtil.getDamager(event); - check(event, damager, damager, StatusFlag.STUN, StatusFlag.DISARM, StatusFlag.CHANNEL); + check(event, damager, damager, StatusFlag.STUN, StatusFlag.DISARM); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPhysicalDamage(PhysicalDamageEvent event) { + check(event, event.getDamager(), event.getDamager(), StatusFlag.CHANNEL); } /** diff --git a/src/com/sucy/skill/listener/ToolListener.java b/src/com/sucy/skill/listener/ToolListener.java new file mode 100644 index 00000000..13c061dc --- /dev/null +++ b/src/com/sucy/skill/listener/ToolListener.java @@ -0,0 +1,85 @@ +/** + * SkillAPI + * com.sucy.skill.listener.GUIToolListener + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.listener; + +import com.sucy.skill.gui.tool.GUIHolder; +import com.sucy.skill.gui.tool.ToolMenu; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.InventoryView; + +public class ToolListener extends SkillAPIListener +{ + @EventHandler + public void onClick(InventoryClickEvent event) + { + if (event.getInventory().getHolder() instanceof ToolMenu) + { + if (event.getAction().name().startsWith("DROP")) + event.setCancelled(true); + else + ((ToolMenu) event.getInventory().getHolder()).handleClick(event); + } + else if (event.getInventory().getHolder() instanceof GUIHolder) + ((GUIHolder) event.getInventory().getHolder()).handleClick(event); + } + + @EventHandler + public void onDrag(InventoryDragEvent event) + { + if (event.getInventory().getHolder() instanceof ToolMenu) + event.setCancelled(true); + else if (event.getInventory().getHolder() instanceof GUIHolder) + ((GUIHolder) event.getInventory().getHolder()).handleDrag(event); + } + + @EventHandler + public void onClose(InventoryCloseEvent event) + { + if (event.getInventory().getHolder() instanceof ToolMenu) + { + event.getPlayer().setItemOnCursor(null); + ((ToolMenu) event.getInventory().getHolder()).restore(); + } + else if (event.getInventory().getHolder() instanceof GUIHolder) + ((GUIHolder) event.getInventory().getHolder()).handleClose(event); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) + { + InventoryView view = event.getPlayer().getOpenInventory(); + if (view != null && view.getTopInventory().getHolder() instanceof ToolMenu) + { + event.getPlayer().setItemOnCursor(null); + ((ToolMenu) view.getTopInventory().getHolder()).restore(); + } + } +} diff --git a/src/com/sucy/skill/listener/TreeListener.java b/src/com/sucy/skill/listener/TreeListener.java deleted file mode 100644 index 9f38bf08..00000000 --- a/src/com/sucy/skill/listener/TreeListener.java +++ /dev/null @@ -1,131 +0,0 @@ -/** - * SkillAPI - * com.sucy.skill.listener.TreeListener - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Steven Sucy - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software") to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.sucy.skill.listener; - -import com.rit.sucy.config.FilterType; -import com.rit.sucy.items.InventoryManager; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.api.classes.RPGClass; -import com.sucy.skill.api.player.PlayerClass; -import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.language.ErrorNodes; -import com.sucy.skill.tree.basic.InventoryTree; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; - -import java.util.Collection; - -/** - * Listener for Skill Tree click events - */ -public class TreeListener implements Listener -{ - public static final String CLASS_LIST_KEY = "sapiClassInv"; - - /** - * Handles skill tree interaction - * - * @param event event details - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void onClick(InventoryClickEvent event) - { - // Class selection - if (InventoryManager.isMatching(event.getInventory(), CLASS_LIST_KEY)) - { - event.setCancelled(true); - PlayerData data = SkillAPI.getPlayerData((Player) event.getWhoClicked()); - Collection classes = data.getClasses(); - boolean top = event.getRawSlot() < event.getView().getTopInventory().getSize(); - if (classes.size() > event.getSlot() && top && event.getSlot() >= 0) - { - PlayerClass c = classes.toArray(new PlayerClass[classes.size()])[event.getSlot()]; - if (c.getData().getSkills().size() == 0) - SkillAPI.getLanguage().sendMessage(ErrorNodes.NO_SKILLS, event.getWhoClicked(), FilterType.COLOR); - else - data.showSkills(data.getPlayer(), c); - } - } - - // Make sure its a skill tree inventory - else if (InventoryManager.isMatching(event.getInventory(), InventoryTree.INVENTORY_KEY)) - { - PlayerData player = SkillAPI.getPlayerData((Player) event.getWhoClicked()); - RPGClass c = SkillAPI.getClass(player.getShownClassName()); - if (c == null) - { - event.setCancelled(true); - event.getWhoClicked().closeInventory(); - return; - } - InventoryTree tree = (InventoryTree) c.getSkillTree(); - - // Do nothing when clicking outside the inventory - if (event.getSlot() == -999) - { - return; - } - - boolean top = event.getRawSlot() < event.getView().getTopInventory().getSize(); - - // Interact with the skill tree when clicking in the top region - if (top) - { - if (tree.checkClick(event.getSlot())) - event.setCancelled(true); - - // If they clicked on a skill, try upgrading it - if (tree.isSkill(event.getWhoClicked(), event.getSlot())) - { - if (event.isLeftClick()) - { - if (player.upgradeSkill(tree.getSkill(event.getSlot()))) - { - tree.show(player.getPlayer()); - } - } - else if (event.isRightClick() && SkillAPI.getSettings().isAllowDowngrade()) - { - if (player.downgradeSkill(tree.getSkill(event.getSlot()))) - { - tree.update(player); - } - } - } - } - - // Do not allow shift clicking items into the inventory - else if (event.isShiftClick()) - { - event.setCancelled(true); - } - } - } -} diff --git a/src/com/sucy/skill/log/Logger.java b/src/com/sucy/skill/log/Logger.java index 2910b4c2..56fa02e9 100644 --- a/src/com/sucy/skill/log/Logger.java +++ b/src/com/sucy/skill/log/Logger.java @@ -46,9 +46,7 @@ public class Logger public static void loadLevels(DataSection config) { for (String key : config.keys()) - { setLevel(key, config.getInt(key)); - } } /** @@ -73,9 +71,7 @@ public static void setLevel(String key, int level) public static void log(String key, int level, String message) { if (LEVELS.containsKey(key) && LEVELS.get(key) >= level) - { Bukkit.getLogger().info(message); - } } /** diff --git a/src/com/sucy/skill/manager/AttributeManager.java b/src/com/sucy/skill/manager/AttributeManager.java index b0a1bd4a..a7eec502 100644 --- a/src/com/sucy/skill/manager/AttributeManager.java +++ b/src/com/sucy/skill/manager/AttributeManager.java @@ -29,34 +29,57 @@ import com.rit.sucy.config.CommentedConfig; import com.rit.sucy.config.parse.DataSection; import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.api.util.DamageLoreRemover; import com.sucy.skill.api.util.Data; -import com.sucy.skill.data.Formula; +import com.sucy.skill.data.formula.Formula; +import com.sucy.skill.data.formula.value.CustomValue; +import com.sucy.skill.dynamic.ComponentType; import com.sucy.skill.dynamic.EffectComponent; +import com.sucy.skill.gui.tool.GUIData; +import com.sucy.skill.gui.tool.GUIPage; +import com.sucy.skill.gui.tool.GUITool; +import com.sucy.skill.gui.tool.IconHolder; import com.sucy.skill.log.LogType; import com.sucy.skill.log.Logger; +import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Set; +import java.util.*; /** * Handles loading and accessing individual * attributes from the configuration. */ -public class AttributeManager -{ +public class AttributeManager { // Keys for supported stat modifiers - public static final String HEALTH = "health"; - public static final String MANA = "mana"; - public static final String MANA_REGEN = "mana-regen"; - public static final String PHYSICAL_DAMAGE = "physical-damage"; - public static final String PHYSICAL_DEFENSE = "physical-defense"; - public static final String SKILL_DAMAGE = "skill-damage"; - public static final String SKILL_DEFENSE = "skill-defense"; - public static final String MOVE_SPEED = "move-speed"; + public static final String HEALTH = "health"; + public static final String MANA = "mana"; + public static final String MANA_REGEN = "mana-regen"; + public static final String PHYSICAL_DAMAGE = "physical-damage"; + public static final String MELEE_DAMAGE = "melee-damage"; + public static final String PROJECTILE_DAMAGE = "projectile-damage"; + public static final String PHYSICAL_DEFENSE = "physical-defense"; + public static final String MELEE_DEFENSE = "melee-defense"; + public static final String PROJECTILE_DEFENSE = "projectile-defense"; + public static final String SKILL_DAMAGE = "skill-damage"; + public static final String SKILL_DEFENSE = "skill-defense"; + public static final String MOVE_SPEED = "move-speed"; + public static final String ATTACK_SPEED = "attack-speed"; + public static final String ARMOR = "armor"; + public static final String LUCK = "luck"; + public static final String ARMOR_TOUGHNESS = "armor-toughness"; + public static final String EXPERIENCE = "exp"; + public static final String HUNGER = "hunger"; + public static final String HUNGER_HEAL = "hunger-heal"; + public static final String COOLDOWN = "cooldown"; + public static final String KNOCKBACK_RESIST = "knockback-resist"; - private LinkedHashMap attributes = new LinkedHashMap(); + private final HashMap attributes = new LinkedHashMap<>(); + private final HashMap lookup = new HashMap<>(); + private final HashMap> byStat = new HashMap<>(); + private final HashMap> byComponent = new HashMap<>(); /** * Sets up the attribute manager, loading the attribute @@ -66,8 +89,7 @@ public class AttributeManager * * @param api SkillAPI reference */ - public AttributeManager(SkillAPI api) - { + public AttributeManager(SkillAPI api) { load(api); } @@ -78,45 +100,95 @@ public AttributeManager(SkillAPI api) * * @return template for the attribute */ - public Attribute getAttribute(String key) - { - return attributes.get(key.toLowerCase()); + public Attribute getAttribute(String key) { + return lookup.get(key.toLowerCase()); } /** - * Retrieves the available attribute keys + * Unsafe getter for the attribute data. + * + * Do not use this method or modify it's return value unless + * you know exactly what you are doing. + * + * @return attributes map + */ + public HashMap getAttributes() { + return attributes; + } + + public List forStat(final String key) { + return byStat.get(key.toLowerCase()); + } + + public List forComponent(final EffectComponent component, final String key) { + return byComponent.get(component.getKey() + "-" + key.toLowerCase()); + } + + /** + * Retrieves the available attribute keys. This + * does not include display names for attributes. * * @return set of available attribute keys */ - public Set getKeys() - { + public Set getKeys() { return attributes.keySet(); } + /** + * Retrieves the available attribute keys including + * both display names and config keys. + * + * @return display name and config keys for attributes + */ + public Set getLookupKeys() { + return lookup.keySet(); + } + + /** + * Normalizes a config key or name into the config key + * for a unified identifier to store stats under. + * + * @param key key to normalize + * @return config key + */ + public String normalize(String key) { + final Attribute attribute = lookup.get(key.toLowerCase()); + if (attribute == null) { throw new IllegalArgumentException("Invalid attribute - " + key); } + return attribute.getKey(); + } + /** * Loads attribute data from the config * * @param api SkillAPI reference */ - private void load(SkillAPI api) - { + private void load(SkillAPI api) { CommentedConfig config = new CommentedConfig(api, "attributes"); config.saveDefaultConfig(); DataSection data = config.getConfig(); Logger.log(LogType.ATTRIBUTE_LOAD, 1, "Loading attributes..."); - for (String key : data.keys()) - { + for (String key : data.keys()) { Logger.log(LogType.ATTRIBUTE_LOAD, 2, " - " + key); - attributes.put(key.toLowerCase(), new Attribute(data.getSection(key), key)); + Attribute attribute = new Attribute(data.getSection(key), key); + attributes.put(attribute.getKey(), attribute); + lookup.put(attribute.getKey(), attribute); + lookup.put(attribute.getName().toLowerCase(), attribute); + } + + GUIData attribs = GUITool.getAttributesMenu(); + if (!attribs.isValid()) { + int i = 0; + GUIPage page = attribs.getPage(0); + for (String key : attributes.keySet()) { if (i < 54) { page.set(i++, key); } } + attribs.resize((attributes.size() + 8) / 9); } } /** * A single attribute template */ - public class Attribute - { + public class Attribute implements IconHolder { private static final String DISPLAY = "display"; private static final String GLOBAL = "global"; private static final String CONDITION = "condition"; @@ -132,12 +204,10 @@ public class Attribute private int max; // Dynamic global modifiers - private HashMap conditions = new HashMap(); - private HashMap mechanics = new HashMap(); - private HashMap targets = new HashMap(); + private Map> dynamicModifiers = new EnumMap<>(ComponentType.class); // General stat modifiers - private HashMap statModifiers = new HashMap(); + private HashMap statModifiers = new HashMap<>(); /** * Creates a new attribute, loading the settings from the given @@ -146,34 +216,26 @@ public class Attribute * @param data config data to load from * @param key the key the attribute was labeled under */ - public Attribute(DataSection data, String key) - { - this.key = key; + public Attribute(DataSection data, String key) { + this.key = key.toLowerCase(); this.display = data.getString(DISPLAY, key).toLowerCase(); this.icon = Data.parseIcon(data); this.max = data.getInt(MAX, 999); // Load dynamic global settings DataSection globals = data.getSection(GLOBAL); - if (globals != null) - { - loadGroup(globals.getSection(CONDITION), conditions); - loadGroup(globals.getSection(MECHANIC), mechanics); - loadGroup(globals.getSection(TARGET), targets); + if (globals != null) { + loadGroup(globals.getSection(CONDITION), ComponentType.CONDITION); + loadGroup(globals.getSection(MECHANIC), ComponentType.MECHANIC); + loadGroup(globals.getSection(TARGET), ComponentType.TARGET); } // Load stat settings DataSection stats = data.getSection(STATS); - if (stats != null) - { - loadStatModifier(stats, HEALTH); - loadStatModifier(stats, MANA); - loadStatModifier(stats, MANA_REGEN); - loadStatModifier(stats, PHYSICAL_DAMAGE); - loadStatModifier(stats, PHYSICAL_DEFENSE); - loadStatModifier(stats, SKILL_DAMAGE); - loadStatModifier(stats, SKILL_DEFENSE); - loadStatModifier(stats, MOVE_SPEED); + if (stats != null) { + for (String stat : stats.keys()) { + loadStatModifier(stats, stat); + } } } @@ -182,8 +244,7 @@ public Attribute(DataSection data, String key) * * @return config key of the attribute */ - public String getKey() - { + public String getKey() { return key; } @@ -192,8 +253,7 @@ public String getKey() * * @return name of the attribute */ - public String getName() - { + public String getName() { return display; } @@ -202,8 +262,48 @@ public String getName() * * @return icon of the attribute */ - public ItemStack getIcon() - { + @Override + public ItemStack getIcon(PlayerData data) { + ItemStack item = icon.clone(); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(filter(data, meta.getDisplayName())); + List lore = meta.getLore(); + if (lore != null) { + for (int j = 0; j < lore.size(); j++) { lore.set(j, filter(data, lore.get(j))); } + meta.setLore(lore); + } + + item.setItemMeta(meta); + return DamageLoreRemover.removeAttackDmg(item); + } + + @Override + public boolean isAllowed(final Player player) { + return true; + } + + /** + * Filters a line of the icon according to the player data + * + * @param data player data to use + * @param text line of text to filter + * + * @return filtered line + */ + private String filter(PlayerData data, String text) { + return text + .replace("{amount}", "" + data.getInvestedAttribute(key)) + .replace("{total}", "" + data.getAttribute(key)); + } + + /** + * @return icon for the attribute for use in the GUI editor + */ + public ItemStack getToolIcon() { + ItemStack icon = this.icon.clone(); + ItemMeta meta = icon.getItemMeta(); + meta.setDisplayName(key); + icon.setItemMeta(meta); return icon; } @@ -212,8 +312,7 @@ public ItemStack getIcon() * * @return max attribute amount */ - public int getMax() - { + public int getMax() { return max; } @@ -222,26 +321,19 @@ public int getMax() * * @param component component to modify for * @param key key of the value to modify - * @param self whether or not the component is targeting the caster * @param value base value * @param amount amount of attribute points * * @return modified value */ - public double modify(EffectComponent component, String key, boolean self, double value, int amount) - { - HashMap map; - if (component.getType().equals("condition")) map = conditions; - else if (component.getType().equals("mechanic")) map = mechanics; - else map = targets; - - key = component.getKey().replaceAll("-.+", "").toLowerCase() + "-" + key.toLowerCase(); - if (map.containsKey(key)) - { + public double modify(EffectComponent component, String key, double value, int amount) { + key = component.getKey() + "-" + key.toLowerCase(); + final Map map = dynamicModifiers.get(component.getType()); + if (map.containsKey(key)) { AttributeValue[] list = map.get(key); - for (AttributeValue attribValue : list) - if (attribValue.passes(component, self)) - return attribValue.apply(value, amount); + for (AttributeValue attribValue : list) { + if (attribValue.passes(component)) { return attribValue.apply(value, amount); } + } } return value; } @@ -255,10 +347,8 @@ public double modify(EffectComponent component, String key, boolean self, double * * @return modified stat value */ - public double modifyStat(String key, double base, int amount) - { - if (statModifiers.containsKey(key)) - { + public double modifyStat(String key, double base, int amount) { + if (statModifiers.containsKey(key)) { return statModifiers.get(key).compute(base, amount); } return base; @@ -268,21 +358,24 @@ public double modifyStat(String key, double base, int amount) * Loads a dynamic group globals settings into the given map * * @param data config data to load from - * @param target target map to store the data in + * @param type the component type to load for */ - private void loadGroup(DataSection data, HashMap target) - { - if (data == null) return; - for (String key : data.keys()) - { + private void loadGroup(DataSection data, ComponentType type) { + if (data == null) { return; } + + final Map target = dynamicModifiers.computeIfAbsent(type, t -> new HashMap<>()); + for (String key : data.keys()) { + final String lower = key.toLowerCase(); Logger.log(LogType.ATTRIBUTE_LOAD, 2, " SkillMod: " + key); - String value = data.getString(key); - String[] formulas = value.split("\\|"); - AttributeValue[] values = new AttributeValue[formulas.length]; + final String value = data.getString(key); + final String[] formulas = value.split("\\|"); + final AttributeValue[] values = new AttributeValue[formulas.length]; int i = 0; - for (String formula : formulas) - values[i++] = new AttributeValue(formula); - target.put(key.toLowerCase(), values); + for (final String formula : formulas) { values[i++] = new AttributeValue(formula); } + target.put(lower, values); + + if (!byComponent.containsKey(lower)) { byComponent.put(lower, new ArrayList<>()); } + byComponent.get(lower).add(this); } } @@ -292,12 +385,15 @@ private void loadGroup(DataSection data, HashMap targe * @param data config data to load from * @param key key of the stat modifier */ - private void loadStatModifier(DataSection data, String key) - { - if (data.has(key)) - { + private void loadStatModifier(DataSection data, String key) { + if (data.has(key)) { Logger.log(LogType.ATTRIBUTE_LOAD, 2, " StatMod: " + key); - statModifiers.put(key, new Formula(data.getString(key, "v"))); + statModifiers.put( + key, + new Formula(data.getString(key, "v"), new CustomValue("v"), new CustomValue("a"))); + + if (!byStat.containsKey(key)) { byStat.put(key, new ArrayList<>()); } + byStat.get(key).add(this); } } } @@ -306,10 +402,9 @@ private void loadStatModifier(DataSection data, String key) * Represents one formula modifier for an attribute * that can have conditions */ - public class AttributeValue - { + public class AttributeValue { private Formula formula; - private HashMap conditions = new HashMap(); + private HashMap conditions = new HashMap<>(); /** * Loads the attribute value that starts with the formula @@ -317,12 +412,10 @@ public class AttributeValue * * @param data data string for the value */ - public AttributeValue(String data) - { + public AttributeValue(String data) { String[] pieces = data.split(":"); - formula = new Formula(pieces[0]); - for (int i = 1; i < pieces.length; i++) - { + formula = new Formula(pieces[0], new CustomValue("v"), new CustomValue("a")); + for (int i = 1; i < pieces.length; i++) { String[] sides = pieces[i].split("="); conditions.put(sides[0], sides[1]); Logger.log(LogType.ATTRIBUTE_LOAD, 3, " Condition: " + sides[0] + " / " + sides[1]); @@ -333,21 +426,12 @@ public AttributeValue(String data) * Checks whether or not the formula should be applied to the component * * @param component component to check for conditions against - * @param self whether or not the component is targeting the caster * * @return true if passes the conditions */ - public boolean passes(EffectComponent component, boolean self) - { - for (String key : conditions.keySet()) - { - if (key.equals("self")) - { - if (conditions.get(key).equalsIgnoreCase("false") != self) - return false; - } - else if (!component.getSettings().getString(key).equalsIgnoreCase(conditions.get(key))) - return false; + public boolean passes(EffectComponent component) { + for (String key : conditions.keySet()) { + if (!component.getSettings().getString(key).equalsIgnoreCase(conditions.get(key))) { return false; } } return true; } @@ -360,8 +444,7 @@ else if (!component.getSettings().getString(key).equalsIgnoreCase(conditions.get * * @return the modified value if the conditions passed or the base value if they failed */ - public double apply(double value, int amount) - { + public double apply(double value, int amount) { return formula.compute(value, amount); } } diff --git a/src/com/sucy/skill/manager/ClassBoardManager.java b/src/com/sucy/skill/manager/ClassBoardManager.java index 402f3553..00972af7 100644 --- a/src/com/sucy/skill/manager/ClassBoardManager.java +++ b/src/com/sucy/skill/manager/ClassBoardManager.java @@ -135,7 +135,11 @@ public static void registerClass(RPGClass c) { if (SkillAPI.getSettings().isShowClassName()) { - BoardManager.registerTeam(new Team(c.getName(), c.getPrefix() + ChatColor.RESET + " ", null)); + String name = c.getName(); + if (name.length() > 16) { + name = name.substring(0, 16); + } + BoardManager.registerTeam(new Team(name, c.getPrefix() + ChatColor.RESET + " ", null)); } } @@ -159,6 +163,7 @@ public static void registerText() { if (SkillAPI.getSettings().isShowClassLevel()) { + BoardManager.init(); BoardManager.setTextBelowNames(SkillAPI.getSettings().getLevelText()); } } diff --git a/src/com/sucy/skill/manager/CmdManager.java b/src/com/sucy/skill/manager/CmdManager.java index e9720ae4..28e474d3 100644 --- a/src/com/sucy/skill/manager/CmdManager.java +++ b/src/com/sucy/skill/manager/CmdManager.java @@ -38,6 +38,8 @@ */ public class CmdManager { + public static ConfigurableCommand PROFESS_COMMAND; + private SkillAPI api; /** @@ -59,31 +61,42 @@ public void initialize() { ConfigurableCommand root = new ConfigurableCommand(api, "class", SenderType.ANYONE); root.addSubCommands( - new ConfigurableCommand(api, "acc", SenderType.PLAYER_ONLY, new CmdAccount(), "Changes account", "", Permissions.BASIC), new ConfigurableCommand(api, "bind", SenderType.PLAYER_ONLY, new CmdBind(), "Binds a skill", "", Permissions.BASIC), new ConfigurableCommand(api, "cast", SenderType.PLAYER_ONLY, new CmdCast(), "Casts a skill", "", Permissions.BASIC), + new ConfigurableCommand(api, "changeclass", SenderType.ANYONE, new CmdChangeClass(), "Swaps classes", " ", Permissions.FORCE), new ConfigurableCommand(api, "clearbind", SenderType.PLAYER_ONLY, new CmdClearBinds(), "Clears skill binds", "", Permissions.BASIC), - new ConfigurableCommand(api, "exp", SenderType.ANYONE, new CmdExp(), "Gives players exp", "[player] ", Permissions.LVL), + new ConfigurableCommand(api, "customize", SenderType.PLAYER_ONLY, new CmdCustomize(), "Opens GUI editor", "", Permissions.GUI), + new ConfigurableCommand(api, "exp", SenderType.ANYONE, new CmdExp(), "Gives players exp", "[player] [group]", Permissions.LVL), new ConfigurableCommand(api, "info", SenderType.ANYONE, new CmdInfo(), "Shows class info", "[player]", Permissions.BASIC), - new ConfigurableCommand(api, "level", SenderType.ANYONE, new CmdLevel(), "Gives players levels", "[player] ", Permissions.LVL), + new ConfigurableCommand(api, "level", SenderType.ANYONE, new CmdLevel(), "Gives players levels", "[player] [group]", Permissions.LVL), new ConfigurableCommand(api, "list", SenderType.ANYONE, new CmdList(), "Displays accounts", "[player]", Permissions.BASIC), new ConfigurableCommand(api, "lore", SenderType.PLAYER_ONLY, new CmdLore(), "Adds lore to item", "", Permissions.LORE), new ConfigurableCommand(api, "mana", SenderType.ANYONE, new CmdMana(), "Gives player mana", "[player] ", Permissions.MANA), new ConfigurableCommand(api, "options", SenderType.PLAYER_ONLY, new CmdOptions(), "Views profess options", "", Permissions.BASIC), new ConfigurableCommand(api, "points", SenderType.ANYONE, new CmdPoints(), "Gives player points", "[player] ", Permissions.POINTS), - new ConfigurableCommand(api, "profess", SenderType.PLAYER_ONLY, new CmdProfess(), "Professes classes", "", Permissions.BASIC), + PROFESS_COMMAND = new ConfigurableCommand(api, "profess", SenderType.PLAYER_ONLY, new CmdProfess(), "Professes classes", "", Permissions.BASIC), new ConfigurableCommand(api, "reload", SenderType.ANYONE, new CmdReload(), "Reloads the plugin", "", Permissions.RELOAD), + new ConfigurableCommand(api, "refund", SenderType.ANYONE, new CmdRefund(), "Refunds self attribute points or skill points", "[player] |attr|points|", Permissions.REFUND), new ConfigurableCommand(api, "reset", SenderType.PLAYER_ONLY, new CmdReset(), "Resets account data", "", Permissions.BASIC), new ConfigurableCommand(api, "skill", SenderType.PLAYER_ONLY, new CmdSkill(), "Shows player skills", "", Permissions.BASIC), - new ConfigurableCommand(api, "unbind", SenderType.PLAYER_ONLY, new CmdUnbind(), "Unbinds held item", "", Permissions.BASIC) + new ConfigurableCommand(api, "unbind", SenderType.PLAYER_ONLY, new CmdUnbind(), "Unbinds held item", "", Permissions.BASIC), + new ConfigurableCommand(api, "world", SenderType.PLAYER_ONLY, new CmdWorld(), "Moves to world", "", Permissions.WORLD) + ); root.addSubCommands( new ConfigurableCommand(api, "forceaccount", SenderType.CONSOLE_ONLY, new CmdForceAccount(), "Changes player's account", " ", Permissions.FORCE), new ConfigurableCommand(api, "forceattr", SenderType.CONSOLE_ONLY, new CmdForceAttr(), "Refunds/gives attributes", " [attr] [amount]", Permissions.FORCE), new ConfigurableCommand(api, "forcecast", SenderType.CONSOLE_ONLY, new CmdForceCast(), "Player casts the skill", " [level]", Permissions.FORCE), new ConfigurableCommand(api, "forceprofess", SenderType.CONSOLE_ONLY, new CmdForceProfess(), "Professes a player", " ", Permissions.FORCE), - new ConfigurableCommand(api, "forcereset", SenderType.CONSOLE_ONLY, new CmdForceReset(), "Resets player data", " [account]", Permissions.FORCE) + new ConfigurableCommand(api, "forcereset", SenderType.CONSOLE_ONLY, new CmdForceReset(), "Resets player data", " [account]", Permissions.FORCE), + new ConfigurableCommand(api, "forceskill", SenderType.CONSOLE_ONLY, new CmdForceSkill(), "Modifies skill levels", " ", Permissions.FORCE) ); + if (SkillAPI.getSettings().isOnePerClass()) { + root.addSubCommand(new ConfigurableCommand(api, "switch", SenderType.PLAYER_ONLY, new CmdSwitch(), "Changes class", "", Permissions.BASIC)); + } + else { + root.addSubCommand(new ConfigurableCommand(api, "acc", SenderType.PLAYER_ONLY, new CmdAccount(), "Changes account", "", Permissions.BASIC)); + } if (SkillAPI.getSettings().isUseSql()) { root.addSubCommand(new ConfigurableCommand(api, "backup", SenderType.ANYONE, new CmdBackup(), "Backs up SQL data", "", Permissions.BACKUP)); @@ -96,22 +109,24 @@ public void initialize() { root.addSubCommand(new ConfigurableCommand(api, "combo", SenderType.PLAYER_ONLY, new CmdCombo(), "Sets skill combo", " ", Permissions.BASIC)); } - if (SkillAPI.getSettings().isMapTreeEnabled()) - { - root.addSubCommand(new ConfigurableCommand(api, "scheme", SenderType.PLAYER_ONLY, new CmdScheme(), "Views/sets map schemes", "[scheme]", Permissions.BASIC)); - } if (SkillAPI.getSettings().isAttributesEnabled()) { root.addSubCommand(new ConfigurableCommand(api, "ap", SenderType.ANYONE, new CmdAP(), "Gives attrib points", "[player] ", Permissions.ATTRIB)); root.addSubCommand(new ConfigurableCommand(api, "attr", SenderType.PLAYER_ONLY, new CmdAttribute(), "Opens attribute menu", "", Permissions.BASIC)); } - if (SkillAPI.getSettings().isMapTreeAvailable() && !SkillAPI.getSettings().isMapTreeEnabled()) - { - root.addSubCommand(new ConfigurableCommand(api, "skillmap", SenderType.PLAYER_ONLY, new CmdSkillMap(), "Alternate skill list", "", Permissions.BASIC)); - } CommandManager.registerCommand(root); } + public static String join(String[] args, int start) { + return join(args, start, args.length - 1); + } + + public static String join(String[] args, int start, int end) { + final StringBuilder builder = new StringBuilder(args[start]); + for (int i = start + 1; i <= end; i++) builder.append(' ').append(args[i]); + return builder.toString(); + } + /** * Unregisters all commands for SkillAPI from the server */ diff --git a/src/com/sucy/skill/manager/ComboManager.java b/src/com/sucy/skill/manager/ComboManager.java index 95892f2c..8ce5e015 100644 --- a/src/com/sucy/skill/manager/ComboManager.java +++ b/src/com/sucy/skill/manager/ComboManager.java @@ -51,12 +51,7 @@ public class ComboManager public ComboManager() { comboSize = Math.min(SkillAPI.getSettings().getComboSize(), Click.MAX_COMBO_SIZE); - clicks = new boolean[] { - false, - SkillAPI.getSettings().isComboLeft(), - SkillAPI.getSettings().isComboRight(), - SkillAPI.getSettings().isComboShift() - }; + clicks = SkillAPI.getSettings().getEnabledClicks(); } /** @@ -290,7 +285,7 @@ public String getSaveString(List clicks) for (Click click : clicks) { if (result.length() > 0) result += ' '; - result += click.name().charAt(0); + result += click.getKey(); } return result; } @@ -304,7 +299,7 @@ public String getSaveString(List clicks) */ public int parseCombo(String combo) { - if (combo == null) + if (combo == null || combo.length() == 0) return -1; String[] parts = combo.toLowerCase().split(" "); @@ -312,17 +307,12 @@ public int parseCombo(String combo) int i = 0; for (String part : parts) { - if (part.equals("l")) - clicks[i++] = Click.LEFT; - else if (part.equals("r")) - clicks[i++] = Click.RIGHT; - else if (part.equals("s")) - clicks[i++] = Click.SHIFT; - else - { + clicks[i] = Click.getByName(part); + if (clicks[i] == null) { Logger.invalid("Invalid combo click type: " + part); return -1; } + i++; } return convertCombo(clicks); diff --git a/src/com/sucy/skill/manager/RegistrationManager.java b/src/com/sucy/skill/manager/RegistrationManager.java index 59382c40..aef71534 100644 --- a/src/com/sucy/skill/manager/RegistrationManager.java +++ b/src/com/sucy/skill/manager/RegistrationManager.java @@ -32,6 +32,7 @@ import com.sucy.skill.api.SkillPlugin; import com.sucy.skill.api.classes.RPGClass; import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.dynamic.ComponentRegistry; import com.sucy.skill.dynamic.DynamicClass; import com.sucy.skill.dynamic.DynamicSkill; import com.sucy.skill.log.LogType; @@ -48,14 +49,12 @@ * them from other plugins while validating everything to make sure it should be * added.

*/ -public class RegistrationManager -{ +public class RegistrationManager { /** * The registration modes used by the manager. These values are used to check * what can be registered at any given time. */ - public enum Mode - { + public enum Mode { STARTUP, SKILL, CLASS, @@ -81,119 +80,125 @@ public enum Mode * * @param api SkillAPI reference */ - public RegistrationManager(SkillAPI api) - { + public RegistrationManager(SkillAPI api) { this.api = api; skillConfig = new CommentedConfig(api, "dynamic" + File.separator + "skills"); classConfig = new CommentedConfig(api, "dynamic" + File.separator + "classes"); - new File(api.getDataFolder().getAbsolutePath() + File.separator + "dynamic" + File.separator + "skill").mkdirs(); - new File(api.getDataFolder().getAbsolutePath() + File.separator + "dynamic" + File.separator + "class").mkdirs(); + new File(api.getDataFolder() + .getAbsolutePath() + File.separator + "dynamic" + File.separator + "skill").mkdirs(); + new File(api.getDataFolder() + .getAbsolutePath() + File.separator + "dynamic" + File.separator + "class").mkdirs(); } /** * Initializes the registration manager, fetching skills and classes from * configuration files and other plugins. */ - public void initialize() - { + public void initialize() { // Make sure dynamic files are created - if (!skillConfig.getConfigFile().exists()) - { + if (!skillConfig.getConfigFile().exists()) { skillConfig.save(); } - if (!classConfig.getConfigFile().exists()) - { + if (!classConfig.getConfigFile().exists()) { classConfig.save(); } + Logger.log(LogType.REGISTRATION, 1, "Loading components..."); + + for (Plugin plugin : api.getServer().getPluginManager().getPlugins()) { + if (plugin instanceof SkillPlugin) { + try { + Logger.log(LogType.REGISTRATION, 2, " - " + plugin.getName()); + ((SkillPlugin) plugin).getTriggers().forEach(ComponentRegistry::register); + ((SkillPlugin) plugin).getComponents().forEach(ComponentRegistry::register); + } catch (Throwable t) { + Logger.invalid("Plugin \"" + plugin.getName() + "\" failed to register skills. Error details:"); + t.printStackTrace(); + } + } + } + ComponentRegistry.save(); + Logger.log(LogType.REGISTRATION, 1, "Loading skills..."); // Request plugin skills mode = Mode.SKILL; - for (Plugin plugin : api.getServer().getPluginManager().getPlugins()) - { - if (plugin instanceof SkillPlugin) - { - Logger.log(LogType.REGISTRATION, 2, " - " + plugin.getName()); - ((SkillPlugin) plugin).registerSkills(api); + for (Plugin plugin : api.getServer().getPluginManager().getPlugins()) { + if (plugin instanceof SkillPlugin) { + try { + Logger.log(LogType.REGISTRATION, 2, " - " + plugin.getName()); + ((SkillPlugin) plugin).registerSkills(api); + } catch (Throwable t) { + Logger.invalid("Plugin \"" + plugin.getName() + "\" failed to register skills. Error details:"); + t.printStackTrace(); + } } } // Load dynamic skills from skills.yml mode = Mode.DYNAMIC; - if (!skillConfig.getConfig().getBoolean("loaded", false)) - { + if (!skillConfig.getConfig().getBoolean("loaded", false)) { Logger.log(LogType.REGISTRATION, 1, "Loading dynamic skills from skills.yml..."); skillConfig.getConfig().set("loaded", true); - for (String key : skillConfig.getConfig().keys()) - { - if (!skillConfig.getConfig().isSection(key)) - { - Logger.log(LogType.REGISTRATION, 3, "Skipping \"" + key + "\" because it isn't a configuration section"); + for (String key : skillConfig.getConfig().keys()) { + if (!skillConfig.getConfig().isSection(key)) { + Logger.log( + LogType.REGISTRATION, + 3, + "Skipping \"" + key + "\" because it isn't a configuration section"); continue; } - if (!SkillAPI.isSkillRegistered(key)) - { + try { DynamicSkill skill = new DynamicSkill(key); - api.getServer().getPluginManager().registerEvents(skill, api); - api.addDynamicSkill(skill); skill.load(skillConfig.getConfig().getSection(key)); - CommentedConfig sConfig = new CommentedConfig(api, SKILL_DIR + key); - sConfig.clear(); - skill.save(sConfig.getConfig().createSection(key)); - skill.save(skillConfig.getConfig().createSection(key)); - sConfig.save(); - Logger.log(LogType.REGISTRATION, 2, "Loaded the dynamic skill: " + key); - } - else - { - Logger.invalid("Duplicate skill detected: " + key); + if (!SkillAPI.isSkillRegistered(skill.getName())) { + api.addDynamicSkill(skill); + skill.registerEvents(api); + CommentedConfig sConfig = new CommentedConfig(api, SKILL_DIR + key); + sConfig.clear(); + skill.save(sConfig.getConfig().createSection(key)); + skill.save(skillConfig.getConfig().createSection(key)); + sConfig.save(); + Logger.log(LogType.REGISTRATION, 2, "Loaded the dynamic skill: " + key); + } else { + Logger.invalid("Duplicate skill detected: " + key); + } + } catch (Exception ex) { + Logger.invalid("Failed to load skill: " + key + " - " + ex.getMessage()); } } - } - else - { + } else { Logger.log(LogType.REGISTRATION, 1, "skills.yml doesn't have any changes, skipping it"); } // Load individual dynamic skills Logger.log(LogType.REGISTRATION, 1, "Loading individual dynamic skill files..."); File skillRoot = new File(api.getDataFolder().getPath() + File.separator + SKILL_FOLDER); - if (skillRoot.exists()) - { + if (skillRoot.exists()) { File[] files = skillRoot.listFiles(); - if (files != null) - { - for (File file : files) - { + if (files != null) { + for (File file : files) { + if (!file.getName().endsWith(".yml")) { continue; } String name = file.getName().replace(".yml", ""); - try - { - if (!SkillAPI.isSkillRegistered(name)) - { - CommentedConfig sConfig = new CommentedConfig(api, SKILL_DIR + name); - DynamicSkill skill = new DynamicSkill(name); - api.getServer().getPluginManager().registerEvents(skill, api); + try { + CommentedConfig sConfig = new CommentedConfig(api, SKILL_DIR + name); + DynamicSkill skill = new DynamicSkill(name); + skill.load(sConfig.getConfig().getSection(name)); + if (!SkillAPI.isSkillRegistered(skill.getName())) { api.addDynamicSkill(skill); - skill.load(sConfig.getConfig().getSection(name)); + skill.registerEvents(api); sConfig.clear(); skill.save(sConfig.getConfig().createSection(name)); skill.save(skillConfig.getConfig().createSection(name)); sConfig.save(); Logger.log(LogType.REGISTRATION, 2, "Loaded the dynamic skill: " + name); - } - else if (SkillAPI.getSkill(name) instanceof DynamicSkill) - { + } else if (SkillAPI.getSkill(name) instanceof DynamicSkill) { Logger.log(LogType.REGISTRATION, 3, name + " is already loaded, skipping it"); - } - else - { + } else { Logger.invalid("Duplicate skill detected: " + name); } - } - catch (Exception ex) - { + } catch (Exception ex) { Logger.invalid("Failed to load skill: " + name + " - " + ex.getMessage()); } } @@ -204,85 +209,75 @@ else if (SkillAPI.getSkill(name) instanceof DynamicSkill) // Request plugin classes mode = Mode.CLASS; - for (Plugin plugin : api.getServer().getPluginManager().getPlugins()) - { - if (plugin instanceof SkillPlugin) - { + for (Plugin plugin : api.getServer().getPluginManager().getPlugins()) { + if (plugin instanceof SkillPlugin) { Logger.log(LogType.REGISTRATION, 2, " - " + plugin.getName()); - ((SkillPlugin) plugin).registerClasses(api); + try { + ((SkillPlugin) plugin).registerClasses(api); + } catch (Throwable t) { + Logger.invalid("Plugin \"" + plugin.getName() + "\" failed to register classes. Error details:"); + t.printStackTrace(); + } } } // Load dynamic classes from classes.yml - if (!classConfig.getConfig().getBoolean("loaded", false)) - { + if (!classConfig.getConfig().getBoolean("loaded", false)) { Logger.log(LogType.REGISTRATION, 1, "Loading dynamic classes from classes.yml..."); classConfig.getConfig().set("loaded", true); - for (String key : classConfig.getConfig().keys()) - { - if (key.equals("loaded")) - { + for (String key : classConfig.getConfig().keys()) { + if (key.equals("loaded")) { continue; } - if (!SkillAPI.isClassRegistered(key)) - { + try { DynamicClass tree = new DynamicClass(api, key); tree.load(classConfig.getConfig().getSection(key)); - api.addDynamicClass(tree); - CommentedConfig cConfig = new CommentedConfig(api, CLASS_DIR + key); - cConfig.clear(); - tree.save(cConfig.getConfig().createSection(key)); - tree.save(classConfig.getConfig().createSection(key)); - cConfig.save(); - Logger.log(LogType.REGISTRATION, 2, "Loaded the dynamic class: " + key); - } - else - { - Logger.invalid("Duplicate class detected: " + key); + if (!SkillAPI.isClassRegistered(tree.getName())) { + api.addDynamicClass(tree); + CommentedConfig cConfig = new CommentedConfig(api, CLASS_DIR + key); + cConfig.clear(); + tree.save(cConfig.getConfig().createSection(key)); + tree.save(classConfig.getConfig().createSection(key)); + cConfig.save(); + Logger.log(LogType.REGISTRATION, 2, "Loaded the dynamic class: " + key); + } else { + Logger.invalid("Duplicate class detected: " + key); + } + } catch (Exception ex) { + Logger.invalid("Failed to load class \"" + key + "\""); + ex.printStackTrace(); } } - } - else - { + } else { Logger.log(LogType.REGISTRATION, 1, "classes.yml doesn't have any changes, skipping it"); } // Load individual dynamic classes Logger.log(LogType.REGISTRATION, 1, "Loading individual dynamic class files..."); File classRoot = new File(api.getDataFolder().getPath() + File.separator + CLASS_FOLDER); - if (classRoot.exists()) - { + if (classRoot.exists()) { File[] files = classRoot.listFiles(); - if (files != null) - { - for (File file : files) - { - try - { + if (files != null) { + for (File file : files) { + if (!file.getName().endsWith(".yml")) { continue; } + try { String name = file.getName().replace(".yml", ""); - if (!SkillAPI.isClassRegistered(name)) - { - CommentedConfig cConfig = new CommentedConfig(api, CLASS_DIR + name); - DynamicClass tree = new DynamicClass(api, name); - tree.load(cConfig.getConfig().getSection(name)); + CommentedConfig cConfig = new CommentedConfig(api, CLASS_DIR + name); + DynamicClass tree = new DynamicClass(api, name); + tree.load(cConfig.getConfig().getSection(name)); + if (!SkillAPI.isClassRegistered(tree.getName())) { api.addDynamicClass(tree); cConfig.clear(); tree.save(cConfig.getConfig().createSection(name)); tree.save(classConfig.getConfig().createSection(name)); cConfig.save(); Logger.log(LogType.REGISTRATION, 2, "Loaded the dynamic class: " + name); - } - else if (SkillAPI.getClass(name) instanceof DynamicClass) - { + } else if (SkillAPI.getClass(name) instanceof DynamicClass) { Logger.log(LogType.REGISTRATION, 3, name + " is already loaded, skipping it"); - } - else - { + } else { Logger.invalid("Duplicate class detected: " + name); } - } - catch (Exception ex) - { + } catch (Exception ex) { Logger.invalid("Failed to load class file: " + file.getName() + " - " + ex.getMessage()); } } @@ -295,8 +290,7 @@ else if (SkillAPI.getClass(name) instanceof DynamicClass) mode = Mode.DONE; // Arrange skill trees - for (RPGClass c : SkillAPI.getClasses().values()) - { + for (RPGClass c : SkillAPI.getClasses().values()) { c.arrange(); } @@ -314,36 +308,30 @@ else if (SkillAPI.getClass(name) instanceof DynamicClass) * * @return the class if valid, null otherwise */ - public Skill validate(Skill skill) - { + public Skill validate(Skill skill) { // Cannot register outside the allotted time - if (mode != Mode.SKILL) - { + if (mode != Mode.SKILL) { throw new IllegalStateException("Skills cannot be added outside the provided SkillPlugin method"); } // Cannot be null - else if (skill == null) - { + else if (skill == null) { throw new IllegalArgumentException("Cannot register a null skill"); } // Cannot have multiple skills with the same name - else if (SkillAPI.isSkillRegistered(skill.getName())) - { + else if (SkillAPI.isSkillRegistered(skill.getName())) { Logger.invalid("Duplicate skill name: \"" + skill.getName() + "\" - skipping the duplicate"); } // Save new data to config - else - { + else { CommentedConfig singleFile = new CommentedConfig(api, "skill" + File.separator + skill.getName()); DataSection config = singleFile.getConfig(); - try - { + try { // Soft save to ensure optional data starts off in the config skill.softSave(config); @@ -354,14 +342,13 @@ else if (SkillAPI.isSkillRegistered(skill.getName())) skill.save(config); singleFile.save(); - if (skill instanceof Listener) + if (skill instanceof Listener) { Bukkit.getServer().getPluginManager().registerEvents((Listener) skill, api); + } // Skill is ready to be registered return skill; - } - catch (Exception ex) - { + } catch (Exception ex) { Logger.bug("Failed to save skill data to config for \"" + skill.getName() + "\" - skipping registration"); ex.printStackTrace(); } @@ -379,36 +366,30 @@ else if (SkillAPI.isSkillRegistered(skill.getName())) * * @return the class if valid, null otherwise */ - public RPGClass validate(RPGClass rpgClass) - { + public RPGClass validate(RPGClass rpgClass) { // Cannot register outside the allotted time - if (mode != Mode.CLASS) - { + if (mode != Mode.CLASS) { throw new IllegalStateException("Classes cannot be added outside the provided SkillPlugin method"); } // Cannot be null - else if (rpgClass == null) - { + else if (rpgClass == null) { throw new IllegalArgumentException("Cannot register a null class"); } // Cannot have multiple skills with the same name - else if (SkillAPI.isClassRegistered(rpgClass.getName())) - { + else if (SkillAPI.isClassRegistered(rpgClass.getName())) { Logger.invalid("Duplicate class name: \"" + rpgClass.getName() + "\" - skipping the duplicate"); } // Save new data to config - else - { + else { CommentedConfig singleFile = new CommentedConfig(api, "class" + File.separator + rpgClass.getName()); DataSection config = singleFile.getConfig(); - try - { + try { // Soft save to ensure optional data starts off in the config rpgClass.softSave(config); @@ -422,9 +403,7 @@ else if (SkillAPI.isClassRegistered(rpgClass.getName())) // Skill is ready to be registered return rpgClass; - } - catch (Exception ex) - { + } catch (Exception ex) { Logger.bug("Failed to save class data to config for \"" + rpgClass.getName() + "\" - skipping registration"); ex.printStackTrace(); } @@ -436,8 +415,7 @@ else if (SkillAPI.isClassRegistered(rpgClass.getName())) /** * @return true if registering dynamic skills, false otherwise */ - public boolean isAddingDynamicSkills() - { + public boolean isAddingDynamicSkills() { return mode == Mode.DYNAMIC; } } diff --git a/src/com/sucy/skill/manager/ResourceManager.java b/src/com/sucy/skill/manager/ResourceManager.java index ede1e6ed..4cd2f650 100644 --- a/src/com/sucy/skill/manager/ResourceManager.java +++ b/src/com/sucy/skill/manager/ResourceManager.java @@ -40,7 +40,7 @@ public class ResourceManager { public static final String QUESTS_FOLDER = "plugins" + File.separator + "Quests" + File.separator + "modules", - SCHEME_FOLDER = "plugins" + File.separator + "SkillAPI" + File.separator + "img" + File.separator + "default"; + PLACEHOLDERS_FOLDER = "plugins" + File.separator + "PlaceholderAPI" + File.separator + "expansions"; /** * Copies a resource embedded in the jar into the given folder @@ -84,4 +84,8 @@ public static void copyQuestsModule() { copyResource("SkillAPIModule.jar", QUESTS_FOLDER); } + + public static void copyPlaceholdersModule() { + copyResource("SkillAPIPlaceholders.jar", PLACEHOLDERS_FOLDER); + } } diff --git a/src/com/sucy/skill/packet/PacketHandler.java b/src/com/sucy/skill/packet/PacketHandler.java new file mode 100644 index 00000000..02817b7c --- /dev/null +++ b/src/com/sucy/skill/packet/PacketHandler.java @@ -0,0 +1,57 @@ +/** + * Minenotch © + * com.minenotch.nms.PacketHandler + */ +package com.sucy.skill.packet; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.event.KeyPressEvent; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; + +/** + * Handles the interception of packets, applying effects where necessary + */ +class PacketHandler extends ChannelDuplexHandler { + private Player player; + private Field dropField; + + PacketHandler(final Player player, final Field dropField) { + this.player = player; + this.dropField = dropField; + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + super.write(ctx, msg, promise); + } + + @Override + public void channelRead(ChannelHandlerContext c, Object m) throws Exception { + switch (m.getClass().getSimpleName()) { + case "PacketPlayInBlockDig": + if (dropField != null){ + if (((Enum) dropField.get(m)).name().equals("DROP_ITEM")) { + callEvent(KeyPressEvent.Key.Q); + } + } + break; + case "PacketPlayInArmAnimation": + callEvent(KeyPressEvent.Key.LEFT); + break; + case "PacketPlayInUseItem": + case "PacketPlayInBlockPlace": + callEvent(KeyPressEvent.Key.RIGHT); + } + super.channelRead(c, m); + } + + private void callEvent(final KeyPressEvent.Key key) { + SkillAPI.schedule(() -> Bukkit.getPluginManager().callEvent(new KeyPressEvent(player, key)), 0); + } +} diff --git a/src/com/sucy/skill/packet/PacketInjector.java b/src/com/sucy/skill/packet/PacketInjector.java new file mode 100644 index 00000000..d38412ae --- /dev/null +++ b/src/com/sucy/skill/packet/PacketInjector.java @@ -0,0 +1,114 @@ +package com.sucy.skill.packet; + +import com.rit.sucy.reflect.Reflection; +import com.sucy.skill.SkillAPI; +import io.netty.channel.Channel; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * SkillAPI © 2018 + * com.sucy.skill.PacketInjector + */ +public class PacketInjector { + private Field playerCon; + private Field network; + private Method handle; + private Field k; + private Field dropField; + + private SkillAPI skillAPI; + + /** + * Sets up the injector, grabbing necessary reflection data + */ + public PacketInjector(final SkillAPI skillAPI) { + this.skillAPI = skillAPI; + + try { + String nms = Reflection.getNMSPackage(); + playerCon = Class.forName(nms + "EntityPlayer") + .getField("playerConnection"); + + Class playerConnection = Class.forName(nms + "PlayerConnection"); + network = playerConnection.getField("networkManager"); + + Class networkManager = Class.forName(nms + "NetworkManager"); + try { + k = networkManager.getField("channel"); + } catch (Exception ex) { + k = networkManager.getDeclaredField("i"); + k.setAccessible(true); + } + + handle = Class.forName(Reflection.getCraftPackage() + "entity.CraftPlayer").getMethod("getHandle"); + } catch (Throwable t) { + this.error(); + t.printStackTrace(); + } + + try { + dropField = Reflection.getNMSClass("PacketPlayInBlockDig").getDeclaredField("c"); + dropField.setAccessible(true); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public boolean isWorking() { + return handle != null; + } + + private void error() { + skillAPI.getLogger().warning("Failed to set up packet listener - some click combos may not behave properly"); + } + + /** + * Injects an interceptor to the player's network manager + * + * @param p player to add to + */ + public void addPlayer(Player p) { + if (handle == null) return; + + try { + Channel ch = getChannel(p); + if (ch.pipeline().get("PacketInjector") == null) { + PacketHandler h = new PacketHandler(p, dropField); + ch.pipeline().addBefore("packet_handler", "PacketInjector", h); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * Removes an interruptor from a player's network manager + * + * @param p player to remove from + */ + public void removePlayer(Player p) { + if (handle == null) return; + + try { + Channel ch = getChannel(p); + if (ch.pipeline().get("PacketInjector") != null) { + ch.pipeline().remove("PacketInjector"); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * Gets the channel used by a player's network manager + * + * @return retrieved channel + */ + private Channel getChannel(final Player player) throws IllegalAccessException, InvocationTargetException { + return (Channel) k.get(network.get(playerCon.get(handle.invoke(player)))); + } +} diff --git a/src/com/sucy/skill/task/CooldownTask.java b/src/com/sucy/skill/task/CooldownTask.java index de862201..48ebcb2c 100644 --- a/src/com/sucy/skill/task/CooldownTask.java +++ b/src/com/sucy/skill/task/CooldownTask.java @@ -29,24 +29,22 @@ import com.rit.sucy.version.VersionManager; import com.sucy.skill.SkillAPI; import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.thread.RepeatThreadTask; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; /** * Handles updating cooldown values on skill bars for players */ -public class CooldownTask extends BukkitRunnable +public class CooldownTask extends RepeatThreadTask { /** * Initializes a new cooldown task. This shouldn't be used by * other plugins as it is already set up by the API and additional * copies would create extra processing for no real gain. - * - * @param api SkillAPI reference */ - public CooldownTask(SkillAPI api) + public CooldownTask() { - runTaskTimer(api, 20, 20); + super(20, 20); } /** diff --git a/src/com/sucy/skill/task/EffectTask.java b/src/com/sucy/skill/task/EffectTask.java new file mode 100644 index 00000000..20f4f178 --- /dev/null +++ b/src/com/sucy/skill/task/EffectTask.java @@ -0,0 +1,44 @@ +/** + * SkillAPI + * com.sucy.skill.task.EffectTask + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.task; + +import com.sucy.skill.api.particle.EffectManager; +import com.sucy.skill.thread.RepeatThreadTask; + +public class EffectTask extends RepeatThreadTask +{ + public EffectTask() + { + super(1, 1); + } + + @Override + public void run() + { + EffectManager.tick(); + } +} diff --git a/src/com/sucy/skill/task/GUITask.java b/src/com/sucy/skill/task/GUITask.java index c6be812a..4a20dfe9 100644 --- a/src/com/sucy/skill/task/GUITask.java +++ b/src/com/sucy/skill/task/GUITask.java @@ -35,14 +35,16 @@ import com.sucy.skill.dynamic.DynamicSkill; import com.sucy.skill.log.LogType; import com.sucy.skill.log.Logger; +import com.sucy.skill.thread.RepeatThreadTask; + +import org.bukkit.attribute.Attribute; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; /** * Task that handles updating GUI elements such as level bar, * food bar, and action bar according to the config.yml content. */ -public class GUITask extends BukkitRunnable +public class GUITask extends RepeatThreadTask { private final boolean levelMana; private final boolean levelLevel; @@ -56,8 +58,6 @@ public class GUITask extends BukkitRunnable private final boolean useAction; private final String actionText; - private boolean isRunning = false; - /** * Sets up the task, running if any of the GUI options are enabled * @@ -65,6 +65,8 @@ public class GUITask extends BukkitRunnable */ public GUITask(SkillAPI api) { + super(5, 5); + String levelBar = SkillAPI.getSettings().getLevelBar().toLowerCase(); levelMana = levelBar.equals("mana"); levelLevel = levelBar.equals("level"); @@ -82,20 +84,9 @@ public GUITask(SkillAPI api) Logger.log(LogType.GUI, 1, "GUI Settings: " + levelMana + "/" + levelLevel + "/" + foodMana + "/" + foodExp + "/" + useAction + "/" + actionText); if (useAction || levelMana || levelLevel || foodMana || foodExp || forceScaling) - { - runTaskTimer(api, 5, 5); - isRunning = true; - } - } + return; - /** - * Checks whether or not the task is running - * - * @return true if running, false otherwise - */ - public boolean isRunning() - { - return isRunning; + expired = true; } /** @@ -117,15 +108,21 @@ public void run() if (oldHealth) player.setHealthScale(20); else - player.setHealthScale(player.getMaxHealth()); + player.setHealthScale(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); } // Level bar options if (levelMana) { Logger.log(LogType.GUI, 2, "Updating level bar with mana"); - player.setLevel((int) data.getMana()); - player.setExp((float) (0.999 * data.getMana() / data.getMaxMana())); + if (data.getMaxMana() == 0) { + player.setLevel(0); + player.setExp(0); + } + else { + player.setLevel((int) data.getMana()); + player.setExp(Math.min(0.999f, (float) (0.999 * data.getMana() / data.getMaxMana()))); + } } else if (levelLevel) { @@ -148,11 +145,17 @@ else if (levelLevel) { Logger.log(LogType.GUI, 2, "Updating food bar with mana"); player.setSaturation(20); - player.setFoodLevel((int) Math.ceil(20 * data.getMana() / data.getMaxMana())); + if (data.getMaxMana() == 0) { + player.setFoodLevel(20);; + } + else { + player.setFoodLevel((int) Math.ceil(20 * data.getMana() / data.getMaxMana())); + } } else if (foodExp) { Logger.log(LogType.GUI, 2, "Updating food bar with class level/exp"); + player.setSaturation(20); if (!data.hasClass()) { player.setFoodLevel(0); @@ -169,7 +172,7 @@ else if (foodExp) { Logger.log(LogType.GUI, 2, "Updating action bar (Working=" + ActionBar.isSupported() + ")"); PlayerClass main = data.getMainClass(); - String filtered = actionText + String filtered = (main.getData().hasActionBarText() ? main.getData().getActionBarText() : actionText) .replace("{combo}", data.getComboData().getCurrentComboString()) .replace("{class}", main.getData().getPrefix()) .replace("{level}", "" + main.getLevel()) @@ -180,7 +183,7 @@ else if (foodExp) .replace("{maxMana}", "" + (int) data.getMaxMana()) .replace("{name}", player.getName()) .replace("{health}", "" + (int) player.getHealth()) - .replace("{maxHealth}", "" + (int) player.getMaxHealth()) + .replace("{maxHealth}", "" + (int) player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue()) .replace("{attr}", "" + data.getAttributePoints()) .replace("{sp}", "" + main.getPoints()); while (filtered.contains("{value:")) diff --git a/src/com/sucy/skill/task/InventoryTask.java b/src/com/sucy/skill/task/InventoryTask.java deleted file mode 100644 index 02b6ad61..00000000 --- a/src/com/sucy/skill/task/InventoryTask.java +++ /dev/null @@ -1,410 +0,0 @@ -/** - * SkillAPI - * com.sucy.skill.task.InventoryTask - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Steven Sucy - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software") to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.sucy.skill.task; - -import com.rit.sucy.config.FilterType; -import com.rit.sucy.version.VersionManager; -import com.sucy.skill.SkillAPI; -import com.sucy.skill.api.player.PlayerData; -import com.sucy.skill.api.skills.Skill; -import com.sucy.skill.language.ErrorNodes; -import com.sucy.skill.manager.AttributeManager; -import org.bukkit.ChatColor; -import org.bukkit.GameMode; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.regex.Pattern; - -/** - * Repeating task to check for equipment requirements - */ -public class InventoryTask extends BukkitRunnable -{ - private static InventoryTask instance; - - private static Pattern levelRegex; - private static Pattern classRegex; - private static Pattern excludeRegex; - - private static SkillAPI plugin; - private int playersPerCheck; - private int index = -1; - - private static HashMap attribs = new HashMap(); - private static HashMap tempAttribs = new HashMap(); - - /** - * Task constructor - * - * @param p API reference - * @param playersPerCheck how many players to check each tick - */ - public InventoryTask(SkillAPI p, int playersPerCheck) - { - instance = this; - - this.playersPerCheck = playersPerCheck; - if (plugin != null) return; - plugin = p; - runTaskTimer(plugin, 1, 1); - - levelRegex = Pattern.compile(SkillAPI.getSettings().getLoreLevelText() + "[0-9]+"); - classRegex = Pattern.compile(SkillAPI.getSettings().getLoreClassText() + ".+"); - excludeRegex = Pattern.compile(SkillAPI.getSettings().getLoreExcludeText() + ".+"); - } - - /** - * Clears the plugin reference on cancel - */ - @Override - public void cancel() - { - super.cancel(); - plugin = null; - } - - /** - * Checks player equipment for requirements - */ - @Override - public void run() - { - Player[] players = VersionManager.getOnlinePlayers(); - for (int i = 0; i < playersPerCheck; i++) - { - if (!getNextPlayer(players)) return; - if (i >= players.length) return; - - // Get the player data - Player player = players[index]; - _check(player); - } - } - - /** - * Checks a player for item requirements and stat bonuses - * - * @param player player to check - */ - private void _check(Player player) - { - if (player.getGameMode() == GameMode.CREATIVE) return; - PlayerData data = SkillAPI.getPlayerData(player); - - // Check for lore strings - int index = 0; - tempAttribs.clear(); - for (ItemStack item : player.getInventory().getArmorContents()) - { - if (cannotUse(data, item)) - removeArmor(player, index); - index++; - } - if (VersionManager.isVersionAtLeast(VersionManager.V1_9_0) - && cannotUse(data, player.getInventory().getItemInOffHand())) - { - player.getInventory().addItem(player.getInventory().getItemInOffHand()); - player.getInventory().setItemInOffHand(null); - } - - if (SkillAPI.getSettings().isDropWeapon()) - { - if (cannotUse(data, player.getItemInHand())) - { - player.getWorld().dropItem(player.getLocation(), player.getItemInHand()); - player.setItemInHand(null); - } - } - else if (SkillAPI.getSettings().isCheckAttributes()) - cannotUse(data, player.getItemInHand()); - - // Give attributes - if (SkillAPI.getSettings().isCheckAttributes()) - { - if (!attribs.containsKey(player.getUniqueId())) - attribs.put(player.getUniqueId(), new AttribBuffs()); - attribs.get(player.getUniqueId()).apply(data); - } - } - - /** - * Checks a player for item requirements and stat bonuses - * - * @param player player to check - */ - public static void check(Player player) - { - if (instance != null) - instance._check(player); - } - - /** - * Removes attribute buff data for a player (should only be called by the API) - * - * @param playerId player UUID - */ - public static void clear(UUID playerId) - { - attribs.remove(playerId); - } - - /** - *

Checks if the player cannot use the item

- *

If SkillAPI is not enabled or it's lore requirement setting - * is disabled, this will always return false

- * - * @param player player to check for - * @param item item to check - * - * @return true if cannot use, false otherwise - */ - public static boolean cannotUse(PlayerData player, ItemStack item) - { - if (plugin == null) return false; - if (item == null) return false; - boolean hasRequirement = false; - boolean needsRequirement = false; - boolean skills = SkillAPI.getSettings().isCheckSkillLore(); - boolean attributes = SkillAPI.getSettings().isAttributesEnabled(); - if (item.hasItemMeta() && item.getItemMeta().hasLore()) - { - List lore = item.getItemMeta().getLore(); - HashMap itemAttribs = new HashMap(); - - // Check each line of the lore - for (String line : lore) - { - String colorless = ChatColor.stripColor(line); - String lower = colorless.toLowerCase(); - - // Level requirements - if (levelRegex.matcher(colorless).matches()) - { - int level = Integer.parseInt(colorless.substring(SkillAPI.getSettings().getLoreLevelText().length())); - if (!player.hasClass() || player.getMainClass().getLevel() < level) - { - return true; - } - } - - // Class requirements - else if (classRegex.matcher(colorless).matches()) - { - needsRequirement = true; - String name = colorless.substring(SkillAPI.getSettings().getLoreClassText().length()); - if (name.contains(", ")) - { - String[] names = name.split(", "); - for (String n : names) - { - if (player.isClass(SkillAPI.getClass(n))) - { - hasRequirement = true; - } - } - } - else - { - if (player.isClass(SkillAPI.getClass(name))) - { - hasRequirement = true; - } - } - } - - // Class exclusion - else if (excludeRegex.matcher(colorless).matches()) - { - String name = colorless.substring(SkillAPI.getSettings().getLoreExcludeText().length()); - if (name.contains(", ")) - { - String[] names = name.split(", "); - for (String n : names) - { - if (player.isClass(SkillAPI.getClass(n))) - { - return true; - } - } - } - else - { - if (player.isClass(SkillAPI.getClass(name))) - { - return true; - } - } - } - - // Skill requirements - else - { - if (skills) - { - for (Skill skill : SkillAPI.getSkills().values()) - { - String check = SkillAPI.getSettings().getSkillText().replace("{skill}", skill.getName()).toLowerCase(); - if (lower.startsWith(check)) - { - if (!player.hasSkill(skill.getName())) - return true; - - int level = Integer.parseInt(colorless.substring(check.length())); - if (player.getSkill(skill.getName()).getLevel() < level) - return true; - } - } - } - - // Attribute requirements - if (attributes) - { - for (String key : SkillAPI.getAttributeManager().getKeys()) - { - AttributeManager.Attribute attr = SkillAPI.getAttributeManager().getAttribute(key); - String name = attr.getName(); - String check = SkillAPI.getSettings().getAttrReqText().replace("{attr}", name).toLowerCase(); - if (lower.startsWith(check)) - { - int amount = Integer.parseInt(colorless.substring(check.length())); - if (player.getAttribute(attr.getKey()) < amount) - { - return true; - } - } - - if (SkillAPI.getSettings().isCheckAttributes()) - { - check = SkillAPI.getSettings().getAttrGiveText().replace("{attr}", name).toLowerCase(); - if (lower.startsWith(check)) - { - int amount = Integer.parseInt(colorless.substring(check.length())); - if (itemAttribs.containsKey(attr.getKey())) - itemAttribs.put(attr.getKey(), itemAttribs.get(attr.getKey()) + amount); - else - itemAttribs.put(attr.getKey(), amount); - } - } - } - } - } - } - - // Add attributes to the result afterwards so when the item isn't usable, - // this part is skipped. - if (needsRequirement == hasRequirement) - { - for (Map.Entry entry : itemAttribs.entrySet()) - { - if (tempAttribs.containsKey(entry.getKey())) - tempAttribs.put(entry.getKey(), tempAttribs.get(entry.getKey()) + entry.getValue()); - else - tempAttribs.put(entry.getKey(), entry.getValue()); - } - } - } - return needsRequirement != hasRequirement; - } - - /** - * Removes the armor piece at the given index - * - * @param player player to remove for - * @param index index of the armor piece to remove - */ - private void removeArmor(Player player, int index) - { - ItemStack[] armor = player.getInventory().getArmorContents(); - player.getInventory().addItem(armor[index]); - armor[index] = null; - player.getInventory().setArmorContents(armor); - SkillAPI.getLanguage().sendMessage(ErrorNodes.CANNOT_USE, player, FilterType.COLOR); - } - - /** - * Gets the next player to check - * - * @return true if found a player, false otherwise - */ - private boolean getNextPlayer(Player[] players) - { - index++; - - // Limit the index - if (index >= players.length) - { - players = VersionManager.getOnlinePlayers(); - index = 0; - } - - // Make sure its a valid player - return players.length > 0 && (players[index].isOnline() || getNextPlayer(players)); - } - - private class AttribBuffs - { - private HashMap attribs = new HashMap(); - - public void apply(PlayerData data) - { - boolean dirty = false; - for (Map.Entry entry : attribs.entrySet()) - { - if (!tempAttribs.containsKey(entry.getKey())) - { - data.addBonusAttributes(entry.getKey(), -entry.getValue()); - dirty = true; - } - else - { - int dif = tempAttribs.get(entry.getKey()) - entry.getValue(); - if (dif != 0) - { - data.addBonusAttributes(entry.getKey(), dif); - dirty = true; - } - } - } - for (Map.Entry entry : tempAttribs.entrySet()) - { - if (!attribs.containsKey(entry.getKey())) - { - data.addBonusAttributes(entry.getKey(), entry.getValue()); - dirty = true; - } - } - if (dirty) - attribs = new HashMap(tempAttribs); - } - } -} diff --git a/src/com/sucy/skill/task/ManaTask.java b/src/com/sucy/skill/task/ManaTask.java index 3979fb99..b89a5dee 100644 --- a/src/com/sucy/skill/task/ManaTask.java +++ b/src/com/sucy/skill/task/ManaTask.java @@ -31,30 +31,27 @@ import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.log.LogType; import com.sucy.skill.log.Logger; +import com.sucy.skill.thread.RepeatThreadTask; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; /** *

Restores mana to all players over time.

*

This task is run by the API and you should not * use this task yourself.

*/ -public class ManaTask extends BukkitRunnable +public class ManaTask extends RepeatThreadTask { - - final SkillAPI plugin; - /** - *

Starts a new task for regenerating mana over time. The task is + * Starts a new task for regenerating mana over time. The task is * started automatically so don't initialize this class unless wanting to - * start a new task.

- * - * @param plugin SkillAPI reference + * start a new task. */ - public ManaTask(SkillAPI plugin) + public ManaTask() { - this.plugin = plugin; - runTaskTimer(plugin, SkillAPI.getSettings().getGainFreq(), SkillAPI.getSettings().getGainFreq()); + super( + SkillAPI.getSettings().getGainFreq(), + SkillAPI.getSettings().getGainFreq() + ); } /** diff --git a/src/com/sucy/skill/task/PreviewTask.java b/src/com/sucy/skill/task/PreviewTask.java new file mode 100644 index 00000000..246e23d0 --- /dev/null +++ b/src/com/sucy/skill/task/PreviewTask.java @@ -0,0 +1,75 @@ +/** + * SkillAPI + * com.sucy.skill.task.PreviewTask + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.task; + +import com.sucy.skill.SkillAPI; +import com.sucy.skill.api.particle.Particle; +import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.cast.IndicatorSettings; +import com.sucy.skill.thread.RepeatThreadTask; +import org.bukkit.entity.Player; + +import java.util.List; + +public class PreviewTask extends RepeatThreadTask +{ + private Player player; + private PlayerData data; + private int step = 0; + + public PreviewTask(Player player) + { + super(0, IndicatorSettings.interval); + + this.player = player; + this.data = SkillAPI.getPlayerData(player); + } + + @Override + public void run() + { + // Expire when not in the hover view anymore + if (!data.getCastBars().isHovering()) + { + expired = true; + return; + } + + // Update and play the effect + try + { + List packets = data.getCastBars().getHoverPackets(player, step++); + if (packets != null) + Particle.send(player, packets); + } + catch (Exception ex) + { + ex.printStackTrace(); + expired = true; + } + } +} diff --git a/src/com/sucy/skill/task/RemoveTask.java b/src/com/sucy/skill/task/RemoveTask.java index 3ebdc593..37d8dbea 100644 --- a/src/com/sucy/skill/task/RemoveTask.java +++ b/src/com/sucy/skill/task/RemoveTask.java @@ -29,37 +29,21 @@ import com.sucy.skill.SkillAPI; import com.sucy.skill.api.skills.PassiveSkill; import com.sucy.skill.api.skills.Skill; +import com.sucy.skill.api.util.BuffManager; +import com.sucy.skill.api.util.FlagManager; import com.sucy.skill.dynamic.DynamicSkill; import com.sucy.skill.dynamic.mechanic.WolfMechanic; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Tameable; import org.bukkit.scheduler.BukkitRunnable; -import java.util.ArrayList; import java.util.List; /** * A simple task for removing an entity after a duration */ -public class RemoveTask extends BukkitRunnable -{ - private List entities; - - /** - * Initializes a new task to remove the entity after the - * given number of ticks. - * - * @param entity entity to remove - * @param ticks ticks to wait before removing the entity - */ - public RemoveTask(Entity entity, int ticks) - { - this.entities = new ArrayList(); - this.entities.add(entity); - SkillAPI.schedule(this, ticks); - } +public class RemoveTask extends BukkitRunnable { + private List entities; /** * Initializes a new task to remove the entity after the @@ -68,53 +52,36 @@ public RemoveTask(Entity entity, int ticks) * @param entities entities to remove * @param ticks ticks to wait before removing the entity */ - public RemoveTask(List entities, int ticks) - { + public RemoveTask(List entities, int ticks) { this.entities = entities; SkillAPI.schedule(this, ticks); } - /** - * Checks if the owner of the tameable entity is the given player - * - * @param player player to compare against - * - * @return true if owned by the player, false otherwise - */ - public boolean isOwnedBy(Player player) - { - return entities.size() == 1 && ((Tameable) entities.get(0)).getOwner() == player; - } - /** * Removes the entity once the time is up */ @Override @SuppressWarnings("unchecked") - public void run() - { + public void run() { // Clear skill setup - for (Entity entity : entities) - { - if (entity.hasMetadata(WolfMechanic.SKILL_META)) - { - List skills = (List) SkillAPI.getMeta(entity, WolfMechanic.SKILL_META); - int level = SkillAPI.getMetaInt(entity, WolfMechanic.LEVEL); - for (String skillName : skills) - { - Skill skill = SkillAPI.getSkill(skillName); - if (skill instanceof PassiveSkill) - { + for (Entity entity : entities) { + if (entity.hasMetadata(WolfMechanic.SKILL_META)) { + final List skills = (List) SkillAPI.getMeta(entity, WolfMechanic.SKILL_META); + final int level = SkillAPI.getMetaInt(entity, WolfMechanic.LEVEL); + for (final String skillName : skills) { + final Skill skill = SkillAPI.getSkill(skillName); + if (skill instanceof PassiveSkill) { ((PassiveSkill) skill).stopEffects((LivingEntity) entity, level); } } DynamicSkill.clearCastData((LivingEntity) entity); + FlagManager.clearFlags((LivingEntity) entity); + BuffManager.clearData((LivingEntity) entity); } // Remove entity - if (entity.isValid()) - { + if (entity.isValid()) { entity.remove(); } } diff --git a/src/com/sucy/skill/task/SaveTask.java b/src/com/sucy/skill/task/SaveTask.java index 512891e0..748e44bd 100644 --- a/src/com/sucy/skill/task/SaveTask.java +++ b/src/com/sucy/skill/task/SaveTask.java @@ -27,12 +27,12 @@ package com.sucy.skill.task; import com.sucy.skill.SkillAPI; -import org.bukkit.scheduler.BukkitRunnable; +import com.sucy.skill.thread.RepeatThreadTask; /** * Handles auto saving periodically */ -public class SaveTask extends BukkitRunnable +public class SaveTask extends RepeatThreadTask { /** * Sets up the save task. This shouldn't be used by other plugins @@ -42,7 +42,10 @@ public class SaveTask extends BukkitRunnable */ public SaveTask(SkillAPI api) { - runTaskTimer(api, SkillAPI.getSettings().getSaveFreq(), SkillAPI.getSettings().getSaveFreq()); + super( + SkillAPI.getSettings().getSaveFreq(), + SkillAPI.getSettings().getSaveFreq() + ); } /** diff --git a/src/com/sucy/skill/task/ScoreboardTask.java b/src/com/sucy/skill/task/ScoreboardTask.java index 7192709f..d35cf5c1 100644 --- a/src/com/sucy/skill/task/ScoreboardTask.java +++ b/src/com/sucy/skill/task/ScoreboardTask.java @@ -29,6 +29,7 @@ import com.rit.sucy.version.VersionPlayer; import com.sucy.skill.api.player.PlayerClass; import com.sucy.skill.api.player.PlayerData; +import com.sucy.skill.hook.CitizensHook; import com.sucy.skill.manager.ClassBoardManager; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; @@ -54,14 +55,15 @@ public ScoreboardTask(PlayerData data) public void run() { Player player = data.getPlayer(); - if (player != null && data.getMainClass() != null) + if (player == null || !player.isOnline() || player.isDead() || CitizensHook.isNPC(player)) + return; + + if (data.getMainClass() != null) { PlayerClass main = data.getMainClass(); ClassBoardManager.update(data, main.getData().getPrefix(), main.getData().getPrefixColor()); } - else if (player != null) - { + else ClassBoardManager.clear(new VersionPlayer(player)); - } } } diff --git a/src/com/sucy/skill/thread/IThreadTask.java b/src/com/sucy/skill/thread/IThreadTask.java new file mode 100644 index 00000000..9100d418 --- /dev/null +++ b/src/com/sucy/skill/thread/IThreadTask.java @@ -0,0 +1,45 @@ +/** + * SkillAPI + * com.sucy.skill.thread.IThreadTask + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.thread; + +/** + * Interface for tasks used by the main async task + */ +public interface IThreadTask extends Runnable +{ + /** + * Ticks the task, checking if it should run + * + * @return true if expired, false otherwise + */ + boolean tick(); + + /** + * Runs the functions of the task + */ + void run(); +} diff --git a/src/com/sucy/skill/thread/MainThread.java b/src/com/sucy/skill/thread/MainThread.java new file mode 100644 index 00000000..7353e3e0 --- /dev/null +++ b/src/com/sucy/skill/thread/MainThread.java @@ -0,0 +1,97 @@ +/** + * SkillAPI + * com.sucy.skill.thread.MainThread + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.thread; + +import java.util.ConcurrentModificationException; +import java.util.Objects; + +/** + * The main async task for SkillAPI functions + */ +public class MainThread extends Thread { + private static final TaskList tasks = new TaskList(); + + private long time; + + private boolean enabled; + + private boolean print = true; + + /** + * Sets up the main thread + */ + public MainThread() { + time = System.currentTimeMillis(); + enabled = true; + start(); + } + + /** + * Runs the thread until disabled or interrupted + */ + @Override + public void run() { + while (enabled) { + try { + tasks.iterator(); + while (tasks.hasNext()) { if (tasks.next().tick()) { tasks.remove(); } } + + long current = System.currentTimeMillis(); + time += 50; + sleep(Math.max(1, time - current)); + } catch (ConcurrentModificationException ex) { + // Concurrent exceptions would happen infrequently + // but shouldn't be a concern. We'll just continue + // functionality next tick. + } catch (Exception ex) { + if (print) { + ex.printStackTrace(); + print = false; + } + } + } + } + + /** + * Disables the main thread, stopping future runs + */ + public void disable() { + for (IThreadTask task : tasks) { task.run(); } + tasks.clear(); + enabled = false; + } + + /** + * Registers a new task to run + * + * @param task task to run + */ + public static void register(IThreadTask task) { + Objects.requireNonNull(task, "Cannot register a null task"); + tasks.add(task); + } +} diff --git a/src/com/sucy/skill/thread/RepeatThreadTask.java b/src/com/sucy/skill/thread/RepeatThreadTask.java new file mode 100644 index 00000000..8818c547 --- /dev/null +++ b/src/com/sucy/skill/thread/RepeatThreadTask.java @@ -0,0 +1,67 @@ +/** + * SkillAPI + * com.sucy.skill.thread.RepeatThreadTask + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.thread; + +/** + * A thread task that continually runs in the background + */ +public abstract class RepeatThreadTask implements IThreadTask +{ + private int interval; + private int time; + + protected boolean expired; + + /** + * Sets up the task with an initial delay and an interval + * + * @param delay delay before first run + * @param interval delay between subsequent runs + */ + public RepeatThreadTask(int delay, int interval) + { + this.interval = Math.max(interval, 1); + this.time = -delay; + expired = false; + } + + /** + * Ticks the task, running periodically depending on the interval + * + * @return true if expired + */ + @Override + public boolean tick() + { + if (++time % interval == 0 && time > 0) + { + run(); + time = 0; + } + return expired; + } +} diff --git a/src/com/sucy/skill/thread/TaskList.java b/src/com/sucy/skill/thread/TaskList.java new file mode 100644 index 00000000..5858a27a --- /dev/null +++ b/src/com/sucy/skill/thread/TaskList.java @@ -0,0 +1,111 @@ +/** + * SkillAPI + * com.sucy.skill.thread.TaskList + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.thread; + +import java.util.Iterator; + +public class TaskList implements Iterable, Iterator +{ + private Entry iteratee; + private Entry head; + private Entry tail; + private int size; + + public TaskList() + { + head = tail = new Entry(); + } + + public int size() + { + return size; + } + + public void add(IThreadTask task) + { + tail.next = new Entry(task); + tail.next.prev = tail; + tail = tail.next; + size++; + } + + public void clear() + { + head.next = null; + tail = head; + size = 0; + } + + @Override + public Iterator iterator() + { + iteratee = head; + return this; + } + + @Override + public boolean hasNext() + { + return iteratee.next != null; + } + + @Override + public IThreadTask next() + { + if (iteratee == null || iteratee.next == null) { + return null; + } + iteratee = iteratee.next; + return iteratee.task; + } + + @Override + public void remove() + { + if (iteratee != null && iteratee.prev != null) + { + if (iteratee == tail) + tail = iteratee.prev; + iteratee.prev.next = iteratee.next; + size--; + } + } + + private static class Entry + { + public Entry() { } + + public Entry(IThreadTask task) + { + this.task = task; + } + + private IThreadTask task; + private Entry prev; + private Entry next; + } +} diff --git a/src/com/sucy/skill/thread/ThreadTask.java b/src/com/sucy/skill/thread/ThreadTask.java new file mode 100644 index 00000000..f65876f6 --- /dev/null +++ b/src/com/sucy/skill/thread/ThreadTask.java @@ -0,0 +1,69 @@ +/** + * SkillAPI + * com.sucy.skill.thread.ThreadTask + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Steven Sucy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.sucy.skill.thread; + +/** + * A task that runs ones and is finished + */ +public abstract class ThreadTask implements IThreadTask +{ + private int time; + + /** + * Sets up the task to run on the next thread iteration + */ + public ThreadTask() + { + this(0); + } + + /** + * Sets up the task to run after a delay + * + * @param delay delay in ticks + */ + public ThreadTask(int delay) + { + time = delay; + } + + /** + * Ticks the task, running it if applicable + * + * @return true after the task runs, false beforehand + */ + @Override + public boolean tick() + { + if (--time <= 0) + { + run(); + return true; + } + return false; + } +} diff --git a/src/com/sucy/skill/tree/SkillTree.java b/src/com/sucy/skill/tree/SkillTree.java index 8ddd0bc4..86b9a7dd 100644 --- a/src/com/sucy/skill/tree/SkillTree.java +++ b/src/com/sucy/skill/tree/SkillTree.java @@ -30,7 +30,6 @@ import com.sucy.skill.api.classes.RPGClass; import com.sucy.skill.api.exception.SkillTreeException; import com.sucy.skill.api.skills.Skill; -import com.sucy.skill.data.Permissions; import com.sucy.skill.log.Logger; import org.bukkit.entity.Player; @@ -67,8 +66,8 @@ public SkillTree(SkillAPI api, RPGClass tree) */ public boolean canShow(Player player, Skill skill) { - if (skill.canAutoLevel() && !skill.canCast() && !SkillAPI.getSettings().isShowingAutoSkills()) return false; - return !skill.needsPermission() || player.hasPermission(Permissions.SKILL) || player.hasPermission(Permissions.SKILL + "." + skill.getName().toLowerCase().replaceAll(" ", "-")); + return !(skill.canAutoLevel() && !skill.canCast() && !SkillAPI.getSettings().isShowingAutoSkills()) + && skill.isAllowed(player); } /** diff --git a/src/com/sucy/skill/tree/basic/InventoryTree.java b/src/com/sucy/skill/tree/basic/InventoryTree.java index 6c420fbe..d94cf6ba 100644 --- a/src/com/sucy/skill/tree/basic/InventoryTree.java +++ b/src/com/sucy/skill/tree/basic/InventoryTree.java @@ -34,7 +34,7 @@ import com.sucy.skill.api.exception.SkillTreeException; import com.sucy.skill.api.player.PlayerData; import com.sucy.skill.api.skills.Skill; -import com.sucy.skill.data.Permissions; +import com.sucy.skill.gui.tool.GUITool; import com.sucy.skill.language.GUINodes; import com.sucy.skill.language.RPGFilter; import com.sucy.skill.tree.SkillTree; @@ -52,7 +52,7 @@ */ public abstract class InventoryTree extends SkillTree { - public static final String INVENTORY_KEY = "SAPI_ST"; + private static final String INVENTORY_KEY = "SAPI_ST"; protected final HashMap skillSlots = new HashMap(); @@ -88,6 +88,7 @@ public void show(Player player) */ public Inventory getInventory(PlayerData player) { + GUITool.getSkillTree(tree); Inventory inv = InventoryManager.createInventory( INVENTORY_KEY, height, @@ -105,7 +106,7 @@ public Inventory getInventory(PlayerData player) { if (canShow(p, entry.getValue())) { - inv.setItem(entry.getKey(), entry.getValue().getIndicator(player.getSkill(entry.getValue().getName()))); + inv.setItem(entry.getKey(), entry.getValue().getIndicator(player.getSkill(entry.getValue().getName()), false)); } } @@ -133,11 +134,9 @@ public boolean checkClick(int slot) */ public boolean isSkill(HumanEntity player, int slot) { - return skillSlots.get(slot) != null && - player != null && - (!skillSlots.get(slot).needsPermission() || - player.hasPermission(Permissions.SKILL) || - player.hasPermission(Permissions.SKILL + "." + skillSlots.get(slot).getName().toLowerCase().replace(" ", "-"))); + return skillSlots.get(slot) != null + && player != null + && skillSlots.get(slot).isAllowed((Player) player); } /** @@ -162,6 +161,11 @@ public HashMap getSkillSlots() return skillSlots; } + public int getHeight() + { + return height; + } + /** * Arranges the skill tree * @@ -189,7 +193,7 @@ public void update(PlayerData player) InventoryView view = player.getPlayer().getOpenInventory(); for (Map.Entry skills : skillSlots.entrySet()) { - view.setItem(skills.getKey(), skills.getValue().getIndicator(player.getSkill(skills.getValue().getName()))); + view.setItem(skills.getKey(), skills.getValue().getIndicator(player.getSkill(skills.getValue().getName()), false)); } } diff --git a/src/com/sucy/skill/tree/map/MapTree.java b/src/com/sucy/skill/tree/map/MapTree.java deleted file mode 100644 index 7b5a54bb..00000000 --- a/src/com/sucy/skill/tree/map/MapTree.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * SkillAPI - * com.sucy.skill.tree.map.MapTree - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Steven Sucy - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software") to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.sucy.skill.tree.map; - -import com.sucy.skill.SkillAPI; -import com.sucy.skill.api.classes.RPGClass; -import com.sucy.skill.api.skills.Skill; -import com.sucy.skill.tree.SkillTree; -import org.bukkit.ChatColor; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Represents a Skill Tree that uses a map for its GUI - */ -public class MapTree extends SkillTree -{ - public static final String IDENTIFIER = "" + ChatColor.DARK_GREEN + ChatColor.BLACK + ChatColor.RESET; - - private ArrayList skills; - - /** - * Constructor - * - * @param api api reference - */ - public MapTree(SkillAPI api, RPGClass tree) - { - super(api, tree); - } - - /** - * Retrieves the list of skills in the tree - * - * @return list of skills - */ - public ArrayList getSkills() - { - return skills; - } - - /** - * Arranges the skill tree - * - * @param skills skills to arrange - */ - @Override - public void arrange(List skills) - { - this.skills = new ArrayList(); - this.skills.addAll(skills); - Collections.sort(skills, MAP_COMPARATOR); - } - - /** - * Checks if the class has the skill registered - * - * @param skill skill to check - * - * @return true if registered, false otherwise - */ - public boolean hasSkill(Skill skill) - { - return skills.contains(skill); - } - - /** - * Comparator for skills for level trees - */ - private static final Comparator MAP_COMPARATOR = new Comparator() - { - - /** - * Compares skills based on their stats for skill tree arrangement - * -> Skills with lower level requirements come first - * -> Then its skills with lower costs - * -> Then its skills alphabetically - * - * @param skill1 skill being compared - * @param skill2 skill to compare to - * @return -1, 0, or 1 - */ - @Override - public int compare(Skill skill1, Skill skill2) - { - return skill1.getLevelReq(0) > skill2.getLevelReq(0) ? 1 - : skill1.getLevelReq(0) < skill2.getLevelReq(0) ? -1 - : skill1.getName().compareTo(skill2.getName()); - } - }; -} diff --git a/src/com/sucy/skill/util/Lists.java b/src/com/sucy/skill/util/Lists.java new file mode 100644 index 00000000..3e4c1cb6 --- /dev/null +++ b/src/com/sucy/skill/util/Lists.java @@ -0,0 +1,15 @@ +package com.sucy.skill.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * SkillAPI © 2018 + * com.sucy.skill.util.Lists + */ +public class Lists { + public static List asList(final T... items) { + return new ArrayList<>(Arrays.asList(items)); + } +} diff --git a/src/config.yml b/src/config.yml new file mode 100644 index 00000000..8f8b3942 --- /dev/null +++ b/src/config.yml @@ -0,0 +1,600 @@ +# ---------------------------------------------- # +# General Configuration # +# ---------------------------------------------- # +# If you are unsure of how to use this file, # +# but would like to change how the plugin shows # +# text, visit the BukkitDev page at: # +# http://dev.bukkit.org/bukkit-plugins/skillapi/ # +# and look at the default configuration section # +# ---------------------------------------------- # +Accounts: + # + # The main class group used for GUI displays + main-class-group: class + # + # Allows one "account" per class, providing the "/class switch" + # command to change between them. This is an alternative to + # using "/class acc " which professes as a class at the same + # time and makes it easier to correlate an account to a class. + # This will not work well if you have one common class + # that turns into the rest. + one-per-class: false + # + # The max number of accounts a normal user can use + max-accounts: 3 + # + # The max number of accounts users can have with certain permissions + # must be in the format ':' + perm-accounts: + - 'skillapi.account.admin:10' +# +# While targeting settings aren't necessary most of the time, +# they can help make running ally checks much faster if +# you are able to use them. +Targeting: + # + # Whether or not all monsters are enemies. + # If you are using pets that include zombies/spiders/etc, + # you shouldn't enable this. + # If you want to enable it for specific worlds, list out the + # worlds it should apply to in a list format such as: + # monsters-enemy: + # - world1 + # - world2 + monsters-enemy: false + # + # Whether or not all passive mobs are allies. + # If there are custom passive mobs that you can fight, + # you should not enable this. + # If you want to enable it for specific worlds, list out the + # worlds it should apply to in a list format such as: + # passive-ally: + # - world1 + # - world2 + passive-ally: false + # + # Whether or not all players are allies. + # If you have any PvP, this should not be enabled. + # If you want to enable it for specific worlds, list out the + # worlds it should apply to in a list format such as: + # player-ally: + # - world1 + # - world2 + player-ally: false + # + # Whether or not to check for player allies via Parties + parties-ally: false + # + # Whether or not for skills to affect NPCs + affect-npcs: false + # + # Whether or not for skills to affect armor stands + affect-armor-stands: false +# +Saving: + # + # Whether or not to auto save data periodically + auto-save: false + # + # How often to auto-save in minutes + minutes: 30 + # + # Whether or not to use an SQL database to save + sql-database: false + # + # Details for connecting to the database + sql-details: + host: localhost + port: 54321 + database: plugins + username: username + password: password + # Time to wait on loading data from the SQL database in ticks. + # Can be used to give time for other servers to synchronize data. Note: + # this does not apply when loading player data on server startup since + # players wouldn't be coming from another server. + delay: 0 +# +Classes: + # + # Whether or not to use SkillAPI's health system + modify-health: true + # + # The default health for players without a class + classless-hp: 20 + # + # Whether or not non-castable auto-leveled skills are shown + show-auto-skills: false + # + # Whether or not attributes are enabled + attributes-enabled: false + # + # Whether or not attributes can be refunded + attributes-downgrade: false + # + # This casts the dynamic skill for the player when they level up, + # allowing you to use dynamic mechanics for level up effects. If + # there is no existing dynamic skill with the name, no effect + # will be played. For level-specific effects, use the level condition. + level-up-skill: 'lvlup' +# +Mana: + # + # Whether or not to use SkillAPI's mana system + enabled: true + # + # How frequently mana is gained in seconds + freq: 1 +# +Skills: + # + # Whether or not players are allowed to downgrade skills + allow-downgrade: true + # + # Whether or not messages should be shown on casting a skill + show-messages: true + # + # The radius in which to show cast messages to nearby players + message-radius: 20 + # + # Whether or not to apply damage knockback when damage is blocked + # by defensive or offesnive buffs and debuffs + knockback-no-damage: false + # + # Blocks to ignore when using the Block mechanic in dynamic skills + # End values in an * to do all materials containing the value + block-filter: + - 'chest' + - 'ender chest' + - 'trapped chest' + - 'redstone*' + - 'acacia wall sign' + - 'acacia sign' + - 'birch wall sign' + - 'birch sign' + - 'dark oak wall sign' + - 'dark oak sign' + - 'oak wall sign' + - 'oak sign' + - 'jungle wall sign' + - 'jungle sign' +# +Items: + # + # Whether or not to check for class, level, and attribute + # requirements in an item's lore + lore-requirements: false + # + # Whether or not to check for skill requirements + # in an item's lore + skill-requirements: false + # + # Whether or not to check for stat bonuses in an item's lore + lore-attributes: false + # + # Whether or not to drop weapons when unable to use them + drop-weapon: false + # + # The text used for class requirements in the lore + lore-class-text: 'Class Req: ' + # + # The text used for skill requirements in lore + lore-skill-text: 'Requires {skill}: ' + # + # the text used for level requirements in the lore + lore-level-text: 'Level Req: ' + # + # The text used for excluded classes in the lore + lore-exclude-text: 'Excluded Class: ' + # + # The text used for attribute requirements + lore-attribute-text: '{attr} Req: ' + # + # The text used for providing attributes + attribute-text: '{attr}: ' + # + # The slots to check for items in and apply requirements to. + # This does not include held item, as that fluctuates and is assumed + # Slots are based on the following: + # 0-8 = hot bar + # 9-35 = main inventory + # 36 = boots + # 37 = leggings + # 38 = chestplate + # 39 = helmet + # 40 = off hand + slots: + - 36 + - 37 + - 38 + - 39 + - 40 +# +GUI: + # + # Whether or not to lock the health bar to 10 hearts + # This does not affect total health, only the display + old-health-bar: false + # + # Whether or not to force SkillAPI to override + # health scaling of other plugins + force-scaling: false + # + # This is what to display using the level bar, if anything. + # Options are: none, mana, level + level-bar: none + # + # This is what to display using the food bar, if anything. + # Options are: none, mana, exp + food-bar: none + # + # Whether or not to use the action bar + use-action-bar: false + # + # The text to display on the action bar. + # Available filters: + # {combo} - the player's current click combo + # {class} - the player's main class name + # {level} - the player's main class level + # {exp} - the player's main class exp + # {expReq} - the player's main class required experience + # {expLeft} - the player's main class experience until the next level + # {health} - the player's current health + # {maxHealth} - the player's max health + # {mana} - the player's current mana + # {maxMana} - the player's max mana + # {name} - the name of the player + # {attr} - the player's attribute points + # {sp} - the player's main class skill points + # {value:} - a skill value + action-bar-text: '{combo}' + # + # Whether or not to use title messages + title-enabled: false + # + # Duration for title messages in seconds + title-duration: 3 + # + # Fade in time for title messages in seconds + title-fade-in: 0.5 + # + # Fade out time for title messages in seconds + title-fade-out: 0.5 + # + # Messages to display using the Title bar + # Second lines of messages will be displayed in the subtitle + # Ones you can use include: + # level_up + # exp_gained + # exp_lost + # status + title-messages: + - level_up + - exp_lost + # + # Whether or not to display class information on a scoreboard + scoreboard-enabled: true + # + # Whether or not to add a prefix to players with their class name + show-class-name: true + # + # Whether or not to display a player's level below their name + show-class-level: true + # + # The text to show with the player's level when enabled. + # This always appears as {level} {text} + class-level-text: 'Level' + # + # Whether or not to append text to skill icons to show what type of item the skill is bound to + show-binds: false + # + # Text to show for bound materials + show-bind-text: 'Bound to {material}' +# +Casting: + # + # Whether or not the main casting option is enabled + enabled: false + # + # Whether or not to use the mult-bar implementation. + # When enabled: + # - Left/Right clicking on item opens skill bars + # - Skills assigned through tree + # - Preview when hovering in the skill bar + # - Limited number of skills can be put on skill bars + # When disabled: + # - Left/Right clicking on item cycles through skills + # - Preview when hovering the item + # - No limit on skills (though makes cycling hard to find skills) + bars: true + # + # Whether or not to use the combat bar implementation. Details: + # - Specified slot becomes a toggle item + # - Can optionally move over or require interacting (left, right, or drop) to swap modes + # - Swaps between combat mode and passive mode + # - In combat mode, works like the classic skill bar + # - In passive mode, no skills are shown + # - Passive and combat modes each have their own stored contents + # - combat mode uses the skill bar settings + combat: false + # + # Global cooldown between skill casts in seconds + cooldown: 0 + # + # Settings for skill target indicators that play effects + # to show where a skill will hit + cast-indicator: + # + # Whether or not the feature is enabled + enabled: true + # + # How tightly to pack particles in the effect. A higher + # density will play more particles. It represents the + # amount of particles played per block units + density: 1 + # + # How often the particles are played for the effect + # in plays per second + frequency: 10 + # + # How fast position animations happen in blocks per second + animation: 1 + # + # Particle to use when it has a target + particle: + particle: 'crit' + dx: 0 + dy: 0 + dz: 0 + speed: 0 + amount: 1 + # + # The slot the item is kept in, must be in the range 1-9 + slot: 9 + # + # The item to use in the cast slot. + # When not using bars, this only shows up when no skills + # are available for use. + item: + type: BOOK + data: 0 + durability: 0 + name: '&dSkills' + lore: + - '' + - '&6Left Click&2 - First skill set' + - '&6Right Click&2 - Second skill set' + - '&6Q&2 - Organize skills' + # + # The item used in the bar GUI to describe the hover bar + hover-item: + type: BOOKSHELF + data: 0 + durability: 0 + name: '&6Hover Bar' + lore: + - '' + - 'Skills in this row will' + - 'be usable via left clicking' + - 'the cast item and will let' + - 'you see where they will hit' + - 'before casting them.' + # + # The item used in the bar GUI to describe the instant bar + instant-item: + type: BOOKSHELF + data: 0 + durability: 0 + name: '&6Instant Bar' + data: 0 + durability: 0 + name: '&6Instant Bar' + lore: + - '' + - 'Skills in this row will' + - 'be usable via right clicking' + - 'the cast item and will be' + - 'cast immediately when switching' + - 'to their slot.' +# +Click Combos: + # + # Whether or not to use click combinations + enabled: false + # + # Whether or not players can customize their combos + allow-custom: false + # + # Whether or not to automatically assign combos to skills + # without a combo manually defined. When disabled, only skills + # configured to have a combo or have had a combo set by + # a command will have combos. + auto-assign: true + # + # Whether or not left clicks are allowed at all + use-click-left: true + # + # Whether or not right clicks are allowed at all + use-click-right: true + # + # Whether or not shift clicks are allowed at all + use-click-shift: false + # + # Whether or not right shift clicks are allowed at all + # This will disable "use-click-shift" if enabled + use-click-right-shift: false + # + # Whether or not left shift clicks are allowed at all + # This will disable "use-click-shift" if enabled + use-click-left-shift: false + # + # Whether or not jump clicks are allowed at all + use-click-space: false + # + # Whether or not Q clicks are allowed at all. + # Enabling this disables dropping items via Q outside of menus. + use-click-q: false + # + # How many clicks are needed to perform a combo + combo-size: 4 + # + # Inactivity time in seconds before clicks for a combo are reset + click-time: 1.0 +# +Skill Bar: + # + # Whether or not to use skill bars + enabled: false + # + # Whether or not to show skill cooldowns in the skill bar + show-cooldown: true + # + # The item to use as a placeholder in the skill bar + empty-icon: + material: PUMPKIN_SEEDS + data: 0 + text: '&7Unassigned' + # + # The default layout for skill bars + # Players can customize unlocked slots + layout: + 1: + skill: true + locked: false + 2: + skill: true + locked: false + 3: + skill: true + locked: false + 4: + skill: true + locked: false + 5: + skill: true + locked: false + 6: + skill: false + locked: false + 7: + skill: false + locked: false + 8: + skill: false + locked: false + 9: + skill: false + locked: false +# +Experience: + # + # Whether or not to use vanilla exp drops for class exp + # Note: any dropped exp will not count towards class exp, + # only enabled sources will count. By default, this is only + # mob deaths. + use-exp-orbs: true + # + # Whether or not to prevent gaining experience from mobs + # spawned via a mob spawner block + block-mob-spawner: true + # + # Whether or not to prevent gaining experience from mobs + # spawned via a mob spawn egg + block-mob-egg: true + # + # Whether or not to prevent gaining experience while + # in creative mode + block-creative: true + # + # Whether or not to display a message when gaining experience + exp-message-enabled: true + # + # Whether or not to display a message when gaining a level + level-message-enabled: true + # + # Whether or not to show a message when losing exp de to dying + lose-exp-message: true + # + # Worlds where experience is not lost on death + lose-exp-blacklist: + - 'pvpWorld' + # + # The formula used for calculating required experience + # The formula is: x*lvl*lvl + y*lvl + z + formula: + x: 1 + y: 8 + z: 16 + # + # Whether or not to use a custom equation + use-custom: false + # + # The custom formula to use with 'lvl' being the current player level + # Note: this formula does not use x, y, or z. Use numbers directly + # in the formula instead. + custom-formula: '25(1.1^(lvl-1))' + # + # The experience yields from each mob type + # When exp orbs are enabled, these values are ignored + yields: + blaze: '10' + cavespider: '5' + creeper: '3' + elderguardian: '10' + enderdragon: '400' + enderman: '5' + endermite: '3' + evoker: '10' + ghast: '5' + giant: '20' + guardian: '10' + husk: '2' + irongolem: '10' + magmacube: '1' + pigzombie: '5' + player: '5' + shulker: '5' + silverfish: '2' + skeleton: '3' + slime: '1' + spider: '3' + stray: '5' + vex: '3' + vindicator: '5' + witch: '3' + wither: '500' + witherskeleton: '5' + zombie: '2' +# +# How much logging to do when loading SkillAPI +# When testing setting up skills/classes, increase this to 1-5 +Logging: + # + # Logs for loading attributes + attribute-load: 0 + # + # Logs for attack/defense buffs applying + buff: 0 + # + # Logs for registration of skills and classes + registration: 0 + # + # Logs for GUI updates + gui: 0 + # + # Logs for mana updates + mana: 0 +# +Worlds: + # + # Whether or not to enable the world restriction + enable: false + # + # Whether or not to use the list as an enabling list + # If true, only worlds in the list will let players use SkillAPI + # If false, any world not in the list will let players use SkillAPI + use-as-enabling: true + # + # The worlds to include in the enable/disable list + worlds: + - world \ No newline at end of file diff --git a/src/effects.yml b/src/effects.yml new file mode 100644 index 00000000..99384157 --- /dev/null +++ b/src/effects.yml @@ -0,0 +1,128 @@ +single: + formula: 0 + steps: 1 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +linear: + formula: 1 + steps: 20 + copies: 1 + domain: 0 + x: 0 + y: 0 + z: 0 +linear-quick: + formula: 1 + steps: 10 + copies: 1 + domain: 0 + x: 0 + y: 0 + z: 0 +still: + formula: 0 + steps: 20 + copies: 1 + domain: 0 + x: 0 + y: 0 + z: 0 +one-point: + formula: 1 + steps: 1 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +two-point: + formula: 1 + steps: 2 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +three-point: + formula: 1 + steps: 3 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +one-circle: + formula: 1 + steps: 20 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +two-circle: + formula: 1 + steps: 20 + copies: 2 + domain: 1 + x: 0 + y: 0 + z: 0 +three-circle: + formula: 1 + steps: 20 + copies: 3 + domain: 1 + x: 0 + y: 0 + z: 0 +one-spiral: + formula: t + steps: 20 + copies: 1 + domain: 1 + x: 0 + y: 0 + z: 0 +two-spiral: + formula: t*0.5 + steps: 40 + copies: 1 + domain: 2 + x: 0 + y: 0 + z: 0 +three-spiral: + formula: t*0.333 + steps: 60 + copies: 1 + domain: 3 + x: 0 + y: 0 + z: 0 +square: + formula: 1/c + steps: 5 + copies: 8 + domain: 0.125 + x: 0 + y: 0 + z: 0 +pentagon: + formula: 1/c + steps: 5 + copies: 10 + domain: 0.1 + x: 0 + y: 0 + z: 0 +hexagon: + formula: 1/c + steps: 4 + copies: 12 + domain: 0.08333 + x: 0 + y: 0 + z: 0 \ No newline at end of file diff --git a/src/exp.yml b/src/exp.yml new file mode 100644 index 00000000..ba395697 --- /dev/null +++ b/src/exp.yml @@ -0,0 +1,76 @@ +# Whether or not to enable controlling experience from broken/placed blocks or crafted items +enabled: false + +# Experience yields for anything except combat. Combat experience is either controlled +# via vanilla experience dropped or the yields defined in config.yml. +# +# These are experience values provided when a player breaks a block. +break: + # + # Whether or not to allow players to place a block back down and break it again + # to get additional experience. Note that disabling this will cause all block + # changes to be tracked in order to remember what was placed or not. Blocks + # existing before this is disabled will not be protected from experience yields. + allow-replace: true + # + # The yields per block type. You can add any block types not in this list by simply + # appending the bukkit name of the block type. See + # https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + # for valid types. This list is not case-sensitive. + types: + # + # You can add classes as well that allow certain blocks only to benefit + # that class. For example, if you want a miner to benefit from ore but + # a farmer to benefit from crops, you can add a "miner" section containing + # the ore and a "farmer" section containing the crops. This would look something + # like: + # + # # Blocks only for miners + # miner: + # COAL: 1 + # IRON_ORE: 10 + # # Blocks only for farmers + # farmer: + # CROPS: 5 + default: + COAL: 1 + QUARTZ_ORE: 1 + IRON_ORE: 10 + GOLD_ORE: 15 + REDSTONE_ORE: 20 + LAPIS_ORE: 25 + DIAMOND_ORE: 50 + EMERALD_ORE: 100 +# +# Experience yields when a player places a block. There's not a built-in way to check +# for players repeatedly placing down the same block, so use this type of experience +# with caution. Works similar to break, just without the replace option. +place: + default: + DIAMOND_BLOCK: 1 +# +# Experience yields when a player crafts items. +craft: + # + # Similar to above, you can specify classes to get + # experience from different crafts. + default: + BOW: 5 + IRON_CHESTPLATE: 80 + IRON_LEGGINGS: 70 + IRON_HELMET: 50 + IRON_BOOTS: 40 + IRON_SWORD: 20 + IRON_AXE: 30 + GOLD_CHESTPLATE: 120 + GOLD_LEGGINGS: 105 + GOLD_HELMET: 75 + GOLD_BOOTS: 60 + GOLD_SWORD: 30 + GOLD_AXE: 45 + DIAMOND_CHESTPLATE: 400 + DIAMOND_LEGGINGS: 350 + DIAMOND_HELMET: 250 + DIAMOND_BOOTS: 200 + DIAMOND_SWORD: 100 + DIAMOND_AXE: 150 \ No newline at end of file diff --git a/src/internal/mobSizes.yml b/src/internal/mobSizes.yml new file mode 100644 index 00000000..0d3c3c08 --- /dev/null +++ b/src/internal/mobSizes.yml @@ -0,0 +1,249 @@ +Baby Turtle: + size: 0.32 + height: 0.12 +Baby Rabbit: + size: 0.2 + height: 0.25 +Cod: + size: 0.5 + height: 0.3 +Baby Chicken: + size: 0.2 + height: 0.35 +Baby Ocelot/Cat: + size: 0.3 + height: 0.35 +Tropical Fish: + size: 0.5 + height: 0.4 +Salmon: + size: 0.7 + height: 0.4 +Turtle: + size: 1.1 + height: 0.4 +Baby Wolf: + size: 0.3 + height: 0.425 +Baby Pig: + size: 0.45 + height: 0.45 +Rabbit: + size: 0.4 + height: 0.5 +Pufferfish: + size: 0.7 + height: 0.7 +Dolphin: + size: 0.9 + height: 0.6 +Baby Panda: + size: 0.65 + height: 0.625 +Baby Sheep: + size: 0.45 + height: 0.675 +Chicken: + size: 0.4 + height: 0.7 +Baby Cow: + size: 0.45 + height: 0.7 +Baby Mooshroom: + size: 0.45 + height: 0.7 +Baby Polar Bear: + size: 0.65 + height: 0.7 +Ocelot: + size: 0.6 + height: 0.7 +Cat: + size: 0.6 + height: 0.7 +Baby Horses: + size: 0.6982 + height: 0.8 +Wolf: + size: 0.6 + height: 0.85 +Dog: + size: 0.6 + height: 0.85 +Pig: + size: 0.9 + height: 0.9 +Baby Llama: + size: 0.45 + height: 0.9375 +Panda: + size: 1.3 + height: 1.25 +Sheep: + size: 0.9 + height: 1.3 +Cow: + size: 0.9 + height: 1.4 +Mooshroom: + size: 0.9 + height: 1.4 +Polar Bear: + size: 1.3 + height: 1.4 +Horse: + size: 1.3964 + height: 1.6 +Llama: + size: 0.9 + height: 1.875 +Ravager: + size: 1.95 + height: 2.2 +Endermite: + size: 0.4 + height: 0.3 +Silverfish: + size: 0.4 + height: 0.3 +Cave Spider: + size: 0.7 + height: 0.5 +Phantom: + size: 0.8 + height: 0.5 +Small Slime: + size: 0.51 + height: 0.51 +Magma Cube: + size: 0.51 + height: 0.51 +Vex: + size: 0.4 + height: 0.8 +Guardian: + size: 0.85 + height: 0.85 +Spider: + size: 1.4 + height: 0.9 +Baby Zombie: + size: 0.3 + height: 0.975 +Baby Husk: + size: 0.3 + height: 0.975 +Baby Drowned: + size: 0.3 + height: 0.975 +Shulker: + size: 1 + height: 1 +Medium Slime: + size: 1.02 + height: 1.02 +Medium Magma Cube: + size: 1.02 + height: 1.02 +Creeper: + size: 0.6 + height: 1.7 +Blaze: + size: 0.6 + height: 1.8 +Zombie: + size: 0.6 + height: 1.95 +Evoker: + size: 0.6 + height: 1.95 +Villager: + size: 0.6 + height: 1.95 +Husk: + size: 0.6 + height: 1.95 +Witch: + size: 0.6 + height: 1.95 +Vindicator: + size: 0.6 + height: 1.95 +Illusioner: + size: 0.6 + height: 1.95 +Drowned: + size: 0.6 + height: 1.95 +Pigman: + size: 0.6 + height: 1.95 +Pillager: + size: 0.6 + height: 1.95 +Skeleton/Stray: + size: 0.6 + height: 1.99 +Elder Guardian: + size: 2 + height: 2 +Large Slime: + size: 2.04 + height: 2.04 +Large Magma Cube: + size: 2.04 + height: 2.04 +Wither Skeleton: + size: 0.7 + height: 2.4 +Enderman: + size: 0.6 + height: 2.9 +Wither: + size: 0.9 + height: 3.5 +Ghast: + size: 4 + height: 4 +Giant: + size: 3.6 + height: 11.7 +Ender Dragon: + size: 16 + height: 8 +Squid: + size: 0.8 + height: 0.8 +Bat/Parrot: + size: 0.5 + height: 0.9 +Snow Golem: + size: 0.7 + height: 1.9 +Iron Golem: + size: 1.4 + height: 2.7 +Minecart: + size: 0.98 + height: 0.7 +Boat: + size: 1.375 + height: 0.5625 +Armor Stand: + size: 0.5 + height: 1.975 +Falling Block: + size: 0.98 + height: 0.98 +Xp Orb: + size: 0.5 + height: 0.5 +Item: + size: 0.25 + height: 0.25 +Ender Crystal: + size: 2 + height: 2 +Player: + size: 0.6 + height: 1.8 \ No newline at end of file diff --git a/src/language.yml b/src/language.yml new file mode 100644 index 00000000..672001b7 --- /dev/null +++ b/src/language.yml @@ -0,0 +1,70 @@ +# ---------------------------------------------- # +# Language Configuration # +# ---------------------------------------------- # +# If you are unsure of how to use this file, # +# but would like to change how the plugin shows # +# text, visit the BukkitDev page at: # +# http://dev.bukkit.org/bukkit-plugins/skillapi/ # +# and look at the tutorials section for the # +# configuration tutorials. # +# ---------------------------------------------- # +Notifications: + cast: '&6{player} &2has cast &6{skill}' + gain-exp: '&2You have gained &6{exp} experience' + gain-lvl: '&6Level Up! &2You are now a &6level {level} {class}' + lose-exp: '&4You have lost &6{exp} {class} &4experience' +Errors: + on-cooldown: '&6{skill} &4cooldown - &6{cooldown} &4seconds left' + no-mana: '&4You need &6{missing} &4more mana' + no-skills: '&4That class does not have any skills' + cannot-use: '&4You cannot equip that item' +GUI: + attribute-title: 'Attributes ({points} points)' + profess-title: 'Profess' + skill-tree: '{class}' + skill-class-list: '{player}' +Skill Tree: + title: '{name} ({level}/{max})' + type: '&2Skill Type: &6{name}' + requirement: + met: '&2' + not-met: '&4' + attribute: + changing: '&6{value} -> {new}' + not-changing: '&6{value}' + layout: + - '&d{name} &7({level}/{max})' + - '&2Type: &6{type}' + - '' + - '{req:lvl}Level: {attr:level}' + - '{req:cost}Cost: {attr:cost}' + - '' + - '&2Mana: {attr:mana}' + - '&2Cooldown: {attr:cooldown}' + - '&2Range: {attr:range}' + - '' + - '&2Details: &7{desc:1}' + - '&7{desc:2-x}' +Stats: + health: '&2Health' + points: '&2SP' + level: '&2Level' + exp: '&2Exp' + attrib: '&2AP' +Status: + silenced: '&4You are silenced for another &6{duration} &4seconds' + stunned: '&4You are stunned for another &6{duration} &4seconds' + rooted: '&4You are rooted for another &6{duration} &4seconds' + disarmed: '&4You are disarmed for another &6{duration} &4seconds' + invincible: '&4That target is invincible for another &6{duration} &4seconds' + absorbed: '&4That target is absorbing attacks to gain health for another &6{duration} &4seconds' + channeling: '&4You are still channeling for another &6{duration} &4seconds' +Combo: + left: '&6Left' + right: '&6Right' + shift: '&6Shift' + left_shift: '&6Shift L' + right_shift: '&6Shift R' + space: '&6Jump' + q: '&6Drop' + diff --git a/src/plugin.yml b/src/plugin.yml new file mode 100644 index 00000000..83d80a11 --- /dev/null +++ b/src/plugin.yml @@ -0,0 +1,65 @@ +name: SkillAPI +main: com.sucy.skill.SkillAPI +version: 2.1 +author: [Eniripsa96,iomatix] +depend: [MCCore] +softdepend: [Vault] +loadbefore: [Quests] +api-version: 1.14 + +permissions: + skillapi.basic: + description: access to skill trees and using skills + default: true + skillapi.reset: + description: access to resetting your class + default: true + skillapi.exp: + description: access to gaining exp + default: true + skillapi.level: + description: access to using the level and exp commands + default: op + skillapi.mana: + description: access to using the mana command + default: op + skillapi.points: + description: access to using the points command + default: op + skillapi.reload: + description: access to using the reload command + default: op + skillapi.class: + description: access to all classes + default: op + skillapi.skill: + description: access to all skills + default: op + skillapi.lore: + description: access to all lore commands + default: op + skillapi.force: + description: access to force commands + default: false + skillapi.attrib: + description: access to giving attribute points + default: op + skillapi.gui: + description: access to GUI editor menu + default: op + + skillapi.*: + description: access to all commands and features + default: op + children: + skillapi.basic: true + skillapi.reset: true + skillapi.exp: true + skillapi.level: true + skillapi.mana: true + skillapi.points: true + skillapi.reload: true + skillapi.class: true + skillapi.skill: true + skillapi.lore: true + skillapi.gui: true diff --git a/src/tool.yml b/src/tool.yml new file mode 100644 index 00000000..646281f4 --- /dev/null +++ b/src/tool.yml @@ -0,0 +1,17 @@ +# Define extra items usable in the GUI editor here. +# NEXT_PAGE nad PREV_PAGE are provided as the items +# used for pages. Do not remove them. +NEXT_PAGE: + type: 'Book' + data: 0 + durability: 0 + name: '&6Next Page' + lore: + - '' +PREV_PAGE: + type: 'Book' + data: 0 + durability: 0 + name: '&6Prev Page' + lore: + - '' \ No newline at end of file diff --git a/src/worldGuard.yml b/src/worldGuard.yml new file mode 100644 index 00000000..77bf9ebe --- /dev/null +++ b/src/worldGuard.yml @@ -0,0 +1,10 @@ +# +# WorldGuard region IDs that players cannot cast skills in +disable-skills: +- fakeZoneId1 +- fakeZoneId2 +# +# WorldGuard region IDs that players cannot earn experience in +disable-exp: +- fakeZoneId1 +- fakeZoneId2 \ No newline at end of file diff --git a/target/SkillAPI.jar b/target/SkillAPI.jar new file mode 100644 index 00000000..1b906493 Binary files /dev/null and b/target/SkillAPI.jar differ