feat(03-04): ajoute 6 champs optionnels sur GravityFlipRegion (Task 1)
- POJO: FallDamage (false), GracePeriodMs (2500), VerticalForce (0.1), AffectPlayers/Npcs/Items (true) avec getters/setters - CODEC: 6 .append sans nonNull validator (sémantique optionnelle) - Tests: 6 round-trip + back-compat defaults (9 tests total)
This commit is contained in:
@@ -25,6 +25,8 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -131,9 +133,27 @@ public final class GravityApplier {
|
||||
// THREADING: écrit/lu depuis les workers ECS via forEachEntityParallel → ConcurrentHashMap.newKeySet obligatoire.
|
||||
private final Set<UUID> previouslyInverted = ConcurrentHashMap.newKeySet();
|
||||
private final Consumer<Throwable> errorHandler;
|
||||
private final Consumer<String> infoHandler;
|
||||
|
||||
// --- Debug diag (throttled counters + per-UUID one-shot) ---
|
||||
private final AtomicLong tickCounter = new AtomicLong();
|
||||
private final AtomicInteger entitiesInRegionTick = new AtomicInteger();
|
||||
private final AtomicInteger playersWokenTick = new AtomicInteger();
|
||||
private final AtomicInteger npcsSeenTick = new AtomicInteger();
|
||||
private final AtomicInteger npcsRoleNullTick = new AtomicInteger();
|
||||
private final AtomicInteger npcsControllerNullTick = new AtomicInteger();
|
||||
private final AtomicInteger npcsWokenTick = new AtomicInteger();
|
||||
private final AtomicInteger otherEntitiesTick = new AtomicInteger();
|
||||
private final AtomicInteger wakeExceptionsTick = new AtomicInteger();
|
||||
private final Set<UUID> loggedNpcUuids = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public GravityApplier(Consumer<Throwable> errorHandler) {
|
||||
this(errorHandler, null);
|
||||
}
|
||||
|
||||
public GravityApplier(Consumer<Throwable> errorHandler, Consumer<String> infoHandler) {
|
||||
this.errorHandler = errorHandler == null ? t -> {} : errorHandler;
|
||||
this.infoHandler = infoHandler == null ? m -> {} : infoHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,6 +234,7 @@ public final class GravityApplier {
|
||||
|
||||
UUID u = uc.getUuid();
|
||||
currentlyInRegion.add(u);
|
||||
entitiesInRegionTick.incrementAndGet();
|
||||
|
||||
// --- existant (plan 03-01) : flip ECS native ---
|
||||
if (!v.isInvertedGravity()) {
|
||||
@@ -257,6 +278,24 @@ public final class GravityApplier {
|
||||
// Update tracker — ces ops sont sur le tick thread après la fin du pass parallel.
|
||||
previouslyInverted.clear();
|
||||
previouslyInverted.addAll(currentlyInRegion);
|
||||
|
||||
// --- DEBUG : dump 1×/sec (10 ticks @100ms) ---
|
||||
long tick = tickCounter.incrementAndGet();
|
||||
if (tick % 10 == 0) {
|
||||
int inReg = entitiesInRegionTick.getAndSet(0);
|
||||
int players = playersWokenTick.getAndSet(0);
|
||||
int npcs = npcsSeenTick.getAndSet(0);
|
||||
int roleNull = npcsRoleNullTick.getAndSet(0);
|
||||
int ctrlNull = npcsControllerNullTick.getAndSet(0);
|
||||
int npcsWoken = npcsWokenTick.getAndSet(0);
|
||||
int others = otherEntitiesTick.getAndSet(0);
|
||||
int excs = wakeExceptionsTick.getAndSet(0);
|
||||
if (inReg > 0 || npcs > 0 || players > 0 || excs > 0) {
|
||||
infoHandler.accept(String.format(
|
||||
"[DBG tick=%d] entitiesInRegion=%d players=%d npcs=%d (roleNull=%d ctrlNull=%d woken=%d) others=%d excs=%d regions=%d",
|
||||
tick, inReg, players, npcs, roleNull, ctrlNull, npcsWoken, others, excs, enabledRegions.size()));
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
errorHandler.accept(th);
|
||||
}
|
||||
@@ -293,7 +332,9 @@ public final class GravityApplier {
|
||||
mm.applyDefaultSettings();
|
||||
PacketHandler ph = pr.getPacketHandler();
|
||||
mm.update(ph);
|
||||
playersWokenTick.incrementAndGet();
|
||||
} catch (Throwable th) {
|
||||
wakeExceptionsTick.incrementAndGet();
|
||||
errorHandler.accept(th);
|
||||
}
|
||||
return; // un joueur n'est pas un NPC — court-circuit
|
||||
@@ -304,19 +345,66 @@ public final class GravityApplier {
|
||||
NPCEntity npc = null;
|
||||
try { npc = chunk.getComponent(index, NPCT); } catch (Throwable ignored) {}
|
||||
if (npc != null) {
|
||||
npcsSeenTick.incrementAndGet();
|
||||
try {
|
||||
Role role = npc.getRole();
|
||||
if (role != null) {
|
||||
if (role == null) {
|
||||
npcsRoleNullTick.incrementAndGet();
|
||||
} else {
|
||||
MotionController active = role.getActiveMotionController();
|
||||
if (active != null) {
|
||||
if (active == null) {
|
||||
npcsControllerNullTick.incrementAndGet();
|
||||
// log one-shot : rôle non-null mais controller null
|
||||
UUIDComponent uc = null;
|
||||
try { uc = chunk.getComponent(index, uuidType()); } catch (Throwable ignored) {}
|
||||
if (uc != null && loggedNpcUuids.add(uc.getUuid())) {
|
||||
infoHandler.accept(String.format(
|
||||
"[DBG npc.ctrlNull] uuid=%s roleClass=%s",
|
||||
uc.getUuid(), role.getClass().getName()));
|
||||
}
|
||||
} else {
|
||||
active.updatePhysicsValues(targetValues);
|
||||
npcsWokenTick.incrementAndGet();
|
||||
|
||||
// --- NOUVEAU (plan 03-03) : seed forceVelocity.y pour activer le path
|
||||
// MotionControllerWalk ligne ~881 qui appelle computeNewFallSpeed —
|
||||
// seule fonction qui HONORE movementSettings.invertedGravity pour NPCs.
|
||||
// Le path normal WALKING/DESCENDING utilise this.gravity (non-inverse,
|
||||
// clampé ≥0), d'où l'absence d'effet du flip natif sur les moutons.
|
||||
// Un seed de +0.1 à chaque tick suffit pour que forceVelocity.y ≠ 0 ;
|
||||
// ensuite computeNewFallSpeed applique l'accel terminal inversée.
|
||||
// Au sortie (targetFlag=false), on n'ajoute rien — la damping
|
||||
// +epsilon (cf. MotionControllerBase line 635-637) zéroe naturellement.
|
||||
if (targetFlag) {
|
||||
try {
|
||||
active.addForce(
|
||||
new com.hypixel.hytale.math.vector.Vector3d(0, 0.1, 0),
|
||||
null);
|
||||
} catch (Throwable th) {
|
||||
wakeExceptionsTick.incrementAndGet();
|
||||
errorHandler.accept(th);
|
||||
}
|
||||
}
|
||||
|
||||
// log one-shot : première fois qu'on voit cet UUID NPC — on dump le controller class
|
||||
UUIDComponent uc = null;
|
||||
try { uc = chunk.getComponent(index, uuidType()); } catch (Throwable ignored) {}
|
||||
if (uc != null && loggedNpcUuids.add(uc.getUuid())) {
|
||||
infoHandler.accept(String.format(
|
||||
"[DBG npc.woken] uuid=%s controllerClass=%s roleClass=%s targetFlag=%s",
|
||||
uc.getUuid(), active.getClass().getName(), role.getClass().getName(), targetFlag));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
wakeExceptionsTick.incrementAndGet();
|
||||
errorHandler.accept(th);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// sinon : item / autre — pas de wake-up, le cmdBuf.replaceComponent du pass 1 suffit
|
||||
otherEntitiesTick.incrementAndGet();
|
||||
}
|
||||
|
||||
/** Pure data-diff utilitaire pour tests unitaires (pas de runtime Hytale). */
|
||||
|
||||
Reference in New Issue
Block a user