diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 33d50ed1e..dc0de02fa 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -302,6 +302,26 @@ public abstract class Ability implements IAbility, IAbilityAction { return hasDamage(); } + // ── Display methods for inventory preview ────────────────────── + + /** Base damage for preview display. Override in damaging types. */ + public float getDisplayDamage() { return 0; } + + /** True if getDisplayDamage() is damage-per-second (e.g., Hazard). */ + public boolean isDisplayDamageDPS() { return false; } + + /** Base barrier health for preview display. Override in barrier types. */ + public float getDisplayBarrierHealth() { return 0; } + + /** True if this ability reflects projectiles. */ + public boolean isDisplayReflect() { return false; } + + /** Reflection strength percentage (0-100). */ + public float getDisplayReflectStrength() { return 0; } + + /** True if this ability absorbs projectiles. */ + public boolean isDisplayAbsorbing() { return false; } + /** * Override to provide variant templates for this ability type. * When a user creates this ability type, they will be shown a selection dialog diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java index 4ad6811d5..c88d63c30 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java @@ -276,6 +276,9 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public float getKnockback() { return knockback; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java index 2fdc8fd48..8c6fecc4c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCutter.java @@ -375,6 +375,9 @@ public class AbilityCutter extends Ability implements IAbilityCutter { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public float getKnockback() { return knockback; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java index 8d3fdbd21..47f9156a8 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java @@ -127,6 +127,12 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { this.damagePerSecond = damagePerSecond; } + @Override + public float getDisplayDamage() { return damagePerSecond; } + + @Override + public boolean isDisplayDamageDPS() { return true; } + public int getDamageInterval() { return damageInterval; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java index 2e5de3006..56458b1ab 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeavyHit.java @@ -174,6 +174,9 @@ public class AbilityHeavyHit extends Ability implements IAbilityHeavyHit { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public float getKnockback() { return knockback; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java index 2c9945299..104209eeb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityProjectile.java @@ -293,6 +293,9 @@ public class AbilityProjectile extends Ability implements IAbilityProjectile { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public float getSpeed() { return speed; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java index a41da923c..c5e860c09 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityShockwave.java @@ -197,6 +197,9 @@ public class AbilityShockwave extends Ability implements IAbilityShockwave { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public boolean isAoe() { return aoe; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java index d7a0f4cd6..0a5c134f7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java @@ -751,6 +751,9 @@ public class AbilitySlam extends Ability implements IAbilitySlam { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public float getRadius() { return radius; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java index 448a431af..d22150c9e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java @@ -536,6 +536,9 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public float getDamageRadius() { return damageRadius; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java index d309b9e66..5e508069a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java @@ -270,6 +270,9 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public float getDamageRadius() { return damageRadius; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java index 7c0c88319..8f9b3f3a0 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java @@ -349,6 +349,9 @@ public class AbilityVortex extends Ability implements IAbilityVortex { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public float getKnockback() { return knockback; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java index d4bdfa924..5d12b4c11 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityBarrier.java @@ -213,6 +213,18 @@ public abstract class AbilityBarrier extends AbilityEnergy { barrierData.useHealth = useHealth; } + @Override + public float getDisplayBarrierHealth() { return barrierData.useHealth ? barrierData.maxHealth : 0; } + + @Override + public boolean isDisplayReflect() { return barrierData.reflect; } + + @Override + public float getDisplayReflectStrength() { return barrierData.reflectStrengthPct; } + + @Override + public boolean isDisplayAbsorbing() { return barrierData.absorbing; } + public int getBarrierDuration() { return barrierData.durationTicks; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 2745f1a2f..7e7986d20 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -639,6 +639,9 @@ public abstract class AbilityEnergyProjectile combatData.damage = damage; } + @Override + public float getDisplayDamage() { return combatData.damage; } + @Override public float getKnockback() { return combatData.knockback; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java index 3fc833ff4..67c2d9c0d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java @@ -235,6 +235,9 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper { this.damage = damage; } + @Override + public float getDisplayDamage() { return damage; } + public int getDamageInterval() { return damageInterval; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java index 104cae221..bfe8c5b36 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityWall.java @@ -200,6 +200,9 @@ public class AbilityWall extends AbilityBarrier { panelData.setLaunchDamage(damage); } + @Override + public float getDisplayDamage() { return panelData.launching ? panelData.launchDamage : 0; } + public float getLaunchKnockback() { return panelData.launchKnockback; } diff --git a/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java b/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java index 36def1d43..7e51146a2 100644 --- a/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/player/inventory/GuiAbilities.java @@ -25,11 +25,17 @@ import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; import tconstruct.client.tabs.AbstractTab; +import noppes.npcs.controllers.MagicController; +import noppes.npcs.controllers.data.Magic; +import noppes.npcs.controllers.data.MagicData; +import noppes.npcs.controllers.data.MagicEntry; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Map; public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/standardbg.png"); @@ -92,6 +98,11 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { private AbilityIcon cachedDetailIcon = null; private int cachedDetailIndex = -1; + // ═══ Info panel scroll ═══ + private int infoScrollOffset = 0; + private int infoContentHeight = 0; + private int lastInfoIndex = -1; + // ═══ Hotbar state ═══ private int hoveredSlotIndex = -1; private AbilityIcon[] hotbarIcons = new AbilityIcon[AbilityHotbarData.TOTAL_SLOTS]; @@ -436,11 +447,17 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { int infoIndex = hoveredGridIndex >= 0 ? hoveredGridIndex : selectedIndex; if (infoIndex < 0 || infoIndex >= filteredKeys.size()) return; + // Reset scroll when ability changes + if (infoIndex != lastInfoIndex) { + infoScrollOffset = 0; + lastInfoIndex = infoIndex; + } + FontRenderer fr = mc.fontRenderer; int panelInnerLeft = guiLeft + infoLeft + infoPad; int panelInnerTop = guiTop + infoTop + infoPad; int panelInnerWidth = (infoRight - infoLeft) - infoPad * 2; - int y = panelInnerTop; + int panelHeight = infoBottom - infoTop; // Cached icon for detail preview if (cachedDetailIndex != infoIndex) { @@ -448,14 +465,28 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { cachedDetailIndex = infoIndex; } + Ability ability = filteredAbilities.get(infoIndex); + // Get toggle state for icon rendering and state display int toggleState = 0; PlayerData togglePlayerData = ClientCacheHandler.playerData; - Ability infoAbility = filteredAbilities.get(infoIndex); - if (infoAbility != null && infoAbility.isToggleable() && togglePlayerData != null && togglePlayerData.abilityData != null) { + if (ability != null && ability.isToggleable() && togglePlayerData != null && togglePlayerData.abilityData != null) { toggleState = togglePlayerData.abilityData.getToggleState(filteredKeys.get(infoIndex)); } + // GL Scissor to clip info panel content + ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + int scaleFactor = sr.getScaleFactor(); + int scissorX = (guiLeft + infoLeft) * scaleFactor; + int scissorY = mc.displayHeight - (guiTop + infoBottom) * scaleFactor; + int scissorW = (infoRight - infoLeft) * scaleFactor; + int scissorH = panelHeight * scaleFactor; + + GL11.glEnable(GL11.GL_SCISSOR_TEST); + GL11.glScissor(scissorX, scissorY, scissorW, scissorH); + + int y = panelInnerTop - infoScrollOffset; + // Preview icon (centered, state-aware for multi-state toggles) if (cachedDetailIcon != null) { float drawSize = cachedDetailIcon.getDrawSize(); @@ -490,9 +521,8 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { } y += 4; - // Type info - Ability ability = filteredAbilities.get(infoIndex); if (ability != null) { + // Type info String typeId = ability.getTypeId(); if (typeId != null && !typeId.isEmpty()) { String translated = StatCollector.translateToLocal(typeId); @@ -503,6 +533,7 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { } } + // Toggle info if (ability.isToggleable()) { String toggleText = "[" + StatCollector.translateToLocal("gui.toggle") + "]"; int textW = fr.getStringWidth(toggleText); @@ -510,12 +541,11 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { y += fr.FONT_HEIGHT + 3; } - // Toggle state (uses pre-computed toggleState) + // Toggle state if (ability.isToggleable()) { String stateText; String prefix; if (toggleState > 0) { - // Active — check for state label (multi-state toggles like Ki Weapon) String stateLabel = ability.getToggleStateLabel(toggleState); stateText = stateLabel != null ? stateLabel : StatCollector.translateToLocal("gui.toggle.active"); prefix = "\u00A72"; @@ -528,20 +558,110 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { y += fr.FONT_HEIGHT + 3; } - // Cooldown info + // Cooldown info — yellow if per-ability cooldown if (ability.getCooldownTicks() > 0) { float seconds = ability.getCooldownTicks() / 20.0f; String cdText = StatCollector.translateToLocal("ability.cooldown") + ": " + String.format("%.1f", seconds) + "s"; int textW2 = fr.getStringWidth(cdText); - fr.drawStringWithShadow("\u00A77" + cdText, panelInnerLeft + (panelInnerWidth - textW2) / 2, y, 0xAAAAAA); + boolean perAbility = ability.isPerAbilityCooldown(); + String cdColor = perAbility ? "\u00A7e" : "\u00A77"; + int cdColorInt = perAbility ? 0xFFFF55 : 0xAAAAAA; + fr.drawStringWithShadow(cdColor + cdText, panelInnerLeft + (panelInnerWidth - textW2) / 2, y, cdColorInt); y += fr.FONT_HEIGHT + 3; - if (ability.isPerAbilityCooldown()) { + if (perAbility) { String perText = "[" + StatCollector.translateToLocal("ability.perAbilityCooldown") + "]"; int ptW = fr.getStringWidth(perText); fr.drawStringWithShadow("\u00A7e" + perText, panelInnerLeft + (panelInnerWidth - ptW) / 2, y, 0xFFFF55); + y += fr.FONT_HEIGHT + 3; } } + + // Damage info + float baseDamage = ability.getDisplayDamage(); + if (baseDamage > 0) { + float displayDamage = baseDamage; + if (AbilityController.Instance != null && mc.thePlayer != null) { + displayDamage = AbilityController.Instance.fireModifyProjectileDamage(ability, mc.thePlayer, baseDamage); + } + String label = ability.isDisplayDamageDPS() + ? StatCollector.translateToLocal("gui.dps") + : StatCollector.translateToLocal("ability.preview.damage"); + String dmgText = label + ": " + String.format("%.1f", displayDamage); + int textW = fr.getStringWidth(dmgText); + fr.drawStringWithShadow("\u00A7c" + dmgText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0xFF5555); + y += fr.FONT_HEIGHT + 3; + } + + // Barrier health + float baseHealth = ability.getDisplayBarrierHealth(); + if (baseHealth > 0) { + float displayHealth = baseHealth; + if (AbilityController.Instance != null && mc.thePlayer != null) { + displayHealth = AbilityController.Instance.fireModifyBarrierHealth(ability, mc.thePlayer, baseHealth); + } + String healthText = StatCollector.translateToLocal("ability.preview.health") + ": " + String.format("%.0f", displayHealth); + int textW = fr.getStringWidth(healthText); + fr.drawStringWithShadow("\u00A7a" + healthText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0x55FF55); + y += fr.FONT_HEIGHT + 3; + } + + // Barrier reflection + if (ability.isDisplayReflect()) { + String reflectText = StatCollector.translateToLocal("ability.reflect") + ": " + String.format("%.0f%%", ability.getDisplayReflectStrength()); + int textW = fr.getStringWidth(reflectText); + fr.drawStringWithShadow("\u00A7d" + reflectText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0xFF55FF); + y += fr.FONT_HEIGHT + 3; + } + + // Barrier absorbing + if (ability.isDisplayAbsorbing()) { + String absorbText = "[" + StatCollector.translateToLocal("ability.absorbing") + "]"; + int textW = fr.getStringWidth(absorbText); + fr.drawStringWithShadow("\u00A7b" + absorbText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0x55FFFF); + y += fr.FONT_HEIGHT + 3; + } + + // Magic info + if (ability.hasMagic()) { + y += 2; + MagicData magicData = ability.getMagicData(); + if (magicData.isEmpty()) { + String magicText = StatCollector.translateToLocal("ability.preview.magic.casters"); + int textW = fr.getStringWidth(magicText); + fr.drawStringWithShadow("\u00A79" + magicText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0x5555FF); + y += fr.FONT_HEIGHT + 3; + } else { + for (Map.Entry entry : magicData.getMagics().entrySet()) { + Magic magic = MagicController.getInstance() != null + ? MagicController.getInstance().getMagic(entry.getKey()) : null; + String magicName = magic != null ? magic.getDisplayName() : "Magic #" + entry.getKey(); + String splitText = magicName + ": " + Math.round(entry.getValue().split * 100) + "%"; + int textW = fr.getStringWidth(splitText); + fr.drawStringWithShadow("\u00A79" + splitText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, 0x5555FF); + y += fr.FONT_HEIGHT + 1; + } + y += 2; + } + } + } + + // Track content height for scroll limits + infoContentHeight = (y + infoScrollOffset) - panelInnerTop + infoPad * 2; + + GL11.glDisable(GL11.GL_SCISSOR_TEST); + + // Scroll indicator + if (infoContentHeight > panelHeight) { + int maxScroll = Math.max(1, infoContentHeight - panelHeight); + if (infoScrollOffset > maxScroll) infoScrollOffset = maxScroll; + float scrollPercent = (float) infoScrollOffset / maxScroll; + int barHeight = panelHeight - 4; + int thumbHeight = Math.max(8, barHeight * panelHeight / infoContentHeight); + int thumbY = guiTop + infoTop + 2 + (int) ((barHeight - thumbHeight) * scrollPercent); + int barX = guiLeft + infoRight - 3; + drawRect(barX, guiTop + infoTop + 2, barX + 2, guiTop + infoTop + 2 + barHeight, 0x40FFFFFF); + drawRect(barX, thumbY, barX + 2, thumbY + thumbHeight, 0xA0FFFFFF); } } @@ -903,11 +1023,26 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener { int wheel = Mouse.getEventDWheel(); if (wheel != 0) { - int maxScroll = getMaxScrollRow(); - if (wheel > 0) { - scrollRow = Math.max(0, scrollRow - 1); + int mouseX = Mouse.getEventX() * width / mc.displayWidth; + int mouseY = height - Mouse.getEventY() * height / mc.displayHeight - 1; + + if (mouseX >= guiLeft + infoLeft && mouseX < guiLeft + infoRight + && mouseY >= guiTop + infoTop && mouseY < guiTop + infoBottom) { + // Info panel scroll + int panelHeight = infoBottom - infoTop; + int maxInfoScroll = Math.max(0, infoContentHeight - panelHeight); + if (wheel > 0) { + infoScrollOffset = Math.max(0, infoScrollOffset - 10); + } else { + infoScrollOffset = Math.min(maxInfoScroll, infoScrollOffset + 10); + } } else { - scrollRow = Math.min(maxScroll, scrollRow + 1); + int maxScroll = getMaxScrollRow(); + if (wheel > 0) { + scrollRow = Math.max(0, scrollRow - 1); + } else { + scrollRow = Math.min(maxScroll, scrollRow + 1); + } } } } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index fd4923a57..e6dc2779e 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1795,6 +1795,9 @@ ability.section.magic=Magic ability.magic.note=Magic Note ability.magic.usesCaster=Uses the Caster's Magic Data ability.magic.editor=Magic Editor +ability.preview.damage=Damage +ability.preview.health=Health +ability.preview.magic.casters=Magic: Caster's ability.section.dodgeAnimations=Dodge Animations ability.section.visual=Visual ability.section.projectile1=Projectile 1