feat(phase-3): runtime integration — chain damage application + cooldown

Wires the pure ChainResolver (Phase 2) to the live Hytale runtime via
four sceptre/ adapters:
- HytaleEntityAdapter — eager snapshot Ref<EntityStore> -> ChainEntity
  via TransformComponent.getPosition() and EntityStatMap.get(health) > 0
- HytalePlayerRayCaster — captures playerRef, delegates to
  TargetUtil.getTargetEntity (auto eye-origin + head-rotation)
- HytaleEntitySource — wraps TargetUtil.getAllEntitiesInSphere
- ChainDamageApplier — fires DamageSystems.executeDamage per hit;
  injectable DamageExecutor SAM keeps the helper unit-testable.

ChainLightningSceptreInteraction.firstRun is rewritten end-to-end:
cooldown gate (hasCooldown(false) -> deductCharge() only on success),
ray-cast -> BFS -> damage application, structured logging, try/catch
wrapper to keep a runtime fault from killing the server tick.

API corrections discovered against the decompiled jar:
- Ref has no uuid() — use "ref:" + getIndex() for the chain id
- DamageCause.PHYSICAL is @Nullable until runtime — use the int-index
  overload of Damage with index 0
- Static mock of DamageSystems crashes class init — abstracted behind
  a DamageExecutor SAM with a default lazy holder

Tests: 33/33 green (25 from Phase 2 + 4 ChainDamageApplier tests +
4 fixture sanity). ./gradlew build SUCCESSFUL, JAR auto-deployed.
MANUAL UAT (10 items) pending in-game.
This commit is contained in:
2026-04-27 12:14:58 +02:00
parent cd5d0bedd3
commit 8725b8a1c7
8 changed files with 524 additions and 27 deletions
@@ -0,0 +1,43 @@
package com.mythlane.chainlightning.sceptre;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.TargetUtil;
import com.mythlane.chainlightning.chain.ChainEntity;
import com.mythlane.chainlightning.chain.EntitySource;
import com.mythlane.chainlightning.chain.Vec3;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
/**
* Implementation Phase 3 de {@link EntitySource} qui delegue a
* {@link TargetUtil#getAllEntitiesInSphere}.
*
* <p><b>Note importante :</b> {@code getAllEntitiesInSphere} retourne une liste THREAD-LOCALE
* (SpatialResource.getThreadLocalReferenceList). On la consomme immediatement en mappant chaque
* ref vers un {@link HytaleEntityAdapter} dans une nouvelle ArrayList -- la liste retournee est
* sure a conserver entre frames.
*/
public final class HytaleEntitySource implements EntitySource {
private final ComponentAccessor<EntityStore> accessor;
public HytaleEntitySource(@Nonnull ComponentAccessor<EntityStore> accessor) {
this.accessor = accessor;
}
@Override
public List<ChainEntity> nearby(Vec3 origin, double radius) {
Vector3d hytaleOrigin = new Vector3d(origin.x(), origin.y(), origin.z());
List<Ref<EntityStore>> refs = TargetUtil.getAllEntitiesInSphere(hytaleOrigin, radius, accessor);
List<ChainEntity> snapshots = new ArrayList<>(refs.size());
for (Ref<EntityStore> ref : refs) {
snapshots.add(HytaleEntityAdapter.snapshot(ref, accessor));
}
return snapshots;
}
}