1
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
canUseLimitedPrimaryNpcChat,
|
||||
resolveActiveSceneActEncounterFocusNpcId,
|
||||
resolveActiveSceneActEncounterNpcIds,
|
||||
} from '../services/customWorldSceneActRuntime';
|
||||
import { AnimationState, Encounter, GameState, SceneNpc, WorldType } from '../types';
|
||||
import { getRecruitedNpcIds } from './companionRoster';
|
||||
import {
|
||||
@@ -15,10 +20,6 @@ import {
|
||||
getSceneHostileNpcs,
|
||||
getWorldCampScenePreset,
|
||||
} from './scenePresets';
|
||||
import {
|
||||
canUseLimitedPrimaryNpcChat,
|
||||
resolveActiveSceneActEncounterNpcIds,
|
||||
} from '../services/customWorldSceneActRuntime';
|
||||
|
||||
export const EXPLORE_APPROACH_DURATION_MS = 4000;
|
||||
export const PREVIEW_ENTITY_X_METERS = 12;
|
||||
@@ -115,7 +116,11 @@ function getAvailableFriendlySceneNpcs(state: GameState) {
|
||||
const activeActNpcIdSet = new Set(activeActNpcIds);
|
||||
|
||||
return getSceneFriendlyNpcs(state.currentScenePreset)
|
||||
.filter(candidate => !isCampScene || Boolean(candidate.characterId))
|
||||
.filter(candidate =>
|
||||
!isCampScene ||
|
||||
Boolean(candidate.characterId) ||
|
||||
activeActNpcIdSet.has(candidate.id),
|
||||
)
|
||||
.filter(candidate => candidate.characterId !== state.playerCharacter?.id)
|
||||
.filter(candidate => !recruitedNpcIds.has(candidate.id))
|
||||
.filter(candidate =>
|
||||
@@ -126,6 +131,29 @@ function getAvailableFriendlySceneNpcs(state: GameState) {
|
||||
);
|
||||
}
|
||||
|
||||
function getAvailableActiveSceneActNpcs(state: GameState) {
|
||||
const recruitedNpcIds = getRecruitedNpcIds(state);
|
||||
const activeActNpcIds = resolveActiveSceneActEncounterNpcIds({
|
||||
profile: state.customWorldProfile,
|
||||
sceneId: state.currentScenePreset?.id ?? null,
|
||||
storyEngineMemory: state.storyEngineMemory,
|
||||
});
|
||||
const activeActNpcIdSet = new Set(activeActNpcIds);
|
||||
if (activeActNpcIdSet.size === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (state.currentScenePreset?.npcs ?? [])
|
||||
.filter(candidate => {
|
||||
const candidateIds = [candidate.id, candidate.characterId].filter(
|
||||
(value): value is string => Boolean(value),
|
||||
);
|
||||
return candidateIds.some(id => activeActNpcIdSet.has(id));
|
||||
})
|
||||
.filter(candidate => candidate.characterId !== state.playerCharacter?.id)
|
||||
.filter(candidate => !recruitedNpcIds.has(candidate.id));
|
||||
}
|
||||
|
||||
function getAvailableHostileSceneNpcs(state: GameState) {
|
||||
const recruitedNpcIds = getRecruitedNpcIds(state);
|
||||
|
||||
@@ -142,6 +170,54 @@ function pickEncounterHostileNpcs(hostileNpcs: Array<SceneNpc & { monsterPresetI
|
||||
return hostileNpcs.filter(npc => selectedMonsterIds.has(npc.monsterPresetId));
|
||||
}
|
||||
|
||||
function pickFriendlySceneNpcForActiveAct(state: GameState, npcs: SceneNpc[]) {
|
||||
const focusNpcId = resolveActiveSceneActEncounterFocusNpcId({
|
||||
profile: state.customWorldProfile,
|
||||
sceneId: state.currentScenePreset?.id ?? null,
|
||||
storyEngineMemory: state.storyEngineMemory,
|
||||
});
|
||||
|
||||
return (
|
||||
npcs.find(
|
||||
(npc) =>
|
||||
npc.id === focusNpcId ||
|
||||
(npc.characterId ? npc.characterId === focusNpcId : false),
|
||||
) ?? pickRandomItem(npcs)
|
||||
);
|
||||
}
|
||||
|
||||
function hasActiveSceneActEncounterTarget(state: GameState) {
|
||||
return resolveActiveSceneActEncounterNpcIds({
|
||||
profile: state.customWorldProfile,
|
||||
sceneId: state.currentScenePreset?.id ?? null,
|
||||
storyEngineMemory: state.storyEngineMemory,
|
||||
}).length > 0;
|
||||
}
|
||||
|
||||
function buildEmptyEncounterPreview() {
|
||||
return {
|
||||
sceneHostileNpcs: [],
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
inBattle: false,
|
||||
};
|
||||
}
|
||||
|
||||
function buildActiveSceneActNpcEncounter(
|
||||
state: GameState,
|
||||
availableNpcs: SceneNpc[],
|
||||
xMeters: number,
|
||||
) {
|
||||
const npc = pickFriendlySceneNpcForActiveAct(state, availableNpcs);
|
||||
|
||||
return {
|
||||
sceneHostileNpcs: [],
|
||||
currentEncounter: npc ? buildFriendlyEncounter(npc, xMeters) : null,
|
||||
npcInteractionActive: false,
|
||||
inBattle: false,
|
||||
};
|
||||
}
|
||||
|
||||
function buildHostileEncounterGroup(
|
||||
state: GameState,
|
||||
entryX: number,
|
||||
@@ -218,12 +294,15 @@ function buildResolvedHostileBattleState(state: GameState, hostileEncounters: En
|
||||
|
||||
export function createSceneEncounterPreview(state: GameState) {
|
||||
if (!state.worldType || !state.currentScenePreset) {
|
||||
return {
|
||||
sceneHostileNpcs: [],
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
inBattle: false,
|
||||
};
|
||||
return buildEmptyEncounterPreview();
|
||||
}
|
||||
|
||||
if (hasActiveSceneActEncounterTarget(state)) {
|
||||
return buildActiveSceneActNpcEncounter(
|
||||
state,
|
||||
getAvailableActiveSceneActNpcs(state),
|
||||
PREVIEW_ENTITY_X_METERS,
|
||||
);
|
||||
}
|
||||
|
||||
const availableNpcs = getAvailableFriendlySceneNpcs(state);
|
||||
@@ -237,12 +316,7 @@ export function createSceneEncounterPreview(state: GameState) {
|
||||
|
||||
const kind = pickRandomItem(availableKinds);
|
||||
if (!kind) {
|
||||
return {
|
||||
sceneHostileNpcs: [],
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
inBattle: false,
|
||||
};
|
||||
return buildEmptyEncounterPreview();
|
||||
}
|
||||
|
||||
if (kind === 'hostile') {
|
||||
@@ -255,7 +329,7 @@ export function createSceneEncounterPreview(state: GameState) {
|
||||
}
|
||||
|
||||
if (kind === 'npc') {
|
||||
const npc = pickRandomItem(availableNpcs);
|
||||
const npc = pickFriendlySceneNpcForActiveAct(state, availableNpcs);
|
||||
|
||||
return {
|
||||
sceneHostileNpcs: [],
|
||||
@@ -276,19 +350,22 @@ export function createSceneEncounterPreview(state: GameState) {
|
||||
|
||||
export function createSceneCallOutEncounter(state: GameState) {
|
||||
if (!state.worldType || !state.currentScenePreset) {
|
||||
return {
|
||||
sceneHostileNpcs: [],
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
inBattle: false,
|
||||
};
|
||||
return buildEmptyEncounterPreview();
|
||||
}
|
||||
|
||||
if (hasActiveSceneActEncounterTarget(state)) {
|
||||
return buildActiveSceneActNpcEncounter(
|
||||
state,
|
||||
getAvailableActiveSceneActNpcs(state),
|
||||
CALL_OUT_ENTRY_X_METERS,
|
||||
);
|
||||
}
|
||||
|
||||
const availableNpcs = getAvailableFriendlySceneNpcs(state);
|
||||
const availableKinds: Array<'hostile' | 'npc' | 'treasure'> = [];
|
||||
const availableHostiles = getAvailableHostileSceneNpcs(state);
|
||||
if (availableHostiles.length > 0) availableKinds.push('hostile');
|
||||
|
||||
const availableNpcs = getAvailableFriendlySceneNpcs(state);
|
||||
if (availableNpcs.length > 0) availableKinds.push('npc');
|
||||
if (TREASURE_ENCOUNTERS_ENABLED && (state.currentScenePreset.treasureHints?.length ?? 0) > 0) {
|
||||
availableKinds.push('treasure');
|
||||
@@ -305,7 +382,7 @@ export function createSceneCallOutEncounter(state: GameState) {
|
||||
}
|
||||
|
||||
if (kind === 'npc') {
|
||||
const npc = pickRandomItem(availableNpcs);
|
||||
const npc = pickFriendlySceneNpcForActiveAct(state, availableNpcs);
|
||||
return {
|
||||
sceneHostileNpcs: [],
|
||||
currentEncounter: npc ? buildFriendlyEncounter(npc, CALL_OUT_ENTRY_X_METERS) : null,
|
||||
|
||||
Reference in New Issue
Block a user