Download universe/MissionSystem.ts from Snapkitty/grisp-shadow-fleet: direct link, hf CLI and curl.
- Browser
- Download file 7.76 kB
-
https://huggingface.co/Snapkitty/grisp-shadow-fleet/resolve/main/universe/MissionSystem.ts
- Command line
-
hf download hf://Snapkitty/grisp-shadow-fleet/universe/MissionSystem.ts
-
curl -L -o MissionSystem.ts https://huggingface.co/Snapkitty/grisp-shadow-fleet/resolve/main/universe/MissionSystem.ts
7.76 kB
| import { clamp } from './deterministic.js'; | |
| import { emitWorldEvent, findRequired } from './state-utils.js'; | |
| import type { CommandResult, EntityId, MissionObjectiveState, MissionState, UniverseState, WorldEventState } from './types.js'; | |
| export class MissionSystem { | |
| private lastProcessedEventIndex: number; | |
| constructor(private state: UniverseState) { | |
| this.lastProcessedEventIndex = state.events.length; | |
| } | |
| replaceState(state: UniverseState): void { | |
| this.state = state; | |
| this.lastProcessedEventIndex = state.events.length; | |
| } | |
| accept(missionId: EntityId): CommandResult { | |
| const mission = findRequired(this.state.missions, missionId, 'Mission'); | |
| if (mission.status !== 'available') return this.failure(`${mission.title} is not available.`); | |
| mission.status = 'active'; | |
| mission.acceptedTick = this.state.tick; | |
| if (!this.state.player.activeMissionIds.includes(mission.id)) this.state.player.activeMissionIds.push(mission.id); | |
| const event = emitWorldEvent(this.state, 'mission-accepted', this.state.player.id, mission.id, `Mission accepted: ${mission.title}.`); | |
| return { ok: true, message: event.summary, eventIds: [event.id] }; | |
| } | |
| addProgress(missionId: EntityId, objectiveId: EntityId, amount = 1): CommandResult { | |
| const mission = findRequired(this.state.missions, missionId, 'Mission'); | |
| if (mission.status !== 'active') return this.failure(`${mission.title} is not active.`); | |
| const objective = findRequired(mission.objectives, objectiveId, 'Mission objective'); | |
| this.progressObjective(objective, amount); | |
| const events: string[] = []; | |
| if (mission.objectives.every((item) => item.completed)) events.push(this.complete(mission).id); | |
| return { ok: true, message: `${objective.description}: ${objective.currentAmount}/${objective.requiredAmount}`, eventIds: events }; | |
| } | |
| tick(): void { | |
| for (const mission of this.state.missions) { | |
| if (mission.status === 'locked' && this.triggered(mission)) { | |
| mission.status = 'available'; | |
| emitWorldEvent(this.state, 'mission-available', mission.issuerId, mission.id, `New mission available: ${mission.title}.`, { persistent: true }); | |
| } | |
| if (mission.status === 'active' && mission.expiresTick !== null && this.state.tick >= mission.expiresTick) { | |
| mission.status = 'failed'; | |
| this.state.player.activeMissionIds = this.state.player.activeMissionIds.filter((id) => id !== mission.id); | |
| emitWorldEvent(this.state, 'mission-failed', this.state.player.id, mission.id, `Mission expired: ${mission.title}.`, { severity: 0.5 }); | |
| } | |
| } | |
| const newEvents = this.state.events.slice(this.lastProcessedEventIndex); | |
| this.lastProcessedEventIndex = this.state.events.length; | |
| for (const event of newEvents) this.applyEventProgress(event); | |
| } | |
| private triggered(mission: MissionState): boolean { | |
| const trigger = mission.trigger; | |
| if (trigger.kind === 'always') return true; | |
| if (trigger.kind === 'traffic-distress') return this.state.traffic.some((traffic) => traffic.id === trigger.targetId && traffic.status === 'distress'); | |
| if (trigger.kind === 'shortage' && trigger.targetId) { | |
| return this.state.economy.markets.some((market) => { | |
| const listing = market.listings.find((candidate) => candidate.commodityId === trigger.targetId); | |
| return Boolean(listing && (listing.shortage || listing.inventory <= trigger.threshold)); | |
| }); | |
| } | |
| if (trigger.kind === 'damage' && trigger.targetId) { | |
| const hull = this.state.hullSections.find((candidate) => candidate.id === trigger.targetId); | |
| const room = this.state.rooms.find((candidate) => candidate.id === trigger.targetId); | |
| return (hull?.hazardLevel ?? room?.damage ?? 0) >= trigger.threshold; | |
| } | |
| if (trigger.kind === 'discovery' && trigger.targetId) { | |
| const discovery = this.state.discoveries.find((candidate) => candidate.id === trigger.targetId); | |
| return Boolean(discovery && !discovery.discovered); | |
| } | |
| if (trigger.kind === 'relationship' && trigger.targetId) return (this.state.player.factionReputation[trigger.targetId] ?? 0) <= trigger.threshold; | |
| if (trigger.kind === 'construction' && trigger.targetId) return this.state.construction.projects.some((project) => project.id === trigger.targetId && project.status !== 'completed'); | |
| if (trigger.kind === 'conflict' && trigger.targetId) { | |
| const targetId = trigger.targetId; | |
| return this.state.factions.some((faction) => faction.activeConflictIds.includes(targetId)); | |
| } | |
| return false; | |
| } | |
| private applyEventProgress(event: WorldEventState): void { | |
| for (const mission of this.state.missions.filter((candidate) => candidate.status === 'active')) { | |
| for (const objective of mission.objectives.filter((candidate) => !candidate.completed)) { | |
| if (!this.eventMatchesObjective(event, objective)) continue; | |
| const eventAmount = typeof event.data.quantity === 'number' ? event.data.quantity : 1; | |
| this.progressObjective(objective, eventAmount); | |
| } | |
| if (mission.objectives.every((objective) => objective.completed)) this.complete(mission); | |
| } | |
| } | |
| private eventMatchesObjective(event: WorldEventState, objective: MissionObjectiveState): boolean { | |
| if (objective.kind === 'interact') return event.type === 'interaction-used' && event.targetId === objective.targetId; | |
| if (objective.kind === 'repair') return event.type.includes('repaired') && (event.targetId === objective.targetId || event.data.repairNodeId === objective.targetId); | |
| if (objective.kind === 'rescue') return event.type === 'traffic-rescued' && event.targetId === objective.targetId; | |
| if (objective.kind === 'scan') return (event.type === 'location-discovered' || event.type === 'agent-task-completed') && event.targetId === objective.targetId; | |
| if (objective.kind === 'visit') return (event.type === 'location-discovered' || event.type === 'ship-jump-completed') && event.targetId === objective.targetId; | |
| if (objective.kind === 'deliver') return event.type === 'cargo-delivered' && event.targetId === objective.targetId; | |
| if (objective.kind === 'trade') return event.type === 'market-trade' && event.targetId === objective.targetId && event.data.side === 'sell'; | |
| if (objective.kind === 'construct') return event.type === 'construction-completed' && event.targetId === objective.targetId; | |
| if (objective.kind === 'defend') return event.type === 'patrol-completed' && event.targetId === objective.targetId; | |
| if (objective.kind === 'negotiate') return event.type === 'faction-agreement' && event.targetId === objective.targetId; | |
| return false; | |
| } | |
| private progressObjective(objective: MissionObjectiveState, amount: number): void { | |
| objective.currentAmount = clamp(objective.currentAmount + Math.max(0, amount), 0, objective.requiredAmount); | |
| objective.completed = objective.currentAmount >= objective.requiredAmount; | |
| } | |
| private complete(mission: MissionState): WorldEventState { | |
| mission.status = 'completed'; | |
| mission.completedTick = this.state.tick; | |
| this.state.player.activeMissionIds = this.state.player.activeMissionIds.filter((id) => id !== mission.id); | |
| this.state.player.credits += mission.rewardCredits; | |
| this.state.player.factionReputation[mission.factionId] = clamp((this.state.player.factionReputation[mission.factionId] ?? 0) + mission.reputationReward, -100, 100); | |
| return emitWorldEvent(this.state, 'mission-completed', this.state.player.id, mission.id, `Mission completed: ${mission.title}.`, { data: { rewardCredits: mission.rewardCredits, reputationReward: mission.reputationReward } }); | |
| } | |
| private failure(message: string): CommandResult { | |
| return { ok: false, message, eventIds: [] }; | |
| } | |
| } | |