Merge branch 'dev' into syntax-highlighter-unified

This commit is contained in:
bigguy345 committed 2026-02-04 22:50:04 +02:00
commit b63d76b6c0
71 files changed
+3781 -400

No files matched your search

@@ -13,6 +13,7 @@ import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import noppes.npcs.controllers.ServerCloneController;
import noppes.npcs.controllers.data.CloneFolder;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.List;
@@ -35,20 +36,24 @@ public class CloneCommand extends CommandKamkeelBase {
@SubCommand(
desc = "Add NPC(s) to clone storage",
usage = "<npc> <tab> [clonedname]",
usage = "<npc> <tab|folder> [clonedname]",
permission = 4
)
public void add(ICommandSender sender, String[] args) {
int tab = 0;
int tab = -1;
String folder = null;
try {
tab = Integer.parseInt(args[1]);
if (tab < 0 || tab > 15) {
if (tab < 1 || tab > 15) {
sendError(sender, "Tab must be within 1-15");
return;
}
} catch (NumberFormatException ex) {
sendError(sender, String.format("Tab is not a number: %s", args[1]));
return;
folder = args[1];
if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) {
sendError(sender, String.format("Unknown folder: %s", folder));
return;
}
}
int x = sender.getPlayerCoordinates().posX;
@@ -64,32 +69,47 @@ public class CloneCommand extends CommandKamkeelBase {
NBTTagCompound compound = new NBTTagCompound();
if (!npc.writeToNBTOptional(compound))
return;
ServerCloneController.Instance.addClone(compound, name, tab);
sendResult(sender, String.format("Added NPC \u00A7e%s\u00A77 to Tab \u00A7b%d\u00A77", name, tab));
if (folder != null) {
ServerCloneController.Instance.addClone(compound, name, folder);
sendResult(sender, String.format("Added NPC \u00A7e%s\u00A77 to Folder \u00A7b%s\u00A77", name, folder));
} else {
ServerCloneController.Instance.addClone(compound, name, tab);
sendResult(sender, String.format("Added NPC \u00A7e%s\u00A77 to Tab \u00A7b%d\u00A77", name, tab));
}
}
}
}
@SubCommand(
desc = "List NPC from clone storage",
usage = "<tab>",
usage = "<tab|folder>",
permission = 2
)
public void list(ICommandSender sender, String[] args) {
sendMessage(sender, "--- Stored NPCs --- (server side)");
int tab = 0;
int tab = -1;
String folder = null;
try {
tab = Integer.parseInt(args[1]);
if (tab < 0 || tab > 15) {
if (tab < 1 || tab > 15) {
sendError(sender, "Tab must be within 1-15");
return;
}
} catch (NumberFormatException ex) {
sendError(sender, String.format("Tab is not a number: %s", args[1]));
return;
folder = args[1];
if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) {
sendError(sender, String.format("Unknown folder: %s", folder));
return;
}
}
for (String name : ServerCloneController.Instance.getClones(tab)) {
List<String> clones;
if (folder != null) {
clones = ServerCloneController.Instance.getClones(folder);
} else {
clones = ServerCloneController.Instance.getClones(tab);
}
for (String name : clones) {
sendMessage(sender, name);
}
sendMessage(sender, "------------------------------------");
@@ -97,59 +117,75 @@ public class CloneCommand extends CommandKamkeelBase {
@SubCommand(
desc = "Remove NPC from clone storage",
usage = "<name> <tab>",
usage = "<name> <tab|folder>",
permission = 4
)
public void del(ICommandSender sender, String[] args) throws CommandException {
String nametodel = args[0];
int tab = 0;
int tab = -1;
String folder = null;
try {
tab = Integer.parseInt(args[1]);
if (tab < 0 || tab > 15) {
sendError(sender, String.format("Tab must be within 1-15"));
if (tab < 1 || tab > 15) {
sendError(sender, "Tab must be within 1-15");
return;
}
} catch (NumberFormatException ex) {
sendError(sender, String.format("Tab is not a number: %s", args[1]));
return;
}
boolean deleted = false;
for (String name : ServerCloneController.Instance.getClones(tab)) {
if (nametodel.equalsIgnoreCase(name)) {
ServerCloneController.Instance.removeClone(name, tab);
deleted = true;
break;
folder = args[1];
if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) {
sendError(sender, String.format("Unknown folder: %s", folder));
return;
}
}
if (!ServerCloneController.Instance.removeClone(nametodel, tab)) {
boolean success;
if (folder != null) {
success = ServerCloneController.Instance.removeClone(nametodel, folder);
} else {
success = ServerCloneController.Instance.removeClone(nametodel, tab);
}
if (!success) {
sendError(sender, String.format("NPC '%s' was not found", nametodel));
} else {
sendResult(sender, String.format("Removed NPC \u00A7e%s\u00A77 from Tab \u00A7b%d\u00A77", nametodel, tab));
if (folder != null) {
sendResult(sender, String.format("Removed NPC \u00A7e%s\u00A77 from Folder \u00A7b%s\u00A77", nametodel, folder));
} else {
sendResult(sender, String.format("Removed NPC \u00A7e%s\u00A77 from Tab \u00A7b%d\u00A77", nametodel, tab));
}
}
}
@SubCommand(
desc = "Spawn cloned NPC",
usage = "<name> <tab> [[world:]x,y,z]] [newname]",
usage = "<name> <tab|folder> [[world:]x,y,z]] [newname]",
permission = 2
)
public void spawn(ICommandSender sender, String[] args) throws CommandException {
String name = args[0].replaceAll("%", " "); // if name of npc separed by space, user must use % in place of space
int tab = 0;
int tab = -1;
String folder = null;
try {
tab = Integer.parseInt(args[1]);
if (tab < 0 || tab > 15) {
sendError(sender, String.format("Tab must be within 1-15"));
if (tab < 1 || tab > 15) {
sendError(sender, "Tab must be within 1-15");
return;
}
} catch (NumberFormatException ex) {
sendError(sender, String.format("Tab is not a number: %s", args[1]));
return;
folder = args[1];
if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) {
sendError(sender, String.format("Unknown folder: %s", folder));
return;
}
}
String newname = null;
NBTTagCompound compound = ServerCloneController.Instance.getCloneData(sender, name, tab);
NBTTagCompound compound;
if (folder != null) {
compound = ServerCloneController.Instance.getCloneData(sender, name, folder);
} else {
compound = ServerCloneController.Instance.getCloneData(sender, name, tab);
}
if (compound == null) {
sendError(sender, "Unknown npc");
return;
@@ -211,22 +247,26 @@ public class CloneCommand extends CommandKamkeelBase {
}
@SubCommand(
desc = "Spawn cloned NPC",
usage = "<name> <tab> [[world:]x,y,z]] [newname]",
desc = "Spawn cloned NPC in a grid",
usage = "<name> <tab|folder> <width> <height> [[world:]x,y,z]] [newname]",
permission = 2
)
public void grid(ICommandSender sender, String[] args) throws CommandException {
String name = args[0].replaceAll("%", " "); // if name of npc separed by space, user must use % in place of space
int tab = 0;
int tab = -1;
String folder = null;
try {
tab = Integer.parseInt(args[1]);
if (tab < 0 || tab > 15) {
sendError(sender, String.format("Tab must be within 1-15"));
if (tab < 1 || tab > 15) {
sendError(sender, "Tab must be within 1-15");
return;
}
} catch (NumberFormatException ex) {
sendError(sender, String.format("Tab is not a number: %s", args[1]));
return;
folder = args[1];
if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) {
sendError(sender, String.format("Unknown folder: %s", folder));
return;
}
}
int width, height;
@@ -238,9 +278,13 @@ public class CloneCommand extends CommandKamkeelBase {
return;
}
String newname = null;
NBTTagCompound compound = ServerCloneController.Instance.getCloneData(sender, name, tab);
NBTTagCompound compound;
if (folder != null) {
compound = ServerCloneController.Instance.getCloneData(sender, name, folder);
} else {
compound = ServerCloneController.Instance.getCloneData(sender, name, tab);
}
if (compound == null) {
sendError(sender, "Unknown npc");
return;
@@ -317,6 +361,163 @@ public class CloneCommand extends CommandKamkeelBase {
return;
}
@SubCommand(
desc = "List all custom clone folders",
usage = "",
permission = 2
)
public void listfolders(ICommandSender sender, String[] args) {
if (ServerCloneController.Instance == null) {
sendError(sender, "Folder system not initialized");
return;
}
sendMessage(sender, "--- Clone Folders ---");
for (CloneFolder f : ServerCloneController.Instance.getFolderList()) {
sendMessage(sender, f.name);
}
sendMessage(sender, "------------------------------------");
}
@SubCommand(
desc = "Create a custom clone folder",
usage = "<name>",
permission = 4
)
public void createfolder(ICommandSender sender, String[] args) {
if (ServerCloneController.Instance == null) {
sendError(sender, "Folder system not initialized");
return;
}
String name = args[0];
if (!CloneFolder.isValidName(name)) {
sendError(sender, String.format("Invalid folder name: %s", name));
return;
}
if (ServerCloneController.Instance.hasFolder(name)) {
sendError(sender, String.format("Folder '%s' already exists", name));
return;
}
CloneFolder created = ServerCloneController.Instance.createFolder(name);
if (created != null) {
sendResult(sender, String.format("Created folder \u00A7b%s", name));
} else {
sendError(sender, String.format("Failed to create folder '%s'", name));
}
}
@SubCommand(
desc = "Rename a custom clone folder",
usage = "<oldname> <newname>",
permission = 4
)
public void renamefolder(ICommandSender sender, String[] args) {
if (ServerCloneController.Instance == null) {
sendError(sender, "Folder system not initialized");
return;
}
String oldName = args[0];
String newName = args[1];
if (!ServerCloneController.Instance.hasFolder(oldName)) {
sendError(sender, String.format("Unknown folder: %s", oldName));
return;
}
if (!CloneFolder.isValidName(newName)) {
sendError(sender, String.format("Invalid folder name: %s", newName));
return;
}
if (ServerCloneController.Instance.renameFolder(oldName, newName)) {
sendResult(sender, String.format("Renamed folder \u00A7b%s\u00A77 to \u00A7b%s", oldName, newName));
} else {
sendError(sender, String.format("Failed to rename folder '%s'", oldName));
}
}
@SubCommand(
desc = "Delete an empty custom clone folder",
usage = "<name>",
permission = 4
)
public void deletefolder(ICommandSender sender, String[] args) {
if (ServerCloneController.Instance == null) {
sendError(sender, "Folder system not initialized");
return;
}
String name = args[0];
if (!ServerCloneController.Instance.hasFolder(name)) {
sendError(sender, String.format("Unknown folder: %s", name));
return;
}
if (ServerCloneController.Instance.deleteFolder(name)) {
sendResult(sender, String.format("Deleted folder \u00A7b%s", name));
} else {
sendError(sender, String.format("Failed to delete folder '%s' (folder must be empty)", name));
}
}
@SubCommand(
desc = "Move a clone between tabs/folders",
usage = "<clonename> <from_tab|folder> <to_tab|folder>",
permission = 4
)
public void move(ICommandSender sender, String[] args) {
if (ServerCloneController.Instance == null) {
sendError(sender, "Folder system not initialized");
return;
}
String cloneName = args[0];
int fromTab = -1;
String fromFolder = null;
try {
fromTab = Integer.parseInt(args[1]);
if (fromTab < 1 || fromTab > 15) {
sendError(sender, "Source tab must be within 1-15");
return;
}
} catch (NumberFormatException ex) {
fromFolder = args[1];
if (!ServerCloneController.Instance.hasFolder(fromFolder)) {
sendError(sender, String.format("Unknown source folder: %s", fromFolder));
return;
}
}
int toTab = -1;
String toFolder = null;
try {
toTab = Integer.parseInt(args[2]);
if (toTab < 1 || toTab > 15) {
sendError(sender, "Destination tab must be within 1-15");
return;
}
} catch (NumberFormatException ex) {
toFolder = args[2];
if (!ServerCloneController.Instance.hasFolder(toFolder)) {
sendError(sender, String.format("Unknown destination folder: %s", toFolder));
return;
}
}
boolean success;
if (fromFolder != null && toFolder != null) {
success = ServerCloneController.Instance.moveClone(cloneName, fromFolder, toFolder);
} else if (fromFolder != null) {
success = ServerCloneController.Instance.moveClone(cloneName, fromFolder, toTab);
} else if (toFolder != null) {
success = ServerCloneController.Instance.moveClone(cloneName, fromTab, toFolder);
} else {
success = ServerCloneController.Instance.moveClone(cloneName, fromTab, toTab);
}
if (success) {
String from = fromFolder != null ? fromFolder : String.valueOf(fromTab);
String to = toFolder != null ? toFolder : String.valueOf(toTab);
sendResult(sender, String.format("Moved \u00A7e%s\u00A77 from \u00A7b%s\u00A77 to \u00A7b%s", cloneName, from, to));
} else {
sendError(sender, String.format("Failed to move '%s'", cloneName));
}
}
public World getWorld(String t) {
WorldServer[] ws = MinecraftServer.getServer().worldServers;
for (WorldServer w : ws) {
@@ -67,6 +67,7 @@ public abstract class Ability implements IAbility {
// Feedback
protected LockMovementType lockMovement = LockMovementType.WINDUP;
protected LockType lockType = LockType.BOTH;
protected int windUpColor = 0x80FF4400; // Telegraph color during wind up
protected int activeColor = 0xC0FF0000; // Telegraph warning/active color
@@ -390,15 +391,27 @@ public abstract class Ability implements IAbility {
defs.add(FieldDef.intField("ability.cooldownTicks", this::getCooldownTicks, this::setCooldownTicks)
.tab("General").range(0, 10000));
defs.add(FieldDef.section("ability.section.movement").tab("General"));
defs.add(FieldDef.stringEnumField("ability.lockMovement", LockMovementType.getDisplayKeys(),
() -> this.getLockMovement().getDisplayKey(),
v -> {
String[] keys = LockMovementType.getDisplayKeys();
for (int i = 0; i < keys.length; i++) {
if (keys[i].equals(v)) { this.setLockMovement(LockMovementType.fromOrdinal(i)); break; }
}
})
.tab("General").hover("ability.hover.lockMove"));
defs.add(FieldDef.row(
FieldDef.stringEnumField("ability.lockPosition", LockMovementType.getDisplayKeys(),
() -> this.getLockMovement().getDisplayKey(),
v -> {
String[] keys = LockMovementType.getDisplayKeys();
for (int i = 0; i < keys.length; i++) {
if (keys[i].equals(v)) { this.setLockMovement(LockMovementType.fromOrdinal(i)); break; }
}
})
.hover("ability.hover.lockPosition"),
FieldDef.stringEnumField("ability.lockType", LockType.getDisplayKeys(),
() -> this.getLockType().getDisplayKey(),
v -> {
String[] keys = LockType.getDisplayKeys();
for (int i = 0; i < keys.length; i++) {
if (keys[i].equals(v)) { this.setLockType(LockType.fromOrdinal(i)); break; }
}
})
.hover("ability.hover.lockType")
.visibleWhen(() -> this.lockMovement != LockMovementType.NO)
).tab("General"));
defs.add(FieldDef.row(
FieldDef.boolField("ability.interruptible", this::isInterruptible, this::setInterruptible)
.hover("ability.hover.interruptible"),
@@ -940,6 +953,7 @@ public abstract class Ability implements IAbility {
nbt.setInteger("recovery", dazedTicks);
nbt.setBoolean("interruptible", interruptible);
nbt.setInteger("lockMovement", lockMovement.ordinal());
nbt.setInteger("lockType", lockType.ordinal());
nbt.setInteger("windUpColor", windUpColor);
nbt.setInteger("activeColor", activeColor);
nbt.setString("windUpSound", windUpSound);
@@ -990,6 +1004,7 @@ public abstract class Ability implements IAbility {
dazedTicks = nbt.getInteger("recovery");
interruptible = nbt.getBoolean("interruptible");
lockMovement = LockMovementType.fromOrdinal(nbt.getInteger("lockMovement"));
lockType = nbt.hasKey("lockType") ? LockType.fromOrdinal(nbt.getInteger("lockType")) : LockType.BOTH;
windUpColor = nbt.getInteger("windUpColor");
activeColor = nbt.getInteger("activeColor");
windUpSound = nbt.getString("windUpSound");
@@ -1154,6 +1169,14 @@ public abstract class Ability implements IAbility {
this.lockMovement = lockMovement;
}
public LockType getLockType() {
return lockType;
}
public void setLockType(LockType lockType) {
this.lockType = lockType;
}
/**
* API method: Get lock movement type as integer.
* @return 0=NO, 1=WINDUP, 2=ACTIVE, 3=WINDUP_AND_ACTIVE
@@ -1173,19 +1196,53 @@ public abstract class Ability implements IAbility {
}
/**
* Check if movement should be locked during WINDUP phase.
* API method: Get lock type as integer.
* @return 0=MOVEMENT, 1=ROTATION, 2=BOTH
*/
@Override
public boolean isMovementLockedDuringWindup() {
return lockMovement.locksWindup();
public int getLockTypeOrdinal() {
return lockType.ordinal();
}
/**
* Check if movement should be locked during ACTIVE phase.
* API method: Set lock type from integer.
* @param type 0=MOVEMENT, 1=ROTATION, 2=BOTH
*/
@Override
public void setLockTypeOrdinal(int type) {
this.lockType = LockType.fromOrdinal(type);
}
/**
* Check if movement (pathfinding/motion) should be locked during WINDUP phase.
*/
@Override
public boolean isMovementLockedDuringWindup() {
return lockMovement.locksWindup() && lockType.locksMovement();
}
/**
* Check if movement (pathfinding/motion) should be locked during ACTIVE phase.
*/
@Override
public boolean isMovementLockedDuringActive() {
return lockMovement.locksActive();
return lockMovement.locksActive() && lockType.locksMovement();
}
/**
* Check if rotation (yaw/pitch) should be locked during WINDUP phase.
*/
@Override
public boolean isRotationLockedDuringWindup() {
return lockMovement.locksWindup() && lockType.locksRotation();
}
/**
* Check if rotation (yaw/pitch) should be locked during ACTIVE phase.
*/
@Override
public boolean isRotationLockedDuringActive() {
return lockMovement.locksActive() && lockType.locksRotation();
}
/**
@@ -1194,9 +1251,23 @@ public abstract class Ability implements IAbility {
public boolean isMovementLockedForCurrentPhase() {
switch (phase) {
case WINDUP:
return lockMovement.locksWindup();
return isMovementLockedDuringWindup();
case ACTIVE:
return lockMovement.locksActive();
return isMovementLockedDuringActive();
default:
return false;
}
}
/**
* Check if rotation should be locked during the current phase.
*/
public boolean isRotationLockedForCurrentPhase() {
switch (phase) {
case WINDUP:
return isRotationLockedDuringWindup();
case ACTIVE:
return isRotationLockedDuringActive();
default:
return false;
}
@@ -0,0 +1,47 @@
package kamkeel.npcs.controllers.data.ability;
/**
* Defines what an ability locks when Lock Position is enabled.
*/
public enum LockType {
MOVEMENT,
ROTATION,
BOTH;
public boolean locksMovement() {
return this == MOVEMENT || this == BOTH;
}
public boolean locksRotation() {
return this == ROTATION || this == BOTH;
}
public String getDisplayKey() {
switch (this) {
case MOVEMENT:
return "ability.lockType.movement";
case ROTATION:
return "ability.lockType.rotation";
case BOTH:
return "ability.lockType.both";
default:
return "ability.lockType.both";
}
}
public static String[] getDisplayKeys() {
return new String[]{
"ability.lockType.movement",
"ability.lockType.rotation",
"ability.lockType.both"
};
}
public static LockType fromOrdinal(int ordinal) {
LockType[] values = values();
if (ordinal >= 0 && ordinal < values.length) {
return values[ordinal];
}
return BOTH;
}
}
@@ -126,6 +126,11 @@ public class AbilityBeam extends Ability implements IAbilityEnergyBeam {
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
}
@Override
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
cleanup();
}
@Override
public void cleanup() {
// Despawn beam entity if still alive
@@ -167,6 +167,11 @@ public class AbilityBeamDual extends Ability {
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
}
@Override
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
cleanup();
}
@Override
public void cleanup() {
// Despawn beam entity if still alive
@@ -127,6 +127,11 @@ public class AbilityDisc extends Ability implements IAbilityDisc {
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
}
@Override
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
cleanup();
}
@Override
public void cleanup() {
// Despawn disc entity if still alive
@@ -176,6 +176,11 @@ public class AbilityDiscDual extends Ability {
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
}
@Override
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
cleanup();
}
@Override
public void cleanup() {
// Despawn disc entity if still alive
@@ -136,6 +136,7 @@ public class AbilityLaserShot extends Ability implements IAbilityLaserShot {
@Override
public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) {
cleanup();
}
@Override
@@ -122,6 +122,11 @@ public class AbilityOrb extends Ability implements IAbilityOrb {
// Nothing to clean up - entity manages itself
}
@Override
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
cleanup();
}
@Override
public void cleanup() {
// Despawn orb entity if still alive
@@ -211,6 +211,11 @@ public class AbilityOrbBarrage extends Ability {
return true;
}
@Override
public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) {
cleanup();
}
@Override
public void cleanup() {
currentOrb = 0;
@@ -161,7 +161,11 @@ public class AbilityOrbDual extends Ability {
@Override
public void onComplete(EntityLivingBase caster, EntityLivingBase target) {
// Nothing to clean up - entity manages itself
}
@Override
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
cleanup();
}
@Override
@@ -217,9 +217,6 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
double dragPowN = Math.pow(drag, flightTicks);
double vHorizontal = horizontalDist * (1.0 - drag) / (1.0 - dragPowN);
// Speed scales horizontal velocity to cover ground faster
vHorizontal *= speedFactor;
// Adjust vertical velocity for height difference between start and target
if (dy > 0) {
vy += dy / flightTicks * 1.5; // Going up - need more velocity
@@ -438,7 +435,7 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
int flightTicks = (int) Math.max(10, Math.min(horizontalDist * 1.5 / speedFactor, 40));
double drag = 0.91;
double dragPowN = Math.pow(drag, flightTicks);
double vHorizontal = horizontalDist * (1.0 - drag) / (1.0 - dragPowN) * speedFactor;
double vHorizontal = horizontalDist * (1.0 - drag) / (1.0 - dragPowN);
double dirX = dx / horizontalDist;
double dirZ = dz / horizontalDist;
@@ -100,6 +100,11 @@ public class AbilitySweeper extends Ability implements IAbilitySweeper {
}
}
@Override
public void onInterrupt(EntityLivingBase caster, net.minecraft.util.DamageSource source, float damage) {
cleanup();
}
@Override
public void cleanup() {
// Clean up entity if still alive
@@ -205,10 +205,19 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
startY = posY;
startZ = posZ;
// Calculate velocity toward target
// Always derive direction from the owner's current yaw/pitch.
// This ensures the laser fires in the direction the NPC is visually facing,
// which matches the telegraph line direction regardless of Lock Movement mode.
Entity owner = getOwnerEntity();
if (target != null) {
if (owner != null) {
float yaw = (float) Math.toRadians(owner.rotationYaw);
float pitch = (float) Math.toRadians(owner.rotationPitch);
this.dirX = -Math.sin(yaw) * Math.cos(pitch);
this.dirY = -Math.sin(pitch);
this.dirZ = Math.cos(yaw) * Math.cos(pitch);
} else if (target != null) {
// Fallback: aim at target if owner is somehow unavailable
double dx = target.posX - startX;
double dy = (target.posY + target.getEyeHeight() - 0.4) - startY;
double dz = target.posZ - startZ;
@@ -218,13 +227,6 @@ public class EntityAbilityLaser extends EntityAbilityProjectile {
this.dirY = dy / len;
this.dirZ = dz / len;
}
} else if (owner != null) {
// Fire in NPC's facing direction
float yaw = (float) Math.toRadians(owner.rotationYaw);
float pitch = (float) Math.toRadians(owner.rotationPitch);
this.dirX = -Math.sin(yaw) * Math.cos(pitch);
this.dirY = -Math.sin(pitch);
this.dirZ = Math.cos(yaw) * Math.cos(pitch);;
}
setPosition(endX, endY, endZ);
@@ -108,6 +108,9 @@ import kamkeel.npcs.network.packets.request.bank.BankSavePacket;
import kamkeel.npcs.network.packets.request.bank.BanksGetPacket;
import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket;
import kamkeel.npcs.network.packets.request.clone.CloneAllTagsShortPacket;
import kamkeel.npcs.network.packets.request.clone.CloneFolderCrudPacket;
import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket;
import kamkeel.npcs.network.packets.request.clone.CloneMovePacket;
import kamkeel.npcs.network.packets.request.clone.CloneListPacket;
import kamkeel.npcs.network.packets.request.clone.ClonePreSavePacket;
import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket;
@@ -325,6 +328,9 @@ public class PacketHandler {
REQUEST_PACKET.registerPacket(new CloneTagListPacket());
REQUEST_PACKET.registerPacket(new CloneAllTagsPacket());
REQUEST_PACKET.registerPacket(new CloneAllTagsShortPacket());
REQUEST_PACKET.registerPacket(new CloneFolderListPacket());
REQUEST_PACKET.registerPacket(new CloneFolderCrudPacket());
REQUEST_PACKET.registerPacket(new CloneMovePacket());
// Linked Packets
@@ -163,6 +163,9 @@ public enum EnumRequestPacket {
CloneAllTags,
CloneAllTagsShort,
ClonePreSave,
CloneFolderList,
CloneFolderCrud,
CloneMove,
EventScript,
PlayerScript,
@@ -0,0 +1,111 @@
package kamkeel.npcs.network.packets.request.clone;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.PacketUtil;
import kamkeel.npcs.network.enums.EnumItemPacketType;
import kamkeel.npcs.network.enums.EnumRequestPacket;
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import noppes.npcs.CustomNpcsPermissions;
import noppes.npcs.controllers.ServerCloneController;
import noppes.npcs.controllers.data.CloneFolder;
import java.io.IOException;
public final class CloneFolderCrudPacket extends AbstractPacket {
public static String packetName = "Request|CloneFolderCrud";
public static final byte ACTION_CREATE = 0;
public static final byte ACTION_RENAME = 1;
public static final byte ACTION_DELETE = 2;
private byte action;
private String name;
private String newName;
public CloneFolderCrudPacket() {
}
public CloneFolderCrudPacket(byte action, String name) {
this.action = action;
this.name = name;
this.newName = "";
}
public CloneFolderCrudPacket(byte action, String name, String newName) {
this.action = action;
this.name = name;
this.newName = newName;
}
@Override
public Enum getType() {
return EnumRequestPacket.CloneFolderCrud;
}
@Override
public PacketChannel getChannel() {
return PacketHandler.REQUEST_PACKET;
}
@Override
public CustomNpcsPermissions.Permission getPermission() {
return CustomNpcsPermissions.NPC_CLONE;
}
@SideOnly(Side.CLIENT)
@Override
public void sendData(ByteBuf out) throws IOException {
out.writeByte(action);
ByteBufUtils.writeString(out, name);
if (action == ACTION_RENAME) {
ByteBufUtils.writeString(out, newName);
}
}
@Override
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
if (!(player instanceof EntityPlayerMP))
return;
if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.CLONER))
return;
byte action = in.readByte();
String name = ByteBufUtils.readString(in);
boolean success = false;
switch (action) {
case ACTION_CREATE:
if (CloneFolder.isValidName(name))
success = ServerCloneController.Instance.createFolder(name) != null;
break;
case ACTION_RENAME:
String newName = ByteBufUtils.readString(in);
if (CloneFolder.isValidName(newName))
success = ServerCloneController.Instance.renameFolder(name, newName);
break;
case ACTION_DELETE:
success = ServerCloneController.Instance.deleteFolder(name);
break;
}
NBTTagCompound compound = new NBTTagCompound();
compound.setBoolean("Success", success);
NBTTagList folderList = new NBTTagList();
for (CloneFolder folder : ServerCloneController.Instance.getFolderList()) {
folderList.appendTag(folder.writeNBT(new NBTTagCompound()));
}
compound.setTag("CloneFolders", folderList);
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
}
}
@@ -0,0 +1,59 @@
package kamkeel.npcs.network.packets.request.clone;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.PacketUtil;
import kamkeel.npcs.network.enums.EnumItemPacketType;
import kamkeel.npcs.network.enums.EnumRequestPacket;
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import noppes.npcs.controllers.ServerCloneController;
import noppes.npcs.controllers.data.CloneFolder;
import java.io.IOException;
public final class CloneFolderListPacket extends AbstractPacket {
public static String packetName = "Request|CloneFolderList";
public CloneFolderListPacket() {
}
@Override
public Enum getType() {
return EnumRequestPacket.CloneFolderList;
}
@Override
public PacketChannel getChannel() {
return PacketHandler.REQUEST_PACKET;
}
@SideOnly(Side.CLIENT)
@Override
public void sendData(ByteBuf out) throws IOException {
}
@Override
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
if (!(player instanceof EntityPlayerMP))
return;
if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.CLONER))
return;
NBTTagCompound compound = new NBTTagCompound();
NBTTagList folderList = new NBTTagList();
for (CloneFolder folder : ServerCloneController.Instance.getFolderList()) {
folderList.appendTag(folder.writeNBT(new NBTTagCompound()));
}
compound.setTag("CloneFolders", folderList);
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
}
}
@@ -23,9 +23,16 @@ public final class CloneListPacket extends AbstractPacket {
public static String packetName = "Request|CloneList";
private int tab;
private String folderName;
public CloneListPacket(int tab) {
this.tab = tab;
this.folderName = null;
}
public CloneListPacket(String folderName) {
this.tab = -1;
this.folderName = folderName;
}
public CloneListPacket() {
@@ -45,6 +52,9 @@ public final class CloneListPacket extends AbstractPacket {
@Override
public void sendData(ByteBuf out) throws IOException {
out.writeInt(tab);
if (tab == -1) {
kamkeel.npcs.util.ByteBufUtils.writeString(out, folderName);
}
}
@Override
@@ -54,14 +64,26 @@ public final class CloneListPacket extends AbstractPacket {
if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.MOUNTER, EnumItemPacketType.CLONER))
return;
NBTTagList list = new NBTTagList();
int tab = in.readInt();
for (String name : ServerCloneController.Instance.getClones(tab))
list.appendTag(new NBTTagString(name));
String folder = null;
if (tab == -1) {
folder = kamkeel.npcs.util.ByteBufUtils.readString(in);
}
NBTTagList list = new NBTTagList();
NBTTagList listDate = new NBTTagList();
for (String name : ServerCloneController.Instance.getClonesDate(tab))
listDate.appendTag(new NBTTagString(name));
if (folder != null) {
for (String name : ServerCloneController.Instance.getClones(folder))
list.appendTag(new NBTTagString(name));
for (String name : ServerCloneController.Instance.getClonesDate(folder))
listDate.appendTag(new NBTTagString(name));
} else {
for (String name : ServerCloneController.Instance.getClones(tab))
list.appendTag(new NBTTagString(name));
for (String name : ServerCloneController.Instance.getClonesDate(tab))
listDate.appendTag(new NBTTagString(name));
}
NBTTagCompound compound = new NBTTagCompound();
compound.setTag("List", list);
@@ -0,0 +1,97 @@
package kamkeel.npcs.network.packets.request.clone;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.PacketUtil;
import kamkeel.npcs.network.enums.EnumItemPacketType;
import kamkeel.npcs.network.enums.EnumRequestPacket;
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.CustomNpcsPermissions;
import noppes.npcs.controllers.ServerCloneController;
import java.io.IOException;
public final class CloneMovePacket extends AbstractPacket {
public static String packetName = "Request|CloneMove";
private String cloneName;
private int fromTab;
private String fromFolder;
private int toTab;
private String toFolder;
public CloneMovePacket() {
}
public CloneMovePacket(String cloneName, int fromTab, String fromFolder, int toTab, String toFolder) {
this.cloneName = cloneName;
this.fromTab = fromTab;
this.fromFolder = fromFolder;
this.toTab = toTab;
this.toFolder = toFolder;
}
@Override
public Enum getType() {
return EnumRequestPacket.CloneMove;
}
@Override
public PacketChannel getChannel() {
return PacketHandler.REQUEST_PACKET;
}
@Override
public CustomNpcsPermissions.Permission getPermission() {
return CustomNpcsPermissions.NPC_CLONE;
}
@SideOnly(Side.CLIENT)
@Override
public void sendData(ByteBuf out) throws IOException {
ByteBufUtils.writeString(out, cloneName);
out.writeInt(fromTab);
ByteBufUtils.writeString(out, fromFolder != null ? fromFolder : "");
out.writeInt(toTab);
ByteBufUtils.writeString(out, toFolder != null ? toFolder : "");
}
@Override
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
if (!(player instanceof EntityPlayerMP))
return;
if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.CLONER))
return;
String cloneName = ByteBufUtils.readString(in);
int fromTab = in.readInt();
String fromFolder = ByteBufUtils.readString(in);
if (fromFolder.isEmpty()) fromFolder = null;
int toTab = in.readInt();
String toFolder = ByteBufUtils.readString(in);
if (toFolder.isEmpty()) toFolder = null;
boolean success = false;
if (fromFolder != null && toFolder != null) {
success = ServerCloneController.Instance.moveClone(cloneName, fromFolder, toFolder);
} else if (fromFolder != null) {
success = ServerCloneController.Instance.moveClone(cloneName, fromFolder, toTab);
} else if (toFolder != null) {
success = ServerCloneController.Instance.moveClone(cloneName, fromTab, toFolder);
} else {
success = ServerCloneController.Instance.moveClone(cloneName, fromTab, toTab);
}
NBTTagCompound compound = new NBTTagCompound();
compound.setBoolean("MoveSuccess", success);
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
}
}
@@ -24,6 +24,7 @@ public final class ClonePreSavePacket extends AbstractPacket {
private String name;
private int tab;
private String folderName;
public ClonePreSavePacket() {
}
@@ -31,6 +32,13 @@ public final class ClonePreSavePacket extends AbstractPacket {
public ClonePreSavePacket(String name, int tab) {
this.name = name;
this.tab = tab;
this.folderName = null;
}
public ClonePreSavePacket(String name, String folderName) {
this.name = name;
this.tab = -1;
this.folderName = folderName;
}
@Override
@@ -53,6 +61,9 @@ public final class ClonePreSavePacket extends AbstractPacket {
public void sendData(ByteBuf out) throws IOException {
ByteBufUtils.writeString(out, this.name);
out.writeInt(tab);
if (tab == -1) {
ByteBufUtils.writeString(out, this.folderName);
}
}
@Override
@@ -62,7 +73,15 @@ public final class ClonePreSavePacket extends AbstractPacket {
if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.CLONER))
return;
boolean bo = ServerCloneController.Instance.getCloneData(null, ByteBufUtils.readString(in), in.readInt()) != null;
String name = ByteBufUtils.readString(in);
int tab = in.readInt();
boolean bo;
if (tab == -1) {
String folder = ByteBufUtils.readString(in);
bo = ServerCloneController.Instance.getCloneData(null, name, folder) != null;
} else {
bo = ServerCloneController.Instance.getCloneData(null, name, tab) != null;
}
NBTTagCompound compound = new NBTTagCompound();
compound.setBoolean("NameExists", bo);
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
@@ -25,6 +25,7 @@ public final class CloneRemovePacket extends AbstractPacket {
public static String packetName = "Request|CloneRemove";
private int tab;
private String folderName;
private String name;
public CloneRemovePacket() {
@@ -32,6 +33,13 @@ public final class CloneRemovePacket extends AbstractPacket {
public CloneRemovePacket(int tab, String name) {
this.tab = tab;
this.folderName = null;
this.name = name;
}
public CloneRemovePacket(String folderName, String name) {
this.tab = -1;
this.folderName = folderName;
this.name = name;
}
@@ -54,6 +62,9 @@ public final class CloneRemovePacket extends AbstractPacket {
@Override
public void sendData(ByteBuf out) throws IOException {
out.writeInt(tab);
if (tab == -1) {
ByteBufUtils.writeString(out, this.folderName);
}
ByteBufUtils.writeString(out, this.name);
}
@@ -65,16 +76,28 @@ public final class CloneRemovePacket extends AbstractPacket {
return;
int tab = in.readInt();
ServerCloneController.Instance.removeClone(ByteBufUtils.readString(in), tab);
String folder = null;
if (tab == -1) {
folder = ByteBufUtils.readString(in);
}
String cloneName = ByteBufUtils.readString(in);
NBTTagList list = new NBTTagList();
for (String name : ServerCloneController.Instance.getClones(tab))
list.appendTag(new NBTTagString(name));
NBTTagList listDate = new NBTTagList();
for (String name : ServerCloneController.Instance.getClonesDate(tab))
listDate.appendTag(new NBTTagString(name));
if (folder != null) {
ServerCloneController.Instance.removeClone(cloneName, folder);
for (String name : ServerCloneController.Instance.getClones(folder))
list.appendTag(new NBTTagString(name));
for (String name : ServerCloneController.Instance.getClonesDate(folder))
listDate.appendTag(new NBTTagString(name));
} else {
ServerCloneController.Instance.removeClone(cloneName, tab);
for (String name : ServerCloneController.Instance.getClones(tab))
list.appendTag(new NBTTagString(name));
for (String name : ServerCloneController.Instance.getClonesDate(tab))
listDate.appendTag(new NBTTagString(name));
}
NBTTagCompound compound = new NBTTagCompound();
compound.setTag("List", list);
@@ -25,6 +25,7 @@ public final class CloneSavePacket extends AbstractPacket {
private String name;
private int tab;
private String folderName;
private NBTTagCompound tagExtra;
private NBTTagCompound tagCompound;
@@ -34,6 +35,15 @@ public final class CloneSavePacket extends AbstractPacket {
public CloneSavePacket(String name, int tab, NBTTagCompound tagExtra, NBTTagCompound tagCompound) {
this.name = name;
this.tab = tab;
this.folderName = null;
this.tagExtra = tagExtra;
this.tagCompound = tagCompound;
}
public CloneSavePacket(String name, String folderName, NBTTagCompound tagExtra, NBTTagCompound tagCompound) {
this.name = name;
this.tab = -1;
this.folderName = folderName;
this.tagExtra = tagExtra;
this.tagCompound = tagCompound;
}
@@ -58,6 +68,9 @@ public final class CloneSavePacket extends AbstractPacket {
public void sendData(ByteBuf out) throws IOException {
ByteBufUtils.writeString(out, this.name);
out.writeInt(tab);
if (tab == -1) {
ByteBufUtils.writeString(out, this.folderName);
}
ByteBufUtils.writeNBT(out, this.tagExtra);
ByteBufUtils.writeNBT(out, this.tagCompound);
}
@@ -75,11 +88,20 @@ public final class CloneSavePacket extends AbstractPacket {
String name = ByteBufUtils.readString(in);
int tab = in.readInt();
String folder = null;
if (tab == -1) {
folder = ByteBufUtils.readString(in);
}
NBTTagCompound tagExtra = ByteBufUtils.readNBT(in);
NBTTagCompound tagCompound = ByteBufUtils.readNBT(in);
NBTTagList tagList = tagCompound.getTagList("TagUUIDs", 8);
data.cloned.setTag("TagUUIDs", tagList);
ServerCloneController.Instance.addClone(data.cloned, name, tab, tagExtra);
if (folder != null) {
ServerCloneController.Instance.addClone(data.cloned, name, folder, tagExtra);
} else {
ServerCloneController.Instance.addClone(data.cloned, name, tab, tagExtra);
}
}
}
@@ -13,6 +13,7 @@ import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import kamkeel.npcs.util.ByteBufUtils;
import noppes.npcs.controllers.ServerTagMapController;
import noppes.npcs.controllers.data.TagMap;
@@ -22,9 +23,16 @@ public final class CloneTagListPacket extends AbstractPacket {
public static String packetName = "Request|CloneTagList";
private int tab;
private String folderName;
public CloneTagListPacket(int tab) {
this.tab = tab;
this.folderName = null;
}
public CloneTagListPacket(String folderName) {
this.tab = -1;
this.folderName = folderName;
}
public CloneTagListPacket() {
@@ -44,6 +52,9 @@ public final class CloneTagListPacket extends AbstractPacket {
@Override
public void sendData(ByteBuf out) throws IOException {
out.writeInt(tab);
if (tab == -1) {
ByteBufUtils.writeString(out, folderName);
}
}
@Override
@@ -54,7 +65,13 @@ public final class CloneTagListPacket extends AbstractPacket {
return;
int tab = in.readInt();
TagMap tagMap = ServerTagMapController.Instance.getTagMap(tab);
TagMap tagMap;
if (tab == -1) {
String folder = ByteBufUtils.readString(in);
tagMap = ServerTagMapController.Instance.getTagMap(folder);
} else {
tagMap = ServerTagMapController.Instance.getTagMap(tab);
}
NBTTagCompound compound = new NBTTagCompound();
compound.setTag("CloneTags", tagMap.writeNBT());
GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound);
@@ -31,6 +31,7 @@ public class MobSpawnerPacket extends AbstractPacket {
private String selectedName;
private int tab;
private String folderName;
private NBTTagCompound compound;
@@ -44,6 +45,17 @@ public class MobSpawnerPacket extends AbstractPacket {
this.posz = posz;
this.selectedName = selectedName;
this.tab = tab;
this.folderName = null;
}
public MobSpawnerPacket(Action type, int posX, int posY, int posz, String selectedName, String folderName) {
this.type = type;
this.posX = posX;
this.posY = posY;
this.posz = posz;
this.selectedName = selectedName;
this.tab = -1;
this.folderName = folderName;
}
public MobSpawnerPacket(Action type, int posX, int posY, int posz, NBTTagCompound compound) {
@@ -58,6 +70,10 @@ public class MobSpawnerPacket extends AbstractPacket {
PacketClient.sendClient(new MobSpawnerPacket(Action.Server, x, y, z, name, tab));
}
public static void ServerFolder(int x, int y, int z, String name, String folderName) {
PacketClient.sendClient(new MobSpawnerPacket(Action.Server, x, y, z, name, folderName));
}
public static void Client(int x, int y, int z, NBTTagCompound compound) {
PacketClient.sendClient(new MobSpawnerPacket(Action.Client, x, y, z, compound));
}
@@ -88,6 +104,9 @@ public class MobSpawnerPacket extends AbstractPacket {
if (type == Action.Server) {
ByteBufUtils.writeString(out, this.selectedName);
out.writeInt(this.tab);
if (this.tab == -1) {
ByteBufUtils.writeString(out, this.folderName);
}
} else {
ByteBufUtils.writeNBT(out, this.compound);
}
@@ -107,10 +126,18 @@ public class MobSpawnerPacket extends AbstractPacket {
int z = in.readInt();
NBTTagCompound compound;
if (requestedAction == Action.Server)
compound = ServerCloneController.Instance.getCloneData(player, ByteBufUtils.readString(in), in.readInt());
else
if (requestedAction == Action.Server) {
String name = ByteBufUtils.readString(in);
int tab = in.readInt();
if (tab == -1) {
String folder = ByteBufUtils.readString(in);
compound = ServerCloneController.Instance.getCloneData(player, name, folder);
} else {
compound = ServerCloneController.Instance.getCloneData(player, name, tab);
}
} else {
compound = ByteBufUtils.readNBT(in);
}
if (!ConfigScript.canScript(player, CustomNpcsPermissions.SCRIPT)) {
return;
@@ -37,6 +37,7 @@ public class SpawnMobPacket extends AbstractPacket {
private String selectedName;
private int tab;
private String folderName;
private NBTTagCompound compound;
@@ -50,6 +51,17 @@ public class SpawnMobPacket extends AbstractPacket {
this.posz = posz;
this.selectedName = selectedName;
this.tab = tab;
this.folderName = null;
}
public SpawnMobPacket(Action type, int posX, int posY, int posz, String selectedName, String folderName) {
this.type = type;
this.posX = posX;
this.posY = posY;
this.posz = posz;
this.selectedName = selectedName;
this.tab = -1;
this.folderName = folderName;
}
public SpawnMobPacket(Action type, int posX, int posY, int posz, NBTTagCompound compound) {
@@ -64,6 +76,10 @@ public class SpawnMobPacket extends AbstractPacket {
PacketClient.sendClient(new SpawnMobPacket(Action.Server, x, y, z, name, tab));
}
public static void ServerFolder(int x, int y, int z, String name, String folderName) {
PacketClient.sendClient(new SpawnMobPacket(Action.Server, x, y, z, name, folderName));
}
public static void Client(int x, int y, int z, NBTTagCompound compound) {
PacketClient.sendClient(new SpawnMobPacket(Action.Client, x, y, z, compound));
}
@@ -94,6 +110,9 @@ public class SpawnMobPacket extends AbstractPacket {
if (type == Action.Server) {
ByteBufUtils.writeString(out, this.selectedName);
out.writeInt(this.tab);
if (this.tab == -1) {
ByteBufUtils.writeString(out, this.folderName);
}
} else {
ByteBufUtils.writeNBT(out, this.compound);
}
@@ -113,10 +132,18 @@ public class SpawnMobPacket extends AbstractPacket {
int z = in.readInt();
NBTTagCompound compound;
if (requestedAction == Action.Server)
compound = ServerCloneController.Instance.getCloneData(player, ByteBufUtils.readString(in), in.readInt());
else
if (requestedAction == Action.Server) {
String name = ByteBufUtils.readString(in);
int tab = in.readInt();
if (tab == -1) {
String folder = ByteBufUtils.readString(in);
compound = ServerCloneController.Instance.getCloneData(player, name, folder);
} else {
compound = ServerCloneController.Instance.getCloneData(player, name, tab);
}
} else {
compound = ByteBufUtils.readNBT(in);
}
if (compound == null)
return;
+70 -10
View File
@@ -5,45 +5,92 @@ import kamkeel.npcs.controllers.data.ability.AbilityController;
import noppes.npcs.LogWriter;
import noppes.npcs.controllers.AnimationController;
import noppes.npcs.controllers.data.Animation;
import noppes.npcs.controllers.data.BuiltInAnimation;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.*;
import java.util.function.Supplier;
public class Register<T> {
public static final Map<String, List<String>> REGISTERED_NAMESPACES = new LinkedHashMap<>();
private final String registryKey;
protected final String namespace;
protected final Map<String, Supplier<T>> entries = new LinkedHashMap<>();
public Register(String registryKey, String namespace) {
private Register(String registryKey, String namespace) {
this.registryKey = registryKey;
this.namespace = namespace;
}
public T register(String factoryName, Supplier<T> factory) {
entries.put(registryKey + "." + namespace + ":" + factoryName, factory);
entries.put(registryKey + "." + namespace + "." + factoryName, factory);
return factory.get();
}
public static boolean isEmpty(String registryKey) {
if (REGISTERED_NAMESPACES.isEmpty()) return true;
if (REGISTERED_NAMESPACES.get(registryKey).isEmpty()) return true;
return false;
}
public List<T> catalogue() {
List<T> list = new ArrayList<>();
for (String regKey : REGISTERED_NAMESPACES.keySet()) {
List<String> nameSp = REGISTERED_NAMESPACES.get(regKey);
for (String name : nameSp) {
List<T> nameSpContent = catalogue(regKey, name);
list.addAll(nameSpContent);
}
}
return list;
}
public List<T> catalogue(String namespace) {
return catalogue(registryKey, namespace);
}
public List<T> catalogue(String registryKey, String namespace) {
List<T> list = new ArrayList<>();
for (Map.Entry<String, Supplier<T>> entry : entries.entrySet()) {
if (entry.getKey().startsWith(registryKey + "." + namespace + ":"))
list.add(entry.getValue().get());
}
return list;
}
public static class Abilities extends Register<Ability> {
public Abilities(String namespace) {
private Abilities(String namespace) {
super("ability", namespace);
}
public void register() {
for (Map.Entry<String, Supplier<Ability>> entry : entries.entrySet()) {
AbilityController.Instance.registerType(entry.getKey(), entry.getValue());
AbilityController.Instance.registerType(entry.getValue().get().getTypeId(), entry.getValue());
}
}
public static Register.Abilities create(String namespace) {
if (!REGISTERED_NAMESPACES.containsKey("ability"))
REGISTERED_NAMESPACES.put("ability", new ArrayList<>());
if (REGISTERED_NAMESPACES.get("ability").contains(namespace)) {
LogWriter.error("REGISTER ABILITIES: Namespace " + namespace + " already registered!");
}
REGISTERED_NAMESPACES.get("ability").add(namespace);
return new Register.Abilities(namespace);
}
}
public static class Animations extends Register<Animation> {
private final Class<?> modClass;
private final String animationsPath;
public Animations(Class<?> modClass, String animationsPath, String namespace) {
super("ability", namespace);
private Animations(Class<?> modClass, String animationsPath, String namespace) {
super("animation", namespace);
this.animationsPath = animationsPath;
this.modClass = modClass;
}
@@ -57,12 +104,25 @@ public class Register<T> {
try {
String path = "/assets/" + namespace + "/" + animationsPath;
for (Map.Entry<String, Supplier<Animation>> entry : entries.entrySet()) {
String name = entry.getKey().substring(entry.getKey().indexOf(":") + 1);
String name = entry.getKey().substring(entry.getKey().indexOf(namespace + ".") + 1);
AnimationController.Instance.loadBuiltInAnimation(modClass, path, name);
}
} catch (Exception e) {
LogWriter.error("Error scanning built-in animations folder", e);
}
}
public static Register.Animations create(Class<?> modClass, String animationsPath, String namespace) {
if (!REGISTERED_NAMESPACES.containsKey("animation"))
REGISTERED_NAMESPACES.put("animation", new ArrayList<>());
if (REGISTERED_NAMESPACES.get("animation").contains(namespace)) {
LogWriter.error("REGISTER ANIMATIONS: Namespace " + namespace + " already registered!");
}
REGISTERED_NAMESPACES.get("animation").add(namespace);
return new Register.Animations(modClass, animationsPath, namespace);
}
}
}
+52 -9
View File
@@ -18,6 +18,7 @@ import noppes.npcs.entity.EntityNPCInterface;
import noppes.npcs.scripted.NpcAPI;
import noppes.npcs.scripted.event.AbilityEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -147,10 +148,15 @@ public class DataAbilities {
// Remove telegraph - it has served its purpose
removeTelegraph(currentAbility);
// Lock rotation if movement is locked during ACTIVE phase
// Capture current rotation values to force them every tick
if (currentAbility.isMovementLockedDuringActive()) {
captureLockedRotation();
// Handle rotation lock transition from WINDUP to ACTIVE
if (currentAbility.isRotationLockedDuringActive()) {
// ACTIVE or WINDUP_AND_ACTIVE: capture (or keep) rotation lock
if (!rotationLocked) {
captureLockedRotation();
}
} else if (rotationLocked) {
// WINDUP only: release the lock now that windup is over
releaseLockedRotation();
}
// Play active sound and animation
@@ -390,6 +396,9 @@ public class DataAbilities {
executeImmediate(ability, target);
} else {
// Normal windup flow
if (ability.isRotationLockedDuringWindup()) {
captureLockedRotation();
}
spawnTelegraph(ability, target);
playAbilitySound(ability.getWindUpSound());
playAbilityAnimation(ability.getWindUpAnimation());
@@ -403,8 +412,8 @@ public class DataAbilities {
* Called when windUpTicks is 0.
*/
private void executeImmediate(Ability ability, EntityLivingBase target) {
// Lock rotation if movement is locked during ACTIVE phase
if (ability.isMovementLockedDuringActive()) {
// Lock rotation if rotation is locked during ACTIVE phase
if (ability.isRotationLockedDuringActive()) {
captureLockedRotation();
}
@@ -934,9 +943,12 @@ public class DataAbilities {
// ROTATION LOCKING
// ═══════════════════════════════════════════════════════════════════
/** Bit flag for rotation lock in the existing data watcher slot 15 bitfield */
private static final int ROTATION_LOCKED_FLAG = 16;
/**
* Capture current rotation values to lock NPC's look direction.
* Called when entering ACTIVE phase with movement locked during active.
* Called when entering a locked phase (WINDUP or ACTIVE depending on LockMovementType).
*/
private void captureLockedRotation() {
lockedYaw = npc.rotationYaw;
@@ -944,22 +956,53 @@ public class DataAbilities {
lockedRenderYawOffset = npc.renderYawOffset;
lockedPitch = npc.rotationPitch;
rotationLocked = true;
npc.setBoolFlag(true, ROTATION_LOCKED_FLAG);
}
/**
* Release the rotation lock.
* Called when leaving ACTIVE phase or ability completes.
* Called when leaving the locked phase or ability completes.
*/
private void releaseLockedRotation() {
rotationLocked = false;
npc.setBoolFlag(false, ROTATION_LOCKED_FLAG);
}
/**
* Apply locked rotation values to the NPC.
* Called AFTER super.onLivingUpdate() in EntityNPCInterface to override
* any rotation changes made by the look helper or AI tasks.
* any rotation changes made by the look helper, AI tasks, or body smoothing.
* Runs on both client and server.
*/
public void applyLockedRotation() {
if (npc.worldObj.isRemote) {
// Client-side: check bit 16 in the existing flags data watcher (slot 15)
boolean flagActive = npc.getBoolFlag(ROTATION_LOCKED_FLAG);
if (!flagActive) {
rotationLocked = false;
return;
}
// First tick the flag is active: capture current rotation values
if (!rotationLocked) {
lockedYaw = npc.rotationYaw;
lockedYawHead = npc.rotationYawHead;
lockedRenderYawOffset = npc.renderYawOffset;
lockedPitch = npc.rotationPitch;
rotationLocked = true;
}
// Force all rotation values back to locked state
npc.rotationYaw = lockedYaw;
npc.rotationYawHead = lockedYawHead;
npc.renderYawOffset = lockedRenderYawOffset;
npc.rotationPitch = lockedPitch;
npc.prevRotationYaw = lockedYaw;
npc.prevRotationYawHead = lockedYawHead;
npc.prevRenderYawOffset = lockedRenderYawOffset;
npc.prevRotationPitch = lockedPitch;
return;
}
// Server-side: use stored locked values
if (!rotationLocked) {
return;
}
@@ -2,6 +2,7 @@ package noppes.npcs;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.SyncController;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.packets.data.ParticlePacket;
import kamkeel.npcs.network.packets.data.PlayerDataInfoPacket;
@@ -775,7 +776,7 @@ public class NoppesUtilServer {
public static void sendAbilityTypesInfo(EntityPlayerMP player) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
kamkeel.npcs.controllers.data.ability.AbilityController controller = kamkeel.npcs.controllers.data.ability.AbilityController.Instance;
AbilityController controller = AbilityController.Instance;
if (controller != null) {
int index = 0;
for (String typeId : controller.getTypes()) {
@@ -93,7 +93,9 @@ public class EntityAIAmbushTarget extends EntityAIBase {
* Updates the task
*/
public void updateTask() {
this.theEntity.getLookHelper().setLookPositionWithEntity(targetEntity, 30.0F, 30.0F);
if (!this.theEntity.abilities.isRotationLocked()) {
this.theEntity.getLookHelper().setLookPositionWithEntity(targetEntity, 30.0F, 30.0F);
}
}
private Vec3 findHidingSpot() {
@@ -118,7 +118,9 @@ public class EntityAIAttackTarget extends EntityAIBase {
* Updates the task
*/
public void updateTask() {
this.npc.getLookHelper().setLookPositionWithEntity(this.entityTarget, 30.0F, 30.0F);
if (!this.npc.abilities.isRotationLocked()) {
this.npc.getLookHelper().setLookPositionWithEntity(this.entityTarget, 30.0F, 30.0F);
}
// Skip pathfinding updates if ability is controlling movement
if (!navOverride && !this.npc.abilities.isAbilityControllingMovement() && --this.field_75445_i <= 0) {
@@ -63,7 +63,7 @@ public class EntityAIDodgeShoot extends EntityAIBase {
* Updates the task
*/
public void updateTask() {
if (this.entity.getAttackTarget() != null)
if (this.entity.getAttackTarget() != null && !this.entity.abilities.isRotationLocked())
this.entity.getLookHelper().setLookPositionWithEntity(this.entity.getAttackTarget(), 30.0F, 30.0F);
}
}
@@ -55,7 +55,9 @@ public class EntityAIFollow extends EntityAIBase {
if (updateTick < 10)
return;
updateTick = 0;
this.npc.getLookHelper().setLookPositionWithEntity(owner, 10.0F, (float) this.npc.getVerticalFaceSpeed());
if (!this.npc.abilities.isRotationLocked()) {
this.npc.getLookHelper().setLookPositionWithEntity(owner, 10.0F, (float) this.npc.getVerticalFaceSpeed());
}
double speed = 1 + distance / 150;
if (speed > 3)
@@ -26,7 +26,7 @@ public class EntityAILook extends EntityAIBase {
*/
@Override
public boolean shouldExecute() {
return !npc.isAttacking() && npc.getNavigator().noPath() && !npc.isPlayerSleeping() && npc.isEntityAlive();
return !npc.abilities.isRotationLocked() && !npc.isAttacking() && npc.getNavigator().noPath() && !npc.isPlayerSleeping() && npc.isEntityAlive();
}
/**
@@ -91,7 +91,9 @@ public class EntityAIOrbitTarget extends EntityAIBase {
* Updates the task
*/
public void updateTask() {
this.theEntity.getLookHelper().setLookPositionWithEntity(targetEntity, 30.0F, 30.0F);
if (!this.theEntity.abilities.isRotationLocked()) {
this.theEntity.getLookHelper().setLookPositionWithEntity(targetEntity, 30.0F, 30.0F);
}
if (this.theEntity.getNavigator().noPath() && this.tick >= 0) {
if (this.theEntity.onGround && !this.theEntity.isInWater()) {
double d0 = this.targetDistance * (double) (MathHelper.cos(angle / 180.0F * (float) Math.PI));
@@ -100,7 +100,9 @@ public class EntityAIRangedAttack extends EntityAIBase {
* Updates the task
*/
public void updateTask() {
this.entityHost.getLookHelper().setLookPositionWithEntity(this.attackTarget, 30.0F, 30.0F);
if (!this.entityHost.abilities.isRotationLocked()) {
this.entityHost.getLookHelper().setLookPositionWithEntity(this.attackTarget, 30.0F, 30.0F);
}
double var1 = this.entityHost.getDistanceSq(this.attackTarget.posX, this.attackTarget.boundingBox.minY, this.attackTarget.posZ);
float field_82642_h = this.entityHost.stats.rangedRange * this.entityHost.stats.rangedRange;
@@ -68,7 +68,9 @@ public class EntityAIStalkTarget extends EntityAIBase {
* Updates the task
*/
public void updateTask() {
this.theEntity.getLookHelper().setLookPositionWithEntity(targetEntity, 30.0F, 30.0F);
if (!this.theEntity.abilities.isRotationLocked()) {
this.theEntity.getLookHelper().setLookPositionWithEntity(targetEntity, 30.0F, 30.0F);
}
if (this.theEntity.getNavigator().noPath() || this.overRide) {
if (this.isLookingAway()) {
@@ -31,7 +31,7 @@ public class EntityAIWatchClosest extends EntityAIBase {
*/
@Override
public boolean shouldExecute() {
if (this.theWatcher.getRNG().nextFloat() >= this.field_75331_e || theWatcher.isInteracting()) {
if (this.theWatcher.abilities.isRotationLocked() || this.theWatcher.getRNG().nextFloat() >= this.field_75331_e || theWatcher.isInteracting()) {
return false;
} else {
if (this.theWatcher.getAttackTarget() != null) {
@@ -8,6 +8,7 @@ import net.minecraft.pathfinding.PathPoint;
import net.minecraft.util.MathHelper;
import net.minecraft.util.Vec3;
import noppes.npcs.constants.AiMutex;
import noppes.npcs.entity.EntityNPCInterface;
public class EntityAIZigZagTarget extends EntityAIBase {
private EntityCreature theEntity;
@@ -82,6 +83,8 @@ public class EntityAIZigZagTarget extends EntityAIBase {
* Updates the task
*/
public void updateTask() {
this.theEntity.getLookHelper().setLookPositionWithEntity(targetEntity, 30.0F, 30.0F);
if (!(this.theEntity instanceof EntityNPCInterface) || !((EntityNPCInterface) this.theEntity).abilities.isRotationLocked()) {
this.theEntity.getLookHelper().setLookPositionWithEntity(targetEntity, 30.0F, 30.0F);
}
}
}
@@ -76,6 +76,7 @@ import noppes.npcs.client.gui.GuiBorderBlock;
import noppes.npcs.client.gui.GuiMerchantAdd;
import noppes.npcs.client.gui.GuiNpcDimension;
import noppes.npcs.client.gui.GuiNpcMobSpawner;
import noppes.npcs.client.gui.GuiNpcMobSpawnerFullscreen;
import noppes.npcs.client.gui.GuiNpcMobSpawnerMounter;
import noppes.npcs.client.gui.GuiNpcPather;
import noppes.npcs.client.gui.GuiNpcRedstoneBlock;
@@ -336,7 +337,6 @@ public class ClientProxy extends CommonProxy {
ClientCloneController.Instance = new ClientCloneController();
ClientTagMapController.Instance = new ClientTagMapController();
MinecraftForge.EVENT_BUS.register(new ClientEventHandler());
// Telegraph rendering system
@@ -605,7 +605,9 @@ public class ClientProxy extends CommonProxy {
return new GuiNpcRedstoneBlock(x, y, z);
else if (gui == EnumGuiType.Cloner)
return new GuiNpcMobSpawner(x, y, z);
return GuiNpcMobSpawner.isFullscreen
? new GuiNpcMobSpawnerFullscreen(x, y, z)
: new GuiNpcMobSpawner(x, y, z);
else if (gui == EnumGuiType.MobSpawnerMounter)
return new GuiNpcMobSpawnerMounter(x, y, z);
@@ -41,6 +41,26 @@ public class ClientCloneController extends ServerCloneController {
return true;
}
@Override
public boolean addToTagMap(NBTTagCompound nbttagcompound, String name, String folderName) {
HashSet<UUID> tagUUIDs = new HashSet<>();
if (nbttagcompound.hasKey("TagUUIDs")) {
NBTTagList nbtTagList = nbttagcompound.getTagList("TagUUIDs", 8);
for (int i = 0; i < nbtTagList.tagCount(); i++) {
tagUUIDs.add(UUID.fromString(nbtTagList.getStringTagAt(i)));
}
}
TagMap tagMap = ClientTagMapController.Instance.getTagMap(folderName);
if (!tagUUIDs.isEmpty()) {
tagMap.putClone(name, tagUUIDs);
} else {
tagMap.removeClone(name);
}
ClientTagMapController.Instance.saveTagMap(tagMap);
return true;
}
@Override
public boolean removeFromTagMap(String name, int tab) {
TagMap tagMap = ClientTagMapController.Instance.getTagMap(tab);
@@ -51,4 +71,43 @@ public class ClientCloneController extends ServerCloneController {
return false;
}
@Override
public boolean removeFromTagMap(String name, String folderName) {
TagMap tagMap = ClientTagMapController.Instance.getTagMap(folderName);
if (tagMap.removeClone(name)) {
ClientTagMapController.Instance.saveTagMap(tagMap);
return true;
}
return false;
}
@Override
protected HashSet<UUID> getTagUUIDsFromTab(String cloneName, int tab) {
TagMap tagMap = ClientTagMapController.Instance.getTagMap(tab);
HashSet<UUID> uuids = tagMap.getUUIDs(cloneName);
return uuids != null ? new HashSet<>(uuids) : new HashSet<>();
}
@Override
protected HashSet<UUID> getTagUUIDsFromFolder(String cloneName, String folderName) {
TagMap tagMap = ClientTagMapController.Instance.getTagMap(folderName);
HashSet<UUID> uuids = tagMap.getUUIDs(cloneName);
return uuids != null ? new HashSet<>(uuids) : new HashSet<>();
}
@Override
protected void setTagUUIDsForTab(String cloneName, int tab, HashSet<UUID> tagUUIDs) {
if (tagUUIDs == null || tagUUIDs.isEmpty()) return;
TagMap tagMap = ClientTagMapController.Instance.getTagMap(tab);
tagMap.putClone(cloneName, tagUUIDs);
ClientTagMapController.Instance.saveTagMap(tagMap);
}
@Override
protected void setTagUUIDsForFolder(String cloneName, String folderName, HashSet<UUID> tagUUIDs) {
if (tagUUIDs == null || tagUUIDs.isEmpty()) return;
TagMap tagMap = ClientTagMapController.Instance.getTagMap(folderName);
tagMap.putClone(cloneName, tagUUIDs);
ClientTagMapController.Instance.saveTagMap(tagMap);
}
}
@@ -2,6 +2,7 @@ package noppes.npcs.client.gui;
import kamkeel.npcs.network.PacketClient;
import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket;
import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket;
import kamkeel.npcs.network.packets.request.clone.CloneListPacket;
import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket;
import kamkeel.npcs.network.packets.request.clone.CloneTagListPacket;
@@ -26,6 +27,8 @@ import noppes.npcs.client.gui.util.GuiNPCInterface;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.GuiNpcLabel;
import noppes.npcs.client.gui.util.GuiNpcTextField;
import noppes.npcs.client.gui.util.IClonerGui;
import noppes.npcs.client.gui.util.ICustomScrollListener;
import noppes.npcs.client.gui.util.IGuiData;
import noppes.npcs.controllers.data.Tag;
import noppes.npcs.controllers.data.TagMap;
@@ -38,52 +41,84 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICustomScrollListener, IClonerGui {
// ==================== STATIC STATE (persists across GUI opens, shared with fullscreen) ====================
public static boolean isFullscreen = false;
public static int activeTab = 1;
public static String activeFolder = null;
public static int showingClones = 0; // 0=Clones, 1=Entities, 2=Server, 3=Filters
public static String search = "";
public static String tagSearch = "";
public static HashMap<UUID, Tag> tags = new HashMap<>();
public static HashMap<String, UUID> tagNames = new HashMap<>();
public static HashSet<String> tagFilters = new HashSet<>();
public static byte displayTags = 0;
// 0 - Any, 1 - All, 2 - Not Any, 3 - Not All
public static byte filterCondition = 0;
public static byte ascending = 0;
// 0 - By Name, 1 - By Date
public static byte sortType = 0;
public TagMap tagMap;
// Scroll position preservation (shared with fullscreen)
public static int savedContentScrollY = 0;
public static int savedNavScrollY = 0;
// ==================== INSTANCE STATE ====================
public TagMap tagMap;
private final GuiCustomScrollCloner scroll = new GuiCustomScrollCloner(this, 0);
private final GuiCustomScroll filterScroll = new GuiCustomScroll(this, 1);
private int posX, posY, posZ;
public int posX, posY, posZ;
private List<String> list;
private List<String> rawList;
private List<String> tagList;
private static int showingClones = 0;
private static String search = "";
private static String tagSearch = "";
private int activeTab = 1;
// ==================== CONSTRUCTOR ====================
public GuiNpcMobSpawner(int i, int j, int k) {
super();
xSize = 354;
posX = i;
posY = j;
posZ = k;
this.closeOnEsc = true;
closeOnEsc = true;
xSize = 354;
setBackground("menubg.png");
PacketClient.sendClient(new CloneAllTagsPacket());
if (showingClones == 2) {
PacketClient.sendClient(new CloneFolderListPacket());
}
// Minimized view doesn't support folders — ensure we're on a tab
if (activeFolder != null) {
if (activeTab < 1 || activeTab > 15) {
activeTab = 1;
}
activeFolder = null;
}
}
// ==================== IClonerGui ====================
@Override
public int getShowingClones() {
return showingClones;
}
@Override
public HashMap<UUID, Tag> getTags() {
return tags;
}
@Override
public TagMap getTagMap() {
return tagMap;
}
// ==================== INIT GUI ====================
public void initGui() {
// Save scroll position before clear
savedContentScrollY = scroll.scrollY;
super.initGui();
guiTop += 10;
guiLeft += 30;
@@ -92,6 +127,7 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
scroll.setSize(293, 188);
scroll.guiLeft = guiLeft + 4;
scroll.guiTop = guiTop + 26;
filterScroll.clear();
filterScroll.setSize(140, 166);
filterScroll.guiLeft = guiLeft + 4;
@@ -99,112 +135,133 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
filterScroll.multipleSelection = true;
filterScroll.setSelectedList(tagFilters);
GuiMenuTopButton button;
addTopButton(button = new GuiMenuTopButton(3, guiLeft + 4, guiTop - 17, "spawner.clones"));
button.active = showingClones == 0;
addTopButton(button = new GuiMenuTopButton(5, button, "gui.server"));
button.active = showingClones == 2;
addTopButton(button = new GuiMenuTopButton(4, button, "spawner.entities"));
button.active = showingClones == 1;
addTopButton(button = new GuiMenuTopButton(16, guiLeft + (xSize - 67), guiTop - 17, "gui.filters"));
button.active = showingClones == 3;
addTopButton(button = new GuiMenuTopButton(17, guiLeft + (xSize - 22), guiTop - 17, "X"));
// Top buttons — build right-to-left to avoid overlap
GuiMenuTopButton closeBtn = new GuiMenuTopButton(17, guiLeft + (xSize - 22), guiTop - 17, "X");
addTopButton(closeBtn);
// Position "+" dynamically to the left of "X"
int plusX = closeBtn.xPosition - (fontRendererObj.getStringWidth("+") + 12) - 2;
GuiMenuTopButton fullscreenBtn = new GuiMenuTopButton(61, plusX, guiTop - 17, "+");
addTopButton(fullscreenBtn);
// Position "Filters" dynamically to the left of "+"
int filterBtnW = fontRendererObj.getStringWidth(StatCollector.translateToLocal("gui.filters")) + 12;
int filterX = fullscreenBtn.xPosition - filterBtnW - 2;
GuiMenuTopButton filterBtn = new GuiMenuTopButton(16, filterX, guiTop - 17, "gui.filters");
filterBtn.active = showingClones == 3;
addTopButton(filterBtn);
// Mode buttons from left
GuiMenuTopButton btn;
addTopButton(btn = new GuiMenuTopButton(3, guiLeft + 4, guiTop - 17, "spawner.clones"));
btn.active = showingClones == 0;
addTopButton(btn = new GuiMenuTopButton(5, btn, "gui.server"));
btn.active = showingClones == 2;
addTopButton(btn = new GuiMenuTopButton(4, btn, "spawner.entities"));
btn.active = showingClones == 1;
if (showingClones < 3) {
addScroll(scroll);
addTextField(new GuiNpcTextField(1, this, fontRendererObj, guiLeft + 4, guiTop + 4, 293, 20, search));
addButton(new GuiNpcButton(1, guiLeft + 298, guiTop + 6, 52, 20, "item.monsterPlacer.name"));
addButton(new GuiNpcButton(2, guiLeft + 298, guiTop + 140, 52, 20, "spawner.mobspawner"));
if (showingClones == 0 || showingClones == 2) {
addSideButton(new GuiMenuSideButton(21, guiLeft - 70, this.guiTop + 2, 70, 22, "1"));
addSideButton(new GuiMenuSideButton(22, guiLeft - 70, this.guiTop + 23, 70, 22, "2"));
addSideButton(new GuiMenuSideButton(23, guiLeft - 70, this.guiTop + 44, 70, 22, "3"));
addSideButton(new GuiMenuSideButton(24, guiLeft - 70, this.guiTop + 65, 70, 22, "4"));
addSideButton(new GuiMenuSideButton(25, guiLeft - 70, this.guiTop + 86, 70, 22, "5"));
addSideButton(new GuiMenuSideButton(26, guiLeft - 70, this.guiTop + 107, 35, 22, "6"));
addSideButton(new GuiMenuSideButton(27, guiLeft - 35, this.guiTop + 107, 35, 22, "7"));
addSideButton(new GuiMenuSideButton(28, guiLeft - 70, this.guiTop + 128, 35, 22, "8"));
addSideButton(new GuiMenuSideButton(29, guiLeft - 35, this.guiTop + 128, 35, 22, "9"));
addSideButton(new GuiMenuSideButton(30, guiLeft - 70, this.guiTop + 149, 35, 22, "10"));
addSideButton(new GuiMenuSideButton(31, guiLeft - 35, this.guiTop + 149, 35, 22, "11"));
addSideButton(new GuiMenuSideButton(32, guiLeft - 70, this.guiTop + 170, 35, 22, "12"));
addSideButton(new GuiMenuSideButton(33, guiLeft - 35, this.guiTop + 170, 35, 22, "13"));
addSideButton(new GuiMenuSideButton(34, guiLeft - 70, this.guiTop + 191, 35, 22, "14"));
addSideButton(new GuiMenuSideButton(35, guiLeft - 35, this.guiTop + 191, 35, 22, "15"));
addButton(new GuiNpcButton(6, guiLeft + 298, guiTop + 190, 52, 20, "gui.remove"));
getSideButton(20 + activeTab).active = true;
initTabSideButtons();
showClones();
// Restore scroll position
scroll.scrollY = Math.min(savedContentScrollY, Math.max(0, scroll.maxScrollY));
} else {
showEntities();
scroll.scrollY = Math.min(savedContentScrollY, Math.max(0, scroll.maxScrollY));
}
} else {
// Show Filters
addLabel(new GuiNpcLabel(1, StatCollector.translateToLocal("cloner.tagFilters"), guiLeft + 7, guiTop + 7));
addScroll(filterScroll);
addTextField(new GuiNpcTextField(2, this, fontRendererObj, guiLeft + 4, guiTop + 190, 140, 20, tagSearch));
addLabel(new GuiNpcLabel(2, StatCollector.translateToLocal("cloner.tagVisibility"), guiLeft + 150, guiTop + 27));
addButton(new GuiNpcButton(12, guiLeft + 215, guiTop + 20, new String[]{"display.show", "display.all", "display.hide"}, displayTags));
addLabel(new GuiNpcLabel(3, StatCollector.translateToLocal("filter.contains"), guiLeft + 150, guiTop + 50));
addButton(new GuiNpcButton(13, guiLeft + 215, guiTop + 43, new String[]{"filter.any", "filter.all", "filter.notany", "filter.notall"}, filterCondition));
addButton(new GuiNpcButton(11, guiLeft + 150, guiTop + 66, 130, 20, "gui.deselectAll"));
addLabel(new GuiNpcLabel(4, StatCollector.translateToLocal("cloner.order"), guiLeft + 150, guiTop + 112));
addButton(new GuiNpcButton(14, guiLeft + 215, guiTop + 105, new String[]{"cloner.ascending", "cloner.descending"}, ascending));
addLabel(new GuiNpcLabel(5, StatCollector.translateToLocal("cloner.type"), guiLeft + 150, guiTop + 135));
addButton(new GuiNpcButton(15, guiLeft + 215, guiTop + 128, new String[]{"cloner.name", "cloner.date"}, sortType));
getButton(12).width = 65;
getButton(12).height = 20;
getButton(13).width = 65;
getButton(13).height = 20;
getButton(14).width = 65;
getButton(14).height = 20;
getButton(15).width = 65;
getButton(15).height = 20;
showFilters();
showFiltersPage();
}
}
public int getShowingClones() {
return showingClones;
private void initTabSideButtons() {
// Tabs 1-5: full width (70px)
addSideButton(new GuiMenuSideButton(21, guiLeft - 70, guiTop + 2, 70, 22, "1"));
addSideButton(new GuiMenuSideButton(22, guiLeft - 70, guiTop + 23, 70, 22, "2"));
addSideButton(new GuiMenuSideButton(23, guiLeft - 70, guiTop + 44, 70, 22, "3"));
addSideButton(new GuiMenuSideButton(24, guiLeft - 70, guiTop + 65, 70, 22, "4"));
addSideButton(new GuiMenuSideButton(25, guiLeft - 70, guiTop + 86, 70, 22, "5"));
// Tabs 6-15: half width (35px), 2-column grid
addSideButton(new GuiMenuSideButton(26, guiLeft - 70, guiTop + 107, 35, 22, "6"));
addSideButton(new GuiMenuSideButton(27, guiLeft - 35, guiTop + 107, 35, 22, "7"));
addSideButton(new GuiMenuSideButton(28, guiLeft - 70, guiTop + 128, 35, 22, "8"));
addSideButton(new GuiMenuSideButton(29, guiLeft - 35, guiTop + 128, 35, 22, "9"));
addSideButton(new GuiMenuSideButton(30, guiLeft - 70, guiTop + 149, 35, 22, "10"));
addSideButton(new GuiMenuSideButton(31, guiLeft - 35, guiTop + 149, 35, 22, "11"));
addSideButton(new GuiMenuSideButton(32, guiLeft - 70, guiTop + 170, 35, 22, "12"));
addSideButton(new GuiMenuSideButton(33, guiLeft - 35, guiTop + 170, 35, 22, "13"));
addSideButton(new GuiMenuSideButton(34, guiLeft - 70, guiTop + 191, 35, 22, "14"));
addSideButton(new GuiMenuSideButton(35, guiLeft - 35, guiTop + 191, 35, 22, "15"));
if (activeTab >= 1 && activeTab <= 15) {
GuiMenuSideButton active = getSideButton(20 + activeTab);
if (active != null) active.active = true;
}
}
public void showFilters() {
// ==================== FILTERS PAGE ====================
private void showFiltersPage() {
int baseY = guiTop;
addLabel(new GuiNpcLabel(1, StatCollector.translateToLocal("cloner.tagFilters"),
guiLeft + 7, baseY + 7));
filterScroll.clear();
filterScroll.setSize(140, 166);
filterScroll.guiLeft = guiLeft + 4;
filterScroll.guiTop = baseY + 19;
filterScroll.multipleSelection = true;
filterScroll.setSelectedList(tagFilters);
addScroll(filterScroll);
addTextField(new GuiNpcTextField(2, this, fontRendererObj,
guiLeft + 4, baseY + 190, 140, 20, tagSearch));
addLabel(new GuiNpcLabel(2, StatCollector.translateToLocal("cloner.tagVisibility"),
guiLeft + 150, baseY + 27));
addButton(new GuiNpcButton(12, guiLeft + 215, baseY + 20,
new String[]{"display.show", "display.all", "display.hide"}, displayTags));
addLabel(new GuiNpcLabel(3, StatCollector.translateToLocal("filter.contains"),
guiLeft + 150, baseY + 50));
addButton(new GuiNpcButton(13, guiLeft + 215, baseY + 43,
new String[]{"filter.any", "filter.all", "filter.notany", "filter.notall"}, filterCondition));
addButton(new GuiNpcButton(11, guiLeft + 150, baseY + 66, 130, 20, "gui.deselectAll"));
addLabel(new GuiNpcLabel(4, StatCollector.translateToLocal("cloner.order"),
guiLeft + 150, baseY + 112));
addButton(new GuiNpcButton(14, guiLeft + 215, baseY + 105,
new String[]{"cloner.ascending", "cloner.descending"}, ascending));
addLabel(new GuiNpcLabel(5, StatCollector.translateToLocal("cloner.type"),
guiLeft + 150, baseY + 135));
addButton(new GuiNpcButton(15, guiLeft + 215, baseY + 128,
new String[]{"cloner.name", "cloner.date"}, sortType));
getButton(12).width = 65;
getButton(12).height = 20;
getButton(13).width = 65;
getButton(13).height = 20;
getButton(14).width = 65;
getButton(14).height = 20;
getButton(15).width = 65;
getButton(15).height = 20;
filterScroll.setList(getTagList());
}
private void showEntities() {
Map<?, ?> data = EntityList.stringToClassMapping;
ArrayList<String> list = new ArrayList<String>();
for (Object name : data.keySet()) {
Class<?> c = (Class<?>) data.get(name);
try {
if (EntityLiving.class.isAssignableFrom(c) && c.getConstructor(new Class[]{World.class}) != null && !Modifier.isAbstract(c.getModifiers()))
list.add(name.toString());
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
}
}
this.list = list;
this.rawList = new ArrayList<String>(this.list);
scroll.setList(getSearchList(), ascending == 0, sortType == 0);
}
// ==================== CLONE / ENTITY DISPLAY ====================
private void showClones() {
if (showingClones == 2) {
PacketClient.sendClient(new CloneTagListPacket(activeTab));
@@ -219,16 +276,41 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
}
this.tagMap = ClientTagMapController.Instance.getTagMap(activeTab);
populateRawListWithTags();
scroll.setList(getSearchList(), ascending == 0, sortType == 0);
}
private void showEntities() {
Map<?, ?> data = EntityList.stringToClassMapping;
ArrayList<String> list = new ArrayList<String>();
for (Object name : data.keySet()) {
Class<?> c = (Class<?>) data.get(name);
try {
if (EntityLiving.class.isAssignableFrom(c)
&& c.getConstructor(new Class[]{World.class}) != null
&& !Modifier.isAbstract(c.getModifiers()))
list.add(name.toString());
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
}
}
this.list = list;
this.rawList = new ArrayList<String>(this.list);
scroll.setList(getSearchList(), ascending == 0, sortType == 0);
}
private void populateRawListWithTags() {
this.rawList = new ArrayList<>(this.list);
if (this.tagMap != null) {
if (GuiNpcMobSpawner.displayTags == 0 || GuiNpcMobSpawner.displayTags == 1) {
if (displayTags == 0 || displayTags == 1) {
for (int i = 0; i < this.list.size(); i++) {
StringBuilder npcName = new StringBuilder(list.get(i));
if (this.tagMap.hasClone(list.get(i))) {
for (UUID tagUUID : this.tagMap.getUUIDsList(list.get(i))) {
Tag tag = tags.get(tagUUID);
if (tag != null) {
if (GuiNpcMobSpawner.displayTags == 1 || !tag.getIsHidden()) {
if (displayTags == 1 || !tag.getIsHidden()) {
npcName.append(" [" + tag.name + "]");
}
}
@@ -238,27 +320,26 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
}
}
}
scroll.setList(getSearchList(), ascending == 0, sortType == 0);
}
public void keyTyped(char c, int i) {
super.keyTyped(c, i);
if (getTextField(1) != null) {
if (search.equals(getTextField(1).getText()))
return;
search = getTextField(1).getText().toLowerCase();
scroll.setList(getSearchList(), ascending == 0, sortType == 0);
}
if (getTextField(2) != null) {
if (tagSearch.equals(getTextField(2).getText()))
return;
tagSearch = getTextField(2).getText().toLowerCase();
filterScroll.setList(getTagList());
}
private NBTTagCompound getCompound() {
String sel = scroll.getSelected();
if (sel == null)
return null;
if (showingClones == 0) {
return ClientCloneController.Instance.getCloneData(player, sel, activeTab);
} else {
Entity entity = EntityList.createEntityByName(sel, Minecraft.getMinecraft().theWorld);
if (entity == null)
return null;
NBTTagCompound compound = new NBTTagCompound();
entity.writeToNBTOptional(compound);
return compound;
}
}
// ==================== SEARCH / FILTER ====================
private List<String> getSearchList() {
if (this.list == null) {
this.list = new ArrayList<String>();
@@ -269,10 +350,8 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
// No Filters - or - Entities
if (tagFilters.size() == 0 || showingClones == 1) {
// No Search
if (search.isEmpty())
return new ArrayList<String>(this.list);
// Yes Search
else {
List<String> list = new ArrayList<String>();
for (int i = 0; i < this.list.size(); i++) {
@@ -284,10 +363,7 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
}
}
////////////////////////
//// WITH FILTERS
// No Search
// With Filters
if (search.isEmpty()) {
List<String> list = new ArrayList<String>();
for (String name : this.list) {
@@ -300,7 +376,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
return list;
}
// Yes Search
List<String> list = new ArrayList<String>();
for (int i = 0; i < this.list.size(); i++) {
if (this.rawList.get(i).toLowerCase().contains(search)) {
@@ -311,7 +386,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
}
}
}
}
return list;
}
@@ -346,7 +420,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
}
}
}
return conditionMet;
}
@@ -354,10 +427,8 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
if (this.tagList == null) {
this.tagList = new ArrayList<String>();
}
// No Search // No Filters - or - Entities
if (tagSearch.isEmpty())
return new ArrayList<String>(tagNames.keySet());
// Yes Search // No Filters - or - Entities
List<String> list = new ArrayList<String>();
for (String name : this.tagList) {
@@ -367,125 +438,162 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
return list;
}
private NBTTagCompound getCompound() {
String sel = scroll.getSelected();
if (sel == null)
return null;
if (showingClones == 0) {
return ClientCloneController.Instance.getCloneData(player, sel, activeTab);
} else {
Entity entity = EntityList.createEntityByName(sel, Minecraft.getMinecraft().theWorld);
if (entity == null)
return null;
NBTTagCompound compound = new NBTTagCompound();
entity.writeToNBTOptional(compound);
return compound;
}
// ==================== MOUSE / KEYBOARD EVENTS ====================
@Override
public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) {
}
@Override
public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) {
}
public void keyTyped(char c, int i) {
super.keyTyped(c, i);
GuiNpcTextField searchField = getTextField(1);
if (searchField != null) {
String newText = searchField.getText().toLowerCase();
if (!search.equals(newText)) {
search = newText;
scroll.setList(getSearchList(), ascending == 0, sortType == 0);
}
}
GuiNpcTextField tagSearchField = getTextField(2);
if (tagSearchField != null) {
String newText = tagSearchField.getText().toLowerCase();
if (!tagSearch.equals(newText)) {
tagSearch = newText;
filterScroll.setList(getTagList());
}
}
}
// ==================== BUTTON ACTIONS ====================
@Override
protected void actionPerformed(GuiButton guibutton) {
int id = guibutton.id;
if (id == 0) {
close();
}
if (id == 1) {
if (showingClones == 2) {
String sel = scroll.getSelected();
if (sel == null)
return;
SpawnMobPacket.Server(posX, posY, posZ, sel, activeTab);
close();
} else {
NBTTagCompound compound = getCompound();
if (compound == null)
return;
SpawnMobPacket.Client(posX, posY, posZ, compound);
close();
}
}
if (id == 2) {
if (showingClones == 2) {
String sel = scroll.getSelected();
if (sel == null)
return;
MobSpawnerPacket.Server(posX, posY, posZ, sel, activeTab);
close();
} else {
NBTTagCompound compound = getCompound();
if (compound == null)
return;
MobSpawnerPacket.Client(posX, posY, posZ, compound);
close();
}
// Tab side buttons (IDs 21-35)
if (id >= 21 && id <= 35) {
activeTab = id - 20;
activeFolder = null;
initGui();
return;
}
// Mode buttons
if (id == 3) {
savedContentScrollY = 0;
showingClones = 0;
initGui();
}
if (id == 4) {
savedContentScrollY = 0;
showingClones = 1;
initGui();
}
if (id == 5) {
savedContentScrollY = 0;
showingClones = 2;
PacketClient.sendClient(new CloneFolderListPacket());
initGui();
}
if (id == 16) {
savedContentScrollY = 0;
showingClones = 3;
initGui();
}
if (id == 17) {
close();
}
// Open fullscreen GUI
if (id == 61) {
isFullscreen = true;
Minecraft.getMinecraft().displayGuiScreen(
new GuiNpcMobSpawnerFullscreen(posX, posY, posZ));
}
// Spawn
if (id == 1) {
if (showingClones == 2) {
String sel = scroll.getSelected();
if (sel == null) return;
SpawnMobPacket.Server(posX, posY, posZ, sel, activeTab);
close();
} else {
NBTTagCompound compound = getCompound();
if (compound == null) return;
SpawnMobPacket.Client(posX, posY, posZ, compound);
close();
}
}
// Mob Spawner
if (id == 2) {
if (showingClones == 2) {
String sel = scroll.getSelected();
if (sel == null) return;
MobSpawnerPacket.Server(posX, posY, posZ, sel, activeTab);
close();
} else {
NBTTagCompound compound = getCompound();
if (compound == null) return;
MobSpawnerPacket.Client(posX, posY, posZ, compound);
close();
}
}
// Remove
if (id == 6) {
if (scroll.getSelected() != null) {
int prevSelected = scroll.selected;
if (showingClones == 2) {
PacketClient.sendClient(new CloneRemovePacket(activeTab, scroll.getSelected()));
return;
}
ClientCloneController.Instance.removeClone(scroll.getSelected(), activeTab);
scroll.selected = -1;
initGui();
if (scroll.list != null && !scroll.list.isEmpty()) {
scroll.selected = Math.min(prevSelected, scroll.list.size() - 1);
} else {
scroll.selected = -1;
}
}
}
// Filter controls
if (id == 11) {
tagFilters = new HashSet<>();
filterScroll.setSelectedList(tagFilters);
initGui();
}
if (id == 12) {
GuiNpcButton button = (GuiNpcButton) guibutton;
displayTags = (byte) button.getValue();
displayTags = (byte) ((GuiNpcButton) guibutton).getValue();
}
if (id == 13) {
GuiNpcButton button = (GuiNpcButton) guibutton;
filterCondition = (byte) button.getValue();
filterCondition = (byte) ((GuiNpcButton) guibutton).getValue();
}
if (id == 14) {
GuiNpcButton button = (GuiNpcButton) guibutton;
ascending = (byte) button.getValue();
ascending = (byte) ((GuiNpcButton) guibutton).getValue();
}
if (id == 15) {
GuiNpcButton button = (GuiNpcButton) guibutton;
sortType = (byte) button.getValue();
}
if (id > 20) {
activeTab = id - 20;
initGui();
sortType = (byte) ((GuiNpcButton) guibutton).getValue();
}
}
// ==================== DATA FROM SERVER ====================
@Override
public void save() {
}
@Override
public void setGuiData(NBTTagCompound compound) {
if (compound.hasKey("CloneTags")) {
if (compound.hasKey("CloneFolders")) {
// Minimized doesn't use folders, but still handle the packet
initGui();
} else if (compound.hasKey("CloneTags")) {
this.tagMap = new TagMap(activeTab);
NBTTagCompound cloneTags = compound.getCompoundTag("CloneTags");
this.tagMap.readNBT(cloneTags);
@@ -521,29 +629,8 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData {
}
this.list = list;
this.rawList = new ArrayList<String>(this.list);
if (this.tagMap != null) {
if (GuiNpcMobSpawner.displayTags == 0 || GuiNpcMobSpawner.displayTags == 1) {
for (int i = 0; i < this.list.size(); i++) {
StringBuilder npcName = new StringBuilder(list.get(i));
if (this.tagMap.hasClone(list.get(i))) {
for (UUID tagUUID : this.tagMap.getUUIDsList(list.get(i))) {
Tag tag = tags.get(tagUUID);
if (tag != null) {
if (GuiNpcMobSpawner.displayTags == 1 || !tag.getIsHidden()) {
npcName.append(" [" + tag.name + "]");
}
}
}
}
this.rawList.set(i, npcName.toString());
}
}
}
populateRawListWithTags();
scroll.setList(getSearchList(), ascending == 0, sortType == 0);
}
}
}
@@ -2,6 +2,7 @@ package noppes.npcs.client.gui;
import kamkeel.npcs.network.PacketClient;
import kamkeel.npcs.network.packets.request.clone.CloneAllTagsShortPacket;
import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket;
import kamkeel.npcs.network.packets.request.clone.ClonePreSavePacket;
import kamkeel.npcs.network.packets.request.clone.CloneSavePacket;
import net.minecraft.client.Minecraft;
@@ -22,12 +23,14 @@ import noppes.npcs.client.gui.util.GuiNpcTextField;
import noppes.npcs.client.gui.util.IGuiData;
import noppes.npcs.client.gui.util.ISubGuiListener;
import noppes.npcs.client.gui.util.SubGuiInterface;
import noppes.npcs.controllers.data.CloneFolder;
import noppes.npcs.controllers.data.Tag;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCallback, IGuiData, ISubGuiListener {
@@ -35,9 +38,16 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall
private Entity toClone;
private NBTTagCompound compound;
private static boolean serverSide = false;
private static int saveMode = 0; // 0=Tab, 1=Folder
private static int tab = 1;
private static String folder = null;
public boolean isNPC = false;
// Client-side folder names
private ArrayList<String> clientFolderNames = new ArrayList<>();
// Server-side folder names (populated via CloneFolderListPacket)
private static ArrayList<String> serverFolderNames = new ArrayList<>();
// Selected Tags to Add
public static NBTTagList tagsCompound;
public static HashSet<String> addTags;
@@ -68,6 +78,37 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall
if (isNPC) {
PacketClient.sendClient(new CloneAllTagsShortPacket());
}
buildClientFolderNames();
// Request server folders if in server mode
if (serverSide) {
PacketClient.sendClient(new CloneFolderListPacket());
}
// Validate saved folder selection
validateFolder();
}
private void buildClientFolderNames() {
clientFolderNames.clear();
if (ClientCloneController.Instance != null) {
for (CloneFolder f : ClientCloneController.Instance.getFolderList()) {
clientFolderNames.add(f.name);
}
}
}
private List<String> getActiveFolderList() {
return serverSide ? serverFolderNames : clientFolderNames;
}
private void validateFolder() {
if (saveMode == 1 && folder != null) {
if (!getActiveFolderList().contains(folder)) {
folder = null;
}
}
}
@Override
@@ -77,61 +118,138 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall
addLabel(new GuiNpcLabel(0, "Save as", guiLeft + 4, guiTop + 6));
addTextField(new GuiNpcTextField(0, this, fontRendererObj, guiLeft + 4, guiTop + 18, 200, 20, name));
// Tab/Folder toggle
addButton(new GuiNpcButton(6, guiLeft + 4, guiTop + 45, 50, 20,
new String[]{"Tab", "Folder"}, saveMode));
addLabel(new GuiNpcLabel(1, "Tab", guiLeft + 10, guiTop + 50));
addButton(new GuiButtonBiDirectional(2, guiLeft + 40, guiTop + 45, 60, 20, new String[]{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"}, tab - 1));
// Destination selector based on saveMode
if (saveMode == 0) {
// Tab selector: 1-15
String[] tabLabels = new String[15];
for (int i = 0; i < 15; i++) tabLabels[i] = String.valueOf(i + 1);
int selectedTab = (tab >= 1 && tab <= 15) ? tab - 1 : 0;
addButton(new GuiButtonBiDirectional(2, guiLeft + 56, guiTop + 45, 90, 20, tabLabels, selectedTab));
} else {
// Folder selector button — opens SubGuiFolderSelect
String btnText = folder != null ? folder : "Select a Folder";
addButton(new GuiNpcButton(2, guiLeft + 56, guiTop + 45, 148, 20, btnText));
}
addButton(new GuiNpcButton(3, guiLeft + 4, guiTop + 95, new String[]{"Client side", "Server side"}, serverSide ? 1 : 0));
addLabel(new GuiNpcLabel(1, "Tab", guiLeft + 10, guiTop + 50));
addButton(new GuiNpcButton(0, guiLeft + 4, guiTop + 70, 80, 20, "gui.save"));
// Save / Cancel
GuiNpcButton saveBtn = new GuiNpcButton(0, guiLeft + 4, guiTop + 70, 80, 20, "gui.save");
if (saveMode == 1 && folder == null) {
saveBtn.enabled = false;
}
addButton(saveBtn);
addButton(new GuiNpcButton(1, guiLeft + 86, guiTop + 70, 80, 20, "gui.cancel"));
// Client / Server toggle
addButton(new GuiNpcButton(3, guiLeft + 4, guiTop + 95, new String[]{"Client side", "Server side"}, serverSide ? 1 : 0));
if (isNPC) {
addButton(new GuiNpcButton(4, guiLeft + 4, guiTop + 120, 99, 20, "cloner.wandTags"));
addButton(new GuiNpcButton(5, guiLeft + 106, guiTop + 120, 99, 20, "cloner.npcTags"));
if (addTags.size() > 0) {
addLabel(new GuiNpcLabel(6, "cloner.wandtagsapplied", guiLeft + 10, guiTop + 160));
addLabel(new GuiNpcLabel(8, "cloner.wandtagsapplied", guiLeft + 10, guiTop + 160));
}
}
}
private void updateDestinationFromSelector() {
if (saveMode == 0) {
GuiNpcButton selector = getButton(2);
if (selector == null) return;
tab = selector.getValue() + 1;
folder = null;
}
// In folder mode, folder is already set via SubGuiFolderSelect
}
public void buttonEvent(GuiButton guibutton) {
int id = guibutton.id;
// Save
if (id == 0) {
updateDestinationFromSelector();
String name = getTextField(0).getText();
if (name.isEmpty())
return;
int tab = ((GuiNpcButton) guibutton).getValue() + 1;
if (saveMode == 1 && folder == null)
return;
if (!serverSide) {
if (ClientCloneController.Instance.getCloneData(null, name, tab) != null)
boolean exists;
if (saveMode == 1 && folder != null && ClientCloneController.Instance != null) {
exists = ClientCloneController.Instance.getCloneData(null, name, folder) != null;
} else {
exists = ClientCloneController.Instance.getCloneData(null, name, tab) != null;
}
if (exists)
displayGuiScreen(new GuiYesNo(this, "Warning", "You are about to overwrite a clone", 1));
else
confirmClicked(true, 0);
} else
PacketClient.sendClient(new ClonePreSavePacket(name, tab));
} else {
if (saveMode == 1 && folder != null) {
PacketClient.sendClient(new ClonePreSavePacket(name, folder));
} else {
PacketClient.sendClient(new ClonePreSavePacket(name, tab));
}
}
}
// Cancel
if (id == 1) {
close();
}
// Destination selector
if (id == 2) {
tab = ((GuiNpcButton) guibutton).getValue() + 1;
if (saveMode == 0) {
// Tab mode — update from BiDirectional
updateDestinationFromSelector();
} else {
// Folder mode — open folder selector Sub GUI
List<String> folders = getActiveFolderList();
setSubGui(new SubGuiFolderSelect(folders, folder));
}
}
// Client / Server toggle
if (id == 3) {
boolean wasServerSide = serverSide;
serverSide = ((GuiNpcButton) guibutton).getValue() == 1;
if (serverSide && !wasServerSide) {
// Switching to server mode — request fresh folder list
PacketClient.sendClient(new CloneFolderListPacket());
}
// Validate folder exists on the new side
validateFolder();
initGui();
}
// Wand Tags
if (id == 4) {
if (isNPC) {
this.setSubGui(new SubGuiClonerQuickTags(this));
}
}
// NPC Tags
if (id == 5) {
if (isNPC) {
this.setSubGui(new SubGuiClonerNPCTags((EntityNPCInterface) toClone, this));
}
}
}
// Tab/Folder mode toggle
if (id == 6) {
saveMode = ((GuiNpcButton) guibutton).getValue();
initGui();
}
}
@Override
public void confirmClicked(boolean confirm, int id) {
@@ -145,13 +263,21 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall
if (isNPC) {
compound.setTag("TagUUIDs", tagsCompound);
}
ClientCloneController.Instance.addClone(compound, name, tab, extraTags);
if (saveMode == 1 && folder != null && ClientCloneController.Instance != null) {
ClientCloneController.Instance.addClone(compound, name, folder, extraTags);
} else {
ClientCloneController.Instance.addClone(compound, name, tab, extraTags);
}
} else {
NBTTagCompound compounder = new NBTTagCompound();
if (isNPC) {
compounder.setTag("TagUUIDs", tagsCompound);
}
PacketClient.sendClient(new CloneSavePacket(name, tab, extraTags, compounder));
if (saveMode == 1 && folder != null) {
PacketClient.sendClient(new CloneSavePacket(name, folder, extraTags, compounder));
} else {
PacketClient.sendClient(new CloneSavePacket(name, tab, extraTags, compounder));
}
}
close();
@@ -159,7 +285,6 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall
displayGuiScreen(this);
}
@Override
public void save() {
}
@@ -171,6 +296,17 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall
displayGuiScreen(new GuiYesNo(this, "Warning", "You are about to overwrite a clone", 1));
else
confirmClicked(true, 0);
} else if (compound.hasKey("CloneFolders")) {
// Server folder list arrived
NBTTagList folderList = compound.getTagList("CloneFolders", 10);
serverFolderNames.clear();
for (int i = 0; i < folderList.tagCount(); i++) {
CloneFolder f = new CloneFolder();
f.readNBT(folderList.getCompoundTagAt(i));
serverFolderNames.add(f.name);
}
validateFolder();
initGui();
} else if (compound.hasKey("ShortTags")) {
NBTTagList validTags = compound.getTagList("ShortTags", 10);
tagMap.clear();
@@ -193,6 +329,12 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall
@Override
public void subGuiClosed(SubGuiInterface subgui) {
if (subgui instanceof SubGuiFolderSelect) {
SubGuiFolderSelect sel = (SubGuiFolderSelect) subgui;
if (sel.selectedFolder != null) {
folder = sel.selectedFolder;
}
}
initGui();
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,67 @@
package noppes.npcs.client.gui;
import net.minecraft.client.gui.GuiButton;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.GuiNpcLabel;
import noppes.npcs.client.gui.util.GuiNpcTextField;
import noppes.npcs.client.gui.util.SubGuiInterface;
import noppes.npcs.controllers.data.CloneFolder;
public class SubGuiCloneFolderName extends SubGuiInterface {
private String originalName;
public boolean cancelled = true;
private String folderName = "";
public SubGuiCloneFolderName(String existingName) {
this.originalName = existingName;
this.folderName = existingName;
xSize = 200;
ySize = 80;
this.closeOnEsc = true;
}
@Override
public void initGui() {
super.initGui();
String title = isRename() ? "Rename Folder" : "New Folder";
addLabel(new GuiNpcLabel(0, title, guiLeft + 5, guiTop + 5));
addTextField(new GuiNpcTextField(0, this, fontRendererObj, guiLeft + 5, guiTop + 20, 190, 20, folderName));
addButton(new GuiNpcButton(0, guiLeft + 5, guiTop + 50, 90, 20, "gui.done"));
addButton(new GuiNpcButton(1, guiLeft + 100, guiTop + 50, 90, 20, "gui.cancel"));
}
@Override
protected void actionPerformed(GuiButton guibutton) {
if (guibutton.id == 0) {
// Done
folderName = getTextField(0).getText().trim();
if (CloneFolder.isValidName(folderName)) {
cancelled = false;
close();
}
}
if (guibutton.id == 1) {
// Cancel
cancelled = true;
close();
}
}
public boolean isRename() {
return originalName != null && !originalName.isEmpty();
}
public String getFolderName() {
return folderName;
}
public String getOriginalName() {
return originalName;
}
@Override
public void save() {
}
}
@@ -0,0 +1,74 @@
package noppes.npcs.client.gui;
import net.minecraft.client.gui.GuiButton;
import noppes.npcs.client.gui.util.GuiCustomScroll;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.ICustomScrollListener;
import noppes.npcs.client.gui.util.SubGuiInterface;
import java.util.ArrayList;
import java.util.List;
public class SubGuiFolderSelect extends SubGuiInterface implements ICustomScrollListener {
public String selectedFolder;
private final List<String> folderList;
private GuiCustomScroll folderScroll;
public SubGuiFolderSelect(List<String> folders, String currentSelection) {
this.folderList = new ArrayList<>(folders);
this.selectedFolder = currentSelection;
setBackground("menubg.png");
xSize = 200;
ySize = 220;
closeOnEsc = true;
}
@Override
public void initGui() {
super.initGui();
folderScroll = new GuiCustomScroll(this, 0);
folderScroll.setSize(xSize - 8, ySize - 50);
folderScroll.guiLeft = guiLeft + 4;
folderScroll.guiTop = guiTop + 4;
folderScroll.setList(new ArrayList<>(folderList));
if (selectedFolder != null) {
folderScroll.setSelected(selectedFolder);
}
addScroll(folderScroll);
int btnW = (xSize - 12) / 2;
addButton(new GuiNpcButton(0, guiLeft + 4, guiTop + ySize - 44, btnW, 20, "gui.done"));
addButton(new GuiNpcButton(1, guiLeft + 4 + btnW + 4, guiTop + ySize - 44, btnW, 20, "gui.cancel"));
getButton(0).enabled = folderScroll.hasSelected();
}
@Override
public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) {
getButton(0).enabled = folderScroll.hasSelected();
}
@Override
public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) {
selectedFolder = selection;
close();
}
@Override
protected void actionPerformed(GuiButton guibutton) {
if (guibutton.id == 0) {
selectedFolder = folderScroll.getSelected();
close();
}
if (guibutton.id == 1) {
selectedFolder = null;
close();
}
}
@Override
public void save() {
}
}
@@ -0,0 +1,57 @@
package noppes.npcs.client.gui;
import net.minecraft.client.gui.GuiButton;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.GuiNpcLabel;
import noppes.npcs.client.gui.util.SubGuiInterface;
public class SubGuiMoveConfirm extends SubGuiInterface {
public boolean confirmed = false;
private final int count;
public final int destTab;
public final String destFolder;
public SubGuiMoveConfirm(int count, int destTab, String destFolder) {
this.count = count;
this.destTab = destTab;
this.destFolder = destFolder;
xSize = 220;
ySize = 90;
closeOnEsc = true;
}
@Override
public void initGui() {
super.initGui();
String line1 = "Move " + count + " clone" + (count != 1 ? "s" : "") + " to:";
addLabel(new GuiNpcLabel(0, line1, guiLeft + 10, guiTop + 10, 0xFFFFFF));
String dest;
if (destFolder != null) {
dest = "Folder: \"" + destFolder + "\"";
} else {
dest = "Tab: " + destTab;
}
addLabel(new GuiNpcLabel(1, " " + dest, guiLeft + 10, guiTop + 25, 0xFFFFFF));
addButton(new GuiNpcButton(0, guiLeft + 10, guiTop + 55, 95, 20, "gui.yes"));
addButton(new GuiNpcButton(1, guiLeft + 115, guiTop + 55, 95, 20, "gui.cancel"));
}
@Override
protected void actionPerformed(GuiButton guibutton) {
if (guibutton.id == 0) {
confirmed = true;
close();
}
if (guibutton.id == 1) {
close();
}
}
@Override
public void save() {
}
}
@@ -8,6 +8,7 @@ import kamkeel.npcs.network.packets.request.ability.AbilitiesGetAllPacket;
import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcGetPacket;
import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcSavePacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket;
import kamkeel.npcs.util.Register;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.resources.I18n;
import net.minecraft.nbt.NBTTagCompound;
@@ -27,11 +28,7 @@ import noppes.npcs.client.gui.util.SubGuiInterface;
import noppes.npcs.constants.EnumScrollData;
import noppes.npcs.entity.EntityNPCInterface;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.*;
/**
* GUI for managing NPC abilities.
@@ -44,6 +41,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
// All available ability types (typeId -> index)
private final HashMap<String, Integer> allAbilityTypes = new HashMap<>();
private final HashMap<String, Integer> filteredAbilityTypes = new HashMap<>();
// Display name to typeId mapping for the available types scroll
private final HashMap<String, String> displayNameToTypeId = new HashMap<>();
@@ -59,12 +57,35 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
private String search = "";
private int selectedAbilityIndex = -1;
public static int modIndex = 0;
public static ScrollType scrollType = ScrollType.CNPC;
public GuiNPCAbilities(EntityNPCInterface npc) {
super(npc);
PacketClient.sendClient(new AbilitiesGetAllPacket());
PacketClient.sendClient(new AbilitiesNpcGetPacket());
}
public enum ScrollType {
CNPC,
MODDED,
ALL;
@Override
public String toString() {
switch (this) {
case CNPC: return "CustomNPCs";
case MODDED:
if (Register.isEmpty("ability"))
return "modded";
return Register.REGISTERED_NAMESPACES.get("ability").get(modIndex);
case ALL: return "filter.all";
default: return name();
}
}
}
@Override
public void initGui() {
super.initGui();
@@ -94,13 +115,15 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
addLabel(new GuiNpcLabel(1, "ability.availableTypes", guiLeft + 5, y));
if (availableTypesScroll == null) {
availableTypesScroll = new GuiCustomScroll(this, 0);
availableTypesScroll.setSize(140, 130);
availableTypesScroll.setSize(140, 103);
}
availableTypesScroll.guiLeft = guiLeft + 5;
availableTypesScroll.guiTop = y + 12;
availableTypesScroll.setList(getFilteredTypeList());
addScroll(availableTypesScroll);
addButton(new GuiNpcButton(50, guiLeft + 5, y + 120, 140, 20, scrollType.toString()));
// Search bar for types
addTextField(new GuiNpcTextField(4, this, fontRendererObj, guiLeft + 5, y + 145, 140, 18, search));
@@ -275,6 +298,31 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
return;
}
if (id == 50) {
if (scrollType != ScrollType.MODDED) {
ScrollType[] values = ScrollType.values();
scrollType = values[(scrollType.ordinal() + 1) % values.length];
} else {
List<String> list = Register.REGISTERED_NAMESPACES.get("ability");
if (list != null && !list.isEmpty()) {
if (modIndex == list.size() - 1) {
scrollType = ScrollType.ALL;
} else {
modIndex = (modIndex + 1) % list.size();
}
} else {
modIndex = 0;
scrollType = ScrollType.ALL;
}
}
Map<String, Integer> dummyMap = new HashMap<>(allAbilityTypes);
filteredAbilityTypes.clear();
filteredAbilityTypes.putAll(getFilteredData(dummyMap));
}
initGui();
}
@@ -283,6 +331,8 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
if (type == EnumScrollData.ABILITY_TYPES) {
allAbilityTypes.clear();
allAbilityTypes.putAll(data);
filteredAbilityTypes.putAll(getFilteredData(allAbilityTypes));
if (availableTypesScroll != null) {
availableTypesScroll.setList(getFilteredTypeList());
}
@@ -341,10 +391,35 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
}
}
private HashMap<String, Integer> getFilteredData(Map<String, Integer> data) {
HashMap<String, Integer> filteredData = new HashMap<>();
if (scrollType == ScrollType.CNPC) {
data.entrySet().stream()
.filter(e -> e.getKey().startsWith("ability.cnpc."))
.forEach(e -> filteredData.put(e.getKey(), e.getValue()));
} else if (scrollType == ScrollType.MODDED) {
if (!Register.isEmpty("ability")) {
List<String> registerList = Register.REGISTERED_NAMESPACES.get("ability");
if (!registerList.isEmpty()) {
String namespace = registerList.get(modIndex);
data.entrySet().stream()
.filter(e -> e.getKey().startsWith("ability." + namespace + "."))
.forEach(e -> filteredData.put(e.getKey(), e.getValue()));
}
}
} else {
filteredData.putAll(data);
}
return filteredData;
}
private List<String> getFilteredTypeList() {
List<String> list = new ArrayList<>();
displayNameToTypeId.clear();
for (String typeId : allAbilityTypes.keySet()) {
for (String typeId : filteredAbilityTypes.keySet()) {
String displayName = I18n.format(typeId);
// Search matches either the display name or the typeId
if (search.isEmpty() || displayName.toLowerCase().contains(search) || typeId.toLowerCase().contains(search)) {
@@ -3,6 +3,7 @@ package noppes.npcs.client.gui.player;
import kamkeel.npcs.network.PacketClient;
import kamkeel.npcs.network.packets.player.GetTraderData;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ResourceLocation;
@@ -332,6 +333,22 @@ public class GuiNPCTrader extends GuiContainerNPCInterface implements IGuiData {
return sb.toString();
}
@Override
protected void handleMouseClick(Slot slot, int slotId, int mouseButton, int modifier) {
// Client-side balance prediction: immediately subtract currency cost on click
// so the displayed balance updates instantly without waiting for server response.
if (slotId >= 0 && slotId < 18 && mouseButton == 0) {
long cost = currencyCost[slotId];
if (cost > 0 && playerBalance >= cost) {
ItemStack sold = role.inventorySold.items.get(slotId);
if (sold != null && container.canBuy(slotId, player)) {
playerBalance -= cost;
}
}
}
super.handleMouseClick(slot, slotId, mouseButton, modifier);
}
@Override
public void save() {
}
@@ -367,6 +367,13 @@ public class GuiCustomScroll extends GuiScreen {
return this;
}
public void setSelectable(boolean selectable) {
this.selectable = selectable;
if (!selectable) {
hover = -1;
}
}
public boolean isMouseOver(int x, int y) {
return x >= this.guiLeft && x <= this.guiLeft + this.xSize && y >= this.guiTop && y <= this.guiTop + this.ySize;
}
@@ -1,5 +1,6 @@
package noppes.npcs.client.gui.util;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.util.StatCollector;
import noppes.npcs.client.gui.GuiNpcMobSpawner;
import noppes.npcs.controllers.data.Tag;
@@ -7,10 +8,10 @@ import noppes.npcs.controllers.data.Tag;
import java.util.UUID;
public class GuiCustomScrollCloner extends GuiCustomScroll {
private final GuiNpcMobSpawner parent;
private final IClonerGui parent;
public GuiCustomScrollCloner(GuiNpcMobSpawner parent, int id) {
super(parent, id);
public GuiCustomScrollCloner(IClonerGui parent, int id) {
super((GuiScreen) parent, id);
this.parent = parent;
}
@@ -38,24 +39,25 @@ public class GuiCustomScrollCloner extends GuiCustomScroll {
text += "...";
} else
text = displayString;
if ((multipleSelection && selectedList.contains(text)) || (!multipleSelection && selected == i)) {
int itemColor = this.colors.getOrDefault(list.get(i), 0xffffff);
if ((multipleSelection && selectedList.contains(list.get(i))) || (!multipleSelection && selected == i)) {
drawVerticalLine(j - 2, k - 4, k + 10, 0xffffffff);
drawVerticalLine(j + xSize - 18 + xOffset, k - 4, k + 10, 0xffffffff);
drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k - 3, 0xffffffff);
drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k + 10, 0xffffffff);
fontRendererObj.drawString(text, j, k, this.colors.getOrDefault(text, 0xffffff));
fontRendererObj.drawString(text, j, k, itemColor);
} else if (i == hover) {
fontRendererObj.drawString(text, j, k, 0x00ff00);
} else {
fontRendererObj.drawString(text, j, k, this.colors.getOrDefault(text, 0xffffff));
fontRendererObj.drawString(text, j, k, itemColor);
}
if ((this.parent.getShowingClones() == 0 || this.parent.getShowingClones() == 2) && this.parent.tagMap != null) {
if ((parent.getShowingClones() == 0 || parent.getShowingClones() == 2) && parent.getTagMap() != null) {
int tagStartX = j + fontRendererObj.getStringWidth(displayString) + 3;
if (GuiNpcMobSpawner.displayTags == 0 || GuiNpcMobSpawner.displayTags == 1) {
if (this.parent.tagMap.hasClone(list.get(i))) {
for (UUID tagUUID : this.parent.tagMap.getUUIDsList(list.get(i))) {
Tag tag = this.parent.tags.get(tagUUID);
if (parent.getTagMap().hasClone(list.get(i))) {
for (UUID tagUUID : parent.getTagMap().getUUIDsList(list.get(i))) {
Tag tag = parent.getTags().get(tagUUID);
if (tag != null) {
if (GuiNpcMobSpawner.displayTags == 1 || !tag.getIsHidden()) {
fontRendererObj.drawString("[" + tag.name + "]", tagStartX, k, tag.color);
@@ -0,0 +1,108 @@
package noppes.npcs.client.gui.util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StatCollector;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.List;
public class GuiCustomScrollIcons extends GuiCustomScroll {
public static final int ICON_NONE = 0;
public static final int ICON_TAB = 1;
public static final int ICON_FOLDER = 2;
private static final ResourceLocation TAB_TEXTURE = new ResourceLocation("customnpcs", "textures/gui/cloner/tab.png");
private static final ResourceLocation FOLDER_TEXTURE = new ResourceLocation("customnpcs", "textures/gui/cloner/folder.png");
private static final int ICON_RENDER_SIZE = 10;
private static final int ICON_PADDING = 14;
private List<Integer> iconTypes = new ArrayList<>();
public GuiCustomScrollIcons(GuiScreen parent, int id) {
super(parent, id);
}
public void setListWithIcons(List<String> names, List<Integer> icons) {
this.setUnsortedList(names);
this.iconTypes = new ArrayList<>(icons);
}
@Override
protected void drawItems() {
Minecraft mc = Minecraft.getMinecraft();
for (int i = 0; i < list.size(); i++) {
int j = 4;
int k = (14 * i + 4) - scrollY;
if (k >= 4 && k + 12 < ySize) {
int xOffset = scrollHeight < ySize - 8 ? 0 : 10;
int iconType = (i < iconTypes.size()) ? iconTypes.get(i) : ICON_NONE;
int textX = j;
if (iconType != ICON_NONE) {
textX = j + ICON_PADDING;
}
// Draw icon texture
if (iconType == ICON_FOLDER || iconType == ICON_TAB) {
ResourceLocation tex = (iconType == ICON_FOLDER) ? FOLDER_TEXTURE : TAB_TEXTURE;
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
mc.getTextureManager().bindTexture(tex);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
drawFullTexture(j, k - 1, ICON_RENDER_SIZE, ICON_RENDER_SIZE);
GL11.glDisable(GL11.GL_BLEND);
}
// Draw text
String displayString = StatCollector.translateToLocal(list.get(i));
String text = "";
float maxWidth = (xSize + xOffset - 8 - (iconType != ICON_NONE ? ICON_PADDING : 0)) * 0.8f;
if (fontRendererObj.getStringWidth(displayString) > maxWidth) {
for (int h = 0; h < displayString.length(); h++) {
char c = displayString.charAt(h);
text += c;
if (fontRendererObj.getStringWidth(text) > maxWidth)
break;
}
if (displayString.length() > text.length())
text += "...";
} else {
text = displayString;
}
int itemColor = this.colors.getOrDefault(list.get(i), 0xffffff);
if ((multipleSelection && selectedList.contains(list.get(i))) || (!multipleSelection && selected == i)) {
drawVerticalLine(j - 2, k - 4, k + 10, 0xffffffff);
drawVerticalLine(j + xSize - 18 + xOffset, k - 4, k + 10, 0xffffffff);
drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k - 3, 0xffffffff);
drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k + 10, 0xffffffff);
fontRendererObj.drawString(text, textX, k, itemColor);
} else if (i == hover) {
fontRendererObj.drawString(text, textX, k, 0x00ff00);
} else {
fontRendererObj.drawString(text, textX, k, itemColor);
}
}
}
}
/**
* Draw a full texture (UV 0,0 to 1,1) scaled to the given width/height.
* Works with any texture size (not just 256x256).
*/
private void drawFullTexture(int x, int y, int w, int h) {
Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
tessellator.addVertexWithUV(x, y + h, 0, 0, 1);
tessellator.addVertexWithUV(x + w, y + h, 0, 1, 1);
tessellator.addVertexWithUV(x + w, y, 0, 1, 0);
tessellator.addVertexWithUV(x, y, 0, 0, 0);
tessellator.draw();
}
}
@@ -0,0 +1,206 @@
package noppes.npcs.client.gui.util;
/**
* Abstract base class for fullscreen directory-browsing GUIs.
* Provides a configurable three-panel layout (left nav, center content, right actions)
* with percentage-based sizing, panel backgrounds/borders, and an optional resizable divider.
*
* Subclasses override abstract hooks to populate each panel region.
* Designed for reuse across Cloner, Quest Manager, Dialog Manager, etc.
*/
public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScrollListener, ISubGuiListener {
// ===== LAYOUT CONFIG (subclasses may override in constructor) =====
protected float leftPanelPercent = 0.20f;
protected float rightPanelPercent = 0.10f;
protected int minLeftPanelW = 80;
protected int minRightPanelW = 40;
protected int minCenterW = 100;
// ===== LAYOUT CONSTANTS =====
protected int pad = 10;
protected int topBarH = 24;
protected int gap = 4;
protected int btnH = 20;
// ===== PANEL COLORS =====
protected int panelBg = 0xC0101010;
protected int panelBorder = 0xFF333333;
protected int topBarBg = 0xC0181818;
// ===== COMPUTED LAYOUT (populated by computeLayout) =====
protected int originX, originY, usableW, usableH;
protected int leftPanelW, rightPanelW;
protected int contentX, contentY, contentW, contentH;
protected int rightX;
// ===== OPTIONAL RESIZABLE DIVIDER (between left and center) =====
protected boolean enableDivider = false;
protected int dividerWidth = 5;
protected int dividerLineHeight = 20;
protected int minDividerPanelW = 50;
private int dividerOffset;
private boolean isDragging = false;
private int dragStartX;
// ===== CONSTRUCTOR =====
public GuiDirectory() {
super();
closeOnEsc = true;
drawDefaultBackground = true;
}
// ===== LAYOUT COMPUTATION =====
protected void computeLayout() {
originX = pad;
originY = pad;
usableW = width - 2 * pad;
usableH = height - 2 * pad;
leftPanelW = Math.max(minLeftPanelW, (int)(usableW * leftPanelPercent));
rightPanelW = Math.max(minRightPanelW, (int)(usableW * rightPanelPercent));
if (rightPanelPercent <= 0) {
rightPanelW = 0;
}
int gapCount = rightPanelW > 0 ? 3 : 2;
contentW = usableW - leftPanelW - rightPanelW - gapCount * gap;
if (contentW < minCenterW) {
contentW = minCenterW;
}
contentX = originX + leftPanelW + gap;
contentY = originY + topBarH + gap;
contentH = usableH - topBarH - gap;
rightX = contentX + contentW + gap;
if (enableDivider && dividerOffset == 0) {
dividerOffset = leftPanelW;
}
}
// ===== INIT GUI =====
@Override
public void initGui() {
super.initGui();
computeLayout();
initTopBar(originY + 2);
initLeftPanel();
initCenterPanel();
if (rightPanelW > 0) {
initRightPanel(contentY + 4);
}
}
// ===== DRAWING =====
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
drawTopBar();
drawPanels();
if (enableDivider) {
drawDivider(mouseX, mouseY);
}
super.drawScreen(mouseX, mouseY, partialTicks);
drawOverlay(mouseX, mouseY, partialTicks);
}
protected void drawTopBar() {
GuiUtil.drawRectD(originX, originY, originX + usableW, originY + topBarH, topBarBg);
}
protected void drawPanels() {
// Left nav panel border
GuiUtil.drawRectD(originX - 1, contentY - 1, originX + leftPanelW + 1, originY + usableH + 1, panelBorder);
// Right action panel border (if present)
if (rightPanelW > 0) {
GuiUtil.drawRectD(rightX - 1, contentY - 1, rightX + rightPanelW + 1, originY + usableH + 1, panelBorder);
}
}
protected void drawDivider(int mouseX, int mouseY) {
if (!enableDivider) return;
int divX = originX + dividerOffset;
int regionTop = contentY;
int regionHeight = contentH;
int handleTop = regionTop + (regionHeight - dividerLineHeight) / 2;
drawRect(divX + 1, handleTop, divX + dividerWidth - 1, handleTop + dividerLineHeight, 0xFF707070);
}
/** Override for extra drawing after super.drawScreen (e.g. status text) */
protected void drawOverlay(int mouseX, int mouseY, float partialTicks) {
}
// ===== DIVIDER MOUSE HANDLING =====
@Override
public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
if (enableDivider && !hasSubGui()) {
int divX = originX + dividerOffset;
int regionTop = contentY;
int handleTop = regionTop + (contentH - dividerLineHeight) / 2;
int handleBottom = handleTop + dividerLineHeight;
if (mouseX >= divX && mouseX <= divX + dividerWidth &&
mouseY >= handleTop && mouseY <= handleBottom) {
isDragging = true;
resizingActive = true;
dragStartX = mouseX;
return;
}
}
super.mouseClicked(mouseX, mouseY, mouseButton);
}
@Override
public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
if (isDragging) {
int dx = mouseX - dragStartX;
dragStartX = mouseX;
dividerOffset += dx;
int maxOffset = (usableW - (rightPanelW > 0 ? rightPanelW + gap : 0)) - dividerWidth - minDividerPanelW;
dividerOffset = Math.max(minDividerPanelW, Math.min(dividerOffset, maxOffset));
onDividerMoved(dividerOffset);
return;
}
super.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick);
}
@Override
protected void mouseMovedOrUp(int mouseX, int mouseY, int state) {
if (isDragging) {
isDragging = false;
resizingActive = false;
return;
}
super.mouseMovedOrUp(mouseX, mouseY, state);
}
/** Called when divider is dragged. Override to update scroll sizes. */
protected void onDividerMoved(int newOffset) {
}
// Accessors for divider offset
protected int getDividerOffset() {
return dividerOffset;
}
protected void setDividerOffset(int offset) {
this.dividerOffset = offset;
}
// ===== ABSTRACT HOOKS (subclass must implement) =====
/** Populate top bar buttons. topBtnY is the y-coordinate for the button row. */
protected abstract void initTopBar(int topBtnY);
/** Populate the left navigation panel (scrolls, search fields, buttons). */
protected abstract void initLeftPanel();
/** Populate the center content panel (main scroll list). */
protected abstract void initCenterPanel();
/** Populate the right action panel. startY is the first button y-coordinate. */
protected abstract void initRightPanel(int startY);
}
@@ -15,6 +15,7 @@ public class GuiMenuSideButton extends GuiNpcButton {
public boolean active;
public boolean rightSided;
public int textColor = -1;
public ItemStack renderStack;
public ResourceLocation renderResource;
public int renderIconPosX = 0;
@@ -96,13 +97,15 @@ public class GuiMenuSideButton extends GuiNpcButton {
text += "...";
} else
text = displayString;
if (active) {
drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xffffa0);
} else if (field_146123_n) {
drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xffffa0);
int color;
if (textColor != -1) {
color = textColor;
} else if (active || field_146123_n) {
color = 0xffffa0;
} else {
drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xe0e0e0);
color = 0xe0e0e0;
}
drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, color);
}
@Override
@@ -0,0 +1,13 @@
package noppes.npcs.client.gui.util;
import noppes.npcs.controllers.data.Tag;
import noppes.npcs.controllers.data.TagMap;
import java.util.HashMap;
import java.util.UUID;
public interface IClonerGui {
int getShowingClones();
HashMap<UUID, Tag> getTags();
TagMap getTagMap();
}
@@ -127,15 +127,11 @@ public class ContainerNPCTrader extends ContainerNpcInterface {
givePlayer(soldItem, entityplayer);
role.addPurchase(i, entityplayer.getDisplayName());
// Sync updated data after purchase
// For per-player stocks: sync only to the purchasing player
// For shared stocks: consumeStock() handles sync (market-wide or local)
// Always sync the purchasing player's data (balance + stock) after trade.
// For shared stocks, consumeStock() syncs stock to other viewers,
// but the purchasing player's balance still needs an explicit sync.
if (entityplayer instanceof EntityPlayerMP) {
if (role.stock.perPlayer) {
// Per-player: only sync to purchasing player (their own stock/balance)
role.syncToPlayer((EntityPlayerMP) entityplayer);
}
// Shared stock sync is already handled in consumeStock()
role.syncToPlayer((EntityPlayerMP) entityplayer);
}
return soldItem;
@@ -15,6 +15,7 @@ import noppes.npcs.api.IPos;
import noppes.npcs.api.IWorld;
import noppes.npcs.api.entity.IEntity;
import noppes.npcs.api.handler.ICloneHandler;
import noppes.npcs.controllers.data.CloneFolder;
import noppes.npcs.controllers.data.TagMap;
import noppes.npcs.scripted.CustomNPCsException;
import noppes.npcs.scripted.NpcAPI;
@@ -27,6 +28,7 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -34,8 +36,11 @@ import java.util.UUID;
public class ServerCloneController implements ICloneHandler {
public static ServerCloneController Instance;
protected Map<String, CloneFolder> folders = new LinkedHashMap<>();
public ServerCloneController() {
loadClones();
loadFolders();
}
private void loadClones() {
@@ -103,6 +108,8 @@ public class ServerCloneController implements ICloneHandler {
return clones;
}
// ==================== Tab-Based Clone Operations ====================
public NBTTagCompound getCloneData(ICommandSender player, String name, int tab) {
File file = new File(new File(getDir(), tab + ""), name + ".json");
if (!file.exists()) {
@@ -194,6 +201,250 @@ public class ServerCloneController implements ICloneHandler {
return name;
}
// ==================== Folder Registry ====================
public void loadFolders() {
folders.clear();
File dir = getDir();
File[] files = dir.listFiles();
if (files != null) {
List<File> folderDirs = new ArrayList<>();
for (File f : files) {
if (!f.isDirectory()) continue;
String name = f.getName();
if (name.startsWith("___")) continue;
try {
Integer.parseInt(name);
continue;
} catch (NumberFormatException ignored) {
}
folderDirs.add(f);
}
folderDirs.sort(Comparator.comparing(File::getName));
for (File f : folderDirs) {
CloneFolder folder = new CloneFolder(f.getName());
folder.createdDate = f.lastModified();
folders.put(f.getName(), folder);
}
}
}
public List<CloneFolder> getFolderList() {
return new ArrayList<>(folders.values());
}
public List<String> getFolderNames() {
return new ArrayList<>(folders.keySet());
}
public boolean hasFolder(String name) {
return folders.containsKey(name);
}
// ==================== Folder CRUD ====================
public CloneFolder createFolder(String name) {
if (!CloneFolder.isValidName(name) || folders.containsKey(name)) {
return null;
}
CloneFolder folder = new CloneFolder(name);
File dir = getFolderDir(name);
if (!dir.exists()) {
dir.mkdir();
}
folders.put(name, folder);
return folder;
}
public boolean renameFolder(String oldName, String newName) {
if (!folders.containsKey(oldName) || !CloneFolder.isValidName(newName) || folders.containsKey(newName)) {
return false;
}
CloneFolder folder = folders.get(oldName);
File oldDir = getFolderDir(oldName);
File newDir = new File(getDir(), newName);
if (!oldDir.renameTo(newDir)) {
return false;
}
folders.remove(oldName);
folder.name = newName;
folders.put(newName, folder);
return true;
}
public boolean deleteFolder(String name) {
if (!folders.containsKey(name)) {
return false;
}
List<String> clones = getClones(name);
if (!clones.isEmpty()) {
return false;
}
File dir = getFolderDir(name);
if (dir.exists()) {
File tagMapFile = new File(dir, "___tagmap.dat");
if (tagMapFile.exists()) tagMapFile.delete();
File tagMapOld = new File(dir, "___tagmap.dat_old");
if (tagMapOld.exists()) tagMapOld.delete();
File tagMapNew = new File(dir, "___tagmap.dat_new");
if (tagMapNew.exists()) tagMapNew.delete();
dir.delete();
}
folders.remove(name);
return true;
}
// ==================== Folder-Based Clone Operations ====================
public File getFolderDir(String folderName) {
return new File(getDir(), folderName);
}
public List<String> getClones(String folderName) {
List<String> list = new ArrayList<>();
File dir = getFolderDir(folderName);
if (!dir.exists() || !dir.isDirectory())
return list;
for (String file : dir.list()) {
if (file.endsWith(".json"))
list.add(file.substring(0, file.length() - 5));
}
return list;
}
public List<String> getClonesDate(String folderName) {
List<String> list = new ArrayList<>();
File dir = getFolderDir(folderName);
if (!dir.exists() || !dir.isDirectory())
return list;
File[] files = dir.listFiles();
if (files == null) return list;
Arrays.sort(files, new Comparator<File>() {
public int compare(File f1, File f2) {
return Long.compare(f1.lastModified(), f2.lastModified());
}
});
for (File file : files) {
String fileName = file.getName();
if (fileName.endsWith(".json"))
list.add(fileName.substring(0, fileName.length() - 5));
}
return list;
}
public NBTTagCompound getCloneData(ICommandSender player, String name, String folderName) {
File file = new File(getFolderDir(folderName), name + ".json");
if (!file.exists()) {
if (player != null)
player.addChatMessage(new ChatComponentText("Could not find clone file"));
return null;
}
try {
return NBTJsonUtil.LoadFile(file);
} catch (Exception e) {
LogWriter.error("Error loading: " + file.getAbsolutePath(), e);
if (player != null)
player.addChatMessage(new ChatComponentText(e.getMessage()));
}
return null;
}
public void saveClone(String folderName, String name, NBTTagCompound compound) {
try {
File dir = getFolderDir(folderName);
if (!dir.exists()) dir.mkdir();
String filename = name + ".json";
File file = new File(dir, filename + "_new");
File file2 = new File(dir, filename);
NBTJsonUtil.SaveFile(file, compound);
addToTagMap(compound, name, folderName);
if (file2.exists()) {
file2.delete();
}
file.renameTo(file2);
} catch (Exception e) {
LogWriter.except(e);
}
}
public boolean removeClone(String name, String folderName) {
File file = new File(getFolderDir(folderName), name + ".json");
if (!file.exists())
return false;
file.delete();
removeFromTagMap(name, folderName);
return true;
}
public String addClone(NBTTagCompound nbttagcompound, String name, String folderName) {
cleanTags(nbttagcompound);
saveClone(folderName, name, nbttagcompound);
return name;
}
public String addClone(NBTTagCompound nbttagcompound, String name, String folderName, NBTTagCompound tempTags) {
cleanTagList(nbttagcompound, tempTags);
cleanTags(nbttagcompound);
saveClone(folderName, name, nbttagcompound);
return name;
}
// ==================== Move Operations ====================
public boolean moveClone(String cloneName, int fromTab, String toFolder) {
NBTTagCompound data = getCloneData(null, cloneName, fromTab);
if (data == null) return false;
HashSet<UUID> tagUUIDs = getTagUUIDsFromTab(cloneName, fromTab);
saveClone(toFolder, cloneName, data);
setTagUUIDsForFolder(cloneName, toFolder, tagUUIDs);
removeClone(cloneName, fromTab);
return true;
}
public boolean moveClone(String cloneName, String fromFolder, int toTab) {
NBTTagCompound data = getCloneData(null, cloneName, fromFolder);
if (data == null) return false;
HashSet<UUID> tagUUIDs = getTagUUIDsFromFolder(cloneName, fromFolder);
saveClone(toTab, cloneName, data);
setTagUUIDsForTab(cloneName, toTab, tagUUIDs);
removeClone(cloneName, fromFolder);
return true;
}
public boolean moveClone(String cloneName, String fromFolder, String toFolder) {
NBTTagCompound data = getCloneData(null, cloneName, fromFolder);
if (data == null) return false;
HashSet<UUID> tagUUIDs = getTagUUIDsFromFolder(cloneName, fromFolder);
saveClone(toFolder, cloneName, data);
setTagUUIDsForFolder(cloneName, toFolder, tagUUIDs);
removeClone(cloneName, fromFolder);
return true;
}
public boolean moveClone(String cloneName, int fromTab, int toTab) {
NBTTagCompound data = getCloneData(null, cloneName, fromTab);
if (data == null) return false;
HashSet<UUID> tagUUIDs = getTagUUIDsFromTab(cloneName, fromTab);
saveClone(toTab, cloneName, data);
setTagUUIDsForTab(cloneName, toTab, tagUUIDs);
removeClone(cloneName, fromTab);
return true;
}
// ==================== Tag Utilities ====================
public NBTTagCompound cleanTagList(NBTTagCompound nbttagcompound, NBTTagCompound tempTags) {
HashSet<UUID> tagUUIDs = new HashSet<UUID>();
if (nbttagcompound.hasKey("TagUUIDs")) {
@@ -243,6 +494,25 @@ public class ServerCloneController implements ICloneHandler {
return true;
}
public boolean addToTagMap(NBTTagCompound nbttagcompound, String name, String folderName) {
HashSet<UUID> tagUUIDs = new HashSet<>();
if (nbttagcompound.hasKey("TagUUIDs")) {
NBTTagList nbtTagList = nbttagcompound.getTagList("TagUUIDs", 8);
for (int i = 0; i < nbtTagList.tagCount(); i++) {
tagUUIDs.add(UUID.fromString(nbtTagList.getStringTagAt(i)));
}
}
TagMap tagMap = ServerTagMapController.Instance.getTagMap(folderName);
if (!tagUUIDs.isEmpty()) {
tagMap.putClone(name, tagUUIDs);
} else {
tagMap.removeClone(name);
}
ServerTagMapController.Instance.saveTagMap(tagMap);
return true;
}
public boolean removeFromTagMap(String name, int tab) {
TagMap tagMap = ServerTagMapController.Instance.getTagMap(tab);
if (tagMap.removeClone(name)) {
@@ -252,6 +522,41 @@ public class ServerCloneController implements ICloneHandler {
return false;
}
public boolean removeFromTagMap(String name, String folderName) {
TagMap tagMap = ServerTagMapController.Instance.getTagMap(folderName);
if (tagMap.removeClone(name)) {
ServerTagMapController.Instance.saveTagMap(tagMap);
return true;
}
return false;
}
protected HashSet<UUID> getTagUUIDsFromTab(String cloneName, int tab) {
TagMap tagMap = ServerTagMapController.Instance.getTagMap(tab);
HashSet<UUID> uuids = tagMap.getUUIDs(cloneName);
return uuids != null ? new HashSet<>(uuids) : new HashSet<>();
}
protected HashSet<UUID> getTagUUIDsFromFolder(String cloneName, String folderName) {
TagMap tagMap = ServerTagMapController.Instance.getTagMap(folderName);
HashSet<UUID> uuids = tagMap.getUUIDs(cloneName);
return uuids != null ? new HashSet<>(uuids) : new HashSet<>();
}
protected void setTagUUIDsForTab(String cloneName, int tab, HashSet<UUID> tagUUIDs) {
if (tagUUIDs == null || tagUUIDs.isEmpty()) return;
TagMap tagMap = ServerTagMapController.Instance.getTagMap(tab);
tagMap.putClone(cloneName, tagUUIDs);
ServerTagMapController.Instance.saveTagMap(tagMap);
}
protected void setTagUUIDsForFolder(String cloneName, String folderName, HashSet<UUID> tagUUIDs) {
if (tagUUIDs == null || tagUUIDs.isEmpty()) return;
TagMap tagMap = ServerTagMapController.Instance.getTagMap(folderName);
tagMap.putClone(cloneName, tagUUIDs);
ServerTagMapController.Instance.saveTagMap(tagMap);
}
public void cleanTags(NBTTagCompound nbttagcompound) {
if (nbttagcompound.hasKey("ItemGiverId"))
nbttagcompound.setInteger("ItemGiverId", 0);
@@ -288,6 +593,8 @@ public class ServerCloneController implements ICloneHandler {
}
}
// ==================== API Methods (ICloneHandler) ====================
public IEntity spawn(double x, double y, double z, int tab, String name, IWorld world, boolean ignoreProtection) {
NBTTagCompound compound = this.getCloneData((ICommandSender) null, name, tab);
if (compound == null) {
@@ -327,7 +634,7 @@ public class ServerCloneController implements ICloneHandler {
for (File file : dir.listFiles()) {
if (file.getName().endsWith(".json")) {
NBTTagCompound compound = NBTJsonUtil.LoadFile(file);
Instance.cleanTags(compound);
cleanTags(compound);
Entity entity = EntityList.createEntityFromNBT(compound, world.getMCWorld());
arrayList.add(entity == null ? null : NpcAPI.Instance().getIEntity(entity));
}
@@ -343,7 +650,7 @@ public class ServerCloneController implements ICloneHandler {
if (compound == null) {
throw new CustomNPCsException("Unknown clone tab:" + tab + " name:" + name, new Object[0]);
} else {
Instance.cleanTags(compound);
cleanTags(compound);
Entity entity = EntityList.createEntityFromNBT(compound, world.getMCWorld());
return entity == null ? null : NpcAPI.Instance().getIEntity(entity);
}
@@ -354,7 +661,6 @@ public class ServerCloneController implements ICloneHandler {
return compound != null;
}
public void set(int tab, String name, IEntity entity) {
NBTTagCompound compound = new NBTTagCompound();
if (!entity.getMCEntity().writeMountToNBT(compound))
@@ -367,4 +673,85 @@ public class ServerCloneController implements ICloneHandler {
public void remove(int tab, String name) {
this.removeClone(name, tab);
}
// --- Folder API Methods ---
public String[] getFolders() {
return getFolderNames().toArray(new String[0]);
}
public IEntity spawn(double x, double y, double z, String folderName, String name, IWorld world, boolean ignoreProtection) {
NBTTagCompound compound = getCloneData(null, name, folderName);
if (compound == null) {
throw new CustomNPCsException("Unknown clone folder:" + folderName + " name:" + name, new Object[0]);
}
Entity entity;
if (!ignoreProtection) {
entity = NoppesUtilServer.spawnCloneWithProtection(compound, (int) x, (int) y, (int) z, world.getMCWorld());
} else {
entity = NoppesUtilServer.spawnClone(compound, (int) x, (int) y, (int) z, world.getMCWorld());
}
return entity == null ? null : NpcAPI.Instance().getIEntity(entity);
}
public IEntity spawn(IPos pos, String folderName, String name, IWorld world, boolean ignoreProtection) {
return this.spawn(pos.getX(), pos.getY(), pos.getZ(), folderName, name, world, ignoreProtection);
}
public IEntity spawn(double x, double y, double z, String folderName, String name, IWorld world) {
return spawn(x, y, z, folderName, name, world, true);
}
public IEntity spawn(IPos pos, String folderName, String name, IWorld world) {
return this.spawn(pos.getX(), pos.getY(), pos.getZ(), folderName, name, world);
}
public IEntity[] getFolder(String folderName, IWorld world) {
File dir = getFolderDir(folderName);
if (!dir.exists() || !dir.isDirectory() || dir.listFiles() == null) {
return new IEntity[]{};
}
ArrayList<IEntity> arrayList = new ArrayList<>();
try {
for (File file : dir.listFiles()) {
if (file.getName().endsWith(".json")) {
NBTTagCompound compound = NBTJsonUtil.LoadFile(file);
cleanTags(compound);
Entity entity = EntityList.createEntityFromNBT(compound, world.getMCWorld());
arrayList.add(entity == null ? null : NpcAPI.Instance().getIEntity(entity));
}
}
} catch (Exception ignored) {
}
return arrayList.toArray(new IEntity[]{});
}
public IEntity get(String folderName, String name, IWorld world) {
NBTTagCompound compound = getCloneData(null, name, folderName);
if (compound == null) {
throw new CustomNPCsException("Unknown clone folder:" + folderName + " name:" + name, new Object[0]);
}
cleanTags(compound);
Entity entity = EntityList.createEntityFromNBT(compound, world.getMCWorld());
return entity == null ? null : NpcAPI.Instance().getIEntity(entity);
}
public boolean has(String folderName, String name) {
NBTTagCompound compound = getCloneData(null, name, folderName);
return compound != null;
}
public void set(String folderName, String name, IEntity entity) {
NBTTagCompound compound = new NBTTagCompound();
if (!entity.getMCEntity().writeMountToNBT(compound))
throw new CustomNPCsException("Cannot save dead entities", new Object[0]);
this.cleanTags(compound);
this.saveClone(folderName, name, compound);
}
public void remove(String folderName, String name) {
this.removeClone(name, folderName);
}
}
@@ -35,6 +35,13 @@ public class ServerTagMapController {
return dir;
}
public File getCloneFolderDir(String folderName) {
File dir = new File(getDir(), folderName);
if (!dir.exists())
dir.mkdir();
return dir;
}
public TagMap getTagMap(int tab) {
this.tagMap = new TagMap(tab);
try {
@@ -65,9 +72,33 @@ public class ServerTagMapController {
this.tagMap.readNBT(nbtCompound);
}
public TagMap getTagMap(String folderName) {
this.tagMap = new TagMap(folderName);
try {
File file = new File(getCloneFolderDir(folderName), "___tagmap.dat");
if (file.exists()) {
loadTagMapFile(file);
}
} catch (Exception e) {
try {
File file = new File(getCloneFolderDir(folderName), "___tagmap.dat_old");
if (file.exists()) {
loadTagMapFile(file);
}
} catch (Exception ignored) {
}
}
return this.tagMap;
}
public void saveTagMap(TagMap tagMap) {
try {
File saveDir = getCloneTabDir(tagMap.cloneTab);
File saveDir;
if (tagMap.cloneFolder != null) {
saveDir = getCloneFolderDir(tagMap.cloneFolder);
} else {
saveDir = getCloneTabDir(tagMap.cloneTab);
}
File file = new File(saveDir, "___tagmap.dat_new");
File file1 = new File(saveDir, "___tagmap.dat_old");
File file2 = new File(saveDir, "___tagmap.dat");
@@ -0,0 +1,61 @@
package noppes.npcs.controllers.data;
import net.minecraft.nbt.NBTTagCompound;
public class CloneFolder {
public String name;
public long createdDate;
public CloneFolder() {
this.name = "";
this.createdDate = System.currentTimeMillis();
}
public CloneFolder(String name) {
this.name = name;
this.createdDate = System.currentTimeMillis();
}
public void readNBT(NBTTagCompound compound) {
name = compound.getString("Name");
createdDate = compound.getLong("Created");
}
public NBTTagCompound writeNBT(NBTTagCompound compound) {
compound.setString("Name", name);
compound.setLong("Created", createdDate);
return compound;
}
public static boolean isValidName(String name) {
if (name == null || name.isEmpty()) {
return false;
}
if (name.length() > 32) {
return false;
}
if (!name.equals(name.trim())) {
return false;
}
if (name.equals(".") || name.equals("..")) {
return false;
}
if (name.startsWith("___")) {
return false;
}
try {
Integer.parseInt(name);
return false;
} catch (NumberFormatException ignored) {
}
for (char c : name.toCharArray()) {
if (c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|') {
return false;
}
if (c < 32) {
return false;
}
}
return true;
}
}
@@ -81,7 +81,7 @@ public class DataScript implements IScriptHandlerPacket {
public void readFromNBT(NBTTagCompound compound) {
scripts = readScript(compound.getTagList("ScriptsContainers", 10));
this.scriptLanguage = compound.getString("ScriptLanguage");
if (!ScriptController.Instance.languages.containsKey(scriptLanguage)) {
if (scriptLanguage == null || scriptLanguage.isEmpty()) {
if (!ScriptController.Instance.languages.isEmpty()) {
this.scriptLanguage = (String) ScriptController.Instance.languages.keySet().toArray()[0];
} else {
@@ -55,7 +55,7 @@ public abstract class MultiScriptHandler extends ScriptHandler implements IScrip
* Normalize the language to a valid one if the current language is not available.
*/
protected void normalizeLanguage() {
if (!ScriptController.Instance.languages.containsKey(this.scriptLanguage)) {
if (this.scriptLanguage == null || this.scriptLanguage.isEmpty()) {
if (!ScriptController.Instance.languages.isEmpty()) {
this.scriptLanguage = (String) ScriptController.Instance.languages.keySet().toArray()[0];
} else {
@@ -142,7 +142,7 @@ public abstract class SingleScriptHandler extends ScriptHandler {
* Normalize the language to a valid one if the current language is not available.
*/
protected void normalizeLanguage() {
if (!ScriptController.Instance.languages.containsKey(this.getLanguage())) {
if (this.getLanguage() == null || this.getLanguage().isEmpty()) {
if (!ScriptController.Instance.languages.isEmpty()) {
this.setLanguage((String) ScriptController.Instance.languages.keySet().toArray()[0]);
} else {
@@ -13,10 +13,18 @@ import java.util.UUID;
public class TagMap {
public int cloneTab;
public String cloneFolder;
public HashMap<String, HashSet<UUID>> tagMap;
public TagMap(int tab) {
this.cloneTab = tab;
this.cloneFolder = null;
this.tagMap = new HashMap<String, HashSet<UUID>>();
}
public TagMap(String folder) {
this.cloneTab = -1;
this.cloneFolder = folder;
this.tagMap = new HashMap<String, HashSet<UUID>>();
}
@@ -1693,7 +1693,7 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti
}
/**
* 1: walking, 2:interacting, 4:attacking, 8:killed
* 1: walking, 2:interacting, 4:attacking, 8:killed, 16:rotationLocked
*/
public boolean getBoolFlag(int id) {
return (dataWatcher.getWatchableObjectInt(15) & id) != 0;