UNPKG

roleplayer

Version:

A library for building table top role playing game worlds, and managing campaigns in those worlds

135 lines (114 loc) 4.32 kB
import { CharacterEventTypes, mapEffect, SystemEventType, type ActionDefinition, type CampaignEvent, type RoleplayerEvent, type Ruleset, } from "../.."; import type { Id } from "../../lib/generate-id"; import type { WithRequired } from "../../types/with-required"; import type { Actor, Position } from "../actor/character"; import type { Roleplayer } from "../roleplayer"; export class Battle { id!: Id; name!: string; roleplayer!: Roleplayer; ruleset: Ruleset; actors: Actor[] = []; actorToAct: Actor | undefined; actorsThatHaveActed: Actor[] = []; unsubscribe: (() => void) | undefined; constructor(b: WithRequired<Partial<Omit<Battle, "ruleset">>, "name" | "id" | "roleplayer">) { Object.assign(this, b); this.ruleset = b.roleplayer.ruleset; this.unsubscribe = b.roleplayer.subscribe(this.reduce.bind(this)); } isBattleOver() { const aliveCharacters = this.actors.filter((actor) => this.ruleset.characterIsDead(actor)); const oneCharacterRemaining = aliveCharacters.length === 1; if (oneCharacterRemaining) { return true; } if (aliveCharacters.length === 0) { return false; } const party = aliveCharacters[0]!.party; const alliedPartiesRemaining = aliveCharacters.every((actor) => actor.party === party && party !== undefined); return alliedPartiesRemaining; } addBattleActor(actor: Actor) { this.actors.push(actor); } getActingOrder(): Actor[] { return this.ruleset.getActingOrder(this.actors); } removeBattleActor(actor: Actor) { this.actors = this.actors.filter((a) => a.id !== actor.id); } calculateTargetsCone(position: Position, range: number, radius: number, angle: number) { return [] as Actor[]; } calculateTargetsLine(position: Position, range: number, radius: number, angle: number) { return [] as Actor[]; } calculateTargetsCircle(position: Position, range: number, radius: number, angle: number) { return [] as Actor[]; } performAction(actor: Actor, actionDef: ActionDefinition, targets: Actor[]) { const events: Array<CampaignEvent> = []; for (const target of targets) { const didHit = this.ruleset.characterHit(actor, actionDef, target); if (!didHit) continue; for (const effect of actionDef.appliesEffects) { events.push(mapEffect(effect, actionDef, actor, target, this.ruleset)); } } for (const requiredResource of actionDef.requiresResources) { const availableResource = actor.resources.find((r) => r.resourceTypeId === requiredResource.resourceTypeId); if (requiredResource.amount > availableResource!.amount) throw new Error("Not enough resources"); events.push({ type: CharacterEventTypes.CharacterResourceLoss, amount: requiredResource.amount, characterId: actor.id, // Target resourceTypeId: requiredResource.resourceTypeId, actionId: actionDef.id, sourceId: actor.id, // Actor }); } this.roleplayer.dispatchEvents(...events); // 1. Check hit // 2. Apply effects - return true; } reduce(event: RoleplayerEvent) { if (!("battleId" in event) || event.battleId !== this.id) return; switch (event.type) { case SystemEventType.CharacterBattleEnter: { const character = this.roleplayer.campaign.characters.find((c) => c.id === event.characterId); if (!character) throw new Error(`Cannot find character with id: ${event.characterId}`); this.addBattleActor(character); break; } case SystemEventType.CharacterBattleLeave: { const character = this.roleplayer.campaign.characters.find((c) => c.id === event.characterId); if (!character) throw new Error(`Cannot find character with id: ${event.characterId}`); this.removeBattleActor(character); break; } case CharacterEventTypes.CharacterEndTurn: { const characterBattle = this.actors.find((actor) => actor.id === event.characterId); if (!characterBattle) { throw new Error("Cannot find battle character"); } this.actorsThatHaveActed.push(characterBattle); this.actorToAct = this.ruleset.getCurrentActorTurn(this); break; } } } dispose() { this.unsubscribe?.(); } }