Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.robertx22.mine_and_slash.event_hooks.ontick.UnequipGear;
import com.robertx22.mine_and_slash.event_hooks.player.OnLogin;
import com.robertx22.mine_and_slash.loot.LootModifiersList;
import com.robertx22.mine_and_slash.mechanics.thresholds.SpendThresholdRuntime;
import com.robertx22.mine_and_slash.mmorpg.MMORPG;
import com.robertx22.mine_and_slash.mmorpg.SlashRef;
import com.robertx22.mine_and_slash.saveclasses.CustomExactStatsData;
Expand Down Expand Up @@ -959,6 +960,14 @@ public void onSpellHitTarget(Entity spellEntity, LivingEntity target) {

}

// Tracks LOSS of resources (spend, drains, damage, etc.)
private final ResourceTracker resourceTracker = new ResourceTracker();
public ResourceTracker getResourceTracker() { return resourceTracker; }

public final SpendThresholdRuntime spendRuntime = new SpendThresholdRuntime();
public SpendThresholdRuntime getSpendRuntime() { return spendRuntime; }



public boolean alreadyHit(Entity spellEntity, LivingEntity target) {
// this makes sure piercing projectiles hit target only once and then pass through
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.robertx22.mine_and_slash.capability.entity;

import com.robertx22.mine_and_slash.saveclasses.unit.ResourceType;

/**
* Accumulates resource LOSS per type (spend, drains, damage, etc).
* Call addLoss(...) whenever a resource actually decreases.
* Use consumeThresholds(...) / addAndConsumeForKey(...) to fire effects and keep remainder.
*/
public class ResourceTracker {
private static final float EPS = 1e-4f;

// Global per-resource accumulators (used for simple thresholds or debug)
private final java.util.EnumMap<ResourceType, Float> lost = new java.util.EnumMap<>(ResourceType.class);

/** Record an actual decrease in a resource. */
public void addLoss(ResourceType rt, float amount) {
if (amount <= 0f) return;
lost.merge(rt, amount, Float::sum);
}

/** Current accumulated loss for a resource. */
public float getLoss(ResourceType rt) {
return lost.getOrDefault(rt, 0f);
}

/** Consume thresholds for a single resource; keep remainder. */
public int consumeThresholds(ResourceType type, float threshold) {
if (threshold <= 0f) return 0;
float have = lost.getOrDefault(type, 0f);
if (have + EPS < threshold) return 0;

int procs = (int) Math.floor((have + EPS) / threshold);
float remainder = have - procs * threshold;

if (remainder <= EPS) lost.remove(type);
else lost.put(type, remainder);
return procs;
}

/**
* Consume as many full thresholds as available across a set of resources (combined bucket).
* Drain is deterministic: the iteration order of the set decides which resource is consumed first.
* <p><b>Note:</b> Pass an {@link java.util.EnumSet} to guarantee stable drain order.</p>
*/
public int consumeThresholdsAcross(java.util.Set<ResourceType> types, float threshold) {
if (threshold <= 0f || types == null || types.isEmpty()) return 0;

int procs = 0;
// Loop while the combined total can pay for at least one threshold
while (total(types) + EPS >= threshold) {
float need = threshold;

for (ResourceType rt : types) {
float have = lost.getOrDefault(rt, 0f);
if (have <= 0f) continue;

float take = Math.min(have, need);
if (take > 0f) {
float remaining = have - take;
if (remaining <= EPS) lost.remove(rt);
else lost.put(rt, remaining);
need -= take;
}
if (need <= EPS) break; // satisfied this proc
}
procs++;
}

// Prune tiny leftovers just in case
for (ResourceType rt : types) {
if (lost.getOrDefault(rt, 0f) <= EPS) lost.remove(rt);
}
return procs;
}

/** Sum of accumulated losses for the given set. */
private float total(java.util.Set<ResourceType> types) {
float sum = 0f;
for (ResourceType rt : types) sum += lost.getOrDefault(rt, 0f);
return sum;
}

// Per-key cursors so multiple specs on the same resource don't interfere
private final java.util.EnumMap<ResourceType, java.util.Map<String, Float>> keyProgress =
new java.util.EnumMap<>(ResourceType.class);

public void clearKey(ResourceType rt, String key) {
if (key == null || key.isEmpty()) return;
var byKey = keyProgress.get(rt);
if (byKey == null) return;
byKey.remove(key);
if (byKey.isEmpty()) {
keyProgress.remove(rt);
}
}

/** Add loss to a specific key’s cursor for this resource and consume thresholds. */
public int addAndConsumeForKey(String key, ResourceType rt, float add, float threshold) {
if (key == null || key.isEmpty() || add <= 0f || threshold <= 0f) return 0;

var byKey = keyProgress.computeIfAbsent(rt, __ -> new java.util.HashMap<>());
float cur = byKey.getOrDefault(key, 0f) + add;

int procs = 0;
while (cur + EPS >= threshold) {
cur -= threshold;
procs++;
}
if (cur <= EPS) byKey.remove(key);
else byKey.put(key, cur);

return procs;
}

/** Read current cursor for debug/UI. */
public float getKeyProgress(String key, ResourceType rt) {
var byKey = keyProgress.get(rt);
return byKey == null ? 0f : byKey.getOrDefault(key, 0f);
}

/** Optional utility if you want to wipe a resource’s accumulator. */
public void clear(ResourceType rt) {
lost.remove(rt);
}

/** Optional: wipe all. */
public void clearAll() {
lost.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.robertx22.mine_and_slash.event_hooks.my_events;

import com.robertx22.mine_and_slash.mechanics.thresholds.SpendThresholdManager;
import com.robertx22.mine_and_slash.saveclasses.unit.ResourceType;
import com.robertx22.mine_and_slash.uncommon.datasaving.Load;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.LivingEntity;

/**
* Unified entrypoint for resource LOSS (spend, drains, damage).
* Health damage integration calls this via the LivingDamageEvent handler below.
*
* Debug printing is handled inside SpendThresholdManager and is toggled by
* OnResourceLost.DEBUG_ENABLED.
*/
public final class OnResourceLost {
private OnResourceLost() {}

public enum LossSource { SpendOrDrain, Damage, Other }

/** Toggle SpendThresholdManager debug logs per player. */
public static boolean DEBUG_ENABLED = false;

/** Call this whenever a resource actually goes down. */
public static void trigger(LivingEntity entity, ResourceType type, float loss, LossSource source) {
if (loss <= 0f) return;
if (!(entity instanceof ServerPlayer sp)) return;

var unit = Load.Unit(sp);
long now = sp.level().getGameTime(); // ticks
SpendThresholdManager.processSpend(sp, unit, type, loss, now);
}

/** Wire health damage into the unified loss path. */
@net.minecraftforge.eventbus.api.SubscribeEvent
public static void onLivingDamage(net.minecraftforge.event.entity.living.LivingDamageEvent evt) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be cool if you moved this method to ApiForgeEvents, let's keep code separated from forge api

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure how exactly I would go about this or where to even look, I'm only familiar with the MNS api for the most part at this point

Copy link

@SaloEater SaloEater Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, specified wrong class. Add something like this to the CommonEvents.java to register method

ForgeEvents.registerForgeEvent(LivingDamageEvent.class, evt -> {
            if (!(evt.getEntity() instanceof ServerPlayer sp)) return;
            float applied = evt.getAmount();
            if (applied > 0f) {
                OnResourceLost.trigger(sp, ResourceType.health, applied, OnResourceLost.LossSource.Damage);
            }
        });

if (!(evt.getEntity() instanceof ServerPlayer sp)) return;
float applied = evt.getAmount();
if (applied > 0f) {
trigger(sp, ResourceType.health, applied, LossSource.Damage);
}
}
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.robertx22.mine_and_slash.mechanics.thresholds;

import com.robertx22.mine_and_slash.capability.entity.EntityData;
import com.robertx22.mine_and_slash.saveclasses.unit.ResourceType;
import net.minecraft.server.level.ServerPlayer;

import javax.annotation.Nullable;
import java.util.Set;

public class DataDrivenSpendThresholdSpec extends SpendThresholdSpec {

public enum ThresholdMode { X_PER_LEVEL, FLAT, PCT_OF_MAX }

private final ThresholdMode mode;
private final float value;
private final boolean multiplyByLevel;
@Nullable private final ResourceType pctMaxOf;

public DataDrivenSpendThresholdSpec(
String key,
ResourceType resource,
ThresholdMode mode,
float value,
boolean multiplyByLevel,
@Nullable ResourceType pctMaxOf,
Set<String> lockWhileEffectIds,
int cooldownTicks,
boolean lockWhileCooldown,
boolean dropProgressWhileLocked,
boolean resetProgressOnProc
) {
super(resource, /*perLevelFactor (unused)*/ 0f, key,
lockWhileEffectIds, cooldownTicks, lockWhileCooldown, dropProgressWhileLocked, resetProgressOnProc);
this.mode = mode;
this.value = value;
this.multiplyByLevel = multiplyByLevel;
this.pctMaxOf = pctMaxOf;
}

@Override
public float thresholdFor(EntityData unit) {
float base;
switch (mode) {
case X_PER_LEVEL:
base = value * Math.max(1, unit.getLevel());
break;
case FLAT:
base = value * (multiplyByLevel ? Math.max(1, unit.getLevel()) : 1f);
break;
case PCT_OF_MAX:
ResourceType rt = (pctMaxOf != null) ? pctMaxOf : resource();
float max = unit.getResources().getMax(unit.getEntity(), rt);
base = (value / 100f) * max;
if (multiplyByLevel) base *= Math.max(1, unit.getLevel());
break;
default:
base = 0f;
}
return Math.max(0f, base);
}

@Override
public void onProc(ServerPlayer sp, int procs) {
// No default action here; datapack loader wires actions.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.robertx22.mine_and_slash.mechanics.thresholds;

import com.robertx22.mine_and_slash.saveclasses.unit.ResourceType;
import com.robertx22.mine_and_slash.uncommon.datasaving.Load;
import net.minecraft.server.level.ServerPlayer;

public final class SpendKeys {
private SpendKeys() {}
public static String key(String nodeId, ResourceType rt) { return "spend." + rt.id + "." + nodeId; }
public static float threshold(ServerPlayer sp, float perLevelFactor) {
return perLevelFactor * Load.Unit(sp).getLevel();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.robertx22.mine_and_slash.mechanics.thresholds;

import com.robertx22.mine_and_slash.capability.entity.EntityData;
import java.util.List;

public interface SpendThresholdContributor {
/** Return zero or more specs active for this unit (e.g., from allocated talents). */
List<SpendThresholdSpec> getSpendThresholds(EntityData unit);
}
Loading