import { Column, CreateDateColumn, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; import { Character } from '../../characters/entities/character.entity'; import { HuntEncounter } from '../../hunting/entities/hunt-encounter.entity'; import { MonsterDefinition } from '../../monsters/entities/monster-definition.entity'; import { ActiveStatusEffect, CombatIntent } from '../combat-engine.types'; import { CombatStatus } from '../combat-status.enum'; import type { MonsterAbilities } from '../../monsters/monster-abilities'; export interface CombatCombatantState { attack: number; armor: number; pendingAction?: CombatIntent; statusEffects?: ActiveStatusEffect[]; } export interface CombatMonsterState extends CombatCombatantState { // Snapshotted from MonsterDefinition when the fight starts, so retuning // content mid-fight cannot change the rules of a running combat. abilities: MonsterAbilities; // Snapshotted monster combat state persisted during the fight. activeGuard?: { remainingRounds: number; armorBonus: number }; enraged?: boolean; } export interface CombatPlayerState extends CombatCombatantState { weaponDamage: number; potionsRemaining: number; } @Entity({ name: 'combats' }) // Deliberately not unique: a lost fight frees the encounter to be retried, // which creates a second combat row for the same encounter. @Index('IDX_combats_hunt_encounter', ['huntEncounterId']) export class Combat { @PrimaryGeneratedColumn('uuid', { name: 'id' }) id!: string; @Column({ name: 'character_id', type: 'uuid' }) characterId!: string; @Column({ name: 'hunt_encounter_id', type: 'uuid' }) huntEncounterId!: string; @Column({ name: 'monster_definition_id', type: 'uuid' }) monsterDefinitionId!: string; @Column({ name: 'status', type: 'enum', enum: CombatStatus, enumName: 'combat_status_enum', }) status!: CombatStatus; @Column({ name: 'round', type: 'integer' }) round!: number; @Column({ name: 'player_max_hp', type: 'integer' }) playerMaxHp!: number; @Column({ name: 'player_current_hp', type: 'integer' }) playerCurrentHp!: number; @Column({ name: 'monster_max_hp', type: 'integer' }) monsterMaxHp!: number; @Column({ name: 'monster_current_hp', type: 'integer' }) monsterCurrentHp!: number; @Column({ name: 'player_state', type: 'jsonb' }) playerState!: CombatPlayerState; @Column({ name: 'monster_state', type: 'jsonb' }) monsterState!: CombatMonsterState; @CreateDateColumn({ name: 'created_at', type: 'timestamptz' }) createdAt!: Date; @UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' }) updatedAt!: Date; @Column({ name: 'completed_at', type: 'timestamptz', nullable: true }) completedAt!: Date | null; @ManyToOne(() => Character, { onDelete: 'RESTRICT' }) @JoinColumn({ name: 'character_id' }) character!: Character; @ManyToOne(() => HuntEncounter, { onDelete: 'RESTRICT' }) @JoinColumn({ name: 'hunt_encounter_id' }) huntEncounter!: HuntEncounter; @ManyToOne(() => MonsterDefinition, { onDelete: 'RESTRICT' }) @JoinColumn({ name: 'monster_definition_id' }) monster!: MonsterDefinition; }