mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 19:34:09 +00:00
Merge branch 'dev' into syntax-highlighter-unified
This commit is contained in:
commit
dabaab7675
39 files changed
+4371
-340
No files matched your search
@@ -51,6 +51,7 @@ import noppes.npcs.LogWriter;
|
||||
import noppes.npcs.api.ability.IChainedAbility;
|
||||
import noppes.npcs.api.handler.IAbilityHandler;
|
||||
import noppes.npcs.controllers.PlayerDataController;
|
||||
import noppes.npcs.controllers.CategoryManager;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import noppes.npcs.util.NBTJsonUtil;
|
||||
import kamkeel.npcs.util.FileNameHelper;
|
||||
@@ -88,6 +89,12 @@ public class AbilityController implements IAbilityHandler {
|
||||
private final Map<String, ChainedAbility> chainedAbilitiesById = new LinkedHashMap<>(); // UUID → ChainedAbility
|
||||
private int chainedAbilityRevision = 0;
|
||||
|
||||
// ── Category Managers ─────────────────────────────────────────────────
|
||||
public final CategoryManager customAbilityCategories = new CategoryManager();
|
||||
public final CategoryManager chainedAbilityCategories = new CategoryManager();
|
||||
private final Map<String, Integer> customAbilityCatMap = new HashMap<>(); // abilityName → catId
|
||||
private final Map<String, Integer> chainedAbilityCatMap = new HashMap<>(); // chainName → catId
|
||||
|
||||
// ── Effect Action Registry ──────────────────────────────────────────────
|
||||
private final Map<String, IEffectAction> effectActions = new LinkedHashMap<>();
|
||||
|
||||
@@ -222,51 +229,18 @@ public class AbilityController implements IAbilityHandler {
|
||||
customAbilities.clear();
|
||||
customAbilitiesById.clear();
|
||||
abilityScriptHandlers.clear();
|
||||
customAbilityCatMap.clear();
|
||||
|
||||
File dir = getDir();
|
||||
File[] files = dir.exists() ? dir.listFiles() : null;
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json")) continue;
|
||||
try {
|
||||
String filename = file.getName();
|
||||
String fileKey = filename.substring(0, filename.length() - 5);
|
||||
customAbilityCategories.loadCategories(dir, "chained");
|
||||
|
||||
NBTTagCompound nbt = NBTJsonUtil.LoadFile(file);
|
||||
Ability ability = fromNBT(nbt);
|
||||
if (ability == null) continue;
|
||||
// Load from root directory (Uncategorized)
|
||||
loadAbilitiesFromDir(dir, CategoryManager.UNCATEGORIZED_ID);
|
||||
|
||||
String name = FileNameHelper.sanitizeName(fileKey, "Ability");
|
||||
name = makeUniqueNameForLoad(customAbilities, dir, file, name, fileKey);
|
||||
|
||||
boolean dirty = false;
|
||||
if (!name.equals(fileKey)) {
|
||||
File renamedFile = new File(dir, name + ".json");
|
||||
if (file.renameTo(renamedFile)) {
|
||||
file = renamedFile;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
ability.setName(name);
|
||||
|
||||
String uuid = ability.getId();
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
ability.setId(uuid);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
NBTJsonUtil.SaveFile(file, ability.writeNBT(true));
|
||||
}
|
||||
|
||||
customAbilities.put(name, ability);
|
||||
customAbilitiesById.put(uuid, ability);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading custom ability: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
// Load from category subdirectories
|
||||
for (Map.Entry<Integer, noppes.npcs.controllers.data.Category> entry : customAbilityCategories.getCategories().entrySet()) {
|
||||
File catDir = new File(dir, entry.getValue().title);
|
||||
loadAbilitiesFromDir(catDir, entry.getKey());
|
||||
}
|
||||
|
||||
customAbilityRevision++;
|
||||
@@ -275,6 +249,55 @@ public class AbilityController implements IAbilityHandler {
|
||||
loadChainedAbilities();
|
||||
}
|
||||
|
||||
private void loadAbilitiesFromDir(File dir, int catId) {
|
||||
File[] files = dir.exists() ? dir.listFiles() : null;
|
||||
if (files == null) return;
|
||||
for (File file : files) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json")) continue;
|
||||
try {
|
||||
String filename = file.getName();
|
||||
String fileKey = filename.substring(0, filename.length() - 5);
|
||||
|
||||
NBTTagCompound nbt = NBTJsonUtil.LoadFile(file);
|
||||
Ability ability = fromNBT(nbt);
|
||||
if (ability == null) continue;
|
||||
|
||||
String name = FileNameHelper.sanitizeName(fileKey, "Ability");
|
||||
name = makeUniqueNameForLoad(customAbilities, dir, file, name, fileKey);
|
||||
|
||||
boolean dirty = false;
|
||||
if (!name.equals(fileKey)) {
|
||||
File renamedFile = new File(dir, name + ".json");
|
||||
if (file.renameTo(renamedFile)) {
|
||||
file = renamedFile;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
ability.setName(name);
|
||||
|
||||
String uuid = ability.getId();
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
ability.setId(uuid);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
NBTJsonUtil.SaveFile(file, ability.writeNBT(true));
|
||||
}
|
||||
|
||||
customAbilities.put(name, ability);
|
||||
customAbilitiesById.put(uuid, ability);
|
||||
if (catId > CategoryManager.UNCATEGORIZED_ID) {
|
||||
customAbilityCatMap.put(name, catId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading custom ability: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean nameFileExists(File dir, String name, File ignoredFile) {
|
||||
File existing = new File(dir, name + ".json");
|
||||
if (!existing.exists()) return false;
|
||||
@@ -307,6 +330,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
|
||||
String uuid = ability.getId();
|
||||
boolean isNew = (uuid == null || uuid.isEmpty());
|
||||
int catId = CategoryManager.UNCATEGORIZED_ID;
|
||||
|
||||
if (isNew) {
|
||||
// Generate UUID for new ability
|
||||
@@ -321,16 +345,23 @@ public class AbilityController implements IAbilityHandler {
|
||||
Ability existing = customAbilitiesById.get(uuid);
|
||||
if (existing != null) {
|
||||
String oldName = existing.getName();
|
||||
catId = customAbilityCatMap.getOrDefault(oldName, CategoryManager.UNCATEGORIZED_ID);
|
||||
name = makeUniqueNameForMap(customAbilities, name, oldName);
|
||||
ability.setName(name);
|
||||
|
||||
if (!oldName.equals(name)) {
|
||||
// Delete old file and remove old map entry
|
||||
File oldFile = new File(getDir(), oldName + ".json");
|
||||
File oldDir = getCustomAbilityDir(oldName);
|
||||
File oldFile = new File(oldDir, oldName + ".json");
|
||||
if (oldFile.exists()) {
|
||||
oldFile.delete();
|
||||
}
|
||||
customAbilities.remove(oldName);
|
||||
// Update catMap key
|
||||
customAbilityCatMap.remove(oldName);
|
||||
if (catId > CategoryManager.UNCATEGORIZED_ID) {
|
||||
customAbilityCatMap.put(name, catId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
name = makeUniqueNameForMap(customAbilities, name, null);
|
||||
@@ -338,7 +369,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
}
|
||||
|
||||
File dir = getDir();
|
||||
File dir = getCustomAbilityDir(name);
|
||||
File fileNew = new File(dir, name + ".json_new");
|
||||
File fileCurrent = new File(dir, name + ".json");
|
||||
|
||||
@@ -389,11 +420,12 @@ public class AbilityController implements IAbilityHandler {
|
||||
abilityScriptHandlers.remove(uuid);
|
||||
}
|
||||
|
||||
File dir = getDir();
|
||||
File dir = getCustomAbilityDir(name);
|
||||
File file = new File(dir, name + ".json");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
customAbilityCatMap.remove(name);
|
||||
|
||||
// Clean up online players' unlocked ability lists
|
||||
if (PlayerDataController.Instance != null) {
|
||||
@@ -465,6 +497,98 @@ public class AbilityController implements IAbilityHandler {
|
||||
return dir;
|
||||
}
|
||||
|
||||
private File getCustomAbilityDir(String name) {
|
||||
int catId = customAbilityCatMap.getOrDefault(name, CategoryManager.UNCATEGORIZED_ID);
|
||||
return customAbilityCategories.getCategoryDir(catId);
|
||||
}
|
||||
|
||||
private File getChainedAbilityDir(String name) {
|
||||
int catId = chainedAbilityCatMap.getOrDefault(name, CategoryManager.UNCATEGORIZED_ID);
|
||||
return chainedAbilityCategories.getCategoryDir(catId);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// CUSTOM ABILITY CATEGORY HELPERS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public Map<String, Integer> getCustomAbilityCategoryScrollData() {
|
||||
return customAbilityCategories.getCategoryScrollData();
|
||||
}
|
||||
|
||||
public Map<String, Integer> getCustomAbilityItemsByCategoryScrollData(int catId) {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
for (Map.Entry<String, Ability> entry : customAbilities.entrySet()) {
|
||||
int assignedCat = customAbilityCatMap.getOrDefault(entry.getKey(), CategoryManager.UNCATEGORIZED_ID);
|
||||
if (assignedCat == catId) {
|
||||
map.put(entry.getKey(), entry.getValue().getAllowedBy().ordinal());
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public void moveCustomAbilityToCategory(String name, int destCatId) {
|
||||
Ability ability = customAbilities.get(name);
|
||||
if (ability == null) return;
|
||||
|
||||
int oldCatId = customAbilityCatMap.getOrDefault(name, CategoryManager.UNCATEGORIZED_ID);
|
||||
if (oldCatId == destCatId) return;
|
||||
|
||||
File oldDir = customAbilityCategories.getCategoryDir(oldCatId);
|
||||
File newDir = customAbilityCategories.getCategoryDir(destCatId);
|
||||
if (!newDir.exists()) newDir.mkdirs();
|
||||
|
||||
File oldFile = new File(oldDir, name + ".json");
|
||||
File newFile = new File(newDir, name + ".json");
|
||||
if (oldFile.exists()) oldFile.renameTo(newFile);
|
||||
|
||||
if (destCatId == CategoryManager.UNCATEGORIZED_ID) {
|
||||
customAbilityCatMap.remove(name);
|
||||
} else {
|
||||
customAbilityCatMap.put(name, destCatId);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// CHAINED ABILITY CATEGORY HELPERS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public Map<String, Integer> getChainedAbilityCategoryScrollData() {
|
||||
return chainedAbilityCategories.getCategoryScrollData();
|
||||
}
|
||||
|
||||
public Map<String, Integer> getChainedAbilityItemsByCategoryScrollData(int catId) {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
for (Map.Entry<String, ChainedAbility> entry : chainedAbilities.entrySet()) {
|
||||
int assignedCat = chainedAbilityCatMap.getOrDefault(entry.getKey(), CategoryManager.UNCATEGORIZED_ID);
|
||||
if (assignedCat == catId) {
|
||||
map.put(entry.getKey(), entry.getValue().getAllowedBy().ordinal());
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public void moveChainedAbilityToCategory(String name, int destCatId) {
|
||||
ChainedAbility chain = chainedAbilities.get(name);
|
||||
if (chain == null) return;
|
||||
|
||||
int oldCatId = chainedAbilityCatMap.getOrDefault(name, CategoryManager.UNCATEGORIZED_ID);
|
||||
if (oldCatId == destCatId) return;
|
||||
|
||||
File oldDir = chainedAbilityCategories.getCategoryDir(oldCatId);
|
||||
File newDir = chainedAbilityCategories.getCategoryDir(destCatId);
|
||||
if (!newDir.exists()) newDir.mkdirs();
|
||||
|
||||
File oldFile = new File(oldDir, name + ".json");
|
||||
File newFile = new File(newDir, name + ".json");
|
||||
if (oldFile.exists()) oldFile.renameTo(newFile);
|
||||
|
||||
if (destCatId == CategoryManager.UNCATEGORIZED_ID) {
|
||||
chainedAbilityCatMap.remove(name);
|
||||
} else {
|
||||
chainedAbilityCatMap.put(name, destCatId);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// CHAINED ABILITIES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -473,57 +597,73 @@ public class AbilityController implements IAbilityHandler {
|
||||
chainedAbilities.clear();
|
||||
chainedAbilitiesById.clear();
|
||||
chainedAbilityScriptHandlers.clear();
|
||||
chainedAbilityCatMap.clear();
|
||||
|
||||
File dir = getChainedDir();
|
||||
File[] files = dir.exists() ? dir.listFiles() : null;
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json")) continue;
|
||||
try {
|
||||
String filename = file.getName();
|
||||
String fileKey = filename.substring(0, filename.length() - 5);
|
||||
chainedAbilityCategories.loadCategories(dir);
|
||||
|
||||
NBTTagCompound nbt = NBTJsonUtil.LoadFile(file);
|
||||
ChainedAbility chain = new ChainedAbility();
|
||||
chain.readNBT(nbt);
|
||||
// Load from root directory (Uncategorized)
|
||||
loadChainedFromDir(dir, CategoryManager.UNCATEGORIZED_ID);
|
||||
|
||||
String name = FileNameHelper.sanitizeName(fileKey, "Chain");
|
||||
name = makeUniqueNameForLoad(chainedAbilities, dir, file, name, fileKey);
|
||||
|
||||
boolean dirty = false;
|
||||
if (!name.equals(fileKey)) {
|
||||
File renamedFile = new File(dir, name + ".json");
|
||||
if (file.renameTo(renamedFile)) {
|
||||
file = renamedFile;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
chain.setName(name);
|
||||
|
||||
String uuid = chain.getId();
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
chain.setId(uuid);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
NBTJsonUtil.SaveFile(file, chain.writeNBT(true));
|
||||
}
|
||||
|
||||
chainedAbilities.put(name, chain);
|
||||
chainedAbilitiesById.put(uuid, chain);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading chained ability: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
// Load from category subdirectories
|
||||
for (Map.Entry<Integer, noppes.npcs.controllers.data.Category> entry : chainedAbilityCategories.getCategories().entrySet()) {
|
||||
File catDir = new File(dir, entry.getValue().title);
|
||||
loadChainedFromDir(catDir, entry.getKey());
|
||||
}
|
||||
|
||||
chainedAbilityRevision++;
|
||||
LogWriter.info("Loaded " + chainedAbilities.size() + " chained abilities");
|
||||
}
|
||||
|
||||
private void loadChainedFromDir(File dir, int catId) {
|
||||
File[] files = dir.exists() ? dir.listFiles() : null;
|
||||
if (files == null) return;
|
||||
for (File file : files) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json")) continue;
|
||||
try {
|
||||
String filename = file.getName();
|
||||
String fileKey = filename.substring(0, filename.length() - 5);
|
||||
|
||||
NBTTagCompound nbt = NBTJsonUtil.LoadFile(file);
|
||||
ChainedAbility chain = new ChainedAbility();
|
||||
chain.readNBT(nbt);
|
||||
|
||||
String name = FileNameHelper.sanitizeName(fileKey, "Chain");
|
||||
name = makeUniqueNameForLoad(chainedAbilities, dir, file, name, fileKey);
|
||||
|
||||
boolean dirty = false;
|
||||
if (!name.equals(fileKey)) {
|
||||
File renamedFile = new File(dir, name + ".json");
|
||||
if (file.renameTo(renamedFile)) {
|
||||
file = renamedFile;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
chain.setName(name);
|
||||
|
||||
String uuid = chain.getId();
|
||||
if (uuid == null || uuid.isEmpty()) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
chain.setId(uuid);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
NBTJsonUtil.SaveFile(file, chain.writeNBT(true));
|
||||
}
|
||||
|
||||
chainedAbilities.put(name, chain);
|
||||
chainedAbilitiesById.put(uuid, chain);
|
||||
if (catId > CategoryManager.UNCATEGORIZED_ID) {
|
||||
chainedAbilityCatMap.put(name, catId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading chained ability: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean saveChainedAbility(ChainedAbility chain) {
|
||||
if (chain == null) return false;
|
||||
|
||||
@@ -543,15 +683,21 @@ public class AbilityController implements IAbilityHandler {
|
||||
ChainedAbility existing = chainedAbilitiesById.get(uuid);
|
||||
if (existing != null) {
|
||||
String oldName = existing.getName();
|
||||
int catId = chainedAbilityCatMap.getOrDefault(oldName, CategoryManager.UNCATEGORIZED_ID);
|
||||
name = makeUniqueNameForMap(chainedAbilities, name, oldName);
|
||||
chain.setName(name);
|
||||
|
||||
if (!oldName.equals(name)) {
|
||||
File oldFile = new File(getChainedDir(), oldName + ".json");
|
||||
File oldDir = getChainedAbilityDir(oldName);
|
||||
File oldFile = new File(oldDir, oldName + ".json");
|
||||
if (oldFile.exists()) {
|
||||
oldFile.delete();
|
||||
}
|
||||
chainedAbilities.remove(oldName);
|
||||
chainedAbilityCatMap.remove(oldName);
|
||||
if (catId > CategoryManager.UNCATEGORIZED_ID) {
|
||||
chainedAbilityCatMap.put(name, catId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
name = makeUniqueNameForMap(chainedAbilities, name, null);
|
||||
@@ -559,7 +705,7 @@ public class AbilityController implements IAbilityHandler {
|
||||
}
|
||||
}
|
||||
|
||||
File dir = getChainedDir();
|
||||
File dir = getChainedAbilityDir(name);
|
||||
File fileNew = new File(dir, name + ".json_new");
|
||||
File fileCurrent = new File(dir, name + ".json");
|
||||
|
||||
@@ -609,11 +755,12 @@ public class AbilityController implements IAbilityHandler {
|
||||
chainedAbilityScriptHandlers.remove(uuid);
|
||||
}
|
||||
|
||||
File dir = getChainedDir();
|
||||
File dir = getChainedAbilityDir(name);
|
||||
File file = new File(dir, name + ".json");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
chainedAbilityCatMap.remove(name);
|
||||
|
||||
// Clean up online players' unlocked ability lists (chain keys use "chain:" prefix)
|
||||
if (PlayerDataController.Instance != null) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
import kamkeel.npcs.entity.EntityEnergyBarrier;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergyDome;
|
||||
import kamkeel.npcs.entity.EntityEnergyPanel;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
@@ -1415,6 +1416,9 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
previewEntityHandler.onEntitySpawned(entity);
|
||||
} else {
|
||||
entity.worldObj.spawnEntityInWorld(entity);
|
||||
if (!entity.worldObj.isRemote && entity instanceof EntityEnergyProjectile) {
|
||||
EventHooks.onEnergyProjectileFired((EntityEnergyProjectile) entity);
|
||||
}
|
||||
}
|
||||
if (burstEnabled && burstOverlap) {
|
||||
burstEntities.add(entity);
|
||||
@@ -2609,6 +2613,96 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a caster's movement in a given direction is blocked by a solid enemy barrier.
|
||||
* Tests a short segment from the caster's current position along the movement direction
|
||||
* against all active dome and panel barriers.
|
||||
*
|
||||
* @return true if a solid enemy barrier blocks the movement
|
||||
*/
|
||||
protected static boolean isMovementBlockedByBarrier(EntityLivingBase caster, double dirX, double dirZ, double speed) {
|
||||
if (caster.worldObj == null) return false;
|
||||
|
||||
double nextX = caster.posX + dirX * speed;
|
||||
double nextZ = caster.posZ + dirZ * speed;
|
||||
double eyeY = caster.posY + caster.getEyeHeight() * 0.5;
|
||||
|
||||
java.util.List<EntityEnergyBarrier> barriers = EntityEnergyBarrier.getActiveBarriers(caster.worldObj);
|
||||
for (EntityEnergyBarrier barrier : barriers) {
|
||||
if (barrier.isDead || barrier.isCharging()) continue;
|
||||
if (!barrier.getBarrierData().solid) continue;
|
||||
|
||||
// Skip barriers owned by or allied to the caster
|
||||
if (barrier.getOwnerEntityId() == caster.getEntityId()) continue;
|
||||
Entity barrierOwner = barrier.getOwnerEntity();
|
||||
if (barrierOwner instanceof EntityLivingBase) {
|
||||
if (AbilityTargetHelper.isAlly(caster, (EntityLivingBase) barrierOwner)) continue;
|
||||
}
|
||||
|
||||
if (barrier instanceof EntityEnergyDome) {
|
||||
EntityEnergyDome dome = (EntityEnergyDome) barrier;
|
||||
float radius = dome.getDomeRadius();
|
||||
|
||||
// Check if the movement segment crosses the dome boundary from outside
|
||||
double ocX = caster.posX - dome.posX;
|
||||
double ocY = eyeY - dome.posY;
|
||||
double ocZ = caster.posZ - dome.posZ;
|
||||
double originDistSq = ocX * ocX + ocY * ocY + ocZ * ocZ;
|
||||
if (originDistSq < (double) radius * radius) continue; // Inside dome, skip
|
||||
|
||||
double rdX = nextX - caster.posX;
|
||||
double rdZ = nextZ - caster.posZ;
|
||||
double a = rdX * rdX + rdZ * rdZ;
|
||||
double b = 2.0 * (ocX * rdX + ocZ * rdZ);
|
||||
double c = ocX * ocX + ocZ * ocZ - (double) radius * radius;
|
||||
double discriminant = b * b - 4.0 * a * c;
|
||||
if (discriminant < 0 || a < 1e-10) continue;
|
||||
double sqrtD = Math.sqrt(discriminant);
|
||||
double t1 = (-b - sqrtD) / (2.0 * a);
|
||||
if (t1 >= 0.0 && t1 <= 1.0) return true;
|
||||
} else if (barrier instanceof EntityEnergyPanel) {
|
||||
EntityEnergyPanel panel = (EntityEnergyPanel) barrier;
|
||||
float halfW = panel.getPanelData().getPanelWidth() * 0.5f;
|
||||
float halfH = panel.getPanelData().getPanelHeight() * 0.5f;
|
||||
float yawRad = (float) Math.toRadians(panel.getPanelYaw());
|
||||
double normalX = -Math.sin(yawRad);
|
||||
double normalZ = Math.cos(yawRad);
|
||||
double cos = Math.cos(yawRad);
|
||||
double sin = Math.sin(yawRad);
|
||||
|
||||
double relX = caster.posX - panel.posX;
|
||||
double relZ = caster.posZ - panel.posZ;
|
||||
double prevDist = relX * normalX + relZ * normalZ;
|
||||
|
||||
double relNX = nextX - panel.posX;
|
||||
double relNZ = nextZ - panel.posZ;
|
||||
double currDist = relNX * normalX + relNZ * normalZ;
|
||||
|
||||
// Check if segment crosses the panel plane
|
||||
if (prevDist * currDist > 0) continue;
|
||||
|
||||
double rayDirX = nextX - caster.posX;
|
||||
double rayDirZ = nextZ - caster.posZ;
|
||||
double denom = rayDirX * normalX + rayDirZ * normalZ;
|
||||
if (Math.abs(denom) < 1e-10) continue;
|
||||
|
||||
double t = -prevDist / denom;
|
||||
if (t < 0.0 || t > 1.0) continue;
|
||||
|
||||
double hitX = caster.posX + rayDirX * t;
|
||||
double hitZ = caster.posZ + rayDirZ * t;
|
||||
|
||||
double localRight = (hitX - panel.posX) * cos + (hitZ - panel.posZ) * sin;
|
||||
if (Math.abs(localRight) > halfW) continue;
|
||||
double localY = eyeY - panel.posY;
|
||||
if (Math.abs(localY) > halfH) continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// LINE-OF-SIGHT UTILITIES
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -202,13 +202,14 @@ public abstract class AbilityMovement extends Ability {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if movement direction is blocked by walls.
|
||||
* Check if movement direction is blocked by walls or solid barriers.
|
||||
*
|
||||
* @return true if blocked
|
||||
*/
|
||||
protected boolean checkBlocked(EntityLivingBase caster, float speed) {
|
||||
if (isPreview() || movementDirection == null) return false;
|
||||
return isMovementBlocked(caster, movementDirection.xCoord, movementDirection.zCoord, speed);
|
||||
return isMovementBlocked(caster, movementDirection.xCoord, movementDirection.zCoord, speed)
|
||||
|| isMovementBlockedByBarrier(caster, movementDirection.xCoord, movementDirection.zCoord, speed);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -427,6 +427,27 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
((EntityNPCInterface) caster).getNavigator().clearPathEntity();
|
||||
}
|
||||
|
||||
// Check if slam trajectory is blocked by a solid barrier
|
||||
if (!isPreview() && !caster.worldObj.isRemote) {
|
||||
double moveX = caster.motionX;
|
||||
double moveZ = caster.motionZ;
|
||||
double hSpeed = Math.sqrt(moveX * moveX + moveZ * moveZ);
|
||||
if (hSpeed > 0.01) {
|
||||
double normX = moveX / hSpeed;
|
||||
double normZ = moveZ / hSpeed;
|
||||
if (isMovementBlockedByBarrier(caster, normX, normZ, hSpeed)) {
|
||||
caster.motionX = 0;
|
||||
caster.motionZ = 0;
|
||||
if (caster instanceof EntityPlayerMP) {
|
||||
((EntityPlayerMP) caster).playerNetServerHandler.sendPacket(
|
||||
new S12PacketEntityVelocity(caster));
|
||||
} else {
|
||||
caster.velocityChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track if entity has risen above launch position.
|
||||
// This prevents premature landing detection for players, where
|
||||
// server-side caster.onGround remains true from stale C03 packets
|
||||
|
||||
@@ -440,7 +440,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
|
||||
/**
|
||||
* Steer beam toward player's look direction (Free Aim mode).
|
||||
* Target point is projected along look vector at the current head distance from origin.
|
||||
* Blends the velocity DIRECTION toward the look vector each tick, producing
|
||||
* a smooth arc. The turn radius scales naturally with speed (faster = wider arc).
|
||||
* <p>
|
||||
* Previous approach projected a target point at headDist along the look line,
|
||||
* which caused oscillation at high speeds (the position target kept shifting)
|
||||
* and spiraling at large distances (cross-track error grew faster than correction).
|
||||
*/
|
||||
private void updateFreeAim() {
|
||||
Entity owner = getOwnerEntity();
|
||||
@@ -449,39 +454,24 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
Vec3 look = getOwnerLookVector();
|
||||
if (look == null) return;
|
||||
|
||||
double headDist = Math.sqrt(headOffsetX * headOffsetX + headOffsetY * headOffsetY + headOffsetZ * headOffsetZ);
|
||||
if (headDist < 0.1) return;
|
||||
double speed = getSpeed();
|
||||
|
||||
// Target point along look vector at current head distance
|
||||
double targetX = startX + look.xCoord * headDist;
|
||||
double targetY = startY + look.yCoord * headDist;
|
||||
double targetZ = startZ + look.zCoord * headDist;
|
||||
|
||||
// Current head world position
|
||||
double headWorldX = startX + headOffsetX;
|
||||
double headWorldY = startY + headOffsetY;
|
||||
double headWorldZ = startZ + headOffsetZ;
|
||||
|
||||
double dx = targetX - headWorldX;
|
||||
double dy = targetY - headWorldY;
|
||||
double dz = targetZ - headWorldZ;
|
||||
double dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist < 0.01) return;
|
||||
|
||||
double desiredVX = (dx / dist) * getSpeed();
|
||||
double desiredVY = (dy / dist) * getSpeed();
|
||||
double desiredVZ = (dz / dist) * getSpeed();
|
||||
// Desired velocity = look direction at current speed
|
||||
double desiredVX = look.xCoord * speed;
|
||||
double desiredVY = look.yCoord * speed;
|
||||
double desiredVZ = look.zCoord * speed;
|
||||
|
||||
// Blend current velocity toward desired direction
|
||||
motionX += (desiredVX - motionX) * FREE_AIM_STRENGTH;
|
||||
motionY += (desiredVY - motionY) * FREE_AIM_STRENGTH;
|
||||
motionZ += (desiredVZ - motionZ) * FREE_AIM_STRENGTH;
|
||||
|
||||
// Renormalize to constant speed
|
||||
double vLen = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
|
||||
if (vLen > 0) {
|
||||
motionX = (motionX / vLen) * getSpeed();
|
||||
motionY = (motionY / vLen) * getSpeed();
|
||||
motionZ = (motionZ / vLen) * getSpeed();
|
||||
motionX = (motionX / vLen) * speed;
|
||||
motionY = (motionY / vLen) * speed;
|
||||
motionZ = (motionZ / vLen) * speed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,10 +225,26 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
dirZ = 0.0;
|
||||
}
|
||||
|
||||
// Position: use anchor if launchFromAnchor, otherwise use look vector position
|
||||
// Position: use anchor if launchFromAnchor, otherwise use look vector position.
|
||||
// When using an anchor point, recalculate direction from anchor toward the target
|
||||
// so the beam aims at the target rather than shooting straight from the offset position.
|
||||
if (anchorData.launchFromAnchor) {
|
||||
Vec3 anchorPos = AnchorPointHelper.calculateAnchorPosition(livingOwner, anchorData);
|
||||
setPosition(anchorPos.xCoord, anchorPos.yCoord, anchorPos.zCoord);
|
||||
|
||||
Entity targetEntity = getTargetEntity();
|
||||
if (targetEntity instanceof EntityLivingBase && targetEntity.isEntityAlive()) {
|
||||
EntityLivingBase target = (EntityLivingBase) targetEntity;
|
||||
double dx = target.posX - anchorPos.xCoord;
|
||||
double dy = (target.posY + target.getEyeHeight() - 0.4) - anchorPos.yCoord;
|
||||
double dz = target.posZ - anchorPos.zCoord;
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len > 0.0001) {
|
||||
dirX = dx / len;
|
||||
dirY = dy / len;
|
||||
dirZ = dz / len;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Vec3 direction = Vec3.createVectorHelper(dirX, dirY, dirZ);
|
||||
setLookVectorLaunchPosition(livingOwner, direction, false);
|
||||
@@ -250,16 +266,19 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
lastBlockHitZ = Integer.MIN_VALUE;
|
||||
lastExplosionTick = -100;
|
||||
|
||||
// Set initial direction: owner look vector (sweeping beam always follows look)
|
||||
Vec3 look = getOwnerLookVector();
|
||||
if (look != null) {
|
||||
dirX = look.xCoord;
|
||||
dirY = look.yCoord;
|
||||
dirZ = look.zCoord;
|
||||
} else if (!setDirectionTowardTarget(target, startX, startY, startZ)) {
|
||||
dirX = 1.0;
|
||||
dirY = 0.0;
|
||||
dirZ = 0.0;
|
||||
// Set initial direction: aim toward target first (like Orb/Beam),
|
||||
// fall back to owner look vector if no target is available.
|
||||
if (!setDirectionTowardTarget(target, startX, startY, startZ)) {
|
||||
Vec3 look = getOwnerLookVector();
|
||||
if (look != null) {
|
||||
dirX = look.xCoord;
|
||||
dirY = look.yCoord;
|
||||
dirZ = look.zCoord;
|
||||
} else {
|
||||
dirX = 1.0;
|
||||
dirY = 0.0;
|
||||
dirZ = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize end point at start (will expand from here)
|
||||
|
||||
@@ -576,6 +576,14 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
barrierImpactPauseTicks = 0;
|
||||
barrierImpactDestroyOnResume = false;
|
||||
|
||||
// Reset start position and lifetime so the reflected projectile gets a fresh
|
||||
// max-distance budget and lifetime. Without this, re-reflection fails because
|
||||
// the projectile expires from the original launch's distance/time limits.
|
||||
startX = posX;
|
||||
startY = posY;
|
||||
startZ = posZ;
|
||||
deathWorldTime = -1;
|
||||
|
||||
// Detach from source ability BEFORE changing ownership.
|
||||
// This lets the ability see the projectile as gone and free the caster
|
||||
// (e.g., release movement/rotation locks on the player).
|
||||
|
||||
@@ -92,6 +92,12 @@ import kamkeel.npcs.network.packets.request.ability.ChainedAbilityGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.ChainedAbilityRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.ChainedAbilitySavePacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.AbilityCategoryMovePacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryItemsRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryListRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryMoveItemPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategorySavePacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.CustomAbilityRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket;
|
||||
@@ -526,6 +532,14 @@ public class PacketHandler {
|
||||
REQUEST_PACKET.registerPacket(new ChainedAbilitySavePacket());
|
||||
REQUEST_PACKET.registerPacket(new ChainedAbilityRemovePacket());
|
||||
|
||||
// Category Packets
|
||||
REQUEST_PACKET.registerPacket(new CategorySavePacket());
|
||||
REQUEST_PACKET.registerPacket(new CategoryRemovePacket());
|
||||
REQUEST_PACKET.registerPacket(new CategoryMoveItemPacket());
|
||||
REQUEST_PACKET.registerPacket(new CategoryListRequestPacket());
|
||||
REQUEST_PACKET.registerPacket(new CategoryItemsRequestPacket());
|
||||
REQUEST_PACKET.registerPacket(new AbilityCategoryMovePacket());
|
||||
|
||||
// Other Packets
|
||||
REQUEST_PACKET.registerPacket(new IsGuiOpenInform());
|
||||
REQUEST_PACKET.registerPacket(new GuiRequestPacket());
|
||||
|
||||
@@ -216,4 +216,11 @@ public enum EnumRequestPacket {
|
||||
ManageAuctionAction,
|
||||
AbilityScript,
|
||||
ChainedAbilityScript,
|
||||
|
||||
CategorySave,
|
||||
CategoryRemove,
|
||||
CategoryMoveItem,
|
||||
CategoryListRequest,
|
||||
CategoryItemsRequest,
|
||||
AbilityCategoryMoveItem,
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package kamkeel.npcs.network.packets.request.category;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
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.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import noppes.npcs.constants.EnumCategoryType;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class AbilityCategoryMovePacket extends AbstractPacket {
|
||||
public static String packetName = "NPC|AbCatMove";
|
||||
|
||||
private int catType;
|
||||
private String name;
|
||||
private int catId;
|
||||
|
||||
public AbilityCategoryMovePacket(int catType, String name, int catId) {
|
||||
this.catType = catType;
|
||||
this.name = name;
|
||||
this.catId = catId;
|
||||
}
|
||||
|
||||
public AbilityCategoryMovePacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumRequestPacket.AbilityCategoryMoveItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.REQUEST_PACKET;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(catType);
|
||||
ByteBufUtils.writeString(out, name);
|
||||
out.writeInt(catId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP))
|
||||
return;
|
||||
|
||||
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
|
||||
return;
|
||||
|
||||
int type = in.readInt();
|
||||
String itemName = ByteBufUtils.readString(in);
|
||||
int cat = in.readInt();
|
||||
|
||||
switch (type) {
|
||||
case EnumCategoryType.ABILITY:
|
||||
AbilityController.Instance.moveCustomAbilityToCategory(itemName, cat);
|
||||
break;
|
||||
case EnumCategoryType.CHAINED_ABILITY:
|
||||
AbilityController.Instance.moveChainedAbilityToCategory(itemName, cat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package kamkeel.npcs.network.packets.request.category;
|
||||
|
||||
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.ScrollDataPacket;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import noppes.npcs.constants.EnumCategoryType;
|
||||
import noppes.npcs.constants.EnumScrollData;
|
||||
import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.CustomEffectController;
|
||||
import noppes.npcs.controllers.LinkedItemController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class CategoryItemsRequestPacket extends AbstractPacket {
|
||||
public static String packetName = "NPC|CatItems";
|
||||
|
||||
private int catType;
|
||||
private int catId;
|
||||
|
||||
public CategoryItemsRequestPacket(int catType, int catId) {
|
||||
this.catType = catType;
|
||||
this.catId = catId;
|
||||
}
|
||||
|
||||
public CategoryItemsRequestPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumRequestPacket.CategoryItemsRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.REQUEST_PACKET;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(catType);
|
||||
out.writeInt(catId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP))
|
||||
return;
|
||||
|
||||
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
|
||||
return;
|
||||
|
||||
int type = in.readInt();
|
||||
int categoryId = in.readInt();
|
||||
|
||||
Map<String, Integer> items = getItemsByCat(type, categoryId);
|
||||
ScrollDataPacket.sendScrollData((EntityPlayerMP) player, items, EnumScrollData.CATEGORY_GROUP);
|
||||
}
|
||||
|
||||
private static Map<String, Integer> getItemsByCat(int type, int catId) {
|
||||
switch (type) {
|
||||
case EnumCategoryType.EFFECT: return CustomEffectController.getInstance().getItemsByCategoryScrollData(catId);
|
||||
case EnumCategoryType.ANIMATION: return AnimationController.getInstance().getItemsByCategoryScrollData(catId);
|
||||
case EnumCategoryType.LINKED_ITEM: return LinkedItemController.getInstance().getItemsByCategoryScrollData(catId);
|
||||
case EnumCategoryType.ABILITY: return AbilityController.Instance.getCustomAbilityItemsByCategoryScrollData(catId);
|
||||
case EnumCategoryType.CHAINED_ABILITY: return AbilityController.Instance.getChainedAbilityItemsByCategoryScrollData(catId);
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package kamkeel.npcs.network.packets.request.category;
|
||||
|
||||
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.ScrollDataPacket;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import noppes.npcs.constants.EnumScrollData;
|
||||
import noppes.npcs.controllers.CategoryManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class CategoryListRequestPacket extends AbstractPacket {
|
||||
public static String packetName = "NPC|CatList";
|
||||
|
||||
private int catType;
|
||||
|
||||
public CategoryListRequestPacket(int catType) {
|
||||
this.catType = catType;
|
||||
}
|
||||
|
||||
public CategoryListRequestPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumRequestPacket.CategoryListRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.REQUEST_PACKET;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(catType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP))
|
||||
return;
|
||||
|
||||
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
|
||||
return;
|
||||
|
||||
int type = in.readInt();
|
||||
|
||||
CategoryManager cm = CategorySavePacket.getManager(type);
|
||||
if (cm == null)
|
||||
return;
|
||||
|
||||
ScrollDataPacket.sendScrollData((EntityPlayerMP) player, cm.getCategoryScrollData(), EnumScrollData.CATEGORY_LIST);
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package kamkeel.npcs.network.packets.request.category;
|
||||
|
||||
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 net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import noppes.npcs.constants.EnumCategoryType;
|
||||
import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.CustomEffectController;
|
||||
import noppes.npcs.controllers.LinkedItemController;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class CategoryMoveItemPacket extends AbstractPacket {
|
||||
public static String packetName = "NPC|CatMove";
|
||||
|
||||
private int catType;
|
||||
private int itemId;
|
||||
private int catId;
|
||||
|
||||
public CategoryMoveItemPacket(int catType, int itemId, int catId) {
|
||||
this.catType = catType;
|
||||
this.itemId = itemId;
|
||||
this.catId = catId;
|
||||
}
|
||||
|
||||
public CategoryMoveItemPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumRequestPacket.CategoryMoveItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.REQUEST_PACKET;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(catType);
|
||||
out.writeInt(itemId);
|
||||
out.writeInt(catId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP))
|
||||
return;
|
||||
|
||||
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
|
||||
return;
|
||||
|
||||
int type = in.readInt();
|
||||
int item = in.readInt();
|
||||
int cat = in.readInt();
|
||||
|
||||
switch (type) {
|
||||
case EnumCategoryType.EFFECT: CustomEffectController.getInstance().moveItemToCategory(item, cat); break;
|
||||
case EnumCategoryType.ANIMATION: AnimationController.getInstance().moveItemToCategory(item, cat); break;
|
||||
case EnumCategoryType.LINKED_ITEM: LinkedItemController.getInstance().moveItemToCategory(item, cat); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package kamkeel.npcs.network.packets.request.category;
|
||||
|
||||
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.ScrollDataPacket;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import noppes.npcs.constants.EnumScrollData;
|
||||
import noppes.npcs.controllers.CategoryManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class CategoryRemovePacket extends AbstractPacket {
|
||||
public static String packetName = "NPC|CatRemove";
|
||||
|
||||
private int catType;
|
||||
private int categoryId;
|
||||
|
||||
public CategoryRemovePacket(int catType, int categoryId) {
|
||||
this.catType = catType;
|
||||
this.categoryId = categoryId;
|
||||
}
|
||||
|
||||
public CategoryRemovePacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumRequestPacket.CategoryRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.REQUEST_PACKET;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(catType);
|
||||
out.writeInt(categoryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP))
|
||||
return;
|
||||
|
||||
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
|
||||
return;
|
||||
|
||||
int type = in.readInt();
|
||||
int catId = in.readInt();
|
||||
|
||||
CategoryManager cm = CategorySavePacket.getManager(type);
|
||||
if (cm == null)
|
||||
return;
|
||||
|
||||
cm.removeCategory(catId);
|
||||
CategorySavePacket.saveController(type);
|
||||
ScrollDataPacket.sendScrollData((EntityPlayerMP) player, cm.getCategoryScrollData(), EnumScrollData.CATEGORY_LIST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package kamkeel.npcs.network.packets.request.category;
|
||||
|
||||
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.ScrollDataPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import noppes.npcs.constants.EnumCategoryType;
|
||||
import noppes.npcs.constants.EnumScrollData;
|
||||
import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.CategoryManager;
|
||||
import noppes.npcs.controllers.CustomEffectController;
|
||||
import noppes.npcs.controllers.LinkedItemController;
|
||||
import noppes.npcs.controllers.data.Category;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class CategorySavePacket extends AbstractPacket {
|
||||
public static String packetName = "NPC|CatSave";
|
||||
|
||||
private int catType;
|
||||
private NBTTagCompound categoryNBT;
|
||||
|
||||
public CategorySavePacket(int catType, NBTTagCompound categoryNBT) {
|
||||
this.catType = catType;
|
||||
this.categoryNBT = categoryNBT;
|
||||
}
|
||||
|
||||
public CategorySavePacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumRequestPacket.CategorySave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.REQUEST_PACKET;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeInt(catType);
|
||||
ByteBufUtils.writeNBT(out, categoryNBT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
if (!(player instanceof EntityPlayerMP))
|
||||
return;
|
||||
|
||||
if (!PacketUtil.verifyItemPacket(packetName, EnumItemPacketType.WAND, player))
|
||||
return;
|
||||
|
||||
int type = in.readInt();
|
||||
NBTTagCompound compound = ByteBufUtils.readNBT(in);
|
||||
|
||||
CategoryManager cm = getManager(type);
|
||||
if (cm == null)
|
||||
return;
|
||||
|
||||
Category cat = new Category();
|
||||
cat.readNBT(compound);
|
||||
|
||||
if (cat.id <= 0) {
|
||||
cm.createCategory(cat.title);
|
||||
} else {
|
||||
cm.saveCategory(cat);
|
||||
}
|
||||
|
||||
saveController(type);
|
||||
ScrollDataPacket.sendScrollData((EntityPlayerMP) player, cm.getCategoryScrollData(), EnumScrollData.CATEGORY_LIST);
|
||||
}
|
||||
|
||||
public static CategoryManager getManager(int type) {
|
||||
switch (type) {
|
||||
case EnumCategoryType.EFFECT: return CustomEffectController.getInstance().categoryManager;
|
||||
case EnumCategoryType.ANIMATION: return AnimationController.getInstance().categoryManager;
|
||||
case EnumCategoryType.LINKED_ITEM: return LinkedItemController.getInstance().categoryManager;
|
||||
case EnumCategoryType.ABILITY: return AbilityController.Instance.customAbilityCategories;
|
||||
case EnumCategoryType.CHAINED_ABILITY: return AbilityController.Instance.chainedAbilityCategories;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void saveController(int type) {
|
||||
switch (type) {
|
||||
case EnumCategoryType.EFFECT: CustomEffectController.getInstance().saveEffectLoadMap(); break;
|
||||
case EnumCategoryType.ANIMATION: AnimationController.getInstance().saveAnimationMap(); break;
|
||||
case EnumCategoryType.LINKED_ITEM: LinkedItemController.getInstance().saveLinkedItemsMap(); break;
|
||||
case EnumCategoryType.ABILITY: break; // abilities save per-file, no bulk save needed
|
||||
case EnumCategoryType.CHAINED_ABILITY: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,6 +268,8 @@ public class ClientProxy extends CommonProxy {
|
||||
public static KeyBinding NPCButton;
|
||||
public static KeyBinding SpecialKey;
|
||||
public static KeyBinding AbilityHudKey;
|
||||
public static KeyBinding AbilityNextKey;
|
||||
public static KeyBinding AbilityPrevKey;
|
||||
public static final Random RAND = new Random();
|
||||
public static FontContainer Font;
|
||||
|
||||
@@ -353,6 +355,11 @@ public class ClientProxy extends CommonProxy {
|
||||
AbilityHudKey = new KeyBinding("key.customnpcs.abilityHudKey", Keyboard.KEY_LMENU, "key.categories.customnpc");
|
||||
ClientRegistry.registerKeyBinding(AbilityHudKey);
|
||||
|
||||
AbilityNextKey = new KeyBinding("key.customnpcs.abilityNext", Keyboard.KEY_NONE, "key.categories.customnpc");
|
||||
AbilityPrevKey = new KeyBinding("key.customnpcs.abilityPrev", Keyboard.KEY_NONE, "key.categories.customnpc");
|
||||
ClientRegistry.registerKeyBinding(AbilityNextKey);
|
||||
ClientRegistry.registerKeyBinding(AbilityPrevKey);
|
||||
|
||||
new PresetController(CustomNpcs.Dir);
|
||||
|
||||
if (ConfigMain.EnableUpdateChecker) {
|
||||
|
||||
@@ -60,15 +60,16 @@ public class ClientTickHandler {
|
||||
if (event.phase == Phase.START) {
|
||||
EntityPlayer player = mc.thePlayer;
|
||||
if (player != null) {
|
||||
boolean suppressMovement = mc.currentScreen == null && ClientAbilityState.shouldSuppressMovementInput();
|
||||
if (!suppressMovement && wasMovementSuppressed) {
|
||||
boolean shouldSuppress = ClientAbilityState.shouldSuppressMovementInput();
|
||||
boolean suppressInput = mc.currentScreen == null && shouldSuppress;
|
||||
if (!suppressInput && wasMovementSuppressed) {
|
||||
syncMovementKeyStates(mc);
|
||||
}
|
||||
wasMovementSuppressed = suppressMovement;
|
||||
wasMovementSuppressed = suppressInput;
|
||||
|
||||
// Suppress player input during ability-controlled phases.
|
||||
// Only suppress when no GUI screen is open (screens already capture input).
|
||||
if (suppressMovement) {
|
||||
// Suppress player keyboard input during ability-controlled phases.
|
||||
// Only suppress key binds when no GUI screen is open (screens already capture input).
|
||||
if (suppressInput) {
|
||||
// Unpress movement keybinds at the source BEFORE updatePlayerMoveState() reads them.
|
||||
KeyBinding.setKeyBindState(mc.gameSettings.keyBindForward.getKeyCode(), false);
|
||||
KeyBinding.setKeyBindState(mc.gameSettings.keyBindBack.getKeyCode(), false);
|
||||
@@ -77,13 +78,17 @@ public class ClientTickHandler {
|
||||
KeyBinding.setKeyBindState(mc.gameSettings.keyBindJump.getKeyCode(), false);
|
||||
KeyBinding.setKeyBindState(mc.gameSettings.keyBindSneak.getKeyCode(), false);
|
||||
KeyBinding.setKeyBindState(mc.gameSettings.keyBindSprint.getKeyCode(), false);
|
||||
}
|
||||
|
||||
// Zero movement input regardless of GUI state — the ability lock must
|
||||
// hold the player in place even when chat or other screens are open.
|
||||
if (shouldSuppress) {
|
||||
mc.thePlayer.movementInput.moveForward = 0;
|
||||
mc.thePlayer.movementInput.moveStrafe = 0;
|
||||
mc.thePlayer.movementInput.jump = false;
|
||||
mc.thePlayer.movementInput.sneak = false;
|
||||
}
|
||||
if (mc.currentScreen == null && ClientAbilityState.shouldLockRotation()) {
|
||||
if (ClientAbilityState.shouldLockRotation()) {
|
||||
mc.thePlayer.rotationYaw = ClientAbilityState.lockedYaw;
|
||||
mc.thePlayer.rotationPitch = ClientAbilityState.lockedPitch;
|
||||
mc.thePlayer.prevRotationYaw = ClientAbilityState.lockedYaw;
|
||||
@@ -107,8 +112,9 @@ public class ClientTickHandler {
|
||||
// EntityPlayerSP.onLivingUpdate() calls updatePlayerMoveState() which
|
||||
// overwrites Phase.START suppression with actual keyboard state, causing
|
||||
// client-predicted movement that fights the server lock. Undo it here.
|
||||
// Skip when a GUI screen is open (screens already capture all input).
|
||||
if (mc.currentScreen == null && ClientAbilityState.shouldSuppressMovementInput()) {
|
||||
// Motion zeroing runs regardless of GUI state so the player doesn't fall
|
||||
// when opening chat or other screens during an ability lock.
|
||||
if (ClientAbilityState.shouldSuppressMovementInput()) {
|
||||
mc.thePlayer.movementInput.moveForward = 0;
|
||||
mc.thePlayer.movementInput.moveStrafe = 0;
|
||||
mc.thePlayer.movementInput.jump = false;
|
||||
@@ -131,7 +137,7 @@ public class ClientTickHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (mc.currentScreen == null && ClientAbilityState.shouldLockRotation()) {
|
||||
if (ClientAbilityState.shouldLockRotation()) {
|
||||
mc.thePlayer.rotationYaw = ClientAbilityState.lockedYaw;
|
||||
mc.thePlayer.rotationPitch = ClientAbilityState.lockedPitch;
|
||||
mc.thePlayer.prevRotationYaw = ClientAbilityState.lockedYaw;
|
||||
@@ -210,7 +216,7 @@ public class ClientTickHandler {
|
||||
return;
|
||||
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
if (mc.thePlayer == null || mc.currentScreen != null)
|
||||
if (mc.thePlayer == null)
|
||||
return;
|
||||
|
||||
if (!ClientAbilityState.shouldSuppressMovementInput())
|
||||
|
||||
@@ -8,6 +8,9 @@ import kamkeel.npcs.network.packets.player.SpecialKeyStatePacket;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.settings.KeyBinding;
|
||||
import noppes.npcs.CustomNpcs;
|
||||
import noppes.npcs.client.gui.hud.ClientHudManager;
|
||||
import noppes.npcs.client.gui.hud.EnumHudComponent;
|
||||
import noppes.npcs.client.gui.hud.ability.AbilityHotbarComponent;
|
||||
import noppes.npcs.client.gui.player.inventory.GuiCNPCInventory;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
@@ -45,6 +48,7 @@ public class KeyPressHandler {
|
||||
|
||||
updateHeldStates(mc);
|
||||
handleNPCButton(mc);
|
||||
handleAbilityCycleKeys(mc);
|
||||
|
||||
if (!Keyboard.isRepeatEvent()) {
|
||||
int key = Keyboard.getEventKey();
|
||||
@@ -69,6 +73,7 @@ public class KeyPressHandler {
|
||||
// Just update held states and handle keybind actions.
|
||||
updateHeldStates(mc);
|
||||
handleNPCButton(mc);
|
||||
handleAbilityCycleKeys(mc);
|
||||
|
||||
int button = Mouse.getEventButton();
|
||||
if (button == -1 && Mouse.getEventDWheel() == 0)
|
||||
@@ -151,6 +156,21 @@ public class KeyPressHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleAbilityCycleKeys(Minecraft mc) {
|
||||
if (mc.currentScreen != null || mc.theWorld == null) return;
|
||||
|
||||
AbilityHotbarComponent comp = (AbilityHotbarComponent) ClientHudManager.getInstance()
|
||||
.getHudComponents().get(EnumHudComponent.AbilityHotbar);
|
||||
if (comp == null || !comp.hasAnyAbilities()) return;
|
||||
|
||||
if (ClientProxy.AbilityNextKey != null && ClientProxy.AbilityNextKey.isPressed()) {
|
||||
comp.onCycleNext();
|
||||
}
|
||||
if (ClientProxy.AbilityPrevKey != null && ClientProxy.AbilityPrevKey.isPressed()) {
|
||||
comp.onCyclePrev();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public State Queries ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import noppes.npcs.client.ClientCacheHandler;
|
||||
import noppes.npcs.client.gui.global.GuiNPCManageEffects;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import noppes.npcs.client.gui.script.GuiScriptInterface;
|
||||
import noppes.npcs.client.renderer.AnimationHelper;
|
||||
import noppes.npcs.client.gui.util.GuiMenuTopButton;
|
||||
@@ -27,13 +27,13 @@ import java.util.List;
|
||||
import static noppes.npcs.client.gui.player.inventory.GuiCNPCInventory.specialIcons;
|
||||
|
||||
public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldListener {
|
||||
private final GuiNPCManageEffects parent;
|
||||
private final GuiScreen parent;
|
||||
public CustomEffect effect;
|
||||
private final String originalName;
|
||||
|
||||
private final List<GuiMenuTopButton> topButtons = new ArrayList<>();
|
||||
|
||||
public SubGuiEffectGeneral(GuiNPCManageEffects parent, CustomEffect effect) {
|
||||
public SubGuiEffectGeneral(GuiScreen parent, CustomEffect effect) {
|
||||
this.effect = effect;
|
||||
this.parent = parent;
|
||||
this.originalName = effect.name;
|
||||
@@ -109,7 +109,8 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi
|
||||
|
||||
x = oldX - 4;
|
||||
y += 23;
|
||||
GuiScrollWindow scrollWindow = new GuiScrollWindow(this, x + 5, y, xSize - 20, ySize - 10 - (y - guiTop), 0) {
|
||||
int scrollClipHeight = ySize - 10 - (y - guiTop);
|
||||
GuiScrollWindow scrollWindow = new GuiScrollWindow(this, x + 5, y, xSize - 20, scrollClipHeight, 0) {
|
||||
@Override
|
||||
public void drawComponents(int mouseX, int mouseY, float partialTicks) {
|
||||
super.drawComponents(mouseX, mouseY, partialTicks);
|
||||
@@ -218,6 +219,9 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi
|
||||
));
|
||||
}
|
||||
|
||||
int contentBottom = y + 20 + 5;
|
||||
scrollWindow.maxScrollY = Math.max(0, contentBottom - scrollClipHeight);
|
||||
|
||||
}
|
||||
|
||||
private GuiNpcTextField setIntegerOnly(GuiNpcTextField field, int min, int max, int def) {
|
||||
|
||||
@@ -0,0 +1,996 @@
|
||||
package noppes.npcs.client.gui.global;
|
||||
|
||||
import kamkeel.npcs.client.renderer.TelegraphRenderer;
|
||||
import kamkeel.npcs.controllers.AbilityController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.controllers.data.ability.AbilityVariant;
|
||||
import kamkeel.npcs.controllers.data.ability.data.ChainedAbility;
|
||||
import kamkeel.npcs.controllers.data.ability.enums.UserType;
|
||||
import kamkeel.npcs.controllers.data.ability.preview.AbilityPreviewExecutor;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.packets.request.ability.BuiltInAbilityGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.ChainedAbilityGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.ChainedAbilityRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.ChainedAbilitySavePacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.CustomAbilitiesGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.CustomAbilityRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket;
|
||||
import kamkeel.npcs.network.packets.request.category.AbilityCategoryMovePacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryItemsRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryListRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategorySavePacket;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.GuiYesNo;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.RenderHelper;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import noppes.npcs.client.ClientEventHandler;
|
||||
import noppes.npcs.client.NoppesUtil;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityTypeSelect;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiAbilityVariantSelect;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiChainedAbilityConfig;
|
||||
import noppes.npcs.client.gui.advanced.SubGuiDuplicateNameConfirm;
|
||||
import noppes.npcs.client.gui.util.GuiCustomScroll;
|
||||
import noppes.npcs.client.gui.util.GuiDirectoryCategorized;
|
||||
import noppes.npcs.client.gui.util.GuiNpcButton;
|
||||
import noppes.npcs.client.gui.util.GuiNpcLabel;
|
||||
import noppes.npcs.client.gui.util.GuiTexturedButton;
|
||||
import noppes.npcs.client.gui.util.GuiUtil;
|
||||
import noppes.npcs.client.gui.util.IAbilityConfigCallback;
|
||||
import noppes.npcs.client.gui.util.IChainedAbilityConfigCallback;
|
||||
import noppes.npcs.client.gui.util.SubGuiInterface;
|
||||
import noppes.npcs.constants.EnumCategoryType;
|
||||
import noppes.npcs.constants.EnumScrollData;
|
||||
import noppes.npcs.controllers.data.AnimationData;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.controllers.data.Category;
|
||||
import noppes.npcs.entity.EntityCustomNpc;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
|
||||
public class GuiAbilityDirectory extends GuiDirectoryCategorized
|
||||
implements IAbilityConfigCallback, IChainedAbilityConfigCallback {
|
||||
|
||||
private EntityNPCInterface npc;
|
||||
private EntityNPCInterface originalNpc;
|
||||
|
||||
// View mode: 0=Built-In, 1=Custom, 2=Chained
|
||||
private int viewMode = 1;
|
||||
private boolean currentIsBuiltIn = false;
|
||||
|
||||
// Data caches
|
||||
private HashMap<String, Integer> builtInData = new HashMap<>();
|
||||
private Ability selectedAbility = null;
|
||||
private ChainedAbility selectedChain = null;
|
||||
|
||||
// User type filter: 0=Both, 1=NPC Only, 2=Player Only
|
||||
private int userTypeFilter = 0;
|
||||
private static final String[] FILTER_LABELS = {"gui.both", "gui.npcs", "gui.players"};
|
||||
|
||||
// Pending save state
|
||||
private Ability pendingSaveAbility = null;
|
||||
private ChainedAbility pendingSaveChain = null;
|
||||
private String pendingTypeId = null;
|
||||
|
||||
// Preview playback
|
||||
private AbilityPreviewExecutor previewExecutor;
|
||||
private long prevTick = 0;
|
||||
private double npcStartX, npcStartY, npcStartZ;
|
||||
private boolean trackingMovement = false;
|
||||
|
||||
// Fixed camera settings (matching GuiAbilityInterface)
|
||||
private static final float NPC_FACING_YAW = 310f;
|
||||
private static final float CAMERA_PITCH = 5f;
|
||||
|
||||
public GuiAbilityDirectory(EntityNPCInterface npc) {
|
||||
super();
|
||||
this.originalNpc = npc;
|
||||
this.npc = createFakeNPC(npc);
|
||||
rightPanelPercent = 0.38f;
|
||||
minRightPanelW = 200;
|
||||
zoomed = 50;
|
||||
|
||||
// Initialize preview executor
|
||||
previewExecutor = new AbilityPreviewExecutor();
|
||||
|
||||
// Re-request categories — viewMode was 0 during super() call
|
||||
requestCategoryList();
|
||||
PacketClient.sendClient(new CustomAbilitiesGetPacket());
|
||||
}
|
||||
|
||||
private static EntityNPCInterface createFakeNPC(EntityNPCInterface original) {
|
||||
EntityCustomNpc fake = new EntityCustomNpc(Minecraft.getMinecraft().theWorld);
|
||||
fake.display.readToNBT(original.display.writeToNBT(new NBTTagCompound()));
|
||||
fake.display.name = "ability preview";
|
||||
fake.height = original.height;
|
||||
fake.width = original.width;
|
||||
fake.display.animationData.setEnabled(true);
|
||||
return fake;
|
||||
}
|
||||
|
||||
private boolean isBuiltInMode() { return viewMode == 0; }
|
||||
private boolean isCustomMode() { return viewMode == 1; }
|
||||
private boolean isChainedMode() { return viewMode == 2; }
|
||||
|
||||
private int getCategoryType() {
|
||||
return isChainedMode() ? EnumCategoryType.CHAINED_ABILITY : EnumCategoryType.ABILITY;
|
||||
}
|
||||
|
||||
// ========== Layout Overrides ==========
|
||||
|
||||
@Override
|
||||
protected boolean hasCategories() { return !isBuiltInMode(); }
|
||||
|
||||
@Override
|
||||
protected void computeLayout() {
|
||||
if (isBuiltInMode()) {
|
||||
leftPanelPercent = 0.0f;
|
||||
minLeftPanelW = 0;
|
||||
} else {
|
||||
leftPanelPercent = 0.15f;
|
||||
minLeftPanelW = 120;
|
||||
}
|
||||
super.computeLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawPanels() {
|
||||
if (isBuiltInMode()) {
|
||||
// No left panel border
|
||||
if (rightPanelW > 0) {
|
||||
GuiUtil.drawRectD(rightX - 1, contentY - 1, rightX + rightPanelW + 1, originY + usableH + 1, panelBorder);
|
||||
}
|
||||
} else {
|
||||
super.drawPanels();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLeftPanel() {
|
||||
if (isBuiltInMode()) return;
|
||||
super.initLeftPanel();
|
||||
}
|
||||
|
||||
// ========== Title ==========
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
if (isBuiltInMode()) return "Abilities (Built-in)";
|
||||
if (isChainedMode()) return "Chained Abilities";
|
||||
return "Abilities";
|
||||
}
|
||||
|
||||
// ========== Top Bar ==========
|
||||
|
||||
@Override
|
||||
protected int initExtraTopBarButtons(int x, int topBtnY) {
|
||||
int topBtnW = 55;
|
||||
|
||||
// View mode toggle: Built-In / Custom / Chained
|
||||
String[] labels = {"gui.builtin", "gui.custom", "gui.chained"};
|
||||
int[] colors = {0x55FFFF, 0xFFFFFF, 0xFFFF55};
|
||||
GuiNpcButton viewBtn = new GuiNpcButton(60, x, topBtnY, topBtnW, btnH, labels[viewMode]);
|
||||
viewBtn.setTextColor(colors[viewMode]);
|
||||
addButton(viewBtn);
|
||||
x += topBtnW + 2;
|
||||
|
||||
// User type filter (only for Custom and Built-in, not Chained)
|
||||
if (!isChainedMode()) {
|
||||
addButton(new GuiNpcButton(61, x, topBtnY, topBtnW, btnH, FILTER_LABELS[userTypeFilter]));
|
||||
x += topBtnW + 2;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initTopBar(int topBtnY) {
|
||||
super.initTopBar(topBtnY);
|
||||
|
||||
// Disable category-dependent buttons in built-in mode
|
||||
if (isBuiltInMode()) {
|
||||
if (getButton(50) != null) getButton(50).enabled = false; // Add
|
||||
if (getButton(54) != null) getButton(54).enabled = false; // Move
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Category Requests ==========
|
||||
|
||||
@Override
|
||||
protected void requestCategoryList() {
|
||||
if (isBuiltInMode()) return;
|
||||
PacketClient.sendClient(new CategoryListRequestPacket(getCategoryType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestItemsInCategory(int catId) {
|
||||
if (isBuiltInMode()) return;
|
||||
PacketClient.sendClient(new CategoryItemsRequestPacket(getCategoryType(), catId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestItemData(int itemId) {
|
||||
// Not used — we override customScrollClicked for name-based requests
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveCategory(Category cat) {
|
||||
PacketClient.sendClient(new CategorySavePacket(getCategoryType(), cat.writeNBT(new NBTTagCompound())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveCategory(int catId) {
|
||||
PacketClient.sendClient(new CategoryRemovePacket(getCategoryType(), catId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddItem(int catId) {
|
||||
if (isBuiltInMode()) return;
|
||||
if (isChainedMode()) {
|
||||
ChainedAbility newChain = new ChainedAbility("New Chain");
|
||||
setSubGui(new SubGuiChainedAbilityConfig(newChain, this));
|
||||
} else {
|
||||
setSubGui(new SubGuiAbilityTypeSelect());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveItem(int itemId) {
|
||||
// Handled via confirmClicked with name-based packets
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEditItem() {
|
||||
stopPreviewPlayback();
|
||||
if (isChainedMode() && selectedChain != null) {
|
||||
setSubGui(new SubGuiChainedAbilityConfig(selectedChain, this));
|
||||
} else if (!currentIsBuiltIn && selectedAbility != null) {
|
||||
setSubGui(selectedAbility.createConfigGui(this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCloneItem() {
|
||||
// Abilities don't support cloning through this mechanism
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onItemReceived(NBTTagCompound compound) {
|
||||
if (isChainedMode() && compound.hasKey("Entries")) {
|
||||
selectedChain = new ChainedAbility();
|
||||
selectedChain.readNBT(compound);
|
||||
setPrevItemName(selectedChain.getName());
|
||||
} else {
|
||||
currentIsBuiltIn = compound.hasKey("BuiltIn") && compound.getBoolean("BuiltIn");
|
||||
selectedAbility = AbilityController.Instance.fromNBT(compound);
|
||||
if (selectedAbility != null) {
|
||||
setPrevItemName(selectedAbility.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasSelectedItem() {
|
||||
if (isChainedMode()) return selectedChain != null;
|
||||
if (currentIsBuiltIn) return selectedAbility != null;
|
||||
return selectedAbility != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSelectedItemId() {
|
||||
return -1; // Abilities use names, not IDs
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendMovePacket(int itemId, int destCatId) {
|
||||
// Not used — we override executeMoveItems for name-based moves
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuiScreen getWindowedVariant() {
|
||||
return new GuiNpcManageAbilities(originalNpc, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveCurrentItem() {
|
||||
// Abilities are saved via SubGui callbacks, not on close
|
||||
}
|
||||
|
||||
// ========== Move Override (name-based) ==========
|
||||
|
||||
@Override
|
||||
protected void executeMoveItems(int destCatId) {
|
||||
for (String name : moveSelection) {
|
||||
PacketClient.sendClient(new AbilityCategoryMovePacket(getCategoryType(), name, destCatId));
|
||||
}
|
||||
movePhase = 0;
|
||||
moveSelection.clear();
|
||||
if (selectedCatId >= 0) {
|
||||
requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
initGui();
|
||||
}
|
||||
|
||||
// ========== Scroll Events (name-based) ==========
|
||||
|
||||
@Override
|
||||
public void customScrollClicked(int i, int j, int k, GuiCustomScroll scroll) {
|
||||
if (scroll.id == 0) {
|
||||
if (isBuiltInMode()) {
|
||||
// Built-in: name-based request
|
||||
String selected = itemScroll.getSelected();
|
||||
if (selected != null && !selected.equals(prevItemName)) {
|
||||
stopPreviewPlayback();
|
||||
currentIsBuiltIn = true;
|
||||
PacketClient.sendClient(new BuiltInAbilityGetPacket(selected));
|
||||
prevItemName = selected;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (movePhase == 1) {
|
||||
// Move phase 1: selecting items
|
||||
if (moveSelection.size() > MAX_MOVE_ITEMS) {
|
||||
java.util.HashSet<String> trimmed = new java.util.HashSet<>();
|
||||
int count = 0;
|
||||
for (String s : moveSelection) {
|
||||
if (count++ >= MAX_MOVE_ITEMS) break;
|
||||
trimmed.add(s);
|
||||
}
|
||||
moveSelection.clear();
|
||||
moveSelection.addAll(trimmed);
|
||||
itemScroll.setSelectedList(moveSelection);
|
||||
}
|
||||
if (getButton(55) != null) {
|
||||
getButton(55).enabled = !moveSelection.isEmpty();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal click: name-based request
|
||||
String selected = itemScroll.getSelected();
|
||||
if (selected != null && !selected.equals(prevItemName)) {
|
||||
stopPreviewPlayback();
|
||||
prevItemName = selected;
|
||||
currentIsBuiltIn = false;
|
||||
if (isChainedMode()) {
|
||||
PacketClient.sendClient(new ChainedAbilityGetPacket(selected));
|
||||
} else {
|
||||
PacketClient.sendClient(new CustomAbilityGetPacket(selected));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Category scroll — delegate to parent
|
||||
super.customScrollClicked(i, j, k, scroll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) {
|
||||
if (scroll.id == 0 && hasSelectedItem() && movePhase == 0) {
|
||||
onEditItem();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Search with User Type Filtering ==========
|
||||
|
||||
@Override
|
||||
protected List<String> getItemSearchList() {
|
||||
if (selectedCatId < 0 && !isBuiltInMode()) return new ArrayList<>();
|
||||
List<String> list = new ArrayList<>();
|
||||
HashMap<String, Integer> data = isBuiltInMode() ? builtInData : itemData;
|
||||
for (Map.Entry<String, Integer> entry : data.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
if (!itemSearch.isEmpty() && !name.toLowerCase().contains(itemSearch)) continue;
|
||||
if (!isChainedMode() && userTypeFilter != 0) {
|
||||
UserType ut = UserType.fromOrdinal(entry.getValue());
|
||||
if (userTypeFilter == 1 && !ut.allowsNpc()) continue;
|
||||
if (userTypeFilter == 2 && !ut.allowsPlayer()) continue;
|
||||
}
|
||||
list.add(name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// ========== Data from Server ==========
|
||||
|
||||
@Override
|
||||
public void setData(Vector<String> list, HashMap<String, Integer> data, EnumScrollData type) {
|
||||
if (type == EnumScrollData.BUILTIN_ABILITIES) {
|
||||
builtInData = data;
|
||||
if (isBuiltInMode()) {
|
||||
itemScroll.setList(getItemSearchList());
|
||||
initGui();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type == EnumScrollData.CUSTOM_ABILITIES) {
|
||||
// Received flat list — used only for built-in data alongside it
|
||||
// Category mode uses CATEGORY_GROUP instead
|
||||
return;
|
||||
}
|
||||
if (type == EnumScrollData.CHAINED_ABILITIES) {
|
||||
// Flat list — not used in category mode
|
||||
return;
|
||||
}
|
||||
super.setData(list, data, type);
|
||||
}
|
||||
|
||||
// ========== Actions ==========
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton guibutton) {
|
||||
int id = guibutton.id;
|
||||
|
||||
// View mode toggle
|
||||
if (id == 60) {
|
||||
viewMode = (viewMode + 1) % 3;
|
||||
stopPreviewPlayback();
|
||||
clearSelection();
|
||||
if (isBuiltInMode()) {
|
||||
selectedCatId = 0;
|
||||
PacketClient.sendClient(new CustomAbilitiesGetPacket());
|
||||
} else {
|
||||
selectedCatId = -1;
|
||||
requestCategoryList();
|
||||
}
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
|
||||
// User type filter
|
||||
if (id == 61) {
|
||||
userTypeFilter = (userTypeFilter + 1) % 3;
|
||||
itemScroll.setList(getItemSearchList());
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
|
||||
// Preview controls
|
||||
if (id == 91) { // Play
|
||||
if (previewExecutor.isPaused()) {
|
||||
previewExecutor.play();
|
||||
} else {
|
||||
startPreviewPlayback();
|
||||
}
|
||||
initGui();
|
||||
return;
|
||||
} else if (id == 92) { // Pause
|
||||
previewExecutor.pause();
|
||||
initGui();
|
||||
return;
|
||||
} else if (id == 93) { // Stop
|
||||
stopPreviewPlayback();
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove — need custom handling for name-based remove
|
||||
if (id == 53 && prevItemName != null && !prevItemName.isEmpty() && movePhase == 0) {
|
||||
int confirmId = isChainedMode() ? 3 : 2;
|
||||
GuiYesNo guiyesno = new GuiYesNo(this, prevItemName, StatCollector.translateToLocal("gui.delete"), confirmId);
|
||||
displayGuiScreen(guiyesno);
|
||||
return;
|
||||
}
|
||||
|
||||
super.actionPerformed(guibutton);
|
||||
}
|
||||
|
||||
private void clearSelection() {
|
||||
prevItemName = "";
|
||||
selectedAbility = null;
|
||||
selectedChain = null;
|
||||
currentIsBuiltIn = false;
|
||||
itemSearch = "";
|
||||
movePhase = 0;
|
||||
moveSelection.clear();
|
||||
trackingMovement = false;
|
||||
}
|
||||
|
||||
// ========== Confirm Dialog ==========
|
||||
|
||||
@Override
|
||||
public void confirmClicked(boolean result, int id) {
|
||||
NoppesUtil.openGUI(player, this);
|
||||
if (!result) return;
|
||||
|
||||
// Remove custom ability
|
||||
if (id == 2 && prevItemName != null && !prevItemName.isEmpty()) {
|
||||
PacketClient.sendClient(new CustomAbilityRemovePacket(prevItemName));
|
||||
selectedAbility = null;
|
||||
prevItemName = "";
|
||||
if (selectedCatId >= 0) requestItemsInCategory(selectedCatId);
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove chained ability
|
||||
if (id == 3 && prevItemName != null && !prevItemName.isEmpty()) {
|
||||
PacketClient.sendClient(new ChainedAbilityRemovePacket(prevItemName));
|
||||
selectedChain = null;
|
||||
prevItemName = "";
|
||||
if (selectedCatId >= 0) requestItemsInCategory(selectedCatId);
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
|
||||
// Category remove (id == 5) handled by parent
|
||||
super.confirmClicked(result, id);
|
||||
}
|
||||
|
||||
// ========== SubGui Callbacks ==========
|
||||
|
||||
@Override
|
||||
protected void onSubGuiClosed(SubGuiInterface subgui) {
|
||||
if (subgui instanceof SubGuiChainedAbilityConfig) {
|
||||
if (pendingSaveChain != null) {
|
||||
PacketClient.sendClient(new ChainedAbilitySavePacket(pendingSaveChain.writeNBT(false)));
|
||||
prevItemName = pendingSaveChain.getName();
|
||||
pendingSaveChain = null;
|
||||
if (selectedCatId >= 0 && isChainedMode()) requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
if (subgui instanceof SubGuiAbilityVariantSelect) {
|
||||
SubGuiAbilityVariantSelect gui = (SubGuiAbilityVariantSelect) subgui;
|
||||
int idx = gui.getSelectedIndex();
|
||||
if (idx >= 0 && pendingTypeId != null) {
|
||||
Ability newAbility = AbilityController.Instance.create(pendingTypeId);
|
||||
if (newAbility != null) {
|
||||
gui.getVariants().get(idx).apply(newAbility);
|
||||
newAbility.setId(UUID.randomUUID().toString());
|
||||
pendingTypeId = null;
|
||||
selectedAbility = newAbility;
|
||||
prevItemName = newAbility.getName();
|
||||
setSubGui(newAbility.createConfigGui(this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
pendingTypeId = null;
|
||||
} else if (subgui instanceof SubGuiAbilityTypeSelect) {
|
||||
String typeId = ((SubGuiAbilityTypeSelect) subgui).getSelectedTypeId();
|
||||
if (typeId != null) {
|
||||
List<AbilityVariant> variants = AbilityController.Instance.getVariantsForType(typeId);
|
||||
if (variants.size() > 1) {
|
||||
pendingTypeId = typeId;
|
||||
setSubGui(new SubGuiAbilityVariantSelect(variants));
|
||||
return;
|
||||
}
|
||||
Ability newAbility = AbilityController.Instance.create(typeId);
|
||||
if (newAbility != null) {
|
||||
if (variants.size() == 1) variants.get(0).apply(newAbility);
|
||||
newAbility.setId(UUID.randomUUID().toString());
|
||||
selectedAbility = newAbility;
|
||||
prevItemName = newAbility.getName();
|
||||
setSubGui(newAbility.createConfigGui(this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (subgui instanceof SubGuiDuplicateNameConfirm) {
|
||||
SubGuiDuplicateNameConfirm gui = (SubGuiDuplicateNameConfirm) subgui;
|
||||
if (pendingSaveAbility != null) {
|
||||
if (gui.isConfirmed()) {
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT(false)));
|
||||
prevItemName = pendingSaveAbility.getName();
|
||||
pendingSaveAbility = null;
|
||||
if (selectedCatId >= 0 && isCustomMode()) requestItemsInCategory(selectedCatId);
|
||||
} else if (gui.isBack()) {
|
||||
Ability ability = pendingSaveAbility;
|
||||
pendingSaveAbility = null;
|
||||
setSubGui(ability.createConfigGui(this));
|
||||
return;
|
||||
} else {
|
||||
pendingSaveAbility = null;
|
||||
}
|
||||
}
|
||||
} else if (pendingSaveAbility != null) {
|
||||
if (hasDuplicateName(pendingSaveAbility)) {
|
||||
setSubGui(new SubGuiDuplicateNameConfirm());
|
||||
return;
|
||||
}
|
||||
PacketClient.sendClient(new CustomAbilitySavePacket(pendingSaveAbility.writeNBT(false)));
|
||||
prevItemName = pendingSaveAbility.getName();
|
||||
pendingSaveAbility = null;
|
||||
if (selectedCatId >= 0 && isCustomMode()) requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
initGui();
|
||||
}
|
||||
|
||||
private boolean hasDuplicateName(Ability ability) {
|
||||
String name = ability.getName();
|
||||
if (name == null || name.isEmpty()) return false;
|
||||
if (!itemData.containsKey(name)) return false;
|
||||
String uuid = ability.getId();
|
||||
if (uuid == null || uuid.isEmpty()) return true;
|
||||
Ability existing = AbilityController.Instance.getCustomAbilityByName(name);
|
||||
return existing == null || !uuid.equals(existing.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAbilitySaved(Ability ability) {
|
||||
pendingSaveAbility = ability;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChainedAbilitySaved(ChainedAbility chain) {
|
||||
pendingSaveChain = chain;
|
||||
}
|
||||
|
||||
// ========== Right Panel ==========
|
||||
|
||||
@Override
|
||||
protected void initRightPanel(int startY) {
|
||||
// Calculate layout: playback row + edit + remove rows
|
||||
int bottomRows = 2; // edit, remove
|
||||
boolean hasPlayback = hasSelectedItem();
|
||||
if (hasPlayback) bottomRows++; // playback row
|
||||
int bottomH = bottomRows * (btnH + gap) + 14;
|
||||
|
||||
previewX = rightX;
|
||||
previewY = contentY;
|
||||
previewW = rightPanelW;
|
||||
previewH = contentH - bottomH - gap;
|
||||
|
||||
// Playback controls
|
||||
if (hasPlayback) {
|
||||
int playY = contentY + previewH + gap;
|
||||
String animTexture = "customnpcs:textures/gui/animation.png";
|
||||
int btnX = rightX + 4;
|
||||
|
||||
boolean isPlaying = previewExecutor.isPlaying() && !previewExecutor.isPaused();
|
||||
boolean isPaused = previewExecutor.isPaused();
|
||||
boolean isActive = previewExecutor.isActive();
|
||||
|
||||
if (!isPlaying || isPaused) {
|
||||
String statusKey = isPaused ? "animation.paused" : "animation.stopped";
|
||||
addLabel(new GuiNpcLabel(90, statusKey, btnX, playY + 5));
|
||||
addButton(new GuiTexturedButton(91, "", btnX + 65, playY, 11, 20, animTexture, 18, 71));
|
||||
} else {
|
||||
addLabel(new GuiNpcLabel(90, "animation.playing", btnX, playY + 5));
|
||||
addButton(new GuiTexturedButton(92, "", btnX + 65, playY, 14, 20, animTexture, 0, 71));
|
||||
}
|
||||
if (isActive) {
|
||||
addButton(new GuiTexturedButton(93, "", btnX + 85, playY, 14, 20, animTexture, 33, 71));
|
||||
}
|
||||
}
|
||||
|
||||
// Edit + Remove at bottom
|
||||
int editY = contentY + contentH - btnH * 2 - gap;
|
||||
GuiNpcButton editBtn = new GuiNpcButton(51, rightX, editY, rightPanelW, btnH, "gui.edit");
|
||||
editBtn.enabled = hasSelectedItem() && movePhase == 0 && !currentIsBuiltIn;
|
||||
addButton(editBtn);
|
||||
|
||||
int removeY = editY + btnH + gap;
|
||||
GuiNpcButton removeBtn = new GuiNpcButton(53, rightX, removeY, rightPanelW, btnH, "gui.remove");
|
||||
removeBtn.enabled = hasSelectedItem() && movePhase == 0 && !currentIsBuiltIn;
|
||||
removeBtn.setTextColor(0xFF5555);
|
||||
addButton(removeBtn);
|
||||
}
|
||||
|
||||
// ========== Preview Rendering ==========
|
||||
|
||||
@Override
|
||||
protected void drawItemPreview(int centerX, int centerY, int mouseX, int mouseY, float partialTicks) {
|
||||
if (selectedAbility == null && selectedChain == null) return;
|
||||
|
||||
boolean previewing = previewExecutor.isActive();
|
||||
|
||||
// When preview is active, use fixed camera like GuiAbilityInterface
|
||||
// Otherwise use the standard mouse-follow rotation
|
||||
if (previewing) {
|
||||
drawAbilityPreview(partialTicks);
|
||||
} else {
|
||||
drawStaticNpcPreview(centerX, centerY, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the full ability preview with fixed camera, preview entities, and telegraph.
|
||||
* Matches GuiAbilityInterface's rendering pipeline.
|
||||
*/
|
||||
private void drawAbilityPreview(float partialTicks) {
|
||||
// Set NPC facing direction
|
||||
npc.prevRenderYawOffset = npc.renderYawOffset = NPC_FACING_YAW;
|
||||
npc.prevRotationYaw = npc.rotationYaw = NPC_FACING_YAW;
|
||||
npc.prevRotationYawHead = npc.rotationYawHead = NPC_FACING_YAW;
|
||||
npc.rotationPitch = 0;
|
||||
|
||||
// Calculate NPC position delta for movement rendering
|
||||
double npcDeltaX = 0, npcDeltaY = 0, npcDeltaZ = 0;
|
||||
if (trackingMovement) {
|
||||
double interpX = npc.prevPosX + (npc.posX - npc.prevPosX) * partialTicks;
|
||||
double interpY = npc.prevPosY + (npc.posY - npc.prevPosY) * partialTicks;
|
||||
double interpZ = npc.prevPosZ + (npc.posZ - npc.prevPosZ) * partialTicks;
|
||||
npcDeltaX = interpX - npcStartX;
|
||||
npcDeltaY = interpY - npcStartY;
|
||||
npcDeltaZ = interpZ - npcStartZ;
|
||||
}
|
||||
|
||||
// NPC render position: left-center of preview area
|
||||
int npcScreenX = previewX + (int)(previewW * 0.33f);
|
||||
int npcScreenY = previewY + (int)(previewH * 0.80f);
|
||||
|
||||
// Scale camera zoom based on panel size
|
||||
float scale = Math.min(previewW, previewH) / 200f;
|
||||
float renderZoom = zoomed * scale;
|
||||
|
||||
// Enable scissor to clip rendering to preview area
|
||||
GL11.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
setScissorClip(previewX, previewY, previewW, previewH);
|
||||
|
||||
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GL11.glPushMatrix();
|
||||
|
||||
GL11.glTranslatef(npcScreenX, npcScreenY, 500F);
|
||||
GL11.glScalef(-renderZoom, renderZoom, renderZoom);
|
||||
GL11.glRotatef(180F, 0.0F, 0.0F, 1.0F);
|
||||
GL11.glRotatef(CAMERA_PITCH, 1.0F, 0.0F, 0.0F);
|
||||
GL11.glRotatef(rotation, 0.0F, 1.0F, 0.0F);
|
||||
|
||||
GL11.glRotatef(135F, 0.0F, 1.0F, 0.0F);
|
||||
RenderHelper.enableStandardItemLighting();
|
||||
GL11.glRotatef(-135F, 0.0F, 1.0F, 0.0F);
|
||||
|
||||
GL11.glTranslatef(0.0F, npc.yOffset, 0.0F);
|
||||
RenderManager.instance.playerViewY = 180F;
|
||||
ClientEventHandler.renderingEntityInGUI = true;
|
||||
|
||||
// Render NPC at its position delta
|
||||
try {
|
||||
RenderManager.instance.renderEntityWithPosYaw(npc, npcDeltaX, npcDeltaY, npcDeltaZ, 0.0F, partialTicks);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// Render preview entities
|
||||
renderPreviewEntities(partialTicks);
|
||||
|
||||
// Render telegraph
|
||||
renderPreviewTelegraph(partialTicks);
|
||||
|
||||
ClientEventHandler.renderingEntityInGUI = false;
|
||||
GL11.glPopMatrix();
|
||||
|
||||
RenderHelper.disableStandardItemLighting();
|
||||
GL11.glDisable(GL12.GL_RESCALE_NORMAL);
|
||||
OpenGlHelper.setActiveTexture(OpenGlHelper.lightmapTexUnit);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
OpenGlHelper.setActiveTexture(OpenGlHelper.defaultTexUnit);
|
||||
|
||||
GL11.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard static NPC preview with mouse-follow rotation (when not previewing an ability).
|
||||
*/
|
||||
private void drawStaticNpcPreview(int centerX, int centerY, int mouseX, int mouseY) {
|
||||
float scale = Math.min(previewW, previewH) / 200f;
|
||||
float renderZoom = zoomed * scale;
|
||||
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
EntityLivingBase entity = this.npc;
|
||||
|
||||
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GL11.glPushMatrix();
|
||||
GL11.glTranslatef(centerX, centerY, 60F);
|
||||
GL11.glScalef(-renderZoom, renderZoom, renderZoom);
|
||||
GL11.glRotatef(180F, 0.0F, 0.0F, 1.0F);
|
||||
|
||||
float f2 = entity.renderYawOffset;
|
||||
float f3 = entity.rotationYaw;
|
||||
float f4 = entity.rotationPitch;
|
||||
float f7 = entity.rotationYawHead;
|
||||
float f5 = (float) centerX - mouseX;
|
||||
float f6 = (float) (centerY - 50) - mouseY;
|
||||
|
||||
GL11.glRotatef(135F, 0.0F, 1.0F, 0.0F);
|
||||
RenderHelper.enableStandardItemLighting();
|
||||
GL11.glRotatef(-135F, 0.0F, 1.0F, 0.0F);
|
||||
GL11.glRotatef(-(float) Math.atan(f6 / 800F) * 20F, 1.0F, 0.0F, 0.0F);
|
||||
entity.prevRenderYawOffset = entity.renderYawOffset = rotation;
|
||||
entity.prevRotationYaw = entity.rotationYaw = (float) Math.atan(f5 / 80F) * 40F + rotation;
|
||||
entity.rotationPitch = -(float) Math.atan(f6 / 80F) * 20F;
|
||||
entity.prevRotationYawHead = entity.rotationYawHead = entity.rotationYaw;
|
||||
GL11.glTranslatef(0.0F, entity.yOffset, 1F);
|
||||
RenderManager.instance.playerViewY = 180F;
|
||||
|
||||
try {
|
||||
RenderManager.instance.renderEntityWithPosYaw(entity, 0.0, 0.0, 0.0, 0.0F, 1.0F);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
entity.prevRenderYawOffset = entity.renderYawOffset = f2;
|
||||
entity.prevRotationYaw = entity.rotationYaw = f3;
|
||||
entity.rotationPitch = f4;
|
||||
entity.prevRotationYawHead = entity.rotationYawHead = f7;
|
||||
|
||||
RenderHelper.disableStandardItemLighting();
|
||||
GL11.glDisable(GL12.GL_RESCALE_NORMAL);
|
||||
OpenGlHelper.setActiveTexture(OpenGlHelper.lightmapTexUnit);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
OpenGlHelper.setActiveTexture(OpenGlHelper.defaultTexUnit);
|
||||
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
private void renderPreviewEntities(float partialTicks) {
|
||||
List<Entity> entities = previewExecutor.getPreviewEntities();
|
||||
if (entities.isEmpty()) return;
|
||||
|
||||
boolean lightingWasEnabled = GL11.glIsEnabled(GL11.GL_LIGHTING);
|
||||
double refX = trackingMovement ? npcStartX : npc.posX;
|
||||
double refY = trackingMovement ? npcStartY : npc.posY;
|
||||
double refZ = trackingMovement ? npcStartZ : npc.posZ;
|
||||
|
||||
for (Entity entity : entities) {
|
||||
if (entity == null || entity.isDead) continue;
|
||||
double offsetX = entity.prevPosX + (entity.posX - entity.prevPosX) * partialTicks - refX;
|
||||
double offsetY = entity.prevPosY + (entity.posY - entity.prevPosY) * partialTicks - refY - npc.yOffset;
|
||||
double offsetZ = entity.prevPosZ + (entity.posZ - entity.prevPosZ) * partialTicks - refZ;
|
||||
|
||||
GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GL11.glDisable(GL11.GL_LIGHTING);
|
||||
try {
|
||||
RenderManager.instance.renderEntityWithPosYaw(entity, offsetX, offsetY, offsetZ, entity.rotationYaw, partialTicks);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
if (lightingWasEnabled) GL11.glEnable(GL11.GL_LIGHTING);
|
||||
GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
private void renderPreviewTelegraph(float partialTicks) {
|
||||
TelegraphInstance telegraph = previewExecutor.getTelegraph();
|
||||
if (telegraph == null || TelegraphRenderer.Instance == null) return;
|
||||
|
||||
double refX = trackingMovement ? npcStartX : npc.posX;
|
||||
double refY = trackingMovement ? npcStartY : npc.posY;
|
||||
double refZ = trackingMovement ? npcStartZ : npc.posZ;
|
||||
|
||||
double offsetX = telegraph.getInterpolatedX(partialTicks) - refX;
|
||||
double offsetY = telegraph.getInterpolatedY(partialTicks) - refY - npc.yOffset;
|
||||
double offsetZ = telegraph.getInterpolatedZ(partialTicks) - refZ;
|
||||
|
||||
TelegraphRenderer.Instance.renderTelegraphInGUI(telegraph, offsetX, offsetY, offsetZ, 1.0f, partialTicks);
|
||||
}
|
||||
|
||||
private void setScissorClip(int x, int y, int w, int h) {
|
||||
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int scale = sr.getScaleFactor();
|
||||
int scaledY = mc.displayHeight - (y + h) * scale;
|
||||
GL11.glScissor(x * scale, scaledY, w * scale, h * scale);
|
||||
}
|
||||
|
||||
// ========== Preview Tick ==========
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
|
||||
// Set NPC rotation BEFORE ticking for correct anchor points
|
||||
if (previewExecutor.isActive()) {
|
||||
npc.prevRenderYawOffset = npc.renderYawOffset = NPC_FACING_YAW;
|
||||
npc.prevRotationYaw = npc.rotationYaw = NPC_FACING_YAW;
|
||||
npc.prevRotationYawHead = npc.rotationYawHead = NPC_FACING_YAW;
|
||||
}
|
||||
|
||||
tickPreview();
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
private void tickPreview() {
|
||||
if (previewExecutor.isPlaying() && !previewExecutor.isPaused()) {
|
||||
long time = mc.theWorld != null ? mc.theWorld.getTotalWorldTime() : System.currentTimeMillis() / 50;
|
||||
if (time != prevTick) {
|
||||
npc.display.animationData.increaseTime();
|
||||
previewExecutor.tick();
|
||||
prevTick = time;
|
||||
|
||||
if (!previewExecutor.isActive()) {
|
||||
trackingMovement = false;
|
||||
initGui();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startPreviewPlayback() {
|
||||
// Save NPC start position for movement tracking
|
||||
npcStartX = npc.posX;
|
||||
npcStartY = npc.posY;
|
||||
npcStartZ = npc.posZ;
|
||||
trackingMovement = true;
|
||||
|
||||
if (isChainedMode() && selectedChain != null) {
|
||||
previewExecutor.startChainPreview(selectedChain, npc);
|
||||
} else if (selectedAbility != null) {
|
||||
previewExecutor.startPreview(selectedAbility, npc);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPreviewPlayback() {
|
||||
previewExecutor.stop();
|
||||
trackingMovement = false;
|
||||
// Reset NPC animation
|
||||
AnimationData data = npc.display.animationData;
|
||||
data.setAnimation(new Animation());
|
||||
}
|
||||
|
||||
// ========== Preview Status Drawing ==========
|
||||
|
||||
@Override
|
||||
protected void drawOverlay(int mouseX, int mouseY, float partialTicks) {
|
||||
super.drawOverlay(mouseX, mouseY, partialTicks);
|
||||
|
||||
// Draw preview status text inside the preview area
|
||||
if (previewExecutor.isActive() && !hasSubGui()) {
|
||||
String status = previewExecutor.getStatusString();
|
||||
fontRendererObj.drawString(status, previewX + 4, previewY + previewH - 12, 0xFFFFFF, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawItemDetails(int x, int y, int w) {
|
||||
if (isChainedMode() && selectedChain != null) {
|
||||
fontRendererObj.drawString(selectedChain.getDisplayName(), x, y, 0xFFFFFF, true);
|
||||
y += 14;
|
||||
fontRendererObj.drawString("Entries: " + selectedChain.getEntries().size(), x, y, 0xB5B5B5, false);
|
||||
y += 12;
|
||||
fontRendererObj.drawString("Cooldown: " + selectedChain.getCooldownTicks() + "t", x, y, 0xB5B5B5, false);
|
||||
y += 12;
|
||||
UserType ut = selectedChain.getAllowedBy();
|
||||
fontRendererObj.drawString("Allowed: " + ut.name(), x, y, 0xB5B5B5, false);
|
||||
} else if (selectedAbility != null) {
|
||||
fontRendererObj.drawString(selectedAbility.getDisplayName(), x, y, 0xFFFFFF, true);
|
||||
y += 14;
|
||||
String typeId = selectedAbility.getTypeId();
|
||||
String typeName = StatCollector.translateToLocal(typeId);
|
||||
fontRendererObj.drawString("Type: " + typeName, x, y, 0xFFAE0D, false);
|
||||
y += 12;
|
||||
fontRendererObj.drawString("Cooldown: " + selectedAbility.getCooldownTicks() + "t", x, y, 0xB5B5B5, false);
|
||||
y += 12;
|
||||
fontRendererObj.drawString("Windup: " + selectedAbility.getWindUpTicks() + "t", x, y, 0xB5B5B5, false);
|
||||
y += 12;
|
||||
UserType ut = selectedAbility.getAllowedBy();
|
||||
fontRendererObj.drawString("Allowed: " + ut.name(), x, y, 0xB5B5B5, false);
|
||||
if (selectedAbility.hasDamage()) {
|
||||
y += 12;
|
||||
fontRendererObj.drawString("Damage: " + selectedAbility.getDisplayDamage(), x, y, 0xFF5555, false);
|
||||
}
|
||||
if (currentIsBuiltIn) {
|
||||
y += 12;
|
||||
fontRendererObj.drawString("(Built-in)", x, y, 0x55FF55, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Cleanup ==========
|
||||
|
||||
@Override
|
||||
public void onGuiClosed() {
|
||||
stopPreviewPlayback();
|
||||
if (npc != null) {
|
||||
npc.display.animationData.setEnabled(false);
|
||||
}
|
||||
super.onGuiClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
package noppes.npcs.client.gui.global;
|
||||
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.packets.request.animation.AnimationGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.animation.AnimationRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.animation.AnimationSavePacket;
|
||||
import kamkeel.npcs.network.packets.request.animation.AnimationsGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.animation.BuiltInAnimationGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryItemsRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryListRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryMoveItemPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategorySavePacket;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.RenderHelper;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import noppes.npcs.client.NoppesUtil;
|
||||
import noppes.npcs.client.gui.util.GuiCustomScroll;
|
||||
import noppes.npcs.client.gui.util.GuiDirectoryCategorized;
|
||||
import noppes.npcs.client.gui.util.GuiNpcButton;
|
||||
import noppes.npcs.client.gui.util.GuiUtil;
|
||||
import noppes.npcs.client.gui.util.GuiNpcLabel;
|
||||
import noppes.npcs.client.gui.util.GuiTexturedButton;
|
||||
import noppes.npcs.client.gui.util.SubGuiInterface;
|
||||
import noppes.npcs.constants.EnumCategoryType;
|
||||
import noppes.npcs.constants.EnumScrollData;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
import noppes.npcs.controllers.data.AnimationData;
|
||||
import noppes.npcs.controllers.data.Category;
|
||||
import noppes.npcs.controllers.data.Frame;
|
||||
import noppes.npcs.controllers.data.FramePart;
|
||||
import noppes.npcs.entity.EntityCustomNpc;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
public class GuiAnimationDirectory extends GuiDirectoryCategorized {
|
||||
public Animation animation = new Animation();
|
||||
public EntityNPCInterface npc;
|
||||
private EntityNPCInterface originalNpc;
|
||||
|
||||
// Animation playback
|
||||
public boolean playingAnimation = false;
|
||||
private long prevTick;
|
||||
|
||||
// Built-in mode
|
||||
private boolean showingBuiltIn = false;
|
||||
private boolean currentIsBuiltIn = false;
|
||||
private HashMap<String, Integer> builtInData = new HashMap<>();
|
||||
|
||||
public GuiAnimationDirectory(EntityNPCInterface npc) {
|
||||
super();
|
||||
this.originalNpc = npc;
|
||||
this.npc = createFakeNPC(npc);
|
||||
zoomed = 70;
|
||||
PacketClient.sendClient(new AnimationsGetPacket());
|
||||
}
|
||||
|
||||
private static EntityNPCInterface createFakeNPC(EntityNPCInterface original) {
|
||||
EntityCustomNpc fake = new EntityCustomNpc(Minecraft.getMinecraft().theWorld);
|
||||
fake.display.readToNBT(original.display.writeToNBT(new NBTTagCompound()));
|
||||
fake.display.name = "anim preview";
|
||||
fake.height = original.height;
|
||||
fake.width = original.width;
|
||||
fake.display.animationData.setEnabled(true);
|
||||
return fake;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasCategories() { return !showingBuiltIn; }
|
||||
|
||||
@Override
|
||||
protected void computeLayout() {
|
||||
if (showingBuiltIn) {
|
||||
leftPanelPercent = 0.0f;
|
||||
minLeftPanelW = 0;
|
||||
} else {
|
||||
leftPanelPercent = 0.15f;
|
||||
minLeftPanelW = 120;
|
||||
}
|
||||
super.computeLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawPanels() {
|
||||
if (showingBuiltIn) {
|
||||
if (rightPanelW > 0) {
|
||||
GuiUtil.drawRectD(rightX - 1, contentY - 1, rightX + rightPanelW + 1, originY + usableH + 1, panelBorder);
|
||||
}
|
||||
} else {
|
||||
super.drawPanels();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLeftPanel() {
|
||||
if (showingBuiltIn) return;
|
||||
super.initLeftPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return showingBuiltIn ? "Animations (Built-in)" : "Animations";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestCategoryList() {
|
||||
if (!showingBuiltIn) {
|
||||
PacketClient.sendClient(new CategoryListRequestPacket(EnumCategoryType.ANIMATION));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestItemsInCategory(int catId) {
|
||||
if (!showingBuiltIn) {
|
||||
PacketClient.sendClient(new CategoryItemsRequestPacket(EnumCategoryType.ANIMATION, catId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestItemData(int itemId) {
|
||||
PacketClient.sendClient(new AnimationGetPacket(itemId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveCategory(Category cat) {
|
||||
PacketClient.sendClient(new CategorySavePacket(EnumCategoryType.ANIMATION, cat.writeNBT(new NBTTagCompound())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveCategory(int catId) {
|
||||
PacketClient.sendClient(new CategoryRemovePacket(EnumCategoryType.ANIMATION, catId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddItem(int catId) {
|
||||
if (showingBuiltIn) return;
|
||||
String name = "New";
|
||||
while (itemData.containsKey(name)) name += "_";
|
||||
Animation newAnim = new Animation(-1, name);
|
||||
PacketClient.sendClient(new AnimationSavePacket(newAnim.writeToNBT()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveItem(int itemId) {
|
||||
if (showingBuiltIn) return;
|
||||
PacketClient.sendClient(new AnimationRemovePacket(itemId));
|
||||
animation = new Animation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEditItem() {
|
||||
if (animation != null && animation.id >= 0 && !currentIsBuiltIn) {
|
||||
NoppesUtil.openGUI(player, new GuiNPCEditAnimation(this, animation, npc));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCloneItem() {
|
||||
if (showingBuiltIn || currentIsBuiltIn) return;
|
||||
if (animation != null && animation.id >= 0) {
|
||||
Animation clone = new Animation();
|
||||
clone.readFromNBT(animation.writeToNBT());
|
||||
clone.id = -1;
|
||||
while (itemData.containsKey(clone.name)) clone.name += "_";
|
||||
PacketClient.sendClient(new AnimationSavePacket(clone.writeToNBT()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onItemReceived(NBTTagCompound compound) {
|
||||
animation = new Animation();
|
||||
animation.readFromNBT(compound);
|
||||
setPrevItemName(animation.name);
|
||||
|
||||
currentIsBuiltIn = compound.hasKey("BuiltIn") && compound.getBoolean("BuiltIn");
|
||||
|
||||
playingAnimation = false;
|
||||
showFirstFrame();
|
||||
}
|
||||
|
||||
private void showFirstFrame() {
|
||||
if (animation.id != -1 || currentIsBuiltIn) {
|
||||
AnimationData data = npc.display.animationData;
|
||||
data.setAnimation(new Animation());
|
||||
data.animation.smooth = animation.smooth;
|
||||
data.animation.loop = 0;
|
||||
if (!animation.frames.isEmpty()) {
|
||||
Frame firstFrame = new Frame();
|
||||
firstFrame.parent = data.animation;
|
||||
firstFrame.readFromNBT(animation.frames.get(0).writeToNBT());
|
||||
data.animation.addFrame(firstFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasSelectedItem() {
|
||||
if (currentIsBuiltIn) return true;
|
||||
return animation != null && animation.id >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSelectedItemId() {
|
||||
return animation != null ? animation.id : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendMovePacket(int itemId, int destCatId) {
|
||||
PacketClient.sendClient(new CategoryMoveItemPacket(EnumCategoryType.ANIMATION, itemId, destCatId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuiScreen getWindowedVariant() {
|
||||
return new GuiNPCManageAnimations(originalNpc, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveCurrentItem() {
|
||||
if (!currentIsBuiltIn && animation != null && animation.id >= 0 && prevItemName != null && !prevItemName.isEmpty()) {
|
||||
PacketClient.sendClient(new AnimationSavePacket(animation.writeToNBT()));
|
||||
prevItemName = animation.name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSubGuiClosed(SubGuiInterface subgui) {
|
||||
if (animation != null && animation.id >= 0) {
|
||||
setPrevItemName(animation.name);
|
||||
if (selectedCatId >= 0 && !showingBuiltIn) requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Built-in Toggle ==========
|
||||
|
||||
@Override
|
||||
protected int initExtraTopBarButtons(int x, int topBtnY) {
|
||||
String toggleLabel = showingBuiltIn ? "gui.builtin" : "gui.custom";
|
||||
GuiNpcButton toggleBtn = new GuiNpcButton(60, x, topBtnY, 55, btnH, toggleLabel);
|
||||
toggleBtn.setTextColor(showingBuiltIn ? 0x55FFFF : 0xFFFFFF);
|
||||
addButton(toggleBtn);
|
||||
return x + 55 + 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initTopBar(int topBtnY) {
|
||||
super.initTopBar(topBtnY);
|
||||
|
||||
// Disable item operations in built-in mode
|
||||
if (showingBuiltIn) {
|
||||
if (getButton(50) != null) getButton(50).enabled = false; // Add
|
||||
if (getButton(54) != null) getButton(54).enabled = false; // Move
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initRightPanel(int startY) {
|
||||
// Calculate layout: playback row + edit/copy + remove rows
|
||||
int bottomRows = 2; // edit+copy, remove
|
||||
boolean hasPlayback = animation != null && !animation.frames.isEmpty() && hasSelectedItem();
|
||||
if (hasPlayback) bottomRows++; // playback row
|
||||
int bottomH = bottomRows * (btnH + gap) + 14;
|
||||
|
||||
previewX = rightX;
|
||||
previewY = contentY;
|
||||
previewW = rightPanelW;
|
||||
previewH = contentH - bottomH - gap;
|
||||
|
||||
// Playback controls
|
||||
if (hasPlayback) {
|
||||
int playY = contentY + previewH + gap;
|
||||
String animTexture = "customnpcs:textures/gui/animation.png";
|
||||
AnimationData data = npc.display.animationData;
|
||||
int btnX = rightX + 4;
|
||||
|
||||
if (!playingAnimation || (data.animation != null && data.animation.paused)) {
|
||||
String statusKey = (data.animation != null && data.animation.paused) ? "animation.paused" : "animation.stopped";
|
||||
addLabel(new GuiNpcLabel(90, statusKey, btnX, playY + 5));
|
||||
addButton(new GuiTexturedButton(91, "", btnX + 65, playY, 11, 20, animTexture, 18, 71));
|
||||
} else {
|
||||
addLabel(new GuiNpcLabel(90, "animation.playing", btnX, playY + 5));
|
||||
addLabel(new GuiNpcLabel(94, "", btnX + 60, playY + 5));
|
||||
addButton(new GuiTexturedButton(92, "", btnX + 65, playY, 14, 20, animTexture, 0, 71));
|
||||
}
|
||||
if (playingAnimation) {
|
||||
addButton(new GuiTexturedButton(93, "", btnX + 85, playY, 14, 20, animTexture, 33, 71));
|
||||
}
|
||||
}
|
||||
|
||||
// Edit + Copy on one row, Remove below
|
||||
int btnY = contentY + contentH - btnH * 2 - gap;
|
||||
int halfW = (rightPanelW - gap) / 2;
|
||||
|
||||
GuiNpcButton editBtn = new GuiNpcButton(51, rightX, btnY, halfW, btnH, "gui.edit");
|
||||
editBtn.enabled = hasSelectedItem() && movePhase == 0 && !currentIsBuiltIn;
|
||||
addButton(editBtn);
|
||||
|
||||
GuiNpcButton cloneBtn = new GuiNpcButton(52, rightX + halfW + gap, btnY, halfW, btnH, "gui.copy");
|
||||
cloneBtn.enabled = hasSelectedItem() && movePhase == 0 && !currentIsBuiltIn;
|
||||
addButton(cloneBtn);
|
||||
|
||||
int removeY = btnY + btnH + gap;
|
||||
GuiNpcButton removeBtn = new GuiNpcButton(53, rightX, removeY, rightPanelW, btnH, "gui.remove");
|
||||
removeBtn.enabled = hasSelectedItem() && movePhase == 0 && !currentIsBuiltIn;
|
||||
removeBtn.setTextColor(0xFF5555);
|
||||
addButton(removeBtn);
|
||||
}
|
||||
|
||||
// ========== Scroll Events Override for Built-in ==========
|
||||
|
||||
@Override
|
||||
public void customScrollClicked(int i, int j, int k, GuiCustomScroll scroll) {
|
||||
if (scroll.id == 0 && showingBuiltIn) {
|
||||
String selected = itemScroll.getSelected();
|
||||
if (selected != null && !selected.equals(prevItemName)) {
|
||||
PacketClient.sendClient(new BuiltInAnimationGetPacket(selected));
|
||||
prevItemName = selected;
|
||||
}
|
||||
return;
|
||||
}
|
||||
super.customScrollClicked(i, j, k, scroll);
|
||||
}
|
||||
|
||||
// ========== Data ==========
|
||||
|
||||
@Override
|
||||
public void setData(Vector<String> list, HashMap<String, Integer> data, EnumScrollData type) {
|
||||
if (type == EnumScrollData.BUILTIN_ANIMATIONS) {
|
||||
this.builtInData = data;
|
||||
if (showingBuiltIn) {
|
||||
this.itemData = data;
|
||||
itemScroll.setList(getItemSearchList());
|
||||
initGui();
|
||||
}
|
||||
return;
|
||||
}
|
||||
super.setData(list, data, type);
|
||||
}
|
||||
|
||||
// ========== Animation Tick ==========
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
|
||||
AnimationData data = npc.display.animationData;
|
||||
if (!data.isActive() && playingAnimation) {
|
||||
playingAnimation = false;
|
||||
initGui();
|
||||
} else if (data.isActive()) {
|
||||
long time = mc.theWorld.getTotalWorldTime();
|
||||
if (time != prevTick) {
|
||||
npc.display.animationData.increaseTime();
|
||||
GuiNpcLabel label = getLabel(94);
|
||||
if (label != null && data.animation != null) {
|
||||
int frameIdx = data.animation.currentFrame;
|
||||
int frameTime = data.animation.currentFrameTime;
|
||||
int totalFrames = data.animation.frames.size();
|
||||
Frame curFrame = (Frame) data.animation.currentFrame();
|
||||
int duration = curFrame != null ? curFrame.getDuration() : 0;
|
||||
label.label = (frameIdx + 1) + "/" + totalFrames + " (" + frameTime + "/" + duration + "t)";
|
||||
}
|
||||
}
|
||||
prevTick = time;
|
||||
}
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
// ========== Actions ==========
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton guibutton) {
|
||||
int id = guibutton.id;
|
||||
|
||||
// Toggle built-in / custom
|
||||
if (id == 60) {
|
||||
showingBuiltIn = !showingBuiltIn;
|
||||
animation = new Animation();
|
||||
currentIsBuiltIn = false;
|
||||
playingAnimation = false;
|
||||
prevItemName = "";
|
||||
itemSearch = "";
|
||||
|
||||
if (showingBuiltIn) {
|
||||
// Show built-in flat list
|
||||
this.itemData = builtInData;
|
||||
selectedCatId = 0; // Ensure list is visible
|
||||
} else {
|
||||
// Re-enter category mode
|
||||
this.itemData = new HashMap<>();
|
||||
selectedCatId = -1;
|
||||
requestCategoryList();
|
||||
}
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
|
||||
// Playback controls
|
||||
AnimationData data = npc.display.animationData;
|
||||
if (id == 91) {
|
||||
if (!playingAnimation || !data.isActive()) {
|
||||
animation.currentFrame = 0;
|
||||
animation.currentFrameTime = 0;
|
||||
for (Frame frame : animation.frames) {
|
||||
for (FramePart framePart : frame.frameParts.values()) {
|
||||
framePart.prevRotations = new float[]{0, 0, 0};
|
||||
framePart.prevPivots = new float[]{0, 0, 0};
|
||||
}
|
||||
}
|
||||
}
|
||||
playingAnimation = true;
|
||||
data.setAnimation(this.animation);
|
||||
data.animation.paused = false;
|
||||
initGui();
|
||||
return;
|
||||
} else if (id == 92) {
|
||||
data.animation.paused = true;
|
||||
initGui();
|
||||
return;
|
||||
} else if (id == 93) {
|
||||
playingAnimation = false;
|
||||
data.animation.paused = false;
|
||||
showFirstFrame();
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
|
||||
super.actionPerformed(guibutton);
|
||||
}
|
||||
|
||||
// ========== Preview Rendering ==========
|
||||
|
||||
@Override
|
||||
protected void drawItemPreview(int centerX, int centerY, int mouseX, int mouseY, float partialTicks) {
|
||||
if (animation == null) return;
|
||||
if (!currentIsBuiltIn && animation.id == -1) return;
|
||||
|
||||
// Scale NPC with panel size
|
||||
float scale = Math.min(previewW, previewH) / 200f;
|
||||
float renderZoom = zoomed * scale;
|
||||
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
EntityLivingBase entity = this.npc;
|
||||
|
||||
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GL11.glPushMatrix();
|
||||
GL11.glTranslatef(centerX, centerY, 60F);
|
||||
GL11.glScalef(-renderZoom, renderZoom, renderZoom);
|
||||
GL11.glRotatef(180F, 0.0F, 0.0F, 1.0F);
|
||||
|
||||
float f2 = entity.renderYawOffset;
|
||||
float f3 = entity.rotationYaw;
|
||||
float f4 = entity.rotationPitch;
|
||||
float f7 = entity.rotationYawHead;
|
||||
float f5 = (float) centerX - mouseX;
|
||||
float f6 = (float) (centerY - 50) - mouseY;
|
||||
|
||||
GL11.glRotatef(135F, 0.0F, 1.0F, 0.0F);
|
||||
RenderHelper.enableStandardItemLighting();
|
||||
GL11.glRotatef(-135F, 0.0F, 1.0F, 0.0F);
|
||||
GL11.glRotatef(-(float) Math.atan(f6 / 800F) * 20F, 1.0F, 0.0F, 0.0F);
|
||||
entity.prevRenderYawOffset = entity.renderYawOffset = rotation;
|
||||
entity.prevRotationYaw = entity.rotationYaw = (float) Math.atan(f5 / 80F) * 40F + rotation;
|
||||
entity.rotationPitch = -(float) Math.atan(f6 / 80F) * 20F;
|
||||
entity.prevRotationYawHead = entity.rotationYawHead = entity.rotationYaw;
|
||||
GL11.glTranslatef(0.0F, entity.yOffset, 1F);
|
||||
RenderManager.instance.playerViewY = 180F;
|
||||
|
||||
try {
|
||||
RenderManager.instance.renderEntityWithPosYaw(entity, 0.0, 0.0, 0.0, 0.0F, 1.0F);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
entity.prevRenderYawOffset = entity.renderYawOffset = f2;
|
||||
entity.prevRotationYaw = entity.rotationYaw = f3;
|
||||
entity.rotationPitch = f4;
|
||||
entity.prevRotationYawHead = entity.rotationYawHead = f7;
|
||||
|
||||
RenderHelper.disableStandardItemLighting();
|
||||
GL11.glDisable(GL12.GL_RESCALE_NORMAL);
|
||||
OpenGlHelper.setActiveTexture(OpenGlHelper.lightmapTexUnit);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
OpenGlHelper.setActiveTexture(OpenGlHelper.defaultTexUnit);
|
||||
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawItemDetails(int x, int y, int w) {
|
||||
if (animation == null) return;
|
||||
if (!currentIsBuiltIn && animation.id == -1) return;
|
||||
|
||||
fontRendererObj.drawString(animation.name, x, y, 0xFFFFFF, true);
|
||||
y += 14;
|
||||
fontRendererObj.drawString("Frames: " + animation.frames.size(), x, y, 0xB5B5B5, false);
|
||||
y += 12;
|
||||
fontRendererObj.drawString("Speed: " + animation.speed, x, y, 0xB5B5B5, false);
|
||||
y += 12;
|
||||
String loopStr = animation.loop == 0 ? "None" : animation.loop == 1 ? "Loop" : "Mirror";
|
||||
fontRendererObj.drawString("Loop: " + loopStr, x, y, 0xB5B5B5, false);
|
||||
if (currentIsBuiltIn) {
|
||||
y += 12;
|
||||
fontRendererObj.drawString("(Built-in)", x, y, 0x55FF55, false);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Cleanup ==========
|
||||
|
||||
@Override
|
||||
public void onGuiClosed() {
|
||||
if (npc != null) {
|
||||
npc.display.animationData.setEnabled(false);
|
||||
}
|
||||
super.onGuiClosed();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package noppes.npcs.client.gui.global;
|
||||
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryItemsRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryListRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryMoveItemPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategorySavePacket;
|
||||
import kamkeel.npcs.network.packets.request.effects.EffectGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.effects.EffectRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.effects.EffectSavePacket;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import noppes.npcs.client.ClientCacheHandler;
|
||||
import noppes.npcs.client.CustomNpcResourceListener;
|
||||
import noppes.npcs.client.gui.SubGuiEffectGeneral;
|
||||
import noppes.npcs.client.gui.util.GuiDirectoryCategorized;
|
||||
import noppes.npcs.client.gui.util.SubGuiInterface;
|
||||
import noppes.npcs.client.renderer.ImageData;
|
||||
import noppes.npcs.constants.EnumCategoryType;
|
||||
import noppes.npcs.controllers.CustomEffectController;
|
||||
import noppes.npcs.controllers.data.Category;
|
||||
import noppes.npcs.controllers.data.CustomEffect;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static noppes.npcs.client.gui.player.inventory.GuiCNPCInventory.specialIcons;
|
||||
|
||||
public class GuiEffectDirectory extends GuiDirectoryCategorized {
|
||||
public CustomEffect effect = new CustomEffect();
|
||||
public EntityNPCInterface npc;
|
||||
|
||||
public GuiEffectDirectory(EntityNPCInterface npc) {
|
||||
super();
|
||||
this.npc = npc;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() { return "Effects"; }
|
||||
|
||||
@Override
|
||||
protected void requestCategoryList() {
|
||||
PacketClient.sendClient(new CategoryListRequestPacket(EnumCategoryType.EFFECT));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestItemsInCategory(int catId) {
|
||||
PacketClient.sendClient(new CategoryItemsRequestPacket(EnumCategoryType.EFFECT, catId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestItemData(int itemId) {
|
||||
PacketClient.sendClient(new EffectGetPacket(itemId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveCategory(Category cat) {
|
||||
PacketClient.sendClient(new CategorySavePacket(EnumCategoryType.EFFECT, cat.writeNBT(new NBTTagCompound())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveCategory(int catId) {
|
||||
PacketClient.sendClient(new CategoryRemovePacket(EnumCategoryType.EFFECT, catId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddItem(int catId) {
|
||||
String name = "New";
|
||||
while (itemData.containsKey(name)) name += "_";
|
||||
CustomEffect newEffect = new CustomEffect(-1, name);
|
||||
PacketClient.sendClient(new EffectSavePacket(newEffect.writeToNBT(false), ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveItem(int itemId) {
|
||||
PacketClient.sendClient(new EffectRemovePacket(itemId));
|
||||
effect = new CustomEffect();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEditItem() {
|
||||
if (effect != null && effect.id >= 0) {
|
||||
setSubGui(new SubGuiEffectGeneral(this, effect));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCloneItem() {
|
||||
if (effect != null && effect.id >= 0) {
|
||||
CustomEffect clone = effect.cloneEffect();
|
||||
while (itemData.containsKey(clone.name)) clone.name += "_";
|
||||
PacketClient.sendClient(new EffectSavePacket(clone.writeToNBT(false), ""));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onItemReceived(NBTTagCompound compound) {
|
||||
effect = new CustomEffect();
|
||||
effect.readFromNBT(compound);
|
||||
setPrevItemName(effect.name);
|
||||
if (effect.id != -1) {
|
||||
CustomEffectController.getInstance().getCustomEffects().replace(effect.id, effect);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasSelectedItem() {
|
||||
return effect != null && effect.id >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSelectedItemId() {
|
||||
return effect != null ? effect.id : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendMovePacket(int itemId, int destCatId) {
|
||||
PacketClient.sendClient(new CategoryMoveItemPacket(EnumCategoryType.EFFECT, itemId, destCatId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuiScreen getWindowedVariant() {
|
||||
return new GuiNPCManageEffects(npc);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveCurrentItem() {
|
||||
if (effect != null && effect.id >= 0 && prevItemName != null && !prevItemName.isEmpty()) {
|
||||
PacketClient.sendClient(new EffectSavePacket(effect.writeToNBT(false), prevItemName));
|
||||
prevItemName = effect.name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSubGuiClosed(SubGuiInterface subgui) {
|
||||
if (subgui instanceof SubGuiEffectGeneral) {
|
||||
if (effect != null && effect.id >= 0) {
|
||||
setPrevItemName(effect.name);
|
||||
if (selectedCatId >= 0) requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawItemPreview(int centerX, int centerY, int mouseX, int mouseY, float partialTicks) {
|
||||
if (effect == null || effect.id == -1) return;
|
||||
|
||||
int iconRenderSize = Math.min(previewW, previewH) / 3;
|
||||
iconRenderSize = Math.max(32, Math.min(iconRenderSize, 128));
|
||||
int x = centerX - iconRenderSize / 2;
|
||||
int y = centerY - iconRenderSize;
|
||||
|
||||
TextureManager textureManager = mc.getTextureManager();
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
ImageData data = ClientCacheHandler.getImageData(effect.icon);
|
||||
if (data.imageLoaded()) {
|
||||
data.bindTexture();
|
||||
int iconX = effect.iconX;
|
||||
int iconY = effect.iconY;
|
||||
int iconWidth = effect.getWidth();
|
||||
int iconHeight = effect.getHeight();
|
||||
int width = data.getTotalWidth();
|
||||
int height = data.getTotalHeight();
|
||||
func_152125_a(x, y, iconX, iconY, iconWidth, iconHeight, iconRenderSize, iconRenderSize, width, height);
|
||||
} else {
|
||||
textureManager.bindTexture(new ResourceLocation("customnpcs", "textures/marks/question.png"));
|
||||
func_152125_a(x, y, 0, 0, 1, 1, iconRenderSize, iconRenderSize, 1, 1);
|
||||
}
|
||||
GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
textureManager.bindTexture(specialIcons);
|
||||
func_152125_a(x, y, 0, 224, 16, 16, iconRenderSize, iconRenderSize, 256, 256);
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawItemDetails(int x, int y, int w) {
|
||||
if (effect == null || effect.id == -1) return;
|
||||
|
||||
String drawString = effect.getMenuName();
|
||||
fontRendererObj.drawString(drawString, x, y, CustomNpcResourceListener.DefaultTextColor, true);
|
||||
|
||||
y += 14;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("gui.name") + ": " + effect.name, x, y, 0xFFFFFF, false);
|
||||
y += 12;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("effect.runsEveryX") + ": " + effect.everyXTick + "t", x, y, 0xB5B5B5, false);
|
||||
y += 12;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("effect.defaultLength") + ": " + effect.length + "s", x, y, 0xB5B5B5, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
package noppes.npcs.client.gui.global;
|
||||
|
||||
import kamkeel.npcs.network.PacketClient;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryItemsRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryListRequestPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryMoveItemPacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategoryRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.category.CategorySavePacket;
|
||||
import kamkeel.npcs.network.packets.request.linked.LinkedGetPacket;
|
||||
import kamkeel.npcs.network.packets.request.linked.LinkedItemBuildPacket;
|
||||
import kamkeel.npcs.network.packets.request.linked.LinkedItemRemovePacket;
|
||||
import kamkeel.npcs.network.packets.request.linked.LinkedItemSavePacket;
|
||||
import kamkeel.npcs.util.ColorUtil;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import noppes.npcs.client.ClientCacheHandler;
|
||||
import noppes.npcs.client.CustomNpcResourceListener;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import noppes.npcs.client.gui.item.SubGuiLinkedItem;
|
||||
import noppes.npcs.client.gui.util.GuiDirectoryCategorized;
|
||||
import noppes.npcs.client.gui.util.GuiNpcButton;
|
||||
import noppes.npcs.client.gui.util.SubGuiInterface;
|
||||
import noppes.npcs.client.renderer.ImageData;
|
||||
import noppes.npcs.constants.EnumCategoryType;
|
||||
import noppes.npcs.controllers.data.Category;
|
||||
import noppes.npcs.controllers.data.LinkedItem;
|
||||
import noppes.npcs.entity.EntityNPCInterface;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class GuiLinkedItemDirectory extends GuiDirectoryCategorized {
|
||||
public LinkedItem linkedItem = null;
|
||||
public EntityNPCInterface npc;
|
||||
|
||||
public GuiLinkedItemDirectory(EntityNPCInterface npc) {
|
||||
super();
|
||||
this.npc = npc;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() { return "Linked Items"; }
|
||||
|
||||
@Override
|
||||
protected void requestCategoryList() {
|
||||
PacketClient.sendClient(new CategoryListRequestPacket(EnumCategoryType.LINKED_ITEM));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestItemsInCategory(int catId) {
|
||||
PacketClient.sendClient(new CategoryItemsRequestPacket(EnumCategoryType.LINKED_ITEM, catId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestItemData(int itemId) {
|
||||
LinkedGetPacket.GetItem(itemId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveCategory(Category cat) {
|
||||
PacketClient.sendClient(new CategorySavePacket(EnumCategoryType.LINKED_ITEM, cat.writeNBT(new NBTTagCompound())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveCategory(int catId) {
|
||||
PacketClient.sendClient(new CategoryRemovePacket(EnumCategoryType.LINKED_ITEM, catId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddItem(int catId) {
|
||||
String name = "New";
|
||||
while (itemData.containsKey(name)) name += "_";
|
||||
LinkedItem newItem = new LinkedItem(name);
|
||||
PacketClient.sendClient(new LinkedItemSavePacket(newItem.writeToNBT(false), ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveItem(int itemId) {
|
||||
PacketClient.sendClient(new LinkedItemRemovePacket(itemId));
|
||||
linkedItem = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEditItem() {
|
||||
if (linkedItem != null && linkedItem.id >= 0) {
|
||||
setSubGui(new SubGuiLinkedItem(this, linkedItem));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCloneItem() {
|
||||
if (linkedItem != null && linkedItem.id >= 0) {
|
||||
LinkedItem clone = linkedItem.clone();
|
||||
while (itemData.containsKey(clone.name)) clone.name += "_";
|
||||
clone.id = -1;
|
||||
PacketClient.sendClient(new LinkedItemSavePacket(clone.writeToNBT(false), ""));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onItemReceived(NBTTagCompound compound) {
|
||||
linkedItem = new LinkedItem();
|
||||
linkedItem.readFromNBT(compound);
|
||||
setPrevItemName(linkedItem.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasSelectedItem() {
|
||||
return linkedItem != null && linkedItem.id >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSelectedItemId() {
|
||||
return linkedItem != null ? linkedItem.id : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendMovePacket(int itemId, int destCatId) {
|
||||
PacketClient.sendClient(new CategoryMoveItemPacket(EnumCategoryType.LINKED_ITEM, itemId, destCatId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuiScreen getWindowedVariant() {
|
||||
return new GuiNPCManageLinked(npc);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveCurrentItem() {
|
||||
if (linkedItem != null && linkedItem.id >= 0 && prevItemName != null && !prevItemName.isEmpty()) {
|
||||
PacketClient.sendClient(new LinkedItemSavePacket(linkedItem.writeToNBT(false), prevItemName));
|
||||
prevItemName = linkedItem.name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSubGuiClosed(SubGuiInterface subgui) {
|
||||
if (subgui instanceof SubGuiLinkedItem) {
|
||||
if (linkedItem != null && linkedItem.id >= 0) {
|
||||
setPrevItemName(linkedItem.name);
|
||||
if (selectedCatId >= 0) requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initRightPanel(int startY) {
|
||||
// Reserve space for: edit+build row, copy+remove row, ID label
|
||||
int bottomH = (btnH + gap) * 2 + 14;
|
||||
previewX = rightX;
|
||||
previewY = contentY;
|
||||
previewW = rightPanelW;
|
||||
previewH = contentH - bottomH - gap;
|
||||
|
||||
int halfW = (rightPanelW - gap) / 2;
|
||||
|
||||
// Edit + Build on first row
|
||||
int row1Y = contentY + contentH - btnH * 2 - gap;
|
||||
GuiNpcButton editBtn = new GuiNpcButton(51, rightX, row1Y, halfW, btnH, "gui.edit");
|
||||
editBtn.enabled = hasSelectedItem() && movePhase == 0;
|
||||
addButton(editBtn);
|
||||
|
||||
GuiNpcButton buildBtn = new GuiNpcButton(56, rightX + halfW + gap, row1Y, halfW, btnH, "gui.build");
|
||||
buildBtn.enabled = hasSelectedItem() && movePhase == 0;
|
||||
addButton(buildBtn);
|
||||
|
||||
// Copy + Remove on second row
|
||||
int row2Y = row1Y + btnH + gap;
|
||||
GuiNpcButton cloneBtn = new GuiNpcButton(52, rightX, row2Y, halfW, btnH, "gui.copy");
|
||||
cloneBtn.enabled = hasSelectedItem() && movePhase == 0;
|
||||
addButton(cloneBtn);
|
||||
|
||||
GuiNpcButton removeBtn = new GuiNpcButton(53, rightX + halfW + gap, row2Y, halfW, btnH, "gui.remove");
|
||||
removeBtn.enabled = hasSelectedItem() && movePhase == 0;
|
||||
removeBtn.setTextColor(0xFF5555);
|
||||
addButton(removeBtn);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton guibutton) {
|
||||
if (guibutton.id == 56 && linkedItem != null && linkedItem.id >= 0) {
|
||||
PacketClient.sendClient(new LinkedItemBuildPacket(linkedItem.getId()));
|
||||
return;
|
||||
}
|
||||
super.actionPerformed(guibutton);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawItemPreview(int centerX, int centerY, int mouseX, int mouseY, float partialTicks) {
|
||||
if (linkedItem == null || linkedItem.id < 0) return;
|
||||
|
||||
int iconRenderSize = Math.min(previewW, previewH) / 3;
|
||||
iconRenderSize = Math.max(32, Math.min(iconRenderSize, 128));
|
||||
int x = centerX - iconRenderSize / 2;
|
||||
int y = centerY - iconRenderSize;
|
||||
|
||||
TextureManager textureManager = mc.getTextureManager();
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
ImageData imageData = ClientCacheHandler.getImageData(linkedItem.display.texture);
|
||||
if (imageData.imageLoaded()) {
|
||||
float[] colors = ColorUtil.hexToRGB(linkedItem.display.itemColor);
|
||||
GL11.glColor3f(colors[0], colors[1], colors[2]);
|
||||
imageData.bindTexture();
|
||||
int iconWidth = imageData.getTotalWidth();
|
||||
int iconHeight = imageData.getTotalHeight();
|
||||
func_152125_a(x, y, 0, 0, iconWidth, iconHeight, iconRenderSize, iconRenderSize, iconWidth, iconHeight);
|
||||
} else {
|
||||
textureManager.bindTexture(new ResourceLocation("customnpcs", "textures/marks/question.png"));
|
||||
func_152125_a(x, y, 0, 0, 1, 1, iconRenderSize, iconRenderSize, 1, 1);
|
||||
}
|
||||
GL11.glColor3f(1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawItemDetails(int x, int y, int w) {
|
||||
if (linkedItem == null || linkedItem.id < 0) return;
|
||||
|
||||
fontRendererObj.drawString(linkedItem.name, x, y, CustomNpcResourceListener.DefaultTextColor, true);
|
||||
y += 14;
|
||||
|
||||
int labelColor = 0xffae0d;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("display.version") + ": " + linkedItem.version, x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
labelColor = 0xff5714;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("display.maxStack") + ": " + linkedItem.stackSize, x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
labelColor = 0xf7ca28;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("display.digSpeed") + ": " + linkedItem.digSpeed, x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
labelColor = 0x29d6b9;
|
||||
String[] useActions = {
|
||||
StatCollector.translateToLocal("use_action.none"),
|
||||
StatCollector.translateToLocal("use_action.block"),
|
||||
StatCollector.translateToLocal("use_action.eat"),
|
||||
StatCollector.translateToLocal("use_action.drink"),
|
||||
StatCollector.translateToLocal("use_action.bow")
|
||||
};
|
||||
int useActionIndex;
|
||||
switch (linkedItem.itemUseAction) {
|
||||
case 0: useActionIndex = 0; break;
|
||||
case 1: useActionIndex = 1; break;
|
||||
case 2: useActionIndex = 4; break;
|
||||
case 3: useActionIndex = 2; break;
|
||||
case 4: useActionIndex = 3; break;
|
||||
default: useActionIndex = 0; break;
|
||||
}
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("display.useAction") + ": " + useActions[useActionIndex], x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
String[] armorOptions = {
|
||||
StatCollector.translateToLocal("armor_type.none"),
|
||||
StatCollector.translateToLocal("armor_type.all"),
|
||||
StatCollector.translateToLocal("armor_type.head"),
|
||||
StatCollector.translateToLocal("armor_type.chestplate"),
|
||||
StatCollector.translateToLocal("armor_type.leggings"),
|
||||
StatCollector.translateToLocal("armor_type.boots")
|
||||
};
|
||||
int armorIndex;
|
||||
if (linkedItem.armorType == -2) armorIndex = 0;
|
||||
else if (linkedItem.armorType == -1) armorIndex = 1;
|
||||
else armorIndex = linkedItem.armorType + 2;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("display.armor") + ": " + armorOptions[armorIndex], x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
labelColor = 0x7cff54;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("display.isTool") + ": " + ("" + linkedItem.isTool).toUpperCase(), x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("display.isNormalItem") + ": " + ("" + linkedItem.isNormalItem).toUpperCase(), x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
labelColor = 0xce75fa;
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("model.scale") + ": " + linkedItem.display.scaleX + ", " + linkedItem.display.scaleY + ", " + linkedItem.display.scaleZ, x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("model.rotate") + ": " + linkedItem.display.rotationX + ", " + linkedItem.display.rotationY + ", " + linkedItem.display.rotationZ, x, y, labelColor, false);
|
||||
y += 12;
|
||||
|
||||
fontRendererObj.drawString(StatCollector.translateToLocal("model.translate") + ": " + linkedItem.display.translateX + ", " + linkedItem.display.translateY + ", " + linkedItem.display.translateZ, x, y, labelColor, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,15 +65,22 @@ public class GuiNPCManageAnimations extends GuiModelInterface2 implements IScrol
|
||||
public void initGui() {
|
||||
super.initGui();
|
||||
|
||||
// Toggle button - added FIRST to maintain stable buttonList index
|
||||
// (prevents double-toggle when initGui() is called from actionPerformed)
|
||||
// Fullscreen button - FIRST
|
||||
GuiNpcButton fullBtn = new GuiNpcButton(66, guiLeft + 368, guiTop + 8, 45, 20, "gui.fullscreen");
|
||||
fullBtn.setTextColor(0x55FF55);
|
||||
fullBtn.setHoverText("gui.fullscreen.tooltip");
|
||||
this.addButton(fullBtn);
|
||||
|
||||
// Toggle button
|
||||
String toggleLabel = showingBuiltIn ? "gui.builtin" : "gui.custom";
|
||||
this.addButton(new GuiNpcButton(10, guiLeft + 368, guiTop + 56, 45, 20, toggleLabel));
|
||||
GuiNpcButton toggleBtn = new GuiNpcButton(10, guiLeft + 368, guiTop + 36, 45, 20, toggleLabel);
|
||||
toggleBtn.setTextColor(showingBuiltIn ? 0x55FFFF : 0xFFFFFF);
|
||||
this.addButton(toggleBtn);
|
||||
|
||||
// Add/Remove buttons - only show for custom animations
|
||||
if (!showingBuiltIn) {
|
||||
this.addButton(new GuiNpcButton(0, guiLeft + 368, guiTop + 8, 45, 20, "gui.add"));
|
||||
this.addButton(new GuiNpcButton(1, guiLeft + 368, guiTop + 32, 45, 20, "gui.remove"));
|
||||
this.addButton(new GuiNpcButton(0, guiLeft + 368, guiTop + 60, 45, 20, "gui.add"));
|
||||
this.addButton(new GuiNpcButton(1, guiLeft + 368, guiTop + 84, 45, 20, "gui.remove"));
|
||||
}
|
||||
|
||||
if (scrollAnimations == null) {
|
||||
@@ -198,6 +205,11 @@ public class GuiNPCManageAnimations extends GuiModelInterface2 implements IScrol
|
||||
protected void actionPerformed(GuiButton guibutton) {
|
||||
GuiNpcButton button = (GuiNpcButton) guibutton;
|
||||
|
||||
if (button.id == 66) {
|
||||
mc.displayGuiScreen(new GuiAnimationDirectory(npc));
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle button
|
||||
if (button.id == 10) {
|
||||
showingBuiltIn = !showingBuiltIn;
|
||||
|
||||
@@ -57,15 +57,21 @@ public class GuiNPCManageEffects extends GuiNPCInterface2 implements ICustomScro
|
||||
|
||||
public void initGui() {
|
||||
super.initGui();
|
||||
addButton(new GuiNpcButton(0, guiLeft + 368, guiTop + 8, 45, 20, "gui.add"));
|
||||
// Fullscreen button - FIRST
|
||||
GuiNpcButton fullBtn = new GuiNpcButton(66, guiLeft + 368, guiTop + 8, 45, 20, "gui.fullscreen");
|
||||
fullBtn.setTextColor(0x55FF55);
|
||||
fullBtn.setHoverText("gui.fullscreen.tooltip");
|
||||
addButton(fullBtn);
|
||||
|
||||
addButton(new GuiNpcButton(1, guiLeft + 368, guiTop + 32, 45, 20, "gui.remove"));
|
||||
addButton(new GuiNpcButton(0, guiLeft + 368, guiTop + 36, 45, 20, "gui.add"));
|
||||
|
||||
addButton(new GuiNpcButton(1, guiLeft + 368, guiTop + 60, 45, 20, "gui.remove"));
|
||||
getButton(1).enabled = effect != null && effect.id != -1;
|
||||
|
||||
addButton(new GuiNpcButton(2, guiLeft + 368, guiTop + 56, 45, 20, "gui.copy"));
|
||||
addButton(new GuiNpcButton(2, guiLeft + 368, guiTop + 84, 45, 20, "gui.copy"));
|
||||
getButton(2).enabled = effect != null && effect.id != -1;
|
||||
|
||||
addButton(new GuiNpcButton(3, guiLeft + 368, guiTop + 80, 45, 20, "gui.edit"));
|
||||
addButton(new GuiNpcButton(3, guiLeft + 368, guiTop + 108, 45, 20, "gui.edit"));
|
||||
getButton(3).enabled = effect != null && effect.id != -1;
|
||||
|
||||
if (scrollEffects == null) {
|
||||
@@ -106,6 +112,11 @@ public class GuiNPCManageEffects extends GuiNPCInterface2 implements ICustomScro
|
||||
PacketClient.sendClient(new EffectSavePacket(effect.writeToNBT(false), ""));
|
||||
}
|
||||
|
||||
if (button.id == 66) {
|
||||
mc.displayGuiScreen(new GuiEffectDirectory(npc));
|
||||
return;
|
||||
}
|
||||
|
||||
if (effect == null)
|
||||
return;
|
||||
|
||||
|
||||
@@ -86,6 +86,15 @@ public class GuiNPCManageLinked extends GuiNPCInterface2 implements IScrollData,
|
||||
|
||||
int y = guiTop + 8;
|
||||
|
||||
// Fullscreen button - FIRST (only for Items tab)
|
||||
if (tab == 1) {
|
||||
GuiNpcButton fullBtn = new GuiNpcButton(66, guiLeft + 368, y, 45, 20, "gui.fullscreen");
|
||||
fullBtn.setTextColor(0x55FF55);
|
||||
fullBtn.setHoverText("gui.fullscreen.tooltip");
|
||||
this.addButton(fullBtn);
|
||||
y += 22;
|
||||
}
|
||||
|
||||
this.addButton(new GuiNpcButton(10, guiLeft + 368, y, 45, 20, "gui.npcs"));
|
||||
this.addButton(new GuiNpcButton(11, guiLeft + 368, y += 22, 45, 20, "gui.items"));
|
||||
getButton(10).enabled = tab == 1;
|
||||
@@ -216,6 +225,7 @@ public class GuiNPCManageLinked extends GuiNPCInterface2 implements IScrollData,
|
||||
}
|
||||
|
||||
private List<String> getSearchList() {
|
||||
if (data == null) return new ArrayList<String>();
|
||||
if (search.isEmpty()) {
|
||||
return new ArrayList<String>(this.data.keySet());
|
||||
}
|
||||
@@ -265,6 +275,11 @@ public class GuiNPCManageLinked extends GuiNPCInterface2 implements IScrollData,
|
||||
scroll.setSelected("");
|
||||
}
|
||||
|
||||
if (button.id == 66) {
|
||||
mc.displayGuiScreen(new GuiLinkedItemDirectory(npc));
|
||||
return;
|
||||
}
|
||||
|
||||
if (linkedItem == null)
|
||||
return;
|
||||
|
||||
|
||||
@@ -145,24 +145,34 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
public void initGui() {
|
||||
super.initGui();
|
||||
|
||||
// Fullscreen button - FIRST
|
||||
GuiNpcButton fullBtn = new GuiNpcButton(66, guiLeft + 368, guiTop + 8, 45, 20, "gui.fullscreen");
|
||||
fullBtn.setTextColor(0x55FF55);
|
||||
fullBtn.setHoverText("gui.fullscreen.tooltip");
|
||||
addButton(fullBtn);
|
||||
|
||||
// Toggle button — cycles: Custom → Built-in → Chained
|
||||
String toggleLabel = showingChained ? "gui.chained" : (showingBuiltIn ? "gui.builtin" : "gui.custom");
|
||||
addButton(new GuiNpcButton(BTN_TOGGLE_VIEW, guiLeft + 368, guiTop + 52, 45, 20, toggleLabel));
|
||||
GuiNpcButton toggleBtn = new GuiNpcButton(BTN_TOGGLE_VIEW, guiLeft + 368, guiTop + 36, 45, 20, toggleLabel);
|
||||
if (showingChained) toggleBtn.setTextColor(0xFFFF55);
|
||||
else if (showingBuiltIn) toggleBtn.setTextColor(0x55FFFF);
|
||||
else toggleBtn.setTextColor(0xFFFFFF);
|
||||
addButton(toggleBtn);
|
||||
|
||||
if (showingChained) {
|
||||
// Chained view buttons
|
||||
addButton(new GuiNpcButton(BTN_ADD, guiLeft + 368, guiTop + 8, 45, 20, "gui.add"));
|
||||
addButton(new GuiNpcButton(BTN_REMOVE, guiLeft + 368, guiTop + 30, 45, 20, "gui.remove"));
|
||||
addButton(new GuiNpcButton(BTN_ADD, guiLeft + 368, guiTop + 60, 45, 20, "gui.add"));
|
||||
addButton(new GuiNpcButton(BTN_REMOVE, guiLeft + 368, guiTop + 84, 45, 20, "gui.remove"));
|
||||
getButton(BTN_REMOVE).setEnabled(selected != null && !selected.isEmpty() && chainedData.containsKey(selected));
|
||||
addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 368, guiTop + 74, 45, 20, "gui.edit"));
|
||||
addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 368, guiTop + 108, 45, 20, "gui.edit"));
|
||||
getButton(BTN_EDIT).setEnabled(selected != null && !selected.isEmpty() && selectedChain != null);
|
||||
} else if (!showingBuiltIn) {
|
||||
// Custom view buttons
|
||||
addButton(new GuiNpcButton(BTN_ADD, guiLeft + 368, guiTop + 8, 45, 20, "gui.add"));
|
||||
addButton(new GuiNpcButton(BTN_REMOVE, guiLeft + 368, guiTop + 30, 45, 20, "gui.remove"));
|
||||
addButton(new GuiNpcButton(BTN_ADD, guiLeft + 368, guiTop + 60, 45, 20, "gui.add"));
|
||||
addButton(new GuiNpcButton(BTN_REMOVE, guiLeft + 368, guiTop + 84, 45, 20, "gui.remove"));
|
||||
getButton(BTN_REMOVE).setEnabled(selected != null && !selected.isEmpty() && customData.containsKey(selected));
|
||||
if (!currentIsBuiltIn) {
|
||||
addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 368, guiTop + 74, 45, 20, "gui.edit"));
|
||||
addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 368, guiTop + 108, 45, 20, "gui.edit"));
|
||||
getButton(BTN_EDIT).setEnabled(selected != null && !selected.isEmpty() && selectedAbility != null);
|
||||
}
|
||||
}
|
||||
@@ -300,6 +310,13 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
|
||||
|
||||
int id = guibutton.id;
|
||||
|
||||
// Fullscreen
|
||||
if (id == 66) {
|
||||
previewExecutor.stop();
|
||||
mc.displayGuiScreen(new GuiAbilityDirectory(npc));
|
||||
return;
|
||||
}
|
||||
|
||||
// User type filter: Both → NPC → Player → Both
|
||||
if (id == BTN_USER_TYPE_FILTER) {
|
||||
userTypeFilter = (userTypeFilter + 1) % 3;
|
||||
|
||||
@@ -12,7 +12,7 @@ import noppes.npcs.client.ClientCacheHandler;
|
||||
import noppes.npcs.client.CustomNpcResourceListener;
|
||||
import noppes.npcs.client.renderer.AnimationHelper;
|
||||
import noppes.npcs.client.gui.SubGuiColorSelector;
|
||||
import noppes.npcs.client.gui.global.GuiNPCManageLinked;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import noppes.npcs.client.gui.script.GuiScriptInterface;
|
||||
import noppes.npcs.client.gui.util.GuiButtonBiDirectional;
|
||||
import noppes.npcs.client.gui.util.GuiMenuTopButton;
|
||||
@@ -37,7 +37,7 @@ public class SubGuiLinkedItem extends SubGuiInterface implements ITextfieldListe
|
||||
private int tab = -1;
|
||||
private int colorPicked = 0;
|
||||
|
||||
public SubGuiLinkedItem(GuiNPCManageLinked parent, LinkedItem linkedItem) {
|
||||
public SubGuiLinkedItem(GuiScreen parent, LinkedItem linkedItem) {
|
||||
this.linkedItem = linkedItem;
|
||||
this.parent = parent;
|
||||
this.originalName = linkedItem.name;
|
||||
|
||||
@@ -17,6 +17,7 @@ import net.minecraft.util.StatCollector;
|
||||
import noppes.npcs.client.ClientCacheHandler;
|
||||
import noppes.npcs.client.gui.hud.ability.AbilityIcon;
|
||||
import noppes.npcs.client.gui.util.GuiMenuTopButton;
|
||||
import noppes.npcs.client.gui.util.GuiNpcButton;
|
||||
import noppes.npcs.client.gui.util.ISubGuiListener;
|
||||
import noppes.npcs.client.gui.util.SubGuiInterface;
|
||||
import noppes.npcs.controllers.data.AbilityHotbarData;
|
||||
@@ -44,6 +45,8 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
private int subTab = 0;
|
||||
private static final int BTN_TAB_ABILITIES = -200;
|
||||
private static final int BTN_TAB_TOGGLES = -201;
|
||||
private static final int BTN_SCROLL_LEFT = -202;
|
||||
private static final int BTN_SCROLL_RIGHT = -203;
|
||||
|
||||
// ═══ Panel Bounds (relative to guiLeft/guiTop) ═══
|
||||
// Selection panel (RIGHT side - icon grid)
|
||||
@@ -148,6 +151,19 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
buildFilteredList();
|
||||
updateHotbarIcons();
|
||||
|
||||
// Scroll buttons for ability grid
|
||||
int totalRows = (filteredKeys.size() + gridCols - 1) / gridCols;
|
||||
int visibleRows = getGridVisibleRows();
|
||||
if (totalRows > visibleRows) {
|
||||
int scrollBtnY = guiTop + selBottom + 1;
|
||||
int btnW = 14;
|
||||
int btnH = 12;
|
||||
addButton(new GuiNpcButton(BTN_SCROLL_LEFT, guiLeft + selRight - btnW * 2 - 2, scrollBtnY, btnW, btnH, "<"));
|
||||
addButton(new GuiNpcButton(BTN_SCROLL_RIGHT, guiLeft + selRight - btnW, scrollBtnY, btnW, btnH, ">"));
|
||||
getButton(BTN_SCROLL_LEFT).setEnabled(scrollRow > 0);
|
||||
getButton(BTN_SCROLL_RIGHT).setEnabled(scrollRow < getMaxScrollRow());
|
||||
}
|
||||
|
||||
// Capture initial selected key for close sync
|
||||
if (initialSelectedKey == null) {
|
||||
PlayerData playerData = ClientCacheHandler.playerData;
|
||||
@@ -217,7 +233,7 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
tempActions.add(action);
|
||||
}
|
||||
|
||||
// Sort: toggles tab sorts enabled first, then alphabetically
|
||||
// Sort: toggles tab sorts enabled first, then by type, then by display name
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
for (int i = 0; i < tempNames.size(); i++) indices.add(i);
|
||||
final boolean sortTogglesFirst = (subTab == 1);
|
||||
@@ -229,6 +245,16 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
boolean bToggled = playerData.abilityData.isAbilityToggled(tempKeys.get(b));
|
||||
if (aToggled != bToggled) return aToggled ? -1 : 1;
|
||||
}
|
||||
// Sort by type first
|
||||
Ability abilityA = tempAbilities.get(a);
|
||||
Ability abilityB = tempAbilities.get(b);
|
||||
String typeA = abilityA != null ? abilityA.getTypeId() : "";
|
||||
String typeB = abilityB != null ? abilityB.getTypeId() : "";
|
||||
if (typeA == null) typeA = "";
|
||||
if (typeB == null) typeB = "";
|
||||
int typeCompare = typeA.compareToIgnoreCase(typeB);
|
||||
if (typeCompare != 0) return typeCompare;
|
||||
// Then by display name (strip color codes for comparison)
|
||||
return stripFormatting(tempNames.get(a)).compareToIgnoreCase(stripFormatting(tempNames.get(b)));
|
||||
}
|
||||
});
|
||||
@@ -432,15 +458,7 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
|
||||
// Scroll indicator (drawn outside scissor)
|
||||
int totalRows = (filteredKeys.size() + gridCols - 1) / gridCols;
|
||||
if (totalRows > visibleRows) {
|
||||
FontRenderer fr = mc.fontRenderer;
|
||||
String scrollText = (scrollRow + 1) + "/" + (totalRows - visibleRows + 1);
|
||||
int textX = guiLeft + selRight - fr.getStringWidth(scrollText) - 4;
|
||||
int textY = guiTop + selBottom + 1;
|
||||
fr.drawStringWithShadow(scrollText, textX, textY, 0x888888);
|
||||
}
|
||||
// Scroll buttons are added in initGui()
|
||||
}
|
||||
|
||||
private void drawInfoPanel() {
|
||||
@@ -1070,6 +1088,16 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
if (btn.id == BTN_SCROLL_LEFT) {
|
||||
scrollRow = Math.max(0, scrollRow - 1);
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
if (btn.id == BTN_SCROLL_RIGHT) {
|
||||
scrollRow = Math.min(getMaxScrollRow(), scrollRow + 1);
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveHotbarSlot(int slotIndex, String abilityKey) {
|
||||
|
||||
@@ -0,0 +1,755 @@
|
||||
package noppes.npcs.client.gui.util;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import noppes.npcs.controllers.data.Category;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.GuiYesNo;
|
||||
import net.minecraft.client.gui.GuiYesNoCallback;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import noppes.npcs.client.CustomNpcResourceListener;
|
||||
import noppes.npcs.client.NoppesUtil;
|
||||
import noppes.npcs.client.gui.SubGuiEditText;
|
||||
import noppes.npcs.constants.EnumScrollData;
|
||||
import org.lwjgl.input.Mouse;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Fullscreen categorized manager base class (Cloner-inspired layout).
|
||||
* Left=categories with icons, Center=items, Right=preview+details.
|
||||
* Top bar has item operations (Add/Remove/Clone/Move) + search.
|
||||
*/
|
||||
public abstract class GuiDirectoryCategorized extends GuiDirectory
|
||||
implements IScrollData, IGuiData, GuiYesNoCallback, ISubGuiListener {
|
||||
|
||||
// ========== Scrolls ==========
|
||||
protected GuiCustomScrollIcons catScroll = new GuiCustomScrollIcons(this, 2);
|
||||
protected GuiCustomScroll itemScroll = new GuiCustomScroll(this, 0);
|
||||
|
||||
// ========== Data ==========
|
||||
protected HashMap<String, Integer> catData = new HashMap<>();
|
||||
protected HashMap<String, Integer> itemData = new HashMap<>();
|
||||
|
||||
// ========== Selection ==========
|
||||
protected String prevCatName = "";
|
||||
protected String prevItemName = "";
|
||||
protected int selectedCatId = -1;
|
||||
|
||||
// ========== Search ==========
|
||||
protected String catSearch = "";
|
||||
protected String itemSearch = "";
|
||||
|
||||
// ========== Preview ==========
|
||||
protected float zoomed = 60;
|
||||
protected float rotation;
|
||||
protected int previewX, previewY, previewW, previewH;
|
||||
|
||||
// ========== Move Workflow ==========
|
||||
protected int movePhase = 0; // 0=off, 1=selecting items, 2=selecting destination
|
||||
protected HashSet<String> moveSelection = new HashSet<>();
|
||||
protected static final int MAX_MOVE_ITEMS = 5;
|
||||
|
||||
// ========== New Item Category ==========
|
||||
protected int pendingNewItemCatId = -1;
|
||||
|
||||
// ========== Category Data Loaded ==========
|
||||
private boolean itemDataLoaded = false;
|
||||
|
||||
// ========== Collapse ==========
|
||||
protected boolean leftCollapsed = false;
|
||||
private static final ResourceLocation DIRECTORY_ICON = new ResourceLocation("customnpcs", "textures/gui/directory.png");
|
||||
|
||||
// ========== Scroll Reset Flag ==========
|
||||
private boolean resetItemScroll = false;
|
||||
|
||||
// ========== Nav list data ==========
|
||||
private List<String> navNames = new ArrayList<>();
|
||||
private List<Integer> navIcons = new ArrayList<>();
|
||||
|
||||
// ========== Constructor ==========
|
||||
public GuiDirectoryCategorized() {
|
||||
super();
|
||||
leftPanelPercent = 0.15f;
|
||||
rightPanelPercent = 0.35f;
|
||||
minLeftPanelW = 120;
|
||||
minRightPanelW = 150;
|
||||
requestCategoryList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this GUI currently has a category panel.
|
||||
* Subclasses override to return false for built-in modes.
|
||||
*/
|
||||
protected boolean hasCategories() { return true; }
|
||||
|
||||
@Override
|
||||
protected void computeLayout() {
|
||||
if (leftCollapsed || !hasCategories()) {
|
||||
leftPanelPercent = 0.0f;
|
||||
minLeftPanelW = 0;
|
||||
} else if (leftPanelPercent <= 0) {
|
||||
leftPanelPercent = 0.15f;
|
||||
minLeftPanelW = 120;
|
||||
}
|
||||
super.computeLayout();
|
||||
|
||||
// Force 50/50 split between center and right when left panel is hidden
|
||||
if (leftPanelW <= 0) {
|
||||
int available = usableW - 3 * gap;
|
||||
rightPanelW = available / 2;
|
||||
contentW = available - rightPanelW;
|
||||
contentX = originX + gap;
|
||||
rightX = contentX + contentW + gap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawPanels() {
|
||||
if (leftPanelW <= 0) {
|
||||
if (rightPanelW > 0) {
|
||||
GuiUtil.drawRectD(rightX - 1, contentY - 1, rightX + rightPanelW + 1, originY + usableH + 1, panelBorder);
|
||||
}
|
||||
} else {
|
||||
super.drawPanels();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Abstract hooks ==========
|
||||
protected abstract String getTitle();
|
||||
protected abstract void requestCategoryList();
|
||||
protected abstract void requestItemsInCategory(int catId);
|
||||
protected abstract void requestItemData(int itemId);
|
||||
protected abstract void onSaveCategory(Category cat);
|
||||
protected abstract void onRemoveCategory(int catId);
|
||||
protected abstract void onAddItem(int catId);
|
||||
protected abstract void onRemoveItem(int itemId);
|
||||
protected abstract void onEditItem();
|
||||
protected abstract void onCloneItem();
|
||||
protected abstract void onItemReceived(NBTTagCompound compound);
|
||||
protected abstract boolean hasSelectedItem();
|
||||
protected abstract int getSelectedItemId();
|
||||
|
||||
protected GuiScreen getWindowedVariant() { return null; }
|
||||
protected void drawItemPreview(int centerX, int centerY, int mouseX, int mouseY, float partialTicks) {}
|
||||
protected void drawItemDetails(int x, int y, int w) {}
|
||||
protected void saveCurrentItem() {}
|
||||
|
||||
protected void setPrevItemName(String name) {
|
||||
this.prevItemName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for subclasses to add buttons at the START of the top bar (before Add/Remove/Clone/Move).
|
||||
* Return the new x position after adding buttons.
|
||||
*/
|
||||
protected int initExtraTopBarButtons(int x, int topBtnY) {
|
||||
return x;
|
||||
}
|
||||
|
||||
// ========== Top Bar ==========
|
||||
@Override
|
||||
protected void initTopBar(int topBtnY) {
|
||||
int x = originX + 2;
|
||||
|
||||
// Collapse/Expand button
|
||||
GuiNpcButton collapseBtn = new GuiNpcButton(19, x, topBtnY, btnH, btnH, "");
|
||||
collapseBtn.enabled = hasCategories() && movePhase == 0;
|
||||
addButton(collapseBtn);
|
||||
x += btnH + 2;
|
||||
|
||||
x = initExtraTopBarButtons(x, topBtnY);
|
||||
int topBtnW = 55;
|
||||
|
||||
// ADD
|
||||
GuiNpcButton addBtn = new GuiNpcButton(50, x, topBtnY, topBtnW, btnH, "gui.add");
|
||||
addBtn.enabled = selectedCatId >= 0 && movePhase == 0;
|
||||
addButton(addBtn);
|
||||
x += topBtnW + 2;
|
||||
|
||||
// MOVE
|
||||
String moveLabel = movePhase > 0 ? "Moving" : "Move";
|
||||
GuiNpcButton moveBtn = new GuiNpcButton(54, x, topBtnY, topBtnW, btnH, moveLabel);
|
||||
moveBtn.enabled = selectedCatId >= 0 && !leftCollapsed && hasCategories();
|
||||
addButton(moveBtn);
|
||||
x += topBtnW + 2;
|
||||
|
||||
// Confirm (only during move phase 1)
|
||||
if (movePhase == 1) {
|
||||
GuiNpcButton confirmBtn = new GuiNpcButton(55, x, topBtnY, topBtnW, btnH, "Confirm");
|
||||
confirmBtn.enabled = !moveSelection.isEmpty();
|
||||
addButton(confirmBtn);
|
||||
x += topBtnW + 2;
|
||||
}
|
||||
|
||||
// Right side: close, minimize
|
||||
int closeX = originX + usableW - btnH - 2;
|
||||
addButton(new GuiNpcButton(17, closeX, topBtnY, btnH, btnH, "X"));
|
||||
int minimizeX = closeX - btnH - 2;
|
||||
if (getWindowedVariant() != null) {
|
||||
addButton(new GuiNpcButton(18, minimizeX, topBtnY, btnH, btnH, "-"));
|
||||
}
|
||||
|
||||
// Search field is now under the center panel — see initCenterPanel()
|
||||
}
|
||||
|
||||
// ========== Left Panel: Category Navigation ==========
|
||||
@Override
|
||||
protected void initLeftPanel() {
|
||||
if (leftCollapsed || !hasCategories()) return;
|
||||
boolean catSelected = selectedCatId > 0; // non-Uncategorized selected
|
||||
int bottomRows = catSelected ? 3 : 2;
|
||||
int bottomH = bottomRows * (btnH + gap);
|
||||
int navH = contentH - bottomH;
|
||||
|
||||
buildNavList();
|
||||
|
||||
int savedCatScrollY = catScroll.scrollY;
|
||||
catScroll.clear();
|
||||
catScroll.colors.clear();
|
||||
catScroll.setSize(leftPanelW, navH);
|
||||
catScroll.guiLeft = originX;
|
||||
catScroll.guiTop = contentY;
|
||||
catScroll.setListWithIcons(navNames, navIcons);
|
||||
catScroll.setSelected(prevCatName);
|
||||
catScroll.scrollY = Math.max(0, Math.min(savedCatScrollY, catScroll.maxScrollY));
|
||||
|
||||
// Phase 2: ensure scroll is clickable for destination selection
|
||||
if (movePhase == 2) {
|
||||
catScroll.setSelectable(true);
|
||||
}
|
||||
|
||||
addScroll(catScroll);
|
||||
|
||||
// Category search
|
||||
int searchY = contentY + navH + gap;
|
||||
addTextField(new GuiNpcTextField(3, this, fontRendererObj, originX, searchY, leftPanelW, btnH, catSearch));
|
||||
|
||||
// Add category button
|
||||
int addY = searchY + btnH + gap;
|
||||
GuiNpcButton addCatBtn = new GuiNpcButton(40, originX, addY, leftPanelW, btnH, "gui.addCategory");
|
||||
if (movePhase > 0) addCatBtn.enabled = false;
|
||||
addButton(addCatBtn);
|
||||
|
||||
// Rename / Remove (non-Uncategorized only)
|
||||
if (catSelected) {
|
||||
int crudY = addY + btnH + gap;
|
||||
int halfW = (leftPanelW - gap) / 2;
|
||||
GuiNpcButton renameBtn = new GuiNpcButton(41, originX, crudY, halfW, btnH, "gui.edit");
|
||||
if (movePhase > 0) renameBtn.enabled = false;
|
||||
addButton(renameBtn);
|
||||
|
||||
GuiNpcButton removeBtn = new GuiNpcButton(42, originX + halfW + gap, crudY, halfW, btnH, "gui.remove");
|
||||
removeBtn.enabled = movePhase == 0 && isCategoryEmpty(selectedCatId);
|
||||
addButton(removeBtn);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Center Panel: Items ==========
|
||||
@Override
|
||||
protected void initCenterPanel() {
|
||||
int searchH = btnH + gap;
|
||||
int scrollH = contentH - (movePhase == 0 ? searchH : 0);
|
||||
|
||||
int savedItemScrollY = resetItemScroll ? 0 : itemScroll.scrollY;
|
||||
resetItemScroll = false;
|
||||
|
||||
itemScroll.clear();
|
||||
itemScroll.colors.clear();
|
||||
itemScroll.setSize(contentW, scrollH);
|
||||
itemScroll.guiLeft = contentX;
|
||||
itemScroll.guiTop = contentY;
|
||||
|
||||
if (movePhase == 1) {
|
||||
itemScroll.multipleSelection = true;
|
||||
itemScroll.setSelectable(true);
|
||||
itemScroll.setSelectedList(moveSelection);
|
||||
} else if (movePhase == 2) {
|
||||
itemScroll.multipleSelection = true;
|
||||
itemScroll.setSelectedList(moveSelection);
|
||||
itemScroll.setSelectable(false);
|
||||
for (String name : moveSelection) {
|
||||
itemScroll.colors.put(name, 0xFFFF55);
|
||||
}
|
||||
} else {
|
||||
itemScroll.multipleSelection = false;
|
||||
itemScroll.setSelectable(true);
|
||||
}
|
||||
|
||||
itemScroll.setList(getItemSearchList());
|
||||
if (movePhase == 0) {
|
||||
itemScroll.setSelected(prevItemName);
|
||||
}
|
||||
itemScroll.scrollY = Math.max(0, Math.min(savedItemScrollY, itemScroll.maxScrollY));
|
||||
addScroll(itemScroll);
|
||||
|
||||
// Search field under center panel
|
||||
if (movePhase == 0) {
|
||||
int searchY = contentY + scrollH + gap;
|
||||
addTextField(new GuiNpcTextField(1, this, fontRendererObj, contentX, searchY, contentW, btnH, itemSearch));
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Right Panel: Preview + Edit/Copy/Remove ==========
|
||||
@Override
|
||||
protected void initRightPanel(int startY) {
|
||||
// Preview area — reserve space for 3 buttons + ID label at bottom
|
||||
int bottomH = (btnH + gap) * 2 + 14; // edit+copy row, remove row, ID label
|
||||
previewX = rightX;
|
||||
previewY = contentY;
|
||||
previewW = rightPanelW;
|
||||
previewH = contentH - bottomH - gap;
|
||||
|
||||
// Bottom buttons: Edit + Copy on one row, Remove below
|
||||
int btnY = contentY + contentH - btnH * 2 - gap;
|
||||
int halfW = (rightPanelW - gap) / 2;
|
||||
|
||||
GuiNpcButton editBtn = new GuiNpcButton(51, rightX, btnY, halfW, btnH, "gui.edit");
|
||||
editBtn.enabled = hasSelectedItem() && movePhase == 0;
|
||||
addButton(editBtn);
|
||||
|
||||
GuiNpcButton cloneBtn = new GuiNpcButton(52, rightX + halfW + gap, btnY, halfW, btnH, "gui.copy");
|
||||
cloneBtn.enabled = hasSelectedItem() && movePhase == 0;
|
||||
addButton(cloneBtn);
|
||||
|
||||
int removeY = btnY + btnH + gap;
|
||||
GuiNpcButton removeBtn = new GuiNpcButton(53, rightX, removeY, rightPanelW, btnH, "gui.remove");
|
||||
removeBtn.enabled = hasSelectedItem() && movePhase == 0;
|
||||
removeBtn.setTextColor(0xFF5555);
|
||||
addButton(removeBtn);
|
||||
}
|
||||
|
||||
// ========== Drawing ==========
|
||||
@Override
|
||||
protected void drawOverlay(int mouseX, int mouseY, float partialTicks) {
|
||||
// Draw collapse button icon
|
||||
GuiNpcButton collapseBtn = getButton(19);
|
||||
if (collapseBtn != null && collapseBtn.visible) {
|
||||
mc.getTextureManager().bindTexture(DIRECTORY_ICON);
|
||||
GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
int iconSize = 16;
|
||||
int iconX = collapseBtn.xPosition + (collapseBtn.width - iconSize) / 2;
|
||||
int iconY = collapseBtn.yPosition + (collapseBtn.height - iconSize) / 2;
|
||||
int texY;
|
||||
if (!collapseBtn.enabled) {
|
||||
texY = 32;
|
||||
} else {
|
||||
boolean hovered = mouseX >= collapseBtn.xPosition && mouseY >= collapseBtn.yPosition
|
||||
&& mouseX < collapseBtn.xPosition + collapseBtn.width
|
||||
&& mouseY < collapseBtn.yPosition + collapseBtn.height;
|
||||
texY = hovered ? 16 : 0;
|
||||
}
|
||||
func_152125_a(iconX, iconY, 0, texY, 16, 16, iconSize, iconSize, 256, 256);
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
}
|
||||
|
||||
if (!hasSubGui()) {
|
||||
// Draw preview background
|
||||
GuiUtil.drawRectD(previewX, previewY, previewX + previewW, previewY + previewH, 0xA0101010);
|
||||
|
||||
// Zoom/rotation in preview area
|
||||
if (isMouseOverPreview(mouseX, mouseY)) {
|
||||
float wheel = Mouse.getDWheel() * 0.035f;
|
||||
if (wheel != 0) {
|
||||
zoomed += wheel;
|
||||
zoomed = Math.max(5, Math.min(200, zoomed));
|
||||
}
|
||||
if (Mouse.isButtonDown(0) || Mouse.isButtonDown(1)) {
|
||||
rotation -= Mouse.getDX() * 0.75f;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw preview content
|
||||
if (hasSelectedItem()) {
|
||||
int centerX = previewX + previewW / 2;
|
||||
int centerY = previewY + (int)(previewH * 0.75f);
|
||||
drawItemPreview(centerX, centerY, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
// Draw details text
|
||||
if (hasSelectedItem()) {
|
||||
drawItemDetails(previewX + 4, previewY + 4, previewW - 8);
|
||||
}
|
||||
|
||||
// Draw ID (skip if -1, e.g. abilities use names not IDs)
|
||||
if (hasSelectedItem() && getSelectedItemId() >= 0) {
|
||||
String idStr = "ID: " + getSelectedItemId();
|
||||
int idY = contentY + contentH - btnH - 14;
|
||||
fontRendererObj.drawString(idStr, previewX + 4, idY, 0xFFFFFF, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Move phase overlay text
|
||||
if (movePhase == 1) {
|
||||
String text = "Select Items to Move (max " + MAX_MOVE_ITEMS + ")";
|
||||
int textW = fontRendererObj.getStringWidth(text);
|
||||
fontRendererObj.drawStringWithShadow(text, width / 2 - textW / 2, height - pad - 10, 0x55FF55);
|
||||
} else if (movePhase == 2) {
|
||||
String text = "Select Destination Category";
|
||||
int textW = fontRendererObj.getStringWidth(text);
|
||||
fontRendererObj.drawStringWithShadow(text, width / 2 - textW / 2, height - pad - 10, 0xFF5555);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isMouseOverPreview(int mouseX, int mouseY) {
|
||||
return mouseX >= previewX && mouseX <= previewX + previewW
|
||||
&& mouseY >= previewY && mouseY <= previewY + previewH;
|
||||
}
|
||||
|
||||
// ========== Nav List Builder ==========
|
||||
private void buildNavList() {
|
||||
navNames.clear();
|
||||
navIcons.clear();
|
||||
String searchLower = catSearch.toLowerCase();
|
||||
|
||||
// Uncategorized always first
|
||||
String uncatName = "Uncategorized";
|
||||
if (searchLower.isEmpty() || uncatName.toLowerCase().contains(searchLower)) {
|
||||
navNames.add(uncatName);
|
||||
navIcons.add(GuiCustomScrollIcons.ICON_TAB);
|
||||
}
|
||||
|
||||
// Other categories
|
||||
for (String name : catData.keySet()) {
|
||||
if (name.equals(uncatName)) continue;
|
||||
if (searchLower.isEmpty() || name.toLowerCase().contains(searchLower)) {
|
||||
navNames.add(name);
|
||||
navIcons.add(GuiCustomScrollIcons.ICON_FOLDER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isCategoryEmpty(int catId) {
|
||||
if (!itemDataLoaded) return false;
|
||||
return itemData == null || itemData.isEmpty();
|
||||
}
|
||||
|
||||
// ========== Search ==========
|
||||
@Override
|
||||
public void keyTyped(char c, int i) {
|
||||
super.keyTyped(c, i);
|
||||
|
||||
GuiNpcTextField itemSearchField = getTextField(1);
|
||||
if (itemSearchField != null) {
|
||||
String newText = itemSearchField.getText().toLowerCase();
|
||||
if (!itemSearch.equals(newText)) {
|
||||
itemSearch = newText;
|
||||
itemScroll.setList(getItemSearchList());
|
||||
}
|
||||
}
|
||||
|
||||
GuiNpcTextField catSearchField = getTextField(3);
|
||||
if (catSearchField != null) {
|
||||
String newText = catSearchField.getText().toLowerCase();
|
||||
if (!catSearch.equals(newText)) {
|
||||
catSearch = newText;
|
||||
buildNavList();
|
||||
catScroll.setListWithIcons(navNames, navIcons);
|
||||
catScroll.setSelected(prevCatName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<String> getItemSearchList() {
|
||||
if (selectedCatId < 0) return new ArrayList<>();
|
||||
if (itemSearch.isEmpty()) return new ArrayList<>(itemData.keySet());
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String name : itemData.keySet()) {
|
||||
if (name.toLowerCase().contains(itemSearch)) list.add(name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// ========== Actions ==========
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton guibutton) {
|
||||
int id = guibutton.id;
|
||||
|
||||
// Close
|
||||
if (id == 17) close();
|
||||
|
||||
// Collapse/Expand
|
||||
if (id == 19 && hasCategories() && movePhase == 0) {
|
||||
leftCollapsed = !leftCollapsed;
|
||||
initGui();
|
||||
return;
|
||||
}
|
||||
|
||||
// Minimize
|
||||
if (id == 18) {
|
||||
GuiScreen windowed = getWindowedVariant();
|
||||
if (windowed != null) {
|
||||
Minecraft.getMinecraft().displayGuiScreen(windowed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Item operations =====
|
||||
|
||||
// Add item
|
||||
if (id == 50 && selectedCatId >= 0 && movePhase == 0) {
|
||||
pendingNewItemCatId = selectedCatId > 0 ? selectedCatId : -1;
|
||||
onAddItem(selectedCatId);
|
||||
requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
|
||||
// Edit item
|
||||
if (id == 51 && hasSelectedItem() && movePhase == 0) {
|
||||
onEditItem();
|
||||
}
|
||||
|
||||
// Clone item
|
||||
if (id == 52 && hasSelectedItem() && movePhase == 0) {
|
||||
onCloneItem();
|
||||
requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
|
||||
// Remove item
|
||||
if (id == 53 && hasSelectedItem() && movePhase == 0) {
|
||||
GuiYesNo guiyesno = new GuiYesNo(this, itemScroll.getSelected(), StatCollector.translateToLocal("gui.delete"), 2);
|
||||
displayGuiScreen(guiyesno);
|
||||
}
|
||||
|
||||
// Move button
|
||||
if (id == 54) {
|
||||
if (movePhase == 0) {
|
||||
// Start move phase 1
|
||||
movePhase = 1;
|
||||
moveSelection.clear();
|
||||
initGui();
|
||||
} else {
|
||||
// Cancel move
|
||||
movePhase = 0;
|
||||
moveSelection.clear();
|
||||
initGui();
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm move (transition phase 1 → 2)
|
||||
if (id == 55 && movePhase == 1 && !moveSelection.isEmpty()) {
|
||||
movePhase = 2;
|
||||
initGui();
|
||||
}
|
||||
|
||||
// ===== Left panel category operations =====
|
||||
|
||||
// Add category
|
||||
if (id == 40 && movePhase == 0) {
|
||||
String name = "New";
|
||||
while (catData.containsKey(name)) name += "_";
|
||||
Category cat = new Category(-1, name);
|
||||
onSaveCategory(cat);
|
||||
}
|
||||
|
||||
// Rename category
|
||||
if (id == 41 && selectedCatId > 0 && movePhase == 0) {
|
||||
setSubGui(new SubGuiEditText(prevCatName));
|
||||
}
|
||||
|
||||
// Remove category
|
||||
if (id == 42 && selectedCatId > 0 && movePhase == 0) {
|
||||
GuiYesNo guiyesno = new GuiYesNo(this, prevCatName, StatCollector.translateToLocal("gui.delete"), 5);
|
||||
displayGuiScreen(guiyesno);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Scroll Events ==========
|
||||
@Override
|
||||
public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) {
|
||||
// Category scroll
|
||||
if (guiCustomScroll.id == 2) {
|
||||
String selected = catScroll.getSelected();
|
||||
if (selected == null) return;
|
||||
|
||||
// Move phase 2: destination selected
|
||||
if (movePhase == 2) {
|
||||
if (!selected.equals(prevCatName)) {
|
||||
Integer destCatId = catData.get(selected);
|
||||
if (destCatId != null) {
|
||||
executeMoveItems(destCatId);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal category selection
|
||||
if (!selected.equals(prevCatName)) {
|
||||
pendingNewItemCatId = -1;
|
||||
itemDataLoaded = false;
|
||||
Integer catId = catData.get(selected);
|
||||
if (catId != null) {
|
||||
selectedCatId = catId;
|
||||
itemScroll.selected = -1;
|
||||
prevItemName = "";
|
||||
requestItemsInCategory(selectedCatId);
|
||||
prevCatName = selected;
|
||||
resetItemScroll = true;
|
||||
initGui();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Item scroll
|
||||
if (guiCustomScroll.id == 0) {
|
||||
pendingNewItemCatId = -1;
|
||||
if (movePhase == 1) {
|
||||
// Enforce max selection during move
|
||||
if (moveSelection.size() > MAX_MOVE_ITEMS) {
|
||||
// Remove excess — keep only first MAX_MOVE_ITEMS
|
||||
HashSet<String> trimmed = new HashSet<>();
|
||||
int count = 0;
|
||||
for (String s : moveSelection) {
|
||||
if (count++ >= MAX_MOVE_ITEMS) break;
|
||||
trimmed.add(s);
|
||||
}
|
||||
moveSelection.clear();
|
||||
moveSelection.addAll(trimmed);
|
||||
itemScroll.setSelectedList(moveSelection);
|
||||
}
|
||||
// Update confirm button state
|
||||
if (getButton(55) != null) {
|
||||
getButton(55).enabled = !moveSelection.isEmpty();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String selected = itemScroll.getSelected();
|
||||
if (selected != null && !selected.equals(prevItemName)) {
|
||||
Integer itemId = itemData.get(selected);
|
||||
if (itemId != null) {
|
||||
requestItemData(itemId);
|
||||
prevItemName = selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) {
|
||||
if (guiCustomScroll.id == 0 && hasSelectedItem() && movePhase == 0) {
|
||||
onEditItem();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Move Execution ==========
|
||||
protected abstract void sendMovePacket(int itemId, int destCatId);
|
||||
|
||||
protected void executeMoveItems(int destCatId) {
|
||||
for (String name : moveSelection) {
|
||||
Integer itemId = itemData.get(name);
|
||||
if (itemId != null) {
|
||||
sendMovePacket(itemId, destCatId);
|
||||
}
|
||||
}
|
||||
movePhase = 0;
|
||||
moveSelection.clear();
|
||||
// Refresh current category items
|
||||
if (selectedCatId >= 0) {
|
||||
requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
initGui();
|
||||
}
|
||||
|
||||
// ========== Data from Server ==========
|
||||
@Override
|
||||
public void setData(Vector<String> list, HashMap<String, Integer> data, EnumScrollData type) {
|
||||
if (type == EnumScrollData.CATEGORY_LIST) {
|
||||
// Category list arrived
|
||||
String name = catScroll.getSelected();
|
||||
this.catData = data;
|
||||
buildNavList();
|
||||
catScroll.setListWithIcons(navNames, navIcons);
|
||||
if (name != null) catScroll.setSelected(name);
|
||||
else catScroll.setSelected(prevCatName);
|
||||
|
||||
// Auto-select Uncategorized on first load
|
||||
if (selectedCatId < 0 && catData.containsKey("Uncategorized")) {
|
||||
selectedCatId = catData.get("Uncategorized");
|
||||
prevCatName = "Uncategorized";
|
||||
catScroll.setSelected(prevCatName);
|
||||
requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
initGui();
|
||||
} else if (type == EnumScrollData.CATEGORY_GROUP) {
|
||||
// Items within selected category
|
||||
String name = itemScroll.getSelected();
|
||||
this.itemData = data;
|
||||
this.itemDataLoaded = true;
|
||||
itemScroll.setList(getItemSearchList());
|
||||
if (name != null) itemScroll.setSelected(name);
|
||||
initGui();
|
||||
}
|
||||
// Ignore OPTIONAL and other types silently
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGuiData(NBTTagCompound compound) {
|
||||
onItemReceived(compound);
|
||||
if (pendingNewItemCatId > 0 && hasSelectedItem() && getSelectedItemId() >= 0) {
|
||||
sendMovePacket(getSelectedItemId(), pendingNewItemCatId);
|
||||
if (selectedCatId >= 0) requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
pendingNewItemCatId = -1;
|
||||
initGui();
|
||||
}
|
||||
|
||||
// ========== SubGui ==========
|
||||
@Override
|
||||
public void subGuiClosed(SubGuiInterface subgui) {
|
||||
if (subgui instanceof SubGuiEditText) {
|
||||
if (!((SubGuiEditText) subgui).cancelled && selectedCatId > 0) {
|
||||
String name = ((SubGuiEditText) subgui).text;
|
||||
if (name != null && !name.isEmpty() && !catData.containsKey(name)) {
|
||||
Category cat = new Category(selectedCatId, name);
|
||||
onSaveCategory(cat);
|
||||
catData.remove(prevCatName);
|
||||
catData.put(name, selectedCatId);
|
||||
prevCatName = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
onSubGuiClosed(subgui);
|
||||
}
|
||||
|
||||
protected void onSubGuiClosed(SubGuiInterface subgui) {}
|
||||
|
||||
// ========== Confirm Dialog ==========
|
||||
@Override
|
||||
public void confirmClicked(boolean result, int id) {
|
||||
NoppesUtil.openGUI(player, this);
|
||||
if (!result) return;
|
||||
|
||||
// Remove category
|
||||
if (id == 5 && selectedCatId > 0) {
|
||||
onRemoveCategory(selectedCatId);
|
||||
selectedCatId = -1;
|
||||
prevCatName = "";
|
||||
itemData.clear();
|
||||
catScroll.selected = -1;
|
||||
}
|
||||
|
||||
// Remove item
|
||||
if (id == 2 && itemScroll.getSelected() != null && itemData.containsKey(itemScroll.getSelected())) {
|
||||
onRemoveItem(itemData.get(itemScroll.getSelected()));
|
||||
itemData.clear();
|
||||
prevItemName = "";
|
||||
if (selectedCatId >= 0) requestItemsInCategory(selectedCatId);
|
||||
}
|
||||
initGui();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(String selected) {}
|
||||
|
||||
@Override
|
||||
public void save() {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package noppes.npcs.constants;
|
||||
|
||||
/**
|
||||
* Identifies which controller a category operation targets.
|
||||
* Used by category packets to route to the correct CategoryManager.
|
||||
*/
|
||||
public class EnumCategoryType {
|
||||
public static final int EFFECT = 1;
|
||||
public static final int ANIMATION = 2;
|
||||
public static final int LINKED_ITEM = 3;
|
||||
public static final int ABILITY = 4;
|
||||
public static final int CHAINED_ABILITY = 5;
|
||||
}
|
||||
@@ -11,5 +11,7 @@ public enum EnumScrollData {
|
||||
BUILTIN_ABILITIES,
|
||||
ANIMATIONS,
|
||||
BUILTIN_ANIMATIONS,
|
||||
CHAINED_ABILITIES
|
||||
CHAINED_ABILITIES,
|
||||
CATEGORY_GROUP,
|
||||
CATEGORY_LIST
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
@@ -47,6 +48,7 @@ public class AnimationController implements IAnimationHandler {
|
||||
|
||||
public static AnimationController Instance = new AnimationController();
|
||||
private int lastUsedID = 0;
|
||||
public CategoryManager categoryManager = new CategoryManager();
|
||||
|
||||
public LinkedHashMap<String, Register.Animations> registeredAnimations = new LinkedHashMap<>();
|
||||
|
||||
@@ -164,45 +166,62 @@ public class AnimationController implements IAnimationHandler {
|
||||
File dir = getDir();
|
||||
if (!dir.exists()) {
|
||||
dir.mkdir();
|
||||
} else {
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
try {
|
||||
Animation animation = new Animation();
|
||||
animation.readFromNBT(NBTJsonUtil.LoadFile(file));
|
||||
animation.name = file.getName().substring(0, file.getName().length() - 5);
|
||||
return;
|
||||
}
|
||||
|
||||
if (animation.id == -1) {
|
||||
animation.id = getUnusedId();
|
||||
}
|
||||
categoryManager.loadCategories(dir);
|
||||
|
||||
int originalID = animation.id;
|
||||
int setID = animation.id;
|
||||
while (bootOrder.containsKey(setID) || animations.containsKey(setID)) {
|
||||
if (bootOrder.containsKey(setID))
|
||||
if (bootOrder.get(setID).equals(animation.name))
|
||||
break;
|
||||
// Load uncategorized animations (root level .json files)
|
||||
loadAnimationsFromDir(dir, CategoryManager.UNCATEGORIZED_ID);
|
||||
|
||||
setID++;
|
||||
}
|
||||
|
||||
animation.id = setID;
|
||||
if (originalID != setID) {
|
||||
LogWriter.info("Found Animation ID Mismatch: " + animation.name + ", New ID: " + setID);
|
||||
animation.save();
|
||||
}
|
||||
|
||||
animations.put(animation.id, animation);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
// Load categorized animations (subdirectories)
|
||||
for (Map.Entry<Integer, noppes.npcs.controllers.data.Category> entry : categoryManager.getCategories().entrySet()) {
|
||||
File catDir = categoryManager.getCategoryDir(entry.getKey());
|
||||
loadAnimationsFromDir(catDir, entry.getKey());
|
||||
}
|
||||
|
||||
saveAnimationMap();
|
||||
}
|
||||
|
||||
private void loadAnimationsFromDir(File dir, int catId) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) return;
|
||||
for (File file : files) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
try {
|
||||
Animation animation = new Animation();
|
||||
animation.readFromNBT(NBTJsonUtil.LoadFile(file));
|
||||
animation.name = file.getName().substring(0, file.getName().length() - 5);
|
||||
|
||||
if (animation.id == -1) {
|
||||
animation.id = getUnusedId();
|
||||
}
|
||||
|
||||
int originalID = animation.id;
|
||||
int setID = animation.id;
|
||||
while (bootOrder.containsKey(setID) || animations.containsKey(setID)) {
|
||||
if (bootOrder.containsKey(setID))
|
||||
if (bootOrder.get(setID).equals(animation.name))
|
||||
break;
|
||||
|
||||
setID++;
|
||||
}
|
||||
|
||||
animation.id = setID;
|
||||
if (originalID != setID) {
|
||||
LogWriter.info("Found Animation ID Mismatch: " + animation.name + ", New ID: " + setID);
|
||||
animation.save();
|
||||
}
|
||||
|
||||
animations.put(animation.id, animation);
|
||||
categoryManager.registerItem(animation.id, catId);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addAnimationRegister(String namespace, Register.Animations register) {
|
||||
registeredAnimations.put(namespace, register);
|
||||
}
|
||||
@@ -265,7 +284,7 @@ public class AnimationController implements IAnimationHandler {
|
||||
saveAnimationMap();
|
||||
|
||||
// Save Animation File
|
||||
File dir = this.getDir();
|
||||
File dir = categoryManager.getItemDir(animation.getID());
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
|
||||
@@ -295,46 +314,29 @@ public class AnimationController implements IAnimationHandler {
|
||||
public void delete(String name) {
|
||||
Animation delete = getAnimationFromName(name);
|
||||
if (delete != null) {
|
||||
// Reject deleting built-in animations
|
||||
if (delete instanceof BuiltInAnimation) {
|
||||
LogWriter.info("Cannot delete built-in animation: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
Animation foundAnimation = this.animations.remove(delete.getID());
|
||||
if (foundAnimation != null && foundAnimation.name != null) {
|
||||
File dir = this.getDir();
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
if (file.getName().equals(foundAnimation.name + ".json")) {
|
||||
file.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
saveAnimationMap();
|
||||
}
|
||||
delete(delete.getID());
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(int id) {
|
||||
// Only user animations have valid IDs (>= 0)
|
||||
if (id < 0 || !this.animations.containsKey(id))
|
||||
return;
|
||||
|
||||
// Reject deleting built-in animations
|
||||
Animation anim = this.animations.get(id);
|
||||
if (anim instanceof BuiltInAnimation) {
|
||||
LogWriter.info("Cannot delete built-in animation: " + anim.name);
|
||||
return;
|
||||
}
|
||||
|
||||
Animation foundAnimation = this.animations.remove(id);
|
||||
if (foundAnimation != null && foundAnimation.name != null) {
|
||||
File dir = this.getDir();
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
if (file.getName().equals(foundAnimation.name + ".json")) {
|
||||
file.delete();
|
||||
break;
|
||||
}
|
||||
File dir = categoryManager.getItemDir(id);
|
||||
File file = new File(dir, foundAnimation.name + ".json");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
categoryManager.removeItem(id);
|
||||
saveAnimationMap();
|
||||
}
|
||||
}
|
||||
@@ -553,5 +555,29 @@ public class AnimationController implements IAnimationHandler {
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// CATEGORY HELPERS
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
public Map<String, Integer> getCategoryScrollData() {
|
||||
return categoryManager.getCategoryScrollData();
|
||||
}
|
||||
|
||||
public void moveItemToCategory(int itemId, int catId) {
|
||||
Animation animation = animations.get(itemId);
|
||||
if (animation == null) return;
|
||||
categoryManager.moveItem(itemId, animation.name + ".json", catId);
|
||||
saveAnimationMap();
|
||||
}
|
||||
|
||||
public Map<String, Integer> getItemsByCategoryScrollData(int catId) {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
List<Integer> itemIds = categoryManager.getItemsInCategory(catId, animations.keySet());
|
||||
for (int itemId : itemIds) {
|
||||
Animation animation = animations.get(itemId);
|
||||
if (animation != null) {
|
||||
map.put(animation.name, animation.id);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package noppes.npcs.controllers;
|
||||
|
||||
import noppes.npcs.controllers.data.Category;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Filesystem-based category manager for organizing items into named groups.
|
||||
* Categories are subdirectories within the controller's base directory.
|
||||
* Items without a category mapping are "Uncategorized" (root-level files).
|
||||
*
|
||||
* One instance per controller (Effect, Animation, LinkedItem, Form, Aura, Outline, etc.)
|
||||
*/
|
||||
public class CategoryManager {
|
||||
public static final int UNCATEGORIZED_ID = 0;
|
||||
public static final String UNCATEGORIZED_NAME = "Uncategorized";
|
||||
|
||||
private File baseDir;
|
||||
private final HashMap<Integer, Category> categories = new HashMap<>();
|
||||
private final HashMap<Integer, Integer> itemCategoryMap = new HashMap<>();
|
||||
private int lastUsedCatID = 0;
|
||||
|
||||
/**
|
||||
* Scan the base directory for subdirectories and create Category objects.
|
||||
* Call at the start of controller loading, before loading items.
|
||||
*/
|
||||
public void loadCategories(File baseDir) {
|
||||
loadCategories(baseDir, new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the base directory for subdirectories and create Category objects,
|
||||
* excluding any subdirectories whose names match excludeDirs.
|
||||
*/
|
||||
public void loadCategories(File baseDir, String... excludeDirs) {
|
||||
this.baseDir = baseDir;
|
||||
categories.clear();
|
||||
itemCategoryMap.clear();
|
||||
lastUsedCatID = 0;
|
||||
|
||||
if (!baseDir.exists()) return;
|
||||
|
||||
Set<String> excludeSet = new HashSet<>(Arrays.asList(excludeDirs));
|
||||
File[] files = baseDir.listFiles();
|
||||
if (files == null) return;
|
||||
for (File sub : files) {
|
||||
if (!sub.isDirectory()) continue;
|
||||
if (excludeSet.contains(sub.getName())) continue;
|
||||
lastUsedCatID++;
|
||||
categories.put(lastUsedCatID, new Category(lastUsedCatID, sub.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an item as belonging to a category.
|
||||
* Call when loading items from a subdirectory.
|
||||
*/
|
||||
public void registerItem(int itemId, int catId) {
|
||||
if (catId > UNCATEGORIZED_ID) {
|
||||
itemCategoryMap.put(itemId, catId);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeItem(int itemId) {
|
||||
itemCategoryMap.remove(itemId);
|
||||
}
|
||||
|
||||
public File getItemDir(int itemId) {
|
||||
int catId = itemCategoryMap.getOrDefault(itemId, UNCATEGORIZED_ID);
|
||||
return getCategoryDir(catId);
|
||||
}
|
||||
|
||||
public File getCategoryDir(int catId) {
|
||||
if (catId == UNCATEGORIZED_ID) return baseDir;
|
||||
Category cat = categories.get(catId);
|
||||
if (cat != null) return new File(baseDir, cat.title);
|
||||
return baseDir;
|
||||
}
|
||||
|
||||
public HashMap<Integer, Category> getCategories() {
|
||||
return categories;
|
||||
}
|
||||
|
||||
// ========== Category CRUD ==========
|
||||
|
||||
public Category createCategory(String name) {
|
||||
while (hasCategoryName(name)) name += "_";
|
||||
lastUsedCatID++;
|
||||
Category cat = new Category(lastUsedCatID, name);
|
||||
categories.put(cat.id, cat);
|
||||
File dir = new File(baseDir, name);
|
||||
if (!dir.exists()) dir.mkdirs();
|
||||
return cat;
|
||||
}
|
||||
|
||||
public void saveCategory(Category cat) {
|
||||
if (cat.id <= 0) return;
|
||||
Category existing = categories.get(cat.id);
|
||||
if (existing != null && !existing.title.equals(cat.title)) {
|
||||
String newTitle = cat.title;
|
||||
while (hasCategoryName(newTitle)) newTitle += "_";
|
||||
File oldDir = new File(baseDir, existing.title);
|
||||
File newDir = new File(baseDir, newTitle);
|
||||
if (oldDir.exists() && !newDir.exists()) {
|
||||
oldDir.renameTo(newDir);
|
||||
}
|
||||
cat.title = newTitle;
|
||||
}
|
||||
categories.put(cat.id, cat);
|
||||
}
|
||||
|
||||
public boolean removeCategory(int catId) {
|
||||
return removeCategory(catId, null);
|
||||
}
|
||||
|
||||
public boolean removeCategory(int catId, Set<Integer> allItemIds) {
|
||||
if (catId <= UNCATEGORIZED_ID) return false;
|
||||
Category cat = categories.get(catId);
|
||||
if (cat == null) return false;
|
||||
|
||||
// Server-side guard: refuse to delete if category has items
|
||||
if (allItemIds != null) {
|
||||
if (!isCategoryEmpty(catId, allItemIds)) return false;
|
||||
} else {
|
||||
// Without item set, check if any items are mapped to this category
|
||||
if (itemCategoryMap.containsValue(catId)) return false;
|
||||
}
|
||||
|
||||
// Also check filesystem — directory must be empty or non-existent
|
||||
File dir = new File(baseDir, cat.title);
|
||||
if (dir.exists()) {
|
||||
String[] contents = dir.list();
|
||||
if (contents != null && contents.length > 0) {
|
||||
return false;
|
||||
}
|
||||
dir.delete();
|
||||
}
|
||||
|
||||
categories.remove(catId);
|
||||
Iterator<Map.Entry<Integer, Integer>> it = itemCategoryMap.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
if (it.next().getValue() == catId) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void moveItem(int itemId, String fileName, int destCatId) {
|
||||
int oldCatId = itemCategoryMap.getOrDefault(itemId, UNCATEGORIZED_ID);
|
||||
if (oldCatId == destCatId) return;
|
||||
|
||||
File oldDir = getCategoryDir(oldCatId);
|
||||
File newDir = getCategoryDir(destCatId);
|
||||
if (!newDir.exists()) newDir.mkdirs();
|
||||
|
||||
File oldFile = new File(oldDir, fileName);
|
||||
File newFile = new File(newDir, fileName);
|
||||
if (oldFile.exists()) {
|
||||
oldFile.renameTo(newFile);
|
||||
}
|
||||
|
||||
if (destCatId == UNCATEGORIZED_ID) {
|
||||
itemCategoryMap.remove(itemId);
|
||||
} else {
|
||||
itemCategoryMap.put(itemId, destCatId);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Query ==========
|
||||
|
||||
public boolean hasCategoryName(String name) {
|
||||
for (Category cat : categories.values()) {
|
||||
if (cat.title.equalsIgnoreCase(name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCategoryEmpty(int catId, Set<Integer> allItemIds) {
|
||||
if (catId == UNCATEGORIZED_ID) {
|
||||
for (int id : allItemIds) {
|
||||
if (!itemCategoryMap.containsKey(id)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> entry : itemCategoryMap.entrySet()) {
|
||||
if (entry.getValue() == catId && allItemIds.contains(entry.getKey())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Category getCategory(int catId) {
|
||||
return categories.get(catId);
|
||||
}
|
||||
|
||||
public int getItemCategory(int itemId) {
|
||||
return itemCategoryMap.getOrDefault(itemId, UNCATEGORIZED_ID);
|
||||
}
|
||||
|
||||
public List<Integer> getItemsInCategory(int catId, Set<Integer> allItemIds) {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (int itemId : allItemIds) {
|
||||
int assignedCat = itemCategoryMap.getOrDefault(itemId, UNCATEGORIZED_ID);
|
||||
if (assignedCat == catId) {
|
||||
result.add(itemId);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========== Scroll Data ==========
|
||||
|
||||
public Map<String, Integer> getCategoryScrollData() {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
map.put(UNCATEGORIZED_NAME, UNCATEGORIZED_ID);
|
||||
for (Category cat : categories.values()) {
|
||||
map.put(cat.title, cat.id);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search all directories (root + subdirectories) for a file and delete it.
|
||||
*/
|
||||
public void deleteFile(String fileName) {
|
||||
if (baseDir == null || !baseDir.exists()) return;
|
||||
|
||||
File file = new File(baseDir, fileName);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
File[] subs = baseDir.listFiles();
|
||||
if (subs != null) {
|
||||
for (File sub : subs) {
|
||||
if (!sub.isDirectory()) continue;
|
||||
File subFile = new File(sub, fileName);
|
||||
if (subFile.exists()) {
|
||||
subFile.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -43,6 +44,7 @@ public class CustomEffectController implements ICustomEffectHandler {
|
||||
|
||||
public HashMap<Integer, EffectScript> customEffectScriptHandlers = new HashMap<>();
|
||||
private HashMap<Integer, String> bootOrder;
|
||||
public CategoryManager categoryManager = new CategoryManager();
|
||||
|
||||
private int lastUsedID = 0;
|
||||
public ConcurrentHashMap<UUID, ConcurrentHashMap<EffectKey, PlayerEffect>> playerEffects = new ConcurrentHashMap<>();
|
||||
@@ -119,16 +121,13 @@ public class CustomEffectController implements ICustomEffectHandler {
|
||||
CustomEffect foundEffect = getCustomEffects().remove(effect.getID());
|
||||
customEffectScriptHandlers.remove(effect.getID());
|
||||
if (foundEffect != null && foundEffect.name != null) {
|
||||
File dir = this.getDir();
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
if (file.getName().equalsIgnoreCase(foundEffect.name + ".json")) {
|
||||
file.delete();
|
||||
SyncController.syncRemove(EnumSyncType.CUSTOM_EFFECTS, foundEffect.getID());
|
||||
break;
|
||||
}
|
||||
File dir = categoryManager.getItemDir(id);
|
||||
File file = new File(dir, foundEffect.name + ".json");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
categoryManager.removeItem(id);
|
||||
SyncController.syncRemove(EnumSyncType.CUSTOM_EFFECTS, foundEffect.getID());
|
||||
saveEffectLoadMap();
|
||||
}
|
||||
}
|
||||
@@ -328,40 +327,58 @@ public class CustomEffectController implements ICustomEffectHandler {
|
||||
File dir = getDir();
|
||||
if (!dir.exists()) {
|
||||
dir.mkdir();
|
||||
} else {
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
try {
|
||||
CustomEffect effect = new CustomEffect();
|
||||
effect.readFromNBT(NBTJsonUtil.LoadFile(file));
|
||||
effect.name = file.getName().substring(0, file.getName().length() - 5);
|
||||
if (effect.id == -1) {
|
||||
effect.id = getUnusedId();
|
||||
}
|
||||
int originalID = effect.id;
|
||||
int setID = effect.id;
|
||||
while (bootOrder.containsKey(setID) || getCustomEffects().containsKey(setID)) {
|
||||
if (bootOrder.containsKey(setID))
|
||||
if (bootOrder.get(setID).equalsIgnoreCase(effect.name))
|
||||
break;
|
||||
setID++;
|
||||
}
|
||||
effect.id = setID;
|
||||
if (originalID != setID) {
|
||||
LogWriter.info("Found Custom Effect ID Mismatch: " + effect.name + ", New ID: " + setID);
|
||||
effect.save();
|
||||
}
|
||||
getCustomEffects().put(effect.id, effect);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
categoryManager.loadCategories(dir);
|
||||
|
||||
// Load uncategorized effects (root level .json files)
|
||||
loadEffectsFromDir(dir, CategoryManager.UNCATEGORIZED_ID);
|
||||
|
||||
// Load categorized effects (subdirectories)
|
||||
for (Map.Entry<Integer, noppes.npcs.controllers.data.Category> entry : categoryManager.getCategories().entrySet()) {
|
||||
File catDir = categoryManager.getCategoryDir(entry.getKey());
|
||||
loadEffectsFromDir(catDir, entry.getKey());
|
||||
}
|
||||
|
||||
this.registerEffectMap(0, getCustomEffects());
|
||||
saveEffectLoadMap();
|
||||
}
|
||||
|
||||
private void loadEffectsFromDir(File dir, int catId) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) return;
|
||||
for (File file : files) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
try {
|
||||
CustomEffect effect = new CustomEffect();
|
||||
effect.readFromNBT(NBTJsonUtil.LoadFile(file));
|
||||
effect.name = file.getName().substring(0, file.getName().length() - 5);
|
||||
if (effect.id == -1) {
|
||||
effect.id = getUnusedId();
|
||||
}
|
||||
int originalID = effect.id;
|
||||
int setID = effect.id;
|
||||
while (bootOrder.containsKey(setID) || getCustomEffects().containsKey(setID)) {
|
||||
if (bootOrder.containsKey(setID))
|
||||
if (bootOrder.get(setID).equalsIgnoreCase(effect.name))
|
||||
break;
|
||||
setID++;
|
||||
}
|
||||
effect.id = setID;
|
||||
if (originalID != setID) {
|
||||
LogWriter.info("Found Custom Effect ID Mismatch: " + effect.name + ", New ID: " + setID);
|
||||
effect.save();
|
||||
}
|
||||
getCustomEffects().put(effect.id, effect);
|
||||
categoryManager.registerItem(effect.id, catId);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<Integer, CustomEffect> getEffectMap(int index) {
|
||||
return indexMapper.get(index);
|
||||
}
|
||||
@@ -370,7 +387,7 @@ public class CustomEffectController implements ICustomEffectHandler {
|
||||
return new File(CustomNpcs.getWorldSaveDirectory(), "customeffects");
|
||||
}
|
||||
|
||||
private void saveEffectLoadMap() {
|
||||
public void saveEffectLoadMap() {
|
||||
try {
|
||||
File saveDir = getMapDir();
|
||||
File file = new File(saveDir, "customeffects.dat_new");
|
||||
@@ -453,12 +470,7 @@ public class CustomEffectController implements ICustomEffectHandler {
|
||||
}
|
||||
|
||||
public void deleteEffectFile(String prevName) {
|
||||
File dir = this.getDir();
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
File file2 = new File(dir, prevName + ".json");
|
||||
if (file2.exists())
|
||||
file2.delete();
|
||||
categoryManager.deleteFile(prevName + ".json");
|
||||
}
|
||||
|
||||
// API Versions
|
||||
@@ -490,21 +502,7 @@ public class CustomEffectController implements ICustomEffectHandler {
|
||||
public void deleteEffect(String name) {
|
||||
ICustomEffect effect = getEffect(name);
|
||||
if (effect != null) {
|
||||
CustomEffect foundEffect = getCustomEffects().remove(effect.getID());
|
||||
customEffectScriptHandlers.remove(effect.getID());
|
||||
if (foundEffect != null && foundEffect.name != null) {
|
||||
File dir = this.getDir();
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
if (file.getName().equalsIgnoreCase(foundEffect.name + ".json")) {
|
||||
file.delete();
|
||||
SyncController.syncRemove(EnumSyncType.CUSTOM_EFFECTS, foundEffect.getID());
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveEffectLoadMap();
|
||||
}
|
||||
delete(effect.getID());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,6 +630,33 @@ public class CustomEffectController implements ICustomEffectHandler {
|
||||
return get(id, index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// CATEGORY HELPERS
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
public Map<String, Integer> getCategoryScrollData() {
|
||||
return categoryManager.getCategoryScrollData();
|
||||
}
|
||||
|
||||
public void moveItemToCategory(int itemId, int catId) {
|
||||
CustomEffect effect = getCustomEffects().get(itemId);
|
||||
if (effect == null) return;
|
||||
categoryManager.moveItem(itemId, effect.name + ".json", catId);
|
||||
saveEffectLoadMap();
|
||||
}
|
||||
|
||||
public Map<String, Integer> getItemsByCategoryScrollData(int catId) {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
List<Integer> itemIds = categoryManager.getItemsInCategory(catId, getCustomEffects().keySet());
|
||||
for (int itemId : itemIds) {
|
||||
CustomEffect effect = getCustomEffects().get(itemId);
|
||||
if (effect != null) {
|
||||
map.put(effect.name, effect.id);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICustomEffect saveEffect(ICustomEffect customEffect) {
|
||||
if (customEffect.getID() < 0) {
|
||||
@@ -648,7 +673,7 @@ public class CustomEffectController implements ICustomEffectHandler {
|
||||
|
||||
saveEffectLoadMap();
|
||||
|
||||
File dir = this.getDir();
|
||||
File dir = categoryManager.getItemDir(customEffect.getID());
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class LinkedItemController implements ILinkedItemHandler {
|
||||
@@ -30,6 +32,7 @@ public class LinkedItemController implements ILinkedItemHandler {
|
||||
public HashMap<Integer, LinkedItemScript> linkedItemsScripts = new HashMap<>();
|
||||
|
||||
private HashMap<Integer, String> bootOrder;
|
||||
public CategoryManager categoryManager = new CategoryManager();
|
||||
|
||||
private LinkedItemController() {
|
||||
}
|
||||
@@ -121,45 +124,62 @@ public class LinkedItemController implements ILinkedItemHandler {
|
||||
File dir = getDir();
|
||||
if (!dir.exists()) {
|
||||
dir.mkdir();
|
||||
} else {
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
try {
|
||||
LinkedItem linkedItem = new LinkedItem();
|
||||
linkedItem.readFromNBT(NBTJsonUtil.LoadFile(file));
|
||||
linkedItem.name = file.getName().substring(0, file.getName().length() - 5);
|
||||
return;
|
||||
}
|
||||
|
||||
if (linkedItem.id == -1) {
|
||||
linkedItem.id = getUnusedId();
|
||||
}
|
||||
categoryManager.loadCategories(dir);
|
||||
|
||||
int originalID = linkedItem.id;
|
||||
int setID = linkedItem.id;
|
||||
while (bootOrder.containsKey(setID) || linkedItems.containsKey(setID)) {
|
||||
if (bootOrder.containsKey(setID))
|
||||
if (bootOrder.get(setID).equals(linkedItem.name))
|
||||
break;
|
||||
// Load uncategorized items (root level .json files)
|
||||
loadItemsFromDir(dir, CategoryManager.UNCATEGORIZED_ID);
|
||||
|
||||
setID++;
|
||||
}
|
||||
|
||||
linkedItem.id = setID;
|
||||
if (originalID != setID) {
|
||||
LogWriter.info("Found Linked Item ID Mismatch: " + linkedItem.name + ", New ID: " + setID);
|
||||
linkedItem.save();
|
||||
}
|
||||
|
||||
linkedItems.put(linkedItem.id, linkedItem);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
// Load categorized items (subdirectories)
|
||||
for (Map.Entry<Integer, noppes.npcs.controllers.data.Category> entry : categoryManager.getCategories().entrySet()) {
|
||||
File catDir = categoryManager.getCategoryDir(entry.getKey());
|
||||
loadItemsFromDir(catDir, entry.getKey());
|
||||
}
|
||||
|
||||
saveLinkedItemsMap();
|
||||
}
|
||||
|
||||
private void loadItemsFromDir(File dir, int catId) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) return;
|
||||
for (File file : files) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
try {
|
||||
LinkedItem linkedItem = new LinkedItem();
|
||||
linkedItem.readFromNBT(NBTJsonUtil.LoadFile(file));
|
||||
linkedItem.name = file.getName().substring(0, file.getName().length() - 5);
|
||||
|
||||
if (linkedItem.id == -1) {
|
||||
linkedItem.id = getUnusedId();
|
||||
}
|
||||
|
||||
int originalID = linkedItem.id;
|
||||
int setID = linkedItem.id;
|
||||
while (bootOrder.containsKey(setID) || linkedItems.containsKey(setID)) {
|
||||
if (bootOrder.containsKey(setID))
|
||||
if (bootOrder.get(setID).equals(linkedItem.name))
|
||||
break;
|
||||
|
||||
setID++;
|
||||
}
|
||||
|
||||
linkedItem.id = setID;
|
||||
if (originalID != setID) {
|
||||
LogWriter.info("Found Linked Item ID Mismatch: " + linkedItem.name + ", New ID: " + setID);
|
||||
linkedItem.save();
|
||||
}
|
||||
|
||||
linkedItems.put(linkedItem.id, linkedItem);
|
||||
categoryManager.registerItem(linkedItem.id, catId);
|
||||
} catch (Exception e) {
|
||||
LogWriter.error("Error loading: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File getDir() {
|
||||
return new File(CustomNpcs.getWorldSaveDirectory(), "linkeditems");
|
||||
}
|
||||
@@ -200,7 +220,7 @@ public class LinkedItemController implements ILinkedItemHandler {
|
||||
saveLinkedItemsMap();
|
||||
|
||||
// Save Linked Item File
|
||||
File dir = this.getDir();
|
||||
File dir = categoryManager.getItemDir(linkedItem.getId());
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
|
||||
@@ -231,27 +251,19 @@ public class LinkedItemController implements ILinkedItemHandler {
|
||||
if (linkedItem != null) {
|
||||
LinkedItem foundItem = remove(id);
|
||||
if (foundItem != null && foundItem.name != null) {
|
||||
File dir = this.getDir();
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.isFile() || !file.getName().endsWith(".json"))
|
||||
continue;
|
||||
if (file.getName().equalsIgnoreCase(foundItem.name + ".json")) {
|
||||
file.delete();
|
||||
break;
|
||||
}
|
||||
File dir = categoryManager.getItemDir(id);
|
||||
File file = new File(dir, foundItem.name + ".json");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
categoryManager.removeItem(id);
|
||||
saveLinkedItemsMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteLinkedItemFile(String prevName) {
|
||||
File dir = this.getDir();
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
File file2 = new File(dir, prevName + ".json");
|
||||
if (file2.exists())
|
||||
file2.delete();
|
||||
categoryManager.deleteFile(prevName + ".json");
|
||||
}
|
||||
|
||||
|
||||
@@ -350,5 +362,29 @@ public class LinkedItemController implements ILinkedItemHandler {
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// CATEGORY HELPERS
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
public Map<String, Integer> getCategoryScrollData() {
|
||||
return categoryManager.getCategoryScrollData();
|
||||
}
|
||||
|
||||
public void moveItemToCategory(int itemId, int catId) {
|
||||
LinkedItem item = linkedItems.get(itemId);
|
||||
if (item == null) return;
|
||||
categoryManager.moveItem(itemId, item.name + ".json", catId);
|
||||
saveLinkedItemsMap();
|
||||
}
|
||||
|
||||
public Map<String, Integer> getItemsByCategoryScrollData(int catId) {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
List<Integer> itemIds = categoryManager.getItemsInCategory(catId, linkedItems.keySet());
|
||||
for (int itemId : itemIds) {
|
||||
LinkedItem item = linkedItems.get(itemId);
|
||||
if (item != null) {
|
||||
map.put(item.name, item.id);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package noppes.npcs.controllers.data;
|
||||
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
/**
|
||||
* Simple category data class for the generic category system.
|
||||
* Used by CategoryManager to organize items into named groups.
|
||||
*/
|
||||
public class Category {
|
||||
public int id = -1;
|
||||
public String title = "";
|
||||
|
||||
public Category() {
|
||||
}
|
||||
|
||||
public Category(int id, String title) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void readNBT(NBTTagCompound compound) {
|
||||
id = compound.getInteger("CatID");
|
||||
title = compound.getString("CatTitle");
|
||||
}
|
||||
|
||||
public NBTTagCompound writeNBT(NBTTagCompound compound) {
|
||||
compound.setInteger("CatID", id);
|
||||
compound.setString("CatTitle", title);
|
||||
return compound;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user