fix(phase-3): resolve PHYSICAL damage cause index dynamically + verbose logs
Bug: ChainDamageApplier hardcoded causeIndex=0 assuming Physical was
loaded first. IndexedLookupTableAssetMap assigns indexes by filesystem
load order — non-deterministic. Index 0 was not Physical at runtime,
damage.getCause() returned null, NPE silently swallowed in
ArmorDamageReduction before any HP was deducted.
Fix: physicalDamageCauseIndex() resolves the real index at call time
via DamageCause.PHYSICAL.getId() + getAssetMap().getIndex(...). Test
contexts where PHYSICAL is null fall back to 0 (neutral, not asserted).
Also: replace getLogger().log(Level, fmt, args) with String.format
upstream — the Hytale log handler does not interpolate {0}/{1}/{2}
placeholders. Add per-step verbose logs throughout the pipeline
(firstRun 1/9..9/9, RayCast hit/miss + snapshot, EntitySource per-ref,
ChainDamageApplier per-hit before/after) to make future runtime
diagnostics trivial.
UAT confirmed in-game: 5 sheep chained at 8/6/4/3/2 HP, cooldown gates
the second click within 4s, no exceptions, causeIndex resolves to 6
(stable across restarts).
This commit is contained in:
+36
-15
@@ -71,25 +71,34 @@ public final class ChainLightningSceptreInteraction extends SimpleInstantInterac
|
||||
protected void firstRun(@Nonnull InteractionType type,
|
||||
@Nonnull InteractionContext context,
|
||||
@Nonnull CooldownHandler cooldownHandler) {
|
||||
LOGGER.info(String.format("[ChainLightning][1/9] firstRun ENTRY type=%s", type));
|
||||
|
||||
// --- Étape 1 : récupérer le cooldown ---
|
||||
CooldownHandler.Cooldown cooldown = cooldownHandler.getCooldown(
|
||||
COOLDOWN_ID, COOLDOWN_TIME, CHARGE_TIMES, FORCE_CREATE, INTERRUPT_RECHARGE);
|
||||
if (cooldown == null) {
|
||||
LOGGER.log(Level.WARNING, "[ChainLightning] cooldown handler returned null — aborting");
|
||||
LOGGER.warning("[ChainLightning][1/9] cooldown handler returned null — aborting");
|
||||
return;
|
||||
}
|
||||
LOGGER.info(String.format("[ChainLightning][1/9] cooldown obtenu id=%s maxTime=%.1fs", COOLDOWN_ID, COOLDOWN_TIME));
|
||||
|
||||
// --- Étape 2 : check cooldown sans décompter ---
|
||||
if (cooldown.hasCooldown(false)) {
|
||||
return; // refus silencieux
|
||||
boolean onCooldown = cooldown.hasCooldown(false);
|
||||
LOGGER.info(String.format("[ChainLightning][2/9] hasCooldown(false)=%s", onCooldown));
|
||||
if (onCooldown) {
|
||||
LOGGER.info("[ChainLightning][2/9] still on cooldown — silent refuse");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// --- Étape 3 : extraire player + commandBuffer ---
|
||||
Ref<EntityStore> playerRef = context.getEntity();
|
||||
CommandBuffer<EntityStore> commandBuffer = context.getCommandBuffer();
|
||||
LOGGER.info(String.format("[ChainLightning][3/9] playerRef=%s commandBuffer=%s",
|
||||
playerRef == null ? "null" : ("ref:" + playerRef.getIndex() + " valid=" + playerRef.isValid()),
|
||||
commandBuffer == null ? "null" : commandBuffer.getClass().getSimpleName()));
|
||||
if (playerRef == null || commandBuffer == null) {
|
||||
LOGGER.log(Level.FINE, "[ChainLightning] missing playerRef or commandBuffer — abort");
|
||||
LOGGER.warning("[ChainLightning][3/9] missing playerRef or commandBuffer — abort");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,36 +106,48 @@ public final class ChainLightningSceptreInteraction extends SimpleInstantInterac
|
||||
// CommandBuffer<EntityStore> implémente ComponentAccessor<EntityStore> — passé directement
|
||||
HytalePlayerRayCaster ray = new HytalePlayerRayCaster(playerRef, commandBuffer);
|
||||
HytaleEntitySource neighbors = new HytaleEntitySource(commandBuffer);
|
||||
LOGGER.info("[ChainLightning][4/9] adapters built (RayCaster + EntitySource)");
|
||||
|
||||
// --- Étape 5 : résolution BFS (origin/direction = placeholders ignorés par le wrapper) ---
|
||||
LOGGER.info(String.format("[ChainLightning][5/9] resolving chain rayMax=%.1f maxTargets=%d radius=%.1f",
|
||||
RAY_MAX_BLOCKS, ChainParameters.DEFAULT.maxTargets(), ChainParameters.DEFAULT.chainRadius()));
|
||||
List<ChainHit> hits = ChainResolver.resolve(
|
||||
Vec3.ZERO, Vec3.ZERO, RAY_MAX_BLOCKS,
|
||||
ray, neighbors, ChainParameters.DEFAULT
|
||||
);
|
||||
LOGGER.info(String.format("[ChainLightning][5/9] resolution returned %d hits", hits.size()));
|
||||
|
||||
// --- Étape 6 : pas de cible → return SANS cooldown ---
|
||||
if (hits.isEmpty()) {
|
||||
LOGGER.log(Level.FINE, "[ChainLightning] no target");
|
||||
LOGGER.info("[ChainLightning][6/9] no target — re-click immediately allowed (no cooldown deducted)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Détail des hits avant damage
|
||||
for (int i = 0; i < hits.size(); i++) {
|
||||
ChainHit h = hits.get(i);
|
||||
LOGGER.info(String.format("[ChainLightning][6/9] hit[%d] target=%s damageHp=%d hopIndex=%d",
|
||||
i, h.target().id(), h.damageHp(), h.hopIndex()));
|
||||
}
|
||||
|
||||
// --- Étape 7 : appliquer les dégâts ---
|
||||
LOGGER.info(String.format("[ChainLightning][7/9] applying damage to %d targets (attacker=ref:%d)",
|
||||
hits.size(), playerRef.getIndex()));
|
||||
ChainDamageApplier.apply(hits, playerRef, commandBuffer, commandBuffer);
|
||||
LOGGER.info("[ChainLightning][7/9] damage application returned");
|
||||
|
||||
// --- Étape 8 : démarrer le cooldown APRÈS succès ---
|
||||
cooldown.deductCharge();
|
||||
LOGGER.info(String.format("[ChainLightning][8/9] cooldown deducted (next available in %.1fs)", COOLDOWN_TIME));
|
||||
|
||||
// --- Étape 9 : log structuré ---
|
||||
if (LOGGER.isLoggable(Level.INFO)) {
|
||||
StringBuilder ids = new StringBuilder();
|
||||
for (int i = 0; i < hits.size(); i++) {
|
||||
if (i > 0) ids.append(',');
|
||||
ids.append(hits.get(i).target().id());
|
||||
}
|
||||
LOGGER.log(Level.INFO,
|
||||
"[ChainLightning] ref:{0} chained {1} targets [{2}]",
|
||||
new Object[]{"ref:" + playerRef.getIndex(), hits.size(), ids.toString()});
|
||||
// --- Étape 9 : log structuré final ---
|
||||
StringBuilder ids = new StringBuilder();
|
||||
for (int i = 0; i < hits.size(); i++) {
|
||||
if (i > 0) ids.append(',');
|
||||
ids.append(hits.get(i).target().id());
|
||||
}
|
||||
LOGGER.info(String.format("[ChainLightning][9/9] DONE ref:%d chained %d targets [%s]",
|
||||
playerRef.getIndex(), hits.size(), ids.toString()));
|
||||
} catch (Throwable t) {
|
||||
// CONTEXT decision : try/catch global pour éviter crash tick serveur
|
||||
LOGGER.log(Level.WARNING, "[ChainLightning] chain resolution failed", t);
|
||||
|
||||
Reference in New Issue
Block a user