mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 21:17:32 +00:00
Complete Ability Hud System + Added-Per-Ability Cooldown System to be Reflected in Hud and Hud GUI
This commit is contained in:
1 parent
877fe2b275
commit
44118ec770
13 files changed
+467
-47
No files matched your search
@@ -109,6 +109,10 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
// Cooldown override
|
||||
protected boolean ignoreCooldown = false;
|
||||
|
||||
// Per-ability cooldown: if true, this ability has its own independent cooldown
|
||||
// instead of sharing the global cooldown with other abilities
|
||||
protected boolean perAbilityCooldown = false;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// BUILT-IN (set in constructor via configureAsBuiltIn, NOT persisted)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -603,8 +607,10 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
FieldDef.boolField("ability.syncWindup", this::isSyncWindupWithAnimation, this::setSyncWindupWithAnimation)
|
||||
.hover("ability.hover.sync")
|
||||
).tab("General").visibleWhen(() -> hasWindUpAnimation() && isSyncWindupWithAnimation()));
|
||||
defs.add(FieldDef.intField("ability.cooldownTicks", this::getCooldownTicks, this::setCooldownTicks)
|
||||
.tab("General").range(0, 10000));
|
||||
defs.add(FieldDef.row(
|
||||
FieldDef.intField("ability.cooldownTicks", this::getCooldownTicks, this::setCooldownTicks).range(0, 10000),
|
||||
FieldDef.boolField("ability.perAbilityCooldown", this::isPerAbilityCooldown, this::setPerAbilityCooldown)
|
||||
).tab("General"));
|
||||
defs.add(FieldDef.section("ability.section.movement").tab("General"));
|
||||
defs.add(FieldDef.stringEnumField("ability.lockMovement", LockMovementType.getDisplayKeys(),
|
||||
() -> this.getLockMovement().getDisplayKey(),
|
||||
@@ -1282,6 +1288,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
nbt.setTag("customData", (NBTTagCompound) customData.copy());
|
||||
nbt.setInteger("allowedBy", allowedBy.ordinal());
|
||||
nbt.setBoolean("ignoreCooldown", ignoreCooldown);
|
||||
nbt.setBoolean("perAbilityCooldown", perAbilityCooldown);
|
||||
|
||||
// Toggle
|
||||
nbt.setInteger("toggleStates", toggleStates);
|
||||
@@ -1363,6 +1370,7 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
customData = nbt.getCompoundTag("customData");
|
||||
allowedBy = UserType.fromOrdinal(nbt.getInteger("allowedBy"));
|
||||
ignoreCooldown = nbt.getBoolean("ignoreCooldown");
|
||||
perAbilityCooldown = nbt.getBoolean("perAbilityCooldown");
|
||||
|
||||
// Toggle
|
||||
toggleStates = nbt.getInteger("toggleStates");
|
||||
@@ -1951,6 +1959,14 @@ public abstract class Ability implements IAbility, IAbilityAction {
|
||||
this.ignoreCooldown = ignoreCooldown;
|
||||
}
|
||||
|
||||
public boolean isPerAbilityCooldown() {
|
||||
return perAbilityCooldown;
|
||||
}
|
||||
|
||||
public void setPerAbilityCooldown(boolean perAbilityCooldown) {
|
||||
this.perAbilityCooldown = perAbilityCooldown;
|
||||
}
|
||||
|
||||
public boolean isToggleable() {
|
||||
return toggleStates > 0;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import kamkeel.npcs.network.packets.data.SoundManagementPacket;
|
||||
import kamkeel.npcs.network.packets.data.SwingPlayerArmPacket;
|
||||
import kamkeel.npcs.network.packets.data.UpdateAnimationsPacket;
|
||||
import kamkeel.npcs.network.packets.data.VillagerListPacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.AbilityCooldownSyncPacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.AbilityHotbarSyncPacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket;
|
||||
@@ -603,6 +604,7 @@ public class PacketHandler {
|
||||
DATA_PACKET.registerPacket(new PlayerAbilitySyncPacket());
|
||||
DATA_PACKET.registerPacket(new PlayerAbilityStatePacket());
|
||||
DATA_PACKET.registerPacket(new AbilityHotbarSyncPacket());
|
||||
DATA_PACKET.registerPacket(new AbilityCooldownSyncPacket());
|
||||
}
|
||||
|
||||
public void registerPlayerPackets() {
|
||||
|
||||
@@ -65,6 +65,7 @@ public enum EnumDataPacket {
|
||||
PLAYER_ABILITY_SYNC,
|
||||
PLAYER_ABILITY_STATE,
|
||||
ABILITY_HOTBAR_SYNC,
|
||||
ABILITY_COOLDOWN_SYNC,
|
||||
|
||||
// Auction System
|
||||
AUCTION_DATA,
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package kamkeel.npcs.network.packets.data.ability;
|
||||
|
||||
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.enums.EnumDataPacket;
|
||||
import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import noppes.npcs.controllers.PlayerDataController;
|
||||
import noppes.npcs.controllers.data.PlayerData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lightweight packet that syncs cooldown state (global + per-ability) to the client.
|
||||
* Sent when cooldowns are rolled, reset, or on login.
|
||||
*/
|
||||
public final class AbilityCooldownSyncPacket extends AbstractPacket {
|
||||
public static final String packetName = "Data|AbilityCooldownSync";
|
||||
|
||||
private long globalCooldownEndTime;
|
||||
private int globalCooldownDuration;
|
||||
private HashMap<String, Long> perAbilityCooldownEndTimes;
|
||||
private HashMap<String, Integer> perAbilityCooldownDurations;
|
||||
|
||||
public AbilityCooldownSyncPacket() {
|
||||
}
|
||||
|
||||
public AbilityCooldownSyncPacket(long globalEndTime, int globalDuration,
|
||||
HashMap<String, Long> perEndTimes,
|
||||
HashMap<String, Integer> perDurations) {
|
||||
this.globalCooldownEndTime = globalEndTime;
|
||||
this.globalCooldownDuration = globalDuration;
|
||||
this.perAbilityCooldownEndTimes = perEndTimes;
|
||||
this.perAbilityCooldownDurations = perDurations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumDataPacket.ABILITY_COOLDOWN_SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.DATA_PACKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeLong(globalCooldownEndTime);
|
||||
out.writeInt(globalCooldownDuration);
|
||||
|
||||
// Write per-ability cooldowns
|
||||
int count = perAbilityCooldownEndTimes != null ? perAbilityCooldownEndTimes.size() : 0;
|
||||
out.writeInt(count);
|
||||
if (count > 0) {
|
||||
for (Map.Entry<String, Long> entry : perAbilityCooldownEndTimes.entrySet()) {
|
||||
ByteBufUtils.writeUTF8String(out, entry.getKey());
|
||||
out.writeLong(entry.getValue());
|
||||
Integer dur = perAbilityCooldownDurations.get(entry.getKey());
|
||||
out.writeInt(dur != null ? dur : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
long globalEndTime = in.readLong();
|
||||
int globalDuration = in.readInt();
|
||||
|
||||
int count = in.readInt();
|
||||
HashMap<String, Long> perEndTimes = new HashMap<>();
|
||||
HashMap<String, Integer> perDurations = new HashMap<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String key = ByteBufUtils.readUTF8String(in);
|
||||
long endTime = in.readLong();
|
||||
int duration = in.readInt();
|
||||
perEndTimes.put(key, endTime);
|
||||
perDurations.put(key, duration);
|
||||
}
|
||||
|
||||
PlayerData data = PlayerData.get(player);
|
||||
if (data != null && data.abilityData != null) {
|
||||
data.abilityData.applyCooldownSync(globalEndTime, globalDuration, perEndTimes, perDurations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the player's current cooldown state to their client.
|
||||
*/
|
||||
public static void sendToPlayer(EntityPlayerMP player) {
|
||||
PlayerData data = PlayerDataController.Instance.getPlayerData(player);
|
||||
if (data == null || data.abilityData == null) return;
|
||||
|
||||
PacketHandler.Instance.sendToPlayer(new AbilityCooldownSyncPacket(
|
||||
data.abilityData.getCooldownEndTime(),
|
||||
data.abilityData.getGlobalCooldownDurationValue(),
|
||||
data.abilityData.getPerAbilityCooldownEndTimes(),
|
||||
data.abilityData.getPerAbilityCooldownDurations()
|
||||
), player);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -44,10 +45,25 @@ public abstract class AbstractDataAbilities {
|
||||
protected Ability currentAbility;
|
||||
|
||||
/**
|
||||
* World time when cooldown ends
|
||||
* World time when global cooldown ends
|
||||
*/
|
||||
protected long cooldownEndTime = 0;
|
||||
|
||||
/**
|
||||
* Duration of the current global cooldown in ticks (for progress calculation)
|
||||
*/
|
||||
protected int globalCooldownDuration = 0;
|
||||
|
||||
/**
|
||||
* Per-ability cooldown end times. Key = ability key, Value = world time when cooldown ends.
|
||||
*/
|
||||
protected HashMap<String, Long> perAbilityCooldownEndTimes = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Per-ability cooldown durations in ticks (for progress calculation).
|
||||
*/
|
||||
protected HashMap<String, Integer> perAbilityCooldownDurations = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Rotation lock state
|
||||
*/
|
||||
@@ -526,6 +542,106 @@ public abstract class AbstractDataAbilities {
|
||||
cooldownEndTime = endTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global cooldown end time.
|
||||
*/
|
||||
public long getCooldownEndTime() {
|
||||
return cooldownEndTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global cooldown duration value.
|
||||
*/
|
||||
public int getGlobalCooldownDurationValue() {
|
||||
return globalCooldownDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the per-ability cooldown end times map.
|
||||
*/
|
||||
public HashMap<String, Long> getPerAbilityCooldownEndTimes() {
|
||||
return perAbilityCooldownEndTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the per-ability cooldown durations map.
|
||||
*/
|
||||
public HashMap<String, Integer> getPerAbilityCooldownDurations() {
|
||||
return perAbilityCooldownDurations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply cooldown sync data from server. Called on client side.
|
||||
*/
|
||||
public void applyCooldownSync(long globalEndTime, int globalDuration,
|
||||
HashMap<String, Long> perEndTimes,
|
||||
HashMap<String, Integer> perDurations) {
|
||||
this.cooldownEndTime = globalEndTime;
|
||||
this.globalCooldownDuration = globalDuration;
|
||||
this.perAbilityCooldownEndTimes = perEndTimes;
|
||||
this.perAbilityCooldownDurations = perDurations;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PER-ABILITY COOLDOWN MANAGEMENT
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Check if a specific ability is on its per-ability cooldown.
|
||||
*/
|
||||
public boolean isOnPerAbilityCooldown(String key) {
|
||||
Long endTime = perAbilityCooldownEndTimes.get(key);
|
||||
return endTime != null && getWorldTime() < endTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a per-ability cooldown.
|
||||
*/
|
||||
public void setPerAbilityCooldown(String key, long endTime, int duration) {
|
||||
perAbilityCooldownEndTimes.put(key, endTime);
|
||||
perAbilityCooldownDurations.put(key, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a specific per-ability cooldown.
|
||||
*/
|
||||
public void resetPerAbilityCooldown(String key) {
|
||||
perAbilityCooldownEndTimes.remove(key);
|
||||
perAbilityCooldownDurations.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all per-ability cooldowns.
|
||||
*/
|
||||
public void resetAllPerAbilityCooldowns() {
|
||||
perAbilityCooldownEndTimes.clear();
|
||||
perAbilityCooldownDurations.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global cooldown progress for HUD rendering.
|
||||
* @return 1.0 when cooldown just started (fully covered), 0.0 when done
|
||||
*/
|
||||
public float getGlobalCooldownProgress() {
|
||||
if (globalCooldownDuration <= 0) return 0f;
|
||||
long remaining = cooldownEndTime - getWorldTime();
|
||||
if (remaining <= 0) return 0f;
|
||||
return Math.min(1f, (float) remaining / globalCooldownDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get per-ability cooldown progress for HUD rendering.
|
||||
* @return 1.0 when cooldown just started (fully covered), 0.0 when done
|
||||
*/
|
||||
public float getPerAbilityCooldownProgress(String key) {
|
||||
Long endTime = perAbilityCooldownEndTimes.get(key);
|
||||
Integer duration = perAbilityCooldownDurations.get(key);
|
||||
if (endTime == null || duration == null || duration <= 0) return 0f;
|
||||
long remaining = endTime - getWorldTime();
|
||||
if (remaining <= 0) return 0f;
|
||||
return Math.min(1f, (float) remaining / duration);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// POSITION LOCKING
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -222,13 +222,18 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
|
||||
@Override
|
||||
protected void rollCooldown(Ability ability) {
|
||||
// Always roll NPC global cooldown (pacing timer)
|
||||
int baseCooldown = minCooldown;
|
||||
if (maxCooldown > minCooldown) {
|
||||
baseCooldown = minCooldown + random.nextInt(maxCooldown - minCooldown + 1);
|
||||
}
|
||||
cooldownEndTime = npc.worldObj.getTotalWorldTime() + baseCooldown;
|
||||
// Add ability-specific cooldown offset
|
||||
cooldownEndTime += ability.getCooldownTicks();
|
||||
cooldownEndTime = npc.worldObj.getTotalWorldTime() + baseCooldown + ability.getCooldownTicks();
|
||||
|
||||
// Additionally set per-ability cooldown if enabled
|
||||
if (ability.isPerAbilityCooldown() && ability.getCooldownTicks() > 0) {
|
||||
long endTime = npc.worldObj.getTotalWorldTime() + ability.getCooldownTicks();
|
||||
setPerAbilityCooldown(ability.getName(), endTime, ability.getCooldownTicks());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -482,6 +487,14 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Per-ability cooldown filter: ability may have its own independent cooldown
|
||||
if (!action.isChain()) {
|
||||
Ability ab = (Ability) action;
|
||||
if (ab.isPerAbilityCooldown() && isOnPerAbilityCooldown(ab.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -726,6 +739,7 @@ public class DataAbilities extends AbstractDataAbilities {
|
||||
stopCurrentAbility();
|
||||
clearActiveToggles();
|
||||
interruptConcurrentSlots();
|
||||
resetAllPerAbilityCooldowns();
|
||||
|
||||
// Roll cooldown so NPC doesn't immediately attack after reset
|
||||
rollCooldownOnReset();
|
||||
|
||||
@@ -56,7 +56,8 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
|
||||
// Fade: when ShowAlways=false, fade in/out when HUD key is held
|
||||
private float fadeAlpha = 0f;
|
||||
private static final float FADE_SPEED = 8f; // per second — ~125ms full transition
|
||||
private long lastFadeTime = 0;
|
||||
private static final float FADE_SPEED = 3f; // per second — ~333ms full transition
|
||||
|
||||
private Minecraft mc;
|
||||
|
||||
@@ -124,6 +125,7 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
ConfigClient.AbilityHotbarTextPositionProperty.set(ConfigClient.AbilityHotbarTextPosition);
|
||||
ConfigClient.AbilityHotbarVisibleSlotsProperty.set(ConfigClient.AbilityHotbarVisibleSlots);
|
||||
ConfigClient.AbilityHotbarShowAlwaysProperty.set(ConfigClient.AbilityHotbarShowAlways);
|
||||
ConfigClient.AbilityHotbarTextVisibilityProperty.set(ConfigClient.AbilityHotbarTextVisibility);
|
||||
|
||||
if (ConfigClient.config.hasChanged()) {
|
||||
ConfigClient.config.save();
|
||||
@@ -138,9 +140,12 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
|
||||
// Fade logic: when ShowAlways=false, only show while HUD key is held
|
||||
boolean showAlways = ConfigClient.AbilityHotbarShowAlways;
|
||||
long now = Minecraft.getSystemTime();
|
||||
if (!showAlways) {
|
||||
boolean hudKeyHeld = isHudKeyHeld();
|
||||
float dt = partialTicks > 0 ? 1f / 20f : 0.05f; // approximate frame time
|
||||
float dt = lastFadeTime > 0 ? (now - lastFadeTime) / 1000f : 0f;
|
||||
dt = Math.min(dt, 0.1f); // cap to avoid jumps after pauses
|
||||
lastFadeTime = now;
|
||||
if (hudKeyHeld || pendingSelectKey != null || scrollOffset != 0f) {
|
||||
fadeAlpha = Math.min(1f, fadeAlpha + FADE_SPEED * dt);
|
||||
} else {
|
||||
@@ -149,6 +154,7 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
if (fadeAlpha <= 0f) return;
|
||||
} else {
|
||||
fadeAlpha = 1f;
|
||||
lastFadeTime = now;
|
||||
}
|
||||
|
||||
long currentTime = Minecraft.getSystemTime();
|
||||
@@ -184,12 +190,15 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
int centerX = overlayWidth / 2;
|
||||
int centerY = overlayHeight / 2;
|
||||
|
||||
float cooldownProgress = 0;
|
||||
PlayerData playerData = ClientCacheHandler.playerData;
|
||||
if (playerData != null && playerData.abilityData != null) {
|
||||
if (playerData.abilityData.isOnCooldown()) {
|
||||
cooldownProgress = 0.5f;
|
||||
}
|
||||
// Compute text visibility: 0=Shown, 1=Hidden, 2=Held
|
||||
boolean showText;
|
||||
int textVis = ConfigClient.AbilityHotbarTextVisibility;
|
||||
if (textVis == 1) {
|
||||
showText = false;
|
||||
} else if (textVis == 2) {
|
||||
showText = isHudKeyHeld();
|
||||
} else {
|
||||
showText = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < TOTAL_SLOTS; i++) {
|
||||
@@ -228,13 +237,18 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
float edgeAlpha = absDist > half - 0.5f ? 1f - (absDist - (half - 0.5f)) : 1f;
|
||||
float slotAlpha = edgeAlpha * fadeAlpha;
|
||||
boolean isCenter = (offset == 0 && scrollOffset == 0f);
|
||||
float slotCooldown = isCenter ? cooldownProgress : 0;
|
||||
|
||||
// Compute per-slot cooldown progress (all slots, not just center)
|
||||
float slotCooldown = 0f;
|
||||
if (rawSlotIndex >= 0 && slots[rawSlotIndex].abilityKey != null) {
|
||||
slotCooldown = getCooldownProgressForSlot(rawSlotIndex);
|
||||
}
|
||||
|
||||
if (rawSlotIndex == -1) {
|
||||
// Deselect slot — draw empty marker
|
||||
drawDeselectSlot(cx, cy, scaledSize, isCenter, slotAlpha);
|
||||
} else {
|
||||
slots[rawSlotIndex].drawCarousel(mc, sr, slotCooldown, cx, cy, scaledSize, isCenter, slotAlpha);
|
||||
slots[rawSlotIndex].drawCarousel(mc, sr, slotCooldown, cx, cy, scaledSize, isCenter, slotAlpha, showText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,6 +455,7 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
buttonList.add(new GuiButton(3, 0, 0, 120, 20, getTextPositionLabel()));
|
||||
buttonList.add(new GuiButton(4, 0, 0, 120, 20, "Slots: " + ConfigClient.AbilityHotbarVisibleSlots));
|
||||
buttonList.add(new GuiButton(5, 0, 0, 120, 20, ConfigClient.AbilityHotbarShowAlways ? "Show: Always" : "Show: Hold Key"));
|
||||
buttonList.add(new GuiButton(6, 0, 0, 120, 20, getTextVisibilityLabel()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -453,7 +468,8 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
ConfigClient.AbilityHotbarAltTexture = !ConfigClient.AbilityHotbarAltTexture;
|
||||
button.displayString = ConfigClient.AbilityHotbarAltTexture ? "Square" : "Circle";
|
||||
} else if (button.id == 3) {
|
||||
ConfigClient.AbilityHotbarTextPosition = (ConfigClient.AbilityHotbarTextPosition + 1) % 3;
|
||||
// Toggle between 1 (Above/Left) and 2 (Below/Right)
|
||||
ConfigClient.AbilityHotbarTextPosition = ConfigClient.AbilityHotbarTextPosition == 1 ? 2 : 1;
|
||||
button.displayString = getTextPositionLabel();
|
||||
} else if (button.id == 4) {
|
||||
int current = ConfigClient.AbilityHotbarVisibleSlots;
|
||||
@@ -465,6 +481,9 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
} else if (button.id == 5) {
|
||||
ConfigClient.AbilityHotbarShowAlways = !ConfigClient.AbilityHotbarShowAlways;
|
||||
button.displayString = ConfigClient.AbilityHotbarShowAlways ? "Show: Always" : "Show: Hold Key";
|
||||
} else if (button.id == 6) {
|
||||
ConfigClient.AbilityHotbarTextVisibility = (ConfigClient.AbilityHotbarTextVisibility + 1) % 3;
|
||||
button.displayString = getTextVisibilityLabel();
|
||||
} else {
|
||||
super.onEditorButtonPressed(button);
|
||||
}
|
||||
@@ -473,10 +492,18 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
private String getTextPositionLabel() {
|
||||
boolean h = ConfigClient.AbilityHotbarHorizontal;
|
||||
switch (ConfigClient.AbilityHotbarTextPosition) {
|
||||
case 0: return "Text: Hide";
|
||||
case 1: return h ? "Text: Above" : "Text: Left";
|
||||
case 2: return h ? "Text: Below" : "Text: Right";
|
||||
default: return "Text: Hide";
|
||||
default: return h ? "Text: Below" : "Text: Right";
|
||||
}
|
||||
}
|
||||
|
||||
private String getTextVisibilityLabel() {
|
||||
switch (ConfigClient.AbilityHotbarTextVisibility) {
|
||||
case 0: return "Text: Shown";
|
||||
case 1: return "Text: Hidden";
|
||||
case 2: return "Text: Held";
|
||||
default: return "Text: Shown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,6 +605,25 @@ public class AbilityHotbarComponent extends HudComponent {
|
||||
return playerData.hotbarData.hasAnyAbilities();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cooldown progress for a hotbar slot.
|
||||
* Per-ability cooldown abilities use their own cooldown; others use global.
|
||||
* @return 1.0 = fully on cooldown, 0.0 = ready
|
||||
*/
|
||||
private float getCooldownProgressForSlot(int slotIndex) {
|
||||
PlayerData playerData = ClientCacheHandler.playerData;
|
||||
if (playerData == null || playerData.abilityData == null) return 0f;
|
||||
|
||||
String key = slots[slotIndex].abilityKey;
|
||||
Ability ability = slots[slotIndex].ability;
|
||||
|
||||
if (ability != null && ability.isPerAbilityCooldown()) {
|
||||
return playerData.abilityData.getPerAbilityCooldownProgress(key);
|
||||
} else {
|
||||
return playerData.abilityData.getGlobalCooldownProgress();
|
||||
}
|
||||
}
|
||||
|
||||
public void onCycleNext() {
|
||||
cycle(1);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ public class AbilityHotbarSlotRenderer extends Gui {
|
||||
}
|
||||
|
||||
public void drawCarousel(Minecraft mc, ScaledResolution sr, float cooldownProgress,
|
||||
int cx, int cy, int size, boolean isCenter, float slotAlpha) {
|
||||
int cx, int cy, int size, boolean isCenter, float slotAlpha,
|
||||
boolean showText) {
|
||||
float nameAlpha = this.nameAlpha;
|
||||
boolean isHorizontal = ConfigClient.AbilityHotbarHorizontal;
|
||||
if (size <= 0 || slotAlpha <= 0) return;
|
||||
@@ -91,16 +92,16 @@ public class AbilityHotbarSlotRenderer extends Gui {
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
// Cooldown arc
|
||||
// Cooldown overlay (all slots)
|
||||
if (cooldownProgress > 0 && abilityKey != null) {
|
||||
drawCooldownArc(radius, cooldownProgress);
|
||||
drawCooldownOverlay(radius, cooldownProgress, altTexture, slotAlpha);
|
||||
}
|
||||
|
||||
GL11.glPopMatrix();
|
||||
|
||||
// Ability name on center slot (respects text position config)
|
||||
// Ability name on center slot (respects text position and visibility config)
|
||||
int textPos = ConfigClient.AbilityHotbarTextPosition;
|
||||
if (textPos != 0 && isCenter && action != null && nameAlpha > 0) {
|
||||
if (showText && isCenter && action != null && nameAlpha > 0) {
|
||||
FontRenderer fr = mc.fontRenderer;
|
||||
String name = getAbilityName();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
@@ -185,26 +186,65 @@ public class AbilityHotbarSlotRenderer extends Gui {
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
private void drawCooldownArc(float radius, float progress) {
|
||||
progress = Math.min(1, Math.max(0, progress));
|
||||
if (progress <= 0) return;
|
||||
/**
|
||||
* Draw a cooldown overlay on the slot.
|
||||
* progress: 1.0 = fully on cooldown (just started), 0.0 = ready
|
||||
* Renders a filled shape covering the entire slot that transitions from red to green.
|
||||
*/
|
||||
private void drawCooldownOverlay(float radius, float progress, boolean altTexture, float slotAlpha) {
|
||||
progress = Math.min(1f, Math.max(0f, progress));
|
||||
if (progress <= 0f) return;
|
||||
|
||||
// Color: red (progress=1.0) → orange → yellow-green → green (progress→0.0)
|
||||
float cr, cg, cb;
|
||||
if (progress > 0.5f) {
|
||||
float t = (progress - 0.5f) * 2f; // 1.0 at progress=1.0, 0.0 at progress=0.5
|
||||
cr = 0.8f;
|
||||
cg = 0.15f + 0.25f * (1f - t); // 0.15 → 0.4
|
||||
cb = 0.1f;
|
||||
} else if (progress > 0.2f) {
|
||||
float t = (progress - 0.2f) / 0.3f; // 1.0 at 0.5, 0.0 at 0.2
|
||||
cr = 0.8f * t + 0.3f * (1f - t); // 0.8 → 0.3
|
||||
cg = 0.4f * t + 0.7f * (1f - t); // 0.4 → 0.7
|
||||
cb = 0.1f;
|
||||
} else {
|
||||
float t = progress / 0.2f; // 1.0 at 0.2, 0.0 at 0.0
|
||||
cr = 0.3f * t + 0.1f * (1f - t); // 0.3 → 0.1
|
||||
cg = 0.7f * t + 0.7f * (1f - t); // 0.7
|
||||
cb = 0.1f + 0.1f * (1f - t); // 0.1 → 0.2
|
||||
}
|
||||
|
||||
// Semi-transparent so icon is visible underneath; fade out near end
|
||||
float overlayAlpha = 0.5f * slotAlpha * Math.min(1f, progress * 3f);
|
||||
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDisable(GL11.GL_CULL_FACE);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glColor4f(0.1f, 0.1f, 0.1f, 0.7f);
|
||||
GL11.glColor4f(cr, cg, cb, overlayAlpha);
|
||||
|
||||
GL11.glBegin(GL11.GL_TRIANGLE_FAN);
|
||||
GL11.glVertex2f(0, 0);
|
||||
int steps = (int) (CIRCLE_SEGMENTS * progress) + 1;
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
double angle = -Math.PI / 2 + 2 * Math.PI * i * progress / steps;
|
||||
GL11.glVertex2f((float) (Math.cos(angle) * radius), (float) (Math.sin(angle) * radius));
|
||||
if (altTexture) {
|
||||
// Square slot: full filled rectangle
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
GL11.glVertex2f(-radius, -radius);
|
||||
GL11.glVertex2f(radius, -radius);
|
||||
GL11.glVertex2f(radius, radius);
|
||||
GL11.glVertex2f(-radius, radius);
|
||||
GL11.glEnd();
|
||||
} else {
|
||||
// Circle slot: full filled circle
|
||||
GL11.glBegin(GL11.GL_TRIANGLE_FAN);
|
||||
GL11.glVertex2f(0, 0);
|
||||
for (int i = 0; i <= CIRCLE_SEGMENTS; i++) {
|
||||
double angle = 2 * Math.PI * i / CIRCLE_SEGMENTS;
|
||||
GL11.glVertex2f((float) (Math.cos(angle) * radius), (float) (Math.sin(angle) * radius));
|
||||
}
|
||||
GL11.glEnd();
|
||||
}
|
||||
GL11.glEnd();
|
||||
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glEnable(GL11.GL_CULL_FACE);
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -525,6 +525,22 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
}
|
||||
int textW = fr.getStringWidth(stateText);
|
||||
fr.drawStringWithShadow(prefix + stateText, panelInnerLeft + (panelInnerWidth - textW) / 2, y, toggleState > 0 ? 0x55FF55 : 0x888888);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
}
|
||||
|
||||
// Cooldown info
|
||||
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);
|
||||
y += fr.FONT_HEIGHT + 3;
|
||||
|
||||
if (ability.isPerAbilityCooldown()) {
|
||||
String perText = "[" + StatCollector.translateToLocal("ability.perAbilityCooldown") + "]";
|
||||
int ptW = fr.getStringWidth(perText);
|
||||
fr.drawStringWithShadow("\u00A7e" + perText, panelInnerLeft + (panelInnerWidth - ptW) / 2, y, 0xFFFF55);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,6 +679,10 @@ public class GuiAbilities extends GuiCNPCInventory implements ISubGuiListener {
|
||||
if (ability.isToggleable()) {
|
||||
lines.add("\u00A7a[" + StatCollector.translateToLocal("gui.toggle") + "]");
|
||||
}
|
||||
if (ability.getCooldownTicks() > 0) {
|
||||
float seconds = ability.getCooldownTicks() / 20.0f;
|
||||
lines.add("\u00A77" + StatCollector.translateToLocal("ability.cooldown") + ": " + String.format("%.1f", seconds) + "s");
|
||||
}
|
||||
}
|
||||
|
||||
drawHoveringText(lines, mouseX, mouseY, mc.fontRenderer);
|
||||
|
||||
@@ -114,7 +114,7 @@ public class ConfigClient {
|
||||
public static Property AbilityHotbarAltTextureProperty;
|
||||
public static boolean AbilityHotbarAltTexture = false;
|
||||
|
||||
// 0=Hide, 1=Above(H)/Left(V), 2=Below(H)/Right(V)
|
||||
// 1=Above(H)/Left(V), 2=Below(H)/Right(V)
|
||||
public static Property AbilityHotbarTextPositionProperty;
|
||||
public static int AbilityHotbarTextPosition = 2;
|
||||
|
||||
@@ -126,6 +126,10 @@ public class ConfigClient {
|
||||
public static Property AbilityHotbarShowAlwaysProperty;
|
||||
public static boolean AbilityHotbarShowAlways = true;
|
||||
|
||||
// Text Visibility: 0=Shown, 1=Hidden, 2=Held (only while HUD key held)
|
||||
public static Property AbilityHotbarTextVisibilityProperty;
|
||||
public static int AbilityHotbarTextVisibility = 0;
|
||||
|
||||
/**
|
||||
* Texture Properties
|
||||
**/
|
||||
@@ -232,7 +236,7 @@ public class ConfigClient {
|
||||
AbilityHotbarAltTextureProperty = config.get(HUD, "Ability Hotbar Alt Texture", false, "Use rounded square instead of circle slots");
|
||||
AbilityHotbarAltTexture = AbilityHotbarAltTextureProperty.getBoolean();
|
||||
|
||||
AbilityHotbarTextPositionProperty = config.get(HUD, "Ability Hotbar Text Position", 2, "Text label position (0=Hide, 1=Above/Left, 2=Below/Right)");
|
||||
AbilityHotbarTextPositionProperty = config.get(HUD, "Ability Hotbar Text Position", 2, "Text label position (1=Above/Left, 2=Below/Right)");
|
||||
AbilityHotbarTextPosition = AbilityHotbarTextPositionProperty.getInt(2);
|
||||
|
||||
AbilityHotbarVisibleSlotsProperty = config.get(HUD, "Ability Hotbar Visible Slots", 5, "Max visible slots in hotbar (3, 5, or 7)");
|
||||
@@ -241,6 +245,9 @@ public class ConfigClient {
|
||||
AbilityHotbarShowAlwaysProperty = config.get(HUD, "Ability Hotbar Show Always", true, "Always show hotbar (false = only while HUD key is held)");
|
||||
AbilityHotbarShowAlways = AbilityHotbarShowAlwaysProperty.getBoolean(true);
|
||||
|
||||
AbilityHotbarTextVisibilityProperty = config.get(HUD, "Ability Hotbar Text Visibility", 0, "Text visibility (0=Shown, 1=Hidden, 2=Held)");
|
||||
AbilityHotbarTextVisibility = AbilityHotbarTextVisibilityProperty.getInt(0);
|
||||
|
||||
// General
|
||||
AllowClientScriptsProperty = config.get(GENERAL, "Allow Client Scripts", true, "Allow the server to run scripts on the client. If disabled, no server scripts will execute client-side.");
|
||||
AllowClientScripts = AllowClientScriptsProperty.getBoolean(true);
|
||||
|
||||
@@ -8,6 +8,7 @@ import kamkeel.npcs.controllers.data.ability.IAbilityAction;
|
||||
import kamkeel.npcs.controllers.data.ability.ToggleEntry;
|
||||
import kamkeel.npcs.controllers.data.ability.type.AbilityGuard;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.network.packets.data.ability.AbilityCooldownSyncPacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket;
|
||||
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
|
||||
@@ -225,14 +226,30 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
|
||||
@Override
|
||||
protected void rollCooldown(Ability ability) {
|
||||
if (!ability.isIgnoreCooldown()) {
|
||||
cooldownEndTime = getWorldTime() + ability.getCooldownTicks();
|
||||
if (ability.isIgnoreCooldown()) return;
|
||||
|
||||
int duration = ability.getCooldownTicks();
|
||||
long endTime = getWorldTime() + duration;
|
||||
|
||||
if (ability.isPerAbilityCooldown()) {
|
||||
// Per-ability: only this ability goes on cooldown
|
||||
if (currentAbilityKey != null) {
|
||||
setPerAbilityCooldown(currentAbilityKey, endTime, duration);
|
||||
}
|
||||
} else {
|
||||
// Global: all global-cooldown abilities share this cooldown
|
||||
cooldownEndTime = endTime;
|
||||
globalCooldownDuration = duration;
|
||||
}
|
||||
syncCooldownToClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rollChainCooldown(ChainedAbility chain) {
|
||||
cooldownEndTime = getWorldTime() + chain.getCooldownTicks();
|
||||
int duration = chain.getCooldownTicks();
|
||||
cooldownEndTime = getWorldTime() + duration;
|
||||
globalCooldownDuration = duration;
|
||||
syncCooldownToClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -372,8 +389,14 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check universal cooldown (abilities can optionally ignore it)
|
||||
if (!ability.isIgnoreCooldown() && isOnCooldown()) return false;
|
||||
// Check cooldown (per-ability cooldowns are independent from global)
|
||||
if (!ability.isIgnoreCooldown()) {
|
||||
if (ability.isPerAbilityCooldown()) {
|
||||
if (isOnPerAbilityCooldown(key)) return false;
|
||||
} else {
|
||||
if (isOnCooldown()) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire extender start hook (e.g., resource cost checks)
|
||||
if (!AbilityController.Instance.fireOnAbilityStart(ability, player, null)) {
|
||||
@@ -428,6 +451,18 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
EntityPlayer player = playerData.player;
|
||||
if (player instanceof EntityPlayerMP) {
|
||||
PlayerAbilitySyncPacket.sendToPlayer((EntityPlayerMP) player);
|
||||
syncCooldownToClient();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync cooldown state (global + per-ability) to the client.
|
||||
* Sent as a lightweight packet separate from full ability sync.
|
||||
*/
|
||||
public void syncCooldownToClient() {
|
||||
EntityPlayer player = playerData.player;
|
||||
if (player instanceof EntityPlayerMP) {
|
||||
AbilityCooldownSyncPacket.sendToPlayer((EntityPlayerMP) player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,9 +582,15 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
|
||||
/**
|
||||
* Check if a specific ability key is on cooldown.
|
||||
* Currently uses universal cooldown (same for all abilities).
|
||||
* Respects per-ability cooldown if the ability has it enabled.
|
||||
*/
|
||||
public boolean isOnCooldown(String key, EntityPlayer player) {
|
||||
if (AbilityController.Instance != null) {
|
||||
Ability ability = AbilityController.Instance.resolveAbility(key);
|
||||
if (ability != null && ability.isPerAbilityCooldown()) {
|
||||
return isOnPerAbilityCooldown(key);
|
||||
}
|
||||
}
|
||||
return isOnCooldown(player);
|
||||
}
|
||||
|
||||
@@ -560,17 +601,21 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
|
||||
/**
|
||||
* Reset cooldown for a specific ability key.
|
||||
* Currently resets the universal cooldown.
|
||||
* Resets per-ability cooldown if applicable, and global cooldown.
|
||||
*/
|
||||
public void resetCooldown(String key) {
|
||||
resetPerAbilityCooldown(key);
|
||||
cooldownEndTime = 0;
|
||||
syncCooldownToClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all cooldowns.
|
||||
* Reset all cooldowns (global + all per-ability).
|
||||
*/
|
||||
public void resetAllCooldowns() {
|
||||
cooldownEndTime = 0;
|
||||
resetAllPerAbilityCooldowns();
|
||||
syncCooldownToClient();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -604,6 +649,8 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
@Override
|
||||
public void resetCooldown() {
|
||||
cooldownEndTime = 0;
|
||||
resetAllPerAbilityCooldowns();
|
||||
syncCooldownToClient();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -656,6 +703,7 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
|
||||
|
||||
if (clearCooldowns) {
|
||||
cooldownEndTime = 0;
|
||||
resetAllPerAbilityCooldowns();
|
||||
}
|
||||
interruptCooldownRolled = false;
|
||||
|
||||
|
||||
Reference in new issue
Block a user