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:
@@ -4,31 +4,38 @@ import com.hypixel.hytale.component.CommandBuffer;
|
|||||||
import com.hypixel.hytale.component.ComponentAccessor;
|
import com.hypixel.hytale.component.ComponentAccessor;
|
||||||
import com.hypixel.hytale.component.Ref;
|
import com.hypixel.hytale.component.Ref;
|
||||||
import com.hypixel.hytale.server.core.modules.entity.damage.Damage;
|
import com.hypixel.hytale.server.core.modules.entity.damage.Damage;
|
||||||
// DamageCause import supprime : on utilise l'index int pour eviter la dependance au runtime Hytale
|
import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause;
|
||||||
// import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause;
|
|
||||||
import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems;
|
import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems;
|
||||||
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
import com.mythlane.chainlightning.chain.ChainHit;
|
import com.mythlane.chainlightning.chain.ChainHit;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper static qui applique {@link DamageSystems#executeDamage} pour chaque hit d'une chaine resolue.
|
* Helper static qui applique {@link DamageSystems#executeDamage} pour chaque hit d'une chaine resolue.
|
||||||
*
|
*
|
||||||
* <p>Utilise {@link DamageCause#PHYSICAL} (deprecie mais fonctionnel -- RESEARCH Q5). Une cause
|
* <p>Utilise {@link DamageCause#PHYSICAL} pour resoudre l'index au moment de l'appel (runtime).
|
||||||
* custom "chain_lightning" via DamageCause asset map est deferee v2.
|
* L'index est lu depuis l'asset map via {@code DamageCause.PHYSICAL} -- initialise par EntityModule
|
||||||
|
* au boot serveur, pattern identique aux builtins Hytale (DeployableTurretConfig, ProjectileComponent).
|
||||||
|
*
|
||||||
|
* <p><b>Pourquoi ne pas hardcoder index=0 :</b> {@code IndexedLookupTableAssetMap} assigne les index
|
||||||
|
* dans l'ordre de chargement filesystem des JSON -- non-deterministe. Index 0 != PHYSICAL en runtime
|
||||||
|
* reel (bug chain-no-damage : damage.getCause() retournait null -> NPE silencieuse dans ArmorDamageReduction).
|
||||||
*
|
*
|
||||||
* <p>Cast explicite {@code ChainHit.target() -> HytaleEntityAdapter} : Phase 3 garantit que c'est
|
* <p>Cast explicite {@code ChainHit.target() -> HytaleEntityAdapter} : Phase 3 garantit que c'est
|
||||||
* la SEULE implementation de ChainEntity produite par les adapters Phase 3 (les snapshots
|
* la SEULE implementation de ChainEntity produite par les adapters Phase 3.
|
||||||
* proviennent uniquement de HytalePlayerRayCaster.firstHit + HytaleEntitySource.nearby).
|
|
||||||
*
|
*
|
||||||
* <p><b>Testabilite :</b> l'overload a {@link DamageExecutor} permet d'injecter un stub en test
|
* <p><b>Testabilite :</b> l'overload a {@link DamageExecutor} permet d'injecter un stub en test
|
||||||
* sans avoir a initialiser le runtime Hytale (DamageSystems possede un initialiseur statique
|
* sans avoir a initialiser le runtime Hytale (DamageSystems possede un initialiseur statique
|
||||||
* dependant de PluginBase/HytaleLogger).
|
* dependant de PluginBase/HytaleLogger). En contexte de test, {@code DamageCause.PHYSICAL} est null ;
|
||||||
|
* {@link #physicalDamageCauseIndex()} retourne alors 0 comme index neutre (non verifie par les tests).
|
||||||
*/
|
*/
|
||||||
public final class ChainDamageApplier {
|
public final class ChainDamageApplier {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ChainDamageApplier.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SAM injectable pour l'application des degats -- permet de stubber DamageSystems en test.
|
* SAM injectable pour l'application des degats -- permet de stubber DamageSystems en test.
|
||||||
*/
|
*/
|
||||||
@@ -54,6 +61,23 @@ public final class ChainDamageApplier {
|
|||||||
return DefaultHolder.INSTANCE;
|
return DefaultHolder.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resout l'index de la cause PHYSICAL au moment de l'appel.
|
||||||
|
*
|
||||||
|
* <p>En runtime Hytale, {@code DamageCause.PHYSICAL} est initialise par EntityModule au boot.
|
||||||
|
* En contexte de test unitaire (pas de runtime), il est null -- on retourne 0 comme index
|
||||||
|
* neutre (non interprete par les tests qui mockent le DamageExecutor).
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
static int physicalDamageCauseIndex() {
|
||||||
|
DamageCause physical = DamageCause.PHYSICAL;
|
||||||
|
if (physical == null) {
|
||||||
|
// Contexte de test : DamageCause.PHYSICAL non initialise, index neutre.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DamageCause.getAssetMap().getIndex(physical.getId());
|
||||||
|
}
|
||||||
|
|
||||||
private ChainDamageApplier() {}
|
private ChainDamageApplier() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,22 +104,35 @@ public final class ChainDamageApplier {
|
|||||||
* @param commandBuffer buffer de commandes du tick courant
|
* @param commandBuffer buffer de commandes du tick courant
|
||||||
* @param executor executeur de degats (DEFAULT en prod, stub en test)
|
* @param executor executeur de degats (DEFAULT en prod, stub en test)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static void apply(@Nonnull List<ChainHit> hits,
|
public static void apply(@Nonnull List<ChainHit> hits,
|
||||||
@Nonnull Ref<EntityStore> attacker,
|
@Nonnull Ref<EntityStore> attacker,
|
||||||
@Nonnull CommandBuffer<EntityStore> commandBuffer,
|
@Nonnull CommandBuffer<EntityStore> commandBuffer,
|
||||||
@Nonnull DamageExecutor executor) {
|
@Nonnull DamageExecutor executor) {
|
||||||
for (ChainHit hit : hits) {
|
int causeIndex = physicalDamageCauseIndex();
|
||||||
|
LOGGER.info(String.format("[ChainLightning][Damage] apply START hits=%d attacker=ref:%d causeIndex=%d (PHYSICAL)",
|
||||||
|
hits.size(), attacker.getIndex(), causeIndex));
|
||||||
|
for (int i = 0; i < hits.size(); i++) {
|
||||||
|
ChainHit hit = hits.get(i);
|
||||||
HytaleEntityAdapter adapter = (HytaleEntityAdapter) hit.target();
|
HytaleEntityAdapter adapter = (HytaleEntityAdapter) hit.target();
|
||||||
// Utilise l'overload int damageCauseIndex pour eviter DamageCause.PHYSICAL (null hors runtime).
|
Ref<EntityStore> targetRef = adapter.ref();
|
||||||
// Index 0 = PHYSICAL par convention dans l'asset map Hytale (verifie : DamageCause.CODEC
|
// Utilise l'overload int avec l'index resolu depuis DamageCause.PHYSICAL au runtime.
|
||||||
// initialise PHYSICAL en premier lors du boot serveur).
|
// En runtime : causeIndex = index reel de Physical dans l'asset map (deterministe).
|
||||||
|
// En test : causeIndex = 0 (neutre, non verifie par les tests unitaires).
|
||||||
Damage damage = new Damage(
|
Damage damage = new Damage(
|
||||||
new Damage.EntitySource(attacker),
|
new Damage.EntitySource(attacker),
|
||||||
0, // damageCauseIndex 0 = PHYSICAL
|
causeIndex,
|
||||||
(float) hit.damageHp()
|
(float) hit.damageHp()
|
||||||
);
|
);
|
||||||
executor.execute(adapter.ref(), commandBuffer, damage);
|
LOGGER.info(String.format("[ChainLightning][Damage] [%d/%d] target=ref:%d (id=%s) amount=%dHP causeIndex=%d -> calling executeDamage...",
|
||||||
|
i + 1, hits.size(), targetRef.getIndex(), adapter.id(), hit.damageHp(), causeIndex));
|
||||||
|
try {
|
||||||
|
executor.execute(targetRef, commandBuffer, damage);
|
||||||
|
LOGGER.info(String.format("[ChainLightning][Damage] [%d/%d] executeDamage OK", i + 1, hits.size()));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(java.util.logging.Level.WARNING,
|
||||||
|
String.format("[ChainLightning][Damage] [%d/%d] executeDamage THREW", i + 1, hits.size()), t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LOGGER.info("[ChainLightning][Damage] apply DONE");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-11
@@ -71,25 +71,34 @@ public final class ChainLightningSceptreInteraction extends SimpleInstantInterac
|
|||||||
protected void firstRun(@Nonnull InteractionType type,
|
protected void firstRun(@Nonnull InteractionType type,
|
||||||
@Nonnull InteractionContext context,
|
@Nonnull InteractionContext context,
|
||||||
@Nonnull CooldownHandler cooldownHandler) {
|
@Nonnull CooldownHandler cooldownHandler) {
|
||||||
|
LOGGER.info(String.format("[ChainLightning][1/9] firstRun ENTRY type=%s", type));
|
||||||
|
|
||||||
// --- Étape 1 : récupérer le cooldown ---
|
// --- Étape 1 : récupérer le cooldown ---
|
||||||
CooldownHandler.Cooldown cooldown = cooldownHandler.getCooldown(
|
CooldownHandler.Cooldown cooldown = cooldownHandler.getCooldown(
|
||||||
COOLDOWN_ID, COOLDOWN_TIME, CHARGE_TIMES, FORCE_CREATE, INTERRUPT_RECHARGE);
|
COOLDOWN_ID, COOLDOWN_TIME, CHARGE_TIMES, FORCE_CREATE, INTERRUPT_RECHARGE);
|
||||||
if (cooldown == null) {
|
if (cooldown == null) {
|
||||||
LOGGER.log(Level.WARNING, "[ChainLightning] cooldown handler returned null — aborting");
|
LOGGER.warning("[ChainLightning][1/9] cooldown handler returned null — aborting");
|
||||||
return;
|
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 ---
|
// --- Étape 2 : check cooldown sans décompter ---
|
||||||
if (cooldown.hasCooldown(false)) {
|
boolean onCooldown = cooldown.hasCooldown(false);
|
||||||
return; // refus silencieux
|
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 {
|
try {
|
||||||
// --- Étape 3 : extraire player + commandBuffer ---
|
// --- Étape 3 : extraire player + commandBuffer ---
|
||||||
Ref<EntityStore> playerRef = context.getEntity();
|
Ref<EntityStore> playerRef = context.getEntity();
|
||||||
CommandBuffer<EntityStore> commandBuffer = context.getCommandBuffer();
|
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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,36 +106,48 @@ public final class ChainLightningSceptreInteraction extends SimpleInstantInterac
|
|||||||
// CommandBuffer<EntityStore> implémente ComponentAccessor<EntityStore> — passé directement
|
// CommandBuffer<EntityStore> implémente ComponentAccessor<EntityStore> — passé directement
|
||||||
HytalePlayerRayCaster ray = new HytalePlayerRayCaster(playerRef, commandBuffer);
|
HytalePlayerRayCaster ray = new HytalePlayerRayCaster(playerRef, commandBuffer);
|
||||||
HytaleEntitySource neighbors = new HytaleEntitySource(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) ---
|
// --- É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(
|
List<ChainHit> hits = ChainResolver.resolve(
|
||||||
Vec3.ZERO, Vec3.ZERO, RAY_MAX_BLOCKS,
|
Vec3.ZERO, Vec3.ZERO, RAY_MAX_BLOCKS,
|
||||||
ray, neighbors, ChainParameters.DEFAULT
|
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 ---
|
// --- Étape 6 : pas de cible → return SANS cooldown ---
|
||||||
if (hits.isEmpty()) {
|
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;
|
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 ---
|
// --- É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);
|
ChainDamageApplier.apply(hits, playerRef, commandBuffer, commandBuffer);
|
||||||
|
LOGGER.info("[ChainLightning][7/9] damage application returned");
|
||||||
|
|
||||||
// --- Étape 8 : démarrer le cooldown APRÈS succès ---
|
// --- Étape 8 : démarrer le cooldown APRÈS succès ---
|
||||||
cooldown.deductCharge();
|
cooldown.deductCharge();
|
||||||
|
LOGGER.info(String.format("[ChainLightning][8/9] cooldown deducted (next available in %.1fs)", COOLDOWN_TIME));
|
||||||
|
|
||||||
// --- Étape 9 : log structuré ---
|
// --- Étape 9 : log structuré final ---
|
||||||
if (LOGGER.isLoggable(Level.INFO)) {
|
|
||||||
StringBuilder ids = new StringBuilder();
|
StringBuilder ids = new StringBuilder();
|
||||||
for (int i = 0; i < hits.size(); i++) {
|
for (int i = 0; i < hits.size(); i++) {
|
||||||
if (i > 0) ids.append(',');
|
if (i > 0) ids.append(',');
|
||||||
ids.append(hits.get(i).target().id());
|
ids.append(hits.get(i).target().id());
|
||||||
}
|
}
|
||||||
LOGGER.log(Level.INFO,
|
LOGGER.info(String.format("[ChainLightning][9/9] DONE ref:%d chained %d targets [%s]",
|
||||||
"[ChainLightning] ref:{0} chained {1} targets [{2}]",
|
playerRef.getIndex(), hits.size(), ids.toString()));
|
||||||
new Object[]{"ref:" + playerRef.getIndex(), hits.size(), ids.toString()});
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
// CONTEXT decision : try/catch global pour éviter crash tick serveur
|
// CONTEXT decision : try/catch global pour éviter crash tick serveur
|
||||||
LOGGER.log(Level.WARNING, "[ChainLightning] chain resolution failed", t);
|
LOGGER.log(Level.WARNING, "[ChainLightning] chain resolution failed", t);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.mythlane.chainlightning.chain.Vec3;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation Phase 3 de {@link EntitySource} qui delegue a
|
* Implementation Phase 3 de {@link EntitySource} qui delegue a
|
||||||
@@ -24,6 +25,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class HytaleEntitySource implements EntitySource {
|
public final class HytaleEntitySource implements EntitySource {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(HytaleEntitySource.class.getName());
|
||||||
|
|
||||||
private final ComponentAccessor<EntityStore> accessor;
|
private final ComponentAccessor<EntityStore> accessor;
|
||||||
|
|
||||||
public HytaleEntitySource(@Nonnull ComponentAccessor<EntityStore> accessor) {
|
public HytaleEntitySource(@Nonnull ComponentAccessor<EntityStore> accessor) {
|
||||||
@@ -33,11 +36,21 @@ public final class HytaleEntitySource implements EntitySource {
|
|||||||
@Override
|
@Override
|
||||||
public List<ChainEntity> nearby(Vec3 origin, double radius) {
|
public List<ChainEntity> nearby(Vec3 origin, double radius) {
|
||||||
Vector3d hytaleOrigin = new Vector3d(origin.x(), origin.y(), origin.z());
|
Vector3d hytaleOrigin = new Vector3d(origin.x(), origin.y(), origin.z());
|
||||||
|
LOGGER.info(String.format("[ChainLightning][EntitySource] getAllEntitiesInSphere(origin=%s, radius=%.1f)",
|
||||||
|
origin, radius));
|
||||||
List<Ref<EntityStore>> refs = TargetUtil.getAllEntitiesInSphere(hytaleOrigin, radius, accessor);
|
List<Ref<EntityStore>> refs = TargetUtil.getAllEntitiesInSphere(hytaleOrigin, radius, accessor);
|
||||||
|
LOGGER.info(String.format("[ChainLightning][EntitySource] Hytale returned %d refs", refs == null ? -1 : refs.size()));
|
||||||
|
if (refs == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
List<ChainEntity> snapshots = new ArrayList<>(refs.size());
|
List<ChainEntity> snapshots = new ArrayList<>(refs.size());
|
||||||
for (Ref<EntityStore> ref : refs) {
|
for (Ref<EntityStore> ref : refs) {
|
||||||
snapshots.add(HytaleEntityAdapter.snapshot(ref, accessor));
|
ChainEntity adapter = HytaleEntityAdapter.snapshot(ref, accessor);
|
||||||
|
LOGGER.info(String.format("[ChainLightning][EntitySource] ref:%d -> id=%s pos=%s alive=%s",
|
||||||
|
ref.getIndex(), adapter.id(), adapter.position(), adapter.isAlive()));
|
||||||
|
snapshots.add(adapter);
|
||||||
}
|
}
|
||||||
|
LOGGER.info(String.format("[ChainLightning][EntitySource] returning %d snapshots", snapshots.size()));
|
||||||
return snapshots;
|
return snapshots;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.mythlane.chainlightning.chain.Vec3;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation Phase 3 de {@link RayCaster} qui delegue a {@link TargetUtil#getTargetEntity}.
|
* Implementation Phase 3 de {@link RayCaster} qui delegue a {@link TargetUtil#getTargetEntity}.
|
||||||
@@ -24,6 +25,8 @@ import java.util.Optional;
|
|||||||
*/
|
*/
|
||||||
public final class HytalePlayerRayCaster implements RayCaster {
|
public final class HytalePlayerRayCaster implements RayCaster {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(HytalePlayerRayCaster.class.getName());
|
||||||
|
|
||||||
private final Ref<EntityStore> playerRef;
|
private final Ref<EntityStore> playerRef;
|
||||||
private final ComponentAccessor<EntityStore> accessor;
|
private final ComponentAccessor<EntityStore> accessor;
|
||||||
|
|
||||||
@@ -35,10 +38,18 @@ public final class HytalePlayerRayCaster implements RayCaster {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ChainEntity> firstHit(Vec3 originIgnored, Vec3 directionIgnored, double maxBlocks) {
|
public Optional<ChainEntity> firstHit(Vec3 originIgnored, Vec3 directionIgnored, double maxBlocks) {
|
||||||
|
LOGGER.info(String.format("[ChainLightning][RayCast] TargetUtil.getTargetEntity(playerRef=ref:%d, maxBlocks=%.1f)",
|
||||||
|
playerRef.getIndex(), maxBlocks));
|
||||||
Ref<EntityStore> target = TargetUtil.getTargetEntity(playerRef, (float) maxBlocks, accessor);
|
Ref<EntityStore> target = TargetUtil.getTargetEntity(playerRef, (float) maxBlocks, accessor);
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
|
LOGGER.info("[ChainLightning][RayCast] no entity hit — returning Optional.empty()");
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return Optional.of(HytaleEntityAdapter.snapshot(target, accessor));
|
LOGGER.info(String.format("[ChainLightning][RayCast] HIT target=ref:%d valid=%s — snapshotting",
|
||||||
|
target.getIndex(), target.isValid()));
|
||||||
|
ChainEntity adapter = HytaleEntityAdapter.snapshot(target, accessor);
|
||||||
|
LOGGER.info(String.format("[ChainLightning][RayCast] snapshot id=%s pos=%s alive=%s",
|
||||||
|
adapter.id(), adapter.position(), adapter.isAlive()));
|
||||||
|
return Optional.of(adapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user