Merge branch 'custom_item_types'

This commit is contained in:
Jules 2024-02-12 01:05:10 +01:00
commit 1fabd75d10
74 changed files with 1000 additions and 1859 deletions

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>MMOItems</artifactId>
<groupId>net.Indyuce</groupId>
<version>6.9.5-SNAPSHOT</version>
<version>6.10-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>

View File

@ -51,10 +51,14 @@ public class ItemStats {
DISABLE_ENCHANTING = new DisableStat("ENCHANTING", VersionMaterial.ENCHANTING_TABLE.toMaterial(), "Disable Enchanting", new String[]{"!block", "all"}, "Players can't enchant this item."),
DISABLE_REPAIRING = new DisableStat("REPAIRING", Material.ANVIL, "Disable Repairing", new String[]{"!block", "all"}, "Players can't use this item in anvils."),
DISABLE_ARROW_SHOOTING = new DisableStat("ARROW_SHOOTING", Material.ARROW, "Disable Arrow Shooting", new Material[]{Material.ARROW}, "Players can't shoot this", "item using a bow."),
DISABLE_ATTACK_PASSIVE = new DisableStat("ATTACK_PASSIVE", Material.BARRIER, "Disable Attack Passive", new String[]{"piercing", "slashing", "blunt"}, "Disables the blunt/slashing/piercing", "passive effects on attacks."),
DISABLE_DROP = new DisableStat("DROPING", Material.LAVA_BUCKET, "Disable Item Dropping", new String[]{"all"}, "Disables the dropping of this item!"),
DISABLE_ARROW_CONSUMPTION = new DisableStat("ARROW_CONSUMPTION", Material.ARROW, "Disable Arrow Consumption", new String[]{"crossbow"}, "Disable arrow requirement and consumption."),
// Special item interactions/skills
LEFT_CLICK_SCRIPT = new ActionLeftClick(),
RIGHT_CLICK_SCRIPT = new ActionRightClick(),
DISABLE_ATTACK_PASSIVE = new DisableStat("ATTACK_PASSIVE", Material.BARRIER, "Disable On-Hit Effect", new String[]{"weapon"}, "Disables on-hit effects granted by the item type."),
// RPG Stats
REQUIRED_LEVEL = new RequiredLevel(),
REQUIRED_CLASS = new RequiredClass(),
@ -74,11 +78,11 @@ public class ItemStats {
COOLDOWN_REDUCTION = new DoubleStat("COOLDOWN_REDUCTION", Material.BOOK, "Cooldown Reduction", new String[]{"Reduces cooldowns of item and player skills (%)."}),
RANGE = new DoubleStat("RANGE", Material.STICK, "Range", new String[]{"The range of your item attacks."}, new String[]{"staff", "whip", "wand", "musket", "gem_stone"}),
MANA_COST = new ManaCost(),
STAMINA_COST = new DoubleStat("STAMINA_COST", VersionMaterial.LIGHT_GRAY_DYE.toMaterial(), "Stamina Cost", new String[]{"Stamina spent by your weapon to be used."}, new String[]{"piercing", "slashing", "blunt", "range"}),
STAMINA_COST = new DoubleStat("STAMINA_COST", VersionMaterial.LIGHT_GRAY_DYE.toMaterial(), "Stamina Cost", new String[]{"Stamina spent by your weapon to be used."}, new String[]{"weapon"}),
ARROW_VELOCITY = new DoubleStat("ARROW_VELOCITY", Material.ARROW, "Arrow Velocity", new String[]{"Determines how far your", "weapon can shoot.", "Default: 1.0"}, new String[]{"gem_stone", "bow", "crossbow"}),
ARROW_POTION_EFFECTS = new ArrowPotionEffects(),
PVE_DAMAGE = new DoubleStat("PVE_DAMAGE", VersionMaterial.PORKCHOP.toMaterial(), "PvE Damage", new String[]{"Additional damage against", "non human entities in %."}, new String[]{"piercing", "slashing", "blunt", "catalyst", "range", "tool", "armor", "gem_stone", "accessory"}),
PVP_DAMAGE = new DoubleStat("PVP_DAMAGE", VersionMaterial.SKELETON_SKULL.toMaterial(), "PvP Damage", new String[]{"Additional damage", "against players in %."}, new String[]{"piercing", "slashing", "blunt", "catalyst", "range", "tool", "armor", "gem_stone", "accessory"}),
PVE_DAMAGE = new DoubleStat("PVE_DAMAGE", VersionMaterial.PORKCHOP.toMaterial(), "PvE Damage", new String[]{"Additional damage against", "non human entities in %."}, new String[]{"weapon", "catalyst", "tool", "armor", "gem_stone", "accessory"}),
PVP_DAMAGE = new DoubleStat("PVP_DAMAGE", VersionMaterial.SKELETON_SKULL.toMaterial(), "PvP Damage", new String[]{"Additional damage", "against players in %."}, new String[]{"weapon", "catalyst", "tool", "armor", "gem_stone", "accessory"}),
BLUNT_POWER = new DoubleStat("BLUNT_POWER", Material.IRON_AXE, "Blunt Power", new String[]{"The radius of the AoE attack.", "If set to 2.0, enemies within 2 blocks", "around your target will take damage."}, new String[]{"blunt", "gem_stone"}),
BLUNT_RATING = new DoubleStat("BLUNT_RATING", Material.BRICK, "Blunt Rating", new String[]{"The force of the blunt attack.", "If set to 50%, enemies hit by the attack", "will take 50% of the initial damage."}, new String[]{"blunt", "gem_stone"}),
WEAPON_DAMAGE = new DoubleStat("WEAPON_DAMAGE", Material.IRON_SWORD, "Weapon Damage", new String[]{"Additional on-hit weapon damage in %."}),
@ -110,7 +114,7 @@ public class ItemStats {
MAX_MANA = new DoubleStat("MAX_MANA", VersionMaterial.LAPIS_LAZULI.toMaterial(), "Max Mana", new String[]{"Adds mana to your max mana bar."}),
KNOCKBACK_RESISTANCE = new KnockbackResistance(),
MOVEMENT_SPEED = new MovementSpeed(),
TWO_HANDED = new BooleanStat("TWO_HANDED", Material.IRON_INGOT, "Two Handed", new String[]{"If set to true, a player will be", "significantly slower if holding two", "items, one being Two Handed."}, new String[]{"piercing", "slashing", "blunt", "catalyst", "range", "tool"}),
TWO_HANDED = new BooleanStat("TWO_HANDED", Material.IRON_INGOT, "Two Handed", new String[]{"If set to true, a player will be", "significantly slower if holding two", "items, one being Two Handed."}, new String[]{"weapon", "catalyst", "tool"}),
REQUIRED_BIOMES = new RequiredBiomes(),
DROP_ON_DEATH = new DisableDeathDrop(),
DURABILITY_BAR = new DurabilityBar(),
@ -154,8 +158,8 @@ public class ItemStats {
CUSTOM_SOUNDS = new CustomSounds(),
ELEMENTS = new Elements(),
COMMANDS = new Commands(),
STAFF_SPIRIT = new StaffSpiritStat(),
LUTE_ATTACK_SOUND = new LuteAttackSoundStat(),
// STAFF_SPIRIT = new StaffSpiritStat(),
LUTE_ATTACK_SOUND = new LuteAttackSoundStat(),
LUTE_ATTACK_EFFECT = new LuteAttackEffectStat(),
NOTE_WEIGHT = new DoubleStat("NOTE_WEIGHT", VersionMaterial.MUSIC_DISC_MALL.toMaterial(), "Note Weight", new String[]{"Defines how the projectile cast", "by your lute tilts downwards."}, new String[]{"lute"}),
REMOVE_ON_CRAFT = new BooleanStat("REMOVE_ON_CRAFT", Material.GLASS_BOTTLE, "Remove on Craft", new String[]{"If the item should be completely", "removed when used in a recipe,", "or if it should become an", "empty bottle or bucket."}, new String[]{"all"}, Material.POTION, Material.SPLASH_POTION, Material.LINGERING_POTION, Material.MILK_BUCKET, Material.LAVA_BUCKET, Material.WATER_BUCKET),
@ -178,8 +182,8 @@ public class ItemStats {
// Abilities & Upgrading
ABILITIES = new Abilities(),
UPGRADE = new UpgradeStat(),
DOWNGRADE_ON_BREAK = new BooleanStat("BREAK_DOWNGRADE", Material.DAMAGED_ANVIL, "Downgrade when Broken", new String[]{"If this item's durability reaches 0,", "it will be fully repaired but also", "downgraded by one level.", "", "&cIt will only break if it cannot be", "&cdowngraded further", "", "Requires to define an &6Upgrade Template", "Required to define &6Custom Durability"}, new String[]{"piercing", "slashing", "blunt", "catalyst", "range", "tool", "armor", "consumable", "accessory"}),
DOWNGRADE_ON_DEATH = new BooleanStat("DEATH_DOWNGRADE", Material.DAMAGED_ANVIL, "Downgrade on Death", new String[]{"If the wearer of this item dies, it may", "downgrade (based on &6Death Downgrade", "&6Chance &7stat)", "", "Required to define an &6Upgrade Template", "Requires keep-inventory gamerule. "}, new String[]{"piercing", "slashing", "blunt", "catalyst", "range", "tool", "armor", "consumable", "accessory"}),
DOWNGRADE_ON_BREAK = new BooleanStat("BREAK_DOWNGRADE", Material.DAMAGED_ANVIL, "Downgrade when Broken", new String[]{"If this item's durability reaches 0,", "it will be fully repaired but also", "downgraded by one level.", "", "&cIt will only break if it cannot be", "&cdowngraded further", "", "Requires to define an &6Upgrade Template", "Required to define &6Custom Durability"}, new String[]{"weapon", "catalyst", "tool", "armor", "consumable", "accessory"}),
DOWNGRADE_ON_DEATH = new BooleanStat("DEATH_DOWNGRADE", Material.DAMAGED_ANVIL, "Downgrade on Death", new String[]{"If the wearer of this item dies, it may", "downgrade (based on &6Death Downgrade", "&6Chance &7stat)", "", "Required to define an &6Upgrade Template", "Requires keep-inventory gamerule. "}, new String[]{"weapon", "catalyst", "tool", "armor", "consumable", "accessory"}),
DOWNGRADE_ON_DEATH_CHANCE = new DoubleStat("DEATH_DOWNGRADE_CHANCE", Material.SKELETON_SKULL, "Death Downgrade Chance", new String[]{"Probability that an item with &cDowngrade ", "&con Death&7 will be downgraded when the", "player dies. ", "", "Exceeding 100% will for sure downgrade", "one item, and roll again to downgrade", "another (with the excess probability).", "&6The same item wont be downgraded twice."}, new String[]{"!miscellaneous", "!block", "all"}, false),
// Unique Item Stats
@ -203,7 +207,7 @@ public class ItemStats {
/**
* @deprecated Item damage is now {@link ItemDamage} and
* custom durability is now {@link CustomDurability}
* custom durability is now {@link CustomDurability}
*/
@Deprecated
public static final ItemStat DURABILITY = ITEM_DAMAGE;

View File

@ -4,8 +4,8 @@ import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackMessage;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.version.SpigotPlugin;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.DeathItemsHandler;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.crafting.MMOItemUIFilter;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
@ -68,7 +68,6 @@ public class MMOItems extends JavaPlugin {
private final LoreFormatManager loreManager = new LoreFormatManager();
private final TemplateManager templateManager = new TemplateManager();
private final SkillManager skillManager = new SkillManager();
private final EntityManager entityManager = new EntityManager();
private final RecipeManager recipeManager = new RecipeManager();
private final LayoutManager layoutManager = new LayoutManager();
private final TypeManager typeManager = new TypeManager();
@ -120,7 +119,7 @@ public class MMOItems extends JavaPlugin {
configManager = new ConfigManager();
statManager.load();
typeManager.reload();
typeManager.reload(false);
templateManager.preloadObjects();
PluginUtils.isDependencyPresent("MMOCore", u -> new MMOCoreMMOLoader());
@ -141,6 +140,8 @@ public class MMOItems extends JavaPlugin {
MMOItemUIFilter.register();
RecipeTypeListGUI.registerNativeRecipes();
typeManager.postload();
skillManager.initialize(false);
final int configVersion = getConfig().contains("config-version", true) ? getConfig().getInt("config-version") : -1;
@ -185,7 +186,6 @@ public class MMOItems extends JavaPlugin {
// This ones are not implementing Reloadable
MMOItemReforger.reload();
Bukkit.getPluginManager().registerEvents(entityManager, this);
Bukkit.getPluginManager().registerEvents(dropTableManager, this);
// Load Dist module
@ -410,10 +410,10 @@ public class MMOItems extends JavaPlugin {
*
* @param value The player inventory subclass
* @deprecated Rather than setting this to the only inventory MMOItems will
* search equipment within, you must add your inventory to the
* handler with <code>getInventory().register()</code>. This method
* will clear all other PlayerInventories for now, as to keep
* backwards compatibility.
* search equipment within, you must add your inventory to the
* handler with <code>getInventory().register()</code>. This method
* will clear all other PlayerInventories for now, as to keep
* backwards compatibility.
*/
@Deprecated
public void setPlayerInventory(PlayerInventory value) {
@ -450,10 +450,6 @@ public class MMOItems extends JavaPlugin {
return tierManager;
}
public EntityManager getEntities() {
return entityManager;
}
public DropTableManager getDropTables() {
return dropTableManager;
}
@ -490,7 +486,7 @@ public class MMOItems extends JavaPlugin {
return templateManager;
}
public LoreFormatManager getLore(){
public LoreFormatManager getLore() {
return loreManager;
}
@ -527,9 +523,9 @@ public class MMOItems extends JavaPlugin {
/**
* @return Generates an item given an item template. The item level will
* scale according to the player RPG level if the template has the
* 'level-item' option. The item will pick a random tier if the
* template has the 'tiered' option
* scale according to the player RPG level if the template has the
* 'level-item' option. The item will pick a random tier if the
* template has the 'tiered' option
*/
@Nullable
public MMOItem getMMOItem(@Nullable Type type, @Nullable String id, @Nullable PlayerData player) {
@ -547,9 +543,9 @@ public class MMOItems extends JavaPlugin {
/**
* @return Generates an item given an item template. The item level will
* scale according to the player RPG level if the template has the
* 'level-item' option. The item will pick a random tier if the
* template has the 'tiered' option
* scale according to the player RPG level if the template has the
* 'level-item' option. The item will pick a random tier if the
* template has the 'tiered' option
*/
@Nullable
public ItemStack getItem(@Nullable Type type, @Nullable String id, @NotNull PlayerData player) {
@ -566,7 +562,7 @@ public class MMOItems extends JavaPlugin {
* @param itemLevel The desired item level
* @param itemTier The desired item tier, can be null
* @return Generates an item given an item template with a
* specific item level and item tier
* specific item level and item tier
*/
@Nullable
public MMOItem getMMOItem(@Nullable Type type, @Nullable String id, int itemLevel, @Nullable ItemTier itemTier) {
@ -586,7 +582,7 @@ public class MMOItems extends JavaPlugin {
* @param itemLevel The desired item level
* @param itemTier The desired item tier, can be null
* @return Generates an item given an item template with a
* specific item level and item tier
* specific item level and item tier
*/
@Nullable
public ItemStack getItem(@Nullable Type type, @Nullable String id, int itemLevel, @Nullable ItemTier itemTier) {
@ -601,10 +597,10 @@ public class MMOItems extends JavaPlugin {
/**
* @return Generates an item given an item template. The item level will be
* 0 and the item will have no item tier unless one is specified in
* the base item data.
* <p></p>
* Will return <code>null</code> if such MMOItem does not exist.
* 0 and the item will have no item tier unless one is specified in
* the base item data.
* <p></p>
* Will return <code>null</code> if such MMOItem does not exist.
*/
@Nullable
public MMOItem getMMOItem(@Nullable Type type, @Nullable String id) {
@ -613,10 +609,10 @@ public class MMOItems extends JavaPlugin {
/**
* @return Generates an item given an item template. The item level will be
* 0 and the item will have no item tier unless one is specified in
* the base item data.
* <p></p>
* Will return <code>null</code> if such MMOItem does not exist.
* 0 and the item will have no item tier unless one is specified in
* the base item data.
* <p></p>
* Will return <code>null</code> if such MMOItem does not exist.
*/
@Nullable
@ -629,10 +625,10 @@ public class MMOItems extends JavaPlugin {
/**
* @return Generates an item given an item template. The item level will be
* 0 and the item will have no item tier unless one is specified in
* the base item data.
* <p></p>
* Will return <code>null</code> if such MMOItem does not exist.
* 0 and the item will have no item tier unless one is specified in
* the base item data.
* <p></p>
* Will return <code>null</code> if such MMOItem does not exist.
*/
@Nullable
public ItemStack getItem(@Nullable Type type, @Nullable String id) {

View File

@ -3,7 +3,12 @@ package net.Indyuce.mmoitems.api;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.player.cooldown.CooldownObject;
import io.lumine.mythic.lib.player.modifier.ModifierSource;
import io.lumine.mythic.lib.script.Script;
import io.lumine.mythic.lib.skill.handler.SkillHandler;
import io.lumine.mythic.lib.util.PostLoadAction;
import io.lumine.mythic.lib.util.PreloadedObject;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.util.identify.UnidentifiedItem;
import net.Indyuce.mmoitems.manager.TypeManager;
@ -20,47 +25,46 @@ import java.util.List;
import java.util.Objects;
@SuppressWarnings("unused")
public class Type {
public class Type implements CooldownObject, PreloadedObject {
// Slashing
public static final Type SWORD = new Type(TypeSet.SLASHING, "SWORD", true, ModifierSource.MELEE_WEAPON);
public static final Type SWORD = new Type("SWORD", true, ModifierSource.MELEE_WEAPON);
// Piercing
public static final Type DAGGER = new Type(TypeSet.PIERCING, "DAGGER", true, ModifierSource.MELEE_WEAPON);
public static final Type SPEAR = new Type(TypeSet.PIERCING, "SPEAR", true, ModifierSource.MELEE_WEAPON);
public static final Type DAGGER = new Type("DAGGER", true, ModifierSource.MELEE_WEAPON);
public static final Type SPEAR = new Type("SPEAR", true, ModifierSource.MELEE_WEAPON);
// Blunt
public static final Type HAMMER = new Type(TypeSet.BLUNT, "HAMMER", true, ModifierSource.MELEE_WEAPON);
public static final Type GAUNTLET = new Type(TypeSet.BLUNT, "GAUNTLET", true, ModifierSource.MELEE_WEAPON);
public static final Type HAMMER = new Type("HAMMER", true, ModifierSource.MELEE_WEAPON);
public static final Type GAUNTLET = new Type("GAUNTLET", true, ModifierSource.MELEE_WEAPON);
// Range
public static final Type WHIP = new Type(TypeSet.RANGE, "WHIP", true, ModifierSource.RANGED_WEAPON);
public static final Type STAFF = new Type(TypeSet.RANGE, "STAFF", true, ModifierSource.RANGED_WEAPON);
public static final Type BOW = new Type(TypeSet.RANGE, "BOW", true, ModifierSource.RANGED_WEAPON);
public static final Type CROSSBOW = new Type(TypeSet.RANGE, "CROSSBOW", false, ModifierSource.RANGED_WEAPON);
public static final Type MUSKET = new Type(TypeSet.RANGE, "MUSKET", true, ModifierSource.RANGED_WEAPON);
public static final Type LUTE = new Type(TypeSet.RANGE, "LUTE", true, ModifierSource.RANGED_WEAPON);
public static final Type WHIP = new Type("WHIP", true, ModifierSource.RANGED_WEAPON);
public static final Type STAFF = new Type("STAFF", true, ModifierSource.RANGED_WEAPON);
public static final Type BOW = new Type("BOW", true, ModifierSource.RANGED_WEAPON);
public static final Type CROSSBOW = new Type("CROSSBOW", true, ModifierSource.RANGED_WEAPON);
public static final Type MUSKET = new Type("MUSKET", true, ModifierSource.RANGED_WEAPON);
public static final Type LUTE = new Type("LUTE", true, ModifierSource.RANGED_WEAPON);
// Hand Accessories
public static final Type CATALYST = new Type(TypeSet.CATALYST, "CATALYST", false, ModifierSource.HAND_ITEM);
public static final Type OFF_CATALYST = new Type(TypeSet.CATALYST, "OFF_CATALYST", false, ModifierSource.OFFHAND_ITEM);
public static final Type MAIN_CATALYST = new Type(TypeSet.CATALYST, "MAIN_CATALYST", false, ModifierSource.MAINHAND_ITEM);
public static final Type CATALYST = new Type("CATALYST", false, ModifierSource.HAND_ITEM);
public static final Type OFF_CATALYST = new Type("OFF_CATALYST", false, ModifierSource.OFFHAND_ITEM);
public static final Type MAIN_CATALYST = new Type("MAIN_CATALYST", false, ModifierSource.MAINHAND_ITEM);
// Any
public static final Type ORNAMENT = new Type(TypeSet.EXTRA, "ORNAMENT", false, ModifierSource.VOID);
public static final Type ORNAMENT = new Type("ORNAMENT", false, ModifierSource.VOID);
// Extra
public static final Type ARMOR = new Type(TypeSet.EXTRA, "ARMOR", false, ModifierSource.ARMOR);
public static final Type TOOL = new Type(TypeSet.EXTRA, "TOOL", false, ModifierSource.MELEE_WEAPON);
public static final Type CONSUMABLE = new Type(TypeSet.EXTRA, "CONSUMABLE", false, ModifierSource.MAINHAND_ITEM);
public static final Type MISCELLANEOUS = new Type(TypeSet.EXTRA, "MISCELLANEOUS", false, ModifierSource.MAINHAND_ITEM);
public static final Type GEM_STONE = new Type(TypeSet.EXTRA, "GEM_STONE", false, ModifierSource.VOID);
public static final Type SKIN = new Type(TypeSet.EXTRA, "SKIN", false, ModifierSource.VOID);
public static final Type ACCESSORY = new Type(TypeSet.EXTRA, "ACCESSORY", false, ModifierSource.ACCESSORY);
public static final Type BLOCK = new Type(TypeSet.EXTRA, "BLOCK", false, ModifierSource.VOID);
public static final Type ARMOR = new Type("ARMOR", false, ModifierSource.ARMOR);
public static final Type TOOL = new Type("TOOL", false, ModifierSource.MELEE_WEAPON);
public static final Type CONSUMABLE = new Type("CONSUMABLE", false, ModifierSource.MAINHAND_ITEM);
public static final Type MISCELLANEOUS = new Type("MISCELLANEOUS", false, ModifierSource.MAINHAND_ITEM);
public static final Type GEM_STONE = new Type("GEM_STONE", false, ModifierSource.VOID);
public static final Type SKIN = new Type("SKIN", false, ModifierSource.VOID);
public static final Type ACCESSORY = new Type("ACCESSORY", false, ModifierSource.ACCESSORY);
public static final Type BLOCK = new Type("BLOCK", false, ModifierSource.VOID);
private final String id;
private final TypeSet set;
private final ModifierSource modifierSource;
private final boolean weapon;
@ -69,6 +73,9 @@ public class Type {
@Nullable
private String loreFormat;
@NotNull
private String attackCooldownKey;
/**
* Used to display the item in the item explorer and in the item recipes
* list in the advanced workbench. can also be edited using the config
@ -77,58 +84,80 @@ public class Type {
private ItemStack item;
/**
* Any type can have a subtype which basically dictates what the item type
* does.
* The parent item type determines:
* - the item options compatible with this new item type
* - if this item type is a weapon or not
* - types of modifiers an item of this type would give
* - the lore format
*/
private Type parent;
private UnidentifiedItem unidentifiedTemplate;
private SkillHandler onLeftClick, onRightClick, onAttack, onEntityInteract;
public Script ent;
private boolean meleeAttacks;
/**
* List of stats which can be applied onto an item which has this type. This
* improves performance when generating an item by a significant amount.
*/
private final List<ItemStat> available = new ArrayList<>();
@Deprecated
public Type(TypeSet set, String id, boolean weapon, ModifierSource modSource, boolean fourGUIMode) {
this(set, id, weapon, modSource);
}
private final PostLoadAction postLoadAction = new PostLoadAction(true, config -> {
onLeftClick = config.contains("on-left-click") ? MythicLib.plugin.getSkills().loadSkillHandler(config.get("on-left-click")) : null;
onRightClick = config.contains("on-right-click") ? MythicLib.plugin.getSkills().loadSkillHandler(config.get("on-right-click")) : null;
onAttack = config.contains("on-attack") ? MythicLib.plugin.getSkills().loadSkillHandler(config.get("on-attack")) : null;
onEntityInteract = config.contains("on-entity-interact") ? MythicLib.plugin.getSkills().loadSkillHandler(config.get("on-entity-interact")) : null;
});
public Type(TypeSet set, String id, boolean weapon, ModifierSource modSource) {
this.set = set;
this.id = id.toUpperCase().replace("-", "_").replace(" ", "_");
/**
* Hard-coded type with given parameters
*/
public Type(String id, boolean weapon, ModifierSource modSource) {
this.id = UtilityMethods.enumName(id);
this.modifierSource = modSource;
this.weapon = weapon;
this.loreFormat = null;
}
/**
* Custom type
*/
public Type(@NotNull TypeManager manager, @NotNull ConfigurationSection config) {
id = config.getName().toUpperCase().replace("-", "_").replace(" ", "_");
id = UtilityMethods.enumName(config.getName());
parent = manager.get(config.getString("parent", "").toUpperCase().replace("-", "_").replace(" ", "_"));
set = (parent != null ? parent.set : TypeSet.EXTRA);
weapon = (parent != null && parent.weapon);
modifierSource = (parent != null ? parent.modifierSource : ModifierSource.OTHER);
this.loreFormat = config.getString("LoreFormat", (parent != null ? parent.loreFormat : null));
weapon = config.getBoolean("weapon", parent != null && parent.weapon);
modifierSource = config.contains("modifier-source") ? ModifierSource.valueOf(UtilityMethods.enumName(config.getString("modifier-source"))) : (parent != null ? parent.modifierSource : ModifierSource.OTHER);
}
public void load(ConfigurationSection config) {
Validate.notNull(config, "Could not find config for " + getId());
public void load(@NotNull ConfigurationSection config) {
postLoadAction.cacheConfig(config);
name = config.getString("name", name);
item = read(config.getString("display", item == null ? Material.STONE.toString() : item.getType().toString()));
(unidentifiedTemplate = new UnidentifiedItem(this)).update(config.getConfigurationSection("unident-item"));
loreFormat = config.getString("LoreFormat", (parent != null ? parent.loreFormat : null));
attackCooldownKey = config.getString("attack-cooldown-key", "default");
meleeAttacks = !config.getBoolean("disable-melee-attacks");
}
// Getting overridden?
loreFormat = config.getString("LoreFormat", (parent != null ? parent.loreFormat : loreFormat));
@Deprecated
public void postload(ConfigurationSection config) {
postLoadAction.cacheConfig(config);
postLoadAction.performAction();
}
@NotNull
@Override
public PostLoadAction getPostLoadAction() {
return postLoadAction;
}
/**
* @deprecated Type is no longer an enum so that external plugins
* can register their own types. Use getId() instead
* can register their own types. Use getId() instead
*/
@Deprecated
public String name() {
@ -144,18 +173,19 @@ public class Type {
return id;
}
public TypeSet getItemSet() {
return set;
}
public boolean isWeapon() {
return weapon;
}
public boolean hasMeleeAttacks() {
return meleeAttacks;
}
public String getName() {
return name;
}
@NotNull
public ModifierSource getModifierSource() {
return modifierSource;
}
@ -164,6 +194,31 @@ public class Type {
return item.clone();
}
@Nullable
public SkillHandler onLeftClick() {
return onLeftClick;
}
@Override
public String getCooldownPath() {
return "mmoitems_weapons:" + attackCooldownKey;
}
@Nullable
public SkillHandler onRightClick() {
return onRightClick;
}
@Nullable
public SkillHandler onAttack() {
return onAttack;
}
@Nullable
public SkillHandler onEntityInteract() {
return onEntityInteract;
}
/**
* @deprecated Use {@link #getSupertype()}
*/
@ -207,21 +262,16 @@ public class Type {
/**
* @return Either if the two types are the same,
* or if this type is a subtype of the given type.
* or if this type is a subtype of the given type.
*/
public boolean corresponds(Type type) {
return getSupertype().equals(type);
}
@Deprecated
public boolean corresponds(TypeSet set) {
return getSupertype().getItemSet() == set;
}
/**
* @return The collection of all stats which can be applied onto this
* specific item type. This list is cached when types are being
* loaded and is a PRETTY GOOD performance improvement.
* specific item type. This list is cached when types are being
* loaded and is a PRETTY GOOD performance improvement.
*/
public List<ItemStat> getAvailableStats() {
return available;
@ -229,7 +279,7 @@ public class Type {
/**
* @return Finds the /item config file corresponding to the item type and
* loads it
* loads it
*/
public ConfigFile getConfigFile() {
return new ConfigFile("/item", getId().toLowerCase());
@ -299,10 +349,12 @@ public class Type {
*/
@Nullable
public static Type get(@Nullable String id) {
if (id == null) return null;
return id == null ? null : MMOItems.plugin.getTypes().get(UtilityMethods.enumName(id));
}
String format = UtilityMethods.enumName(id);
return MMOItems.plugin.getTypes().has(format) ? MMOItems.plugin.getTypes().get(format) : null;
@Nullable
public static Type get(@NotNull NBTItem item) {
return get(item.getType());
}
/**

View File

@ -1,166 +0,0 @@
package net.Indyuce.mmoitems.api;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.AttackMetadata;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.version.VersionSound;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.player.PlayerData.CooldownType;
import net.Indyuce.mmoitems.util.MMOUtils;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
public enum TypeSet {
/**
* Slashing weapons deal damage in a cone behind the player's initial
* target, which makes it a deadly AoE weapon for warriors
*/
SLASHING((attack, attacker, attackerData, target, weapon) -> {
if (!MMOItems.plugin.getConfig().getBoolean("item-ability.slashing.enabled")
|| attackerData.isOnCooldown(CooldownType.SET_TYPE_ATTACK))
return;
attackerData.applyCooldown(CooldownType.SET_TYPE_ATTACK, MMOItems.plugin.getConfig().getDouble("item-ability.slashing.cooldown"));
Location loc = attacker.getPlayer().getLocation().clone().add(0, 1.3, 0);
final double a1 = (loc.getYaw() + 90) / 180 * Math.PI, p = -loc.getPitch() / 180 * Math.PI;
for (double r = 1; r < 5; r += .3)
for (double a = -Math.PI / 6; a < Math.PI / 6; a += Math.PI / 8 / r)
loc.getWorld().spawnParticle(Particle.CRIT, loc.clone().add(Math.cos(a + a1) * r, Math.sin(p) * r, Math.sin(a + a1) * r), 0);
for (Entity entity : MMOUtils.getNearbyChunkEntities(loc))
if (entity.getLocation().distanceSquared(loc) < 40
&& attacker.getPlayer().getEyeLocation().getDirection()
.angle(entity.getLocation().subtract(attacker.getPlayer().getLocation()).toVector()) < Math.PI / 3
&& UtilityMethods.canTarget(attacker.getPlayer(), entity, InteractionType.OFFENSE_ACTION) && !entity.equals(target))
attacker.attack((LivingEntity) entity, attack.getDamage().getDamage() * .4, DamageType.WEAPON, DamageType.PHYSICAL);
}),
/**
* Piercing weapons deal damage in a line behind the initial target, which
* is harder to land than a slashing weapon but the AoE damage ratio is
* increased which makes it a perfect 'double or nothing' weapon for
* assassins
*/
PIERCING((attack, attacker, attackerData, target, weapon) -> {
if (!MMOItems.plugin.getConfig().getBoolean("item-ability.piercing.enabled")
|| attackerData.isOnCooldown(CooldownType.SET_TYPE_ATTACK))
return;
attackerData.applyCooldown(CooldownType.SET_TYPE_ATTACK, MMOItems.plugin.getConfig().getDouble("item-ability.piercing.cooldown"));
Location loc = attacker.getPlayer().getLocation().clone().add(0, 1.3, 0);
final double a1 = (loc.getYaw() + 90) / 180 * Math.PI, p = -loc.getPitch() / 180 * Math.PI;
for (double r = 1; r < 5; r += .3)
for (double a = -Math.PI / 12; a < Math.PI / 12; a += Math.PI / 16 / r)
loc.getWorld().spawnParticle(Particle.CRIT, loc.clone().add(Math.cos(a + a1) * r, Math.sin(p) * r, Math.sin(a + a1) * r), 0);
for (Entity entity : MMOUtils.getNearbyChunkEntities(loc))
if (!entity.equals(target) && entity.getLocation().distanceSquared(attacker.getPlayer().getLocation()) < 40
&& attacker.getPlayer().getEyeLocation().getDirection()
.angle(entity.getLocation().toVector().subtract(attacker.getPlayer().getLocation().toVector())) < Math.PI / 12
&& UtilityMethods.canTarget(attacker.getPlayer(), entity, InteractionType.OFFENSE_ACTION))
attacker.attack((LivingEntity) entity, attack.getDamage().getDamage() * .6, DamageType.WEAPON, DamageType.PHYSICAL);
}),
/**
* Blunt weapons are like 1.9 sweep attacks. They damage
* all enemies nearby and apply a slight knockback
*/
BLUNT((attack, attacker, attackerData, target, weapon) -> {
final Random random = new Random();
float pitchRange = 0.7f + random.nextFloat() * (0.9f - 0.7f);
final double bluntPower;
if (MMOItems.plugin.getConfig().getBoolean("item-ability.blunt.aoe.enabled")
&& !attackerData.isOnCooldown(CooldownType.SPECIAL_ATTACK)
&& (bluntPower = attacker.getStat("BLUNT_POWER")) > 0) {
attackerData.applyCooldown(CooldownType.SPECIAL_ATTACK, MMOItems.plugin.getConfig().getDouble("item-ability.blunt.aoe.cooldown"));
target.getWorld().playSound(target.getLocation(), Sound.BLOCK_ANVIL_LAND, 0.6f, pitchRange);
target.getWorld().spawnParticle(Particle.EXPLOSION_LARGE, target.getLocation().add(0, 1, 0), 0);
final double bluntRating = weapon.requireNonZero(attacker.getStat("BLUNT_RATING"),
MMOItems.plugin.getConfig().getDouble("default.blunt-rating")) / 100;
for (Entity entity : target.getNearbyEntities(bluntPower, bluntPower, bluntPower))
if (UtilityMethods.canTarget(attacker.getPlayer(), entity, InteractionType.OFFENSE_ACTION) && !entity.equals(target))
attacker.attack((LivingEntity) entity, attack.getDamage().getDamage() * bluntRating, DamageType.WEAPON, DamageType.PHYSICAL);
}
if (MMOItems.plugin.getConfig().getBoolean("item-ability.blunt.stun.enabled")
&& !attackerData.isOnCooldown(CooldownType.SPECIAL_ATTACK)
&& random.nextDouble() < MMOItems.plugin.getConfig().getDouble("item-ability.blunt.stun.chance") / 100) {
attackerData.applyCooldown(CooldownType.SPECIAL_ATTACK, MMOItems.plugin.getConfig().getDouble("item-ability.blunt.stun.cooldown"));
target.getWorld().playSound(target.getLocation(), VersionSound.ENTITY_ZOMBIE_ATTACK_WOODEN_DOOR.toSound(), 1, 2);
target.removePotionEffect(PotionEffectType.SLOW);
target.removePotionEffect(PotionEffectType.BLINDNESS);
target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 20, 0));
target.addPotionEffect(
new PotionEffect(PotionEffectType.SLOW, (int) (30 * MMOItems.plugin.getConfig().getDouble("item-ability.blunt.stun.power")), 1));
Location loc = target.getLocation();
loc.setYaw((float) (loc.getYaw() + 2 * (random.nextDouble() - .5) * 90));
loc.setPitch((float) (loc.getPitch() + 2 * (random.nextDouble() - .5) * 30));
}
}),
/**
* Ranged attacks based weapons. when the player is too squishy to fight in
* the middle of the battle-field, these weapons allow him to take some
* distance and still deal some good damage
*/
RANGE,
/**
* Hand/Mainhand/Offhand catalysts
*/
CATALYST,
/**
* Any other item type, like armor, consumables, etc. They all have their
* very specific passive depending on their item type
*/
EXTRA;
private final SetAttackHandler attackHandler;
private final String name;
private TypeSet() {
this(null);
}
private TypeSet(SetAttackHandler attackHandler) {
this.attackHandler = attackHandler;
this.name = UtilityMethods.caseOnWords(name().toLowerCase());
}
public boolean hasAttackEffect() {
return attackHandler != null;
}
public void applyAttackEffect(AttackMetadata attackMeta, PlayerMetadata attacker, PlayerData attackerData, LivingEntity target, Weapon weapon) {
attackHandler.apply(attackMeta, attacker, attackerData, target, weapon);
}
public String getName() {
return name;
}
@FunctionalInterface
interface SetAttackHandler {
void apply(@NotNull AttackMetadata attack, @NotNull PlayerMetadata attacker, @NotNull PlayerData attackerData, @NotNull LivingEntity target, @NotNull Weapon weapon);
}
}

View File

@ -1,6 +1,7 @@
package net.Indyuce.mmoitems.api.crafting;
import io.lumine.mythic.lib.api.util.PostLoadObject;
import io.lumine.mythic.lib.util.PostLoadAction;
import io.lumine.mythic.lib.util.PreloadedObject;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.crafting.ingredient.inventory.IngredientInventory;
import net.Indyuce.mmoitems.api.crafting.recipe.CheckedRecipe;
@ -13,12 +14,13 @@ import org.apache.commons.lang.Validate;
import org.bukkit.Sound;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.logging.Level;
public class CraftingStation extends PostLoadObject {
public class CraftingStation implements PreloadedObject {
private final String id;
private final String name;
private final Layout layout;
@ -38,8 +40,17 @@ public class CraftingStation extends PostLoadObject {
private CraftingStation parent;
private final PostLoadAction postLoadAction = new PostLoadAction(config -> {
if (!config.contains("parent")) return;
String id = config.getString("parent").toLowerCase().replace(" ", "-").replace("_", "-");
Validate.isTrue(!id.equals(CraftingStation.this.id), "Station cannot use itself as parent");
Validate.isTrue(MMOItems.plugin.getCrafting().hasStation(id), "Could not find parent station with ID '" + id + "'");
parent = MMOItems.plugin.getCrafting().getStation(id);
});
public CraftingStation(String id, FileConfiguration config) {
super(config);
postLoadAction.cacheConfig(config);
this.id = id.toLowerCase().replace("_", "-").replace(" ", "-");
this.name = config.getString("name", "Unnamed");
@ -59,8 +70,6 @@ public class CraftingStation extends PostLoadObject {
}
public CraftingStation(String id, String name, Layout layout, Sound sound, StationItemOptions itemOptions, int maxQueueSize, CraftingStation parent) {
super(null);
Validate.notNull(id, "Crafting station ID must not be null");
Validate.notNull(name, "Crafting station name must not be null");
Validate.notNull(sound, "Crafting station sound must not be null");
@ -74,6 +83,12 @@ public class CraftingStation extends PostLoadObject {
this.parent = parent;
}
@NotNull
@Override
public PostLoadAction getPostLoadAction() {
return postLoadAction;
}
public String getId() {
return id;
}
@ -169,16 +184,6 @@ public class CraftingStation extends PostLoadObject {
return Math.max(1, (int) Math.ceil((double) recipes / getLayout().getRecipeSlots().size()));
}
@Override
protected void whenPostLoaded(ConfigurationSection config) {
if (config.contains("parent")) {
String id = config.getString("parent").toLowerCase().replace(" ", "-").replace("_", "-");
Validate.isTrue(!id.equals(this.id), "Station cannot use itself as parent");
Validate.isTrue(MMOItems.plugin.getCrafting().hasStation(id), "Could not find parent station with ID '" + id + "'");
parent = MMOItems.plugin.getCrafting().getStation(id);
}
}
/*
* find type of crafting recipe based on section. there is no 'type' recipe
* parameter because old files would be out of date, instead just looks for

View File

@ -1,14 +1,14 @@
package net.Indyuce.mmoitems.api.event.item;
import net.Indyuce.mmoitems.api.event.PlayerDataEvent;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.UntargetedWeapon;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
public class UntargetedWeaponUseEvent extends PlayerDataEvent implements Cancellable {
private final UntargetedWeapon weapon;
private final Weapon weapon;
private boolean cancelled;
@ -20,7 +20,7 @@ public class UntargetedWeaponUseEvent extends PlayerDataEvent implements Cancell
* @param who Player attacking
* @param weapon Weapon being used
*/
public UntargetedWeaponUseEvent(@NotNull PlayerData who, @NotNull UntargetedWeapon weapon) {
public UntargetedWeaponUseEvent(@NotNull PlayerData who, @NotNull Weapon weapon) {
super(who);
this.weapon = weapon;
@ -30,7 +30,7 @@ public class UntargetedWeaponUseEvent extends PlayerDataEvent implements Cancell
* @return The weapon used by the player that fired this event
*/
@NotNull
public UntargetedWeapon getWeapon() {
public Weapon getWeapon() {
return weapon;
}

View File

@ -11,6 +11,7 @@ import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.event.item.ConsumableConsumedEvent;
import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem;
import net.Indyuce.mmoitems.api.item.util.LoreUpdate;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import net.Indyuce.mmoitems.stat.type.PlayerConsumable;
import org.bukkit.Bukkit;
@ -22,10 +23,16 @@ import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public class Consumable extends UseItem {
@Deprecated
public Consumable(Player player, NBTItem item) {
super(player, item);
}
public Consumable(PlayerData player, NBTItem item) {
super(player, item);
}
@Override
public boolean checkItemRequirements() {
return MythicLib.plugin.getFlags().isFlagAllowed(player, CustomFlag.MI_CONSUMABLES) && playerData.getRPG().canUse(getNBTItem(), true);

View File

@ -6,6 +6,7 @@ import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.event.item.ApplyGemStoneEvent;
import net.Indyuce.mmoitems.api.item.mmoitem.LiveMMOItem;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.stat.Enchants;
import net.Indyuce.mmoitems.stat.GemUpgradeScaling;
@ -28,11 +29,15 @@ import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class GemStone extends UseItem {
@Deprecated
public GemStone(Player player, NBTItem item) {
super(player, item);
}
public GemStone(PlayerData player, NBTItem item) {
super(player, item);
}
@NotNull
public ApplyResult applyOntoItem(@NotNull NBTItem target, @NotNull Type targetType) {
@ -60,7 +65,7 @@ public class GemStone extends UseItem {
// Checks if the gem supports the item type, or the item set, or a weapon
String appliableTypes = getNBTItem().getString(ItemStats.ITEM_TYPE_RESTRICTION.getNBTPath());
if (!appliableTypes.equals("") && (!targetType.isWeapon() || !appliableTypes.contains("WEAPON"))
&& !appliableTypes.contains(targetType.getItemSet().name()) && !appliableTypes.contains(targetType.getId()))
&& !appliableTypes.contains(targetType.getId()))
return new ApplyResult(ResultType.NONE);
// Check for success rate

View File

@ -7,6 +7,7 @@ import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.stat.data.SkullTextureData;
import net.Indyuce.mmoitems.stat.data.StringListData;
@ -19,16 +20,20 @@ import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.List;
public class ItemSkin extends UseItem {
@Deprecated
public ItemSkin(Player player, NBTItem item) {
super(player, item);
}
public ItemSkin(PlayerData player, NBTItem item) {
super(player, item);
}
public ApplyResult applyOntoItem(NBTItem target, Type targetType) {
if (targetType == Type.SKIN)
return new ApplyResult(ResultType.NONE);
@ -42,9 +47,9 @@ public class ItemSkin extends UseItem {
//SKIN//MMOItems.log("\u00a78SKIN \u00a7eCPT\u00a77 Applying onto " + MMOUtils.getDisplayName(target.getItem()));
// Types compatibility check
if (getMMOItem().hasData(ItemStats.COMPATIBLE_TYPES)) {
if (mmoitem.hasData(ItemStats.COMPATIBLE_TYPES)) {
//SKIN//MMOItems.log("\u00a78SKIN \u00a7eCPT\u00a77 Testing that TYPE is compatible: ");
final List<String> acceptedTypes = ((StringListData) getMMOItem().getData(ItemStats.COMPATIBLE_TYPES)).getList();
final List<String> acceptedTypes = ((StringListData) mmoitem.getData(ItemStats.COMPATIBLE_TYPES)).getList();
if (acceptedTypes.size() > 0 && acceptedTypes.stream().noneMatch(s -> s.equalsIgnoreCase(targetType.getId()))) {
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 2);
Message.SKIN_INCOMPATIBLE.format(ChatColor.RED, "#item#", MMOUtils.getDisplayName(target.getItem()))
@ -54,9 +59,9 @@ public class ItemSkin extends UseItem {
}
// IDs compatibility check
if (getMMOItem().hasData(ItemStats.COMPATIBLE_IDS)) {
if (mmoitem.hasData(ItemStats.COMPATIBLE_IDS)) {
//SKIN//MMOItems.log("\u00a78SKIN \u00a7eCPT\u00a77 Testing that ID is compatible: ");
final List<String> acceptedIDs = ((StringListData) getMMOItem().getData(ItemStats.COMPATIBLE_IDS)).getList();
final List<String> acceptedIDs = ((StringListData) mmoitem.getData(ItemStats.COMPATIBLE_IDS)).getList();
final String targetId = target.getString("MMOITEMS_ITEM_ID");
if (acceptedIDs.size() > 0 && acceptedIDs.stream()
@ -69,9 +74,9 @@ public class ItemSkin extends UseItem {
}
// Material compatibility check
if (getMMOItem().hasData(ItemStats.COMPATIBLE_MATERIALS)) {
if (mmoitem.hasData(ItemStats.COMPATIBLE_MATERIALS)) {
//SKIN//MMOItems.log("\u00a78SKIN \u00a7eCPT\u00a77 Testing that MATERIAL is compatible: ");
List<String> acceptedMaterials = ((StringListData) getMMOItem().getData(ItemStats.COMPATIBLE_MATERIALS)).getList();
List<String> acceptedMaterials = ((StringListData) mmoitem.getData(ItemStats.COMPATIBLE_MATERIALS)).getList();
if (acceptedMaterials.size() > 0 && acceptedMaterials.stream()
.noneMatch(s -> s.equalsIgnoreCase(target.getItem().getType().name()))) {
@ -93,7 +98,7 @@ public class ItemSkin extends UseItem {
}
// Apply skin
ItemStack item = applySkin(target, getMMOItem());
ItemStack item = applySkin(target, mmoitem);
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 2);
Message.SKIN_APPLIED.format(ChatColor.YELLOW, "#item#", MMOUtils.getDisplayName(target.getItem())).send(player);

View File

@ -6,9 +6,9 @@ import io.lumine.mythic.lib.comp.flags.CustomFlag;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.interaction.weapon.Gauntlet;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.*;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.Lute;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.Musket;
import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.stat.data.CommandData;
@ -27,11 +27,12 @@ public class UseItem {
protected static final Random RANDOM = new Random();
public UseItem(Player player, NBTItem nbtItem) {
@Deprecated
public UseItem(@NotNull Player player, @NotNull NBTItem nbtItem) {
this(PlayerData.get(player), nbtItem);
}
public UseItem(PlayerData playerData, NBTItem nbtItem) {
public UseItem(@NotNull PlayerData playerData, @NotNull NBTItem nbtItem) {
this.player = playerData.getPlayer();
this.playerData = playerData;
this.mmoitem = new VolatileMMOItem(nbtItem);
@ -85,11 +86,9 @@ public class UseItem {
private void scheduleCommandExecution(CommandData command) {
String parsed = MythicLib.plugin.getPlaceholderParser().parse(player, command.getCommand());
if (!command.hasDelay())
dispatchCommand(parsed, command.isConsoleCommand(), command.hasOpPerms());
if (!command.hasDelay()) dispatchCommand(parsed, command.isConsoleCommand(), command.hasOpPerms());
else
Bukkit.getScheduler().runTaskLater(MMOItems.plugin, () -> dispatchCommand(parsed, command.isConsoleCommand(), command.hasOpPerms()),
(long) command.getDelay() * 20);
Bukkit.getScheduler().runTaskLater(MMOItems.plugin, () -> dispatchCommand(parsed, command.isConsoleCommand(), command.hasOpPerms()), (long) command.getDelay() * 20);
}
/**
@ -116,33 +115,22 @@ public class UseItem {
} finally {
player.setOp(false);
}
} else
Bukkit.dispatchCommand(player, parsed);
} else Bukkit.dispatchCommand(player, parsed);
}
@Deprecated
public static UseItem getItem(Player player, NBTItem item, String type) {
return getItem(player, item, Type.get(type));
}
public static UseItem getItem(@NotNull Player player, @NotNull NBTItem item, @NotNull Type type) {
if (type.corresponds(Type.CONSUMABLE))
return new Consumable(player, item);
if (type.corresponds(Type.SKIN))
return new ItemSkin(player, item);
if (type.corresponds(Type.GEM_STONE))
return new GemStone(player, item);
if (type.corresponds(Type.MUSKET))
return new Musket(player, item);
if (type.corresponds(Type.CROSSBOW))
return new Crossbow(player, item);
if (type.corresponds(Type.GAUNTLET))
return new Gauntlet(player, item);
if (type.corresponds(Type.WHIP))
return new Whip(player, item);
if (type.corresponds(Type.LUTE))
return new Lute(player, item);
if (type.corresponds(Type.STAFF))
return new Staff(player, item);
return type.isWeapon() ? new Weapon(player, item) : new UseItem(player, item);
final PlayerData playerData = PlayerData.get(player);
if (type.corresponds(Type.CONSUMABLE)) return new Consumable(playerData, item);
if (type.corresponds(Type.SKIN)) return new ItemSkin(playerData, item);
if (type.corresponds(Type.GEM_STONE)) return new GemStone(playerData, item);
if (type.corresponds(Type.MUSKET)) return new Musket(playerData, item);
if (type.corresponds(Type.LUTE)) return new Lute(playerData, item);
return type.isWeapon() ? new Weapon(playerData, item) : new UseItem(playerData, item);
}
}

View File

@ -0,0 +1,35 @@
package net.Indyuce.mmoitems.api.interaction;
public enum WeaponAttackResult {
/**
* Attack was performed successfully
*/
SUCCESS,
/**
* Attakc was not performed due to missing resource (mana or stamina)
* or ongoing cooldown.
*/
WEAPON_COSTS,
/**
* Attack was canceled because the item is broken
*/
DURABILITY,
/**
* Attack was canceled due to a precondition of the executed skill
*/
SKILL_SPECIFIC,
/**
* Attack was canceled due to an event
*/
BUKKIT_EVENT,
/**
* No attack effect was detected on the item.
*/
NO_ATTACK,
}

View File

@ -1,81 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.projectile;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.player.PlayerMetadata;
import net.Indyuce.mmoitems.stat.data.PotionEffectData;
import org.apache.commons.lang.Validate;
import org.bukkit.entity.LivingEntity;
import org.bukkit.potion.PotionEffectType;
/**
* Since MMOItems 6.7.5 vanilla bows and custom bows are
* not treated the same way:
* - vanilla bows see NO changes in their damage computations
* - custom bows override
*/
public class ProjectileData {
private final NBTItem sourceItem;
private final PlayerMetadata shooter;
private final double damageMultiplier;
@Deprecated
public ProjectileData(PlayerMetadata shooter, NBTItem sourceItem, boolean customWeapon, double damageMultiplier) {
this(shooter, sourceItem, damageMultiplier);
}
public ProjectileData(PlayerMetadata shooter, NBTItem sourceItem, double damageMultiplier) {
this.shooter = shooter;
this.sourceItem = sourceItem;
this.damageMultiplier = damageMultiplier;
}
public NBTItem getSourceItem() {
return sourceItem;
}
public PlayerMetadata getShooter() {
return shooter;
}
public double getDamageMultiplier() {
return damageMultiplier;
}
/**
* Used to check if that projectile data is linked to
* a projectile that want sent using a MMOItems bow.
* <p>
* If so, it needs to apply on-hit effects like
* elemental damage or on-hit potion effects
*/
@Deprecated
public boolean isCustomWeapon() {
return true;
}
/**
* Will throw an error if it's not a custom bow
*
* @return Damage of custom bow
*/
public double getDamage() {
Validate.isTrue(isCustomWeapon(), "Not a custom bow");
return shooter.getStat("ATTACK_DAMAGE") * damageMultiplier;
}
/**
* @see {@link #getDamage()}
*/
@Deprecated
public void setDamage(double damage) {
Validate.isTrue(isCustomWeapon(), "Not a custom bow");
shooter.setStat("ATTACK_DAMAGE", damage);
}
public void applyPotionEffects(LivingEntity target) {
if (sourceItem.hasTag("MMOITEMS_ARROW_POTION_EFFECTS"))
for (ArrowPotionEffectArrayItem entry : MythicLib.plugin.getJson().parse(sourceItem.getString("MMOITEMS_ARROW_POTION_EFFECTS"), ArrowPotionEffectArrayItem[].class))
target.addPotionEffect(new PotionEffectData(PotionEffectType.getByName(entry.type), entry.duration, entry.level).toEffect());
}
}

View File

@ -1,33 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon;
import io.lumine.mythic.lib.api.item.NBTItem;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.player.PlayerData.CooldownType;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
public class Gauntlet extends Weapon {
public Gauntlet(Player player, NBTItem item) {
super(player, item);
}
public void specialAttack(LivingEntity target) {
if (!MMOItems.plugin.getConfig().getBoolean("item-ability.gauntlet.enabled"))
return;
if (!checkWeaponCosts(CooldownType.SPECIAL_ATTACK))
return;
applyWeaponCosts(MMOItems.plugin.getConfig().getDouble("item-ability.gauntlet.cooldown"), CooldownType.SPECIAL_ATTACK);
target.getWorld().spawnParticle(Particle.EXPLOSION_LARGE, target.getLocation().add(0, 1, 0), 0);
target.getWorld().playSound(target.getLocation(), Sound.BLOCK_ANVIL_LAND, 1, 0);
target.removePotionEffect(PotionEffectType.BLINDNESS);
target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 40, 1));
target.setVelocity(getPlayer().getEyeLocation().getDirection().setY(0).normalize().setY(.8));
target.setVelocity(getPlayer().getEyeLocation().getDirection().setY(0).normalize().multiply(2).setY(.3));
}
}

View File

@ -2,13 +2,28 @@ package net.Indyuce.mmoitems.api.interaction.weapon;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.flags.CustomFlag;
import io.lumine.mythic.lib.damage.AttackMetadata;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.skill.SimpleSkill;
import io.lumine.mythic.lib.skill.SkillMetadata;
import io.lumine.mythic.lib.skill.handler.SkillHandler;
import io.lumine.mythic.lib.skill.result.SkillResult;
import io.lumine.mythic.lib.skill.trigger.TriggerMetadata;
import io.lumine.mythic.lib.skill.trigger.TriggerType;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.event.item.UntargetedWeaponUseEvent;
import net.Indyuce.mmoitems.api.interaction.UseItem;
import net.Indyuce.mmoitems.api.interaction.WeaponAttackResult;
import net.Indyuce.mmoitems.api.interaction.util.UntargetedDurabilityItem;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.LegacyWeapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.player.PlayerData.CooldownType;
import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.stat.ActionLeftClick;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.entity.LivingEntity;
@ -18,6 +33,8 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
public class Weapon extends UseItem {
@Deprecated
public Weapon(Player player, NBTItem item) {
this(PlayerData.get(player), item);
}
@ -43,18 +60,17 @@ public class Weapon extends UseItem {
* @return If the attack was cast successfully
*/
public boolean checkAndApplyWeaponCosts() {
if (!checkWeaponCosts(null)) return false;
applyWeaponCosts(0, null);
if (!checkWeaponCosts(false)) return false;
applyWeaponCosts(null);
return true;
}
/**
* Checks for cooldown, mana and stamina weapon costs
*
* @return If requirements were met ie the attack can be cast successfully
* @return If instantaneous weapon costs are met.
* @see {@link #applyWeaponCosts(Double)}
*/
public boolean checkWeaponCosts(@Nullable CooldownType cooldown) {
if (cooldown != null && getPlayerData().isOnCooldown(cooldown))
public boolean checkWeaponCosts(boolean cooldowns) {
if (cooldowns && getPlayerData().getMMOPlayerData().getCooldownMap().isOnCooldown(mmoitem.getType()))
return false;
double manaCost = getNBTItem().getStat("MANA_COST");
@ -73,22 +89,25 @@ public class Weapon extends UseItem {
}
/**
* Applies cooldown, mana and stamina weapon costs
* Hard coded instantaneous weapon costs are as follows:
* - mana and stamina (resources)
* - cooldown (given by the item type attack cooldown key)
* <p>
* Other weapon costs are inherent to the item type and are
* fully configurable inside of the types config file.
*
* @param attackDelay The weapon attack period/delay
* @param cooldown The weapon cooldown type. When set to null, no
* cooldown will be applied. This is made to handle
* custom weapons
* @see {@link #checkWeaponCosts(boolean)}
*/
public void applyWeaponCosts(double attackDelay, @Nullable CooldownType cooldown) {
public void applyWeaponCosts(@Nullable Double attackDelay) {
double manaCost = getNBTItem().getStat("MANA_COST");
final double manaCost = getNBTItem().getStat("MANA_COST");
if (manaCost > 0) playerData.getRPG().giveMana(-manaCost);
double staminaCost = getNBTItem().getStat("STAMINA_COST");
final double staminaCost = getNBTItem().getStat("STAMINA_COST");
if (staminaCost > 0) playerData.getRPG().giveStamina(-staminaCost);
if (cooldown != null) getPlayerData().applyCooldown(cooldown, attackDelay);
if (attackDelay != null)
getPlayerData().getMMOPlayerData().getCooldownMap().applyCooldown(mmoitem.getType(), attackDelay);
}
/**
@ -103,20 +122,120 @@ public class Weapon extends UseItem {
public boolean handleTargetedAttack(AttackMetadata attackMeta, @NotNull PlayerMetadata attacker, LivingEntity target) {
// Handle weapon mana and stamina costs ONLY
if (!checkAndApplyWeaponCosts())
return false;
if (!checkAndApplyWeaponCosts()) return false;
// Handle item set attack effects
if (getMMOItem().getType().getItemSet().hasAttackEffect() && !getNBTItem().getBoolean("MMOITEMS_DISABLE_ATTACK_PASSIVE"))
getMMOItem().getType().getItemSet().applyAttackEffect(attackMeta, attacker, playerData, target, this);
// Handle on-hit attack effects
final SkillHandler onHitSkill = mmoitem.getType().onAttack();
if (onHitSkill != null && !getNBTItem().getBoolean("MMOITEMS_DISABLE_ATTACK_PASSIVE"))
new SimpleSkill(onHitSkill).cast(new TriggerMetadata(attacker, TriggerType.API, target, attackMeta));
return true;
}
@Nullable
private SkillHandler findClickSkill(boolean rightClick) {
String skillId = getNBTItem().getString((rightClick ? ItemStats.RIGHT_CLICK_SCRIPT : ItemStats.LEFT_CLICK_SCRIPT).getNBTPath());
// (Deprecated) Support for staff spirits on left-clicks
if (!rightClick && (skillId == null || skillId.isEmpty()))
skillId = getNBTItem().getString(ActionLeftClick.LEGACY_PATH);
if (skillId == null || skillId.isEmpty()) {
// Find item type action
final Type itemType = mmoitem.getType();
return rightClick ? itemType.onRightClick() : itemType.onLeftClick();
}
// Override item type with custom action
return MythicLib.plugin.getSkills().getHandler(skillId);
}
/**
* Called when the player interacts with the item. This method is used to
* apply durability and cast the weapon attack
*
* @param rightClick Is the click a right click? If false, it's a left click.
* @param actionHand Slot being interacted with
* @return Null if there are no attack detected
* @implNote Since MI 6.7.3 this method handles custom durability, cooldown
* checks and player stat snapshots.
*/
@Nullable
public WeaponAttackResult handleUntargetedAttack(boolean rightClick, @NotNull EquipmentSlot actionHand) {
if (this instanceof LegacyWeapon) return legacyHandleUntargetedAttack(rightClick, actionHand);
// Check for item type attack effect
final SkillHandler handler = findClickSkill(rightClick);
if (handler == null) return WeaponAttackResult.NO_ATTACK;
// Check for attack effect conditions
final SkillMetadata meta = new TriggerMetadata(getPlayerData().getMMOPlayerData(), TriggerType.API).toSkillMetadata(new SimpleSkill(handler));
final SkillResult result = handler.getResult(meta);
if (!result.isSuccessful(meta)) return WeaponAttackResult.NO_ATTACK;
// Check for durability
UntargetedDurabilityItem durItem = new UntargetedDurabilityItem(getPlayer(), getNBTItem(), actionHand);
if (durItem.isBroken()) return WeaponAttackResult.DURABILITY;
// Apply weapon instantaneous costs
PlayerMetadata stats = getPlayerData().getStats().newTemporary(actionHand);
if (!checkWeaponCosts(true)) return WeaponAttackResult.WEAPON_COSTS;
// Check for Bukkit event
UntargetedWeaponUseEvent called = new UntargetedWeaponUseEvent(playerData, this);
Bukkit.getPluginManager().callEvent(called);
if (called.isCancelled()) return WeaponAttackResult.BUKKIT_EVENT;
// Attack is ready to be performed.
// Apply weapon costs
final double attackDelay = 1 / requireNonZero(stats.getStat("ATTACK_SPEED"), MMOItems.plugin.getConfig().getDouble("default.attack-speed"));
applyWeaponCosts(attackDelay);
// Apply weapon attack effect
handler.whenCast(result, meta);
// Apply durability loss
if (durItem.isValid()) durItem.decreaseDurability(1).inventoryUpdate();
return WeaponAttackResult.SUCCESS;
}
@Deprecated
private WeaponAttackResult legacyHandleUntargetedAttack(boolean rightClick, @NotNull EquipmentSlot actionHand) {
// Check for attack effect conditions
if (((LegacyWeapon) this).canAttack(rightClick, actionHand)) return WeaponAttackResult.NO_ATTACK;
// Check for durability
UntargetedDurabilityItem durItem = new UntargetedDurabilityItem(getPlayer(), getNBTItem(), actionHand);
if (durItem.isBroken()) return WeaponAttackResult.DURABILITY;
// Apply weapon instantaneous costs
PlayerMetadata stats = getPlayerData().getStats().newTemporary(actionHand);
if (!checkWeaponCosts(true)) return WeaponAttackResult.WEAPON_COSTS;
// Check for Bukkit event
UntargetedWeaponUseEvent called = new UntargetedWeaponUseEvent(playerData, this);
Bukkit.getPluginManager().callEvent(called);
if (called.isCancelled()) return WeaponAttackResult.BUKKIT_EVENT;
// Attack is ready to be performed.
// Apply weapon costs
final double attackDelay = 1 / requireNonZero(stats.getStat("ATTACK_SPEED"), MMOItems.plugin.getConfig().getDouble("default.attack-speed"));
applyWeaponCosts(attackDelay);
// Apply weapon attack effect
final PlayerMetadata caster = playerData.getMMOPlayerData().getStatMap().cache(actionHand);
((LegacyWeapon) this).applyAttackEffect(caster, actionHand);
// Apply durability loss
if (durItem.isValid()) durItem.decreaseDurability(1).inventoryUpdate();
return WeaponAttackResult.SUCCESS;
}
protected Location getGround(Location loc) {
for (int j = 0; j < 20; j++) {
if (loc.getBlock().getType().isSolid())
return loc;
if (loc.getBlock().getType().isSolid()) return loc;
loc.add(0, -1, 0);
}
return loc;
@ -125,7 +244,7 @@ public class Weapon extends UseItem {
/**
* @return First argument, or second if zero or lower
*/
public double requireNonZero(double number, double elseNumber) {
protected double requireNonZero(double number, double elseNumber) {
return number <= 0 ? elseNumber : number;
}
}

View File

@ -1,52 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.skill.trigger.TriggerType;
import io.lumine.mythic.lib.util.CustomProjectile;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.CustomSound;
import net.Indyuce.mmoitems.listener.CustomSoundListener;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class Crossbow extends UntargetedWeapon {
private boolean consumesArrow;
public Crossbow(Player player, NBTItem item) {
super(player, item, UntargetedWeaponType.RIGHT_CLICK);
}
@Override
public boolean canAttack(EquipmentSlot slot) {
consumesArrow = !getNBTItem().getBoolean("MMOITEMS_DISABLE_ARROW_CONSUMPTION");
return player.getGameMode() == GameMode.CREATIVE || !consumesArrow || getPlayer().getInventory().containsAtLeast(new ItemStack(Material.ARROW), 1);
}
@Override
public void applyAttackEffect(PlayerMetadata stats, EquipmentSlot slot) {
// Consume arrow
if (getPlayer().getGameMode() != GameMode.CREATIVE && consumesArrow)
getPlayer().getInventory().removeItem(new ItemStack(Material.ARROW));
final Arrow arrow = getPlayer().launchProjectile(Arrow.class);
arrow.setVelocity(getPlayer().getEyeLocation().getDirection().multiply(3 * requireNonZero(stats.getStat("ARROW_VELOCITY"), 1)));
getPlayer().setVelocity(getPlayer().getVelocity().setX(0).setZ(0));
// Play custom sound
CustomSoundListener.playSound(getNBTItem().getItem(), CustomSound.ON_CROSSBOW, player, Sound.ENTITY_ARROW_SHOOT);
// Register custom projectile
MMOItems.plugin.getEntities().registerCustomProjectile(getNBTItem(), stats, arrow, 1);
// Trigger abilities
stats.getData().triggerSkills(TriggerType.SHOOT_BOW, slot, arrow);
new CustomProjectile(stats.getData(), CustomProjectile.ProjectileType.ARROW, arrow, slot);
}
}

View File

@ -0,0 +1,17 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.player.PlayerMetadata;
/**
* These weapon types need to be adapted to raw YAML scripts.
*/
@Deprecated
public interface LegacyWeapon {
@Deprecated
boolean canAttack(boolean rightClick, EquipmentSlot slot);
@Deprecated
void applyAttackEffect(PlayerMetadata stats, EquipmentSlot slot);
}

View File

@ -8,6 +8,8 @@ import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.version.VersionSound;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.SoundReader;
import net.Indyuce.mmoitems.stat.LuteAttackEffectStat.LuteAttackEffect;
import net.Indyuce.mmoitems.stat.data.ProjectileParticlesData;
@ -23,14 +25,20 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.List;
public class Lute extends UntargetedWeapon {
@Deprecated
public class Lute extends Weapon implements LegacyWeapon {
@Deprecated
public Lute(Player player, NBTItem item) {
super(player, item, UntargetedWeaponType.RIGHT_CLICK);
super(player, item);
}
public Lute(PlayerData player, NBTItem item) {
super(player, item);
}
@Override
public boolean canAttack(EquipmentSlot slot) {
return true;
public boolean canAttack(boolean rightClick, EquipmentSlot slot) {
return rightClick;
}
@Override
@ -40,8 +48,7 @@ public class Lute extends UntargetedWeapon {
final Vector weight = new Vector(0, -.003 * stats.getStat("NOTE_WEIGHT"), 0);
final @Nullable LuteAttackEffect effect = LuteAttackEffect.get(getNBTItem());
@Deprecated
final SoundReader sound = new SoundReader(getNBTItem().getString("MMOITEMS_LUTE_ATTACK_SOUND"), VersionSound.BLOCK_NOTE_BLOCK_BELL.toSound());
@Deprecated final SoundReader sound = new SoundReader(getNBTItem().getString("MMOITEMS_LUTE_ATTACK_SOUND"), VersionSound.BLOCK_NOTE_BLOCK_BELL.toSound());
final @NotNull ProjectileParticlesData projParticle = getNBTItem().hasTag("MMOITEMS_PROJECTILE_PARTICLES") ?
new ProjectileParticlesData(getNBTItem().getString("MMOITEMS_PROJECTILE_PARTICLES")) : ProjectileParticlesData.DEFAULT;

View File

@ -7,21 +7,28 @@ import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.util.RayTrace;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
public class Musket extends UntargetedWeapon {
@Deprecated
public class Musket extends Weapon implements LegacyWeapon {
@Deprecated
public Musket(Player player, NBTItem item) {
super(player, item, UntargetedWeaponType.RIGHT_CLICK);
super(player, item);
}
public Musket(PlayerData player, NBTItem item) {
super(player, item);
}
@Override
public boolean canAttack(EquipmentSlot slot) {
return true;
public boolean canAttack(boolean rightClick, EquipmentSlot slot) {
return rightClick;
}
@Override

View File

@ -1,72 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.util.RayTrace;
import io.lumine.mythic.lib.version.VersionSound;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.player.PlayerData.CooldownType;
import net.Indyuce.mmoitems.stat.StaffSpiritStat.StaffSpirit;
import org.bukkit.EntityEffect;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class Staff extends UntargetedWeapon {
public Staff(Player player, NBTItem item) {
super(player, item, UntargetedWeaponType.LEFT_CLICK);
}
@Override
public boolean canAttack(EquipmentSlot slot) {
return true;
}
@Override
public void applyAttackEffect(PlayerMetadata stats, EquipmentSlot slot) {
double attackDamage = requireNonZero(stats.getStat("ATTACK_DAMAGE"), 1);
double range = requireNonZero(stats.getStat("RANGE"), MMOItems.plugin.getConfig().getDouble("default.range"));
StaffSpirit spirit = StaffSpirit.get(getNBTItem());
if (spirit != null) {
spirit.getAttack().handle(stats, attackDamage, getNBTItem(), slot, range);
return;
}
RayTrace trace = new RayTrace(stats.getPlayer(), slot, range, entity -> UtilityMethods.canTarget(stats.getPlayer(), entity, InteractionType.OFFENSE_ACTION));
if (trace.hasHit())
stats.attack(trace.getHit(), attackDamage, DamageType.WEAPON, DamageType.MAGIC, DamageType.PROJECTILE);
trace.draw(.5, tick -> tick.getWorld().spawnParticle(Particle.EXPLOSION_NORMAL, tick, 0, .1, .1, .1, 0));
getPlayer().getWorld().playSound(getPlayer().getLocation(), VersionSound.ENTITY_FIREWORK_ROCKET_TWINKLE.toSound(), 2, 2);
}
public void specialAttack(LivingEntity target) {
if (!MMOItems.plugin.getConfig().getBoolean("item-ability.staff.enabled"))
return;
if (!checkWeaponCosts(CooldownType.SPECIAL_ATTACK))
return;
applyWeaponCosts(MMOItems.plugin.getConfig().getDouble("item-ability.staff.cooldown"), CooldownType.SPECIAL_ATTACK);
double power = MMOItems.plugin.getConfig().getDouble("item-ability.staff.power");
try {
Vector vec = target.getLocation().toVector().subtract(getPlayer().getLocation().toVector()).setY(0).normalize().multiply(1.75 * power).setY(.65 * power);
target.getWorld().spawnParticle(Particle.EXPLOSION_LARGE, target.getLocation().add(0, 1, 0), 0);
target.getWorld().spawnParticle(Particle.EXPLOSION_NORMAL, target.getLocation().add(0, 1, 0), 16, 0, 0, 0, .1);
target.setVelocity(vec);
target.playEffect(EntityEffect.HURT);
target.getWorld().playSound(target.getLocation(), Sound.BLOCK_ANVIL_LAND, 1, 2);
} catch (IllegalArgumentException ignored) {
}
}
}

View File

@ -1,78 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.player.PlayerMetadata;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.event.item.UntargetedWeaponUseEvent;
import net.Indyuce.mmoitems.api.interaction.util.UntargetedDurabilityItem;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public abstract class UntargetedWeapon extends Weapon {
protected final UntargetedWeaponType weaponType;
public UntargetedWeapon(Player player, NBTItem item, UntargetedWeaponType weaponType) {
super(player, item);
this.weaponType = weaponType;
}
/**
* Called when the player interacts with the item. This method is used to
* apply durability and cast the weapon attack
*
* @param slot Slot being interacted with
* @implNote Since MI 6.7.3 this method handles custom durability, cooldown
* checks and player stat snapshots.
*/
public void handleTargetFreeAttack(EquipmentSlot slot) {
if (!canAttack(slot))
return;
// Check for durability
UntargetedDurabilityItem durItem = new UntargetedDurabilityItem(getPlayer(), getNBTItem(), slot);
if (durItem.isBroken())
return;
// Apply weapon mana/stamina costs and cooldown
PlayerMetadata stats = getPlayerData().getStats().newTemporary(slot);
if (!checkWeaponCosts(PlayerData.CooldownType.BASIC_ATTACK))
return;
// Check for Bukkit event
UntargetedWeaponUseEvent called = new UntargetedWeaponUseEvent(playerData, this);
Bukkit.getPluginManager().callEvent(called);
if (called.isCancelled())
return;
// Apply weapon costs
double attackDelay = 1 / requireNonZero(stats.getStat("ATTACK_SPEED"), MMOItems.plugin.getConfig().getDouble("default.attack-speed"));
applyWeaponCosts(attackDelay, PlayerData.CooldownType.BASIC_ATTACK);
// Specific weapon attack effect
applyAttackEffect(stats, slot);
// Apply durability loss
if (durItem.isValid())
durItem.decreaseDurability(1).inventoryUpdate();
}
/**
* Used for instance by the Crossbow item type to apply
* ammo requirements. This is the first ever condition checked
* when trying to perform an untargeted attack
*
* @param slot Slot used to attack
* @return Extra attack condition, specific to the untargeted weapon type
*/
public abstract boolean canAttack(EquipmentSlot slot);
public abstract void applyAttackEffect(PlayerMetadata stats, EquipmentSlot slot);
public UntargetedWeaponType getWeaponType() {
return weaponType;
}
}

View File

@ -1,22 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted;
import org.bukkit.event.block.Action;
import org.jetbrains.annotations.NotNull;
/**
* Used to determine if the item must be left or right clicked in order to
* cast a basic attack. Whips, staffs are left click weapons whereas muskets
* are right click weapons
*
* @author cympe
*/
public enum UntargetedWeaponType {
RIGHT_CLICK,
LEFT_CLICK;
public boolean corresponds(@NotNull Action action) {
return this == RIGHT_CLICK ?
action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK :
action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK;
}
}

View File

@ -1,38 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.util.RayTrace;
import io.lumine.mythic.lib.version.VersionSound;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import org.bukkit.Particle;
import org.bukkit.entity.Player;
public class Whip extends UntargetedWeapon {
public Whip(Player player, NBTItem item) {
super(player, item, UntargetedWeaponType.LEFT_CLICK);
}
@Override
public boolean canAttack(EquipmentSlot slot) {
return true;
}
@Override
public void applyAttackEffect(PlayerMetadata stats, EquipmentSlot slot) {
double attackDamage = requireNonZero(stats.getStat("ATTACK_DAMAGE"), 7);
double range = requireNonZero(getNBTItem().getStat(ItemStats.RANGE.getId()), MMOItems.plugin.getConfig().getDouble("default.range"));
RayTrace trace = new RayTrace(getPlayer(), slot, range, entity -> UtilityMethods.canTarget(stats.getPlayer(), entity, InteractionType.OFFENSE_ACTION));
if (trace.hasHit())
stats.attack(trace.getHit(), attackDamage, DamageType.WEAPON, DamageType.PROJECTILE, DamageType.PHYSICAL);
trace.draw(.5, tick -> tick.getWorld().spawnParticle(Particle.CRIT, tick, 0, .1, .1, .1, 0));
getPlayer().getWorld().playSound(getPlayer().getLocation(), VersionSound.ENTITY_FIREWORK_ROCKET_BLAST.toSound(), 1, 2);
}
}

View File

@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
@Deprecated
public class BruteLuteAttack implements LuteAttackHandler {
@Override

View File

@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
@Deprecated
public class CircularLuteAttack implements LuteAttackHandler {
@Override

View File

@ -9,6 +9,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Random;
@Deprecated
public interface LuteAttackHandler {
static final Random RANDOM = new Random();

View File

@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
@Deprecated
public class SimpleLuteAttack implements LuteAttackHandler {
@Override

View File

@ -16,6 +16,7 @@ import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
@Deprecated
public class SlashLuteAttack implements LuteAttackHandler {
@Override

View File

@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
@Deprecated
public class WaveLuteAttack implements LuteAttackHandler {
@Override

View File

@ -1,25 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.util.RayTrace;
import io.lumine.mythic.lib.version.VersionSound;
import org.bukkit.Particle;
public class LightningSpirit implements StaffAttackHandler {
@Override
public void handle(PlayerMetadata caster, double damage, NBTItem nbt, EquipmentSlot slot, double range) {
caster.getPlayer().getWorld().playSound(caster.getPlayer().getLocation(), VersionSound.ENTITY_FIREWORK_ROCKET_BLAST.toSound(), 2, 2);
RayTrace trace = new RayTrace(caster.getPlayer(), slot, range, entity -> UtilityMethods.canTarget(caster.getPlayer(), entity, InteractionType.OFFENSE_ACTION));
if (trace.hasHit())
caster.attack(trace.getHit(), damage, DamageType.WEAPON, DamageType.MAGIC, DamageType.PROJECTILE);
trace.draw(.5, loc1 -> loc1.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, loc1, 0));
}
}

View File

@ -1,62 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.util.MMOUtils;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.List;
public class ManaSpirit implements StaffAttackHandler {
@Override
public void handle(PlayerMetadata caster, double damage, NBTItem nbt, EquipmentSlot slot, double range) {
new BukkitRunnable() {
final Vector vec = caster.getPlayer().getEyeLocation().getDirection().multiply(.4);
final Location loc = caster.getPlayer().getEyeLocation();
int ti = 0;
final double r = .2;
public void run() {
if (ti++ > range)
cancel();
if (ti % 2 == 0)
loc.getWorld().playSound(loc, Sound.BLOCK_SNOW_BREAK, 2, 2);
List<Entity> targets = MMOUtils.getNearbyChunkEntities(loc);
for (int j = 0; j < 3; j++) {
loc.add(vec);
if (loc.getBlock().getType().isSolid()) {
cancel();
break;
}
for (double item = 0; item < Math.PI * 2; item += Math.PI / 3.5) {
Vector vec = MMOUtils.rotateFunc(new Vector(r * Math.cos(item), r * Math.sin(item), 0), loc);
if (RANDOM.nextDouble() <= .6)
loc.getWorld().spawnParticle(Particle.REDSTONE, loc.clone().add(vec), 1, new Particle.DustOptions(Color.AQUA, 1));
}
for (Entity target : targets)
if (UtilityMethods.canTarget(caster.getPlayer(), loc, target, InteractionType.OFFENSE_ACTION)) {
caster.attack((LivingEntity) target, damage, DamageType.WEAPON, DamageType.MAGIC, DamageType.PROJECTILE);
loc.getWorld().spawnParticle(Particle.EXPLOSION_LARGE, loc, 0);
cancel();
return;
}
}
}
}.runTaskTimer(MMOItems.plugin, 0, 1);
}
}

View File

@ -1,56 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.util.MMOUtils;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.List;
public class NetherSpirit implements StaffAttackHandler {
@Override
public void handle(PlayerMetadata caster, double damage, NBTItem nbt, EquipmentSlot slot, double range) {
new BukkitRunnable() {
final Vector vec = caster.getPlayer().getEyeLocation().getDirection().multiply(.3);
final Location loc = caster.getPlayer().getEyeLocation();
int ti = 0;
public void run() {
if (ti++ % 2 == 0)
loc.getWorld().playSound(loc, Sound.BLOCK_FIRE_AMBIENT, 2, 2);
List<Entity> targets = MMOUtils.getNearbyChunkEntities(loc);
for (int j = 0; j < 3; j++) {
loc.add(vec);
if (loc.getBlock().getType().isSolid()) {
cancel();
break;
}
loc.getWorld().spawnParticle(Particle.FLAME, loc, 2, .07, .07, .07, 0);
loc.getWorld().spawnParticle(Particle.SMOKE_NORMAL, loc, 0);
for (Entity target : targets)
if (UtilityMethods.canTarget(caster.getPlayer(), loc, target, InteractionType.OFFENSE_ACTION)) {
caster.attack((LivingEntity) target, damage, DamageType.WEAPON, DamageType.MAGIC, DamageType.PROJECTILE);
loc.getWorld().spawnParticle(Particle.EXPLOSION_LARGE, loc, 0);
cancel();
return;
}
}
if (ti >= range)
cancel();
}
}.runTaskTimer(MMOItems.plugin, 0, 1);
}
}

View File

@ -1,23 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.player.PlayerMetadata;
import org.bukkit.Location;
import java.util.Random;
public interface StaffAttackHandler {
static final Random RANDOM = new Random();
void handle(PlayerMetadata caster, double damage, NBTItem nbt, EquipmentSlot slot, double range);
default Location getGround(Location loc) {
for (int j = 0; j < 20; j++) {
if (loc.getBlock().getType().isSolid())
return loc;
loc.add(0, -1, 0);
}
return loc;
}
}

View File

@ -1,59 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.version.VersionSound;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.util.MMOUtils;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
public class SunfireSpirit implements StaffAttackHandler {
@Override
public void handle(PlayerMetadata caster, double damage, NBTItem nbt, EquipmentSlot slot, double range) {
caster.getPlayer().getWorld().playSound(caster.getPlayer().getLocation(), Sound.ENTITY_WITHER_SHOOT, 2, 2);
new BukkitRunnable() {
final Location target = getGround(caster.getPlayer().getTargetBlock(null, (int) range * 2).getLocation()).add(0, 1.2, 0);
final double a = RANDOM.nextDouble() * Math.PI * 2;
final double dt = .02;
final Location loc = target.clone().add(Math.cos(a) * 4, 10, Math.sin(a) * 4);
final Vector vec = target.toVector().subtract(loc.toVector()).multiply(dt);
double ti = 0;
int counter;
public void run() {
if (counter++ % 2 == 0)
loc.getWorld().playSound(loc, Sound.BLOCK_FIRE_AMBIENT, 2, 2);
for (int j = 0; j < 3; j++) {
ti += dt;
loc.add(vec);
loc.getWorld().spawnParticle(Particle.FLAME, loc, 0, .03, 0, .03, 0);
// Projectile explode
if (ti >= 1) {
loc.getWorld().spawnParticle(Particle.FLAME, loc, 24, 0, 0, 0, .12);
loc.getWorld().spawnParticle(Particle.SMOKE_NORMAL, loc, 24, 0, 0, 0, .12);
loc.getWorld().spawnParticle(Particle.EXPLOSION_LARGE, loc, 0);
loc.getWorld().playSound(loc, VersionSound.ENTITY_FIREWORK_ROCKET_BLAST.toSound(), 2, 2);
for (Entity target : MMOUtils.getNearbyChunkEntities(loc))
if (UtilityMethods.canTarget(caster.getPlayer(), target, InteractionType.OFFENSE_ACTION) && target.getLocation().distanceSquared(loc) <= 9)
caster.attack((LivingEntity) target, damage, DamageType.WEAPON, DamageType.MAGIC, DamageType.PROJECTILE);
cancel();
return;
}
}
}
}.runTaskTimer(MMOItems.plugin, 0, 1);
}
}

View File

@ -1,57 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.version.VersionSound;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.util.MMOUtils;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
public class ThunderSpirit implements StaffAttackHandler {
@Override
public void handle(PlayerMetadata caster, double damage, NBTItem nbt, EquipmentSlot slot, double range) {
caster.getPlayer().getWorld().playSound(caster.getPlayer().getLocation(), Sound.ENTITY_WITHER_SHOOT, 2, 2);
new BukkitRunnable() {
final Location target = getGround(caster.getPlayer().getTargetBlock(null, (int) range * 2).getLocation()).add(0, 1.2, 0);
final double a = RANDOM.nextDouble() * Math.PI * 2;
final double dt = .02;
double ti = 0;
final Location loc = target.clone().add(Math.cos(a) * 4, 10, Math.sin(a) * 4);
final Vector vec = target.toVector().subtract(loc.toVector()).multiply(dt);
int counter;
public void run() {
if (counter++ % 2 == 0)
loc.getWorld().playSound(loc, VersionSound.BLOCK_NOTE_BLOCK_HAT.toSound(), 2, 2);
for (int j = 0; j < 3; j++) {
ti += dt;
loc.add(vec);
loc.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, loc, 0, .03, 0, .03, 0);
// Projectile explode
if (ti >= 1) {
loc.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, loc, 24, 0, 0, 0, .12);
loc.getWorld().playSound(loc, VersionSound.ENTITY_FIREWORK_ROCKET_BLAST.toSound(), 2, 2);
for (Entity target : MMOUtils.getNearbyChunkEntities(loc))
if (UtilityMethods.canTarget(caster.getPlayer(), target, InteractionType.OFFENSE_ACTION) && target.getLocation().distanceSquared(loc) <= 9)
caster.attack((LivingEntity) target, damage, DamageType.WEAPON, DamageType.MAGIC, DamageType.PROJECTILE);
cancel();
return;
}
}
}
}.runTaskTimer(MMOItems.plugin, 0, 1);
}
}

View File

@ -1,38 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.damage.DamageMetadata;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.skill.ShulkerMissile;
import org.bukkit.Sound;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.ShulkerBullet;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
public class VoidSpirit implements StaffAttackHandler {
@Override
public void handle(PlayerMetadata caster, double damage, NBTItem nbt, EquipmentSlot slot, double range) {
Vector vec = caster.getPlayer().getEyeLocation().getDirection();
caster.getPlayer().getWorld().playSound(caster.getPlayer().getLocation(), Sound.ENTITY_WITHER_SHOOT, 2, 2);
ShulkerBullet shulkerBullet = (ShulkerBullet) caster.getPlayer().getWorld().spawnEntity(caster.getPlayer().getLocation().add(0, 1, 0), EntityType.valueOf("SHULKER_BULLET"));
shulkerBullet.setShooter(caster.getPlayer());
new BukkitRunnable() {
double ti = 0;
public void run() {
ti += .1;
if (shulkerBullet.isDead() || ti >= range / 4) {
shulkerBullet.remove();
cancel();
}
shulkerBullet.setVelocity(vec);
}
}.runTaskTimer(MMOItems.plugin, 0, 1);
MMOItems.plugin.getEntities().registerCustomEntity(shulkerBullet, new ShulkerMissile.ShulkerMissileEntityData(caster, new DamageMetadata(damage, DamageType.WEAPON, DamageType.MAGIC, DamageType.PROJECTILE), 0, nbt));
}
}

View File

@ -1,25 +0,0 @@
package net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.util.RayTrace;
import org.bukkit.Color;
import org.bukkit.Sound;
public class XRaySpirit implements StaffAttackHandler {
@Override
public void handle(PlayerMetadata caster, double damage, NBTItem nbt, EquipmentSlot slot, double range) {
caster.getPlayer().getWorld().playSound(caster.getPlayer().getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, 2, 2);
RayTrace trace = new RayTrace(caster.getPlayer(), slot, range, entity -> UtilityMethods.canTarget(caster.getPlayer(), entity, InteractionType.OFFENSE_ACTION));
if (trace.hasHit())
caster.attack(trace.getHit(), damage, DamageType.WEAPON, DamageType.MAGIC, DamageType.PROJECTILE);
trace.draw(.5, Color.BLACK);
caster.getPlayer().getWorld().playSound(caster.getPlayer().getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 0.40f, 2);
}
}

View File

@ -1,8 +1,10 @@
package net.Indyuce.mmoitems.api.item.template;
import io.lumine.mythic.lib.api.util.PostLoadObject;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.util.PostLoadAction;
import io.lumine.mythic.lib.util.PreloadedObject;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.Type;
@ -25,7 +27,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class MMOItemTemplate extends PostLoadObject implements ItemReference {
public class MMOItemTemplate implements ItemReference, PreloadedObject {
private final Type type;
private final String id;
private final int revId;
@ -42,40 +44,7 @@ public class MMOItemTemplate extends PostLoadObject implements ItemReference {
private ModifierGroup modifierGroup;
private final Set<TemplateOption> options = new HashSet<>();
/**
* Public constructor which can be used to register extra item templates
* using other addons or plugins
*
* @param type The item type of your template
* @param id The template identifier, it's ok if two templates with
* different item types share the same ID
*/
public MMOItemTemplate(Type type, String id) {
super(null);
this.type = type;
this.id = id;
this.revId = 1;
}
/**
* Used to load MMOItem templates from config files
*
* @param type The item type of your template
* @param config The config file read to load the template
*/
public MMOItemTemplate(Type type, ConfigurationSection config) {
super(config);
Validate.notNull(config, "Could not load template config");
this.type = type;
this.id = config.getName().toUpperCase().replace("-", "_").replace(" ", "_");
this.revId = config.getInt("base.revision-id", 1);
}
@Override
protected void whenPostLoaded(ConfigurationSection config) {
private final PostLoadAction postLoadAction = new PostLoadAction(config -> {
FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffp.activatePrefix(true, getType().getId() + " " + getId());
@ -95,7 +64,7 @@ public class MMOItemTemplate extends PostLoadObject implements ItemReference {
// Read modifiers
try {
modifierGroup = config.contains("modifiers") ? new ModifierGroup(id, config) : null;
modifierGroup = config.contains("modifiers") ? new ModifierGroup(getId(), config) : null;
if (modifierGroup != null) modifierGroup.getPostLoadAction().performAction();
} catch (Exception exception) {
ffp.log(FriendlyFeedbackCategory.ERROR, "Could not load modifier group: {0}", exception.getMessage());
@ -105,10 +74,9 @@ public class MMOItemTemplate extends PostLoadObject implements ItemReference {
Validate.notNull(config.getConfigurationSection("base"), FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Could not find base item data"));
for (String key : config.getConfigurationSection("base").getKeys(false))
try {
String id = key.toUpperCase().replace("-", "_");
Validate.isTrue(MMOItems.plugin.getStats().has(id), FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Could not find stat with ID '$i{0}$b'", id));
ItemStat stat = MMOItems.plugin.getStats().get(id);
final String id = UtilityMethods.enumName(key);
final ItemStat stat = MMOItems.plugin.getStats().get(id);
Validate.notNull(stat, FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Could not find stat with ID '$i{0}$b'", id));
RandomStatData data = stat.whenInitialized(config.get("base." + key));
if (data != null)
base.put(stat, data);
@ -121,6 +89,42 @@ public class MMOItemTemplate extends PostLoadObject implements ItemReference {
// Print all failures
ffp.sendTo(FriendlyFeedbackCategory.INFORMATION, MMOItems.getConsole());
});
/**
* Public constructor which can be used to register extra item templates
* using other addons or plugins
*
* @param type The item type of your template
* @param id The template identifier, it's ok if two templates with
* different item types share the same ID
*/
public MMOItemTemplate(Type type, String id) {
this.type = type;
this.id = id;
this.revId = 1;
}
/**
* Used to load MMOItem templates from config files
*
* @param type The item type of your template
* @param config The config file read to load the template
*/
public MMOItemTemplate(Type type, ConfigurationSection config) {
Validate.notNull(config, "Could not load template config");
postLoadAction.cacheConfig(config);
this.type = type;
this.id = config.getName().toUpperCase().replace("-", "_").replace(" ", "_");
this.revId = config.getInt("base.revision-id", 1);
}
@NotNull
@Override
public PostLoadAction getPostLoadAction() {
return postLoadAction;
}
@NotNull
@ -281,8 +285,8 @@ public class MMOItemTemplate extends PostLoadObject implements ItemReference {
/**
* @return Attempts to get the crafted amount registered in the Stat.
* <p></p>
* Default is <b>1</b> obviously.
* <p></p>
* Default is <b>1</b> obviously.
* @deprecated Don't use this method, the Crafted Amount Stat will be deleted in the near future.
*/
@Deprecated

View File

@ -1,7 +1,7 @@
package net.Indyuce.mmoitems.api.item.template;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.util.PostLoadAction;
import io.lumine.mythic.lib.util.PreloadedObject;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.build.MMOItemBuilder;
import net.Indyuce.mmoitems.stat.data.random.RandomStatData;
@ -22,10 +22,9 @@ public class TemplateModifier extends ModifierNode {
Validate.notNull(config.getConfigurationSection("stats"), "Could not find base item data");
for (String key : config.getConfigurationSection("stats").getKeys(false))
try {
String id = key.toUpperCase().replace("-", "_");
Validate.isTrue(MMOItems.plugin.getStats().has(id), "Could not find stat with ID '" + id + "'");
ItemStat stat = MMOItems.plugin.getStats().get(id);
final String statId = UtilityMethods.enumName(key);
final ItemStat stat = MMOItems.plugin.getStats().get(statId);
Validate.notNull(stat, "Could not find stat with ID '" + statId + "'");
TemplateModifier.this.data.put(stat, stat.whenInitialized(config.get("stats." + key)));
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.INFO, "An error occurred while trying to load modifier node " + getId() + ": " + exception.getMessage());

View File

@ -408,11 +408,6 @@ public class PlayerData extends SynchronizedDataHolder implements Closeable {
public enum CooldownType {
/**
* Basic attack cooldown like staffs and lutes
*/
BASIC_ATTACK,
/**
* Elemental attacks cooldown
*/
@ -431,12 +426,6 @@ public class PlayerData extends SynchronizedDataHolder implements Closeable {
*
* @see {@link Tool#miningEffects(Block)}
*/
BOUNCING_CRACK,
/**
* Special item set attack effects including slashing, piercing and
* blunt attack effects
*/
SET_TYPE_ATTACK
BOUNCING_CRACK;
}
}

View File

@ -59,7 +59,7 @@ public class ReloadCommandTreeNode extends CommandTreeNode {
MMOItems.plugin.getLanguage().reload();
MMOItems.plugin.getDropTables().reload();
MMOItems.plugin.getTypes().reload();
MMOItems.plugin.getTypes().reload(true);
MMOItems.plugin.getTiers().reload();
MMOItems.plugin.getSets().reload();
MMOItems.plugin.getUpgrades().reload();

View File

@ -12,7 +12,6 @@ public class ListCommandTreeNode extends CommandTreeNode {
addChild(new AbilityCommandTreeNode(this));
addChild(new LuteAttackCommandTreeNode(this));
addChild(new StaffSpiritCommandTreeNode(this));
addChild(new TypeCommandTreeNode(this));
}
@ -21,7 +20,6 @@ public class ListCommandTreeNode extends CommandTreeNode {
sender.sendMessage(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "-----------------[" + ChatColor.LIGHT_PURPLE + " MMOItems: lists "
+ ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "]-----------------");
sender.sendMessage(ChatColor.LIGHT_PURPLE + "/mi list type " + ChatColor.WHITE + "shows all item types (sword, axe...)");
sender.sendMessage(ChatColor.LIGHT_PURPLE + "/mi list spirit " + ChatColor.WHITE + "shows all available staff spirits");
sender.sendMessage(ChatColor.LIGHT_PURPLE + "/mi list lute " + ChatColor.WHITE + "shows all available lute attack effects");
sender.sendMessage(ChatColor.LIGHT_PURPLE + "/mi list ability " + ChatColor.WHITE + "shows all available abilities");
if (sender instanceof Player) {

View File

@ -1,24 +0,0 @@
package net.Indyuce.mmoitems.command.mmoitems.list;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import net.Indyuce.mmoitems.stat.StaffSpiritStat.StaffSpirit;
import io.lumine.mythic.lib.command.api.CommandTreeNode;
public class StaffSpiritCommandTreeNode extends CommandTreeNode {
public StaffSpiritCommandTreeNode(CommandTreeNode parent) {
super(parent, "spirit");
}
@Override
public CommandResult execute(CommandSender sender, String[] args) {
sender.sendMessage(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "-----------------[" + ChatColor.LIGHT_PURPLE + " Staff Spirits "
+ ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "]-----------------");
for (StaffSpirit ss : StaffSpirit.values()) {
String lore = ss.hasLore() ? " " + ChatColor.WHITE + ">> " + ChatColor.GRAY + "" + ChatColor.ITALIC + ss.getDefaultLore() : "";
sender.sendMessage("* " + ChatColor.LIGHT_PURPLE + ss.name() + lore);
}
return CommandResult.SUCCESS;
}
}

View File

@ -7,7 +7,7 @@ import io.lumine.mythic.bukkit.events.MythicReloadedEvent;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.comp.mythicmobs.mechanics.MMOItemsArrowVolleyMechanic;
import net.Indyuce.mmoitems.comp.mythicmobs.mechanics.MMOItemsOnShootAura;
import net.Indyuce.mmoitems.comp.mythicmobs.mechanics.MMOItemsOnUseAura;
import net.Indyuce.mmoitems.comp.mythicmobs.stat.FactionDamage;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -34,17 +34,22 @@ public class MythicMobsCompatibility implements Listener {
Bukkit.getPluginManager().registerEvents(this, MMOItems.plugin);
}
@EventHandler(priority = EventPriority.HIGH)
public void b(MythicMechanicLoadEvent event) {
// Switch Mechanic ig
switch (event.getMechanicName().toLowerCase()) {
case "mmoitemsvolley":
event.register(new MMOItemsArrowVolleyMechanic(event.getContainer().getManager(), event.getContainer().getFile(), event.getContainer().getConfigLine(), event.getConfig()));
event.register(new MMOItemsArrowVolleyMechanic(event.getContainer().getManager(),
event.getContainer().getFile(),
event.getConfig().getLine(),
event.getConfig()));
break;
case "onmmoitemuse":
event.register(new MMOItemsOnShootAura(event.getContainer().getManager(), event.getContainer().getFile(), event.getContainer().getConfigLine(), event.getConfig()));
event.register(new MMOItemsOnUseAura(event.getContainer().getManager(),
event.getContainer().getFile(),
event.getConfig().getLine(),
event.getConfig()));
break;
default:
break;

View File

@ -12,7 +12,6 @@ import io.lumine.mythic.core.skills.auras.Aura;
import io.lumine.mythic.bukkit.utils.Events;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.event.item.SpecialWeaponAttackEvent;
import net.Indyuce.mmoitems.api.interaction.weapon.Gauntlet;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.*;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
@ -31,7 +30,7 @@ import java.util.Optional;
*
* @author Gunging
*/
public class MMOItemsOnShootAura extends Aura implements ITargetedEntitySkill {
public class MMOItemsOnUseAura extends Aura implements ITargetedEntitySkill {
@NotNull
PlaceholderString skillName;
@NotNull
@ -44,7 +43,7 @@ public class MMOItemsOnShootAura extends Aura implements ITargetedEntitySkill {
@NotNull
final ArrayList<UseItemTypes> auraWeapons = new ArrayList<>();
public MMOItemsOnShootAura(SkillExecutor manager, File file, String line, MythicLineConfig mlc) {
public MMOItemsOnUseAura(SkillExecutor manager, File file, String line, MythicLineConfig mlc) {
super(manager, file, line, mlc);
skillName = mlc.getPlaceholderString(new String[]{"skill", "s", "ondamagedskill", "ondamaged", "od", "onhitskill", "onhit", "oh", "meta", "m", "mechanics", "$", "()"}, "skill not found");
@ -109,7 +108,7 @@ public class MMOItemsOnShootAura extends Aura implements ITargetedEntitySkill {
caster = new GenericCaster(target);
}
new MMOItemsOnShootAura.Tracker(caster, data, target);
new MMOItemsOnUseAura.Tracker(caster, data, target);
return SkillResult.SUCCESS;
}
@ -169,7 +168,7 @@ public class MMOItemsOnShootAura extends Aura implements ITargetedEntitySkill {
}
}));
this.executeAuraSkill(MMOItemsOnShootAura.this.onStartSkill, this.skillMetadata);
this.executeAuraSkill(MMOItemsOnUseAura.this.onStartSkill, this.skillMetadata);
}
}
@ -223,12 +222,13 @@ public class MMOItemsOnShootAura extends Aura implements ITargetedEntitySkill {
}
enum UseItemTypes {
CROSSBOW(Crossbow.class),
GAUNTLET(Gauntlet.class),
// CROSSBOW(Crossbow.class),
// GAUNTLET(Gauntlet.class),
LUTE(Lute.class),
MUSKET(Musket.class),
STAFF(Staff.class),
WHIP(Whip.class);
// STAFF(Staff.class),
// WHIP(Whip.class);
;
/**
* @return Class to use InstanceOf and identify a weapon.

View File

@ -13,7 +13,6 @@ import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
@ -21,6 +20,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ItemEdition extends EditionInventory {
private static final int[] slots = {19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 31, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43};
@ -45,7 +45,7 @@ public class ItemEdition extends EditionInventory {
* the for loop will just let some slots empty
*/
List<ItemStat> appliable = new ArrayList<>(getEdited().getType().getAvailableStats()).stream()
.filter(stat -> stat.hasValidMaterial(getCachedItem()) && !(stat instanceof InternalStat)).toList();
.filter(stat -> stat.hasValidMaterial(getCachedItem()) && !(stat instanceof InternalStat)).collect(Collectors.toList());
for (int j = min; j < Math.min(appliable.size(), max); j++) {
ItemStat stat = appliable.get(j);
@ -53,7 +53,7 @@ public class ItemEdition extends EditionInventory {
ItemMeta meta = item.getItemMeta();
meta.addItemFlags(ItemFlag.values());
meta.setDisplayName(ChatColor.GREEN + stat.getName());
List<String> lore = MythicLib.plugin.parseColors(Arrays.stream(stat.getLore()).map(s -> ChatColor.GRAY + s).toList());
List<String> lore = MythicLib.plugin.parseColors(Arrays.stream(stat.getLore()).map(s -> ChatColor.GRAY + s).collect(Collectors.toList()));
lore.add("");
stat.whenDisplayed(lore, getEventualStatData(stat));

View File

@ -13,7 +13,6 @@ 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 org.jetbrains.annotations.NotNull;

View File

@ -7,16 +7,17 @@ import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.MeleeAttackMetadata;
import net.Indyuce.mmoitems.MMOItems;
import io.lumine.mythic.lib.entity.ProjectileMetadata;
import io.lumine.mythic.lib.entity.ProjectileType;
import io.lumine.mythic.lib.skill.SimpleSkill;
import io.lumine.mythic.lib.skill.handler.SkillHandler;
import io.lumine.mythic.lib.skill.trigger.TriggerMetadata;
import io.lumine.mythic.lib.skill.trigger.TriggerType;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.TypeSet;
import net.Indyuce.mmoitems.api.event.item.SpecialWeaponAttackEvent;
import net.Indyuce.mmoitems.api.interaction.*;
import net.Indyuce.mmoitems.api.interaction.projectile.ProjectileData;
import net.Indyuce.mmoitems.api.interaction.weapon.Gauntlet;
import net.Indyuce.mmoitems.api.interaction.projectile.ArrowParticles;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.Staff;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.UntargetedWeapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.message.Message;
import org.bukkit.Bukkit;
@ -31,6 +32,7 @@ import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.entity.EntityShootBowEvent;
import org.bukkit.event.inventory.InventoryAction;
@ -48,18 +50,17 @@ public class ItemUse implements Listener {
// || event.getHand() != EquipmentSlot.HAND
return;
NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(event.getItem());
if (!item.hasType())
return;
final NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(event.getItem());
final Type itemType = Type.get(item);
if (itemType == null) return;
/*
* Some consumables must be fully eaten through the vanilla eating
* animation and are handled there {@link #handleVanillaEatenConsumables(PlayerItemConsumeEvent)}
*/
final Player player = event.getPlayer();
final UseItem useItem = UseItem.getItem(player, item, item.getType());
if (useItem instanceof Consumable && ((Consumable) useItem).hasVanillaEating())
return;
final UseItem useItem = UseItem.getItem(player, item, itemType);
if (useItem instanceof Consumable && ((Consumable) useItem).hasVanillaEating()) return;
// (BUG FIX) Cancel the event to prevent things like shield blocking
if (!useItem.checkItemRequirements()) {
@ -68,12 +69,11 @@ public class ItemUse implements Listener {
}
// Commands & consummables
if (event.getAction().name().contains("RIGHT_CLICK")) {
final boolean rightClick = event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK;
if (rightClick) {
if (useItem.getPlayerData().getMMOPlayerData().getCooldownMap().isOnCooldown(useItem.getMMOItem())) {
final double cd = useItem.getPlayerData().getMMOPlayerData().getCooldownMap().getCooldown(useItem.getMMOItem());
Message.ITEM_ON_COOLDOWN
.format(ChatColor.RED, "#left#", MythicLib.plugin.getMMOConfig().decimal.format(cd), "#s#", cd >= 2 ? "s" : "")
.send(player);
Message.ITEM_ON_COOLDOWN.format(ChatColor.RED, "#left#", MythicLib.plugin.getMMOConfig().decimal.format(cd), "#s#", cd >= 2 ? "s" : "").send(player);
event.setUseItemInHand(Event.Result.DENY);
return;
}
@ -81,8 +81,7 @@ public class ItemUse implements Listener {
if (useItem instanceof Consumable) {
event.setUseItemInHand(Event.Result.DENY);
Consumable.ConsumableConsumeResult result = ((Consumable) useItem).useOnPlayer(event.getHand(), false);
if (result == Consumable.ConsumableConsumeResult.CANCEL)
return;
if (result == Consumable.ConsumableConsumeResult.CANCEL) return;
else if (result == Consumable.ConsumableConsumeResult.CONSUME)
event.getItem().setAmount(event.getItem().getAmount() - 1);
@ -92,48 +91,40 @@ public class ItemUse implements Listener {
useItem.executeCommands();
}
// Target free weapon attack
if (useItem instanceof UntargetedWeapon) {
UntargetedWeapon weapon = (UntargetedWeapon) useItem;
if (weapon.getWeaponType().corresponds(event.getAction()))
weapon.handleTargetFreeAttack(EquipmentSlot.fromBukkit(event.getHand()));
}
// Target-free weapon effects
if (useItem instanceof Weapon)
((Weapon) useItem).handleUntargetedAttack(rightClick, EquipmentSlot.fromBukkit(event.getHand()));
}
@EventHandler(ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void meleeAttacks(PlayerAttackEvent event) {
// Checks if it's a melee attack
if (!(event.getAttack() instanceof MeleeAttackMetadata))
// Make sure it's a melee attack
if (!(event.getAttack() instanceof MeleeAttackMetadata)) return;
final Player player = event.getPlayer();
final ItemStack weaponUsed = player.getInventory().getItem(((MeleeAttackMetadata) event.getAttack()).getHand().toBukkit());
final NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(weaponUsed);
final Type itemType = Type.get(item);
if (itemType == null || itemType == Type.BLOCK) return;
// Prevent melee attacks with non-melee weapons
if (!itemType.hasMeleeAttacks()) {
event.setCancelled(true);
return;
/*
* Must apply attack conditions before apply any effects. the event must
* be cancelled before anything is applied
*/
Player player = event.getPlayer();
PlayerData playerData = PlayerData.get(player);
ItemStack weaponUsed = player.getInventory().getItem(((MeleeAttackMetadata) event.getAttack()).getHand().toBukkit());
NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(weaponUsed);
if (item.hasType() && Type.get(item.getType()) != Type.BLOCK) {
Weapon weapon = new Weapon(playerData, item);
if (weapon.getMMOItem().getType().getItemSet() == TypeSet.RANGE) {
event.setCancelled(true);
return;
}
if (!weapon.checkItemRequirements()) {
event.setCancelled(true);
return;
}
if (!weapon.handleTargetedAttack(event.getAttack(), event.getAttacker(), event.getEntity())) {
event.setCancelled(true);
return;
}
}
// Check item requirements
final PlayerData playerData = PlayerData.get(player);
final Weapon weapon = new Weapon(playerData, item);
if (!weapon.checkItemRequirements()) {
event.setCancelled(true);
return;
}
// Apply melee attack
if (!weapon.handleTargetedAttack(event.getAttack(), event.getAttacker(), event.getEntity()))
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
@ -142,12 +133,10 @@ public class ItemUse implements Listener {
final Player player = event.getPlayer();
final Block block = event.getBlock();
if (player.getGameMode() == GameMode.CREATIVE)
return;
if (player.getGameMode() == GameMode.CREATIVE) return;
NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(player.getInventory().getItemInMainHand());
if (!item.hasType())
return;
if (!item.hasType()) return;
Tool tool = new Tool(player, item);
if (!tool.checkItemRequirements()) {
@ -155,92 +144,74 @@ public class ItemUse implements Listener {
return;
}
if (tool.miningEffects(block))
event.setCancelled(true);
if (tool.miningEffects(block)) event.setCancelled(true);
}
@EventHandler
public void rightClickWeaponInteractions(PlayerInteractEntityEvent event) {
Player player = event.getPlayer();
if (!(event.getRightClicked() instanceof LivingEntity))
return;
final Player player = event.getPlayer();
if (!(event.getRightClicked() instanceof LivingEntity)) return;
NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(player.getInventory().getItemInMainHand());
if (!item.hasType())
return;
final NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(player.getInventory().getItem(event.getHand()));
final Type itemType = Type.get(item);
if (itemType == null) return;
LivingEntity target = (LivingEntity) event.getRightClicked();
if (!UtilityMethods.canTarget(player, target, InteractionType.OFFENSE_ACTION))
return;
final LivingEntity target = (LivingEntity) event.getRightClicked();
if (!UtilityMethods.canTarget(player, target, InteractionType.OFFENSE_ACTION)) return;
UseItem weapon = UseItem.getItem(player, item, item.getType());
if (!weapon.checkItemRequirements())
return;
// Check for usability
final UseItem usableItem = UseItem.getItem(player, item, itemType);
if (!usableItem.checkItemRequirements()) return;
// Special staff attack
if (weapon instanceof Staff) {
SpecialWeaponAttackEvent attackEvent = new SpecialWeaponAttackEvent(weapon.getPlayerData(), (Weapon) weapon, target);
Bukkit.getPluginManager().callEvent(attackEvent);
if (!attackEvent.isCancelled())
((Staff) weapon).specialAttack(target);
}
// Special gauntlet attack
if (weapon instanceof Gauntlet) {
SpecialWeaponAttackEvent attackEvent = new SpecialWeaponAttackEvent(weapon.getPlayerData(), (Weapon) weapon, target);
Bukkit.getPluginManager().callEvent(attackEvent);
if (!attackEvent.isCancelled())
((Gauntlet) weapon).specialAttack(target);
// Apply type-specific entity interactions
final SkillHandler onEntityInteract = usableItem.getMMOItem().getType().onEntityInteract();
if (onEntityInteract != null) {
SpecialWeaponAttackEvent called = new SpecialWeaponAttackEvent(usableItem.getPlayerData(), (Weapon) usableItem, target);
Bukkit.getPluginManager().callEvent(called);
if (!called.isCancelled())
new SimpleSkill(onEntityInteract).cast(new TriggerMetadata(usableItem.getPlayerData().getMMOPlayerData(), TriggerType.API, target));
}
}
// TODO: Rewrite this with a custom 'ApplyMMOItemEvent'?
@EventHandler
public void gemStonesAndItemStacks(InventoryClickEvent event) {
Player player = (Player) event.getWhoClicked();
if (event.getAction() != InventoryAction.SWAP_WITH_CURSOR)
return;
final Player player = (Player) event.getWhoClicked();
if (event.getAction() != InventoryAction.SWAP_WITH_CURSOR) return;
NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(event.getCursor());
if (!item.hasType())
return;
final NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(event.getCursor());
final Type type = Type.get(item);
if (type == null) return;
UseItem useItem = UseItem.getItem(player, item, item.getType());
if (!useItem.checkItemRequirements())
return;
final UseItem useItem = UseItem.getItem(player, item, type);
if (!useItem.checkItemRequirements()) return;
if (useItem instanceof ItemSkin) {
NBTItem picked = MythicLib.plugin.getVersion().getWrapper().getNBTItem(event.getCurrentItem());
if (!picked.hasType())
return;
if (!picked.hasType()) return;
ItemSkin.ApplyResult result = ((ItemSkin) useItem).applyOntoItem(picked, Type.get(picked.getType()));
if (result.getType() == ItemSkin.ResultType.NONE)
return;
if (result.getType() == ItemSkin.ResultType.NONE) return;
event.setCancelled(true);
item.getItem().setAmount(item.getItem().getAmount() - 1);
if (result.getType() == ItemSkin.ResultType.FAILURE)
return;
if (result.getType() == ItemSkin.ResultType.FAILURE) return;
event.setCurrentItem(result.getResult());
}
if (useItem instanceof GemStone) {
NBTItem picked = MythicLib.plugin.getVersion().getWrapper().getNBTItem(event.getCurrentItem());
if (!picked.hasType())
return;
if (!picked.hasType()) return;
GemStone.ApplyResult result = ((GemStone) useItem).applyOntoItem(picked, Type.get(picked.getType()));
if (result.getType() == GemStone.ResultType.NONE)
return;
if (result.getType() == GemStone.ResultType.NONE) return;
event.setCancelled(true);
item.getItem().setAmount(item.getItem().getAmount() - 1);
if (result.getType() == GemStone.ResultType.FAILURE)
return;
if (result.getType() == GemStone.ResultType.FAILURE) return;
event.setCurrentItem(result.getResult());
}
@ -253,17 +224,11 @@ public class ItemUse implements Listener {
}
/**
* This handler listens to ALL bow shootings, including both
* custom bows from MMOItems AND vanilla bows, since MMOItems needs to
* apply on-hit effects like crits, elemental damage... even if the
* player is using a vanilla bow.
* <p>
* Fixing commit 4aec1433
* This handler registers arrows from custom MMOItems bows
*/
@EventHandler
public void handleCustomBows(EntityShootBowEvent event) {
if (!(event.getProjectile() instanceof AbstractArrow) || !(event.getEntity() instanceof Player))
return;
if (!(event.getProjectile() instanceof AbstractArrow) || !(event.getEntity() instanceof Player)) return;
final NBTItem item = NBTItem.get(event.getBow());
final Type type = Type.get(item.getType());
@ -279,13 +244,17 @@ public class ItemUse implements Listener {
// Have to get hand manually because 1.15 and below does not have event.getHand()
final ItemStack itemInMainHand = playerData.getPlayer().getInventory().getItemInMainHand();
final EquipmentSlot bowSlot = itemInMainHand.isSimilar(event.getBow()) ? EquipmentSlot.MAIN_HAND : EquipmentSlot.OFF_HAND;
final ProjectileData projData = MMOItems.plugin.getEntities().registerCustomProjectile(item, playerData.getStats().newTemporary(bowSlot), event.getProjectile(), event.getForce());
final ProjectileMetadata proj = ProjectileMetadata.create(playerData.getStats().newTemporary(bowSlot), ProjectileType.ARROW, event.getProjectile());
proj.setSourceItem(item);
proj.setCustomDamage(true);
proj.setDamageMultiplier(event.getForce());
if (item.hasTag("MMOITEMS_ARROW_PARTICLES"))
new ArrowParticles((AbstractArrow) event.getProjectile(), item);
final AbstractArrow arrow = (AbstractArrow) event.getProjectile();
// Apply arrow velocity
final double arrowVelocity = projData.getShooter().getStat("ARROW_VELOCITY");
if (arrowVelocity > 0)
arrow.setVelocity(arrow.getVelocity().multiply(arrowVelocity));
final double arrowVelocity = proj.getShooter().getStat("ARROW_VELOCITY");
if (arrowVelocity > 0) arrow.setVelocity(arrow.getVelocity().multiply(arrowVelocity));
}
}
@ -295,12 +264,12 @@ public class ItemUse implements Listener {
*/
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void handleVanillaEatenConsumables(PlayerItemConsumeEvent event) {
NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(event.getItem());
if (!item.hasType())
return;
final NBTItem item = MythicLib.plugin.getVersion().getWrapper().getNBTItem(event.getItem());
final Type itemType = Type.get(item);
if (itemType == null) return;
Player player = event.getPlayer();
UseItem useItem = UseItem.getItem(player, item, item.getType());
UseItem useItem = UseItem.getItem(player, item, itemType);
if (!useItem.checkItemRequirements()) {
event.setCancelled(true);
return;
@ -310,9 +279,7 @@ public class ItemUse implements Listener {
if (useItem.getPlayerData().getMMOPlayerData().getCooldownMap().isOnCooldown(useItem.getMMOItem())) {
final double cd = useItem.getPlayerData().getMMOPlayerData().getCooldownMap().getCooldown(useItem.getMMOItem());
Message.ITEM_ON_COOLDOWN
.format(ChatColor.RED, "#left#", MythicLib.plugin.getMMOConfig().decimal.format(cd), "#s#", cd >= 2 ? "s" : "")
.send(player);
Message.ITEM_ON_COOLDOWN.format(ChatColor.RED, "#left#", MythicLib.plugin.getMMOConfig().decimal.format(cd), "#s#", cd >= 2 ? "s" : "").send(player);
event.setCancelled(true);
return;
}
@ -326,8 +293,7 @@ public class ItemUse implements Listener {
}
// Item is not consumed but its effects are applied anyways
if (result == Consumable.ConsumableConsumeResult.NOT_CONSUME)
event.setCancelled(true);
if (result == Consumable.ConsumableConsumeResult.NOT_CONSUME) event.setCancelled(true);
useItem.getPlayerData().getMMOPlayerData().getCooldownMap().applyCooldown(useItem.getMMOItem(), useItem.getNBTItem().getStat("ITEM_COOLDOWN"));
useItem.executeCommands();

View File

@ -11,7 +11,7 @@ import net.Indyuce.mmoitems.api.util.NumericStatFormula;
import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.stat.GemUpgradeScaling;
import net.Indyuce.mmoitems.stat.LuteAttackEffectStat.LuteAttackEffect;
import net.Indyuce.mmoitems.stat.StaffSpiritStat.StaffSpirit;
//import net.Indyuce.mmoitems.stat.StaffSpiritStat.StaffSpirit;
import net.Indyuce.mmoitems.util.LanguageFile;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
@ -138,9 +138,9 @@ public class ConfigManager implements Reloadable {
// Staff spirits
final LanguageFile attackEffects = new LanguageFile("attack-effects");
for (StaffSpirit sp : StaffSpirit.values())
/* for (StaffSpirit sp : StaffSpirit.values())
sp.setName(attackEffects.computeTranslation("staff-spirit." + sp.name().toLowerCase().replace("_", "-"),
() -> UtilityMethods.caseOnWords(sp.name().toLowerCase().replace("_", " "))));
() -> UtilityMethods.caseOnWords(sp.name().toLowerCase().replace("_", " "))));*/
// Lute attack effects
for (LuteAttackEffect eff : LuteAttackEffect.values())
@ -262,11 +262,11 @@ public class ConfigManager implements Reloadable {
public String getLuteAttackEffectName(LuteAttackEffect effect) {
return effect.getName();
}
/*
@Deprecated
public String getStaffSpiritName(StaffSpirit spirit) {
return spirit.getName();
}
}*/
/**
* @deprecated See {@link net.Indyuce.mmoitems.api.item.util.LoreUpdate}

View File

@ -109,7 +109,7 @@ public class CraftingManager implements Reloadable {
for (CraftingStation station : stations.values())
try {
station.postLoad();
station.getPostLoadAction().performAction();
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING,
"Could not post-load station '" + station.getId() + "': " + exception.getMessage());

View File

@ -1,153 +0,0 @@
package net.Indyuce.mmoitems.manager;
import io.lumine.mythic.lib.api.event.PlayerAttackEvent;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.damage.ProjectileAttackMetadata;
import io.lumine.mythic.lib.player.PlayerMetadata;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.interaction.projectile.ArrowParticles;
import net.Indyuce.mmoitems.api.interaction.projectile.EntityData;
import net.Indyuce.mmoitems.api.interaction.projectile.ProjectileData;
import org.bukkit.Bukkit;
import org.bukkit.entity.*;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.AbstractArrow;
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.EntityDeathEvent;
import org.bukkit.event.entity.ProjectileHitEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
public class EntityManager implements Listener {
/**
* Entity data used by abilities or staff attacks that utilize entities like
* evoker fangs or shulker missiles. It can correspond to the damage the
* entity is supposed to deal, etc
*/
private final Map<Integer, EntityData> entities = new HashMap<>();
private final Map<Integer, ProjectileData> projectiles = new WeakHashMap<>();
@Deprecated
public void registerCustomProjectile(@NotNull NBTItem sourceItem, @NotNull PlayerMetadata attacker, @NotNull Entity entity, boolean customWeapon, double damageMultiplicator) {
registerCustomProjectile(sourceItem, attacker, entity, damageMultiplicator);
}
/**
* Registers a custom projectile. This is used for bows, crossbows and tridents.
*
* @param sourceItem Item used to shoot the projectile
* @param attacker Cached stats of the player shooting the projectile
* @param entity The custom entity
* @param damageMultiplicator The damage coefficient. For bows, this is basically the pull force.
* For tridents or anything else this is always set to 1
*/
@NotNull
public ProjectileData registerCustomProjectile(@NotNull NBTItem sourceItem, @NotNull PlayerMetadata attacker, @NotNull Entity entity, double damageMultiplicator) {
// Initialize projectile data
final ProjectileData projectileData = new ProjectileData(attacker, sourceItem, damageMultiplicator);
/*
* Load arrow particles if the entity is an arrow and if the item has
* arrow particles. Currently projectiles are only arrows so there is no
* problem with other projectiles like snowballs etc.
*/
if (entity instanceof AbstractArrow && sourceItem.hasTag("MMOITEMS_ARROW_PARTICLES"))
new ArrowParticles((AbstractArrow) entity, sourceItem);
projectiles.put(entity.getEntityId(), projectileData);
return projectileData;
}
public void registerCustomEntity(Entity entity, EntityData data) {
entities.put(entity.getEntityId(), data);
}
public boolean isCustomProjectile(Projectile projectile) {
return projectiles.containsKey(projectile.getEntityId());
}
public boolean isCustomEntity(Entity entity) {
return entities.containsKey(entity.getEntityId());
}
public ProjectileData getProjectileData(Projectile projectile) {
return Objects.requireNonNull(projectiles.get(projectile.getEntityId()), "Provided entity is not a custom projectile");
}
public EntityData getEntityData(Entity entity) {
return Objects.requireNonNull(entities.get(entity.getEntityId()), "Provided entity is not a custom entity");
}
public void unregisterCustomProjectile(Projectile projectile) {
projectiles.remove(projectile.getEntityId());
}
public void unregisterCustomEntity(Entity entity) {
entities.remove(entity.getEntityId());
}
/**
* This event is called on LOWEST and only edits the custom bow base damage.
* It does NOT take into account the base damage passed in Bow#getDamage()
* and fully overrides any change.
*
* This applies to tridents, arrows, spectral arrows etc.
* <p>
* Event order: ProjectileHit -> EntityDamage / EntityDeathEvent
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void customProjectileDamage(EntityDamageByEntityEvent event) {
if (!(event.getDamager() instanceof Projectile) || !(event.getEntity() instanceof LivingEntity) || event.getEntity().hasMetadata("NPC"))
return;
final Projectile projectile = (Projectile) event.getDamager();
final ProjectileData data = projectiles.get(projectile.getEntityId());
if (data == null)
return;
// Calculate custom base damage
double baseDamage = data.getDamage();
// Apply power vanilla enchant
if (projectile instanceof AbstractArrow && data.getSourceItem().getItem().hasItemMeta()
&& data.getSourceItem().getItem().getItemMeta().getEnchants().containsKey(Enchantment.ARROW_DAMAGE))
baseDamage *= 1.25 + (.25 * data.getSourceItem().getItem().getItemMeta().getEnchantLevel(Enchantment.ARROW_DAMAGE));
event.setDamage(baseDamage);
}
@EventHandler(ignoreCancelled = true)
public void onHitEffects(PlayerAttackEvent event) {
if (!(event.getAttack() instanceof ProjectileAttackMetadata))
return;
final ProjectileAttackMetadata projAttack = (ProjectileAttackMetadata) event.getAttack();
final @Nullable ProjectileData data = projectiles.get(projAttack.getProjectile().getEntityId());
if (data == null)
return;
// Apply MMOItems specific modifications
data.applyPotionEffects(event.getEntity());
}
@EventHandler(priority = EventPriority.MONITOR)
public void unregisterProjectileData(ProjectileHitEvent event) {
Bukkit.getScheduler().scheduleSyncDelayedTask(MMOItems.plugin, () -> unregisterCustomProjectile(event.getEntity()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void unregisterEntityData(EntityDeathEvent event) {
Bukkit.getScheduler().scheduleSyncDelayedTask(MMOItems.plugin, () -> unregisterCustomEntity(event.getEntity()));
}
}

View File

@ -6,7 +6,6 @@ import io.lumine.mythic.lib.skill.handler.SkillHandler;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.skill.RegisteredSkill;
import net.Indyuce.mmoitems.skill.ShulkerMissile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -81,8 +80,6 @@ public class SkillManager {
if (clearBefore)
skills.clear();
MythicLib.plugin.getSkills().registerSkillHandler(new ShulkerMissile());
File skillFolder = new File(MMOItems.plugin.getDataFolder() + "/skill");
if (!skillFolder.exists()) {

View File

@ -23,6 +23,12 @@ import java.util.logging.Level;
public class StatManager {
private final Map<String, ItemStat<?, ?>> stats = new LinkedHashMap<>();
/**
* TODO refactor with stat categories
*/
@Deprecated
private final Map<String, ItemStat<?, ?>> legacyAliases = new HashMap<>();
/*
* These lists are sets of stats collected when the stats are registered for
* the first time to make their access easier. Check the classes
@ -33,11 +39,13 @@ public class StatManager {
private final List<ConsumableItemInteraction> consumableActions = new ArrayList<>();
private final List<PlayerConsumable> playerConsumables = new ArrayList<>();
/**
* Load default stats using java reflection, get all public static final
* fields in the ItemStat and register them as stat instances
* <p>
* TODO refactor
*/
@Deprecated
public void load() {
for (Field field : ItemStats.class.getFields())
try {
@ -53,9 +61,9 @@ public class StatManager {
/**
* @see FictiveNumericStat
* @deprecated Needs refactor
* <p>
* TODO refactor
*/
@Deprecated
public void reload(boolean cleanFirst) {
// Clean fictive numeric stats before
@ -81,10 +89,9 @@ public class StatManager {
/**
* Load custom stats
*
* @deprecated Needs refactor
* <p>
* TODO refactor
*/
@Deprecated
public void loadCustom() {
ConfigManager.DefaultFile.CUSTOM_STATS.checkFile();
ConfigFile config = new ConfigFile("custom-stats");
@ -95,10 +102,9 @@ public class StatManager {
/**
* Register all MythicLib elements as stats
*
* @deprecated Needs refactor
* <p>
* TODO refactor
*/
@Deprecated
public void loadElements() {
for (ElementStatType type : ElementStatType.values())
for (Element element : MythicLib.plugin.getElements().getAll())
@ -154,11 +160,21 @@ public class StatManager {
@Nullable
public ItemStat<?, ?> get(String id) {
ItemStat<?, ?> stat = stats.getOrDefault(id, null);
if (stat == null) {
stat = numeric.stream().filter(doubleStat -> doubleStat.getId().equals(id)).findFirst().orElse(null);
}
return stat;
// Default registry
ItemStat<?, ?> stat = stats.get(id);
if (stat != null) return stat;
// Numeric registry (see to-do)
stat = numeric.stream().filter(doubleStat -> doubleStat.getId().equals(id)).findFirst().orElse(null);
if (stat != null) return stat;
// Legacy liases
stat = legacyAliases.get(id);
if (stat != null) return stat;
// Non existing stat
return null;
}
/**
@ -189,9 +205,14 @@ public class StatManager {
return;
}
// Main registry
stats.put(stat.getId(), stat);
// Custom registries
// Register aliases
for (String alias : stat.getAliases())
legacyAliases.put(alias, stat);
// Use-case specific registries
if (stat instanceof DoubleStat && !(stat instanceof GemStoneStat) && stat.isCompatible(Type.GEM_STONE))
numeric.add((DoubleStat) stat);
if (stat instanceof ItemRestriction) itemRestriction.add((ItemRestriction) stat);
@ -244,8 +265,7 @@ public class StatManager {
// Create a new stat instance
try {
ItemStat<?, ?> stat = statClass.getConstructor(String.class, Material.class, String.class, String[].class, String[].class, Material[].class)
.newInstance(statId, Material.PAPER, name, lore, new String[]{"!miscellaneous", "!block", "all"}, new Material[0]);
ItemStat<?, ?> stat = statClass.getConstructor(String.class, Material.class, String.class, String[].class, String[].class, Material[].class).newInstance(statId, Material.PAPER, name, lore, new String[]{"!miscellaneous", "!block", "all"}, new Material[0]);
register(stat);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {

View File

@ -172,7 +172,7 @@ public class TemplateManager implements Reloadable {
try {
MMOItemTemplate template = new MMOItemTemplate(type, type.getConfigFile().getConfig().getConfigurationSection(id));
template.postLoad();
template.getPostLoadAction().performAction();
registerTemplate(template);
return template;
@ -327,7 +327,7 @@ public class TemplateManager implements Reloadable {
ffp.log(FriendlyFeedbackCategory.INFORMATION, "Loading item templates, please wait...");
templates.forEach(template -> {
try {
template.postLoad();
template.getPostLoadAction().performAction();
} catch (IllegalArgumentException exception) {
ffp.activatePrefix(true, "Item Templates \u00a78($r" + template.getType().getId() + "\u00a78)");
ffp.log(FriendlyFeedbackCategory.INFORMATION, "Could not post-load item template '" + template.getId() + "': " + exception.getMessage());

View File

@ -5,6 +5,8 @@ import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.manager.ConfigManager.DefaultFile;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -13,100 +15,112 @@ import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Level;
public class TypeManager implements Reloadable {
private final Map<String, Type> map = new LinkedHashMap<>();
public class TypeManager {
private final Map<String, Type> map = new LinkedHashMap<>();
/**
* Reloads the type manager. It entirely empties the currently registered
* item types, registers default item types again and reads item-types.yml
*/
public void reload() {
map.clear();
/**
* Reloads the type manager. It entirely empties the currently registered
* item types, registers default item types again and reads item-types.yml
*/
public void reload(boolean clearBefore) {
if (clearBefore) map.clear();
// Load default types
for (Field field : Type.class.getFields())
try {
if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.get(null) instanceof Type)
register((Type) field.get(null));
} catch (Exception exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Couldn't register type called '" + field.getName() + "': " + exception.getMessage());
}
// Load default types
for (Field field : Type.class.getFields())
try {
if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.get(null) instanceof Type)
register((Type) field.get(null));
} catch (Exception exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Couldn't register type called '" + field.getName() + "': " + exception.getMessage());
}
/*
* Register all other types. Important: check if the map already
* contains the id, this way the DEFAULT types are not registered twice,
* and only custom types are registered with a parent.
*/
DefaultFile.ITEM_TYPES.checkFile();
// Load custom types
DefaultFile.ITEM_TYPES.checkFile();
FileConfiguration config = new ConfigFile("item-types").getConfig();
for (String id : config.getKeys(false))
if (!map.containsKey(id))
try {
register(new Type(this, config.getConfigurationSection(id)));
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not register type '" + id + "': " + exception.getMessage());
}
ConfigFile config = new ConfigFile("item-types");
for (String id : config.getConfig().getKeys(false))
if (!map.containsKey(id))
try {
register(new Type(this, config.getConfig().getConfigurationSection(id)));
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not register type '" + id + "': " + exception.getMessage());
}
for (Iterator<Type> iterator = map.values().iterator(); iterator.hasNext(); ) {
Type type = iterator.next();
for (Iterator<Type> iterator = map.values().iterator(); iterator.hasNext();) {
Type type = iterator.next();
try {
ConfigurationSection section = config.getConfigurationSection(type.getId());
Validate.notNull(section, "Could not find config section for type '" + type.getId() + "'");
type.load(section);
if (clearBefore) type.getPostLoadAction().performAction();
} catch (RuntimeException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not register type '" + type.getId() + "': " + exception.getMessage());
iterator.remove();
continue;
}
try {
type.load(config.getConfig().getConfigurationSection(type.getId()));
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not register type '" + type.getId() + "': " + exception.getMessage());
iterator.remove();
continue;
}
/*
* caches all the stats which the type can have to reduce future
* both item generation (and GUI) calculations. probably the thing
* which takes the most time when loading item types.
*/
type.getAvailableStats().clear();
MMOItems.plugin.getStats().getAll().stream().filter(stat -> stat.isCompatible(type)).forEach(stat -> type.getAvailableStats().add(stat));
}
}
/*
* caches all the stats which the type can have to reduce future
* both item generation (and GUI) calculations. probably the thing
* which takes the most time when loading item types.
*/
type.getAvailableStats().clear();
MMOItems.plugin.getStats().getAll().stream().filter(stat -> stat.isCompatible(type)).forEach(stat -> type.getAvailableStats().add(stat));
}
}
public void postload() {
for (Type type : map.values())
try {
type.getPostLoadAction().performAction();
} catch (RuntimeException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "An error occured while post-loading type '" + type.getId() + "': " + exception.getMessage());
}
}
public void register(Type type) {
map.put(type.getId(), type);
}
public void register(Type type) {
map.put(type.getId(), type);
}
public void registerAll(Type... types) {
for (Type type : types)
register(type);
}
public void registerAll(Type... types) {
for (Type type : types)
register(type);
}
/**
* @param id Internal ID of the type
*
* @return The MMOItem Type if it found.
*/
@Nullable public Type get(@Nullable String id) {
if (id == null) { return null; }
return map.get(id);
}
/**
* @param id Internal ID of the type
* @return The MMOItem Type if it found.
*/
@Nullable
public Type get(@Nullable String id) {
if (id == null) {
return null;
}
return map.get(id);
}
@NotNull public Type getOrThrow(@Nullable String id) {
Validate.isTrue(map.containsKey(id), "Could not find item type with ID '" + id + "'");
return map.get(id);
}
@NotNull
public Type getOrThrow(@Nullable String id) {
Validate.isTrue(map.containsKey(id), "Could not find item type with ID '" + id + "'");
return map.get(id);
}
public boolean has(String id) {
return map.containsKey(id);
}
public boolean has(String id) {
return map.containsKey(id);
}
public Collection<Type> getAll() {
return map.values();
}
public Collection<Type> getAll() {
return map.values();
}
/**
* @return The names of all loaded types.
*/
public ArrayList<String> getAllTypeNames() {
ArrayList<String> ret = new ArrayList<>();
for (Type t : getAll()) { ret.add(t.getId()); }
return ret;
}
/**
* @return The names of all loaded types.
*/
public ArrayList<String> getAllTypeNames() {
ArrayList<String> ret = new ArrayList<>();
for (Type t : getAll()) {
ret.add(t.getId());
}
return ret;
}
}

View File

@ -1,146 +0,0 @@
package net.Indyuce.mmoitems.skill;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.comp.interaction.InteractionType;
import io.lumine.mythic.lib.damage.DamageMetadata;
import io.lumine.mythic.lib.damage.DamageType;
import io.lumine.mythic.lib.player.PlayerMetadata;
import io.lumine.mythic.lib.skill.SkillMetadata;
import io.lumine.mythic.lib.skill.handler.SkillHandler;
import io.lumine.mythic.lib.skill.result.def.VectorSkillResult;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.interaction.projectile.EntityData;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.ShulkerBullet;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
public class ShulkerMissile extends SkillHandler<VectorSkillResult> implements Listener {
public ShulkerMissile() {
super("SHULKER_MISSILE");
registerModifiers("damage", "effect-duration", "duration");
}
@NotNull
@Override
public VectorSkillResult getResult(SkillMetadata meta) {
return new VectorSkillResult(meta);
}
@Override
public void whenCast(VectorSkillResult result, SkillMetadata skillMeta) {
double duration = skillMeta.getParameter("duration");
Player caster = skillMeta.getCaster().getPlayer();
new BukkitRunnable() {
double n = 0;
public void run() {
if (n++ > 3) {
cancel();
return;
}
Vector vec = result.getTarget();
caster.getWorld().playSound(caster.getLocation(), Sound.ENTITY_WITHER_SHOOT, 2, 2);
ShulkerBullet shulkerBullet = (ShulkerBullet) caster.getWorld().spawnEntity(caster.getLocation().add(0, 1, 0),
EntityType.SHULKER_BULLET);
shulkerBullet.setShooter(caster);
MMOItems.plugin.getEntities().registerCustomEntity(shulkerBullet, new ShulkerMissileEntityData(skillMeta.getCaster(), new DamageMetadata(skillMeta.getParameter("damage"), DamageType.SKILL, DamageType.MAGIC, DamageType.PROJECTILE), skillMeta.getParameter("effect-duration"), null));
new BukkitRunnable() {
double ti = 0;
public void run() {
if (shulkerBullet.isDead() || ti++ >= duration * 20) {
shulkerBullet.remove();
cancel();
} else
shulkerBullet.setVelocity(vec);
}
}.runTaskTimer(MMOItems.plugin, 0, 1);
}
}.runTaskTimer(MMOItems.plugin, 0, 3);
}
@EventHandler
public void a(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof ShulkerBullet && event.getEntity() instanceof LivingEntity) {
ShulkerBullet damager = (ShulkerBullet) event.getDamager();
LivingEntity entity = (LivingEntity) event.getEntity();
if (!MMOItems.plugin.getEntities().isCustomEntity(damager))
return;
ShulkerMissileEntityData data = (ShulkerMissileEntityData) MMOItems.plugin.getEntities().getEntityData(damager);
if (!UtilityMethods.canTarget(data.caster.getPlayer(), null, entity, data.isWeaponAttack() ? InteractionType.OFFENSE_ACTION : InteractionType.OFFENSE_SKILL)) {
event.setCancelled(true);
return;
}
event.setDamage(data.damage.getDamage());
new BukkitRunnable() {
final Location loc = entity.getLocation();
double y = 0;
public void run() {
// Potion effect should apply right after the damage with a 1 tick delay.
if (y == 0) {
entity.removePotionEffect(PotionEffectType.LEVITATION);
entity.addPotionEffect(new PotionEffect(PotionEffectType.LEVITATION, (int) (data.duration * 20), 0));
}
for (int j1 = 0; j1 < 3; j1++) {
y += .04;
for (int j = 0; j < 2; j++) {
double xz = y * Math.PI * 1.3 + (j * Math.PI);
loc.getWorld().spawnParticle(Particle.REDSTONE, loc.clone().add(Math.cos(xz), y, Math.sin(xz)), 1,
new Particle.DustOptions(Color.MAROON, 1));
}
}
if (y >= 2)
cancel();
}
}.runTaskTimer(MMOItems.plugin, 0, 1);
}
}
public static class ShulkerMissileEntityData implements EntityData {
private final PlayerMetadata caster;
private final DamageMetadata damage;
private final double duration;
@Nullable
private final NBTItem weapon;
public ShulkerMissileEntityData(PlayerMetadata caster, DamageMetadata damage, double duration, NBTItem weapon) {
this.caster = caster;
this.damage = damage;
this.duration = duration;
this.weapon = weapon;
}
public boolean isWeaponAttack() {
return damage.hasType(DamageType.WEAPON);
}
}
}

View File

@ -0,0 +1,62 @@
package net.Indyuce.mmoitems.stat;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.ItemTag;
import io.lumine.mythic.lib.api.item.SupportedNBTTagValues;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.api.item.mmoitem.ReadMMOItem;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.stat.data.StringData;
import net.Indyuce.mmoitems.stat.type.StringStat;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class ActionLeftClick extends StringStat {
public ActionLeftClick() {
super("ON_LEFT_CLICK", Material.COMMAND_BLOCK_MINECART, "Left Click Action", new String[]{"ID of skill ran when left clicking. When used,", "The item will naturally apply item costs like", "mana, stamina, cooldown. This option overrides the", "script provided by the item type."}, new String[]{"weapon"});
// Staff spirit set as alias
setAliases(LEGACY_ID);
}
@Override
public void whenInput(@NotNull EditionInventory inv, @NotNull String message, Object... info) {
final String format = UtilityMethods.enumName(message);
MythicLib.plugin.getSkills().getHandlerOrThrow(format);
inv.getEditedSection().set(getPath(), format);
inv.registerTemplateEdition();
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + getName() + " successfully changed to '" + format + "'");
}
@Override
public void whenApplied(@NotNull ItemStackBuilder item, @NotNull StringData data) {
item.addItemTag(new ItemTag(getNBTPath(), UtilityMethods.enumName(data.toString())));
// No lore insertion
}
public static final String LEGACY_ID = "STAFF_SPIRIT";
public static final String LEGACY_PATH = "MMOITEMS_" + LEGACY_ID;
// TODO refactor with stat categories
@Override
@Deprecated
public void whenLoaded(@NotNull ReadMMOItem mmoitem) {
ArrayList<ItemTag> relevantTags = new ArrayList<>();
// New path
if (mmoitem.getNBT().hasTag(getNBTPath()))
relevantTags.add(ItemTag.getTagAtPath(getNBTPath(), mmoitem.getNBT(), SupportedNBTTagValues.STRING));
// Legacy path
if (mmoitem.getNBT().hasTag(LEGACY_PATH))
relevantTags.add(new ItemTag(getNBTPath(), ItemTag.getTagAtPath(LEGACY_PATH, mmoitem.getNBT(), SupportedNBTTagValues.STRING).getValue()));
StringData bakedData = getLoadedNBT(relevantTags);
if (bakedData != null) mmoitem.setData(this, bakedData);
}
}

View File

@ -0,0 +1,34 @@
package net.Indyuce.mmoitems.stat;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.ItemTag;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.stat.data.StringData;
import net.Indyuce.mmoitems.stat.type.StringStat;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
public class ActionRightClick extends StringStat {
public ActionRightClick() {
super("ON_RIGHT_CLICK", Material.COMMAND_BLOCK_MINECART, "Right Click Action", new String[]{"ID of skill ran when right clicking. When used,", "The item will naturally apply item costs like", "mana, stamina, cooldown. This option overrides the", "script provided by the item type."}, new String[]{"weapon"});
}
@Override
public void whenInput(@NotNull EditionInventory inv, @NotNull String message, Object... info) {
final String format = UtilityMethods.enumName(message);
MythicLib.plugin.getSkills().getHandlerOrThrow(format);
inv.getEditedSection().set(getPath(), format);
inv.registerTemplateEdition();
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + getName() + " successfully changed to '" + format + "'");
}
@Override
public void whenApplied(@NotNull ItemStackBuilder item, @NotNull StringData data) {
item.addItemTag(new ItemTag(getNBTPath(), UtilityMethods.enumName(data.toString())));
// No lore insertion
}
}

View File

@ -20,7 +20,7 @@ import net.Indyuce.mmoitems.stat.data.SkullTextureData;
import net.Indyuce.mmoitems.stat.type.BooleanStat;
import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import net.Indyuce.mmoitems.util.MMOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
@ -103,7 +103,7 @@ public class CanDeskin extends BooleanStat implements ConsumableItemInteraction
// Try to find the skin item.
// This is for backwards compatibility as cases with SKIN subtypes were not handled
// in the past, inducing an unfixable data loss for item skins applied onto items
@BackwardsCompatibility(version = "unknown") final String skinTypeId = target.getString(ItemSkin.SKIN_TYPE_TAG);
@Deprecated final String skinTypeId = target.getString(ItemSkin.SKIN_TYPE_TAG);
final Type type = Objects.requireNonNullElse(Type.get(skinTypeId), Type.SKIN);
Validate.notNull(type, "Could not find item type of applied skin");
final MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplateOrThrow(Type.SKIN, skinId);

View File

@ -36,7 +36,7 @@ import java.util.*;
public class Elements extends ItemStat<RandomElementListData, ElementListData> implements Previewable<RandomElementListData, ElementListData> {
public Elements() {
super("ELEMENT", Material.SLIME_BALL, "Elements", new String[]{"The elements of your item."},
new String[]{"slashing", "piercing", "blunt", "catalyst", "range", "tool", "armor", "gem_stone"});
new String[]{"weapon", "catalyst", "tool", "armor", "gem_stone"});
}
@Override

View File

@ -33,7 +33,7 @@ import java.util.Optional;
public class GemSockets extends ItemStat<GemSocketsData, GemSocketsData> {
public GemSockets() {
super("GEM_SOCKETS", Material.EMERALD, "Gem Sockets", new String[]{"The amount of gem", "sockets your weapon has."},
new String[]{"piercing", "slashing", "blunt", "catalyst", "range", "tool", "armor", "accessory", "!gem_stone"});
new String[]{"weapon", "catalyst", "tool", "armor", "accessory", "!gem_stone"});
}
@Override

View File

@ -18,7 +18,6 @@ import org.bukkit.event.inventory.InventoryClickEvent;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.TypeSet;
import net.Indyuce.mmoitems.api.edition.StatEdition;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
@ -195,10 +194,6 @@ public class ItemTypeRestriction extends ItemStat<StringListData, StringListData
if (type.getId().equals(format))
return true;
for (TypeSet set : TypeSet.values())
if (set.name().equals(format))
return true;
return false;
}

View File

@ -24,7 +24,7 @@ import org.jetbrains.annotations.NotNull;
public class ManaCost extends DoubleStat implements ItemRestriction, PlayerConsumable {
public ManaCost() {
super("MANA_COST", VersionMaterial.LAPIS_LAZULI.toMaterial(), "Mana Cost", new String[]{"Mana spent by your weapon to be used."}, new String[]{"piercing", "slashing", "blunt", "range"});
super("MANA_COST", VersionMaterial.LAPIS_LAZULI.toMaterial(), "Mana Cost", new String[]{"Mana spent by your weapon to be used."}, new String[]{"weapon"});
}

View File

@ -1,94 +0,0 @@
package net.Indyuce.mmoitems.stat;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.item.ItemTag;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.version.VersionMaterial;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.interaction.weapon.untargeted.staff.*;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.stat.data.StringData;
import net.Indyuce.mmoitems.stat.type.StringStat;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class StaffSpiritStat extends StringStat {
public StaffSpiritStat() {
super("STAFF_SPIRIT", VersionMaterial.BONE_MEAL.toMaterial(), "Staff Spirit",
new String[]{"Spirit changes the texture", "of the magic attack.", "&9Tip: /mi list spirit"}, new String[]{"staff", "wand"});
}
@Override
public void whenInput(@NotNull EditionInventory inv, @NotNull String message, Object... info) {
try {
StaffSpirit spirit = StaffSpirit.valueOf(message.toUpperCase().replace(" ", "_").replace("-", "_"));
inv.getEditedSection().set("staff-spirit", spirit.name());
inv.registerTemplateEdition();
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + "Staff Spirit successfully changed to " + spirit.getName() + ".");
} catch (IllegalArgumentException exception) {
throw new IllegalArgumentException(exception.getMessage() + " (See all Staff Spirits here: /mi list spirit).");
}
}
@Override
public void whenApplied(@NotNull ItemStackBuilder item, @NotNull StringData data) {
StaffSpirit staffSpirit = StaffSpirit.valueOf(data.toString().toUpperCase().replace(" ", "_").replace("-", "_"));
item.addItemTag(new ItemTag("MMOITEMS_STAFF_SPIRIT", staffSpirit.name()));
item.getLore().insert("staff-spirit", staffSpirit.getName());
}
public enum StaffSpirit {
NETHER_SPIRIT("Shoots fire beams.", new NetherSpirit()),
VOID_SPIRIT("Shoots shulker missiles.", new VoidSpirit()),
MANA_SPIRIT("Summons mana bolts.", new ManaSpirit()),
LIGHTNING_SPIRIT("Summons lightning bolts.", new LightningSpirit()),
XRAY_SPIRIT("Fires piercing & powerful X-rays.", new XRaySpirit()),
THUNDER_SPIRIT("Fires AoE damaging thunder strikes.", new ThunderSpirit()),
SUNFIRE_SPIRIT("Fires AoE damaging fire comets.", new SunfireSpirit()),
// CURSED_SPIRIT(ChatColor.DARK_PURPLE, "Cursed Spirit", "Fires a
// targeted cursed projectile."), new CursedSpirit()),
;
private final String defaultLore;
private final StaffAttackHandler handler;
@NotNull
private String name = UtilityMethods.caseOnWords(name().toLowerCase().replace("_", " "));
StaffSpirit(String description, StaffAttackHandler handler) {
this.defaultLore = description;
this.handler = handler;
}
@Nullable
public static StaffSpirit get(NBTItem item) {
try {
return StaffSpirit.valueOf(item.getString("MMOITEMS_STAFF_SPIRIT"));
} catch (Exception e) {
return null;
}
}
public String getName() {
return name;
}
public boolean hasLore() {
return defaultLore != null;
}
public String getDefaultLore() {
return defaultLore;
}
public StaffAttackHandler getAttack() {
return handler;
}
public void setName(String str) {
this.name = str;
}
}
}

View File

@ -49,7 +49,7 @@ public class UpgradeStat extends ItemStat<UpgradeData, UpgradeData> implements C
super("UPGRADE", Material.FLINT, "Item Upgrading",
new String[] { "Upgrading your item improves its", "current stats. It requires either a", "consumable or a specific crafting ",
"station. Upgrading may sometimes &cfail&7..." },
new String[] { "piercing", "slashing", "blunt", "catalyst", "range", "tool", "armor", "consumable", "accessory" });
new String[] { "weapon", "catalyst", "tool", "armor", "consumable", "accessory" });
}
@Override

View File

@ -29,6 +29,9 @@ public abstract class ItemStat<R extends RandomStatData<S>, S extends StatData>
private final List<String> compatibleTypes;
private final List<Material> compatibleMaterials;
@Nullable
private String[] aliases = {};
/**
* The stat can be enabled or not, depending on the server version to
* prevent from displaying useless editable stats in the edition menu.
@ -193,6 +196,25 @@ public abstract class ItemStat<R extends RandomStatData<S>, S extends StatData>
return id;
}
/**
* Mainly for backwards compatibility. Aliases are basically
* other string identifiers that point to the same item stat.
* Useful when changing stat keys inside the item configs.
*
* Aliases have to follow the UPPER_CASE stat identifier format.
*/
@Nullable
public String[] getAliases() {
return aliases;
}
/**
* @see #getAliases()
*/
public void setAliases(String... aliases) {
this.aliases = aliases;
}
/**
* @return The stat ID
* @deprecated Use getId() instead. Type is no longer an util since they can
@ -234,6 +256,7 @@ public abstract class ItemStat<R extends RandomStatData<S>, S extends StatData>
return lore;
}
@NotNull
public List<String> getCompatibleTypes() {
return compatibleTypes;
}
@ -242,11 +265,17 @@ public abstract class ItemStat<R extends RandomStatData<S>, S extends StatData>
* @param type The item type to check
* @return If a certain item type is compatible with this item stat
*/
public boolean isCompatible(Type type) {
String lower = type.getId().toLowerCase();
return type.isSubtype() ? isCompatible(type.getParent())
: !compatibleTypes.contains("!" + lower) && (compatibleTypes.contains("all") || compatibleTypes.contains(lower)
|| compatibleTypes.contains(type.getItemSet().getName().toLowerCase()));
public boolean isCompatible(@NotNull Type type) {
// Special rule for weapons
if (type.isWeapon() && compatibleTypes.contains("weapon")) return true;
// Recursive call with root types
if (type.isSubtype()) return isCompatible(type.getParent());
// Parent item types
final String lower = type.getId().toLowerCase();
return !compatibleTypes.contains("!" + lower) && (compatibleTypes.contains("all") || compatibleTypes.contains(lower));
}
public boolean hasValidMaterial(ItemStack item) {

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>MMOItems</artifactId>
<groupId>net.Indyuce</groupId>
<version>6.9.5-SNAPSHOT</version>
<version>6.10-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
@ -28,7 +28,7 @@
<dependency>
<groupId>net.Indyuce</groupId>
<artifactId>MMOItems-API</artifactId>
<version>6.9.5-SNAPSHOT</version>
<version>6.10-SNAPSHOT</version>
<optional>true</optional>
</dependency>

View File

@ -1,18 +1,25 @@
package net.Indyuce.mmoitems.listener;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.event.PlayerAttackEvent;
import io.lumine.mythic.lib.api.event.armorequip.ArmorEquipEvent;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.damage.ProjectileAttackMetadata;
import io.lumine.mythic.lib.entity.ProjectileMetadata;
import io.lumine.mythic.lib.entity.ProjectileType;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.DeathItemsHandler;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.interaction.projectile.ArrowPotionEffectArrayItem;
import net.Indyuce.mmoitems.api.interaction.util.InteractItem;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.DeathDowngrading;
import net.Indyuce.mmoitems.stat.data.PotionEffectData;
import net.Indyuce.mmoitems.util.MMOUtils;
import org.bukkit.Material;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Trident;
import org.bukkit.event.EventHandler;
@ -24,8 +31,10 @@ import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
@ -119,17 +128,37 @@ public class PlayerListener implements Listener {
return;
}
MMOItems.plugin.getEntities().registerCustomProjectile(nbtItem, playerData.getStats().newTemporary(EquipmentSlot.fromBukkit(item.getSlot())), event.getEntity(), 1);
final ProjectileMetadata proj = ProjectileMetadata.create(playerData.getStats().newTemporary(EquipmentSlot.fromBukkit(item.getSlot())), ProjectileType.TRIDENT, event.getEntity());
proj.setSourceItem(nbtItem);
proj.setCustomDamage(true);
}
}
@EventHandler(ignoreCancelled = true)
public void registerArrowSpecialEffects(PlayerAttackEvent event) {
if (!(event.getAttack() instanceof ProjectileAttackMetadata)) return;
final ProjectileAttackMetadata projAttack = (ProjectileAttackMetadata) event.getAttack();
final @Nullable ProjectileMetadata data = ProjectileMetadata.get(projAttack.getProjectile());
if (data == null || data.getSourceItem() == null) return;
// Apply MMOItems-specific effects
applyPotionEffects(data, event.getEntity());
}
private void applyPotionEffects(ProjectileMetadata proj, LivingEntity target) {
if (proj.getSourceItem().hasTag("MMOITEMS_ARROW_POTION_EFFECTS"))
for (ArrowPotionEffectArrayItem entry : MythicLib.plugin.getJson().parse(proj.getSourceItem().getString("MMOITEMS_ARROW_POTION_EFFECTS"), ArrowPotionEffectArrayItem[].class))
target.addPotionEffect(new PotionEffectData(PotionEffectType.getByName(entry.type), entry.duration, entry.level).toEffect());
}
/**
* Fixes an issue where quickly swapping items in hand just
* does not update the player's inventory which can make the
* player cast abilities or attacks with not the correct stats
*
* @deprecated This does cost some performance and that update
* method NEEDS some improvement in the future
* method NEEDS some improvement in the future
*/
@Deprecated
@EventHandler
@ -143,7 +172,7 @@ public class PlayerListener implements Listener {
* player cast abilities or attacks with not the correct stats
*
* @deprecated This does cost some performance and that update
* method NEEDS some improvement in the future
* method NEEDS some improvement in the future
*/
@Deprecated
@EventHandler

View File

@ -65,7 +65,6 @@ default-item-capacity:
default:
attack-speed: 0.67
range: 16
blunt-rating: 33
recoil: 0.1
# Restart server when changing this option

View File

@ -31,6 +31,9 @@ SWORD:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
# What the item type does
on-attack: slashing_attack_effect
DAGGER:
display: STONE_SWORD
name: 'Dagger'
@ -44,6 +47,8 @@ DAGGER:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: piercing_attack_effect
SPEAR:
display: TRIDENT
name: 'Spear'
@ -57,6 +62,8 @@ SPEAR:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: piercing_attack_effect
HAMMER:
display: IRON_AXE
name: 'Hammer'
@ -70,8 +77,10 @@ HAMMER:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: blunt_attack_effect
GAUNTLET:
display: IRON_HORSE_ARMOR
display: IRON_SHOVEL
name: 'Gauntlet'
unident-item:
name: '&f#prefix#Unidentified Gauntlet'
@ -83,6 +92,9 @@ GAUNTLET:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: blunt_attack_effect
on-entity-interact: gauntlet_special_attack
WHIP:
display: LEAD
name: 'Whip'
@ -96,6 +108,10 @@ WHIP:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
disable-melee-attacks: true
on-left-click: whip_attack
on-attack: slashing_attack_effect
STAFF:
display: DIAMOND_HOE
name: 'Staff'
@ -109,6 +125,11 @@ STAFF:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
disable-melee-attacks: true
on-left-click: staff_default
attack-cooldown-key: "staff"
on-entity-interact: staff_special_attack # Special action when right-clicking entities
BOW:
display: BOW
name: 'Bow'
@ -122,6 +143,8 @@ BOW:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
disable-melee-attacks: true
CROSSBOW:
display: WOODEN_PICKAXE
name: 'Crossbow'
@ -134,6 +157,9 @@ CROSSBOW:
- '{tier}&8Item Info:'
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
disable-melee-attacks: true
on-right-click: crossbow_attack
MUSKET:
display: IRON_HORSE_ARMOR
@ -147,6 +173,10 @@ MUSKET:
- '{tier}&8Item Info:'
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
disable-melee-attacks: true
# Lutes and muskets attack effects are hardcoded as
# of MI 6.10 and cannot be modified at the moment.
LUTE:
display: NAME_TAG
@ -160,6 +190,10 @@ LUTE:
- '{tier}&8Item Info:'
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
disable-melee-attacks: true
# Lutes and muskets attack effects are hardcoded as
# of MI 6.10 and cannot be modified at the moment.
# Applies stats in both hands
CATALYST:
@ -320,7 +354,6 @@ BLOCK:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
# Default subtypes
GREATSWORD:
display: DIAMOND_SWORD
@ -335,6 +368,8 @@ GREATSWORD:
- '{tier}&8Item Info:'
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: slashing_attack_effect
LONG_SWORD:
display: STONE_SWORD
@ -349,6 +384,8 @@ LONG_SWORD:
- '{tier}&8Item Info:'
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: slashing_attack_effect
KATANA:
display: IRON_SWORD
@ -363,6 +400,8 @@ KATANA:
- '{tier}&8Item Info:'
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: slashing_attack_effect
THRUSTING_SWORD:
display: GOLDEN_SWORD
@ -378,6 +417,8 @@ THRUSTING_SWORD:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: piercing_attack_effect
AXE:
display: STONE_AXE
name: 'Axe'
@ -392,6 +433,8 @@ AXE:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: slashing_attack_effect
GREATAXE:
display: DIAMOND_AXE
name: 'Greataxe'
@ -406,6 +449,8 @@ GREATAXE:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: slashing_attack_effect
HALBERD:
display: IRON_AXE
name: 'Halberd'
@ -420,6 +465,8 @@ HALBERD:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: blunt_attack_effect
LANCE:
display: STICK
name: 'Lance'
@ -434,6 +481,8 @@ LANCE:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: piercing_attack_effect
GREATHAMMER:
display: DIAMOND_AXE
name: 'Greathammer'
@ -448,6 +497,8 @@ GREATHAMMER:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: blunt_attack_effect
GREATSTAFF:
display: DIAMOND_HOE
name: 'Greatstaff'
@ -462,6 +513,8 @@ GREATSTAFF:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: blunt_attack_effect
STAVE:
display: IRON_HOE
name: 'Stave'
@ -476,6 +529,8 @@ STAVE:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
on-attack: blunt_attack_effect
TOME:
display: BOOK
name: 'Tome'
@ -518,6 +573,11 @@ WAND:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
disable-melee-attacks: true
on-left-click: staff_default
attack-cooldown-key: "staff"
on-entity-interact: staff_special_attack # Special action when right-clicking entities
GREATBOW:
display: BOW
name: 'Greatbow'
@ -532,6 +592,8 @@ GREATBOW:
- '{range}&8- &7Lvl Range: &e#range#'
- '{tier}&8- &7Item Tier: #prefix##tier#'
disable-melee-attacks: true
SHIELD:
display: SHIELD
name: 'Shield'

View File

@ -1,6 +1,6 @@
FIGHTING_GAUNTLETS:
base:
material: IRON_HORSE_ARMOR
material: IRON_SHOVEL
lore:
- §7Tired of breaking your bones when
- §7doing hand-to-hand combat?

View File

@ -1,11 +1,3 @@
staff-spirit:
nether-spirit: '&3 &7■ Nether Spirit'
void-spirit: '&3 &7■ Void Spirit'
mana-spirit: '&3 &7■ Mana Spirit'
lightning-spirit: '&3 &7■ Lightning Spirit'
xray-spirit: '&3 &7■ X-Ray Spirit'
thunder-spirit: '&3 &7■ Thunder Spirit'
sunfire-spirit: '&3 &7■ Sunfire Spirit'
lute-attack:
wave: '&3 &7■ Wave Melody'
circular: '&3 &7■ Circular Melody'

View File

@ -82,7 +82,6 @@ lore-format:
- '#mana-regeneration#'
- '#stamina-regeneration#'
- '#movement-speed#'
- '#staff-spirit#'
- '#lute-attack-effect#'
- '#two-handed#'
- '#handworn#'

View File

@ -5,7 +5,7 @@
<groupId>net.Indyuce</groupId>
<artifactId>MMOItems</artifactId>
<packaging>pom</packaging>
<version>6.9.5-SNAPSHOT</version>
<version>6.10-SNAPSHOT</version>
<modules>
<module>MMOItems-API</module>