Skip to content

Commit ee39b40

Browse files
authored
Merge branch 'pr/threshold-decay' into pr/onExpire+newUi
2 parents 589ff64 + a36d2c9 commit ee39b40

11 files changed

Lines changed: 107 additions & 78 deletions

File tree

src/main/java/com/robertx22/mine_and_slash/capability/entity/ResourceTracker.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
public class ResourceTracker {
1111
private static final float EPS = 1e-4f;
12+
private static final float DEFAULT_KEY_PROGRESS = 0f;
1213

1314
private final java.util.EnumMap<ResourceType, Float> lost = new java.util.EnumMap<>(ResourceType.class);
1415

@@ -73,6 +74,10 @@ private float total(java.util.Set<ResourceType> types) {
7374
private final java.util.EnumMap<ResourceType, java.util.Map<String, Float>> keyProgress =
7475
new java.util.EnumMap<>(ResourceType.class);
7576

77+
private java.util.Map<String, Float> getKeyProgressOrCreate(ResourceType rt) {
78+
return keyProgress.computeIfAbsent(rt, __ -> new java.util.HashMap<>());
79+
}
80+
7681
public void clearKey(ResourceType rt, String key) {
7782
if (key == null || key.isEmpty()) return;
7883
var byKey = keyProgress.get(rt);
@@ -86,8 +91,8 @@ public void clearKey(ResourceType rt, String key) {
8691
public int addAndConsumeForKey(String key, ResourceType rt, float add, float threshold) {
8792
if (key == null || key.isEmpty() || add <= 0f || threshold <= 0f) return 0;
8893

89-
var byKey = keyProgress.computeIfAbsent(rt, __ -> new java.util.HashMap<>());
90-
float cur = byKey.getOrDefault(key, 0f) + add;
94+
var byKey = getKeyProgressOrCreate(rt);
95+
float cur = byKey.getOrDefault(key, DEFAULT_KEY_PROGRESS) + add;
9196

9297
int procs = 0;
9398
while (cur + EPS >= threshold) {
@@ -102,7 +107,22 @@ public int addAndConsumeForKey(String key, ResourceType rt, float add, float thr
102107

103108
public float getKeyProgress(String key, ResourceType rt) {
104109
var byKey = keyProgress.get(rt);
105-
return byKey == null ? 0f : byKey.getOrDefault(key, 0f);
110+
return byKey == null ? DEFAULT_KEY_PROGRESS : byKey.getOrDefault(key, DEFAULT_KEY_PROGRESS);
111+
}
112+
113+
114+
/**
115+
* Decrease the cursor by a fixed amount, clamped at zero. Returns the new value.
116+
*/
117+
public float decayKeyProgress(String key, ResourceType rt, float amount) {
118+
if (key == null || key.isEmpty() || amount <= 0f) return getKeyProgress(key, rt);
119+
var byKey = keyProgress.get(rt);
120+
if (byKey == null) return DEFAULT_KEY_PROGRESS;
121+
float cur = byKey.getOrDefault(key, DEFAULT_KEY_PROGRESS);
122+
float next = Math.max(0f, cur - amount);
123+
if (next <= EPS) byKey.remove(key); else byKey.put(key, next);
124+
if (byKey.isEmpty()) keyProgress.remove(rt);
125+
return next;
106126
}
107127

108128
public void setKeyProgress(String key, ResourceType rt, float value) {

src/main/java/com/robertx22/mine_and_slash/event_hooks/ontick/OnServerTick.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public static void onEndTick(ServerPlayer player) {
133133
long now = player.level().getGameTime();
134134
var unit = Load.Unit(player);
135135
if (unit != null) {
136-
for (var rt : com.robertx22.mine_and_slash.saveclasses.unit.ResourceType.values()) {
136+
for (var rt : ResourceType.values()) {
137137
for (var key : unit.getSpendRuntime().getActiveKeys(rt)) {
138138
var spec = unit.getSpendRuntime().getSpec(key);
139139
if (!spec.showUi()) continue;
@@ -152,8 +152,7 @@ public static void onEndTick(ServerPlayer player) {
152152
float newVal = unit.getResourceTracker().decayKeyProgress(key, rt, decayPerSecond);
153153
int cint = (int) newVal;
154154
if (unit.getSpendRuntime().progressIntChanged(key, cint)) {
155-
com.robertx22.library_of_exile.main.Packets.sendToClient(player,
156-
new ThresholdUiPacket(key, rt.id, newVal > 0f, newVal));
155+
Packets.sendToClient(player, new ThresholdUiPacket(key, rt.id, newVal > 0f, newVal));
157156
}
158157
if (newVal <= 0f) {
159158
unit.getSpendRuntime().removeActive(rt, key);

src/main/java/com/robertx22/mine_and_slash/mechanics/thresholds/DataDrivenSpendThresholdSpec.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ public enum ThresholdMode { FLAT, PERCENT_OF_MAX }
1515
private final float value;
1616
private final boolean multiplyByLevel;
1717
@Nullable private final ResourceType percentMaxOf;
18-
private final boolean showUi;
1918

2019
public DataDrivenSpendThresholdSpec(
2120
String key,
@@ -28,19 +27,17 @@ public DataDrivenSpendThresholdSpec(
2827
int cooldownTicks,
2928
boolean lockWhileCooldown,
3029
boolean dropProgressWhileLocked,
31-
boolean resetProgressOnProc,
30+
boolean dropProgressOnProc,
3231
boolean showUi
3332
) {
3433
super(resource, 0f, key,
35-
lockWhileEffectIds, cooldownTicks, lockWhileCooldown, dropProgressWhileLocked, resetProgressOnProc, showUi);
34+
lockWhileEffectIds, cooldownTicks, lockWhileCooldown, dropProgressWhileLocked, dropProgressOnProc);
3635
this.mode = mode;
3736
this.value = value;
3837
this.multiplyByLevel = multiplyByLevel;
3938
this.percentMaxOf = percentMaxOf;
40-
this.showUi = showUi;
4139
}
4240

43-
4441
@Override
4542
public float thresholdFor(EntityData unit) {
4643
float base;

src/main/java/com/robertx22/mine_and_slash/mechanics/thresholds/SpendKeys.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/main/java/com/robertx22/mine_and_slash/mechanics/thresholds/SpendThresholdContributor.java

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/main/java/com/robertx22/mine_and_slash/mechanics/thresholds/SpendThresholdManager.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,11 @@ public static void processSpend(ServerPlayer sp, EntityData unit, ResourceType t
5757
if (debug) {
5858
sp.sendSystemMessage(net.minecraft.network.chat.Component.literal("[SPEND:" + spec.key() + "] locked"));
5959
}
60+
6061
if (spec.showUi()) {
6162
com.robertx22.library_of_exile.main.Packets.sendToClient(sp, new ThresholdUiPacket(key, type.id, false, 0));
6263
}
64+
6365
continue;
6466
}
6567

@@ -75,7 +77,7 @@ public static void processSpend(ServerPlayer sp, EntityData unit, ResourceType t
7577
if (procs > 0) {
7678
spec.onProc(sp, procs);
7779
spec.startCooldown(unit, now);
78-
if (spec.resetOnProc()) {
80+
if (spec.dropProgressOnProc()) {
7981
tracker.clearKey(type, key);
8082
}
8183
if (debug) dbg(sp, "[SPEND:" + spec.key() + "] " + type.id + " ×" + procs + " (thr=" + fmt(threshold) + ")");

src/main/java/com/robertx22/mine_and_slash/mechanics/thresholds/SpendThresholdRuntime.java

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,58 +9,73 @@
99

1010
public class SpendThresholdRuntime {
1111

12-
private final Map<String, Long> cooldownUntil = new HashMap<>();
13-
14-
private final Map<String, Long> lastActivityTick = new HashMap<>();
15-
16-
private final Map<String, Long> lastDecayTick = new HashMap<>();
12+
private static final class KeyState {
13+
long cooldownUntil;
14+
long lastActivityTick;
15+
long lastDecayTick;
16+
int lastProgressIntSent = Integer.MIN_VALUE;
17+
SpendThresholdSpec spec;
18+
}
1719

18-
private final Map<String, Integer> lastProgressIntSent = new HashMap<>();
20+
private final Map<String, KeyState> states = new HashMap<>();
1921

2022
private final java.util.EnumMap<ResourceType, Set<String>> activeByResource = new java.util.EnumMap<>(ResourceType.class);
21-
private final Map<String, SpendThresholdSpec> specByKey = new HashMap<>();
23+
private final java.util.EnumMap<ResourceType, Set<String>> activeByResourceReadOnly = new java.util.EnumMap<>(ResourceType.class);
2224

2325
public void startCooldown(String key, long now, int cooldownTicks) {
2426
if (cooldownTicks <= 0) return;
25-
cooldownUntil.put(key, now + cooldownTicks);
27+
if (key == null || key.isEmpty()) return;
28+
KeyState ks = states.computeIfAbsent(key, __ -> new KeyState());
29+
ks.cooldownUntil = now + cooldownTicks;
2630
}
2731

2832
public boolean isCoolingDown(String key, long now) {
29-
Long until = cooldownUntil.get(key);
30-
return until != null && now < until;
33+
if (key == null || key.isEmpty()) return false;
34+
KeyState ks = states.get(key);
35+
return ks != null && now < ks.cooldownUntil;
3136
}
3237

3338
public int cooldownRemainingTicks(String key, long now) {
34-
Long until = cooldownUntil.get(key);
35-
if (until == null) return 0;
39+
if (key == null || key.isEmpty()) return 0;
40+
KeyState ks = states.get(key);
41+
long until = (ks == null) ? 0L : ks.cooldownUntil;
3642
long rem = until - now;
3743
return (int) Math.max(0, rem);
3844
}
3945

4046
// === Activity/Decay tracking ===
4147
public void markActivity(String key, long now) {
4248
if (key == null || key.isEmpty()) return;
43-
lastActivityTick.put(key, now);
44-
lastDecayTick.remove(key);
49+
KeyState ks = states.computeIfAbsent(key, __ -> new KeyState());
50+
ks.lastActivityTick = now;
51+
ks.lastDecayTick = 0L;
4552
}
4653

4754
public long getLastActivity(String key) {
48-
return lastActivityTick.getOrDefault(key, 0L);
55+
if (key == null || key.isEmpty()) return 0L;
56+
KeyState ks = states.get(key);
57+
return ks == null ? 0L : ks.lastActivityTick;
4958
}
5059

5160
public long getLastDecay(String key) {
52-
return lastDecayTick.getOrDefault(key, 0L);
61+
if (key == null || key.isEmpty()) return 0L;
62+
KeyState ks = states.get(key);
63+
return ks == null ? 0L : ks.lastDecayTick;
5364
}
5465

5566
public void markDecay(String key, long now) {
5667
if (key == null || key.isEmpty()) return;
57-
lastDecayTick.put(key, now);
68+
KeyState ks = states.computeIfAbsent(key, __ -> new KeyState());
69+
ks.lastDecayTick = now;
5870
}
5971

6072
public boolean progressIntChanged(String key, int intProgress) {
61-
Integer prev = lastProgressIntSent.get(key);
62-
if (prev == null || prev.intValue() != intProgress) {
63-
lastProgressIntSent.put(key, intProgress);
73+
if (key == null || key.isEmpty()) return false;
74+
KeyState ks = states.get(key);
75+
int prev = (ks == null) ? Integer.MIN_VALUE : ks.lastProgressIntSent;
76+
if (prev != intProgress) {
77+
if (ks == null) ks = states.computeIfAbsent(key, __ -> new KeyState());
78+
ks.lastProgressIntSent = intProgress;
6479
return true;
6580
}
6681
return false;
@@ -69,29 +84,52 @@ public boolean progressIntChanged(String key, int intProgress) {
6984
// === Active key index ===
7085
public void markActive(ResourceType rt, String key, SpendThresholdSpec spec) {
7186
if (rt == null || key == null || key.isEmpty() || spec == null) return;
72-
activeByResource.computeIfAbsent(rt, __ -> new HashSet<>()).add(key);
73-
specByKey.put(key, spec);
87+
Set<String> set = activeByResource.get(rt);
88+
if (set == null) {
89+
set = new HashSet<>();
90+
activeByResource.put(rt, set);
91+
// create and cache a read-only view for this resource set to avoid future allocations
92+
activeByResourceReadOnly.put(rt, java.util.Collections.unmodifiableSet(set));
93+
}
94+
set.add(key);
95+
KeyState ks = states.computeIfAbsent(key, __ -> new KeyState());
96+
ks.spec = spec;
7497
}
7598

7699
public void removeActive(ResourceType rt, String key) {
77100
if (rt == null || key == null || key.isEmpty()) return;
78101
var set = activeByResource.get(rt);
79102
if (set != null) {
80103
set.remove(key);
81-
if (set.isEmpty()) activeByResource.remove(rt);
104+
if (set.isEmpty()) {
105+
activeByResource.remove(rt);
106+
activeByResourceReadOnly.remove(rt);
107+
}
108+
}
109+
KeyState ks = states.get(key);
110+
if (ks != null) {
111+
// clear volatile state but keep cooldown to preserve gating behavior
112+
ks.spec = null;
113+
ks.lastActivityTick = 0L;
114+
ks.lastDecayTick = 0L;
115+
ks.lastProgressIntSent = Integer.MIN_VALUE;
82116
}
83-
specByKey.remove(key);
84-
lastActivityTick.remove(key);
85-
lastDecayTick.remove(key);
86-
lastProgressIntSent.remove(key);
87117
}
88118

89119
public Set<String> getActiveKeys(ResourceType rt) {
90120
var s = activeByResource.get(rt);
91-
return s == null ? java.util.Set.of() : java.util.Set.copyOf(s);
121+
if (s == null || s.isEmpty()) return java.util.Set.of();
122+
var view = activeByResourceReadOnly.get(rt);
123+
if (view == null) {
124+
view = java.util.Collections.unmodifiableSet(s);
125+
activeByResourceReadOnly.put(rt, view);
126+
}
127+
return view;
92128
}
93129

94130
public SpendThresholdSpec getSpec(String key) {
95-
return specByKey.get(key);
131+
if (key == null || key.isEmpty()) return null;
132+
KeyState ks = states.get(key);
133+
return ks == null ? null : ks.spec;
96134
}
97135
}

src/main/java/com/robertx22/mine_and_slash/mechanics/thresholds/SpendThresholdSpec.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ public abstract class SpendThresholdSpec {
1515
private final float perLevelFactor;
1616
private final String key;
1717

18-
// gating/cooldown controls
1918
private final Set<String> lockWhileEffectIds;
2019
private final int cooldownTicks;
2120
private final boolean lockWhileCooldown; // Used to lock the threshold while the cooldown is active. **RECOMMENDED FOR DEBUGGING ONLY**
2221
private final boolean dropProgressWhileLocked;
23-
private final boolean resetProgressOnProc;
22+
private final boolean dropProgressOnProc;
2423
private final boolean showUi;
2524

2625
private int priority = 0;
@@ -32,17 +31,16 @@ public SpendThresholdSpec(ResourceType resource,
3231
int cooldownTicks,
3332
boolean lockWhileCooldown,
3433
boolean dropProgressWhileLocked,
35-
boolean resetProgressOnProc,
36-
boolean showUi) {
34+
boolean dropProgressOnProc
35+
) {
3736
this.resource = resource;
3837
this.perLevelFactor = perLevelFactor;
3938
this.key = key;
4039
this.lockWhileEffectIds = (lockWhileEffectIds == null) ? Collections.emptySet() : Set.copyOf(lockWhileEffectIds);
4140
this.cooldownTicks = Math.max(0, cooldownTicks);
4241
this.lockWhileCooldown = lockWhileCooldown;
4342
this.dropProgressWhileLocked = dropProgressWhileLocked;
44-
this.resetProgressOnProc = resetProgressOnProc;
45-
this.showUi = showUi;
43+
this.dropProgressOnProc = dropProgressOnProc;
4644
}
4745

4846
// ===== accessors =====
@@ -51,10 +49,9 @@ public SpendThresholdSpec(ResourceType resource,
5149
public String keyFor(EntityData unit) { return key; }
5250
public boolean lockWhileCooldown() { return lockWhileCooldown; }
5351
public boolean dropProgressWhileLocked() { return dropProgressWhileLocked; }
54-
public boolean resetOnProc() { return resetProgressOnProc; }
52+
public boolean dropProgressOnProc() { return dropProgressOnProc; }
5553
public int cooldownTicks() { return cooldownTicks; }
5654
public int priority() { return priority; }
57-
public boolean showUi() { return showUi; }
5855

5956

6057
public SpendThresholdSpec withPriority(int p) {

src/main/java/com/robertx22/mine_and_slash/mechanics/thresholds/datapack/SpendThresholdDef.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static class Locks {
3131
public List<String> effects = new ArrayList<>();
3232
@SerializedName("lock_while_cooldown") public boolean lockWhileCooldown = false;
3333
@SerializedName("drop_progress_while_locked") public boolean dropProgressWhileLocked = true;
34-
@SerializedName("reset_progress_on_proc") public boolean resetProgressOnProc = true;
34+
@SerializedName("drop_progress_on_proc") public boolean dropProgressOnProc = true;
3535
}
3636
public Locks locks = new Locks();
3737

@@ -60,7 +60,7 @@ public SpendThresholdSpec toSpec() {
6060
if ("PERCENT_OF_MAX".equals(rawMode)) {
6161
mode = DataDrivenSpendThresholdSpec.ThresholdMode.PERCENT_OF_MAX;
6262
} else {
63-
mode = DataDrivenSpendThresholdSpec.ThresholdMode.FLAT; // default + treats legacy values as FLAT
63+
mode = DataDrivenSpendThresholdSpec.ThresholdMode.FLAT; // default
6464
}
6565

6666
ResourceType percentOf = null;
@@ -83,7 +83,7 @@ public SpendThresholdSpec toSpec() {
8383
cooldownTicks,
8484
locks != null && locks.lockWhileCooldown,
8585
locks != null && locks.dropProgressWhileLocked,
86-
locks != null && locks.resetProgressOnProc,
86+
locks != null && locks.dropProgressOnProc,
8787
showUi
8888
) {
8989
@Override
@@ -113,7 +113,6 @@ public void onProc(ServerPlayer sp, int procs) {
113113
}
114114
}
115115
}
116-
117116
}
118117
}
119118

src/main/resources/data/mmorpg/spend_thresholds/25percent_health.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"locks": {
1414
"effects": [],
1515
"drop_progress_while_locked": true,
16-
"reset_progress_on_proc": true
16+
"drop_progress_on_proc": true
1717
},
1818
"on_proc": [
1919
{

0 commit comments

Comments
 (0)