From 4a5b0f49956e745c04d12a1fe6064a7e235d563b Mon Sep 17 00:00:00 2001 From: Vidal Date: Fri, 30 Jan 2026 20:21:45 -0300 Subject: [PATCH 1/9] Updated overrides and imports --- .../java/kamkeel/npcs/command/AbilityCommand.java | 3 +++ .../npcs/scripted/wrapper/ScriptDataAbilities.java | 5 ++++- .../scripted/wrapper/ScriptPlayerAbilityData.java | 12 +++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/kamkeel/npcs/command/AbilityCommand.java b/src/main/java/kamkeel/npcs/command/AbilityCommand.java index 499f9491d..d5b9420cc 100644 --- a/src/main/java/kamkeel/npcs/command/AbilityCommand.java +++ b/src/main/java/kamkeel/npcs/command/AbilityCommand.java @@ -4,6 +4,9 @@ import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; import net.minecraft.command.ICommandSender; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Set; import static kamkeel.npcs.util.ColorUtil.sendError; diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java index 6cbbb4dae..d0b5259ea 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java @@ -2,12 +2,15 @@ package noppes.npcs.scripted.wrapper; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; +import kamkeel.npcs.controllers.data.ability.AbilitySlot; import net.minecraft.entity.EntityLivingBase; import noppes.npcs.DataAbilities; import noppes.npcs.api.ability.IAbility; import noppes.npcs.api.ability.IDataAbilities; import noppes.npcs.entity.EntityNPCInterface; +import java.util.List; + /** * Script wrapper for DataAbilities providing API access to NPC abilities. */ @@ -65,7 +68,7 @@ public class ScriptDataAbilities implements IDataAbilities { @Override public boolean isAbilityReference(String abilityId) { - List slots = data.getAbilitySlots(); + List slots = data.getAbilitySlots(); for (int i = 0; i < slots.size(); i++) { kamkeel.npcs.controllers.data.ability.AbilitySlot slot = slots.get(i); if (slot.isReference()) { diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java index 2570653fc..a36c65d51 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java @@ -21,7 +21,7 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData { @Override public String[] getUnlockedAbilities() { - return data.getUnlockedAbilities().toArray(new String[0]); + return data.getUnlockedAbilities(); } @Override @@ -79,6 +79,11 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData { data.interruptCurrentAbility(); } + @Override + public boolean isOnCooldown() { + return data.isOnCooldown(); + } + @Override public boolean isOnCooldown(String key) { EntityPlayer player = playerData.player; @@ -86,6 +91,11 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData { return data.isOnCooldown(key, player); } + @Override + public void resetCooldown() { + data.resetCooldown(); + } + @Override public void resetCooldown(String key) { data.resetCooldown(key); From a8ca3293fdea207d6e03b9219dc3a309ffd0f850 Mon Sep 17 00:00:00 2001 From: Vidal Date: Fri, 30 Jan 2026 20:25:15 -0300 Subject: [PATCH 2/9] Removed wrong @param --- .../java/kamkeel/npcs/controllers/data/ability/Ability.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 6f66614e0..e31b8f8fd 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -369,7 +369,7 @@ public abstract class Ability implements IAbility { * Create a telegraph instance for this ability. * Override for custom telegraph shapes. * - * @param npc The caster + * @param caster The caster * @param target The target (for position calculation) * @return The telegraph instance, or null if no telegraph */ From 5dc27f7212edce361c1c4f7970df03165305c41c Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 31 Jan 2026 02:02:18 -0300 Subject: [PATCH 3/9] Updated anchor point method calls --- .../controllers/data/ability/data/EnergyAnchorData.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java index 39c7db9e7..66983f89b 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyAnchorData.java @@ -24,25 +24,25 @@ public class EnergyAnchorData { } public EnergyAnchorData(int anchorPoint) { - this.anchorPoint = AnchorPoint.fromId(anchorPoint); + this.anchorPoint = AnchorPoint.fromOrdinal(anchorPoint); } public EnergyAnchorData(int anchorPoint, float anchorOffsetX, float anchorOffsetY, float anchorOffsetZ) { - this.anchorPoint = AnchorPoint.fromId(anchorPoint); + this.anchorPoint = AnchorPoint.fromOrdinal(anchorPoint); this.anchorOffsetX = anchorOffsetX; this.anchorOffsetY = anchorOffsetY; this.anchorOffsetZ = anchorOffsetZ; } public void writeNBT(NBTTagCompound nbt) { - nbt.setInteger("anchorPoint", anchorPoint.getId()); + nbt.setInteger("anchorPoint", anchorPoint.ordinal()); nbt.setFloat("anchorOffsetX", anchorOffsetX); nbt.setFloat("anchorOffsetY", anchorOffsetY); nbt.setFloat("anchorOffsetZ", anchorOffsetZ); } public void readNBT(NBTTagCompound nbt) { - this.anchorPoint = nbt.hasKey("anchorPoint") ? AnchorPoint.fromId(nbt.getInteger("anchorPoint")) : AnchorPoint.FRONT; + this.anchorPoint = nbt.hasKey("anchorPoint") ? AnchorPoint.fromOrdinal(nbt.getInteger("anchorPoint")) : AnchorPoint.FRONT; this.anchorOffsetX = nbt.hasKey("anchorOffsetX") ? nbt.getFloat("anchorOffsetX") : 0; this.anchorOffsetY = nbt.hasKey("anchorOffsetY") ? nbt.getFloat("anchorOffsetY") : 0; this.anchorOffsetZ = nbt.hasKey("anchorOffsetZ") ? nbt.getFloat("anchorOffsetZ") : 0; From 3ffd62c2e7760da8a116a9d85c2c6802e2421321 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 31 Jan 2026 02:03:07 -0300 Subject: [PATCH 4/9] More animations --- .../animations/Ability_DualBeam_Active.json | 203 ++++++++++++++++++ .../animations/Ability_DualBeam_Windup.json | 201 +++++++++++++++++ 2 files changed, 404 insertions(+) create mode 100644 src/main/resources/assets/customnpcs/animations/Ability_DualBeam_Active.json create mode 100644 src/main/resources/assets/customnpcs/animations/Ability_DualBeam_Windup.json diff --git a/src/main/resources/assets/customnpcs/animations/Ability_DualBeam_Active.json b/src/main/resources/assets/customnpcs/animations/Ability_DualBeam_Active.json new file mode 100644 index 000000000..fa47349f9 --- /dev/null +++ b/src/main/resources/assets/customnpcs/animations/Ability_DualBeam_Active.json @@ -0,0 +1,203 @@ +{ + "Loop": 0, + "Speed": 1.0f, + "Frames": [ + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 3.0f, + "FrameParts": [ + { + "Pivot2": -1.0f, + "Part": "RIGHT_LEG", + "Rotation1": 31.0f, + "Rotation0": 18.0f, + "Pivot0": -3.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "LEFT_LEG", + "Rotation1": -31.0f, + "Rotation0": 18.0f, + "Pivot0": 3.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "RIGHT_ARM", + "Rotation1": -25.0f, + "Rotation0": -90.0f, + "Pivot0": 1.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "LEFT_ARM", + "Rotation1": 25.0f, + "Rotation0": -90.0f, + "Pivot0": -1.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 2 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 3.0f, + "FrameParts": [ + { + "Pivot2": -1.0f, + "Part": "RIGHT_LEG", + "Rotation1": 31.0f, + "Rotation0": 18.0f, + "Pivot0": -3.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "LEFT_LEG", + "Rotation1": -31.0f, + "Rotation0": 18.0f, + "Pivot0": 3.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "RIGHT_ARM", + "Rotation1": -25.0f, + "Rotation0": -85.0f, + "Pivot0": 1.0f, + "Pivot1": 1.0f, + "Rotation2": 20.0f + }, + { + "Pivot2": -3.0f, + "Part": "LEFT_ARM", + "Rotation1": 25.0f, + "Rotation0": -85.0f, + "Pivot0": -1.0f, + "Pivot1": 1.0f, + "Rotation2": -20.0f + }, + { + "Pivot2": -1.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 2 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 3.0f, + "FrameParts": [ + { + "Pivot2": -1.0f, + "Part": "RIGHT_LEG", + "Rotation1": 31.0f, + "Rotation0": 18.0f, + "Pivot0": -3.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "LEFT_LEG", + "Rotation1": -31.0f, + "Rotation0": 18.0f, + "Pivot0": 3.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "RIGHT_ARM", + "Rotation1": -25.0f, + "Rotation0": -85.0f, + "Pivot0": 1.0f, + "Pivot1": 1.0f, + "Rotation2": -20.0f + }, + { + "Pivot2": -3.0f, + "Part": "LEFT_ARM", + "Rotation1": 25.0f, + "Rotation0": -85.0f, + "Pivot0": -1.0f, + "Pivot1": 1.0f, + "Rotation2": 20.0f + }, + { + "Pivot2": -1.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 2 + } + ], + "CurrentFrameTime": 0, + "CurrentFrame": 3, + "Smooth": 0b, + "ID": 12, + "WhileAttacking": 1b, + "WhileWalking": 1b, + "Name": "Ability_DualBeam_Active", + "WhileStanding": 1b +} diff --git a/src/main/resources/assets/customnpcs/animations/Ability_DualBeam_Windup.json b/src/main/resources/assets/customnpcs/animations/Ability_DualBeam_Windup.json new file mode 100644 index 000000000..99437fc2d --- /dev/null +++ b/src/main/resources/assets/customnpcs/animations/Ability_DualBeam_Windup.json @@ -0,0 +1,201 @@ +{ + "Loop": -1, + "Speed": 1.0f, + "Frames": [ + { + "ColorMarker": 16777215, + "Comment": "", + "FrameParts": [ + { + "Pivot2": 0.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": -6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 18.0f + }, + { + "Pivot2": 0.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": -6.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": -18.0f + }, + { + "Pivot2": -5.0f, + "Part": "RIGHT_ARM", + "Rotation1": 90.0f, + "Rotation0": -90.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -5.0f, + "Part": "LEFT_ARM", + "Rotation1": -90.0f, + "Rotation0": -90.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -5.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 25.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -5.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Duration": 60 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 2.0f, + "FrameParts": [ + { + "Pivot2": 0.0f, + "Part": "RIGHT_LEG", + "Rotation1": 0.0f, + "Rotation0": 12.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": 18.0f + }, + { + "Pivot2": 0.0f, + "Part": "LEFT_LEG", + "Rotation1": 0.0f, + "Rotation0": 12.0f, + "Pivot0": 0.0f, + "Pivot1": 0.0f, + "Rotation2": -18.0f + }, + { + "Pivot2": 4.0f, + "Part": "RIGHT_ARM", + "Rotation1": 75.0f, + "Rotation0": -90.0f, + "Pivot0": 0.0f, + "Pivot1": 2.0f, + "Rotation2": -25.0f + }, + { + "Pivot2": 4.0f, + "Part": "LEFT_ARM", + "Rotation1": -75.0f, + "Rotation0": -90.0f, + "Pivot0": 0.0f, + "Pivot1": 2.0f, + "Rotation2": 25.0f + }, + { + "Pivot2": 3.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": -18.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": 3.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": -31.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 5 + }, + { + "ColorMarker": 16777215, + "Comment": "", + "Speed": 3.0f, + "FrameParts": [ + { + "Pivot2": -1.0f, + "Part": "RIGHT_LEG", + "Rotation1": 31.0f, + "Rotation0": 18.0f, + "Pivot0": -3.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "LEFT_LEG", + "Rotation1": -31.0f, + "Rotation0": 18.0f, + "Pivot0": 3.0f, + "Pivot1": 0.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "RIGHT_ARM", + "Rotation1": -25.0f, + "Rotation0": -90.0f, + "Pivot0": 1.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -3.0f, + "Part": "LEFT_ARM", + "Rotation1": 25.0f, + "Rotation0": -90.0f, + "Pivot0": -1.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "BODY", + "Rotation1": 0.0f, + "Rotation0": 6.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + }, + { + "Pivot2": -1.0f, + "Part": "HEAD", + "Rotation1": 0.0f, + "Rotation0": 0.0f, + "Pivot0": 0.0f, + "Pivot1": 1.0f, + "Rotation2": 0.0f + } + ], + "Smooth": 0b, + "Duration": 15 + } + ], + "CurrentFrameTime": 0, + "CurrentFrame": 2, + "Smooth": 0b, + "ID": 11, + "WhileAttacking": 1b, + "WhileWalking": 1b, + "Name": "Ability_DualBeam_Windup", + "WhileStanding": 1b +} From b9e82e4c5b1bded76ddab8d12dcaa08729b2dca0 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 31 Jan 2026 16:37:28 -0300 Subject: [PATCH 5/9] Fixed mysterious override stopping the game from running Kam let's please stop using claude for everything this BuiltInAbility is not even used --- src/api | 2 +- .../npcs/controllers/data/ability/BuiltInAbility.java | 8 -------- src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java | 6 ++++-- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/api b/src/api index db6a68e43..bf650319d 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit db6a68e43073b2828ec05a3c2ede660453c4544e +Subproject commit bf650319da422790bff294d570dd879143fe8d4c diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/BuiltInAbility.java b/src/main/java/kamkeel/npcs/controllers/data/ability/BuiltInAbility.java index 69151291f..904fad0b9 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/BuiltInAbility.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/BuiltInAbility.java @@ -31,14 +31,6 @@ public abstract class BuiltInAbility extends Ability { return registryKey; } - /** - * Built-in abilities are not customizable. - */ - @Override - public boolean isCustomizable() { - return false; - } - /** * Built-in abilities do not write type-specific NBT (they have fixed configs). */ diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java index 26f7a1ba7..f534f9dbc 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptNpc.java @@ -5,6 +5,7 @@ import net.minecraft.item.ItemStack; import noppes.npcs.NoppesUtilServer; import noppes.npcs.api.IPos; import noppes.npcs.api.ITimers; +import noppes.npcs.api.ability.IDataAbilities; import noppes.npcs.api.entity.ICustomNpc; import noppes.npcs.api.entity.IEntityLivingBase; import noppes.npcs.api.entity.IPlayer; @@ -51,6 +52,7 @@ import noppes.npcs.scripted.roles.ScriptRoleInterface; import noppes.npcs.scripted.roles.ScriptRoleMailman; import noppes.npcs.scripted.roles.ScriptRoleTrader; import noppes.npcs.scripted.roles.ScriptRoleTransporter; +import noppes.npcs.scripted.wrapper.ScriptDataAbilities; import noppes.npcs.util.ValueUtil; import java.util.ArrayList; @@ -1458,7 +1460,7 @@ public class ScriptNpc extends ScriptLiving imp return npc.stats.magicData; } - public noppes.npcs.api.ability.IDataAbilities getAbilityData() { - return new noppes.npcs.scripted.wrapper.ScriptDataAbilities(npc); + public IDataAbilities getAbilityData() { + return new ScriptDataAbilities(npc); } } From 9df8fbb88d9d0775cf4a0cbccc67f9f7141e0d7e Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 31 Jan 2026 19:22:43 -0300 Subject: [PATCH 6/9] Registry to add new Abilities, could possibly be used to register other stuff like animations --- src/main/java/kamkeel/npcs/util/Register.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/java/kamkeel/npcs/util/Register.java diff --git a/src/main/java/kamkeel/npcs/util/Register.java b/src/main/java/kamkeel/npcs/util/Register.java new file mode 100644 index 000000000..d12e49146 --- /dev/null +++ b/src/main/java/kamkeel/npcs/util/Register.java @@ -0,0 +1,36 @@ +package kamkeel.npcs.util; + +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AbilityController; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class Register { + private final String registryKey; + private final String namespace; + protected final Map> entries = new LinkedHashMap<>(); + + public Register(String registryKey, String namespace) { + this.registryKey = registryKey; + this.namespace = namespace; + } + + public T register(String factoryName, Supplier factory) { + entries.put(registryKey + "." + namespace + ":" + factoryName, factory); + return factory.get(); + } + + public static class Abilities extends Register { + public Abilities(String namespace) { + super("ability", namespace); + } + + public void register() { + for (Map.Entry> entry : entries.entrySet()) { + AbilityController.Instance.registerType(entry.getKey(), entry.getValue()); + } + } + } +} From 245e8f81626acb117692098e2a09b164564e9987 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 31 Jan 2026 21:55:39 -0300 Subject: [PATCH 7/9] Added Dual Beam Ability --- .../data/ability/AbilityController.java | 20 +- .../data/ability/type/AbilityDualBeam.java | 401 ++++++++++++++++++ .../npcs/entity/EntityAbilityBeam.java | 23 + .../ability/SubGuiAbilityDualBeam.java | 328 ++++++++++++++ 4 files changed, 754 insertions(+), 18 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualBeam.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualBeam.java diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java index 62d226bf7..5b3c15e05 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java @@ -1,23 +1,6 @@ package kamkeel.npcs.controllers.data.ability; -import kamkeel.npcs.controllers.data.ability.type.AbilitySweeper; -import kamkeel.npcs.controllers.data.ability.type.AbilityCharge; -import kamkeel.npcs.controllers.data.ability.type.AbilityCutter; -import kamkeel.npcs.controllers.data.ability.type.AbilityDash; -import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; -import kamkeel.npcs.controllers.data.ability.type.AbilityHazard; -import kamkeel.npcs.controllers.data.ability.type.AbilityHeal; -import kamkeel.npcs.controllers.data.ability.type.AbilityHeavyHit; -import kamkeel.npcs.controllers.data.ability.type.AbilityOrb; -import kamkeel.npcs.controllers.data.ability.type.AbilityDisc; -import kamkeel.npcs.controllers.data.ability.type.AbilityLaserShot; -import kamkeel.npcs.controllers.data.ability.type.AbilityEnergyBeam; -import kamkeel.npcs.controllers.data.ability.type.AbilityProjectile; -import kamkeel.npcs.controllers.data.ability.type.AbilityShockwave; -import kamkeel.npcs.controllers.data.ability.type.AbilitySlam; -import kamkeel.npcs.controllers.data.ability.type.AbilityTeleport; -import kamkeel.npcs.controllers.data.ability.type.AbilityTrap; -import kamkeel.npcs.controllers.data.ability.type.AbilityVortex; +import kamkeel.npcs.controllers.data.ability.type.*; import kamkeel.npcs.controllers.SyncController; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.CustomNpcs; @@ -383,6 +366,7 @@ public class AbilityController implements IAbilityHandler { registerType("cnpc:disc", AbilityDisc::new); registerType("cnpc:laser_shot", AbilityLaserShot::new); registerType("cnpc:beam", AbilityEnergyBeam::new); + registerType("cnpc:dual_beam", AbilityDualBeam::new); // Movement abilities registerType("cnpc:charge", AbilityCharge::new); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualBeam.java new file mode 100644 index 000000000..6704b0e33 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualBeam.java @@ -0,0 +1,401 @@ +package kamkeel.npcs.controllers.data.ability.type; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.LockMovementType; +import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.telegraph.Telegraph; +import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; +import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityAbilityBeam; +import kamkeel.npcs.util.AnchorPointHelper; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; +import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; +import noppes.npcs.client.gui.advanced.ability.SubGuiAbilityDualBeam; +import noppes.npcs.client.gui.util.IAbilityConfigCallback; +import noppes.npcs.entity.EntityNPCInterface; +import noppes.npcs.util.ValueUtil; + +public class AbilityDualBeam extends Ability { + private float beamWidth = 0.4f; + private float headSize = 0.6f; + private boolean dualFire= true; + private int dualFireDelay = 0; + + // Data classes + private EnergyColorData[] colorData = new EnergyColorData[]{ + new EnergyColorData(0xFFFFFF, 0x00AAFF, true, 0.4f, 0.5f, 6.0f), + new EnergyColorData(0xFFFFFF, 0xFF0000, true, 0.4f, 0.5f, 6.0f), + }; + private EnergyCombatData combatData = new EnergyCombatData(10.0f, 1.5f, 0.2f, false, 4.0f, 0.5f); + private EnergyHomingData homingData = new EnergyHomingData(0.4f, true, 0.1f, 15.0f); + private EnergyLightningData[] lightningData = new EnergyLightningData[]{ + new EnergyLightningData(), new EnergyLightningData() + }; + private EnergyLifespanData lifespanData = new EnergyLifespanData(25.0f, 200); + private EnergyAnchorData[] anchorData = new EnergyAnchorData[]{ + new EnergyAnchorData(AnchorPoint.RIGHT_HAND), new EnergyAnchorData(AnchorPoint.LEFT_HAND) + }; + + // Transient state for beam entity (used during windup charging) + private transient EntityAbilityBeam beamEntity = null; + private transient EntityAbilityBeam beamEntity2 = null; + + public AbilityDualBeam() { + this.typeId = "ability.cnpc.dual_beam"; + this.name = "Dual Beam"; + this.targetingMode = TargetingMode.AGGRO_TARGET; + this.maxRange = 20.0f; + this.minRange = 5.0f; + this.cooldownTicks = 0; + this.windUpTicks = 80; + this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE; + this.telegraphType = TelegraphType.CIRCLE; + this.showTelegraph = true; + // Default built-in animations + this.windUpAnimationName = "Ability_DualBeam_Windup"; + this.activeAnimationName = "Ability_DualBeam_Active"; + } + + @Override + public boolean hasTypeSettings() { + return true; + } + + @Override + @SideOnly(Side.CLIENT) + public SubGuiAbilityConfig createConfigGui(IAbilityConfigCallback callback) { + return new SubGuiAbilityDualBeam(this, callback); + } + + @Override + public boolean isTargetingModeLocked() { + return true; + } + + @Override + public TargetingMode[] getAllowedTargetingModes() { + return new TargetingMode[]{TargetingMode.AGGRO_TARGET}; + } + + @Override + public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) { + if (world.isRemote) { + signalCompletion(); + return; + } + + // Start firing the beam that was spawned during windup + if (beamEntity != null && !beamEntity.isDead) { + beamEntity.startFiring(target); + } + + if (dualFire) { + if (beamEntity2 != null && !beamEntity2.isDead && dualFireDelay <= 0) { + beamEntity2.startFiring(target); + } + } else { + beamEntity2.setDead(); + } + + // Ability stays active until entity dies (prevents firing another while projectile is alive) + // Movement locking is handled separately by the base class + } + + @Override + public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) { + if (world.isRemote) return; + + // Spawn beam in charging mode on first tick of windup + if (tick == 1) { + float offsetDist = 1.0f; + + // Create beam in charging mode - follows caster based on anchor point during windup + Vec3 spawnPos1 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[0], offsetDist); + beamEntity = new EntityAbilityBeam( + world, caster, target, + spawnPos1.xCoord, spawnPos1.yCoord, spawnPos1.zCoord, + beamWidth, headSize, + colorData[0], combatData, homingData, lightningData[0], lifespanData, + lockMovement.locksActive()); + + Vec3 spawnPos2 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[1], offsetDist); + beamEntity2 = new EntityAbilityBeam( + world, caster, target, + spawnPos2.xCoord, spawnPos2.yCoord, spawnPos2.zCoord, + beamWidth, headSize, + colorData[1], combatData, homingData, lightningData[1], lifespanData, + lockMovement.locksActive()); + + beamEntity.setSiblingUUID(beamEntity2.getPersistentID()); + beamEntity2.setSiblingUUID(beamEntity.getPersistentID()); + + beamEntity.setupCharging(anchorData[0], windUpTicks, offsetDist); + beamEntity2.setupCharging(anchorData[1], windUpTicks, offsetDist); + + beamEntity.setEffects(this.effects); + if (dualFire) beamEntity2.setEffects(this.effects); + + world.spawnEntityInWorld(beamEntity); + world.spawnEntityInWorld(beamEntity2); + } + } + + @Override + public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) { + // Signal completion when entity dies + if (dualFire) { + if ((beamEntity == null || beamEntity.isDead) && (beamEntity2 == null || beamEntity2.isDead)) { + beamEntity = null; + beamEntity2 = null; + signalCompletion(); + } + + if (tick == dualFireDelay && beamEntity2 != null && !beamEntity2.isDead) { + beamEntity2.startFiring(target); + } + } else { + if (beamEntity == null || beamEntity.isDead) { + beamEntity = null; + signalCompletion(); + } + } + } + + @Override + public void onComplete(EntityLivingBase caster, EntityLivingBase target) { + } + + @Override + public void cleanup() { + // Despawn beam entity if still alive + if (beamEntity != null && !beamEntity.isDead) { + beamEntity.setDead(); + } + + if (beamEntity2 != null && !beamEntity2.isDead) { + beamEntity2.setDead(); + } + + beamEntity = null; + beamEntity2 = null; + } + + @Override + public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) { + if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) { + return null; + } + + // Create circle telegraph at target position + Telegraph telegraph = Telegraph.circle(headSize * 2.0f); + telegraph.setDurationTicks(windUpTicks); + telegraph.setColor(windUpColor); + telegraph.setWarningColor(activeColor); + telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4)); + telegraph.setHeightOffset(telegraphHeightOffset); + + // Position at target and follow target during windup + TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, caster.rotationYaw); + instance.setCasterEntityId(caster.getEntityId()); + instance.setEntityIdToFollow(target.getEntityId()); + + return instance; + } + + @Override + public float getTelegraphRadius() { + return headSize * 2.0f; + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setFloat("beamWidth", beamWidth); + nbt.setFloat("headSize", headSize); + nbt.setBoolean("dualFire", dualFire); + nbt.setInteger("dualFireDelay", dualFireDelay); + combatData.writeNBT(nbt); + homingData.writeNBT(nbt); + lifespanData.writeNBT(nbt); + + NBTTagCompound beam1 = new NBTTagCompound(); + NBTTagCompound beam2 = new NBTTagCompound(); + + anchorData[0].writeNBT(beam1); + anchorData[1].writeNBT(beam2); + colorData[0].writeNBT(beam1); + colorData[1].writeNBT(beam2); + lightningData[0].writeNBT(beam1); + lightningData[1].writeNBT(beam2); + + nbt.setTag("Beam_1", beam1); + nbt.setTag("Beam_2", beam2); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + NBTTagCompound beam1 = nbt.hasKey("Beam_1") ? nbt.getCompoundTag("Beam_1") : new NBTTagCompound(); + NBTTagCompound beam2 = nbt.hasKey("Beam_2") ? nbt.getCompoundTag("Beam_2") : new NBTTagCompound(); + + this.beamWidth = nbt.hasKey("beamWidth") ? nbt.getFloat("beamWidth") : 0.4f; + this.headSize = nbt.hasKey("headSize") ? nbt.getFloat("headSize") : 0.6f; + this.dualFire = !nbt.hasKey("dualFire") || nbt.getBoolean("dualFire"); + this.dualFireDelay = nbt.hasKey("dualFireDelay") ? nbt.getInteger("dualFireDelay") : 0; + combatData.readNBT(nbt); + homingData.readNBT(nbt); + lifespanData.readNBT(nbt); + + anchorData[0].readNBT(beam1); + anchorData[1].readNBT(beam2); + colorData[0].readNBT(beam1); + colorData[1].readNBT(beam2); + lightningData[0].readNBT(beam1); + lightningData[1].readNBT(beam2); + } + + // Getters & Setters - Standalone fields + public float getBeamWidth() { return beamWidth; } + public void setBeamWidth(float beamWidth) { this.beamWidth = beamWidth; } + public float getHeadSize() { return headSize; } + public void setHeadSize(float headSize) { this.headSize = headSize; } + public boolean isDualFire() { return dualFire; } + public void setDualFire(boolean dualFire) { this.dualFire = dualFire; } + public int getDualFireDelay() { return dualFireDelay; } + public void setDualFireDelay(int dualFireDelay) { this.dualFireDelay = dualFireDelay; } + + private EnergyColorData getColorData(int beam) { + beam = ValueUtil.clamp(beam, 0, 1); + return colorData[beam]; + } + private EnergyLightningData getLightningData(int beam) { + beam = ValueUtil.clamp(beam, 0, 1); + return lightningData[beam]; + } + private EnergyAnchorData getAnchorData(int beam) { + beam = ValueUtil.clamp(beam, 0, 1); + return anchorData[beam]; + } + + // Getters & Setters - Color data + public int getInnerColor(int beam) { return getColorData(beam).innerColor; } + public void setInnerColor(int beam, int innerColor) { getColorData(beam).innerColor = innerColor; } + public int getOuterColor(int beam) { return getColorData(beam).outerColor; } + public void setOuterColor(int beam, int outerColor) { getColorData(beam).outerColor = outerColor; } + public boolean isOuterColorEnabled(int beam) { return getColorData(beam).outerColorEnabled; } + public void setOuterColorEnabled(int beam, boolean enabled) { getColorData(beam).outerColorEnabled = enabled; } + public float getOuterColorWidth(int beam) { return getColorData(beam).outerColorWidth; } + public void setOuterColorWidth(int beam, float width) { getColorData(beam).outerColorWidth = width; } + public float getOuterColorAlpha(int beam) { return getColorData(beam).outerColorAlpha; } + public void setOuterColorAlpha(int beam, float alpha) { getColorData(beam).outerColorAlpha = alpha; } + public float getRotationSpeed(int beam) { return getColorData(beam).rotationSpeed; } + public void setRotationSpeed(int beam, float speed) { getColorData(beam).rotationSpeed = speed; } + + + public int getInnerColor() { return getInnerColor(0); } + public void setInnerColor(int innerColor) { setInnerColor(0, innerColor); } + public int getOuterColor() { return getOuterColor(0); } + public void setOuterColor(int outerColor) { setOuterColor(0, outerColor); } + public boolean isOuterColorEnabled() { return isOuterColorEnabled(0); } + public void setOuterColorEnabled(boolean enabled) { setOuterColorEnabled(0, enabled); } + public float getOuterColorWidth() { return getOuterColorWidth(0); } + public void setOuterColorWidth(float width) { setOuterColorWidth(0, width); } + public float getOuterColorAlpha() { return getOuterColorAlpha(0); } + public void setOuterColorAlpha(float alpha) { setOuterColorAlpha(0, alpha); } + public float getRotationSpeed() { return getRotationSpeed(0); } + public void setRotationSpeed(float speed) { setRotationSpeed(0, speed); } + + // Getters & Setters - Combat data + public float getDamage() { return combatData.damage; } + public void setDamage(float damage) { this.combatData.damage = damage; } + public float getKnockback() { return combatData.knockback; } + public void setKnockback(float knockback) { this.combatData.knockback = knockback; } + public float getKnockbackUp() { return combatData.knockbackUp; } + public void setKnockbackUp(float knockbackUp) { this.combatData.knockbackUp = knockbackUp; } + public boolean isExplosive() { return combatData.explosive; } + public void setExplosive(boolean explosive) { this.combatData.explosive = explosive; } + public float getExplosionRadius() { return combatData.explosionRadius; } + public void setExplosionRadius(float explosionRadius) { this.combatData.explosionRadius = explosionRadius; } + public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; } + public void setExplosionDamageFalloff(float explosionDamageFalloff) { this.combatData.explosionDamageFalloff = explosionDamageFalloff; } + + // Getters & Setters - Homing data + public float getSpeed() { return homingData.speed; } + public void setSpeed(float speed) { this.homingData.speed = speed; } + public boolean isHoming() { return homingData.homing; } + public void setHoming(boolean homing) { this.homingData.homing = homing; } + public float getHomingStrength() { return homingData.homingStrength; } + public void setHomingStrength(float homingStrength) { this.homingData.homingStrength = homingStrength; } + public float getHomingRange() { return homingData.homingRange; } + public void setHomingRange(float homingRange) { this.homingData.homingRange = homingRange; } + + // Getters & Setters - Lightning data + public boolean hasLightningEffect(int beam) { return getLightningData(beam).lightningEffect; } + public void setLightningEffect(int beam, boolean lightningEffect) { getLightningData(beam).lightningEffect = lightningEffect; } + public float getLightningDensity(int beam) { return getLightningData(beam).lightningDensity; } + public void setLightningDensity(int beam, float lightningDensity) { getLightningData(beam).lightningDensity = lightningDensity; } + public float getLightningRadius(int beam) { return getLightningData(beam).lightningRadius; } + public void setLightningRadius(int beam, float lightningRadius) { getLightningData(beam).lightningRadius = lightningRadius; } + + public boolean hasLightningEffect() { return hasLightningEffect(0); } + public void setLightningEffect(boolean lightningEffect) { setLightningEffect(0, lightningEffect); } + public float getLightningDensity() { return getLightningDensity(0); } + public void setLightningDensity(float lightningDensity) { setLightningDensity(0, lightningDensity); } + public float getLightningRadius() { return getLightningRadius(0); } + public void setLightningRadius(float lightningRadius) { setLightningRadius(0, lightningRadius); } + + + // Getters & Setters - Lifespan data + public float getMaxDistance() { return lifespanData.maxDistance; } + public void setMaxDistance(float maxDistance) { this.lifespanData.maxDistance = maxDistance; } + public int getMaxLifetime() { return lifespanData.maxLifetime; } + public void setMaxLifetime(int maxLifetime) { this.lifespanData.maxLifetime = maxLifetime; } + + // Getters & Setters - Anchor point + public AnchorPoint getAnchorPointEnum(int beam) { return getAnchorData(beam).anchorPoint; } + public void setAnchorPointEnum(int beam, AnchorPoint anchorPoint) { getAnchorData(beam).anchorPoint = anchorPoint; } + public float getAnchorOffsetX(int beam) { return getAnchorData(beam).anchorOffsetX; } + public void setAnchorOffsetX(int beam, float x) { getAnchorData(beam).anchorOffsetX = x; } + public float getAnchorOffsetY(int beam) { return getAnchorData(beam).anchorOffsetY; } + public void setAnchorOffsetY(int beam, float y) { getAnchorData(beam).anchorOffsetY = y; } + public float getAnchorOffsetZ(int beam) { return getAnchorData(beam).anchorOffsetZ; } + public void setAnchorOffsetZ(int beam, float z) { getAnchorData(beam).anchorOffsetZ = z; } + + public AnchorPoint getAnchorPointEnum() { return getAnchorPointEnum(0); } + public void setAnchorPointEnum(AnchorPoint anchorPoint) { setAnchorPointEnum(0, anchorPoint); } + public float getAnchorOffsetX() { return getAnchorOffsetX(0); } + public void setAnchorOffsetX(float x) { setAnchorOffsetX(0, x); } + public float getAnchorOffsetY() { return getAnchorOffsetY(0); } + public void setAnchorOffsetY(float y) { setAnchorOffsetY(0, y); } + public float getAnchorOffsetZ() { return getAnchorOffsetZ(0); } + public void setAnchorOffsetZ(float z) { setAnchorOffsetZ(0, z); } + + //@Override + public int getAnchorPoint(int beam) { return getAnchorData(beam).anchorPoint.ordinal(); } + public int getAnchorPoint() { return getAnchorData(0).anchorPoint.ordinal(); } + + //@Override + public void setAnchorPoint(int beam, int point) { getAnchorData(beam).anchorPoint = AnchorPoint.fromOrdinal(point); } + public void setAnchorPoint(int point) { getAnchorData(0).anchorPoint = AnchorPoint.fromOrdinal(point); } + + @Override + @SideOnly(Side.CLIENT) + public Entity createPreviewEntity(EntityNPCInterface npc) { + if (npc == null || npc.worldObj == null) return null; + + EntityAbilityBeam beam = new EntityAbilityBeam(npc.worldObj); + beam.setupPreview(npc, beamWidth, headSize, colorData[0], lightningData[0], anchorData[0], windUpTicks, 1.0f); + return beam; + } + + @Override + public int getPreviewActiveDuration() { + return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100; + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index a9cfffc43..bceae89aa 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -15,6 +15,7 @@ import noppes.npcs.LogWriter; import java.util.ArrayList; import java.util.List; +import java.util.UUID; /** * Beam projectile - head with trailing path that curves with homing. @@ -32,6 +33,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { private boolean homing = true; private float homingStrength = 0.35f; // Increased from 0.15 for better tracking private float homingRange = 20.0f; + private UUID siblingUUID = null; // Beam shape properties private float beamWidth = 0.3f; @@ -646,6 +648,12 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { } } + @Override + protected boolean shouldIgnoreEntity(Entity entity) { + if (entity.getPersistentID().equals(siblingUUID)) return true; + return super.shouldIgnoreEntity(entity); + } + // ==================== GETTERS FOR RENDERER ==================== public float getBeamWidth() { @@ -744,6 +752,18 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { return trailFadeTime; } + public UUID getSiblingUUID() { + return siblingUUID; + } + + public void setSiblingUUID(UUID siblingUUID) { + if (siblingUUID == null) + return; + + if (this.siblingUUID == null) + this.siblingUUID = siblingUUID; + } + // ==================== NBT ==================== @Override @@ -787,6 +807,8 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { )); } } + + this.siblingUUID = nbt.hasKey("SiblingUUID") ? UUID.fromString(nbt.getString("SiblingUUID")) : null; } @Override @@ -821,5 +843,6 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { trailList.appendTag(pointNbt); } nbt.setTag("Trail", trailList); + nbt.setString("SiblingUUID", siblingUUID.toString()); } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualBeam.java b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualBeam.java new file mode 100644 index 000000000..87144adfc --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualBeam.java @@ -0,0 +1,328 @@ +package noppes.npcs.client.gui.advanced.ability; + +import kamkeel.npcs.controllers.data.ability.AbilityEffect; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.type.AbilityDualBeam; +import noppes.npcs.client.gui.SubGuiColorSelector; +import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; +import noppes.npcs.client.gui.util.*; + +import java.util.List; + +public class SubGuiAbilityDualBeam extends SubGuiAbilityConfig { + private final AbilityDualBeam beam; + private int editingVisualColorId = 0; + private int editingBeam = 0; + + public SubGuiAbilityDualBeam(AbilityDualBeam ability, IAbilityConfigCallback callback) { + super(ability, callback); + this.beam = ability; + } + + @Override + protected boolean hasVisualSettings() { + return true; + } + + @Override + protected void initTypeTab(int startY) { + int y = startY; + int labelX = guiLeft + 8; + int fieldX = guiLeft + 85; + int col2LabelX = guiLeft + 145; + int col2FieldX = guiLeft + 205; + + // Row 1: Damage + Speed + addLabel(new GuiNpcLabel(100, "enchantment.damage", labelX, y + 5)); + addTextField(createFloatField(100, fieldX, y, 50, beam.getDamage())); + + addLabel(new GuiNpcLabel(101, "stats.speed", col2LabelX, y + 5)); + addTextField(createFloatField(101, col2FieldX, y, 50, beam.getSpeed())); + + y += 24; + + // Row 2: Beam Width + Head Size + addLabel(new GuiNpcLabel(102, "ability.beamWidth", labelX, y + 5)); + addTextField(createFloatField(102, fieldX, y, 50, beam.getBeamWidth())); + + addLabel(new GuiNpcLabel(103, "ability.headSize", col2LabelX, y + 5)); + addTextField(createFloatField(103, col2FieldX, y, 50, beam.getHeadSize())); + + y += 24; + + // Row 3: Homing + Homing Strength (only show strength if homing) + addLabel(new GuiNpcLabel(104, "ability.homing", labelX, y + 5)); + GuiNpcButton homingBtn = new GuiNpcButton(104, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, beam.isHoming() ? 1 : 0); + homingBtn.setHoverText("ability.hover.homing"); + addButton(homingBtn); + + if (beam.isHoming()) { + addLabel(new GuiNpcLabel(105, "ability.homingStr", col2LabelX, y + 5)); + addTextField(createFloatField(105, col2FieldX, y, 50, beam.getHomingStrength())); + } + + y += 24; + + // Row 4: Max Distance (+ Homing Range only if homing) + if (beam.isHoming()) { + addLabel(new GuiNpcLabel(106, "ability.homingRange", labelX, y + 5)); + addTextField(createFloatField(106, fieldX, y, 50, beam.getHomingRange())); + } + + addLabel(new GuiNpcLabel(107, "ability.maxDist", beam.isHoming() ? col2LabelX : labelX, y + 5)); + addTextField(createFloatField(107, beam.isHoming() ? col2FieldX : fieldX, y, 50, beam.getMaxDistance())); + + y += 24; + + // Row 5: Explosive + Explosion Radius (only show radius if explosive) + addLabel(new GuiNpcLabel(108, "ability.explosive", labelX, y + 5)); + GuiNpcButton explosiveBtn = new GuiNpcButton(108, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, beam.isExplosive() ? 1 : 0); + explosiveBtn.setHoverText("ability.hover.explosive"); + addButton(explosiveBtn); + + if (beam.isExplosive()) { + addLabel(new GuiNpcLabel(109, "ability.explosionRad", col2LabelX, y + 5)); + addTextField(createFloatField(109, col2FieldX, y, 50, beam.getExplosionRadius())); + } + + y += 24; + + // Row 6: Knockback + Max Lifetime + addLabel(new GuiNpcLabel(110, "ability.knockback", labelX, y + 5)); + addTextField(createFloatField(110, fieldX, y, 50, beam.getKnockback())); + + addLabel(new GuiNpcLabel(111, "ability.lifetime", col2LabelX, y + 5)); + addTextField(createIntField(111, col2FieldX, y, 50, beam.getMaxLifetime())); + + y += 24; + + addLabel(new GuiNpcLabel(112, "ability.dualFire", labelX, y + 5)); + addButton(new GuiNpcButtonYesNo(112, fieldX, y, beam.isDualFire())); + + if (beam.isDualFire()) { + addLabel(new GuiNpcLabel(113, "ability.dualFireDelay", col2LabelX, y + 5)); + addTextField(createIntField(113, col2FieldX, y, 50, beam.getDualFireDelay())); + } + + y += 24; + + addButton(new GuiNpcButton(150, labelX, y, 80, 20, "ability.effects")); + } + + @Override + protected void initVisualTab(int startY) { + int y = startY; + int labelX = guiLeft + 8; + int fieldX = guiLeft + 85; + int col2LabelX = guiLeft + 180; + int col2FieldX = guiLeft + 260; + + // Row 1: Anchor Point + addLabel(new GuiNpcLabel(210, "ability.anchorPoint", labelX, y + 5)); + GuiNpcButton anchorBtn = new GuiNpcButton(210, fieldX, y, 80, 20, AnchorPoint.getDisplayNames(), beam.getAnchorPointEnum(editingBeam).ordinal()); + anchorBtn.setHoverText("ability.hover.anchorPoint"); + addButton(anchorBtn); + + y += 24; + + // Row 2: Inner Color + Outer Color + addLabel(new GuiNpcLabel(200, "ability.innerColor", labelX, y + 5)); + String innerHex = String.format("%06X", beam.getInnerColor(editingBeam) & 0xFFFFFF); + GuiNpcButton innerColorBtn = new GuiNpcButton(200, fieldX, y, 55, 20, innerHex); + innerColorBtn.setTextColor(beam.getInnerColor(editingBeam) & 0xFFFFFF); + addButton(innerColorBtn); + + addLabel(new GuiNpcLabel(201, "ability.outerColor", col2LabelX, y + 5)); + String outerHex = String.format("%06X", beam.getOuterColor(editingBeam) & 0xFFFFFF); + GuiNpcButton outerColorBtn = new GuiNpcButton(201, col2FieldX, y, 55, 20, outerHex); + outerColorBtn.setTextColor(beam.getOuterColor(editingBeam) & 0xFFFFFF); + outerColorBtn.setEnabled(beam.isOuterColorEnabled(editingBeam)); + addButton(outerColorBtn); + + y += 24; + + // Row 2: Outer Color Enabled + Outer Color Width + addLabel(new GuiNpcLabel(202, "ability.outerEnabled", labelX, y + 5)); + addButton(new GuiNpcButton(202, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, beam.isOuterColorEnabled(editingBeam) ? 1 : 0)); + + addLabel(new GuiNpcLabel(203, "ability.outerWidth", col2LabelX, y + 5)); + GuiNpcTextField widthField = createFloatField(203, col2FieldX, y, 55, beam.getOuterColorWidth(editingBeam)); + widthField.setEnabled(beam.isOuterColorEnabled(editingBeam)); + addTextField(widthField); + + y += 24; + + // Row 3: Rotation Speed + addLabel(new GuiNpcLabel(204, "ability.outerAlpha", labelX, y + 5)); + GuiNpcTextField alphaField = createFloatField(204, fieldX, y, 55, beam.getOuterColorAlpha(editingBeam)); + alphaField.setEnabled(beam.isOuterColorEnabled(editingBeam)); + alphaField.setMinMaxDefaultFloat(0, 1, 0.5f); + addTextField(alphaField); + + addLabel(new GuiNpcLabel(205, "ability.rotationSpeed", col2LabelX, y + 5)); + addTextField(createFloatField(205, col2FieldX, y, 50, beam.getRotationSpeed(editingBeam))); + + y += 24; + + // Row 4: Lightning Effect Enabled (only affects head) + addLabel(new GuiNpcLabel(206, "ability.lightning", labelX, y + 5)); + GuiNpcButton lightningBtn = new GuiNpcButton(206, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, beam.hasLightningEffect(editingBeam) ? 1 : 0); + lightningBtn.setHoverText("ability.hover.lightning"); + addButton(lightningBtn); + + // Only show Lightning settings if Lightning is enabled + if (beam.hasLightningEffect(editingBeam)) { + y += 24; + + // Row 5: Density + Radius + addLabel(new GuiNpcLabel(207, "ability.lightningDensity", labelX, y + 5)); + GuiNpcTextField densityField = new GuiNpcTextField(207, this, fontRendererObj, fieldX, y, 55, 18, "" + beam.getLightningDensity(editingBeam)); + densityField.setMinMaxDefaultFloat(0.01f, 5.0f, 0.15f); + addTextField(densityField); + + addLabel(new GuiNpcLabel(208, "ability.lightningRadius", col2LabelX, y + 5)); + GuiNpcTextField radiusField = new GuiNpcTextField(208, this, fontRendererObj, col2FieldX, y, 55, 18, "" + beam.getLightningRadius(editingBeam)); + radiusField.setMinMaxDefaultFloat(0.1f, 10.0f, 0.5f); + addTextField(radiusField); + } + + y += 24; + + addButton(new GuiNpcButton(400, col2FieldX, y, 55, 20, new String[]{"Beam 1", "Beam 2"}, editingBeam)); + } + + @Override + protected void handleTypeButton(int id, GuiNpcButton button) { + int value = button.getValue(); + switch (id) { + case 104: + beam.setHoming(value == 1); + initGui(); + break; + case 108: + beam.setExplosive(value == 1); + initGui(); + break; + case 112: + beam.setDualFire(value == 1); + initGui(); + break; + case 150: + setSubGui(new SubGuiAbilityEffects(beam.getEffects())); + break; + } + } + + @Override + protected void handleVisualButton(int id, GuiNpcButton button) { + int value = button.getValue(); + switch (id) { + case 200: + editingVisualColorId = 200; + setSubGui(new SubGuiColorSelector(beam.getInnerColor(editingBeam))); + break; + case 201: + editingVisualColorId = 201; + setSubGui(new SubGuiColorSelector(beam.getOuterColor(editingBeam))); + break; + case 202: + beam.setOuterColorEnabled(editingBeam, value == 1); + initGui(); + break; + case 206: + beam.setLightningEffect(editingBeam, value == 1); + initGui(); + break; + case 210: + beam.setAnchorPointEnum(editingBeam, AnchorPoint.fromOrdinal(value)); + break; + case 400: + editingBeam = (editingBeam + 1) % 2; + initGui(); + break; + } + } + + @Override + public void subGuiClosed(SubGuiInterface subgui) { + if (subgui instanceof SubGuiColorSelector && editingVisualColorId != 0) { + SubGuiColorSelector colorSelector = (SubGuiColorSelector) subgui; + int rgb = colorSelector.color & 0x00FFFFFF; + if (editingVisualColorId == 200) { + beam.setInnerColor(editingBeam, rgb); + } else if (editingVisualColorId == 201) { + beam.setOuterColor(editingBeam, rgb); + } + editingVisualColorId = 0; + initGui(); + } else if (subgui instanceof SubGuiAbilityEffects) { + SubGuiAbilityEffects effectsGui = (SubGuiAbilityEffects) subgui; + List result = effectsGui.getResult(); + if (result != null) { + beam.setEffects(result); + } + } else { + super.subGuiClosed(subgui); + } + } + + @Override + protected void handleTypeTextField(int id, GuiNpcTextField field) { + switch (id) { + case 100: + beam.setDamage(parseFloat(field, beam.getDamage())); + break; + case 101: + beam.setSpeed(parseFloat(field, beam.getSpeed())); + break; + case 102: + beam.setBeamWidth(parseFloat(field, beam.getBeamWidth())); + break; + case 103: + beam.setHeadSize(parseFloat(field, beam.getHeadSize())); + break; + case 105: + beam.setHomingStrength(parseFloat(field, beam.getHomingStrength())); + break; + case 106: + beam.setHomingRange(parseFloat(field, beam.getHomingRange())); + break; + case 107: + beam.setMaxDistance(parseFloat(field, beam.getMaxDistance())); + break; + case 109: + beam.setExplosionRadius(parseFloat(field, beam.getExplosionRadius())); + break; + case 110: + beam.setKnockback(parseFloat(field, beam.getKnockback())); + break; + case 111: + beam.setMaxLifetime(field.getInteger()); + break; + case 113: + beam.setDualFireDelay(field.getInteger()); + break; + } + } + + @Override + protected void handleVisualTextField(int id, GuiNpcTextField field) { + switch (id) { + case 203: + beam.setOuterColorWidth(editingBeam, parseFloat(field, beam.getOuterColorWidth(editingBeam))); + break; + case 204: + beam.setOuterColorAlpha(editingBeam, parseFloat(field, beam.getOuterColorAlpha(editingBeam))); + break; + case 205: + beam.setRotationSpeed(editingBeam, parseFloat(field, beam.getRotationSpeed(editingBeam))); + break; + case 207: + beam.setLightningDensity(editingBeam, parseFloat(field, beam.getLightningDensity(editingBeam))); + break; + case 208: + beam.setLightningRadius(editingBeam, parseFloat(field, beam.getLightningRadius(editingBeam))); + break; + } + } +} From e9f22dab2a59ab3fb4e98edeb0c70ccb923c7627 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 31 Jan 2026 21:56:14 -0300 Subject: [PATCH 8/9] Added Dual Orb Ability --- .../data/ability/AbilityController.java | 1 + .../data/ability/type/AbilityDualOrb.java | 481 ++++++++++++++++++ .../kamkeel/npcs/entity/EntityAbilityOrb.java | 15 + .../ability/SubGuiAbilityDualOrb.java | 313 ++++++++++++ 4 files changed, 810 insertions(+) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualOrb.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualOrb.java diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java index 5b3c15e05..5603ac4ff 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java @@ -363,6 +363,7 @@ public class AbilityController implements IAbilityHandler { registerType("cnpc:sweeper", AbilitySweeper::new); registerType("cnpc:projectile", AbilityProjectile::new); registerType("cnpc:orb", AbilityOrb::new); + registerType("cnpc:dual_orb", AbilityDualOrb::new); registerType("cnpc:disc", AbilityDisc::new); registerType("cnpc:laser_shot", AbilityLaserShot::new); registerType("cnpc:beam", AbilityEnergyBeam::new); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualOrb.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualOrb.java new file mode 100644 index 000000000..baddfb910 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualOrb.java @@ -0,0 +1,481 @@ +package kamkeel.npcs.controllers.data.ability.type; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.LockMovementType; +import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.telegraph.Telegraph; +import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; +import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityAbilityOrb; +import kamkeel.npcs.util.AnchorPointHelper; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; +import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; +import noppes.npcs.client.gui.advanced.ability.SubGuiAbilityDualOrb; +import noppes.npcs.client.gui.util.IAbilityConfigCallback; +import noppes.npcs.entity.EntityNPCInterface; +import noppes.npcs.util.ValueUtil; + +public class AbilityDualOrb extends Ability { + // Ability-specific properties + private float orbSize = 1.0f; + private boolean dualFire= true; + private int dualFireDelay = 20; + + // Data classes for energy properties + public final EnergyColorData[] colorData = new EnergyColorData[]{ + new EnergyColorData(), new EnergyColorData() + }; + public final EnergyCombatData combatData = new EnergyCombatData(); + public final EnergyHomingData homingData = new EnergyHomingData(); + public final EnergyLightningData[] lightningData = new EnergyLightningData[]{ + new EnergyLightningData(), new EnergyLightningData() + }; + public final EnergyLifespanData lifespanData = new EnergyLifespanData(); + private EnergyAnchorData[] anchorData = new EnergyAnchorData[]{ + new EnergyAnchorData(AnchorPoint.RIGHT_HAND), new EnergyAnchorData(AnchorPoint.LEFT_HAND) + }; + + // Transient state for orb entity (used during windup charging) + private transient EntityAbilityOrb orbEntity = null; + private transient EntityAbilityOrb orbEntity2 = null; + + public AbilityDualOrb() { + this.typeId = "ability.cnpc.dual_orb"; + this.name = "Dual Orb"; + this.targetingMode = TargetingMode.AGGRO_TARGET; + this.maxRange = 25.0f; + this.minRange = 5.0f; + this.cooldownTicks = 0; + this.windUpTicks = 30; + this.lockMovement = LockMovementType.WINDUP; + this.telegraphType = TelegraphType.CIRCLE; + this.showTelegraph = true; + // Default built-in animation + this.windUpAnimationName = "Ability_DualBeam_Windup"; + this.activeAnimationName = "Ability_DualBeam_Active"; + } + + @Override + public boolean hasTypeSettings() { + return true; + } + + @Override + @SideOnly(Side.CLIENT) + public SubGuiAbilityConfig createConfigGui(IAbilityConfigCallback callback) { + return new SubGuiAbilityDualOrb(this, callback); + } + + @Override + public boolean isTargetingModeLocked() { + return true; + } + + @Override + public TargetingMode[] getAllowedTargetingModes() { + return new TargetingMode[]{TargetingMode.AGGRO_TARGET}; + } + + @Override + public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) { + if (world.isRemote) { + signalCompletion(); + return; + } + + // Start moving the orb that was spawned during windup + if (orbEntity != null && !orbEntity.isDead) { + orbEntity.startMoving(target); + } + + if (dualFire) { + if (orbEntity2 != null && !orbEntity2.isDead && dualFireDelay <= 0) { + orbEntity2.startMoving(target); + } + } else { + orbEntity2.setDead(); + } + + // Ability stays active until entity dies (prevents firing another while projectile is alive) + // Movement locking is handled separately by the base class + } + + @Override + public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) { + if (world.isRemote) return; + + // Spawn orb in charging mode on first tick of windup + if (tick == 1) { + // Create orb in charging mode - follows caster based on anchor point during windup + Vec3 spawnPos1 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[0]); + orbEntity = new EntityAbilityOrb( + world, caster, target, + spawnPos1.xCoord, spawnPos1.yCoord, spawnPos1.zCoord, orbSize, + colorData[0], combatData, homingData, lightningData[0], lifespanData); + + Vec3 spawnPos2 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[1]); + orbEntity2 = new EntityAbilityOrb( + world, caster, target, + spawnPos2.xCoord, spawnPos2.yCoord, spawnPos2.zCoord, orbSize, + colorData[1], combatData, homingData, lightningData[1], lifespanData); + + orbEntity.setSiblingUUID(orbEntity2.getPersistentID()); + orbEntity2.setSiblingUUID(orbEntity.getPersistentID()); + + orbEntity.setupCharging(anchorData[0], windUpTicks); + orbEntity2.setupCharging(anchorData[1], windUpTicks); + + orbEntity.setEffects(this.effects); + + if (dualFire) { + orbEntity2.setEffects(this.effects); + } + + world.spawnEntityInWorld(orbEntity); + world.spawnEntityInWorld(orbEntity2); + } + } + + @Override + public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) { + // Signal completion when entity dies + if (dualFire) { + if ((orbEntity == null || orbEntity.isDead) && (orbEntity2 == null || orbEntity2.isDead)) { + orbEntity = null; + orbEntity2 = null; + signalCompletion(); + } + + if (tick == dualFireDelay && orbEntity2 != null && !orbEntity2.isDead) { + orbEntity2.startMoving(target); + } + } else { + if (orbEntity == null || orbEntity.isDead) { + orbEntity = null; + signalCompletion(); + } + } + } + + @Override + public void onComplete(EntityLivingBase caster, EntityLivingBase target) { + // Nothing to clean up - entity manages itself + } + + @Override + public void cleanup() { + // Despawn orb entity if still alive + if (orbEntity != null && !orbEntity.isDead) { + orbEntity.setDead(); + } + + if (orbEntity2 != null && !orbEntity2.isDead) { + orbEntity2.setDead(); + } + + orbEntity = null; + orbEntity2 = null; + } + + @Override + public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) { + if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) { + return null; + } + + // Create small circle telegraph at target position + Telegraph telegraph = Telegraph.circle(orbSize * 1.5f); + telegraph.setDurationTicks(windUpTicks); + telegraph.setColor(windUpColor); + telegraph.setWarningColor(activeColor); + telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4)); + telegraph.setHeightOffset(telegraphHeightOffset); + + // Position at target and follow target during windup + TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, caster.rotationYaw); + instance.setCasterEntityId(caster.getEntityId()); + instance.setEntityIdToFollow(target.getEntityId()); + + return instance; + } + + @Override + public float getTelegraphRadius() { + return orbSize * 1.5f; + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setFloat("orbSize", orbSize); + nbt.setBoolean("dualFire", dualFire); + nbt.setInteger("dualFireDelay", dualFireDelay); + combatData.writeNBT(nbt); + homingData.writeNBT(nbt); + lifespanData.writeNBT(nbt); + // Backward compat: old key was "orbSpeed" + nbt.setFloat("orbSpeed", homingData.speed); + + NBTTagCompound orb1 = new NBTTagCompound(); + NBTTagCompound orb2 = new NBTTagCompound(); + + anchorData[0].writeNBT(orb1); + anchorData[1].writeNBT(orb2); + colorData[0].writeNBT(orb1); + colorData[1].writeNBT(orb2); + lightningData[0].writeNBT(orb1); + lightningData[1].writeNBT(orb2); + + nbt.setTag("Orb_1", orb1); + nbt.setTag("Orb_2", orb2); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + NBTTagCompound orb1 = nbt.hasKey("Orb_1") ? nbt.getCompoundTag("Orb_1") : new NBTTagCompound(); + NBTTagCompound orb2 = nbt.hasKey("Orb_2") ? nbt.getCompoundTag("Orb_2") : new NBTTagCompound(); + + this.orbSize = nbt.hasKey("orbSize") ? nbt.getFloat("orbSize") : 1.0f; + this.dualFire = !nbt.hasKey("dualFire") || nbt.getBoolean("dualFire"); + this.dualFireDelay = nbt.hasKey("dualFireDelay") ? nbt.getInteger("dualFireDelay") : 0; + combatData.readNBT(nbt); + homingData.readNBT(nbt); + lifespanData.readNBT(nbt); + // Backward compat: old key was "orbSpeed" + if (nbt.hasKey("orbSpeed")) { + homingData.speed = nbt.getFloat("orbSpeed"); + } + + anchorData[0].readNBT(orb1); + anchorData[1].readNBT(orb2); + colorData[0].readNBT(orb1); + colorData[1].readNBT(orb2); + lightningData[0].readNBT(orb1); + lightningData[1].readNBT(orb2); + } + + // Getters & Setters + private EnergyColorData getColorData(int orb) { + orb = ValueUtil.clamp(orb, 0, 1); + return colorData[orb]; + } + private EnergyLightningData getLightningData(int orb) { + orb = ValueUtil.clamp(orb, 0, 1); + return lightningData[orb]; + } + private EnergyAnchorData getAnchorData(int orb) { + orb = ValueUtil.clamp(orb, 0, 1); + return anchorData[orb]; + } + + public float getOrbSpeed() { + return homingData.speed; + } + + public void setOrbSpeed(float orbSpeed) { + homingData.speed = orbSpeed; + } + + public float getOrbSize() { + return orbSize; + } + + public void setOrbSize(float orbSize) { + this.orbSize = orbSize; + } + + public boolean isDualFire() { + return dualFire; + } + + public void setDualFire(boolean dualFire) { + this.dualFire = dualFire; + } + + public int getDualFireDelay() { + return dualFireDelay; + } + + public void setDualFireDelay(int dualFireDelay) { + this.dualFireDelay = dualFireDelay; + } + + public float getMaxDistance() { + return lifespanData.maxDistance; + } + + public void setMaxDistance(float maxDistance) { + lifespanData.maxDistance = maxDistance; + } + + public int getMaxLifetime() { + return lifespanData.maxLifetime; + } + + public void setMaxLifetime(int maxLifetime) { + lifespanData.maxLifetime = maxLifetime; + } + + public float getDamage() { + return combatData.damage; + } + + public void setDamage(float damage) { + combatData.damage = damage; + } + + public float getKnockback() { + return combatData.knockback; + } + + public void setKnockback(float knockback) { + combatData.knockback = knockback; + } + + public float getKnockbackUp() { + return combatData.knockbackUp; + } + + public void setKnockbackUp(float knockbackUp) { + combatData.knockbackUp = knockbackUp; + } + + public boolean isHoming() { + return homingData.homing; + } + + public void setHoming(boolean homing) { + homingData.homing = homing; + } + + public float getHomingStrength() { + return homingData.homingStrength; + } + + public void setHomingStrength(float homingStrength) { + homingData.homingStrength = homingStrength; + } + + public float getHomingRange() { + return homingData.homingRange; + } + + public void setHomingRange(float homingRange) { + homingData.homingRange = homingRange; + } + + public boolean isExplosive() { + return combatData.explosive; + } + + public void setExplosive(boolean explosive) { + combatData.explosive = explosive; + } + + public float getExplosionRadius() { + return combatData.explosionRadius; + } + + public void setExplosionRadius(float explosionRadius) { + combatData.explosionRadius = explosionRadius; + } + + public float getExplosionDamageFalloff() { + return combatData.explosionDamageFalloff; + } + + public void setExplosionDamageFalloff(float explosionDamageFalloff) { + combatData.explosionDamageFalloff = explosionDamageFalloff; + } + + + public int getInnerColor(int orb) { return getColorData(orb).innerColor; } + public void setInnerColor(int orb, int innerColor) { getColorData(orb).innerColor = innerColor; } + public int getOuterColor(int orb) { return getColorData(orb).outerColor; } + public void setOuterColor(int orb, int outerColor) { getColorData(orb).outerColor = outerColor; } + public boolean isOuterColorEnabled(int orb) { return getColorData(orb).outerColorEnabled; } + public void setOuterColorEnabled(int orb, boolean enabled) { getColorData(orb).outerColorEnabled = enabled; } + public float getOuterColorWidth(int orb) { return getColorData(orb).outerColorWidth; } + public void setOuterColorWidth(int orb, float width) { getColorData(orb).outerColorWidth = width; } + public float getOuterColorAlpha(int orb) { return getColorData(orb).outerColorAlpha; } + public void setOuterColorAlpha(int orb, float alpha) { getColorData(orb).outerColorAlpha = alpha; } + public float getRotationSpeed(int orb) { return getColorData(orb).rotationSpeed; } + public void setRotationSpeed(int orb, float speed) { getColorData(orb).rotationSpeed = speed; } + + public int getInnerColor() { return getInnerColor(0); } + public void setInnerColor(int innerColor) { setInnerColor(0, innerColor); } + public int getOuterColor() { return getOuterColor(0); } + public void setOuterColor(int outerColor) { setOuterColor(0, outerColor); } + public boolean isOuterColorEnabled() { return isOuterColorEnabled(0); } + public void setOuterColorEnabled(boolean enabled) { setOuterColorEnabled(0, enabled); } + public float getOuterColorWidth() { return getOuterColorWidth(0); } + public void setOuterColorWidth(float width) { setOuterColorWidth(0, width); } + public float getOuterColorAlpha() { return getOuterColorAlpha(0); } + public void setOuterColorAlpha(float alpha) { setOuterColorAlpha(0, alpha); } + public float getRotationSpeed() { return getRotationSpeed(0); } + public void setRotationSpeed(float speed) { setRotationSpeed(0, speed); } + + + public boolean hasLightningEffect(int orb) { return getLightningData(orb).lightningEffect; } + public void setLightningEffect(int orb, boolean lightningEffect) { getLightningData(orb).lightningEffect = lightningEffect; } + public float getLightningDensity(int orb) { return getLightningData(orb).lightningDensity; } + public void setLightningDensity(int orb, float lightningDensity) { getLightningData(orb).lightningDensity = lightningDensity; } + public float getLightningRadius(int orb) { return getLightningData(orb).lightningRadius; } + public void setLightningRadius(int orb, float lightningRadius) { getLightningData(orb).lightningRadius = lightningRadius; } + + public boolean hasLightningEffect() { return hasLightningEffect(0); } + public void setLightningEffect(boolean lightningEffect) { setLightningEffect(0, lightningEffect); } + public float getLightningDensity() { return getLightningDensity(0); } + public void setLightningDensity(float lightningDensity) { setLightningDensity(0, lightningDensity); } + public float getLightningRadius() { return getLightningRadius(0); } + public void setLightningRadius(float lightningRadius) { setLightningRadius(0, lightningRadius); } + + + public AnchorPoint getAnchorPointEnum(int orb) { return getAnchorData(orb).anchorPoint; } + public void setAnchorPointEnum(int orb, AnchorPoint anchorPoint) { getAnchorData(orb).anchorPoint = anchorPoint; } + public float getAnchorOffsetX(int orb) { return getAnchorData(orb).anchorOffsetX; } + public void setAnchorOffsetX(int orb, float x) { getAnchorData(orb).anchorOffsetX = x; } + public float getAnchorOffsetY(int orb) { return getAnchorData(orb).anchorOffsetY; } + public void setAnchorOffsetY(int orb, float y) { getAnchorData(orb).anchorOffsetY = y; } + public float getAnchorOffsetZ(int orb) { return getAnchorData(orb).anchorOffsetZ; } + public void setAnchorOffsetZ(int orb, float z) { getAnchorData(orb).anchorOffsetZ = z; } + + public AnchorPoint getAnchorPointEnum() { return getAnchorPointEnum(0); } + public void setAnchorPointEnum(AnchorPoint anchorPoint) { setAnchorPointEnum(0, anchorPoint); } + public float getAnchorOffsetX() { return getAnchorOffsetX(0); } + public void setAnchorOffsetX(float x) { setAnchorOffsetX(0, x); } + public float getAnchorOffsetY() { return getAnchorOffsetY(0); } + public void setAnchorOffsetY(float y) { setAnchorOffsetY(0, y); } + public float getAnchorOffsetZ() { return getAnchorOffsetZ(0); } + public void setAnchorOffsetZ(float z) { setAnchorOffsetZ(0, z); } + + //@Override + public int getAnchorPoint(int orb) { return getAnchorData(orb).anchorPoint.ordinal(); } + public int getAnchorPoint() { return getAnchorData(0).anchorPoint.ordinal(); } + + //@Override + public void setAnchorPoint(int orb, int point) { getAnchorData(orb).anchorPoint = AnchorPoint.fromOrdinal(point); } + public void setAnchorPoint(int point) { getAnchorData(0).anchorPoint = AnchorPoint.fromOrdinal(point); } + + @Override + @SideOnly(Side.CLIENT) + public Entity createPreviewEntity(EntityNPCInterface npc) { + if (npc == null || npc.worldObj == null) return null; + + EntityAbilityOrb orb = new EntityAbilityOrb(npc.worldObj); + orb.setupPreview(npc, orbSize, colorData[0], lightningData[0], anchorData[0], windUpTicks); + return orb; + } + + @Override + public int getPreviewActiveDuration() { + return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100; + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index c403f25de..a4384d035 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -12,6 +12,7 @@ import net.minecraft.util.Vec3; import net.minecraft.world.World; import java.util.List; +import java.util.UUID; /** * Orb projectile - spherical homing energy ball. @@ -37,6 +38,8 @@ public class EntityAbilityOrb extends EntityAbilityProjectile { // Data watcher index for charging state (synced to clients) private static final int DW_CHARGING = 20; + private UUID siblingUUID = null; + public EntityAbilityOrb(World world) { super(world); } @@ -441,6 +444,18 @@ public class EntityAbilityOrb extends EntityAbilityProjectile { return size; } + public UUID getSiblingUUID() { + return siblingUUID; + } + + public void setSiblingUUID(UUID siblingUUID) { + if (siblingUUID == null) + return; + + if (this.siblingUUID == null) + this.siblingUUID = siblingUUID; + } + // ==================== NBT ==================== @Override diff --git a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualOrb.java b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualOrb.java new file mode 100644 index 000000000..03a868a05 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualOrb.java @@ -0,0 +1,313 @@ +package noppes.npcs.client.gui.advanced.ability; + +import kamkeel.npcs.controllers.data.ability.AbilityEffect; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.type.AbilityDualOrb; +import kamkeel.npcs.controllers.data.ability.type.AbilityOrb; +import noppes.npcs.client.gui.SubGuiColorSelector; +import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; +import noppes.npcs.client.gui.util.*; + +import java.util.List; + +public class SubGuiAbilityDualOrb extends SubGuiAbilityConfig { + private final AbilityDualOrb orb; + private int editingVisualColorId = 0; + private int editingOrb = 0; + + public SubGuiAbilityDualOrb(AbilityDualOrb ability, IAbilityConfigCallback callback) { + super(ability, callback); + this.orb = ability; + } + + @Override + protected boolean hasVisualSettings() { + return true; + } + + @Override + protected void initTypeTab(int startY) { + int y = startY; + int labelX = guiLeft + 8; + int fieldX = guiLeft + 85; + int col2LabelX = guiLeft + 145; + int col2FieldX = guiLeft + 205; + + // Row 1: Damage + Speed + addLabel(new GuiNpcLabel(100, "enchantment.damage", labelX, y + 5)); + addTextField(createFloatField(100, fieldX, y, 50, orb.getDamage())); + + addLabel(new GuiNpcLabel(101, "stats.speed", col2LabelX, y + 5)); + addTextField(createFloatField(101, col2FieldX, y, 50, orb.getOrbSpeed())); + + y += 24; + + // Row 2: Orb Size + Max Distance + addLabel(new GuiNpcLabel(102, "stats.size", labelX, y + 5)); + addTextField(createFloatField(102, fieldX, y, 50, orb.getOrbSize())); + + addLabel(new GuiNpcLabel(103, "ability.maxDist", col2LabelX, y + 5)); + addTextField(createFloatField(103, col2FieldX, y, 50, orb.getMaxDistance())); + + y += 24; + + // Row 3: Homing + Homing Strength (only show strength if homing) + addLabel(new GuiNpcLabel(104, "ability.homing", labelX, y + 5)); + GuiNpcButton homingBtn = new GuiNpcButton(104, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, orb.isHoming() ? 1 : 0); + homingBtn.setHoverText("ability.hover.homing"); + addButton(homingBtn); + + if (orb.isHoming()) { + addLabel(new GuiNpcLabel(105, "ability.homingStr", col2LabelX, y + 5)); + addTextField(createFloatField(105, col2FieldX, y, 50, orb.getHomingStrength())); + } + + y += 24; + + // Row 4: Explosive + Explosion Radius (only show radius if explosive) + addLabel(new GuiNpcLabel(106, "ability.explosive", labelX, y + 5)); + GuiNpcButton explosiveBtn = new GuiNpcButton(106, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, orb.isExplosive() ? 1 : 0); + explosiveBtn.setHoverText("ability.hover.explosive"); + addButton(explosiveBtn); + + if (orb.isExplosive()) { + addLabel(new GuiNpcLabel(107, "ability.explosionRad", col2LabelX, y + 5)); + addTextField(createFloatField(107, col2FieldX, y, 50, orb.getExplosionRadius())); + } + + y += 24; + + // Row 5: Knockback + Max Lifetime + addLabel(new GuiNpcLabel(108, "ability.knockback", labelX, y + 5)); + addTextField(createFloatField(108, fieldX, y, 50, orb.getKnockback())); + + addLabel(new GuiNpcLabel(109, "ability.lifetime", col2LabelX, y + 5)); + addTextField(createIntField(109, col2FieldX, y, 50, orb.getMaxLifetime())); + + y += 24; + + addLabel(new GuiNpcLabel(110, "ability.dualFire", labelX, y + 5)); + addButton(new GuiNpcButtonYesNo(110, fieldX, y, orb.isDualFire())); + + if (orb.isDualFire()) { + addLabel(new GuiNpcLabel(111, "ability.dualFireDelay", col2LabelX, y + 5)); + addTextField(createIntField(111, col2FieldX, y, 50, orb.getDualFireDelay())); + } + + y += 24; + + // Row 7: Effects button + addButton(new GuiNpcButton(150, labelX, y, 80, 20, "ability.effects")); + } + + @Override + protected void initVisualTab(int startY) { + int y = startY; + int labelX = guiLeft + 8; + int fieldX = guiLeft + 85; + int col2LabelX = guiLeft + 180; + int col2FieldX = guiLeft + 260; + + // Row 1: Anchor Point + addLabel(new GuiNpcLabel(210, "ability.anchorPoint", labelX, y + 5)); + GuiNpcButton anchorBtn = new GuiNpcButton(210, fieldX, y, 80, 20, AnchorPoint.getDisplayNames(), orb.getAnchorPointEnum(editingOrb).ordinal()); + anchorBtn.setHoverText("ability.hover.anchorPoint"); + addButton(anchorBtn); + + y += 24; + + // Row 2: Inner Color + Rotation Speed + addLabel(new GuiNpcLabel(200, "ability.innerColor", labelX, y + 5)); + String innerHex = String.format("%06X", orb.getInnerColor(editingOrb) & 0xFFFFFF); + GuiNpcButton innerColorBtn = new GuiNpcButton(200, fieldX, y, 55, 20, innerHex); + innerColorBtn.setTextColor(orb.getInnerColor(editingOrb) & 0xFFFFFF); + addButton(innerColorBtn); + + addLabel(new GuiNpcLabel(205, "ability.rotationSpeed", col2LabelX, y + 5)); + addTextField(createFloatField(205, col2FieldX, y, 55, orb.getRotationSpeed(editingOrb))); + + y += 24; + + // Row 3: Outer Glow Enabled + addLabel(new GuiNpcLabel(202, "ability.outerEnabled", labelX, y + 5)); + addButton(new GuiNpcButton(202, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, orb.isOuterColorEnabled(editingOrb) ? 1 : 0)); + + // Only show Outer Color and Width if Outer Glow is enabled + if (orb.isOuterColorEnabled()) { + addLabel(new GuiNpcLabel(204, "ability.outerAlpha", col2LabelX, y + 5)); + GuiNpcTextField alphaField = createFloatField(204, col2FieldX, y, 55, orb.getOuterColorAlpha(editingOrb)); + alphaField.setEnabled(orb.isOuterColorEnabled(editingOrb)); + alphaField.setMinMaxDefaultFloat(0, 1, 0.5f); + addTextField(alphaField); + + y += 24; + + // Row 4: Outer Color + Outer Width + addLabel(new GuiNpcLabel(201, "ability.outerColor", labelX, y + 5)); + String outerHex = String.format("%06X", orb.getOuterColor(editingOrb) & 0xFFFFFF); + GuiNpcButton outerColorBtn = new GuiNpcButton(201, fieldX, y, 55, 20, outerHex); + outerColorBtn.setTextColor(orb.getOuterColor(editingOrb) & 0xFFFFFF); + addButton(outerColorBtn); + + addLabel(new GuiNpcLabel(203, "ability.outerWidth", col2LabelX, y + 5)); + addTextField(createFloatField(203, col2FieldX, y, 55, orb.getOuterColorWidth(editingOrb))); + } + + y += 24; + + // Row 5: Lightning Effect Enabled + addLabel(new GuiNpcLabel(206, "ability.lightning", labelX, y + 5)); + GuiNpcButton lightningBtn = new GuiNpcButton(206, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, orb.hasLightningEffect(editingOrb) ? 1 : 0); + lightningBtn.setHoverText("ability.hover.lightning"); + addButton(lightningBtn); + + // Only show Lightning settings if Lightning is enabled + if (orb.hasLightningEffect(editingOrb)) { + y += 24; + + // Row 61: Density + Radius + addLabel(new GuiNpcLabel(207, "ability.lightningDensity", labelX, y + 5)); + GuiNpcTextField densityField = new GuiNpcTextField(207, this, fontRendererObj, fieldX, y, 55, 18, "" + orb.getLightningDensity(editingOrb)); + densityField.setMinMaxDefaultFloat(0.01f, 5.0f, 0.15f); + addTextField(densityField); + + addLabel(new GuiNpcLabel(208, "ability.lightningRadius", col2LabelX, y + 5)); + GuiNpcTextField radiusField = new GuiNpcTextField(208, this, fontRendererObj, col2FieldX, y, 55, 18, "" + orb.getLightningRadius(editingOrb)); + radiusField.setMinMaxDefaultFloat(0.1f, 10.0f, 0.5f); + addTextField(radiusField); + } + + y += 24; + + addButton(new GuiNpcButton(400, col2FieldX, y, 55, 20, new String[]{"Orb 1", "Orb 2"}, editingOrb)); + } + + @Override + protected void handleTypeButton(int id, GuiNpcButton button) { + int value = button.getValue(); + switch (id) { + case 104: + orb.setHoming(value == 1); + initGui(); + break; + case 106: + orb.setExplosive(value == 1); + initGui(); + break; + case 110: + orb.setDualFire(value == 1); + initGui(); + break; + case 150: + setSubGui(new SubGuiAbilityEffects(orb.getEffects())); + break; + } + } + + @Override + protected void handleVisualButton(int id, GuiNpcButton button) { + int value = button.getValue(); + switch (id) { + case 200: + editingVisualColorId = 200; + setSubGui(new SubGuiColorSelector(orb.getInnerColor(editingOrb))); + break; + case 201: + editingVisualColorId = 201; + setSubGui(new SubGuiColorSelector(orb.getOuterColor(editingOrb))); + break; + case 202: + orb.setOuterColorEnabled(editingOrb, value == 1); + initGui(); + break; + case 206: + orb.setLightningEffect(editingOrb, value == 1); + initGui(); + break; + case 210: + orb.setAnchorPointEnum(editingOrb, AnchorPoint.fromOrdinal(value)); + break; + case 400: + editingOrb = (editingOrb + 1) % 2; + initGui(); + break; + } + } + + @Override + public void subGuiClosed(SubGuiInterface subgui) { + if (subgui instanceof SubGuiColorSelector && editingVisualColorId != 0) { + SubGuiColorSelector colorSelector = (SubGuiColorSelector) subgui; + int rgb = colorSelector.color & 0x00FFFFFF; + if (editingVisualColorId == 200) { + orb.setInnerColor(rgb); + } else if (editingVisualColorId == 201) { + orb.setOuterColor(rgb); + } + editingVisualColorId = 0; + initGui(); + } else if (subgui instanceof SubGuiAbilityEffects) { + SubGuiAbilityEffects effectsGui = (SubGuiAbilityEffects) subgui; + List result = effectsGui.getResult(); + if (result != null) { + orb.setEffects(result); + } + } else { + super.subGuiClosed(subgui); + } + } + + @Override + protected void handleTypeTextField(int id, GuiNpcTextField field) { + switch (id) { + case 100: + orb.setDamage(parseFloat(field, orb.getDamage())); + break; + case 101: + orb.setOrbSpeed(parseFloat(field, orb.getOrbSpeed())); + break; + case 102: + orb.setOrbSize(parseFloat(field, orb.getOrbSize())); + break; + case 103: + orb.setMaxDistance(parseFloat(field, orb.getMaxDistance())); + break; + case 105: + orb.setHomingStrength(parseFloat(field, orb.getHomingStrength())); + break; + case 107: + orb.setExplosionRadius(parseFloat(field, orb.getExplosionRadius())); + break; + case 108: + orb.setKnockback(parseFloat(field, orb.getKnockback())); + break; + case 109: + orb.setMaxLifetime(field.getInteger()); + break; + case 111: + orb.setDualFireDelay(field.getInteger()); + break; + } + } + + @Override + protected void handleVisualTextField(int id, GuiNpcTextField field) { + switch (id) { + case 203: + orb.setOuterColorWidth(editingOrb, parseFloat(field, orb.getOuterColorWidth(editingOrb))); + break; + case 204: + orb.setOuterColorAlpha(editingOrb, parseFloat(field, orb.getOuterColorAlpha(editingOrb))); + break; + case 205: + orb.setRotationSpeed(editingOrb, parseFloat(field, orb.getRotationSpeed(editingOrb))); + break; + case 207: + orb.setLightningDensity(editingOrb, parseFloat(field, orb.getLightningDensity(editingOrb))); + break; + case 208: + orb.setLightningRadius(editingOrb, parseFloat(field, orb.getLightningRadius(editingOrb))); + break; + } + } +} From 71065fc5208b29de4f8ed4ba44c709a429ffe4d1 Mon Sep 17 00:00:00 2001 From: Vidal Date: Sat, 31 Jan 2026 22:36:12 -0300 Subject: [PATCH 9/9] Added Dual Disc Ability --- .../data/ability/AbilityController.java | 3 +- .../data/ability/type/AbilityDualDisc.java | 410 ++++++++++++++++++ .../npcs/entity/EntityAbilityDisc.java | 17 + .../kamkeel/npcs/entity/EntityAbilityOrb.java | 4 + .../ability/SubGuiAbilityDualDisc.java | 342 +++++++++++++++ .../ability/SubGuiAbilityDualOrb.java | 5 +- .../assets/customnpcs/lang/en_US.lang | 7 + 7 files changed, 784 insertions(+), 4 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualDisc.java create mode 100644 src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualDisc.java diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java index 5603ac4ff..43d7bd8bb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/AbilityController.java @@ -364,7 +364,8 @@ public class AbilityController implements IAbilityHandler { registerType("cnpc:projectile", AbilityProjectile::new); registerType("cnpc:orb", AbilityOrb::new); registerType("cnpc:dual_orb", AbilityDualOrb::new); - registerType("cnpc:disc", AbilityDisc::new); + registerType("cnpc:dual_disc", AbilityDisc::new); + registerType("cnpc:dual_disc", AbilityDualDisc::new); registerType("cnpc:laser_shot", AbilityLaserShot::new); registerType("cnpc:beam", AbilityEnergyBeam::new); registerType("cnpc:dual_beam", AbilityDualBeam::new); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualDisc.java new file mode 100644 index 000000000..873530119 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDualDisc.java @@ -0,0 +1,410 @@ +package kamkeel.npcs.controllers.data.ability.type; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.LockMovementType; +import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.ability.data.*; +import kamkeel.npcs.controllers.data.telegraph.Telegraph; +import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; +import kamkeel.npcs.controllers.data.telegraph.TelegraphType; +import kamkeel.npcs.entity.EntityAbilityDisc; +import kamkeel.npcs.util.AnchorPointHelper; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; +import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; +import noppes.npcs.client.gui.advanced.ability.SubGuiAbilityDualDisc; +import noppes.npcs.client.gui.util.IAbilityConfigCallback; +import noppes.npcs.entity.EntityNPCInterface; +import noppes.npcs.util.ValueUtil; + +public class AbilityDualDisc extends Ability { + + // Disc geometry + private float discRadius = 1.0f; + private float discThickness = 0.2f; + + // Boomerang properties + private boolean boomerang = false; + private int boomerangDelay = 40; + + // Dual properties + private boolean dualFire= true; + private int dualFireDelay = 10; + + // Energy data classes + private EnergyColorData[] colorData = new EnergyColorData[]{ + new EnergyColorData(0xFFFFFF, 0xFF8800, true, 0.4f, 0.5f, 5.0f), + new EnergyColorData(0xFFFFFF, 0x8800FF, true, 0.4f, 0.5f, 5.0f) + }; + private EnergyCombatData combatData = new EnergyCombatData(8.0f, 1.2f, 0.15f, false, 3.0f, 0.5f); + private EnergyHomingData homingData = new EnergyHomingData(0.6f, true, 0.12f, 18.0f); + public final EnergyLightningData[] lightningData = new EnergyLightningData[]{ + new EnergyLightningData(), new EnergyLightningData() + }; + private EnergyLifespanData lifespanData = new EnergyLifespanData(35.0f, 200); + private EnergyAnchorData[] anchorData = new EnergyAnchorData[]{ + new EnergyAnchorData(AnchorPoint.RIGHT_HAND), new EnergyAnchorData(AnchorPoint.LEFT_HAND) + }; + + // Transient state for disc entity (used during windup charging) + private transient EntityAbilityDisc discEntity = null; + private transient EntityAbilityDisc discEntity2 = null; + + public AbilityDualDisc() { + this.typeId = "ability.cnpc.dual_disc"; + this.name = "Dual Disc"; + this.targetingMode = TargetingMode.AGGRO_TARGET; + this.maxRange = 30.0f; + this.minRange = 5.0f; + this.cooldownTicks = 0; + this.windUpTicks = 60; + this.lockMovement = LockMovementType.WINDUP; + this.telegraphType = TelegraphType.CIRCLE; + this.showTelegraph = true; + // Default built-in animations + this.windUpAnimationName = "Ability_DualBeam_Windup"; + this.activeAnimationName = "Ability_DualBeam_Active"; + } + + @Override + public boolean hasTypeSettings() { + return true; + } + + @Override + @SideOnly(Side.CLIENT) + public SubGuiAbilityConfig createConfigGui(IAbilityConfigCallback callback) { + return new SubGuiAbilityDualDisc(this, callback); + } + + @Override + public boolean isTargetingModeLocked() { + return true; + } + + @Override + public TargetingMode[] getAllowedTargetingModes() { + return new TargetingMode[]{TargetingMode.AGGRO_TARGET}; + } + + @Override + public void onExecute(EntityLivingBase caster, EntityLivingBase target, World world) { + if (world.isRemote) { + signalCompletion(); + return; + } + + // Start moving the orb that was spawned during windup + if (discEntity != null && !discEntity.isDead) { + discEntity.startMoving(target); + } + + if (dualFire) { + if (discEntity2 != null && !discEntity2.isDead && dualFireDelay <= 0) { + discEntity2.startMoving(target); + } + } else { + discEntity2.setDead(); + } + + // Ability stays active until entity dies (prevents firing another while projectile is alive) + // Movement locking is handled separately by the base class + } + + @Override + public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) { + if (world.isRemote) return; + + // Spawn disc in charging mode on first tick of windup + if (tick == 1) { + // Create disc in charging mode - follows caster based on anchor point during windup + Vec3 spawnPos1 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[0]); + discEntity = new EntityAbilityDisc( + world, caster, target, + spawnPos1.xCoord, spawnPos1.yCoord, spawnPos1.zCoord, + discRadius, discThickness, + colorData[0], combatData, homingData, lightningData[0], lifespanData, + boomerang, boomerangDelay); + + Vec3 spawnPos2 = AnchorPointHelper.calculateAnchorPosition(caster, anchorData[1]); + discEntity2 = new EntityAbilityDisc( + world, caster, target, + spawnPos2.xCoord, spawnPos2.yCoord, spawnPos2.zCoord, + discRadius, discThickness, + colorData[1], combatData, homingData, lightningData[1], lifespanData, + boomerang, boomerangDelay); + + discEntity.setSiblingUUID(discEntity2.getPersistentID()); + discEntity2.setSiblingUUID(discEntity.getPersistentID()); + + discEntity.setupCharging(anchorData[0], windUpTicks); + discEntity2.setupCharging(anchorData[1], windUpTicks); + + discEntity.setEffects(this.effects); + + if (dualFire) { + discEntity2.setEffects(this.effects); + } + + world.spawnEntityInWorld(discEntity); + world.spawnEntityInWorld(discEntity2); + } + } + + @Override + public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) { + // Signal completion when entity dies + if (dualFire) { + if ((discEntity == null || discEntity.isDead) && (discEntity2 == null || discEntity2.isDead)) { + discEntity = null; + discEntity2 = null; + signalCompletion(); + } + + if (tick == dualFireDelay && discEntity2 != null && !discEntity2.isDead) { + discEntity2.startMoving(target); + } + } else { + if (discEntity == null || discEntity.isDead) { + discEntity = null; + signalCompletion(); + } + } + } + + @Override + public void onComplete(EntityLivingBase caster, EntityLivingBase target) { + } + + @Override + public void cleanup() { + // Despawn disc entity if still alive + if (discEntity != null && !discEntity.isDead) { + discEntity.setDead(); + } + + if (discEntity2 != null && !discEntity2.isDead) { + discEntity2.setDead(); + } + + discEntity = null; + discEntity2 = null; + } + + @Override + public TelegraphInstance createTelegraph(EntityLivingBase caster, EntityLivingBase target) { + if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) { + return null; + } + + // Create circle telegraph at target position + Telegraph telegraph = Telegraph.circle(discRadius * 1.5f); + telegraph.setDurationTicks(windUpTicks); + telegraph.setColor(windUpColor); + telegraph.setWarningColor(activeColor); + telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4)); + telegraph.setHeightOffset(telegraphHeightOffset); + + // Position at target and follow target during windup + TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, caster.rotationYaw); + instance.setCasterEntityId(caster.getEntityId()); + instance.setEntityIdToFollow(target.getEntityId()); + + return instance; + } + + @Override + public float getTelegraphRadius() { + return discRadius * 1.5f; + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setFloat("discRadius", discRadius); + nbt.setFloat("discThickness", discThickness); + nbt.setBoolean("boomerang", boomerang); + nbt.setInteger("boomerangDelay", boomerangDelay); + nbt.setBoolean("dualFire", dualFire); + nbt.setInteger("dualFireDelay", dualFireDelay); + combatData.writeNBT(nbt); + homingData.writeNBT(nbt); + lifespanData.writeNBT(nbt); + + NBTTagCompound disc1 = new NBTTagCompound(); + NBTTagCompound disc2 = new NBTTagCompound(); + + anchorData[0].writeNBT(disc1); + anchorData[1].writeNBT(disc2); + colorData[0].writeNBT(disc1); + colorData[1].writeNBT(disc2); + lightningData[0].writeNBT(disc1); + lightningData[1].writeNBT(disc2); + + nbt.setTag("Disc_1", disc1); + nbt.setTag("Disc_2", disc2); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + NBTTagCompound disc1 = nbt.hasKey("Disc_1") ? nbt.getCompoundTag("Disc_1") : new NBTTagCompound(); + NBTTagCompound disc2 = nbt.hasKey("Disc_2") ? nbt.getCompoundTag("Disc_2") : new NBTTagCompound(); + + this.discRadius = nbt.hasKey("discRadius") ? nbt.getFloat("discRadius") : 1.0f; + this.discThickness = nbt.hasKey("discThickness") ? nbt.getFloat("discThickness") : 0.2f; + this.boomerang = nbt.hasKey("boomerang") && nbt.getBoolean("boomerang"); + this.boomerangDelay = nbt.hasKey("boomerangDelay") ? nbt.getInteger("boomerangDelay") : 40; + this.dualFire = !nbt.hasKey("dualFire") || nbt.getBoolean("dualFire"); + this.dualFireDelay = nbt.hasKey("dualFireDelay") ? nbt.getInteger("dualFireDelay") : 0; + combatData.readNBT(nbt); + homingData.readNBT(nbt); + lifespanData.readNBT(nbt); + + anchorData[0].readNBT(disc1); + anchorData[1].readNBT(disc2); + colorData[0].readNBT(disc1); + colorData[1].readNBT(disc2); + lightningData[0].readNBT(disc1); + lightningData[1].readNBT(disc2); + } + + // Getters & Setters + private EnergyColorData getColorData(int orb) { + orb = ValueUtil.clamp(orb, 0, 1); + return colorData[orb]; + } + private EnergyLightningData getLightningData(int orb) { + orb = ValueUtil.clamp(orb, 0, 1); + return lightningData[orb]; + } + private EnergyAnchorData getAnchorData(int orb) { + orb = ValueUtil.clamp(orb, 0, 1); + return anchorData[orb]; + } + + public boolean isDualFire() { return dualFire; } + public void setDualFire(boolean dualFire) { this.dualFire = dualFire; } + public int getDualFireDelay() { return dualFireDelay; } + public void setDualFireDelay(int dualFireDelay) { this.dualFireDelay = dualFireDelay; } + public float getSpeed() { return homingData.speed; } + public void setSpeed(float speed) { homingData.speed = speed; } + public float getDiscRadius() { return discRadius; } + public void setDiscRadius(float discRadius) { this.discRadius = discRadius; } + public float getDiscThickness() { return discThickness; } + public void setDiscThickness(float discThickness) { this.discThickness = discThickness; } + public float getMaxDistance() { return lifespanData.maxDistance; } + public void setMaxDistance(float maxDistance) { lifespanData.maxDistance = maxDistance; } + public int getMaxLifetime() { return lifespanData.maxLifetime; } + public void setMaxLifetime(int maxLifetime) { lifespanData.maxLifetime = maxLifetime; } + public float getDamage() { return combatData.damage; } + public void setDamage(float damage) { combatData.damage = damage; } + public float getKnockback() { return combatData.knockback; } + public void setKnockback(float knockback) { combatData.knockback = knockback; } + public float getKnockbackUp() { return combatData.knockbackUp; } + public void setKnockbackUp(float knockbackUp) { combatData.knockbackUp = knockbackUp; } + public boolean isHoming() { return homingData.homing; } + public void setHoming(boolean homing) { homingData.homing = homing; } + public float getHomingStrength() { return homingData.homingStrength; } + public void setHomingStrength(float homingStrength) { homingData.homingStrength = homingStrength; } + public float getHomingRange() { return homingData.homingRange; } + public void setHomingRange(float homingRange) { homingData.homingRange = homingRange; } + public boolean isBoomerang() { return boomerang; } + public void setBoomerang(boolean boomerang) { this.boomerang = boomerang; } + public int getBoomerangDelay() { return boomerangDelay; } + public void setBoomerangDelay(int boomerangDelay) { this.boomerangDelay = boomerangDelay; } + public boolean isExplosive() { return combatData.explosive; } + public void setExplosive(boolean explosive) { combatData.explosive = explosive; } + public float getExplosionRadius() { return combatData.explosionRadius; } + public void setExplosionRadius(float explosionRadius) { combatData.explosionRadius = explosionRadius; } + public float getExplosionDamageFalloff() { return combatData.explosionDamageFalloff; } + public void setExplosionDamageFalloff(float explosionDamageFalloff) { combatData.explosionDamageFalloff = explosionDamageFalloff; } + + + public int getInnerColor(int orb) { return getColorData(orb).innerColor; } + public void setInnerColor(int orb, int innerColor) { getColorData(orb).innerColor = innerColor; } + public int getOuterColor(int orb) { return getColorData(orb).outerColor; } + public void setOuterColor(int orb, int outerColor) { getColorData(orb).outerColor = outerColor; } + public boolean isOuterColorEnabled(int orb) { return getColorData(orb).outerColorEnabled; } + public void setOuterColorEnabled(int orb, boolean enabled) { getColorData(orb).outerColorEnabled = enabled; } + public float getOuterColorWidth(int orb) { return getColorData(orb).outerColorWidth; } + public void setOuterColorWidth(int orb, float width) { getColorData(orb).outerColorWidth = width; } + public float getOuterColorAlpha(int orb) { return getColorData(orb).outerColorAlpha; } + public void setOuterColorAlpha(int orb, float alpha) { getColorData(orb).outerColorAlpha = alpha; } + public float getRotationSpeed(int orb) { return getColorData(orb).rotationSpeed; } + public void setRotationSpeed(int orb, float speed) { getColorData(orb).rotationSpeed = speed; } + + public int getInnerColor() { return getInnerColor(0); } + public void setInnerColor(int innerColor) { setInnerColor(0, innerColor); } + public int getOuterColor() { return getOuterColor(0); } + public void setOuterColor(int outerColor) { setOuterColor(0, outerColor); } + public boolean isOuterColorEnabled() { return isOuterColorEnabled(0); } + public void setOuterColorEnabled(boolean enabled) { setOuterColorEnabled(0, enabled); } + public float getOuterColorWidth() { return getOuterColorWidth(0); } + public void setOuterColorWidth(float width) { setOuterColorWidth(0, width); } + public float getOuterColorAlpha() { return getOuterColorAlpha(0); } + public void setOuterColorAlpha(float alpha) { setOuterColorAlpha(0, alpha); } + public float getRotationSpeed() { return getRotationSpeed(0); } + public void setRotationSpeed(float speed) { setRotationSpeed(0, speed); } + + + public boolean hasLightningEffect(int orb) { return getLightningData(orb).lightningEffect; } + public void setLightningEffect(int orb, boolean lightningEffect) { getLightningData(orb).lightningEffect = lightningEffect; } + public float getLightningDensity(int orb) { return getLightningData(orb).lightningDensity; } + public void setLightningDensity(int orb, float lightningDensity) { getLightningData(orb).lightningDensity = lightningDensity; } + public float getLightningRadius(int orb) { return getLightningData(orb).lightningRadius; } + public void setLightningRadius(int orb, float lightningRadius) { getLightningData(orb).lightningRadius = lightningRadius; } + + public boolean hasLightningEffect() { return hasLightningEffect(0); } + public void setLightningEffect(boolean lightningEffect) { setLightningEffect(0, lightningEffect); } + public float getLightningDensity() { return getLightningDensity(0); } + public void setLightningDensity(float lightningDensity) { setLightningDensity(0, lightningDensity); } + public float getLightningRadius() { return getLightningRadius(0); } + public void setLightningRadius(float lightningRadius) { setLightningRadius(0, lightningRadius); } + + + public AnchorPoint getAnchorPointEnum(int orb) { return getAnchorData(orb).anchorPoint; } + public void setAnchorPointEnum(int orb, AnchorPoint anchorPoint) { getAnchorData(orb).anchorPoint = anchorPoint; } + public float getAnchorOffsetX(int orb) { return getAnchorData(orb).anchorOffsetX; } + public void setAnchorOffsetX(int orb, float x) { getAnchorData(orb).anchorOffsetX = x; } + public float getAnchorOffsetY(int orb) { return getAnchorData(orb).anchorOffsetY; } + public void setAnchorOffsetY(int orb, float y) { getAnchorData(orb).anchorOffsetY = y; } + public float getAnchorOffsetZ(int orb) { return getAnchorData(orb).anchorOffsetZ; } + public void setAnchorOffsetZ(int orb, float z) { getAnchorData(orb).anchorOffsetZ = z; } + + public AnchorPoint getAnchorPointEnum() { return getAnchorPointEnum(0); } + public void setAnchorPointEnum(AnchorPoint anchorPoint) { setAnchorPointEnum(0, anchorPoint); } + public float getAnchorOffsetX() { return getAnchorOffsetX(0); } + public void setAnchorOffsetX(float x) { setAnchorOffsetX(0, x); } + public float getAnchorOffsetY() { return getAnchorOffsetY(0); } + public void setAnchorOffsetY(float y) { setAnchorOffsetY(0, y); } + public float getAnchorOffsetZ() { return getAnchorOffsetZ(0); } + public void setAnchorOffsetZ(float z) { setAnchorOffsetZ(0, z); } + + //@Override + public int getAnchorPoint(int orb) { return getAnchorData(orb).anchorPoint.ordinal(); } + public int getAnchorPoint() { return getAnchorData(0).anchorPoint.ordinal(); } + + //@Override + public void setAnchorPoint(int orb, int point) { getAnchorData(orb).anchorPoint = AnchorPoint.fromOrdinal(point); } + public void setAnchorPoint(int point) { getAnchorData(0).anchorPoint = AnchorPoint.fromOrdinal(point); } + + @Override + @SideOnly(Side.CLIENT) + public Entity createPreviewEntity(EntityNPCInterface npc) { + if (npc == null || npc.worldObj == null) return null; + + EntityAbilityDisc disc = new EntityAbilityDisc(npc.worldObj); + disc.setupPreview(npc, discRadius, discThickness, colorData[0], lightningData[0], anchorData[0], windUpTicks); + return disc; + } + + @Override + public int getPreviewActiveDuration() { + return lifespanData.maxLifetime > 0 ? Math.min(lifespanData.maxLifetime, 100) : 100; + } +} diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index 7dc3a8a82..d0005c7e0 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -12,6 +12,7 @@ import net.minecraft.util.Vec3; import net.minecraft.world.World; import java.util.List; +import java.util.UUID; /** * Disc projectile - flat spinning disc with optional boomerang behavior. @@ -48,6 +49,8 @@ public class EntityAbilityDisc extends EntityAbilityProjectile { // Data watcher index for charging state (synced to clients) private static final int DW_CHARGING = 20; + private UUID siblingUUID = null; + public EntityAbilityDisc(World world) { super(world); } @@ -565,6 +568,18 @@ public class EntityAbilityDisc extends EntityAbilityProjectile { return returning; } + public UUID getSiblingUUID() { + return siblingUUID; + } + + public void setSiblingUUID(UUID siblingUUID) { + if (siblingUUID == null) + return; + + if (this.siblingUUID == null) + this.siblingUUID = siblingUUID; + } + // ==================== NBT ==================== @Override @@ -587,6 +602,7 @@ public class EntityAbilityDisc extends EntityAbilityProjectile { this.targetDiscRadius = nbt.hasKey("TargetDiscRadius") ? nbt.getFloat("TargetDiscRadius") : this.discRadius; this.targetDiscThickness = nbt.hasKey("TargetDiscThickness") ? nbt.getFloat("TargetDiscThickness") : this.discThickness; this.anchorData.readNBT(nbt); + this.siblingUUID = nbt.hasKey("SiblingUUID") ? UUID.fromString(nbt.getString("SiblingUUID")) : null; } @Override @@ -609,5 +625,6 @@ public class EntityAbilityDisc extends EntityAbilityProjectile { // Anchor data this.anchorData.writeNBT(nbt); + nbt.setString("SiblingUUID", siblingUUID.toString()); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index a4384d035..250b1d629 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -473,6 +473,8 @@ public class EntityAbilityOrb extends EntityAbilityProjectile { this.targetSize = nbt.hasKey("TargetSize") ? nbt.getFloat("TargetSize") : this.size; // Anchor data this.anchorData.readNBT(nbt); + + this.siblingUUID = nbt.hasKey("SiblingUUID") ? UUID.fromString(nbt.getString("SiblingUUID")) : null; } @Override @@ -487,5 +489,7 @@ public class EntityAbilityOrb extends EntityAbilityProjectile { nbt.setInteger("ChargeTick", chargeTick); anchorData.writeNBT(nbt); nbt.setFloat("TargetSize", targetSize); + + nbt.setString("SiblingUUID", siblingUUID.toString()); } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualDisc.java b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualDisc.java new file mode 100644 index 000000000..f8bdbf9f2 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualDisc.java @@ -0,0 +1,342 @@ +package noppes.npcs.client.gui.advanced.ability; + +import kamkeel.npcs.controllers.data.ability.AbilityEffect; +import kamkeel.npcs.controllers.data.ability.AnchorPoint; +import kamkeel.npcs.controllers.data.ability.type.AbilityDisc; +import kamkeel.npcs.controllers.data.ability.type.AbilityDualDisc; +import noppes.npcs.client.gui.SubGuiColorSelector; +import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; +import noppes.npcs.client.gui.util.*; + +import java.util.List; + +public class SubGuiAbilityDualDisc extends SubGuiAbilityConfig { + private final AbilityDualDisc disc; + private int editingVisualColorId = 0; + private int editingDisc = 0; + + public SubGuiAbilityDualDisc(AbilityDualDisc ability, IAbilityConfigCallback callback) { + super(ability, callback); + this.disc = ability; + } + + @Override + protected boolean hasVisualSettings() { + return true; + } + + @Override + protected void initTypeTab(int startY) { + int y = startY; + int labelX = guiLeft + 8; + int fieldX = guiLeft + 85; + int col2LabelX = guiLeft + 145; + int col2FieldX = guiLeft + 205; + + // Row 1: Damage + Speed + addLabel(new GuiNpcLabel(100, "enchantment.damage", labelX, y + 5)); + addTextField(createFloatField(100, fieldX, y, 50, disc.getDamage())); + + addLabel(new GuiNpcLabel(101, "stats.speed", col2LabelX, y + 5)); + addTextField(createFloatField(101, col2FieldX, y, 50, disc.getSpeed())); + + y += 24; + + // Row 2: Disc Radius + Disc Thickness + addLabel(new GuiNpcLabel(102, "ability.discRadius", labelX, y + 5)); + addTextField(createFloatField(102, fieldX, y, 50, disc.getDiscRadius())); + + addLabel(new GuiNpcLabel(103, "ability.discThickness", col2LabelX, y + 5)); + addTextField(createFloatField(103, col2FieldX, y, 50, disc.getDiscThickness())); + + y += 24; + + // Row 3: Homing + Homing Strength (only show strength if homing) + addLabel(new GuiNpcLabel(104, "ability.homing", labelX, y + 5)); + GuiNpcButton homingBtn = new GuiNpcButton(104, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, disc.isHoming() ? 1 : 0); + homingBtn.setHoverText("ability.hover.homing"); + addButton(homingBtn); + + if (disc.isHoming()) { + addLabel(new GuiNpcLabel(105, "ability.homingStr", col2LabelX, y + 5)); + addTextField(createFloatField(105, col2FieldX, y, 50, disc.getHomingStrength())); + } + + y += 24; + + // Row 4: Boomerang + Boomerang Delay (only show delay if boomerang) + addLabel(new GuiNpcLabel(106, "ability.boomerang", labelX, y + 5)); + GuiNpcButton boomerangBtn = new GuiNpcButton(106, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, disc.isBoomerang() ? 1 : 0); + boomerangBtn.setHoverText("ability.hover.boomerang"); + addButton(boomerangBtn); + + if (disc.isBoomerang()) { + addLabel(new GuiNpcLabel(107, "ability.boomerangDelay", col2LabelX, y + 5)); + addTextField(createIntField(107, col2FieldX, y, 50, disc.getBoomerangDelay())); + } + + y += 24; + + // Row 5: Explosive + Explosion Radius (only show radius if explosive) + addLabel(new GuiNpcLabel(108, "ability.explosive", labelX, y + 5)); + GuiNpcButton explosiveBtn = new GuiNpcButton(108, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, disc.isExplosive() ? 1 : 0); + explosiveBtn.setHoverText("ability.hover.explosive"); + addButton(explosiveBtn); + + if (disc.isExplosive()) { + addLabel(new GuiNpcLabel(109, "ability.explosionRad", col2LabelX, y + 5)); + addTextField(createFloatField(109, col2FieldX, y, 50, disc.getExplosionRadius())); + } + + y += 24; + + // Row 6: Knockback + Max Distance + addLabel(new GuiNpcLabel(110, "ability.knockback", labelX, y + 5)); + addTextField(createFloatField(110, fieldX, y, 50, disc.getKnockback())); + + addLabel(new GuiNpcLabel(111, "ability.maxDist", col2LabelX, y + 5)); + addTextField(createFloatField(111, col2FieldX, y, 50, disc.getMaxDistance())); + + y += 24; + + // Row 7: Max Lifetime + Effects button + addLabel(new GuiNpcLabel(112, "ability.lifetime", labelX, y + 5)); + addTextField(createIntField(112, fieldX, y, 50, disc.getMaxLifetime())); + + y += 24; + + addLabel(new GuiNpcLabel(113, "ability.dualFire", labelX, y + 5)); + addButton(new GuiNpcButtonYesNo(113, fieldX, y, disc.isDualFire())); + + if (disc.isDualFire()) { + addLabel(new GuiNpcLabel(114, "ability.dualFireDelay", col2LabelX, y + 5)); + addTextField(createIntField(114, col2FieldX, y, 50, disc.getDualFireDelay())); + } + + addButton(new GuiNpcButton(150, col2LabelX + 120, y, 80, 20, "ability.effects")); + } + + @Override + protected void initVisualTab(int startY) { + int y = startY; + int labelX = guiLeft + 8; + int fieldX = guiLeft + 85; + int col2LabelX = guiLeft + 180; + int col2FieldX = guiLeft + 260; + + // Row 1: Anchor Point + addLabel(new GuiNpcLabel(210, "ability.anchorPoint", labelX, y + 5)); + GuiNpcButton anchorBtn = new GuiNpcButton(210, fieldX, y, 80, 20, AnchorPoint.getDisplayNames(), disc.getAnchorPointEnum(editingDisc).ordinal()); + anchorBtn.setHoverText("ability.hover.anchorPoint"); + addButton(anchorBtn); + + y += 24; + + // Row 2: Inner Color + Outer Color + addLabel(new GuiNpcLabel(200, "ability.innerColor", labelX, y + 5)); + String innerHex = String.format("%06X", disc.getInnerColor(editingDisc) & 0xFFFFFF); + GuiNpcButton innerColorBtn = new GuiNpcButton(200, fieldX, y, 55, 20, innerHex); + innerColorBtn.setTextColor(disc.getInnerColor(editingDisc) & 0xFFFFFF); + addButton(innerColorBtn); + + addLabel(new GuiNpcLabel(201, "ability.outerColor", col2LabelX, y + 5)); + String outerHex = String.format("%06X", disc.getOuterColor(editingDisc) & 0xFFFFFF); + GuiNpcButton outerColorBtn = new GuiNpcButton(201, col2FieldX, y, 55, 20, outerHex); + outerColorBtn.setTextColor(disc.getOuterColor(editingDisc) & 0xFFFFFF); + outerColorBtn.setEnabled(disc.isOuterColorEnabled(editingDisc)); + addButton(outerColorBtn); + + y += 24; + + // Row 2: Outer Color Enabled + Outer Color Width + addLabel(new GuiNpcLabel(202, "ability.outerEnabled", labelX, y + 5)); + addButton(new GuiNpcButton(202, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, disc.isOuterColorEnabled(editingDisc) ? 1 : 0)); + + addLabel(new GuiNpcLabel(203, "ability.outerWidth", col2LabelX, y + 5)); + GuiNpcTextField widthField = createFloatField(203, col2FieldX, y, 55, disc.getOuterColorWidth(editingDisc)); + widthField.setEnabled(disc.isOuterColorEnabled(editingDisc)); + addTextField(widthField); + + y += 24; + + // Row 3: Outer Color Alpha + addLabel(new GuiNpcLabel(204, "ability.outerAlpha", labelX, y + 5)); + GuiNpcTextField alphaField = createFloatField(204, fieldX, y, 55, disc.getOuterColorAlpha(editingDisc)); + alphaField.setEnabled(disc.isOuterColorEnabled(editingDisc)); + alphaField.setMinMaxDefaultFloat(0, 1, 0.5f); + addTextField(alphaField); + + y += 24; + + // Row 4: Rotation Speed + addLabel(new GuiNpcLabel(205, "ability.rotationSpeed", labelX, y + 5)); + addTextField(createFloatField(205, fieldX, y, 50, disc.getRotationSpeed(editingDisc))); + + y += 24; + + // Row 5: Lightning Effect Enabled + addLabel(new GuiNpcLabel(206, "ability.lightning", labelX, y + 5)); + GuiNpcButton lightningBtn = new GuiNpcButton(206, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, disc.hasLightningEffect(editingDisc) ? 1 : 0); + lightningBtn.setHoverText("ability.hover.lightning"); + addButton(lightningBtn); + + // Only show Lightning settings if Lightning is enabled + if (disc.hasLightningEffect(editingDisc)) { + y += 24; + + // Row 6: Density + Radius + addLabel(new GuiNpcLabel(207, "ability.lightningDensity", labelX, y + 5)); + GuiNpcTextField densityField = new GuiNpcTextField(207, this, fontRendererObj, fieldX, y, 55, 18, "" + disc.getLightningDensity(editingDisc)); + densityField.setMinMaxDefaultFloat(0.01f, 5.0f, 0.15f); + addTextField(densityField); + + addLabel(new GuiNpcLabel(208, "ability.lightningRadius", col2LabelX, y + 5)); + GuiNpcTextField radiusField = new GuiNpcTextField(208, this, fontRendererObj, col2FieldX, y, 55, 18, "" + disc.getLightningRadius(editingDisc)); + radiusField.setMinMaxDefaultFloat(0.1f, 10.0f, 0.5f); + addTextField(radiusField); + } + + y += 24; + + addButton(new GuiNpcButton(400, col2FieldX, y, 55, 20, new String[]{"Disc 1", "Disc 2"}, editingDisc)); + } + + @Override + protected void handleTypeButton(int id, GuiNpcButton button) { + int value = button.getValue(); + switch (id) { + case 104: + disc.setHoming(value == 1); + initGui(); + break; + case 106: + disc.setBoomerang(value == 1); + initGui(); + break; + case 108: + disc.setExplosive(value == 1); + initGui(); + break; + case 113: + disc.setDualFire(value == 1); + initGui(); + break; + case 150: + setSubGui(new SubGuiAbilityEffects(disc.getEffects())); + break; + } + } + + @Override + protected void handleVisualButton(int id, GuiNpcButton button) { + int value = button.getValue(); + switch (id) { + case 200: + editingVisualColorId = 200; + setSubGui(new SubGuiColorSelector(disc.getInnerColor(editingDisc))); + break; + case 201: + editingVisualColorId = 201; + setSubGui(new SubGuiColorSelector(disc.getOuterColor(editingDisc))); + break; + case 202: + disc.setOuterColorEnabled(editingDisc, value == 1); + initGui(); + break; + case 206: + disc.setLightningEffect(editingDisc, value == 1); + initGui(); + break; + case 210: + disc.setAnchorPointEnum(editingDisc, AnchorPoint.fromOrdinal(value)); + break; + case 400: + editingDisc = (editingDisc + 1) % 2; + initGui(); + break; + } + } + + @Override + public void subGuiClosed(SubGuiInterface subgui) { + if (subgui instanceof SubGuiColorSelector && editingVisualColorId != 0) { + SubGuiColorSelector colorSelector = (SubGuiColorSelector) subgui; + int rgb = colorSelector.color & 0x00FFFFFF; + if (editingVisualColorId == 200) { + disc.setInnerColor(editingDisc, rgb); + } else if (editingVisualColorId == 201) { + disc.setOuterColor(editingDisc, rgb); + } + editingVisualColorId = 0; + initGui(); + } else if (subgui instanceof SubGuiAbilityEffects) { + SubGuiAbilityEffects effectsGui = (SubGuiAbilityEffects) subgui; + List result = effectsGui.getResult(); + if (result != null) { + disc.setEffects(result); + } + } else { + super.subGuiClosed(subgui); + } + } + + @Override + protected void handleTypeTextField(int id, GuiNpcTextField field) { + switch (id) { + case 100: + disc.setDamage(parseFloat(field, disc.getDamage())); + break; + case 101: + disc.setSpeed(parseFloat(field, disc.getSpeed())); + break; + case 102: + disc.setDiscRadius(parseFloat(field, disc.getDiscRadius())); + break; + case 103: + disc.setDiscThickness(parseFloat(field, disc.getDiscThickness())); + break; + case 105: + disc.setHomingStrength(parseFloat(field, disc.getHomingStrength())); + break; + case 107: + disc.setBoomerangDelay(field.getInteger()); + break; + case 109: + disc.setExplosionRadius(parseFloat(field, disc.getExplosionRadius())); + break; + case 110: + disc.setKnockback(parseFloat(field, disc.getKnockback())); + break; + case 111: + disc.setMaxDistance(parseFloat(field, disc.getMaxDistance())); + break; + case 112: + disc.setMaxLifetime(field.getInteger()); + break; + case 114: + disc.setDualFireDelay(field.getInteger()); + break; + } + } + + @Override + protected void handleVisualTextField(int id, GuiNpcTextField field) { + switch (id) { + case 203: + disc.setOuterColorWidth(editingDisc, parseFloat(field, disc.getOuterColorWidth(editingDisc))); + break; + case 204: + disc.setOuterColorAlpha(editingDisc, parseFloat(field, disc.getOuterColorAlpha(editingDisc))); + break; + case 205: + disc.setRotationSpeed(editingDisc, parseFloat(field, disc.getRotationSpeed(editingDisc))); + break; + case 207: + disc.setLightningDensity(editingDisc, parseFloat(field, disc.getLightningDensity(editingDisc))); + break; + case 208: + disc.setLightningRadius(editingDisc, parseFloat(field, disc.getLightningRadius(editingDisc))); + break; + } + } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualOrb.java b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualOrb.java index 03a868a05..a8b9caadf 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualOrb.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityDualOrb.java @@ -3,7 +3,6 @@ package noppes.npcs.client.gui.advanced.ability; import kamkeel.npcs.controllers.data.ability.AbilityEffect; import kamkeel.npcs.controllers.data.ability.AnchorPoint; import kamkeel.npcs.controllers.data.ability.type.AbilityDualOrb; -import kamkeel.npcs.controllers.data.ability.type.AbilityOrb; import noppes.npcs.client.gui.SubGuiColorSelector; import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig; import noppes.npcs.client.gui.util.*; @@ -240,9 +239,9 @@ public class SubGuiAbilityDualOrb extends SubGuiAbilityConfig { SubGuiColorSelector colorSelector = (SubGuiColorSelector) subgui; int rgb = colorSelector.color & 0x00FFFFFF; if (editingVisualColorId == 200) { - orb.setInnerColor(rgb); + orb.setInnerColor(editingOrb, rgb); } else if (editingVisualColorId == 201) { - orb.setOuterColor(rgb); + orb.setOuterColor(editingOrb, rgb); } editingVisualColorId = 0; initGui(); diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index fef02e070..f64e3820f 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1562,8 +1562,10 @@ ability.cnpc.slam=Slam ability.cnpc.heavy_hit=Heavy Hit ability.cnpc.cutter=Cutter ability.cnpc.beam=Beam +ability.cnpc.dual_beam=Dual Beam ability.cnpc.projectile=Projectile ability.cnpc.orb=Orb +ability.cnpc.dual_orb=Dual Orb ability.cnpc.charge=Charge ability.cnpc.dash=Dash ability.cnpc.teleport=Teleport @@ -1574,6 +1576,7 @@ ability.cnpc.heal=Heal ability.cnpc.hazard=Hazard ability.cnpc.trap=Trap ability.cnpc.disc=Disc +ability.cnpc.dual_disc=Dual Disc ability.cnpc.laser_shot=Laser Shot ability.cnpc.sweeper=Sweeper @@ -1699,6 +1702,10 @@ ability.lightningRadius=Radius ability.anchorPoint=Anchor Point ability.noVisualSettings=No visual settings for this ability type +# Dual Energy Projectile Settings +ability.dualFire=Dual Fire +ability.dualFireDelay=Fire Delay + # Laser Shot Settings ability.laserWidth=Laser Width ability.expansionSpeed=Expansion Spd