forked from phenom/healer-man
Release Healer Man 0.1.46
This commit is contained in:
1 parent
f82daf1c13
commit
9ff574ac0b
73 files changed
+33486
-174
No files matched your search
+70
-36
@@ -23,6 +23,10 @@ import {
|
||||
onlineSessionOwnsWorldSimulation,
|
||||
} from "./onlineSessionRuntime";
|
||||
import { combatSimulationTimerShouldRun } from "./combatSimulationLifecycle";
|
||||
import {
|
||||
DUNGEON_SIMULATION_FIXED_STEP_MS,
|
||||
useDungeonSimulationStore,
|
||||
} from "./dungeonSimulationStore";
|
||||
|
||||
/**
|
||||
* Connects the pure combat runtime to the active character and persistent
|
||||
@@ -75,7 +79,7 @@ export function CombatBridge() {
|
||||
// after Character Select has already replaced the active character.
|
||||
const shell = useShellStore.getState();
|
||||
if (shell.activeCharacter?.id !== characterId || shell.session?.ownerId !== ownerId) return;
|
||||
if (useGameStore.getState().bossTest) return;
|
||||
if (useGameStore.getState().gmTestMode) return;
|
||||
|
||||
// Persisted collections are replaced immutably by combat actions. A
|
||||
// reference/primitive comparison therefore detects the same changes as
|
||||
@@ -121,54 +125,80 @@ export function CombatBridge() {
|
||||
let previous = performance.now();
|
||||
let timer: number | null = null;
|
||||
|
||||
const advanceOneTick = (delta: number, epochNow: number, spectator: boolean) => {
|
||||
const game = useGameStore.getState();
|
||||
const simulation = useDungeonSimulationStore.getState();
|
||||
const combat = useCombatStore.getState();
|
||||
combat.setPlayerPosition(game.playerPosition);
|
||||
combat.tick(delta, epochNow);
|
||||
if (
|
||||
onlineSessionOwnsWorldSimulation()
|
||||
&& game.gameMode === "dungeon"
|
||||
&& (!spectator || simulation.mode === "full-combat")
|
||||
) {
|
||||
useCombatStore.getState().engageNearbyMobs(epochNow, game.playerPosition);
|
||||
}
|
||||
if (onlineSessionOwnsWorldSimulation()) {
|
||||
if (!spectator || simulation.mode === "full-combat") {
|
||||
useCombatStore.getState().advanceMobCombat(epochNow, game.playerPosition);
|
||||
}
|
||||
advanceDungeonPartyCombat(epochNow);
|
||||
}
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
if (onlineSessionOwnsWorldSimulation() && game.gameMode === "dungeon") {
|
||||
const mobs = useCombatStore.getState().mobs;
|
||||
const combatEvents = deriveDungeonEncounterCombatEvents(
|
||||
encounterMobCursor.current,
|
||||
mobs,
|
||||
epochNow,
|
||||
);
|
||||
encounterMobCursor.current = combatEvents.cursor;
|
||||
for (const event of combatEvents.events) dispatchActiveDungeonEntityEvent(event);
|
||||
const registeredEntityIds = Object.keys(mobs);
|
||||
advanceDungeonEncounters(game.activeDungeonId, {
|
||||
kind: "tick",
|
||||
now: epochNow,
|
||||
facts: {
|
||||
registeredEntityIds,
|
||||
deadEntityIds: registeredEntityIds.filter((id) => mobs[id].dead),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const advance = () => {
|
||||
const now = performance.now();
|
||||
const delta = Math.min(0.25, Math.max(0, (now - previous) / 1_000));
|
||||
const wallDeltaMs = Math.min(1_000, Math.max(0, now - previous));
|
||||
const delta = Math.min(0.25, wallDeltaMs / 1_000);
|
||||
previous = now;
|
||||
const simulation = useDungeonSimulationStore.getState();
|
||||
if (simulation.sessionMode === "spectate-replay") {
|
||||
simulation.consumeTicks(wallDeltaMs);
|
||||
return;
|
||||
}
|
||||
if (simulation.sessionMode === "spectate-live") {
|
||||
const assetStatus = useGameStore.getState().assetStatus;
|
||||
if (assetStatus !== "ready" && assetStatus !== "proxy") return;
|
||||
for (const tickNow of simulation.consumeTicks(wallDeltaMs)) {
|
||||
advanceOneTick(DUNGEON_SIMULATION_FIXED_STEP_MS / 1_000, tickNow, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const game = useGameStore.getState();
|
||||
const sharedOnline = onlineSessionIsActive();
|
||||
if ((!game.paused || sharedOnline) && (!game.companionOpen || sharedOnline)) {
|
||||
const epochNow = Date.now();
|
||||
const combat = useCombatStore.getState();
|
||||
combat.setPlayerPosition(game.playerPosition);
|
||||
combat.tick(delta, epochNow);
|
||||
if (onlineSessionOwnsWorldSimulation() && game.gameMode === "dungeon") {
|
||||
useCombatStore.getState().engageNearbyMobs(epochNow, game.playerPosition);
|
||||
}
|
||||
if (onlineSessionOwnsWorldSimulation()) {
|
||||
useCombatStore.getState().advanceMobCombat(epochNow, game.playerPosition);
|
||||
advanceDungeonPartyCombat(epochNow);
|
||||
}
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
if (onlineSessionOwnsWorldSimulation() && game.gameMode === "dungeon") {
|
||||
const mobs = useCombatStore.getState().mobs;
|
||||
const combatEvents = deriveDungeonEncounterCombatEvents(
|
||||
encounterMobCursor.current,
|
||||
mobs,
|
||||
epochNow,
|
||||
);
|
||||
encounterMobCursor.current = combatEvents.cursor;
|
||||
for (const event of combatEvents.events) dispatchActiveDungeonEntityEvent(event);
|
||||
const registeredEntityIds = Object.keys(mobs);
|
||||
advanceDungeonEncounters(game.activeDungeonId, {
|
||||
kind: "tick",
|
||||
now: epochNow,
|
||||
facts: {
|
||||
registeredEntityIds,
|
||||
deadEntityIds: registeredEntityIds.filter((id) => mobs[id].dead),
|
||||
},
|
||||
});
|
||||
}
|
||||
advanceOneTick(delta, Date.now(), false);
|
||||
}
|
||||
};
|
||||
|
||||
const reconcileTimer = () => {
|
||||
const game = useGameStore.getState();
|
||||
const simulation = useDungeonSimulationStore.getState();
|
||||
const shouldRun = combatSimulationTimerShouldRun({
|
||||
sharedOnline: onlineSessionIsActive(),
|
||||
paused: game.paused,
|
||||
companionOpen: game.companionOpen,
|
||||
documentHidden: document.hidden,
|
||||
paused: simulation.sessionMode === "play" ? game.paused : false,
|
||||
companionOpen: simulation.sessionMode === "play" ? game.companionOpen : false,
|
||||
documentHidden: simulation.sessionMode === "play" ? document.hidden : false,
|
||||
});
|
||||
if (shouldRun && timer === null) {
|
||||
// Reset the clock on resume so a long pause/background period cannot
|
||||
@@ -193,6 +223,9 @@ export function CombatBridge() {
|
||||
// store notification gives the bridge a reactive point at which to
|
||||
// reconcile while preserving continuous shared-online simulation.
|
||||
const unsubscribeOnlineSession = useOnlineSessionStore.subscribe(reconcileTimer);
|
||||
const unsubscribeSimulation = useDungeonSimulationStore.subscribe((state, previousState) => {
|
||||
if (state.sessionMode !== previousState.sessionMode || state.runState !== previousState.runState) reconcileTimer();
|
||||
});
|
||||
document.addEventListener("visibilitychange", reconcileTimer);
|
||||
reconcileTimer();
|
||||
|
||||
@@ -200,6 +233,7 @@ export function CombatBridge() {
|
||||
if (timer !== null) window.clearInterval(timer);
|
||||
unsubscribeGame();
|
||||
unsubscribeOnlineSession();
|
||||
unsubscribeSimulation();
|
||||
document.removeEventListener("visibilitychange", reconcileTimer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
Reference in new issue
Block a user