import {isDefined} from "../types/type_check"; export enum CharacterHealth { Full = "Full", Healthy = "Healthy", Wounded = "Wounded", Crisis = "Crisis", Peril = "Peril", KO = "KO", } export interface CharacterHealthLevel { readonly minPercentage: number|null readonly minInclusive: boolean|null readonly maxPercentage: number|null readonly maxInclusive: boolean|null readonly health: CharacterHealth } export const CharacterHealthLevels= { [CharacterHealth.Full]: { maxPercentage: null, maxInclusive: null, minPercentage: 100, minInclusive: true, health: CharacterHealth.Full, }, [CharacterHealth.Healthy]: { maxPercentage: 100, maxInclusive: false, minPercentage: 90, minInclusive: true, health: CharacterHealth.Healthy, }, [CharacterHealth.Wounded]: { maxPercentage: 90, maxInclusive: false, minPercentage: 50, minInclusive: false, health: CharacterHealth.Wounded, }, [CharacterHealth.Crisis]: { maxPercentage: 50, maxInclusive: true, minPercentage: 10, minInclusive: false, health: CharacterHealth.Crisis, }, [CharacterHealth.Peril]: { maxPercentage: 10, maxInclusive: true, minPercentage: 0, minInclusive: false, health: CharacterHealth.Peril, }, [CharacterHealth.KO]: { maxPercentage: 0, maxInclusive: true, minPercentage: null, minInclusive: null, health: CharacterHealth.KO, }, } as const satisfies {readonly [value in CharacterHealth]: CharacterHealthLevel & {health: value}} export const CharacterHealthLevelList: readonly CharacterHealthLevel[] = Object.values(CharacterHealthLevels) export function healthToFraction(health: CharacterHealth | undefined): number { const {minPercentage, maxPercentage} = CharacterHealthLevels[health ?? CharacterHealth.Wounded] return ((minPercentage ?? maxPercentage ?? 0) + (maxPercentage ?? minPercentage ?? 0)) / 200 } export function healthToBounds(health: CharacterHealth | undefined): string { if (!health) { return "???" } const {minPercentage = null, maxPercentage = null} = CharacterHealthLevels[health] return `${minPercentage ?? ""}${minPercentage !== null && maxPercentage !== null ? "-" : ""}${maxPercentage ?? ""}%` } export function hpToHealth(hp: number | undefined, maxHp: number | undefined): CharacterHealth | undefined { if (!isDefined(hp) || !isDefined(maxHp) || maxHp <= 0) { return } const compareHp = 100 * hp for (const level of CharacterHealthLevelList) { if (level.minPercentage === null || level.minInclusive === null) { return level.health } const compareLevel = maxHp * level.minPercentage ?? 0 if (level.minInclusive ? compareHp >= compareLevel : compareHp > compareLevel) { return level.health } } // Returns undefined by falling off here. // However, won't reach this point; will always return at the last level in the loop } export enum CharacterTurnState { None = "None", Ready = "Ready", HighTurns = "HighTurns", Done = "Done", CantAct = "CantAct", Downed = "Downed", Active = "Active", } export interface CharacterTurnStateData { readonly title: string readonly description: string } export const CharacterTurnStates = { [CharacterTurnState.None]: { title: "None", description: "Does not take turns.", }, [CharacterTurnState.Ready]: { title: "Ready", description: "Has not acted yet this round.", }, [CharacterTurnState.HighTurns]: { title: "Multiple Turns", description: "Has %c% turns left out of %m% turns. Must still alternate with opponents.", }, [CharacterTurnState.Done]: { title: "Done", description: "Has finished acting this round.", }, [CharacterTurnState.CantAct]: { title: "Can't Act", description: "Is currently unable to act.", }, [CharacterTurnState.Downed]: { title: "Downed", description: "Has 0 HP. Is currently down and out of the action and unable to act.", }, [CharacterTurnState.Active]: { title: "Active", description: "Currently taking a turn." } } as const satisfies {readonly [value in CharacterTurnState]: CharacterTurnStateData} export function turnStateToTitle(state: CharacterTurnState): string { return CharacterTurnStates[state].title } export function turnStateToDescription(state: CharacterTurnState): string { return CharacterTurnStates[state].description } export enum SPType { UltimaPoints = "Ultima", FabulaPoints = "Fabula", } export interface SPTypeData { readonly description: string } export const SPTypes = { [SPType.UltimaPoints]: { description: "The number of Ultima Points. Ultima Points can be used to make a getaway, " + "recover MP and clear status effects, or perform special villainy moves.", }, [SPType.FabulaPoints]: { description: "The number of Fabula Points. Fabula Points can be used to buy rerolls by " + "invoking your Traits, boost your rolls by invoking your Bonds, or add elements to the story." } } as const satisfies {readonly [value in SPType]: SPTypeData} export function spTypeToDescription(sp: SPType): string { return SPTypes[sp].description } export interface StatusEffectInstance { readonly id: string readonly count?: number } export enum CharacterSide { Ally = "ally", Enemy = "enemy" } export interface Character { readonly id: string readonly side?: CharacterSide readonly portraitUrl?: string readonly name?: string readonly altName?: string readonly level?: number readonly xp?: number readonly maxXp?: number readonly hp?: number readonly maxHp?: number readonly health?: CharacterHealth readonly koText?: string readonly mp?: number readonly maxMp?: number readonly ip?: number readonly maxIp?: number readonly sp?: number readonly spBank?: number readonly spType?: SPType readonly zp?: number readonly maxZp?: number readonly bp?: number readonly maxBp?: number readonly turnsLeft?: number readonly turnsTotal?: number readonly canAct?: boolean readonly statuses?: readonly StatusEffectInstance[] readonly order?: number readonly privacy?: CharacterPrivacy } interface CharacterPrivacySetting { readonly showCharacter: boolean readonly showHp: boolean readonly showHealth: boolean readonly showMp: boolean readonly showIp: boolean readonly showSp: boolean readonly showZp: boolean readonly showName: boolean readonly showPortrait: boolean readonly showTurns: boolean readonly showStatuses: boolean readonly showLevel: boolean } export enum CharacterPrivacy { Friend = "friend", FullyScannedEnemy = "fully scanned enemy", ScannedEnemy = "scanned enemy", LightlyScannedEnemy = "lightly scanned enemy", UnscannedEnemy = "unscanned enemy", SecretiveEnemy = "secretive", Hidden = "hidden", } export const CharacterPrivacySettings = { [CharacterPrivacy.Friend]: { showCharacter: true, showHp: true, showHealth: true, showMp: true, showIp: true, showSp: true, showZp: true, showName: true, showPortrait: true, showTurns: true, showStatuses: true, showLevel: true, }, [CharacterPrivacy.FullyScannedEnemy]: { showCharacter: true, showHp: true, showHealth: true, showMp: true, showIp: false, showSp: true, showZp: true, showName: true, showPortrait: true, showTurns: true, showStatuses: true, showLevel: true, }, [CharacterPrivacy.ScannedEnemy]: { showCharacter: true, showHp: true, showHealth: true, showMp: false, showIp: false, showSp: true, showZp: true, showName: true, showPortrait: true, showTurns: true, showStatuses: true, showLevel: true, }, [CharacterPrivacy.LightlyScannedEnemy]: { showCharacter: true, showHp: true, showHealth: true, showMp: false, showIp: false, showSp: true, showZp: true, showName: true, showPortrait: true, showTurns: true, showStatuses: true, showLevel: true, }, [CharacterPrivacy.UnscannedEnemy]: { showCharacter: true, showHp: false, showHealth: true, showMp: false, showIp: false, showSp: true, showZp: true, showName: true, showPortrait: true, showTurns: true, showStatuses: true, showLevel: false, }, [CharacterPrivacy.SecretiveEnemy]: { showCharacter: true, showHp: false, showHealth: true, showMp: false, showIp: false, showSp: false, showZp: false, showName: false, showPortrait: false, showTurns: true, showStatuses: true, showLevel: false, }, [CharacterPrivacy.Hidden]: { showCharacter: false, showHp: false, showHealth: false, showMp: false, showIp: false, showSp: false, showZp: false, showName: false, showPortrait: false, showTurns: false, showStatuses: false, showLevel: false, } } as const satisfies {readonly [value in CharacterPrivacy]: CharacterPrivacySetting} export function applyCharacterPrivacy(character: Character): Character|null { const privacySettings = CharacterPrivacySettings[character.privacy ?? CharacterPrivacy.Hidden] if (!privacySettings.showCharacter) { return null } const out: {-readonly [Field in keyof Character]: Character[Field]} = Object.assign({}, character) delete out.privacy if (!privacySettings.showHp) { delete out.hp delete out.maxHp if (!privacySettings.showHealth) { delete out.health } } if (!privacySettings.showMp) { delete out.mp delete out.maxMp } if (!privacySettings.showIp) { delete out.ip delete out.maxIp } if (!privacySettings.showSp) { delete out.sp delete out.spBank delete out.spType } if (!privacySettings.showZp) { delete out.zp delete out.maxZp delete out.bp delete out.maxBp } if (!privacySettings.showName) { if (isDefined(out.altName)) { out.name = out.altName delete out.altName } else { delete out.name } } if (!privacySettings.showPortrait) { delete out.portraitUrl } if (!privacySettings.showTurns) { delete out.turnsLeft delete out.turnsTotal delete out.canAct } if (!privacySettings.showStatuses) { delete out.statuses } if (!privacySettings.showLevel) { delete out.level delete out.xp delete out.maxXp } return out }