refactor: simplify ChainResolver and RayCaster interfaces, enhance ChainDamageApplier logic
- Updated ChainResolver to remove unused shooterOrigin and shooterDirection parameters, streamlining the resolve method. - Modified RayCaster interface to reflect changes in method signature, focusing on maxBlocks only. - Enhanced ChainDamageApplier to utilize a new CauseIndexHolder for resolving the PHYSICAL damage cause index, improving clarity and performance. - Refactored ChainDamageApplier.apply method to use HytaleEntityAdapter for target references, ensuring correct damage application. - Adjusted tests in ChainResolverTest and ChainDamageApplierTest to align with the new method signatures and logic. Tests: All tests passing. Build: ./gradlew clean build successful.
This commit is contained in:
@@ -12,15 +12,12 @@ public final class ChainResolver {
|
|||||||
private ChainResolver() {}
|
private ChainResolver() {}
|
||||||
|
|
||||||
/** Resolves the full chain; returns the primary plus up to maxTargets-1 nearest unique neighbors. */
|
/** Resolves the full chain; returns the primary plus up to maxTargets-1 nearest unique neighbors. */
|
||||||
public static List<ChainHit> resolve(
|
public static List<ChainHit> resolve(double rayMaxBlocks,
|
||||||
Vec3 shooterOrigin,
|
|
||||||
Vec3 shooterDirection,
|
|
||||||
double rayMaxBlocks,
|
|
||||||
RayCaster ray,
|
RayCaster ray,
|
||||||
EntitySource neighbors,
|
EntitySource neighbors,
|
||||||
ChainParameters params) {
|
ChainParameters params) {
|
||||||
|
|
||||||
Optional<ChainEntity> primaryOpt = ray.firstHit(shooterOrigin, shooterDirection, rayMaxBlocks);
|
Optional<ChainEntity> primaryOpt = ray.firstHit(rayMaxBlocks);
|
||||||
if (primaryOpt.isEmpty()) {
|
if (primaryOpt.isEmpty()) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package com.mythlane.chainlightning.chain;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/** SAM that returns the first entity hit along a ray, or empty. */
|
/** SAM that returns the first entity along the caster's look ray, or empty. */
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface RayCaster {
|
public interface RayCaster {
|
||||||
Optional<ChainEntity> firstHit(Vec3 origin, Vec3 direction, double maxBlocks);
|
Optional<ChainEntity> firstHit(double maxBlocks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.util.logging.Level;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
/** Applies DamageSystems.executeDamage to each chain hit; injectable executor keeps unit tests off the Hytale runtime. */
|
/** Applies DamageSystems.executeDamage to each chain hit; injectable executor keeps unit tests off the Hytale runtime. */
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public final class ChainDamageApplier {
|
public final class ChainDamageApplier {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ChainDamageApplier.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ChainDamageApplier.class.getName());
|
||||||
@@ -28,24 +29,28 @@ public final class ChainDamageApplier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Lazy holder keeps DamageSystems.<clinit> out of the test classpath. */
|
/** Lazy holder keeps DamageSystems.<clinit> out of the test classpath. */
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private static final class DefaultHolder {
|
private static final class DefaultHolder {
|
||||||
static final DamageExecutor INSTANCE = DamageSystems::executeDamage;
|
static final DamageExecutor INSTANCE = DamageSystems::executeDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Lazy holder caches the asset-map index of PHYSICAL once the runtime is initialized. */
|
||||||
|
private static final class CauseIndexHolder {
|
||||||
|
static final int VALUE = computeIndex();
|
||||||
|
|
||||||
|
private static int computeIndex() {
|
||||||
|
DamageCause physical = DamageCause.PHYSICAL;
|
||||||
|
return physical == null ? 0 : DamageCause.getAssetMap().getIndex(physical.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Production executor that delegates to DamageSystems.executeDamage. */
|
/** Production executor that delegates to DamageSystems.executeDamage. */
|
||||||
public static DamageExecutor defaultExecutor() {
|
public static DamageExecutor defaultExecutor() {
|
||||||
return DefaultHolder.INSTANCE;
|
return DefaultHolder.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolves the PHYSICAL cause index at call time so the runtime asset map ordering is honored. */
|
/** PHYSICAL cause index resolved from the asset map; falls back to 0 in unit tests where the runtime is not booted. */
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
static int physicalDamageCauseIndex() {
|
static int physicalDamageCauseIndex() {
|
||||||
DamageCause physical = DamageCause.PHYSICAL;
|
return CauseIndexHolder.VALUE;
|
||||||
if (physical == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return DamageCause.getAssetMap().getIndex(physical.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChainDamageApplier() {}
|
private ChainDamageApplier() {}
|
||||||
@@ -64,17 +69,13 @@ public final class ChainDamageApplier {
|
|||||||
@Nonnull CommandBuffer<EntityStore> commandBuffer,
|
@Nonnull CommandBuffer<EntityStore> commandBuffer,
|
||||||
@Nonnull DamageExecutor executor) {
|
@Nonnull DamageExecutor executor) {
|
||||||
int causeIndex = physicalDamageCauseIndex();
|
int causeIndex = physicalDamageCauseIndex();
|
||||||
|
Damage.EntitySource source = new Damage.EntitySource(attacker);
|
||||||
int succeeded = 0;
|
int succeeded = 0;
|
||||||
for (ChainHit hit : hits) {
|
for (ChainHit hit : hits) {
|
||||||
HytaleEntityAdapter adapter = (HytaleEntityAdapter) hit.target();
|
HytaleEntityAdapter adapter = HytaleEntityAdapter.from(hit);
|
||||||
Ref<EntityStore> targetRef = adapter.ref();
|
Damage damage = new Damage(source, causeIndex, (float) hit.damageHp());
|
||||||
Damage damage = new Damage(
|
|
||||||
new Damage.EntitySource(attacker),
|
|
||||||
causeIndex,
|
|
||||||
(float) hit.damageHp()
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
executor.execute(targetRef, commandBuffer, damage);
|
executor.execute(adapter.ref(), commandBuffer, damage);
|
||||||
succeeded++;
|
succeeded++;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
LOGGER.log(Level.WARNING,
|
LOGGER.log(Level.WARNING,
|
||||||
|
|||||||
+1
-5
@@ -11,7 +11,6 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
|||||||
import com.mythlane.chainlightning.chain.ChainHit;
|
import com.mythlane.chainlightning.chain.ChainHit;
|
||||||
import com.mythlane.chainlightning.chain.ChainParameters;
|
import com.mythlane.chainlightning.chain.ChainParameters;
|
||||||
import com.mythlane.chainlightning.chain.ChainResolver;
|
import com.mythlane.chainlightning.chain.ChainResolver;
|
||||||
import com.mythlane.chainlightning.chain.Vec3;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -81,10 +80,7 @@ public final class ChainLightningSceptreInteraction extends SimpleInstantInterac
|
|||||||
@Nonnull CommandBuffer<EntityStore> commandBuffer) {
|
@Nonnull CommandBuffer<EntityStore> commandBuffer) {
|
||||||
HytalePlayerRayCaster ray = new HytalePlayerRayCaster(playerRef, commandBuffer);
|
HytalePlayerRayCaster ray = new HytalePlayerRayCaster(playerRef, commandBuffer);
|
||||||
HytaleEntitySource neighbors = new HytaleEntitySource(commandBuffer);
|
HytaleEntitySource neighbors = new HytaleEntitySource(commandBuffer);
|
||||||
return ChainResolver.resolve(
|
return ChainResolver.resolve(RAY_MAX_BLOCKS, ray, neighbors, ChainParameters.DEFAULT);
|
||||||
Vec3.ZERO, Vec3.ZERO, RAY_MAX_BLOCKS,
|
|
||||||
ray, neighbors, ChainParameters.DEFAULT
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** VFX emit is best-effort: damage is already applied so a failure must not abort the cooldown step. */
|
/** VFX emit is best-effort: damage is already applied so a failure must not abort the cooldown step. */
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue;
|
|||||||
import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes;
|
import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes;
|
||||||
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.ChainEntity;
|
import com.mythlane.chainlightning.chain.ChainEntity;
|
||||||
|
import com.mythlane.chainlightning.chain.ChainHit;
|
||||||
import com.mythlane.chainlightning.chain.Vec3;
|
import com.mythlane.chainlightning.chain.Vec3;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
@@ -54,6 +55,12 @@ public final class HytaleEntityAdapter implements ChainEntity {
|
|||||||
return new HytaleEntityAdapter(ref, id, vec, alive);
|
return new HytaleEntityAdapter(ref, id, vec, alive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Single cast point so callers stop reaching into ChainEntity to recover the runtime adapter. */
|
||||||
|
@Nonnull
|
||||||
|
public static HytaleEntityAdapter from(@Nonnull ChainHit hit) {
|
||||||
|
return (HytaleEntityAdapter) hit.target();
|
||||||
|
}
|
||||||
|
|
||||||
/** Underlying Hytale ref, exposed so DamageSystems and EffectControllerComponent can address the entity. */
|
/** Underlying Hytale ref, exposed so DamageSystems and EffectControllerComponent can address the entity. */
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public Ref<EntityStore> ref() {
|
public Ref<EntityStore> ref() {
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
|||||||
import com.hypixel.hytale.server.core.util.TargetUtil;
|
import com.hypixel.hytale.server.core.util.TargetUtil;
|
||||||
import com.mythlane.chainlightning.chain.ChainEntity;
|
import com.mythlane.chainlightning.chain.ChainEntity;
|
||||||
import com.mythlane.chainlightning.chain.RayCaster;
|
import com.mythlane.chainlightning.chain.RayCaster;
|
||||||
import com.mythlane.chainlightning.chain.Vec3;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/** Phase 3 RayCaster delegating to TargetUtil; origin/direction args are ignored since TargetUtil derives them from playerRef. */
|
/** Phase 3 RayCaster delegating to TargetUtil; eye origin and look direction are derived from the playerRef inside Hytale. */
|
||||||
public final class HytalePlayerRayCaster implements RayCaster {
|
public final class HytalePlayerRayCaster implements RayCaster {
|
||||||
|
|
||||||
private final Ref<EntityStore> playerRef;
|
private final Ref<EntityStore> playerRef;
|
||||||
@@ -24,7 +23,7 @@ public final class HytalePlayerRayCaster implements RayCaster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ChainEntity> firstHit(Vec3 originIgnored, Vec3 directionIgnored, double maxBlocks) {
|
public Optional<ChainEntity> firstHit(double 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) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
|||||||
@@ -37,28 +37,17 @@ public final class HytaleVfxEmitter {
|
|||||||
|
|
||||||
ComponentAccessor<EntityStore> accessor = commandBuffer;
|
ComponentAccessor<EntityStore> accessor = commandBuffer;
|
||||||
int applied = 0;
|
int applied = 0;
|
||||||
int skipped = 0;
|
for (ChainHit hit : hits) {
|
||||||
for (int i = 0; i < hits.size(); i++) {
|
Ref<EntityStore> targetRef = HytaleEntityAdapter.from(hit).ref();
|
||||||
Ref<EntityStore> targetRef = ((HytaleEntityAdapter) hits.get(i).target()).ref();
|
if (!targetRef.isValid()) continue;
|
||||||
if (targetRef == null || !targetRef.isValid()) {
|
|
||||||
skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
EffectControllerComponent ecc = accessor.getComponent(targetRef, EffectControllerComponent.getComponentType());
|
EffectControllerComponent ecc = accessor.getComponent(targetRef, EffectControllerComponent.getComponentType());
|
||||||
if (ecc == null) {
|
if (ecc != null && ecc.addEffect(targetRef, entityEffect, accessor)) {
|
||||||
skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
boolean ok = ecc.addEffect(targetRef, entityEffect, accessor);
|
|
||||||
if (ok) {
|
|
||||||
applied++;
|
applied++;
|
||||||
} else {
|
|
||||||
skipped++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info(String.format(
|
LOGGER.info(String.format(
|
||||||
"[ChainLightning][Vfx] EntityEffect '%s' applied to %d/%d targets (skipped=%d, caster=%s)",
|
"[ChainLightning][Vfx] EntityEffect '%s' applied to %d/%d targets (caster=%s)",
|
||||||
EFFECT_ID, applied, hits.size(), skipped, playerRef));
|
EFFECT_ID, applied, hits.size(), playerRef));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,16 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||||||
|
|
||||||
final class ChainResolverTest {
|
final class ChainResolverTest {
|
||||||
|
|
||||||
private static final Vec3 ORIGIN = new Vec3(0, 0, 0);
|
|
||||||
private static final Vec3 DIR = new Vec3(1, 0, 0);
|
|
||||||
private static final double RAY_MAX = 25.0;
|
private static final double RAY_MAX = 25.0;
|
||||||
|
private static final ChainParameters DEFAULT = ChainParameters.DEFAULT;
|
||||||
|
private static final ChainParameters TWO_HOPS = new ChainParameters(2, 8.0, new int[]{8, 6});
|
||||||
|
|
||||||
private static RayCaster rayMisses() {
|
private static RayCaster rayMisses() {
|
||||||
return (o, d, max) -> Optional.empty();
|
return max -> Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RayCaster rayHits(ChainEntity e) {
|
private static RayCaster rayHits(ChainEntity e) {
|
||||||
return (o, d, max) -> Optional.of(e);
|
return max -> Optional.of(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EntitySource neighborsAlways(List<ChainEntity> candidates) {
|
private static EntitySource neighborsAlways(List<ChainEntity> candidates) {
|
||||||
@@ -35,16 +35,14 @@ final class ChainResolverTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void resolve_noPrimaryHit_returnsEmpty() {
|
void resolve_noPrimaryHit_returnsEmpty() {
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
List<ChainHit> hits = ChainResolver.resolve(RAY_MAX, rayMisses(), neighborsEmpty(), DEFAULT);
|
||||||
ORIGIN, DIR, RAY_MAX, rayMisses(), neighborsEmpty(), ChainParameters.DEFAULT);
|
|
||||||
assertTrue(hits.isEmpty());
|
assertTrue(hits.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void resolve_primaryOnly_noNeighbors_returnsSingleHit() {
|
void resolve_primaryOnly_noNeighbors_returnsSingleHit() {
|
||||||
ChainEntity primary = entity("p", 10, 0, 0);
|
ChainEntity primary = entity("p", 10, 0, 0);
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
List<ChainHit> hits = ChainResolver.resolve(RAY_MAX, rayHits(primary), neighborsEmpty(), DEFAULT);
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(primary), neighborsEmpty(), ChainParameters.DEFAULT);
|
|
||||||
assertEquals(1, hits.size());
|
assertEquals(1, hits.size());
|
||||||
assertSame(primary, hits.get(0).target());
|
assertSame(primary, hits.get(0).target());
|
||||||
assertEquals(8, hits.get(0).damageHp());
|
assertEquals(8, hits.get(0).damageHp());
|
||||||
@@ -60,8 +58,7 @@ final class ChainResolverTest {
|
|||||||
ChainEntity e4 = entity("e4", 18, 0, 0);
|
ChainEntity e4 = entity("e4", 18, 0, 0);
|
||||||
List<ChainEntity> all = List.of(e0, e1, e2, e3, e4);
|
List<ChainEntity> all = List.of(e0, e1, e2, e3, e4);
|
||||||
|
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
List<ChainHit> hits = ChainResolver.resolve(RAY_MAX, rayHits(e0), neighborsAlways(all), DEFAULT);
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(e0), neighborsAlways(all), ChainParameters.DEFAULT);
|
|
||||||
|
|
||||||
assertEquals(5, hits.size());
|
assertEquals(5, hits.size());
|
||||||
assertSame(e0, hits.get(0).target()); assertEquals(8, hits.get(0).damageHp()); assertEquals(0, hits.get(0).hopIndex());
|
assertSame(e0, hits.get(0).target()); assertEquals(8, hits.get(0).damageHp()); assertEquals(0, hits.get(0).hopIndex());
|
||||||
@@ -74,19 +71,19 @@ final class ChainResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
void resolve_moreThanFiveCandidates_stopsAtMaxTargets() {
|
void resolve_moreThanFiveCandidates_stopsAtMaxTargets() {
|
||||||
ChainEntity primary = entity("e0", 10, 0, 0);
|
ChainEntity primary = entity("e0", 10, 0, 0);
|
||||||
ChainEntity e1 = entity("e1", 11, 0, 0);
|
List<ChainEntity> all = List.of(
|
||||||
ChainEntity e2 = entity("e2", 12, 0, 0);
|
primary,
|
||||||
ChainEntity e3 = entity("e3", 13, 0, 0);
|
entity("e1", 11, 0, 0),
|
||||||
ChainEntity e4 = entity("e4", 14, 0, 0);
|
entity("e2", 12, 0, 0),
|
||||||
ChainEntity e5 = entity("e5", 15, 0, 0);
|
entity("e3", 13, 0, 0),
|
||||||
ChainEntity e6 = entity("e6", 16, 0, 0);
|
entity("e4", 14, 0, 0),
|
||||||
ChainEntity e7 = entity("e7", 17, 0, 0);
|
entity("e5", 15, 0, 0),
|
||||||
ChainEntity e8 = entity("e8", 18, 0, 0);
|
entity("e6", 16, 0, 0),
|
||||||
ChainEntity e9 = entity("e9", 19, 0, 0);
|
entity("e7", 17, 0, 0),
|
||||||
List<ChainEntity> all = List.of(primary, e1, e2, e3, e4, e5, e6, e7, e8, e9);
|
entity("e8", 18, 0, 0),
|
||||||
|
entity("e9", 19, 0, 0));
|
||||||
|
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
List<ChainHit> hits = ChainResolver.resolve(RAY_MAX, rayHits(primary), neighborsAlways(all), DEFAULT);
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(primary), neighborsAlways(all), ChainParameters.DEFAULT);
|
|
||||||
|
|
||||||
assertEquals(5, hits.size());
|
assertEquals(5, hits.size());
|
||||||
}
|
}
|
||||||
@@ -94,14 +91,12 @@ final class ChainResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
void resolve_candidatesOutsideRadius_excluded() {
|
void resolve_candidatesOutsideRadius_excluded() {
|
||||||
ChainEntity primary = entity("p", 0, 0, 0);
|
ChainEntity primary = entity("p", 0, 0, 0);
|
||||||
ChainEntity far1 = entity("f1", 9, 0, 0);
|
List<ChainEntity> far = List.of(
|
||||||
ChainEntity far2 = entity("f2", 0, 9, 0);
|
entity("f1", 9, 0, 0),
|
||||||
ChainEntity far3 = entity("f3", 0, 0, 9);
|
entity("f2", 0, 9, 0),
|
||||||
|
entity("f3", 0, 0, 9));
|
||||||
|
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
List<ChainHit> hits = ChainResolver.resolve(RAY_MAX, rayHits(primary), neighborsAlways(far), DEFAULT);
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(primary),
|
|
||||||
neighborsAlways(List.of(far1, far2, far3)),
|
|
||||||
ChainParameters.DEFAULT);
|
|
||||||
|
|
||||||
assertEquals(1, hits.size());
|
assertEquals(1, hits.size());
|
||||||
assertSame(primary, hits.get(0).target());
|
assertSame(primary, hits.get(0).target());
|
||||||
@@ -111,11 +106,8 @@ final class ChainResolverTest {
|
|||||||
void resolve_noDoubleHit_visitedExcluded() {
|
void resolve_noDoubleHit_visitedExcluded() {
|
||||||
ChainEntity a = entity("a", 0, 0, 0);
|
ChainEntity a = entity("a", 0, 0, 0);
|
||||||
ChainEntity b = entity("b", 2, 0, 0);
|
ChainEntity b = entity("b", 2, 0, 0);
|
||||||
List<ChainEntity> mutual = List.of(a, b);
|
|
||||||
|
|
||||||
ChainParameters p = new ChainParameters(5, 8.0, new int[]{8, 6, 4, 3, 2});
|
List<ChainHit> hits = ChainResolver.resolve(RAY_MAX, rayHits(a), neighborsAlways(List.of(a, b)), DEFAULT);
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(a), neighborsAlways(mutual), p);
|
|
||||||
|
|
||||||
assertEquals(2, hits.size());
|
assertEquals(2, hits.size());
|
||||||
assertSame(a, hits.get(0).target());
|
assertSame(a, hits.get(0).target());
|
||||||
@@ -129,9 +121,7 @@ final class ChainResolverTest {
|
|||||||
ChainEntity far = entity("far", 7, 0, 0);
|
ChainEntity far = entity("far", 7, 0, 0);
|
||||||
|
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
List<ChainHit> hits = ChainResolver.resolve(
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(primary),
|
RAY_MAX, rayHits(primary), neighborsAlways(List.of(near, far)), TWO_HOPS);
|
||||||
neighborsAlways(List.of(near, far)),
|
|
||||||
new ChainParameters(2, 8.0, new int[]{8, 6}));
|
|
||||||
|
|
||||||
assertEquals(2, hits.size());
|
assertEquals(2, hits.size());
|
||||||
assertSame(near, hits.get(1).target());
|
assertSame(near, hits.get(1).target());
|
||||||
@@ -144,14 +134,10 @@ final class ChainResolverTest {
|
|||||||
ChainEntity alpha = entity("alpha", 0, 5, 0);
|
ChainEntity alpha = entity("alpha", 0, 5, 0);
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
List<ChainEntity> order1 = List.of(zebra, alpha);
|
|
||||||
List<ChainEntity> order2 = List.of(alpha, zebra);
|
|
||||||
List<ChainHit> h1 = ChainResolver.resolve(
|
List<ChainHit> h1 = ChainResolver.resolve(
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(primary), neighborsAlways(order1),
|
RAY_MAX, rayHits(primary), neighborsAlways(List.of(zebra, alpha)), TWO_HOPS);
|
||||||
new ChainParameters(2, 8.0, new int[]{8, 6}));
|
|
||||||
List<ChainHit> h2 = ChainResolver.resolve(
|
List<ChainHit> h2 = ChainResolver.resolve(
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(primary), neighborsAlways(order2),
|
RAY_MAX, rayHits(primary), neighborsAlways(List.of(alpha, zebra)), TWO_HOPS);
|
||||||
new ChainParameters(2, 8.0, new int[]{8, 6}));
|
|
||||||
assertSame(alpha, h1.get(1).target(), "run " + i + " order1");
|
assertSame(alpha, h1.get(1).target(), "run " + i + " order1");
|
||||||
assertSame(alpha, h2.get(1).target(), "run " + i + " order2");
|
assertSame(alpha, h2.get(1).target(), "run " + i + " order2");
|
||||||
}
|
}
|
||||||
@@ -164,9 +150,7 @@ final class ChainResolverTest {
|
|||||||
ChainEntity alive = entity("alive", 4, 0, 0);
|
ChainEntity alive = entity("alive", 4, 0, 0);
|
||||||
|
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
List<ChainHit> hits = ChainResolver.resolve(
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(primary),
|
RAY_MAX, rayHits(primary), neighborsAlways(List.of(deadOne, alive)), TWO_HOPS);
|
||||||
neighborsAlways(List.of(deadOne, alive)),
|
|
||||||
new ChainParameters(2, 8.0, new int[]{8, 6}));
|
|
||||||
|
|
||||||
assertEquals(2, hits.size());
|
assertEquals(2, hits.size());
|
||||||
assertSame(alive, hits.get(1).target());
|
assertSame(alive, hits.get(1).target());
|
||||||
@@ -175,15 +159,15 @@ final class ChainResolverTest {
|
|||||||
@Test
|
@Test
|
||||||
void resolve_customMaxTargets_truncatesEarly() {
|
void resolve_customMaxTargets_truncatesEarly() {
|
||||||
ChainEntity e0 = entity("e0", 0, 0, 0);
|
ChainEntity e0 = entity("e0", 0, 0, 0);
|
||||||
ChainEntity e1 = entity("e1", 1, 0, 0);
|
List<ChainEntity> all = List.of(
|
||||||
ChainEntity e2 = entity("e2", 2, 0, 0);
|
e0,
|
||||||
ChainEntity e3 = entity("e3", 3, 0, 0);
|
entity("e1", 1, 0, 0),
|
||||||
ChainEntity e4 = entity("e4", 4, 0, 0);
|
entity("e2", 2, 0, 0),
|
||||||
List<ChainEntity> all = List.of(e0, e1, e2, e3, e4);
|
entity("e3", 3, 0, 0),
|
||||||
|
entity("e4", 4, 0, 0));
|
||||||
|
|
||||||
ChainParameters p = new ChainParameters(3, 8.0, new int[]{10, 5, 1});
|
ChainParameters p = new ChainParameters(3, 8.0, new int[]{10, 5, 1});
|
||||||
List<ChainHit> hits = ChainResolver.resolve(
|
List<ChainHit> hits = ChainResolver.resolve(RAY_MAX, rayHits(e0), neighborsAlways(all), p);
|
||||||
ORIGIN, DIR, RAY_MAX, rayHits(e0), neighborsAlways(all), p);
|
|
||||||
|
|
||||||
assertEquals(3, hits.size());
|
assertEquals(3, hits.size());
|
||||||
assertEquals(10, hits.get(0).damageHp());
|
assertEquals(10, hits.get(0).damageHp());
|
||||||
|
|||||||
@@ -23,12 +23,7 @@ final class ChainDamageApplierTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void apply_invokes_executeDamage_per_hit() {
|
void apply_invokes_executeDamage_per_hit() {
|
||||||
@SuppressWarnings("unchecked")
|
Fixture f = new Fixture();
|
||||||
Ref<EntityStore> attacker = (Ref<EntityStore>) mock(Ref.class);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
CommandBuffer<EntityStore> buf = (CommandBuffer<EntityStore>) mock(CommandBuffer.class);
|
|
||||||
ChainDamageApplier.DamageExecutor executor = mock(ChainDamageApplier.DamageExecutor.class);
|
|
||||||
|
|
||||||
List<ChainHit> hits = List.of(
|
List<ChainHit> hits = List.of(
|
||||||
hit("a", 8, 0),
|
hit("a", 8, 0),
|
||||||
hit("b", 6, 1),
|
hit("b", 6, 1),
|
||||||
@@ -37,53 +32,42 @@ final class ChainDamageApplierTest {
|
|||||||
hit("e", 2, 4)
|
hit("e", 2, 4)
|
||||||
);
|
);
|
||||||
|
|
||||||
ChainDamageApplier.apply(hits, attacker, buf, executor);
|
ChainDamageApplier.apply(hits, f.attacker, f.buf, f.executor);
|
||||||
|
|
||||||
verify(executor, times(5)).execute(
|
verify(f.executor, times(5)).execute(
|
||||||
ArgumentMatchers.any(),
|
ArgumentMatchers.any(),
|
||||||
ArgumentMatchers.eq(buf),
|
ArgumentMatchers.eq(f.buf),
|
||||||
ArgumentMatchers.any(Damage.class)
|
ArgumentMatchers.any(Damage.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void apply_passes_correct_damage_amounts_in_order() {
|
void apply_passes_correct_damage_amounts_in_order() {
|
||||||
@SuppressWarnings("unchecked")
|
Fixture f = new Fixture();
|
||||||
Ref<EntityStore> attacker = (Ref<EntityStore>) mock(Ref.class);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
CommandBuffer<EntityStore> buf = (CommandBuffer<EntityStore>) mock(CommandBuffer.class);
|
|
||||||
ChainDamageApplier.DamageExecutor executor = mock(ChainDamageApplier.DamageExecutor.class);
|
|
||||||
|
|
||||||
List<ChainHit> hits = List.of(
|
List<ChainHit> hits = List.of(
|
||||||
hit("a", 8, 0),
|
hit("a", 8, 0),
|
||||||
hit("b", 6, 1),
|
hit("b", 6, 1),
|
||||||
hit("c", 4, 2)
|
hit("c", 4, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
ArgumentCaptor<Damage> damageCaptor = ArgumentCaptor.forClass(Damage.class);
|
ArgumentCaptor<Damage> damageCaptor = ArgumentCaptor.forClass(Damage.class);
|
||||||
|
|
||||||
ChainDamageApplier.apply(hits, attacker, buf, executor);
|
ChainDamageApplier.apply(hits, f.attacker, f.buf, f.executor);
|
||||||
|
|
||||||
verify(executor, times(3)).execute(
|
verify(f.executor, times(3)).execute(
|
||||||
ArgumentMatchers.any(),
|
ArgumentMatchers.any(),
|
||||||
ArgumentMatchers.eq(buf),
|
ArgumentMatchers.eq(f.buf),
|
||||||
damageCaptor.capture()
|
damageCaptor.capture()
|
||||||
);
|
);
|
||||||
|
|
||||||
assertThat(damageCaptor.getAllValues()).hasSize(3);
|
assertThat(damageCaptor.getAllValues()).hasSize(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void apply_with_empty_list_invokes_nothing() {
|
void apply_with_empty_list_invokes_nothing() {
|
||||||
@SuppressWarnings("unchecked")
|
Fixture f = new Fixture();
|
||||||
Ref<EntityStore> attacker = (Ref<EntityStore>) mock(Ref.class);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
CommandBuffer<EntityStore> buf = (CommandBuffer<EntityStore>) mock(CommandBuffer.class);
|
|
||||||
ChainDamageApplier.DamageExecutor executor = mock(ChainDamageApplier.DamageExecutor.class);
|
|
||||||
|
|
||||||
ChainDamageApplier.apply(List.of(), attacker, buf, executor);
|
ChainDamageApplier.apply(List.of(), f.attacker, f.buf, f.executor);
|
||||||
|
|
||||||
verify(executor, never()).execute(
|
verify(f.executor, never()).execute(
|
||||||
ArgumentMatchers.any(),
|
ArgumentMatchers.any(),
|
||||||
ArgumentMatchers.any(),
|
ArgumentMatchers.any(),
|
||||||
ArgumentMatchers.any()
|
ArgumentMatchers.any()
|
||||||
@@ -92,30 +76,22 @@ final class ChainDamageApplierTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void apply_passes_adapter_ref_not_attacker_ref() {
|
void apply_passes_adapter_ref_not_attacker_ref() {
|
||||||
@SuppressWarnings("unchecked")
|
Fixture f = new Fixture();
|
||||||
Ref<EntityStore> attacker = (Ref<EntityStore>) mock(Ref.class, "attacker");
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Ref<EntityStore> targetRef = (Ref<EntityStore>) mock(Ref.class, "target");
|
Ref<EntityStore> targetRef = (Ref<EntityStore>) mock(Ref.class, "target");
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
CommandBuffer<EntityStore> buf = (CommandBuffer<EntityStore>) mock(CommandBuffer.class);
|
|
||||||
ChainDamageApplier.DamageExecutor executor = mock(ChainDamageApplier.DamageExecutor.class);
|
|
||||||
|
|
||||||
HytaleEntityAdapter adapter = HytaleEntityAdapter.forTest(targetRef, "target", Vec3.ZERO, false);
|
HytaleEntityAdapter adapter = HytaleEntityAdapter.forTest(targetRef, "target", Vec3.ZERO, false);
|
||||||
ChainHit hit = new ChainHit(adapter, 8, 0);
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
ArgumentCaptor<Ref<EntityStore>> refCaptor = ArgumentCaptor.forClass(Ref.class);
|
ArgumentCaptor<Ref<EntityStore>> refCaptor = ArgumentCaptor.forClass(Ref.class);
|
||||||
|
|
||||||
ChainDamageApplier.apply(List.of(hit), attacker, buf, executor);
|
ChainDamageApplier.apply(List.of(new ChainHit(adapter, 8, 0)), f.attacker, f.buf, f.executor);
|
||||||
|
|
||||||
verify(executor, times(1)).execute(
|
verify(f.executor, times(1)).execute(
|
||||||
refCaptor.capture(),
|
refCaptor.capture(),
|
||||||
ArgumentMatchers.eq(buf),
|
ArgumentMatchers.eq(f.buf),
|
||||||
ArgumentMatchers.any(Damage.class)
|
ArgumentMatchers.any(Damage.class)
|
||||||
);
|
);
|
||||||
|
|
||||||
assertThat(refCaptor.getValue()).isSameAs(targetRef);
|
assertThat(refCaptor.getValue()).isSameAs(targetRef);
|
||||||
assertThat(refCaptor.getValue()).isNotSameAs(attacker);
|
assertThat(refCaptor.getValue()).isNotSameAs(f.attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Builds a ChainHit backed by a HytaleEntityAdapter created via forTest to skip Hytale runtime init. */
|
/** Builds a ChainHit backed by a HytaleEntityAdapter created via forTest to skip Hytale runtime init. */
|
||||||
@@ -125,4 +101,13 @@ final class ChainDamageApplierTest {
|
|||||||
HytaleEntityAdapter adapter = HytaleEntityAdapter.forTest(ref, id, Vec3.ZERO, false);
|
HytaleEntityAdapter adapter = HytaleEntityAdapter.forTest(ref, id, Vec3.ZERO, false);
|
||||||
return new ChainHit(adapter, dmg, hop);
|
return new ChainHit(adapter, dmg, hop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Bundles the three mocks every test needs to keep test bodies focused on assertions. */
|
||||||
|
private static final class Fixture {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Ref<EntityStore> attacker = (Ref<EntityStore>) mock(Ref.class, "attacker");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final CommandBuffer<EntityStore> buf = (CommandBuffer<EntityStore>) mock(CommandBuffer.class);
|
||||||
|
final ChainDamageApplier.DamageExecutor executor = mock(ChainDamageApplier.DamageExecutor.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user