Tracker made in React for keeping track of HP and MP and so on.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

402 lines
11 KiB

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
}