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.
 
 
 
fabula-ultima-react/src/CharacterStatus.tsx

232 lines
8.1 KiB

import { animated } from "@react-spring/web";
import {ReactElement, useMemo} from "react";
import {
evaluateResourceBarStyles,
ResourceBarColors,
ResourceBarStyles
} from "./resource_bar";
import {isDefined} from "./type_check";
import {
SpringyValueInterpolatables,
useSpringyValue
} from "./SpringyValueHook";
import "./CharacterStatus.css";
export enum CharacterHealth {
Full = "Full",
Healthy = "Healthy",
Wounded = "Wounded",
Crisis = "Crisis",
Peril = "Peril",
KO = "KO",
}
export enum CharacterTurnState {
None = "None",
Ready = "Ready",
HighTurns = "HighTurns",
Done = "Done",
CantAct = "CantAct",
KO = "KO",
}
export enum SPType {
UltimaPoints = "Ultima",
FabulaPoints = "Fabula",
}
export interface StatusEffect {
name: string
iconUrl: string
}
export interface Character {
portraitUrl?: string
name?: string
level?: number
hp?: number
maxHp?: number
health?: CharacterHealth
mp?: number
maxMp?: number
ip?: number
maxIp?: number
sp?: number
spType?: SPType
turnsLeft?: number
turnsTotal?: number
canAct?: boolean
statuses?: StatusEffect[]
}
const hpBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
foreground: 'linear-gradient(to bottom, rgb(255, 255, 255, 0.1) 0%, rgb(0, 0, 0, 0) 30% 50%, rgb(0, 0, 0, 0.1) 80%, rgb(0, 0, 0, 0.2) 95%, rgb(0, 0, 0, 0.3) 100%)',
barDirection: "to right",
barColors: ({flashValue}: {flashValue: number}): ResourceBarColors => {
return {
emptiedColor: `rgb(${Math.min(1, Math.max(flashValue, 0)) * 55 + 55}, 55, 55)`,
toEmptyColor: 'rgb(200, 0, 0)',
toFillColor: 'rgb(150, 250, 250)',
filledColor: 'rgb(0, 200, 0)',
}
},
}
const mpBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
foreground: 'linear-gradient(to bottom, rgb(255, 255, 255, 0.1) 0%, rgb(0, 0, 0, 0) 30% 50%, rgb(0, 0, 0, 0.1) 80%, rgb(0, 0, 0, 0.2) 95%, rgb(0, 0, 0, 0.3) 100%)',
barColors: ({flashValue}: {flashValue: number}): ResourceBarColors => {
return {
emptiedColor: `rgb(${Math.min(1, Math.max(flashValue, 0)) * 55 + 55}, 55, 55)`,
toEmptyColor: 'rgb(250, 250, 60)',
toFillColor: 'rgb(150, 250, 250)',
filledColor: 'rgb(0, 150, 255)',
}
},
}
const ipBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
foreground: 'linear-gradient(to bottom, rgb(255, 255, 255, 0.1) 0%, rgb(0, 0, 0, 0) 30% 50%, rgb(0, 0, 0, 0.1) 80%, rgb(0, 0, 0, 0.2) 95%, rgb(0, 0, 0, 0.3) 100%)',
barColors: ({flashValue}: {flashValue: number}): ResourceBarColors => {
return {
emptiedColor: `rgb(${Math.min(1, Math.max(flashValue, 0)) * 55 + 55}, 55, 55)`,
toEmptyColor: 'rgb(160, 55, 195)',
toFillColor: 'rgb(250, 250, 60)',
filledColor: 'rgb(255, 150, 55)',
}
},
}
export function CharacterStatus({character}: {character: Character}): ReactElement {
const {name, level, health} = character
const {hp, maxHp} = character
const {interpolate: hpInterpolate} = useSpringyValue({
current: hp,
max: maxHp,
flash: isDefined(maxHp) && isDefined(hp) && hp * 2 < maxHp && hp > 0,
})
const {hpText, hpBarStyleInterpolated} = useMemo(() => {
if (isDefined(hp) && isDefined(maxHp) && maxHp > 0) {
return {
hpText: hpInterpolate(({recentValue}) => `${Math.round(recentValue)}`),
hpBarStyleInterpolated: evaluateResourceBarStyles(hpBarStyle, hpInterpolate),
}
} else {
return {}
}
}, [hp, maxHp, hpInterpolate])
const {mp, maxMp} = character
const {interpolate: mpInterpolate} = useSpringyValue({
current: mp,
max: maxMp,
flash: false,
})
const {mpText, mpBarStyleInterpolated} = useMemo(() => {
if (isDefined(mp) && isDefined(maxMp) && maxMp > 0) {
return {
mpText: mpInterpolate(({recentValue}) => `${Math.round(recentValue)}`),
mpBarStyleInterpolated: evaluateResourceBarStyles(mpBarStyle, mpInterpolate),
}
} else {
return {}
}
}, [mp, maxMp, mpInterpolate])
const {ip, maxIp} = character
const {interpolate: ipInterpolate} = useSpringyValue({
current: ip,
max: maxIp,
flash: false,
})
const {ipText, ipBarStyleInterpolated} = useMemo(() => {
if (isDefined(ip) && isDefined(maxIp) && maxIp > 0) {
return {
ipText: ipInterpolate(({recentValue}) => `${Math.round(recentValue)}`),
ipBarStyleInterpolated: evaluateResourceBarStyles(ipBarStyle, ipInterpolate),
}
} else {
return {}
}
}, [ip, maxIp, ipInterpolate])
const {sp, spType} = character
const {interpolate: spInterpolate} = useSpringyValue({
current: sp,
flash: isDefined(spType) && isDefined(sp) && sp > 0,
})
const {spText} = useMemo(() => {
if (isDefined(sp) && isDefined(spType)) {
return {
spText: spInterpolate(({recentValue}) => recentValue.toFixed(0))
}
} else {
return {}
}
}, [sp, spType, spInterpolate])
const {turnsLeft, turnsTotal, canAct} = character
const {turnsState, turnsText} = useMemo(() => {
if (isDefined(turnsTotal) && hp === 0 && isDefined(maxHp) && maxHp > 0) {
return {
turnsState: CharacterTurnState.KO,
}
} else if (isDefined(turnsTotal) && (canAct === false || turnsTotal === 0)) {
return {
turnsState: CharacterTurnState.CantAct,
}
} else if (isDefined(turnsTotal) && turnsLeft === 0) {
return {
turnsState: CharacterTurnState.Done,
}
} else if (turnsTotal === 1 && turnsLeft === 1) {
return {
turnsState: CharacterTurnState.Ready,
}
} else if (isDefined(turnsTotal) && turnsTotal > 1 && isDefined(turnsLeft)) {
return {
turnsState: CharacterTurnState.HighTurns,
turnsText: `${turnsLeft}`
}
} else {
return {
turnsState: CharacterTurnState.None
}
}
}, [hp, maxHp, canAct, turnsLeft, turnsTotal])
return <div className="characterStatus">
<div className={"characterPortrait"} />
{isDefined(turnsState) &&
<div className={"characterTurns characterTurns" + turnsState}>{turnsText}</div>}
<div className={"characterHeader"}>
{isDefined(level) &&
<div className="characterLevel">
<span className="characterLevelLabel">Lv</span>
<span className="characterLevelValue">{level}</span>
</div>}
{isDefined(name) &&
<div className={"characterName characterName" + (health ?? "Unknown")}>{name}</div>}
</div>
{isDefined(hpText) &&
<div className={"characterHp"}>
<animated.div className={"characterHpBar"} style={hpBarStyleInterpolated} />
<animated.div className={"characterHpValue"}>{hpText}</animated.div>
</div>}
{isDefined(mpText) &&
<div className={"characterMp"}>
<animated.div className={"characterMpBar"} style={mpBarStyleInterpolated} />
<animated.div className={"characterMpValue"}>{mpText}</animated.div>
</div>}
{isDefined(ipText) &&
<div className={"characterIp"}>
<animated.div className={"characterIpBar"} style={ipBarStyleInterpolated} />
<animated.div className={"characterIpValue"}>{ipText}</animated.div>
</div>}
{isDefined(spText) &&
<animated.div className={"characterSp characterSp" + spType}>
<animated.span className={"characterSpValue characterSpValue" + spType}>
{spText}</animated.span>
</animated.div>}
</div>
}