mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 03:47:27 +00:00
Built In Animation System + Fixes to Defaults for Energy Projectile Abilities
This commit is contained in:
1 parent
7d2bba7b95
commit
e73054a529
20 files changed
+622
-238
No files matched your search
@@ -64,8 +64,12 @@ public abstract class Ability implements IAbility {
|
||||
protected String activeSound = ""; // Sound to play when active phase starts
|
||||
|
||||
// Animations (global animation IDs, -1 = none)
|
||||
protected int windUpAnimationId = -1; // Animation to play during wind up
|
||||
protected int activeAnimationId = -1; // Animation to play during active phase
|
||||
protected int windUpAnimationId = -1; // Animation to play during wind up (user animations)
|
||||
protected int activeAnimationId = -1; // Animation to play during active phase (user animations)
|
||||
|
||||
// Built-in animation names (empty = none, takes priority over IDs if set)
|
||||
protected String windUpAnimationName = ""; // Built-in animation name for wind up
|
||||
protected String activeAnimationName = ""; // Built-in animation name for active phase
|
||||
|
||||
// Telegraph configuration
|
||||
protected boolean showTelegraph = true;
|
||||
@@ -669,6 +673,8 @@ public abstract class Ability implements IAbility {
|
||||
nbt.setString("activeSound", activeSound);
|
||||
nbt.setInteger("windUpAnimationId", windUpAnimationId);
|
||||
nbt.setInteger("activeAnimationId", activeAnimationId);
|
||||
nbt.setString("windUpAnimationName", windUpAnimationName);
|
||||
nbt.setString("activeAnimationName", activeAnimationName);
|
||||
nbt.setBoolean("showTelegraph", showTelegraph);
|
||||
nbt.setString("telegraphType", telegraphType.name());
|
||||
nbt.setFloat("telegraphHeightOffset", telegraphHeightOffset);
|
||||
@@ -738,6 +744,8 @@ public abstract class Ability implements IAbility {
|
||||
windUpAnimationId = -1;
|
||||
}
|
||||
activeAnimationId = nbt.hasKey("activeAnimationId") ? nbt.getInteger("activeAnimationId") : -1;
|
||||
windUpAnimationName = nbt.hasKey("windUpAnimationName") ? nbt.getString("windUpAnimationName") : "";
|
||||
activeAnimationName = nbt.hasKey("activeAnimationName") ? nbt.getString("activeAnimationName") : "";
|
||||
showTelegraph = !nbt.hasKey("showTelegraph") || nbt.getBoolean("showTelegraph");
|
||||
if (nbt.hasKey("telegraphType")) {
|
||||
try {
|
||||
@@ -959,23 +967,63 @@ public abstract class Ability implements IAbility {
|
||||
public Animation getWindUpAnimation() {
|
||||
if (AnimationController.Instance == null) return null;
|
||||
|
||||
return (Animation) AnimationController.Instance.get(windUpAnimationId);
|
||||
// Built-in animation by name takes priority
|
||||
if (windUpAnimationName != null && !windUpAnimationName.isEmpty()) {
|
||||
return (Animation) AnimationController.Instance.get(windUpAnimationName);
|
||||
}
|
||||
// Fall back to user animation by ID
|
||||
if (windUpAnimationId >= 0) {
|
||||
return (Animation) AnimationController.Instance.get(windUpAnimationId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getWindUpAnimationId() {
|
||||
return windUpAnimationId;
|
||||
}
|
||||
|
||||
public void setWindUpAnimationId(int windUpAnimationId) {
|
||||
this.windUpAnimationId = windUpAnimationId;
|
||||
}
|
||||
|
||||
public String getWindUpAnimationName() {
|
||||
return windUpAnimationName;
|
||||
}
|
||||
|
||||
public void setWindUpAnimationName(String windUpAnimationName) {
|
||||
this.windUpAnimationName = windUpAnimationName != null ? windUpAnimationName : "";
|
||||
}
|
||||
|
||||
public Animation getActiveAnimation() {
|
||||
if (AnimationController.Instance == null) return null;
|
||||
|
||||
return (Animation) AnimationController.Instance.get(activeAnimationId);
|
||||
// Built-in animation by name takes priority
|
||||
if (activeAnimationName != null && !activeAnimationName.isEmpty()) {
|
||||
return (Animation) AnimationController.Instance.get(activeAnimationName);
|
||||
}
|
||||
// Fall back to user animation by ID
|
||||
if (activeAnimationId >= 0) {
|
||||
return (Animation) AnimationController.Instance.get(activeAnimationId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getActiveAnimationId() {
|
||||
return activeAnimationId;
|
||||
}
|
||||
|
||||
public void setActiveAnimationId(int activeAnimationId) {
|
||||
this.activeAnimationId = activeAnimationId;
|
||||
}
|
||||
|
||||
public String getActiveAnimationName() {
|
||||
return activeAnimationName;
|
||||
}
|
||||
|
||||
public void setActiveAnimationName(String activeAnimationName) {
|
||||
this.activeAnimationName = activeAnimationName != null ? activeAnimationName : "";
|
||||
}
|
||||
|
||||
public boolean isShowTelegraph() {
|
||||
return showTelegraph;
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package kamkeel.npcs.controllers.data.ability;
|
||||
|
||||
import noppes.npcs.CustomNpcs;
|
||||
import noppes.npcs.LogWriter;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.util.NBTJsonUtil;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static kamkeel.npcs.controllers.data.ability.AbilityPhase.*;
|
||||
|
||||
public enum AbilityAnimation {
|
||||
ABILITYORB("Ability_Orb_Windup", WINDUP),
|
||||
ABILITYBEAM("Ability_Beam_Windup", "Ability_Beam_Active");
|
||||
private final HashMap<AbilityPhase, String> animationNames = new HashMap<>();
|
||||
|
||||
AbilityAnimation(String idle, String windUp, String active, String recovery) {
|
||||
animationNames.put(IDLE, idle);
|
||||
animationNames.put(WINDUP, windUp);
|
||||
animationNames.put(ACTIVE, active);
|
||||
animationNames.put(DAZED, recovery);
|
||||
}
|
||||
|
||||
AbilityAnimation(String idle, String windUp, String active) {
|
||||
animationNames.put(IDLE, idle);
|
||||
animationNames.put(WINDUP, windUp);
|
||||
animationNames.put(ACTIVE, active);
|
||||
}
|
||||
|
||||
AbilityAnimation(String windUp, String active) {
|
||||
animationNames.put(WINDUP, windUp);
|
||||
animationNames.put(ACTIVE, active);
|
||||
}
|
||||
|
||||
AbilityAnimation(String anim, AbilityPhase phase) {
|
||||
animationNames.put(phase, anim);
|
||||
}
|
||||
|
||||
public String fileName(AbilityPhase phase) {
|
||||
return animationNames.get(phase);
|
||||
}
|
||||
|
||||
public File getFile(AbilityPhase phase) {
|
||||
if (animationNames.get(phase).isEmpty() || animationNames.get(phase).equals("N/A"))
|
||||
return null;
|
||||
|
||||
return getFileFromName(animationNames.get(phase));
|
||||
}
|
||||
|
||||
public Animation getAnimation(AbilityPhase phase) {
|
||||
try {
|
||||
File file = getFile(phase);
|
||||
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Animation animation = new Animation();
|
||||
animation.readFromNBT(NBTJsonUtil.LoadFile(file));
|
||||
|
||||
return animation;
|
||||
} catch (Exception e) {
|
||||
LogWriter.except(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Animation idle() {
|
||||
return getAnimation(IDLE);
|
||||
}
|
||||
|
||||
public Animation windUp() {
|
||||
return getAnimation(WINDUP);
|
||||
}
|
||||
|
||||
public Animation active() {
|
||||
return getAnimation(ACTIVE);
|
||||
}
|
||||
|
||||
public Animation dazed() {
|
||||
return getAnimation(DAZED);
|
||||
}
|
||||
|
||||
private File getFileFromName(String fileName) {
|
||||
try (InputStream stream = CustomNpcs.class.getResourceAsStream("/internal/animations/" + name() + "/" + fileName + ".json")) {
|
||||
|
||||
File file = new File("temp");
|
||||
|
||||
FileUtils.copyInputStreamToFile(stream, file);
|
||||
|
||||
return file;
|
||||
} catch (Exception i) {
|
||||
LogWriter.except(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -121,10 +121,13 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
if (discEntity != null && !discEntity.isDead) {
|
||||
discEntity.startMoving(target);
|
||||
}
|
||||
discEntity = null;
|
||||
|
||||
// Disc entity manages itself - ability is done
|
||||
signalCompletion();
|
||||
// If movement is locked during active phase, keep ability active until entity dies
|
||||
// Otherwise signal completion immediately
|
||||
if (!lockMovement.locksActive()) {
|
||||
discEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,6 +157,11 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityNPCInterface npc, EntityLivingBase target, World world, int tick) {
|
||||
// If we're tracking the entity for movement lock, check if it's dead
|
||||
if (discEntity != null && discEntity.isDead) {
|
||||
discEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,7 +3,6 @@ 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.AbilityAnimation;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.LockMovementType;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
@@ -13,13 +12,12 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityAbilityBeam;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
|
||||
import noppes.npcs.client.gui.advanced.ability.SubGuiAbilityEnergyBeam;
|
||||
import noppes.npcs.client.gui.util.IAbilityConfigCallback;
|
||||
import noppes.npcs.api.ability.type.IAbilityEnergyBeam;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
/**
|
||||
@@ -71,9 +69,7 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
|
||||
private float lightningRadius = 0.5f;
|
||||
|
||||
// Anchor point for charging position
|
||||
private AnchorPoint anchorPoint = AnchorPoint.FRONT;
|
||||
|
||||
private final AbilityAnimation animation = AbilityAnimation.ABILITYBEAM;
|
||||
private AnchorPoint anchorPoint = AnchorPoint.RIGHT_HAND;
|
||||
|
||||
// Transient state for beam entity (used during windup charging)
|
||||
private transient EntityAbilityBeam beamEntity = null;
|
||||
@@ -89,6 +85,9 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
|
||||
this.lockMovement = LockMovementType.WINDUP_AND_ACTIVE;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animations
|
||||
this.windUpAnimationName = "Ability_Beam_Windup";
|
||||
this.activeAnimationName = "Ability_Beam_Active";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,10 +122,13 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
|
||||
if (beamEntity != null && !beamEntity.isDead) {
|
||||
beamEntity.startFiring(target);
|
||||
}
|
||||
beamEntity = null;
|
||||
|
||||
// Beam entity manages itself - ability is done
|
||||
signalCompletion();
|
||||
// If movement is locked during active phase, keep ability active until entity dies
|
||||
// Otherwise signal completion immediately
|
||||
if (!lockMovement.locksActive()) {
|
||||
beamEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,6 +161,11 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityNPCInterface npc, EntityLivingBase target, World world, int tick) {
|
||||
// If we're tracking the entity for movement lock, check if it's dead
|
||||
if (beamEntity != null && beamEntity.isDead) {
|
||||
beamEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -313,23 +320,6 @@ public class AbilityEnergyBeam extends Ability implements IAbilityEnergyBeam {
|
||||
public float getLightningRadius() { return lightningRadius; }
|
||||
public void setLightningRadius(float lightningRadius) { this.lightningRadius = lightningRadius; }
|
||||
|
||||
|
||||
@Override
|
||||
public Animation getWindUpAnimation() {
|
||||
if (windUpAnimationId == -1)
|
||||
return animation.windUp();
|
||||
|
||||
return super.getWindUpAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animation getActiveAnimation() {
|
||||
if (activeAnimationId == -1)
|
||||
return animation.active();
|
||||
|
||||
return super.getActiveAnimation();
|
||||
}
|
||||
|
||||
public AnchorPoint getAnchorPointEnum() { return anchorPoint; }
|
||||
public void setAnchorPointEnum(AnchorPoint anchorPoint) { this.anchorPoint = anchorPoint; }
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
private float lightningDensity = 0.15f;
|
||||
private float lightningRadius = 0.5f;
|
||||
|
||||
// Transient state for laser entity (used for movement locking)
|
||||
private transient EntityAbilityLaser laserEntity = null;
|
||||
|
||||
public AbilityLaserShot() {
|
||||
this.typeId = "ability.cnpc.laser_shot";
|
||||
this.name = "Laser Shot";
|
||||
@@ -111,7 +114,7 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
double spawnY = npc.posY + npc.getEyeHeight() * 0.8;
|
||||
double spawnZ = npc.posZ;
|
||||
|
||||
EntityAbilityLaser laser = new EntityAbilityLaser(
|
||||
laserEntity = new EntityAbilityLaser(
|
||||
world, npc, target,
|
||||
spawnX, spawnY, spawnZ,
|
||||
laserWidth, innerColor, outerColor, outerColorEnabled, outerColorWidth,
|
||||
@@ -123,15 +126,23 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
lightningEffect, lightningDensity, lightningRadius
|
||||
);
|
||||
|
||||
world.spawnEntityInWorld(laser);
|
||||
world.spawnEntityInWorld(laserEntity);
|
||||
|
||||
// Laser entity manages itself - ability is done
|
||||
signalCompletion();
|
||||
// If movement is locked during active phase, keep ability active until entity dies
|
||||
// Otherwise signal completion immediately
|
||||
if (!lockMovement.locksActive()) {
|
||||
laserEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityNPCInterface npc, EntityLivingBase target, World world, int tick) {
|
||||
// Laser entity manages itself
|
||||
// If we're tracking the entity for movement lock, check if it's dead
|
||||
if (laserEntity != null && laserEntity.isDead) {
|
||||
laserEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,6 +153,15 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
|
||||
public void onInterrupt(EntityNPCInterface npc, DamageSource source, float damage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// Despawn laser entity if still alive
|
||||
if (laserEntity != null && !laserEntity.isDead) {
|
||||
laserEntity.setDead();
|
||||
}
|
||||
laserEntity = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeNBT(NBTTagCompound nbt) {
|
||||
nbt.setFloat("laserWidth", laserWidth);
|
||||
|
||||
@@ -3,7 +3,6 @@ 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.AbilityAnimation;
|
||||
import kamkeel.npcs.controllers.data.ability.AnchorPoint;
|
||||
import kamkeel.npcs.controllers.data.ability.LockMovementType;
|
||||
import kamkeel.npcs.controllers.data.ability.TargetingMode;
|
||||
@@ -19,7 +18,6 @@ import net.minecraft.world.World;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityConfig;
|
||||
import noppes.npcs.client.gui.advanced.ability.SubGuiAbilityOrb;
|
||||
import noppes.npcs.client.gui.util.IAbilityConfigCallback;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.api.ability.type.IAbilityOrb;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
|
||||
@@ -70,13 +68,11 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
private int lightningFadeTime = 6; // Ticks before lightning fades out
|
||||
|
||||
// Anchor point for charging position
|
||||
private AnchorPoint anchorPoint = AnchorPoint.FRONT;
|
||||
private AnchorPoint anchorPoint = AnchorPoint.RIGHT_HAND;
|
||||
|
||||
// Transient state for orb entity (used during windup charging)
|
||||
private transient EntityAbilityOrb orbEntity = null;
|
||||
|
||||
private final AbilityAnimation animation = AbilityAnimation.ABILITYORB;
|
||||
|
||||
public AbilityOrb() {
|
||||
this.typeId = "ability.cnpc.orb";
|
||||
this.name = "Orb";
|
||||
@@ -88,6 +84,8 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
this.lockMovement = LockMovementType.WINDUP;
|
||||
this.telegraphType = TelegraphType.CIRCLE;
|
||||
this.showTelegraph = true;
|
||||
// Default built-in animation
|
||||
this.windUpAnimationName = "Ability_Orb_Windup";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,10 +120,13 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
if (orbEntity != null && !orbEntity.isDead) {
|
||||
orbEntity.startMoving(target);
|
||||
}
|
||||
orbEntity = null;
|
||||
|
||||
// Orb entity manages itself - ability is done
|
||||
signalCompletion();
|
||||
// If movement is locked during active phase, keep ability active until entity dies
|
||||
// Otherwise signal completion immediately
|
||||
if (!lockMovement.locksActive()) {
|
||||
orbEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,7 +155,11 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityNPCInterface npc, EntityLivingBase target, World world, int tick) {
|
||||
// Nothing to do - entity manages itself
|
||||
// If we're tracking the entity for movement lock, check if it's dead
|
||||
if (orbEntity != null && orbEntity.isDead) {
|
||||
orbEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -467,14 +472,6 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
|
||||
this.anchorPoint = anchorPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animation getWindUpAnimation() {
|
||||
if (windUpAnimationId != -1)
|
||||
return super.getWindUpAnimation();
|
||||
|
||||
return animation.windUp();
|
||||
}
|
||||
|
||||
public int getAnchorPoint() {
|
||||
return anchorPoint.getId();
|
||||
}
|
||||
|
||||
@@ -91,8 +91,6 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
return;
|
||||
}
|
||||
|
||||
LogWriter.info("[Sweeper] onExecute called at NPC pos " + npc.posX + ", " + npc.posY + ", " + npc.posZ);
|
||||
|
||||
// Spawn the entity that handles BOTH visuals AND damage
|
||||
activeEntity = new EntityAbilitySweeper(world, npc, target,
|
||||
beamLength, beamWidth, beamHeight,
|
||||
@@ -102,14 +100,19 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
|
||||
lockOnTarget);
|
||||
world.spawnEntityInWorld(activeEntity);
|
||||
|
||||
LogWriter.info("[Sweeper] Spawned sweeper entity");
|
||||
// Entity manages itself - completion signaled from onActiveTick when entity dies
|
||||
// If movement is locked during active phase, keep ability active until entity dies
|
||||
// Otherwise signal completion immediately
|
||||
if (!lockMovement.locksActive()) {
|
||||
activeEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveTick(EntityNPCInterface npc, EntityLivingBase target, World world, int tick) {
|
||||
// Entity handles everything - check if entity finished its rotations
|
||||
if (activeEntity == null || activeEntity.isDead) {
|
||||
// If we're tracking the entity for movement lock, check if it's dead
|
||||
if (activeEntity != null && activeEntity.isDead) {
|
||||
activeEntity = null;
|
||||
signalCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,5 +44,6 @@ public final class AnimationsGetPacket extends AbstractPacket {
|
||||
return;
|
||||
|
||||
NoppesUtilServer.sendAnimationDataAll((EntityPlayerMP) player);
|
||||
NoppesUtilServer.sendBuiltInAnimationData((EntityPlayerMP) player);
|
||||
}
|
||||
}
|
||||
@@ -412,7 +412,7 @@ public class DataAbilities {
|
||||
* @param animation The animation to play, or null if none
|
||||
*/
|
||||
public void playAbilityAnimation(Animation animation) {
|
||||
if (animation == null || animation.id < 0) return;
|
||||
if (animation == null) return;
|
||||
if (AnimationController.Instance == null) return;
|
||||
|
||||
npc.display.animationData.setEnabled(true);
|
||||
|
||||
@@ -163,7 +163,16 @@ public class NoppesUtilServer {
|
||||
for (Animation animation : AnimationController.getInstance().animations.values()) {
|
||||
map.put(animation.name, animation.id);
|
||||
}
|
||||
sendScrollData(player, map, EnumScrollData.OPTIONAL);
|
||||
sendScrollData(player, map, EnumScrollData.ANIMATIONS);
|
||||
}
|
||||
|
||||
public static void sendBuiltInAnimationData(EntityPlayerMP player) {
|
||||
Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
for (String name : AnimationController.getInstance().builtInAnimations.keySet()) {
|
||||
// Use -1 as a marker for built-in animations (they have no ID)
|
||||
map.put(AnimationController.getInstance().builtInAnimations.get(name).getName(), -1);
|
||||
}
|
||||
sendScrollData(player, map, EnumScrollData.BUILTIN_ANIMATIONS);
|
||||
}
|
||||
|
||||
public static void sendTagDataAll(EntityPlayerMP player) {
|
||||
|
||||
@@ -129,8 +129,11 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
// Effects
|
||||
this.windUpSound = ability.getWindUpSound();
|
||||
this.activeSound = ability.getActiveSound();
|
||||
this.windUpAnimationId = ability.getWindUpAnimation() != null ? ability.getWindUpAnimation().getID() : -1;
|
||||
this.activeAnimationId = ability.getActiveAnimation() != null ? ability.getActiveAnimation().getID() : -1;
|
||||
// Animation: check name first (built-in), then ID (user)
|
||||
this.windUpAnimationName = ability.getWindUpAnimationName();
|
||||
this.activeAnimationName = ability.getActiveAnimationName();
|
||||
this.windUpAnimationId = ability.getWindUpAnimationId();
|
||||
this.activeAnimationId = ability.getActiveAnimationId();
|
||||
|
||||
// Telegraph
|
||||
this.showTelegraph = ability.isShowTelegraph();
|
||||
@@ -286,8 +289,8 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
windupField.setMinMaxDefault(0, 1000, 20);
|
||||
addTextField(windupField);
|
||||
|
||||
// Sync button - only show if windup animation is selected
|
||||
if (windUpAnimationId >= 0) {
|
||||
// Sync button - only show if windup animation is selected (either by name or ID)
|
||||
if (windUpAnimationId >= 0 || (windUpAnimationName != null && !windUpAnimationName.isEmpty())) {
|
||||
GuiNpcButton syncBtn = new GuiNpcButton(17, guiLeft + 115, y, 40, 20, "gui.sync");
|
||||
syncBtn.setHoverText("ability.hover.sync");
|
||||
addButton(syncBtn);
|
||||
@@ -591,12 +594,16 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
}
|
||||
|
||||
private String getAnimationName(int animId, boolean isWindUp) {
|
||||
if (animId < 0) return StatCollector.translateToLocal("gui.none");
|
||||
String cachedName = isWindUp ? windUpAnimationName : activeAnimationName;
|
||||
if (cachedName != null && !cachedName.isEmpty()) {
|
||||
return truncateString(cachedName, 8);
|
||||
String animName = isWindUp ? windUpAnimationName : activeAnimationName;
|
||||
// Check if we have a built-in animation (by name)
|
||||
if (animName != null && !animName.isEmpty()) {
|
||||
return truncateString(animName, 8);
|
||||
}
|
||||
return "ID: " + animId;
|
||||
// Check if we have a user animation (by ID)
|
||||
if (animId >= 0) {
|
||||
return "ID: " + animId;
|
||||
}
|
||||
return StatCollector.translateToLocal("gui.none");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -726,19 +733,19 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
// Animation buttons
|
||||
else if (id == 32) {
|
||||
editingAnimationId = 32;
|
||||
setSubGui(new GuiAnimationSelection(windUpAnimationId));
|
||||
setSubGui(new GuiAnimationSelection(windUpAnimationId, windUpAnimationName));
|
||||
} else if (id == 33) {
|
||||
editingAnimationId = 33;
|
||||
setSubGui(new GuiAnimationSelection(activeAnimationId));
|
||||
setSubGui(new GuiAnimationSelection(activeAnimationId, activeAnimationName));
|
||||
}
|
||||
// Animation clear buttons
|
||||
else if (id == 37) {
|
||||
windUpAnimationId = -1;
|
||||
windUpAnimationName = null;
|
||||
windUpAnimationName = "";
|
||||
initGui();
|
||||
} else if (id == 38) {
|
||||
activeAnimationId = -1;
|
||||
activeAnimationName = null;
|
||||
activeAnimationName = "";
|
||||
initGui();
|
||||
}
|
||||
|
||||
@@ -824,11 +831,25 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
} else if (subgui instanceof GuiAnimationSelection) {
|
||||
GuiAnimationSelection animSelector = (GuiAnimationSelection) subgui;
|
||||
if (editingAnimationId == 32) {
|
||||
windUpAnimationId = animSelector.selectedAnimationId;
|
||||
windUpAnimationName = animSelector.getSelectedName();
|
||||
if (animSelector.isBuiltInSelected()) {
|
||||
// Built-in animation selected - use name, clear ID
|
||||
windUpAnimationName = animSelector.selectedBuiltInName;
|
||||
windUpAnimationId = -1;
|
||||
} else {
|
||||
// User animation selected - use ID, clear name
|
||||
windUpAnimationId = animSelector.selectedAnimationId;
|
||||
windUpAnimationName = "";
|
||||
}
|
||||
} else if (editingAnimationId == 33) {
|
||||
activeAnimationId = animSelector.selectedAnimationId;
|
||||
activeAnimationName = animSelector.getSelectedName();
|
||||
if (animSelector.isBuiltInSelected()) {
|
||||
// Built-in animation selected - use name, clear ID
|
||||
activeAnimationName = animSelector.selectedBuiltInName;
|
||||
activeAnimationId = -1;
|
||||
} else {
|
||||
// User animation selected - use ID, clear name
|
||||
activeAnimationId = animSelector.selectedAnimationId;
|
||||
activeAnimationName = "";
|
||||
}
|
||||
}
|
||||
editingAnimationId = 0;
|
||||
initGui();
|
||||
@@ -896,6 +917,8 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
ability.setActiveSound(activeSound);
|
||||
ability.setWindUpAnimationId(windUpAnimationId);
|
||||
ability.setActiveAnimationId(activeAnimationId);
|
||||
ability.setWindUpAnimationName(windUpAnimationName != null ? windUpAnimationName : "");
|
||||
ability.setActiveAnimationName(activeAnimationName != null ? activeAnimationName : "");
|
||||
|
||||
// Telegraph
|
||||
ability.setShowTelegraph(showTelegraph);
|
||||
@@ -948,11 +971,20 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi
|
||||
* Syncs windup ticks with the selected windup animation's total duration.
|
||||
*/
|
||||
private void syncWindupWithAnimation() {
|
||||
if (windUpAnimationId < 0 || AnimationController.Instance == null) {
|
||||
if (AnimationController.Instance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Animation animation = (Animation) AnimationController.Instance.get(windUpAnimationId);
|
||||
Animation animation = null;
|
||||
// Check for built-in animation (by name) first
|
||||
if (windUpAnimationName != null && !windUpAnimationName.isEmpty()) {
|
||||
animation = (Animation) AnimationController.Instance.get(windUpAnimationName);
|
||||
}
|
||||
// Fall back to user animation (by ID)
|
||||
else if (windUpAnimationId >= 0) {
|
||||
animation = (Animation) AnimationController.Instance.get(windUpAnimationId);
|
||||
}
|
||||
|
||||
if (animation == null || animation.frames.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,20 +19,42 @@ import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Animation selection dialog for choosing global animations.
|
||||
* Requests animation data from the server via packets.
|
||||
* Toggle between Built-in animations (by name) and Custom animations (by ID).
|
||||
*/
|
||||
public class GuiAnimationSelection extends SubGuiInterface implements ICustomScrollListener, IScrollData {
|
||||
|
||||
private GuiCustomScroll scrollAnimations;
|
||||
private HashMap<String, Integer> animationData = new HashMap<String, Integer>();
|
||||
|
||||
// Built-in animations (name only, no ID)
|
||||
private HashMap<String, Integer> builtInData = new HashMap<>();
|
||||
// Custom/user animations (name -> ID)
|
||||
private HashMap<String, Integer> customData = new HashMap<>();
|
||||
|
||||
// View mode: true = built-in, false = custom
|
||||
private boolean showingBuiltIn = true;
|
||||
|
||||
// Initial values passed in
|
||||
private int initialAnimationId;
|
||||
public int selectedAnimationId = -1;
|
||||
private String selectedName = null;
|
||||
private String initialAnimationName;
|
||||
|
||||
// Selected values to return
|
||||
public int selectedAnimationId = -1;
|
||||
public String selectedBuiltInName = "";
|
||||
|
||||
private String selectedCustomName = null;
|
||||
private String search = "";
|
||||
|
||||
/**
|
||||
* Constructor for selecting by ID (user animations).
|
||||
*/
|
||||
public GuiAnimationSelection(int animationId) {
|
||||
this(animationId, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for selecting by name (built-in) or ID (user).
|
||||
*/
|
||||
public GuiAnimationSelection(int animationId, String animationName) {
|
||||
drawDefaultBackground = false;
|
||||
title = "";
|
||||
setBackground("menubg.png");
|
||||
@@ -40,7 +62,12 @@ public class GuiAnimationSelection extends SubGuiInterface implements ICustomScr
|
||||
ySize = 226;
|
||||
|
||||
this.initialAnimationId = animationId;
|
||||
this.initialAnimationName = animationName != null ? animationName : "";
|
||||
this.selectedAnimationId = animationId;
|
||||
this.selectedBuiltInName = this.initialAnimationName;
|
||||
|
||||
// Start showing built-in if we have a built-in name, otherwise custom
|
||||
this.showingBuiltIn = !this.initialAnimationName.isEmpty();
|
||||
|
||||
// Request animation data from server
|
||||
PacketClient.sendClient(new AnimationsGetPacket());
|
||||
@@ -50,56 +77,87 @@ public class GuiAnimationSelection extends SubGuiInterface implements ICustomScr
|
||||
public void initGui() {
|
||||
super.initGui();
|
||||
|
||||
this.addLabel(new GuiNpcLabel(0, "menu.animations", guiLeft + 8, guiTop + 4));
|
||||
int y = guiTop + 4;
|
||||
|
||||
this.addButton(new GuiNpcButton(1, guiLeft + 4, guiTop + ySize - 35, 50, 20, "gui.clear"));
|
||||
this.addButton(new GuiNpcButton(2, guiLeft + xSize - 56, guiTop + ySize - 35, 50, 20, "gui.done"));
|
||||
this.addButton(new GuiNpcButton(3, guiLeft + xSize - 108, guiTop + ySize - 35, 50, 20, "gui.cancel"));
|
||||
// Toggle button for switching views
|
||||
String viewLabel = showingBuiltIn ? "gui.builtinAnimations" : "gui.customAnimations";
|
||||
this.addButton(new GuiNpcButton(10, guiLeft + 4, y, 212, 20, viewLabel));
|
||||
y += 24;
|
||||
|
||||
// Animation scroll list
|
||||
if (scrollAnimations == null) {
|
||||
scrollAnimations = new GuiCustomScroll(this, 0, 0);
|
||||
scrollAnimations.setSize(212, 150);
|
||||
scrollAnimations.setSize(212, 130);
|
||||
}
|
||||
scrollAnimations.setList(getSearchList());
|
||||
if (selectedName != null) {
|
||||
scrollAnimations.setSelected(selectedName);
|
||||
}
|
||||
scrollAnimations.guiLeft = guiLeft + 4;
|
||||
scrollAnimations.guiTop = guiTop + 14;
|
||||
this.addScroll(scrollAnimations);
|
||||
|
||||
// Search textbox - moved up to avoid overlapping with buttons
|
||||
addTextField(new GuiNpcTextField(55, this, fontRendererObj, guiLeft + 4, guiTop + 166, 212, 20, search));
|
||||
// Set selection based on current mode
|
||||
if (showingBuiltIn && !selectedBuiltInName.isEmpty()) {
|
||||
scrollAnimations.setSelected(selectedBuiltInName);
|
||||
} else if (!showingBuiltIn && selectedCustomName != null) {
|
||||
scrollAnimations.setSelected(selectedCustomName);
|
||||
}
|
||||
|
||||
scrollAnimations.guiLeft = guiLeft + 4;
|
||||
scrollAnimations.guiTop = y;
|
||||
this.addScroll(scrollAnimations);
|
||||
y += 134;
|
||||
|
||||
// Search textbox
|
||||
addTextField(new GuiNpcTextField(55, this, fontRendererObj, guiLeft + 4, y, 212, 20, search));
|
||||
y += 24;
|
||||
|
||||
// Buttons
|
||||
this.addButton(new GuiNpcButton(1, guiLeft + 4, y, 50, 20, "gui.clear"));
|
||||
this.addButton(new GuiNpcButton(3, guiLeft + xSize - 108, y, 50, 20, "gui.cancel"));
|
||||
this.addButton(new GuiNpcButton(2, guiLeft + xSize - 56, y, 50, 20, "gui.done"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(Vector<String> list, HashMap<String, Integer> data, EnumScrollData type) {
|
||||
this.animationData = data;
|
||||
if (type == EnumScrollData.BUILTIN_ANIMATIONS) {
|
||||
this.builtInData = data;
|
||||
|
||||
// Find the name for our initial animation ID
|
||||
if (initialAnimationId >= 0) {
|
||||
for (String name : data.keySet()) {
|
||||
if (data.get(name) == initialAnimationId) {
|
||||
selectedName = name;
|
||||
break;
|
||||
// Check if initial name matches a built-in animation
|
||||
if (!initialAnimationName.isEmpty()) {
|
||||
for (String name : builtInData.keySet()) {
|
||||
if (name.equalsIgnoreCase(initialAnimationName)) {
|
||||
selectedBuiltInName = name;
|
||||
selectedAnimationId = -1;
|
||||
selectedCustomName = null;
|
||||
showingBuiltIn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type == EnumScrollData.ANIMATIONS) {
|
||||
this.customData = data;
|
||||
|
||||
// Find the name for our initial animation ID (if not using built-in)
|
||||
if (selectedBuiltInName.isEmpty() && initialAnimationId >= 0) {
|
||||
for (String name : data.keySet()) {
|
||||
if (data.get(name) == initialAnimationId) {
|
||||
selectedCustomName = name;
|
||||
showingBuiltIn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollAnimations != null) {
|
||||
scrollAnimations.setList(getSearchList());
|
||||
if (selectedName != null) {
|
||||
scrollAnimations.setSelected(selectedName);
|
||||
if (showingBuiltIn && !selectedBuiltInName.isEmpty()) {
|
||||
scrollAnimations.setSelected(selectedBuiltInName);
|
||||
} else if (!showingBuiltIn && selectedCustomName != null) {
|
||||
scrollAnimations.setSelected(selectedCustomName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(String selected) {
|
||||
this.selectedName = selected;
|
||||
if (scrollAnimations != null) {
|
||||
scrollAnimations.setSelected(selected);
|
||||
}
|
||||
// Not used in this implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,18 +168,22 @@ public class GuiAnimationSelection extends SubGuiInterface implements ICustomScr
|
||||
if (search.equals(getTextField(55).getText()))
|
||||
return;
|
||||
search = getTextField(55).getText().toLowerCase();
|
||||
scrollAnimations.resetScroll();
|
||||
scrollAnimations.setList(getSearchList());
|
||||
if (scrollAnimations != null) {
|
||||
scrollAnimations.resetScroll();
|
||||
scrollAnimations.setList(getSearchList());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getSearchList() {
|
||||
HashMap<String, Integer> sourceData = showingBuiltIn ? builtInData : customData;
|
||||
|
||||
if (search.isEmpty()) {
|
||||
return new ArrayList<String>(animationData.keySet());
|
||||
return new ArrayList<>(sourceData.keySet());
|
||||
}
|
||||
List<String> list = new ArrayList<String>();
|
||||
for (String name : animationData.keySet()) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String name : sourceData.keySet()) {
|
||||
if (name.toLowerCase().contains(search))
|
||||
list.add(name);
|
||||
}
|
||||
@@ -130,11 +192,20 @@ public class GuiAnimationSelection extends SubGuiInterface implements ICustomScr
|
||||
|
||||
@Override
|
||||
public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) {
|
||||
if (guiCustomScroll.id == 0) {
|
||||
String selected = scrollAnimations.getSelected();
|
||||
if (selected != null && animationData.containsKey(selected)) {
|
||||
selectedName = selected;
|
||||
selectedAnimationId = animationData.get(selected);
|
||||
String selected = scrollAnimations.getSelected();
|
||||
if (selected == null) return;
|
||||
|
||||
if (showingBuiltIn) {
|
||||
if (builtInData.containsKey(selected)) {
|
||||
selectedBuiltInName = selected;
|
||||
selectedAnimationId = -1;
|
||||
selectedCustomName = null;
|
||||
}
|
||||
} else {
|
||||
if (customData.containsKey(selected)) {
|
||||
selectedCustomName = selected;
|
||||
selectedAnimationId = customData.get(selected);
|
||||
selectedBuiltInName = "";
|
||||
}
|
||||
}
|
||||
initGui();
|
||||
@@ -142,27 +213,40 @@ public class GuiAnimationSelection extends SubGuiInterface implements ICustomScr
|
||||
|
||||
@Override
|
||||
public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) {
|
||||
if (selectedAnimationId < 0)
|
||||
return;
|
||||
close();
|
||||
// Double-click to confirm selection
|
||||
if (!selectedBuiltInName.isEmpty() || selectedAnimationId >= 0) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton guibutton) {
|
||||
int id = guibutton.id;
|
||||
if (id == 1) {
|
||||
// Clear selection
|
||||
selectedName = null;
|
||||
|
||||
// Toggle view button
|
||||
if (id == 10) {
|
||||
showingBuiltIn = !showingBuiltIn;
|
||||
search = "";
|
||||
if (scrollAnimations != null) {
|
||||
scrollAnimations.resetScroll();
|
||||
}
|
||||
initGui();
|
||||
}
|
||||
// Clear
|
||||
else if (id == 1) {
|
||||
selectedBuiltInName = "";
|
||||
selectedAnimationId = -1;
|
||||
selectedCustomName = null;
|
||||
close();
|
||||
}
|
||||
if (id == 2) {
|
||||
// Done - keep selection
|
||||
// Done
|
||||
else if (id == 2) {
|
||||
close();
|
||||
}
|
||||
if (id == 3) {
|
||||
// Cancel - restore initial value
|
||||
// Cancel
|
||||
else if (id == 3) {
|
||||
selectedAnimationId = initialAnimationId;
|
||||
selectedBuiltInName = initialAnimationName;
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -171,6 +255,16 @@ public class GuiAnimationSelection extends SubGuiInterface implements ICustomScr
|
||||
* Get the name of the selected animation (for display purposes).
|
||||
*/
|
||||
public String getSelectedName() {
|
||||
return selectedName;
|
||||
if (!selectedBuiltInName.isEmpty()) {
|
||||
return selectedBuiltInName;
|
||||
}
|
||||
return selectedCustomName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a built-in animation was selected.
|
||||
*/
|
||||
public boolean isBuiltInSelected() {
|
||||
return !selectedBuiltInName.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,7 @@ public enum EnumScrollData {
|
||||
MAGIC_CYCLES,
|
||||
DIALOG_GROUP,
|
||||
QUEST_GROUP,
|
||||
ABILITY_TYPES
|
||||
ABILITY_TYPES,
|
||||
ANIMATIONS,
|
||||
BUILTIN_ANIMATIONS
|
||||
}
|
||||
@@ -8,23 +8,43 @@ import noppes.npcs.LogWriter;
|
||||
import noppes.npcs.api.handler.IAnimationHandler;
|
||||
import noppes.npcs.api.handler.data.IAnimation;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.controllers.data.BuiltInAnimation;
|
||||
import noppes.npcs.util.NBTJsonUtil;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class AnimationController implements IAnimationHandler {
|
||||
public HashMap<Integer, Animation> animations;
|
||||
private HashMap<Integer, String> bootOrder;
|
||||
|
||||
// Built-in animations loaded from assets folder (read-only, keyed by name)
|
||||
public HashMap<String, BuiltInAnimation> builtInAnimations = new HashMap<>();
|
||||
private static final String BUILTIN_ANIMATIONS_PATH = "/assets/customnpcs/animations";
|
||||
private static final String BUILTIN_ANIMATIONS_RESOURCE = "assets/customnpcs/animations";
|
||||
|
||||
public static AnimationController Instance;
|
||||
private int lastUsedID = 0;
|
||||
|
||||
@@ -42,12 +62,94 @@ public class AnimationController implements IAnimationHandler {
|
||||
public void load() {
|
||||
bootOrder = new HashMap<>();
|
||||
animations = new HashMap<>();
|
||||
builtInAnimations = new HashMap<>();
|
||||
LogWriter.info("Loading animations...");
|
||||
loadBuiltInAnimations();
|
||||
readAnimationMap();
|
||||
loadAnimations();
|
||||
LogWriter.info("Done loading animations.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load built-in animations from assets/customnpcs/animations/ folder.
|
||||
* These animations are read-only and accessed by name only (no IDs).
|
||||
* Scans the folder directly instead of using a manifest.
|
||||
*/
|
||||
private void loadBuiltInAnimations() {
|
||||
try {
|
||||
URL resourceUrl = CustomNpcs.class.getResource(BUILTIN_ANIMATIONS_PATH);
|
||||
if (resourceUrl == null) {
|
||||
LogWriter.info("Built-in animations folder not found: " + BUILTIN_ANIMATIONS_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
URI uri = resourceUrl.toURI();
|
||||
Path animationsPath;
|
||||
|
||||
if (uri.getScheme().equals("jar")) {
|
||||
// Running from JAR - need to create a FileSystem to access it
|
||||
FileSystem fileSystem = null;
|
||||
try {
|
||||
fileSystem = FileSystems.getFileSystem(uri);
|
||||
} catch (Exception e) {
|
||||
fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
|
||||
}
|
||||
animationsPath = fileSystem.getPath(BUILTIN_ANIMATIONS_PATH);
|
||||
} else {
|
||||
// Running from file system (dev environment)
|
||||
animationsPath = Paths.get(uri);
|
||||
}
|
||||
|
||||
// Scan the directory for .json files
|
||||
try (Stream<Path> paths = Files.walk(animationsPath, 1)) {
|
||||
paths.filter(path -> path.toString().endsWith(".json"))
|
||||
.forEach(path -> {
|
||||
String fileName = path.getFileName().toString();
|
||||
String animName = fileName.substring(0, fileName.length() - 5); // Remove .json
|
||||
try {
|
||||
loadBuiltInAnimation(animName);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading built-in animation: " + animName, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LogWriter.info("Loaded " + builtInAnimations.size() + " built-in animations.");
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error scanning built-in animations folder", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single built-in animation by name from assets folder.
|
||||
*/
|
||||
private void loadBuiltInAnimation(String animName) throws Exception {
|
||||
String resourcePath = BUILTIN_ANIMATIONS_PATH + "/" + animName + ".json";
|
||||
try (InputStream stream = CustomNpcs.class.getResourceAsStream(resourcePath)) {
|
||||
if (stream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read JSON content
|
||||
StringBuilder content = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
content.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
// Parse NBT from JSON
|
||||
NBTTagCompound nbt = NBTJsonUtil.Convert(content.toString());
|
||||
|
||||
BuiltInAnimation animation = new BuiltInAnimation(animName);
|
||||
animation.readFromNBT(nbt);
|
||||
|
||||
// Store with lowercase key for case-insensitive lookup
|
||||
builtInAnimations.put(animName.toLowerCase(), animation);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAnimations() {
|
||||
animations.clear();
|
||||
|
||||
@@ -110,6 +212,12 @@ public class AnimationController implements IAnimationHandler {
|
||||
}
|
||||
|
||||
public IAnimation saveAnimation(IAnimation animation) {
|
||||
// Reject saving built-in animations
|
||||
if (animation instanceof BuiltInAnimation) {
|
||||
LogWriter.info("Cannot save built-in animation: " + animation.getName());
|
||||
return animation;
|
||||
}
|
||||
|
||||
if (animation.getID() < 0) {
|
||||
animation.setID(getUnusedId());
|
||||
while (hasName(animation.getName()))
|
||||
@@ -157,6 +265,12 @@ public class AnimationController implements IAnimationHandler {
|
||||
public void delete(String name) {
|
||||
Animation delete = getAnimationFromName(name);
|
||||
if (delete != null) {
|
||||
// Reject deleting built-in animations
|
||||
if (delete instanceof BuiltInAnimation) {
|
||||
LogWriter.info("Cannot delete built-in animation: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
Animation foundAnimation = this.animations.remove(delete.getID());
|
||||
if (foundAnimation != null && foundAnimation.name != null) {
|
||||
File dir = this.getDir();
|
||||
@@ -175,7 +289,8 @@ public class AnimationController implements IAnimationHandler {
|
||||
}
|
||||
|
||||
public void delete(int id) {
|
||||
if (!this.animations.containsKey(id))
|
||||
// Only user animations have valid IDs (>= 0)
|
||||
if (id < 0 || !this.animations.containsKey(id))
|
||||
return;
|
||||
|
||||
Animation foundAnimation = this.animations.remove(id);
|
||||
@@ -199,20 +314,83 @@ public class AnimationController implements IAnimationHandler {
|
||||
}
|
||||
|
||||
public IAnimation get(String name) {
|
||||
// Check built-in animations first (by name)
|
||||
BuiltInAnimation builtIn = builtInAnimations.get(name.toLowerCase());
|
||||
if (builtIn != null) {
|
||||
return builtIn;
|
||||
}
|
||||
// Then check user animations
|
||||
return getAnimationFromName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user animation by ID.
|
||||
* Built-in animations have no IDs and cannot be retrieved this way.
|
||||
*/
|
||||
public IAnimation get(int id) {
|
||||
// Only user animations have valid IDs
|
||||
if (id < 0) {
|
||||
return null;
|
||||
}
|
||||
return this.animations.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a built-in animation by name.
|
||||
* @param name The animation name (case-insensitive)
|
||||
* @return The built-in animation, or null if not found
|
||||
*/
|
||||
public BuiltInAnimation getBuiltInAnimation(String name) {
|
||||
return builtInAnimations.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public IAnimation[] getAnimations() {
|
||||
ArrayList<IAnimation> animations = new ArrayList<>(this.animations.values());
|
||||
return animations.toArray(new IAnimation[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all built-in animations.
|
||||
*/
|
||||
public IAnimation[] getBuiltInAnimations() {
|
||||
return builtInAnimations.values().toArray(new IAnimation[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all animations (both built-in and user-created).
|
||||
*/
|
||||
public IAnimation[] getAllAnimations() {
|
||||
ArrayList<IAnimation> all = new ArrayList<>();
|
||||
all.addAll(builtInAnimations.values());
|
||||
all.addAll(animations.values());
|
||||
return all.toArray(new IAnimation[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an animation name is a built-in animation.
|
||||
* @param name The animation name to check
|
||||
* @return true if this is a built-in animation
|
||||
*/
|
||||
public boolean isBuiltIn(String name) {
|
||||
return builtInAnimations.containsKey(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Built-in animations no longer use IDs. Use isBuiltIn(String name) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isBuiltIn(int id) {
|
||||
return false; // Built-in animations have no IDs
|
||||
}
|
||||
|
||||
public Animation getAnimationFromName(String animation) {
|
||||
for (Map.Entry<Integer, Animation> entryAnimation : AnimationController.getInstance().animations.entrySet()) {
|
||||
// Check built-in animations first (by name)
|
||||
BuiltInAnimation builtIn = builtInAnimations.get(animation.toLowerCase());
|
||||
if (builtIn != null) {
|
||||
return builtIn;
|
||||
}
|
||||
// Then check user animations
|
||||
for (Map.Entry<Integer, Animation> entryAnimation : animations.entrySet()) {
|
||||
if (entryAnimation.getValue().name.equalsIgnoreCase(animation)) {
|
||||
return entryAnimation.getValue();
|
||||
}
|
||||
@@ -220,6 +398,13 @@ public class AnimationController implements IAnimationHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get names of all built-in animations.
|
||||
*/
|
||||
public String[] getBuiltInAnimationNames() {
|
||||
return builtInAnimations.keySet().toArray(new String[0]);
|
||||
}
|
||||
|
||||
public String[] getNames() {
|
||||
String[] names = new String[animations.size()];
|
||||
int i = 0;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package noppes.npcs.controllers.data;
|
||||
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.api.handler.data.IAnimation;
|
||||
|
||||
/**
|
||||
* A read-only animation loaded from mod assets.
|
||||
* Built-in animations are identified by name only, never by ID.
|
||||
* They cannot be saved, deleted, or modified.
|
||||
*/
|
||||
public class BuiltInAnimation extends Animation {
|
||||
|
||||
public BuiltInAnimation() {
|
||||
super();
|
||||
this.id = -1; // No valid ID for built-in animations
|
||||
}
|
||||
|
||||
public BuiltInAnimation(String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.id = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in animations cannot be saved.
|
||||
* @return this animation unchanged
|
||||
*/
|
||||
@Override
|
||||
public IAnimation save() {
|
||||
// Do nothing - built-in animations are read-only
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in animations have no valid ID.
|
||||
* @return always -1
|
||||
*/
|
||||
@Override
|
||||
public int getID() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in animations cannot have their ID changed.
|
||||
*/
|
||||
@Override
|
||||
public void setID(int newID) {
|
||||
// Ignore - built-in animations don't use IDs
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a built-in animation.
|
||||
* @return always true for BuiltInAnimation
|
||||
*/
|
||||
public boolean isBuiltIn() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read animation data from NBT.
|
||||
* Preserves the name set in constructor (from filename) rather than reading from NBT.
|
||||
*/
|
||||
@Override
|
||||
public void readFromNBT(NBTTagCompound compound) {
|
||||
// Preserve the name set in constructor - don't read from NBT
|
||||
// (filename is the source of truth for built-in animations)
|
||||
String preservedName = this.name;
|
||||
|
||||
speed = compound.getFloat("Speed");
|
||||
smooth = compound.getByte("Smooth");
|
||||
loop = compound.getInteger("Loop");
|
||||
|
||||
frames.clear();
|
||||
net.minecraft.nbt.NBTTagList list = compound.getTagList("Frames", 10);
|
||||
for (int i = 0; i < list.tagCount(); i++) {
|
||||
NBTTagCompound item = list.getCompoundTagAt(i);
|
||||
Frame frame = new Frame();
|
||||
frame.parent = this;
|
||||
frame.readFromNBT(item);
|
||||
frames.add(frame);
|
||||
}
|
||||
|
||||
this.whileStanding = compound.getBoolean("WhileStanding");
|
||||
this.whileMoving = compound.getBoolean("WhileWalking");
|
||||
this.whileAttacking = compound.getBoolean("WhileAttacking");
|
||||
|
||||
this.currentFrame = compound.getInteger("CurrentFrame");
|
||||
this.currentFrameTime = compound.getInteger("CurrentFrameTime");
|
||||
|
||||
// Ensure name and ID are preserved
|
||||
this.name = preservedName;
|
||||
this.id = -1;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user