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.
232 lines
8.1 KiB
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>
|
|
}
|
|
|