Keep client-only sub-GUI types out of condition and zone descriptors

Same leak as the ability magic editor: @SideOnly strips the annotated method on a
server but not the synthetic lambdas it generates, so ConditionHasEffect,
ConditionQuestCompleted, AbilityTrap and AbilityZone all shipped methods whose
descriptors named a client-only GUI class. Each field is now built in the GUI
class it belongs to.
This commit is contained in:
KAMKEEL committed 2026-08-11 05:00:02 -04:00
1 parent 466b49db5e
commit 677d50fa03
8 files changed
+104 -49

No files matched your search

@@ -34,27 +34,15 @@ public class ConditionHasEffect extends AbilityCondition {
return CustomEffectController.getInstance().hasEffect(player, effectId, effectIndex);
}
public void setEffect(int effectId, int effectIndex) {
this.effectId = effectId;
this.effectIndex = effectIndex;
}
@SideOnly(Side.CLIENT)
@Override
public void getConditionDefinitions(List<FieldDef> defs) {
defs.add(FieldDef.subGuiField("condition.select_effect",
() -> new SubGuiCustomEffectSelect(effectId, effectIndex),
gui -> {
SubGuiCustomEffectSelect sel = (SubGuiCustomEffectSelect) gui;
if (sel.getSelectedEffectId() >= 0) {
effectId = sel.getSelectedEffectId();
effectIndex = sel.getSelectedIndex();
}
})
.buttonLabel(() -> {
if (effectId < 0) return "None";
CustomEffect effect = getEffect();
return effect != null ? effect.getName() : "ID:" + effectId;
})
.clearable(() -> {
effectId = -1;
effectIndex = 0;
}));
defs.add(SubGuiCustomEffectSelect.createSelectField(this));
}
@SideOnly(Side.CLIENT)
@@ -74,7 +62,7 @@ public class ConditionHasEffect extends AbilityCondition {
return effectId >= 0;
}
private CustomEffect getEffect() {
public CustomEffect getEffect() {
HashMap<Integer, CustomEffect> map = CustomEffectController.getInstance().getEffectMap(effectIndex);
return map != null ? map.get(effectId) : null;
}
@@ -38,20 +38,7 @@ public class ConditionQuestCompleted extends AbilityCondition {
@SideOnly(Side.CLIENT)
@Override
public void getConditionDefinitions(List<FieldDef> defs) {
defs.add(FieldDef.subGuiField("condition.select_quest",
() -> new GuiQuestSelection(questId),
gui -> {
GuiQuestSelection sel = (GuiQuestSelection) gui;
if (sel.selectedQuest != null) {
questId = sel.selectedQuest.id;
}
})
.buttonLabel(() -> {
if (questId < 0) return "None";
Quest q = QuestController.Instance.quests.get(questId);
return q != null ? q.title : "ID:" + questId;
})
.clearable(() -> questId = -1));
defs.add(GuiQuestSelection.createConditionField(this));
}
@SideOnly(Side.CLIENT)
@@ -300,14 +300,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap {
@SideOnly(Side.CLIENT)
@Override
protected void addPresetFieldDef(List<FieldDef> defs) {
defs.add(FieldDef.subGuiField("gui.applyPreset",
SubGuiTrapPresetSelector::new,
gui -> {
SubGuiTrapPresetSelector selector = (SubGuiTrapPresetSelector) gui;
if (selector.selectedPreset != null) {
applyTrapPresetDefaults(selector.selectedPreset);
}
}));
defs.add(SubGuiTrapPresetSelector.createPresetField(this::applyTrapPresetDefaults));
}
@SideOnly(Side.CLIENT)
@@ -690,14 +690,7 @@ public abstract class AbilityZone extends Ability {
@SideOnly(Side.CLIENT)
protected void addPresetFieldDef(List<FieldDef> defs) {
defs.add(FieldDef.subGuiField("gui.applyPreset",
SubGuiZonePresetSelector::new,
gui -> {
SubGuiZonePresetSelector selector = (SubGuiZonePresetSelector) gui;
if (selector.selectedPreset != null) {
applyPresetDefaults(selector.selectedPreset);
}
}));
defs.add(SubGuiZonePresetSelector.createPresetField(this::applyPresetDefaults));
}
@SideOnly(Side.CLIENT)
@@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.StatCollector;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.SubGuiInterface;
@@ -12,6 +13,25 @@ public class SubGuiTrapPresetSelector extends SubGuiInterface {
public String selectedPreset = null;
/**
* Builds the trap editor's preset field.
* <p>
* This lives here rather than in AbilityTrap because the lambda below carries this class in
* its method descriptor. AbilityTrap is a both-side class, and @SideOnly does not strip the
* synthetic lambda methods it generates - so anything reflecting over its declared methods
* on a server would resolve that descriptor and fail to find this client-only type.
*/
public static FieldDef createPresetField(java.util.function.Consumer<String> applyPreset) {
return FieldDef.subGuiField("gui.applyPreset",
SubGuiTrapPresetSelector::new,
gui -> {
SubGuiTrapPresetSelector selector = (SubGuiTrapPresetSelector) gui;
if (selector.selectedPreset != null) {
applyPreset.accept(selector.selectedPreset);
}
});
}
private static final String[] PRESET_NAMES = {"HIDDEN", "VENOM", "EXPLOSIVE", "CURSED", "SHOCK", "SNARE"};
private static final String[] PRESET_LABELS = {
"ability.preset.hidden", "ability.preset.venom", "ability.preset.explosive",
@@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.StatCollector;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.SubGuiInterface;
@@ -12,6 +13,25 @@ public class SubGuiZonePresetSelector extends SubGuiInterface {
public String selectedPreset = null;
/**
* Builds the zone editor's preset field.
* <p>
* This lives here rather than in AbilityZone because the lambda below carries this class in
* its method descriptor. AbilityZone is a both-side class, and @SideOnly does not strip the
* synthetic lambda methods it generates - so anything reflecting over its declared methods
* on a server would resolve that descriptor and fail to find this client-only type.
*/
public static FieldDef createPresetField(java.util.function.Consumer<String> applyPreset) {
return FieldDef.subGuiField("gui.applyPreset",
SubGuiZonePresetSelector::new,
gui -> {
SubGuiZonePresetSelector selector = (SubGuiZonePresetSelector) gui;
if (selector.selectedPreset != null) {
applyPreset.accept(selector.selectedPreset);
}
});
}
private static final String[] PRESET_NAMES = {"DEFAULT", "TOXIC", "INFERNO", "ARCANE", "ELECTRIC", "FROST"};
private static final String[] PRESET_LABELS = {
"ability.preset.default", "ability.preset.toxic", "ability.preset.inferno",
@@ -3,6 +3,8 @@ package noppes.npcs.client.gui.advanced;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.gui.GuiButton;
import kamkeel.npcs.controllers.data.ability.conditions.ConditionHasEffect;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.client.gui.util.GuiCustomScroll;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.GuiNpcTextField;
@@ -25,6 +27,31 @@ import java.util.Map;
@SideOnly(Side.CLIENT)
public class SubGuiCustomEffectSelect extends SubGuiInterface implements ICustomScrollListener, ITextfieldListener {
/**
* Builds the condition editor's effect-select field.
* <p>
* This lives here rather than in ConditionHasEffect because the lambdas below carry this
* class in their method descriptors. The condition is a both-side class, and @SideOnly does
* not strip the synthetic lambda methods it generates - so anything reflecting over its
* declared methods on a server would resolve those descriptors and fail to find this type.
*/
public static FieldDef createSelectField(ConditionHasEffect condition) {
return FieldDef.subGuiField("condition.select_effect",
() -> new SubGuiCustomEffectSelect(condition.getEffectId(), condition.getEffectIndex()),
gui -> {
SubGuiCustomEffectSelect sel = (SubGuiCustomEffectSelect) gui;
if (sel.getSelectedEffectId() >= 0) {
condition.setEffect(sel.getSelectedEffectId(), sel.getSelectedIndex());
}
})
.buttonLabel(() -> {
if (condition.getEffectId() < 0) return "None";
CustomEffect effect = condition.getEffect();
return effect != null ? effect.getName() : "ID:" + condition.getEffectId();
})
.clearable(() -> condition.setEffect(-1, 0));
}
private GuiCustomScroll scroll;
private final HashMap<String, Integer> displayNameToId = new HashMap<>();
private final HashMap<String, Integer> allDisplayNameToId = new HashMap<>();
@@ -2,6 +2,8 @@ package noppes.npcs.client.gui.select;
import com.google.common.collect.Lists;
import net.minecraft.client.gui.GuiButton;
import kamkeel.npcs.controllers.data.ability.conditions.ConditionQuestCompleted;
import noppes.npcs.client.gui.builder.FieldDef;
import noppes.npcs.client.gui.util.GuiCustomScroll;
import noppes.npcs.client.gui.util.GuiNpcButton;
import noppes.npcs.client.gui.util.GuiNpcLabel;
@@ -27,6 +29,31 @@ public class GuiQuestSelection extends SubGuiInterface implements ICustomScrollL
private QuestCategory selectedCategory;
public Quest selectedQuest;
/**
* Builds the condition editor's quest-select field.
* <p>
* This lives here rather than in ConditionQuestCompleted because the lambdas below carry
* this class in their method descriptors. The condition is a both-side class, and @SideOnly
* does not strip the synthetic lambda methods it generates - so anything reflecting over its
* declared methods on a server would resolve those descriptors and fail to find this type.
*/
public static FieldDef createConditionField(ConditionQuestCompleted condition) {
return FieldDef.subGuiField("condition.select_quest",
() -> new GuiQuestSelection(condition.getQuestId()),
gui -> {
GuiQuestSelection sel = (GuiQuestSelection) gui;
if (sel.selectedQuest != null) {
condition.setQuestId(sel.selectedQuest.id);
}
})
.buttonLabel(() -> {
if (condition.getQuestId() < 0) return "None";
Quest q = QuestController.Instance.quests.get(condition.getQuestId());
return q != null ? q.title : "ID:" + condition.getQuestId();
})
.clearable(() -> condition.setQuestId(-1));
}
private GuiSelectionListener listener;
private String catSearch = "";
private String questSearch = "";