feat: add built-in ability handling and UI updates for ability management

This commit is contained in:
Kam committed 2026-02-12 20:30:12 -05:00
1 parent 9dae4bae55
commit 2c00ecae9e
12 files changed
+201 -47

No files matched your search

@@ -1278,6 +1278,10 @@ public abstract class Ability implements IAbility {
return typeId;
}
public boolean isBuiltIn() {
return false;
}
public int getWeight() {
return weight;
}
@@ -48,6 +48,9 @@ public class AbilityController implements IAbilityHandler {
/** External variant definitions registered by addons, keyed by typeId. */
private final Map<String, List<AbilityVariant>> externalVariants = new LinkedHashMap<>();
/** TypeIds that produce BuiltInAbility instances (non-editable in GUI). */
private final Set<String> builtInTypeIds = new HashSet<>();
public AbilityController() {
registerBuiltinTypes();
}
@@ -173,7 +176,9 @@ public class AbilityController implements IAbilityHandler {
if (abilities.containsKey(name)) {
LogWriter.info("AbilityController: Overwriting built-in ability: " + name);
}
ability.setName(name);
if (!ability.isBuiltIn()) {
ability.setName(name);
}
abilities.put(name, ability);
LogWriter.info("Registered ability: " + name);
}
@@ -281,12 +286,22 @@ public class AbilityController implements IAbilityHandler {
// ACCESSORS
// ═══════════════════════════════════════════════════════════════════════════
/** Get a built-in ability by name. Returns the stored instance (not a copy). */
/** Get a built-in ability by map key. Returns the stored instance (not a copy). */
public Ability getAbility(String name) {
return abilities.get(name);
}
/** Get all built-in ability names. */
/** Get a built-in ability by its display name (ability.getName()). */
public Ability getAbilityByDisplayName(String displayName) {
for (Ability ability : abilities.values()) {
if (ability.getName().equals(displayName)) {
return ability;
}
}
return null;
}
/** Get all built-in ability map keys. */
public Set<String> getAbilityNames() {
return new LinkedHashSet<>(abilities.keySet());
}
@@ -338,6 +353,10 @@ public class AbilityController implements IAbilityHandler {
return typeFactories.keySet().toArray(new String[0]);
}
public boolean isBuiltInType(String typeId) {
return builtInTypeIds.contains(typeId);
}
@Override
public boolean hasType(String typeId) {
return typeFactories.containsKey(typeId);
@@ -386,6 +405,9 @@ public class AbilityController implements IAbilityHandler {
LogWriter.info("AbilityController: Overwriting type: " + typeId);
}
typeFactories.put(typeId, factory);
if (temp.isBuiltIn()) {
builtInTypeIds.add(typeId);
}
}
/**
@@ -21,7 +21,13 @@ public abstract class BuiltInAbility extends Ability {
this.registryKey = registryKey;
this.id = registryKey;
this.name = registryKey;
this.typeId = registryKey;
// Convert "namespace:name" to "ability.namespace.name" for GUI filter compatibility
this.typeId = "ability." + registryKey.replace(':', '.');
}
@Override
public boolean isBuiltIn() {
return true;
}
/**
@@ -96,6 +96,7 @@ import kamkeel.npcs.network.packets.request.ability.AbilitiesNpcSavePacket;
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.BuiltInAbilityGetPacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket;
import kamkeel.npcs.network.packets.request.animation.AnimationGetPacket;
import kamkeel.npcs.network.packets.request.animation.AnimationRemovePacket;
@@ -516,6 +517,7 @@ public class PacketHandler {
REQUEST_PACKET.registerPacket(new CustomAbilityGetPacket());
REQUEST_PACKET.registerPacket(new CustomAbilityRemovePacket());
REQUEST_PACKET.registerPacket(new CustomAbilitySavePacket());
REQUEST_PACKET.registerPacket(new BuiltInAbilityGetPacket());
// Other Packets
REQUEST_PACKET.registerPacket(new IsGuiOpenInform());
@@ -205,6 +205,7 @@ public enum EnumRequestPacket {
CustomAbilityGet,
CustomAbilityRemove,
CustomAbilitySave,
BuiltInAbilityGet,
Hammer,
RecipeScript,
@@ -47,5 +47,6 @@ public final class CustomAbilitiesGetPacket extends AbstractPacket {
return;
NoppesUtilServer.sendCustomAbilitiesData((EntityPlayerMP) player);
NoppesUtilServer.sendBuiltInAbilitiesData((EntityPlayerMP) player);
}
}
+18 -1
View File
@@ -37,6 +37,7 @@ public class Register<T> {
public static class Abilities extends Register<Ability> {
protected final Map<String, String> uniqueNames = new LinkedHashMap<>();
protected final Set<String> typeOnly = new HashSet<>();
private Abilities(String namespace) {
super("ability", namespace);
@@ -50,9 +51,25 @@ public class Register<T> {
return factory.get();
}
/**
* Register a type only (shell template, no built-in preset).
* The type will appear in the ability type picker but won't be
* available as a pre-configured ability by name.
*/
public Ability registerType(String factoryName, Supplier<Ability> factory) {
String name = registryKey + "." + namespace + "." + factoryName.trim().toLowerCase().replaceAll(" ", "_");
entries.put(name, factory);
uniqueNames.put(name, factoryName);
typeOnly.add(name);
return factory.get();
}
public void register() {
for (Map.Entry<String, Supplier<Ability>> entry : entries.entrySet()) {
AbilityController.Instance.registerAbility(uniqueNames.get(entry.getKey()), entry.getValue().get());
AbilityController.Instance.registerType(entry.getKey(), entry.getValue());
if (!typeOnly.contains(entry.getKey())) {
AbilityController.Instance.registerAbility(uniqueNames.get(entry.getKey()), entry.getValue().get());
}
}
}
@@ -781,12 +781,29 @@ public class NoppesUtilServer {
if (controller != null) {
int index = 0;
for (String typeId : controller.getTypes()) {
// Exclude built-in types from the NPC type list
if (controller.isBuiltInType(typeId)) continue;
map.put(typeId, index++);
}
}
sendScrollData(player, map, EnumScrollData.ABILITY_TYPES);
}
public static void sendBuiltInAbilitiesData(EntityPlayerMP player) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
AbilityController controller = AbilityController.Instance;
if (controller != null) {
int index = 0;
for (String name : controller.getAbilityNames()) {
Ability ability = controller.getAbility(name);
if (ability != null) {
map.put(ability.getName(), index++);
}
}
}
sendScrollData(player, map, EnumScrollData.BUILTIN_ABILITIES);
}
public static void sendCustomAbilitiesData(EntityPlayerMP player) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
kamkeel.npcs.controllers.data.ability.AbilityController controller = kamkeel.npcs.controllers.data.ability.AbilityController.Instance;
@@ -168,9 +168,16 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
addButton(toggleBtn);
}
// Check if selected ability is built-in (non-editable)
boolean selectedIsBuiltIn = false;
if (selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) {
Ability sel = npcSlots.get(selectedAbilityIndex).getAbility();
if (sel != null && sel.isBuiltIn()) selectedIsBuiltIn = true;
}
// Right side buttons: Edit and Up/Down carrots with Save button
addButton(new GuiNpcButton(72, guiLeft + 210, y + 145, 55, 20, "gui.edit"));
getButton(72).setEnabled(selectedAbilityIndex >= 0);
getButton(72).setEnabled(selectedAbilityIndex >= 0 && !selectedIsBuiltIn);
// Up/Down carrot buttons
addButton(new GuiNpcButton(73, guiLeft + 270, y + 145, 20, 20, "<"));
@@ -180,7 +187,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
// Save button (to the right of carrots, only enabled when ability is selected)
addButton(new GuiNpcButton(76, centerX, y + 82, 40, 20, "gui.save"));
getButton(76).setEnabled(selectedAbilityIndex >= 0);
getButton(76).setEnabled(selectedAbilityIndex >= 0 && !selectedIsBuiltIn);
}
@Override
@@ -464,6 +471,10 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
String displayName = I18n.format(typeId);
// Search matches either the display name or the typeId
if (search.isEmpty() || displayName.toLowerCase().contains(search) || typeId.toLowerCase().contains(search)) {
// Gray out built-in types to distinguish them from configurable types
if (AbilityController.Instance.isBuiltInType(typeId)) {
displayName = "\u00A77" + displayName;
}
list.add(displayName);
displayNameToTypeId.put(displayName, typeId);
}
@@ -512,6 +523,9 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
if (slot.isReference()) {
return colorPrefix + (index + 1) + ". \u00A7e> " + nameDisplay + "\u00A7r";
}
if (ability.isBuiltIn()) {
return colorPrefix + (index + 1) + ". \u00A77" + nameDisplay + "\u00A7r";
}
return colorPrefix + (index + 1) + ". " + nameDisplay;
}
@@ -612,10 +626,11 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC
public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) {
if (scroll == npcAbilitiesScroll && selectedAbilityIndex >= 0 && selectedAbilityIndex < npcSlots.size()) {
AbilitySlot slot = npcSlots.get(selectedAbilityIndex);
Ability ability = slot.getAbility();
if (ability != null && ability.isBuiltIn()) return;
if (slot.isReference()) {
setSubGui(new SubGuiAbilityEditMode());
} else {
Ability ability = slot.getAbility();
if (ability != null) {
setSubGui(ability.createConfigGui(this));
}
@@ -58,6 +58,8 @@ public class SubGuiAbilityTypeSelect extends SubGuiInterface implements ICustomS
allDisplayNameToTypeId.clear();
String[] types = AbilityController.Instance.getTypes();
for (String typeId : types) {
// Exclude built-in types — they cannot be created as custom presets
if (AbilityController.Instance.isBuiltInType(typeId)) continue;
String displayName = I18n.format(typeId);
allDisplayNameToTypeId.put(displayName, typeId);
}
@@ -6,6 +6,7 @@ import kamkeel.npcs.controllers.data.ability.AbilityController;
import java.util.UUID;
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.CustomAbilitiesGetPacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilityGetPacket;
import kamkeel.npcs.network.packets.request.ability.CustomAbilityRemovePacket;
@@ -51,6 +52,7 @@ import java.util.Vector;
* - Left side: 3D preview of NPC with ability effects
* - Right side: Ability list with search
* - Play/Pause/Stop buttons to preview abilities
* - Toggle between Custom and Built-in ability views
*/
public class GuiNpcManageAbilities extends GuiAbilityInterface
implements ICustomScrollListener, ISubGuiListener, IAbilityConfigCallback,
@@ -58,7 +60,8 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
// ==================== DATA ====================
private GuiCustomScroll scroll;
private HashMap<String, Integer> abilityData = new HashMap<>();
private HashMap<String, Integer> customData = new HashMap<>();
private HashMap<String, Integer> builtInData = new HashMap<>();
/** Maps display name -> UUID for custom abilities */
private final HashMap<String, String> displayToUuid = new HashMap<>();
private String selected = null;
@@ -66,6 +69,10 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
private String search = "";
private Ability selectedAbility = null;
// ==================== BUILT-IN TOGGLE ====================
private boolean showingBuiltIn = false;
private boolean currentIsBuiltIn = false;
// ==================== PENDING SAVE ====================
private Ability pendingSaveAbility = null;
private boolean pendingNewCreation = false;
@@ -88,7 +95,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
previewExecutor = new AbilityPreviewExecutor();
previewExecutor.setParentGui(this);
// Request ability data from server
// Request ability data from server (sends both custom and built-in)
PacketClient.sendClient(new CustomAbilitiesGetPacket());
// Enable animation data
@@ -100,18 +107,26 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
public void initGui() {
super.initGui();
// Add button
addButton(new GuiNpcButton(2, guiLeft + 368, guiTop + 8, 45, 20, "gui.add"));
// Toggle button — added FIRST to maintain stable buttonList index
String toggleLabel = showingBuiltIn ? "gui.builtin" : "gui.custom";
addButton(new GuiNpcButton(10, guiLeft + 368, guiTop + 52, 45, 20, toggleLabel));
// Remove button
addButton(new GuiNpcButton(1, guiLeft + 368, guiTop + 30, 45, 20, "gui.remove"));
getButton(1).setEnabled(selected != null && !selected.isEmpty() && displayToUuid.containsKey(selected));
if (!showingBuiltIn) {
// Add button — only for custom view
addButton(new GuiNpcButton(2, guiLeft + 368, guiTop + 8, 45, 20, "gui.add"));
// Edit button
addButton(new GuiNpcButton(100, guiLeft + 368, guiTop + 52, 45, 20, "gui.edit"));
getButton(100).setEnabled(selected != null && !selected.isEmpty() && selectedAbility != null);
// Remove button — only for custom view
addButton(new GuiNpcButton(1, guiLeft + 368, guiTop + 30, 45, 20, "gui.remove"));
getButton(1).setEnabled(selected != null && !selected.isEmpty() && displayToUuid.containsKey(selected));
}
// Scroll list of saved abilities (right side)
// Edit button — hidden when viewing built-in
if (!showingBuiltIn && !currentIsBuiltIn) {
addButton(new GuiNpcButton(100, guiLeft + 368, guiTop + 74, 45, 20, "gui.edit"));
getButton(100).setEnabled(selected != null && !selected.isEmpty() && selectedAbility != null);
}
// Scroll list of abilities (right side)
if (scroll == null) {
scroll = new GuiCustomScroll(this, 0);
scroll.setSize(143, 185);
@@ -122,7 +137,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
// Update scroll list
scroll.setList(getSearchList());
if (selected != null && displayToUuid.containsKey(selected)) {
if (selected != null) {
scroll.setSelected(selected);
}
@@ -134,9 +149,6 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
return;
}
// ID/Name label (below search bar)
addLabel(new GuiNpcLabel(10, "ID: " + selectedAbility.getId(), guiLeft + 220, guiTop + 215));
// Play/Pause/Stop buttons (below model preview area)
String animTexture = "customnpcs:textures/gui/animation.png";
int playButtonOffsetX = 60;
@@ -161,17 +173,34 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
}
}
private HashMap<String, Integer> getCurrentData() {
return showingBuiltIn ? builtInData : customData;
}
private List<String> getSearchList() {
if (search.isEmpty()) {
return new ArrayList<>(displayToUuid.keySet());
}
List<String> list = new ArrayList<>();
for (String name : displayToUuid.keySet()) {
if (name.toLowerCase().contains(search.toLowerCase())) {
list.add(name);
if (showingBuiltIn) {
if (search.isEmpty()) {
return new ArrayList<>(builtInData.keySet());
}
List<String> list = new ArrayList<>();
for (String name : builtInData.keySet()) {
if (name.toLowerCase().contains(search.toLowerCase())) {
list.add(name);
}
}
return list;
} else {
if (search.isEmpty()) {
return new ArrayList<>(displayToUuid.keySet());
}
List<String> list = new ArrayList<>();
for (String name : displayToUuid.keySet()) {
if (name.toLowerCase().contains(search.toLowerCase())) {
list.add(name);
}
}
return list;
}
return list;
}
@Override
@@ -227,13 +256,30 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
int id = guibutton.id;
if (id == 2) {
// Toggle between custom and built-in views
if (id == 10) {
showingBuiltIn = !showingBuiltIn;
selected = null;
selectedUuid = null;
selectedAbility = null;
currentIsBuiltIn = false;
search = "";
previewExecutor.stop();
if (scroll != null) {
scroll.clear();
}
PacketClient.sendClient(new CustomAbilitiesGetPacket());
initGui();
return;
}
if (id == 2 && !showingBuiltIn) {
// Add — open type selection
setSubGui(new SubGuiAbilityTypeSelect());
} else if (id == 1 && selected != null && selectedUuid != null) {
} else if (id == 1 && !showingBuiltIn && selected != null && selectedUuid != null) {
GuiYesNo guiyesno = new GuiYesNo(this, selected, StatCollector.translateToLocal("gui.delete"), 1);
displayGuiScreen(guiyesno);
} else if (id == 100 && selectedAbility != null) {
} else if (id == 100 && !showingBuiltIn && !currentIsBuiltIn && selectedAbility != null) {
// Edit
previewExecutor.stop();
setSubGui(selectedAbility.createConfigGui(this));
@@ -276,11 +322,20 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
String newSelection = scroll.getSelected();
if (newSelection != null && !newSelection.equals(selected)) {
previewExecutor.stop();
selected = newSelection;
selectedUuid = displayToUuid.get(selected);
if (selectedUuid != null) {
PacketClient.sendClient(new CustomAbilityGetPacket(selectedUuid));
if (showingBuiltIn) {
// Fetch built-in ability by display name
selectedUuid = null;
currentIsBuiltIn = true;
PacketClient.sendClient(new BuiltInAbilityGetPacket(selected));
} else {
// Fetch custom ability by UUID
selectedUuid = displayToUuid.get(selected);
currentIsBuiltIn = false;
if (selectedUuid != null) {
PacketClient.sendClient(new CustomAbilityGetPacket(selectedUuid));
}
}
}
}
@@ -289,8 +344,8 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
@Override
public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) {
if (scroll.id == 0 && selection != null && !selection.isEmpty()) {
// Double-click to edit
if (selectedAbility != null) {
// Double-click to edit — only for custom, non-built-in abilities
if (!showingBuiltIn && !currentIsBuiltIn && selectedAbility != null) {
previewExecutor.stop();
setSubGui(selectedAbility.createConfigGui(this));
}
@@ -301,7 +356,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
public void setData(Vector<String> list, HashMap<String, Integer> data, EnumScrollData type) {
if (type == EnumScrollData.CUSTOM_ABILITIES) {
String prevSelected = scroll != null ? scroll.getSelected() : null;
this.abilityData = data;
this.customData = data;
displayToUuid.clear();
for (String key : data.keySet()) {
// key format: "displayName\tUUID"
@@ -314,18 +369,30 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
displayToUuid.put(key, key);
}
}
if (scroll != null) {
if (!showingBuiltIn && scroll != null) {
scroll.setList(getSearchList());
if (prevSelected != null && displayToUuid.containsKey(prevSelected)) {
scroll.setSelected(prevSelected);
}
}
initGui();
} else if (type == EnumScrollData.BUILTIN_ABILITIES) {
this.builtInData = data;
if (showingBuiltIn && scroll != null) {
scroll.setList(getSearchList());
if (selected != null && builtInData.containsKey(selected)) {
scroll.setSelected(selected);
}
}
initGui();
}
}
@Override
public void setGuiData(NBTTagCompound compound) {
// Check if this is a built-in ability response
currentIsBuiltIn = compound.hasKey("BuiltIn") && compound.getBoolean("BuiltIn");
// Received ability data from server
selectedAbility = AbilityController.Instance.fromNBT(compound);
if (selectedAbility != null) {
@@ -364,7 +431,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
return;
}
pendingTypeId = null;
saveAndOpenConfig(newAbility);
openConfig(newAbility);
return;
}
}
@@ -390,7 +457,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
setSubGui(new SubGuiDuplicateNameConfirm());
return;
}
saveAndOpenConfig(newAbility);
openConfig(newAbility);
return;
}
}
@@ -398,7 +465,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
SubGuiDuplicateNameConfirm confirm = (SubGuiDuplicateNameConfirm) subgui;
if (confirm.isConfirmed() && pendingSaveAbility != null) {
if (pendingNewCreation) {
saveAndOpenConfig(pendingSaveAbility);
openConfig(pendingSaveAbility);
pendingSaveAbility = null;
pendingNewCreation = false;
return;
@@ -434,8 +501,7 @@ public class GuiNpcManageAbilities extends GuiAbilityInterface
initGui();
}
private void saveAndOpenConfig(Ability ability) {
PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT()));
private void openConfig(Ability ability) {
selectedAbility = ability;
selected = ability.getName();
selectedUuid = ability.getId();
@@ -8,6 +8,7 @@ public enum EnumScrollData {
QUEST_GROUP,
ABILITY_TYPES,
CUSTOM_ABILITIES,
BUILTIN_ABILITIES,
ANIMATIONS,
BUILTIN_ANIMATIONS
}