daughter friendship

main
Mari 7 months ago
parent 84f70f86d8
commit 8ae259951b
  1. 15
      public/api/current.json
  2. 20
      src/grammar/grammar.ohm
  3. 63
      src/grammar/interpreter.ts
  4. 9
      src/model/Character.ts
  5. 25
      src/ui/App.tsx
  6. 77
      src/ui/CharacterStatus.css
  7. 236
      src/ui/CharacterStatus.tsx

@ -35,7 +35,7 @@
{
"id": "flow",
"side": "ally",
"minion": true,
"leader": "aelica",
"name": "Flow",
"portraitUrl": "/portraits/flow.png",
"statuses": []
@ -64,8 +64,9 @@
{
"id": "galvelle",
"side": "ally",
"minion": true,
"leader": "athetyz",
"name": "Galvelle",
"portraitUrl": "/portraits/galvelle.png",
"statuses": []
},
{
@ -92,10 +93,10 @@
{
"id": "gale",
"side": "ally",
"minion": true,
"leader": "echo",
"name": "Gale",
"level": 5,
"hp": 70,
"hp": 0,
"maxHp": 70,
"mp": 58,
"maxMp": 58,
@ -128,7 +129,7 @@
{
"id": "calor",
"side": "ally",
"minion": true,
"leader": "gravitas",
"name": "Calor",
"portraitUrl": "/portraits/calor.png",
"statuses": []
@ -157,7 +158,7 @@
{
"id": "terra",
"side": "ally",
"minion": true,
"leader": "linnet",
"name": "Terra",
"portraitUrl": "/portraits/terra.png",
"statuses": []
@ -187,7 +188,7 @@
{
"id": "selia",
"side": "ally",
"minion": true,
"leader": "prandia",
"name": "Sélia",
"portraitUrl": "/portraits/selia.png",
"statuses": []

@ -191,21 +191,25 @@ FabulaDSL {
StatusOrItemCounter = StatusOrItemCounterWrapped | StatusOrItemCounterUnwrapped
StatusOrItemDeltaWrapped = "(" Delta ")"
StatusOrItemDelta = StatusOrItemDeltaWrapped | Delta
StatusSeparator = colon colon
// StatusOrItemAddOperation: operands, identifier, numberValue, evaluate, renderMarkdown
StatusOrItemAddOperation = Operands colon plus identifier StatusOrItemCounter?
StatusOrItemAddOperation = Operands StatusSeparator plus identifier StatusOrItemCounter?
// TODO: StatusOrItemRemoveOperation: operands, identifier, evaluate, renderMarkdown
StatusOrItemRemoveOperation = Operands colon minus identifier
StatusOrItemRemoveOperation = Operands StatusSeparator minus identifier
StatusOrItemRemoveOperationAlternate = Operands #wordSep identifier StatusSeparator null
// TODO: StatusOrItemDeltaOperation/Alternate: operands, identifier, numberValue, evaluate, renderMarkdown
StatusOrItemCounterDeltaOperation = Operands colon identifier StatusOrItemDelta
StatusOrItemCounterDeltaOperationAlternate = Operands colon StatusOrItemDelta identifier
StatusOrItemCounterDeltaOperation = Operands #wordSep identifier StatusSeparator StatusOrItemDelta
StatusOrItemCounterDeltaOperationAlternate = Operands StatusSeparator StatusOrItemDelta identifier
StatusOrItemCounterDeltaOperationAlternate2 = Operands StatusSeparator identifier StatusOrItemDelta
// TODO: Fix conflict with resource mutators (use brackets?)
// TODO: StatusOrItemSetOperation/Alternate: operands, identifier, numberValue, evaluate, renderMarkdown
StatusOrItemCounterSetOperation = Operands colon identifier StatusOrItemCounter?
StatusOrItemCounterSetOperationAlternate = Operands colon StatusOrItemCounter identifier
StatusOrItemCounterSetOperation = Operands #wordSep identifier StatusSeparator StatusOrItemCounter?
StatusOrItemCounterSetOperationAlternate = Operands StatusSeparator StatusOrItemCounter identifier
StatusOrItemCounterSetOperationAlternate2 = Operands StatusSeparator identifier StatusOrItemCounter?
// TODO: continue implementation list from here
@ -312,8 +316,8 @@ FabulaDSL {
PrintOperationWithOperands = Operands colon printOperator textToEndOfLine
Operation = StatusOrItemAddOperation | StatusOrItemRemoveOperation
| StatusOrItemCounterDeltaOperation | StatusOrItemCounterDeltaOperationAlternate
| StatusOrItemCounterSetOperation | StatusOrItemCounterSetOperationAlternate
| StatusOrItemCounterDeltaOperation | StatusOrItemCounterDeltaOperationAlternate | StatusOrItemCounterDeltaOperationAlternate2
| StatusOrItemCounterSetOperation | StatusOrItemCounterSetOperationAlternate | StatusOrItemCounterSetOperationAlternate2
| DeltaOperation | DeltaOperationAlternate | DeltaOperationAlternate2
| SetMeteredOperation | SetMeteredOperationAlternate | SetMeteredOperationAlternate2
| SetValueOperation | SetValueOperationAlternate | SetValueOperationAlternate2

@ -203,9 +203,6 @@ interpreter.addAttribute<NumberSign>("sign", {
minus(_: TerminalNode): NumberSign.Negative {
return NumberSign.Negative
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): NumberSign {
return (delta as InterpreterNode).sign
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's number sign is`)
},
@ -282,10 +279,16 @@ interpreter.addAttribute<number>("numberValue", {
}
return (counter.child(0)).numberValue
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): number {
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, delta: NonterminalNode): number {
return (delta as InterpreterNode).numberValue
},
StatusOrItemCounterDeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, _identifier: NonterminalNode): number {
return (delta as InterpreterNode).numberValue
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
StatusOrItemCounterDeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): number {
return (delta as InterpreterNode).numberValue
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, counter: NonterminalNode): number {
return (counter as InterpreterNode).numberValue
},
_iter(): never {
@ -324,10 +327,16 @@ interpreter.addAttribute<string>("identifier", {
StatusOrItemRemoveOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, identifier: NonterminalNode): string {
return (identifier as InterpreterNode).identifier;
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, _delta: NonterminalNode): string {
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, _delta: NonterminalNode): string {
return (identifier as InterpreterNode).identifier
},
StatusOrItemCounterDeltaOperationAlternate(operands: NonterminalNode, _sep: NonterminalNode, _delta: NonterminalNode, identifier: NonterminalNode): string {
return (identifier as InterpreterNode).identifier
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, _counter: NonterminalNode): string {
StatusOrItemCounterDeltaOperationAlternate2(operands: NonterminalNode, _sep: NonterminalNode, identifier: NonterminalNode, _delta: NonterminalNode): string {
return (identifier as InterpreterNode).identifier
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, _delta: NonterminalNode): string {
return (identifier as InterpreterNode).identifier
},
_iter(): never {
@ -602,10 +611,16 @@ interpreter.addAttribute<Operands>("operands", {
SetValueOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _value: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, _colon: NonterminalNode, _statusOrItem: NonterminalNode, _delta: NonterminalNode): Operands {
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, _wordSep: NonterminalNode, _statusOrItem: NonterminalNode, _sep: NonterminalNode, _delta: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
StatusOrItemCounterDeltaOperationAlternate(operands: NonterminalNode, _sep: NonterminalNode, _delta: NonterminalNode, _statusOrItem: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, _colon: NonterminalNode, _statusOrItem: NonterminalNode, _value: NonterminalNode): Operands {
StatusOrItemCounterDeltaOperationAlternate2(operands: NonterminalNode, _sep: NonterminalNode, _statusOrItem: NonterminalNode, _delta: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, _wordSep: NonterminalNode, _statusOrItem: NonterminalNode, _sep: NonterminalNode, _value: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
StatusOrItemAddOperation(operands: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _statusOrItem: NonterminalNode, _counter: IterationNode): Operands {
@ -737,10 +752,10 @@ interpreter.addAttribute<number>("currentValue", {
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): number {
return (value as InterpreterNode).currentValue;
},
StatusOrItemAddOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
StatusOrItemAddOperation(operands: NonterminalNode, sep: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
return (counter as InterpreterNode).numberValue
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
StatusOrItemCounterSetOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, counter: NonterminalNode): number {
return (counter as InterpreterNode).numberValue
},
_iter(): never {
@ -996,6 +1011,22 @@ function EvaluateClear(node: EvaluationNode): EvaluationContext {
}
}
function EvaluateStatusCounterDelta(node: EvaluationNode): EvaluationContext {
const ctx = node.args.ctx
const operands = evaluateOperands(node.operands, ctx)
const statusOrItemId = node.identifier
const stacks = node.numberValue
return {
...ctx,
game: {
...ctx.game,
characters: ctx.game.characters.map((c) =>
operands.has(c.id) ? CharacterStatuses.applyStatusDelta(c, statusOrItemId, stacks) : c),
}
}
}
interpreter.addOperation<EvaluationContext>("evaluate(ctx)", {
CodeSegment(items: IterationNode): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
@ -1094,6 +1125,16 @@ interpreter.addOperation<EvaluationContext>("evaluate(ctx)", {
}
}
},
StatusOrItemCounterDeltaOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
return EvaluateStatusCounterDelta(this as EvaluationNode)
},
StatusOrItemCounterDeltaOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateStatusCounterDelta(this as EvaluationNode)
},
StatusOrItemCounterDeltaOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateStatusCounterDelta(this as EvaluationNode)
},
ClearOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
return EvaluateClear(this as EvaluationNode);
},

@ -200,7 +200,7 @@ export enum CharacterSide {
export interface Character {
readonly id: string
readonly side?: CharacterSide
readonly minion?: boolean
readonly leader?: string
readonly portraitUrl?: string
readonly name?: string
readonly altName?: string
@ -474,6 +474,13 @@ export const CharacterResources: ResourceManipulator<Character, Resource> = {
return result
}
},
clear(object: Character, resource: Resource): Character {
const result: {-readonly [key in keyof Character]: Character[key]} = baseCharacterResource.clear(object, resource)
if (resource === MeteredResource.Health) {
delete result.health
}
return result
}
}
export function applyCharacterPrivacy(character: Character): Character|null {

@ -41,7 +41,7 @@ function App() {
@: ZP 5/6
@: ZP +1
&: 20 HP
gale, calor,gravitas HP: 3
calor,gravitas HP: 3
&: ZP 5
&: ZP /7
&: /99 IP
@ -50,12 +50,13 @@ function App() {
athetyz: - MP
linnet: HP -
&: Lv -
gale, echo, aelica: +Digesting
gale: -Digesting
flow: +Digesting 2
echo, flow: +Digesting 3
calor, flow, echo: Digesting +1
gale, echo: +1 Digesting
gale, echo, aelica:: +Digesting
flow:: +Digesting 2
echo, flow:: +Digesting 3
gale:: -Digesting
calor Digesting:: +1
flow, gale:: Digesting +1
echo, flow, gale:: -1 Digesting
End`)
}
} catch (ex) {
@ -107,23 +108,25 @@ function App() {
<Col xs={{span: true}} xxl={{span: true, order: "first"}}>
<Stack direction="vertical" className={"align-items-center"}>
<h1 className={"ally-head " + (state?.conflict?.activeSide === CharacterSide.Ally ? "active" : state?.conflict?.activeSide === CharacterSide.Enemy ? "inactive" : "")}>Allies</h1>
{state && state.characters.filter((character) => character.side === CharacterSide.Ally).map((character) =>
{state && state.characters.filter((character) => !character.leader && character.side === CharacterSide.Ally).map((character) =>
<CharacterStatus
key={character.id}
character={character}
minions={state.characters.filter((minion) => minion.leader === character.id)}
statuses={state.statuses}
active={character.id === state.conflict?.activeCharacterId} />)}
activeId={state.conflict?.activeCharacterId ?? ""} />)}
</Stack>
</Col>
<Col xs={{span: true}} xxl={{span: true, order: "last"}}>
<Stack direction="vertical" className={"align-items-center"}>
<h1 className={"enemy-head " + (state?.conflict?.activeSide === CharacterSide.Enemy ? "active" : state?.conflict?.activeSide === CharacterSide.Ally ? "inactive" : "")}>Enemies</h1>
{state && state.characters.filter((character) => character.side === CharacterSide.Enemy).map((character) =>
{state && state.characters.filter((character) => !character.leader && character.side === CharacterSide.Enemy).map((character) =>
<CharacterStatus
key={character.id}
character={character}
minions={state.characters.filter((minion) => minion.leader === character.id)}
statuses={state.statuses}
active={character.id === state.conflict?.activeCharacterId} />)}
activeId={state.conflict?.activeCharacterId ?? ""} />)}
</Stack>
</Col>
</Row>

@ -1,13 +1,49 @@
.characterStatus {
height: 7.5em;
width: 27.25em;
width: 30.25em;
position: relative;
box-sizing: content-box;
}
.characterStatus.minion {
margin-left: 6.8125em;
}
.characterStatus.minion.collapsed {
font-size: 0.80em;
height: 6.25em;
width: 6.25em;
margin-left: 0;
}
.characterStatus.minion.collapsed .characterPortrait {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.characterStatus.minion.collapsed .characterKOBar {
left: 0;
bottom: 1.406em;
}
.characterStatus.minion.collapsed .characterHeader,
.characterStatus.minion.collapsed .characterTurns,
.characterStatus.minion.collapsed .characterSp,
.characterStatus.minion.collapsed .characterBp,
.characterStatus.minion.collapsed .characterHp,
.characterStatus.minion.collapsed .characterMp,
.characterStatus.minion.collapsed .characterIp,
.characterStatus.minion.collapsed .characterZeroGauge,
.characterStatus.minion.collapsed .characterStatuses {
display: none;
}
.characterStatus .characterStatus.minion {
position: absolute;
bottom: 1.5em;
left: 5.625em;
}
.characterHeader {
@ -17,6 +53,10 @@
z-index: 4;
}
.characterStatus/*.leader*/ .characterHeader {
left: 13.25em;
}
.characterLevel {
display: inline;
color: white;
@ -44,6 +84,10 @@
user-select: none;
}
.characterName .minion {
font-size: 0.75em;
}
.characterHpBar, .characterMpBar, .characterIpBar {
box-shadow: 0.1em 0.1em 0.1em rgba(0, 0, 0, 0.5);
border: 0.1em solid black;
@ -79,6 +123,10 @@
box-sizing: content-box;
}
.characterStatus/*.leader*/ .characterHp {
left: 10.85em;
}
.characterHpBar {
height: 1.25em;
}
@ -111,6 +159,10 @@
z-index: 3;
}
.characterStatus/*.leader*/ .characterMp {
left: 10em;
}
.characterIp {
width: 7.5em;
right: 1em;
@ -141,23 +193,28 @@
z-index: 0;
}
.characterStatus/*.leader*/ .characterPortrait {
left: 7.5em;
}
.characterZeroGauge {
position: absolute;
top: 0.25em;
bottom: 0.5em;
left: 2.5em;
width: 3.75em;
width: 2.5em;
}
.characterZeroBar, .characterZeroBarBack, .characterZeroBarPulse{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
right: -1.25em;
background-repeat: no-repeat;
background-position: left bottom;
background-size: auto 6.75em;
z-index: 0;
pointer-events: none;
}
.characterZeroBarBack {
@ -197,8 +254,8 @@
.characterKOBar {
position: absolute;
top: 1.592em;
bottom: 2.518em;
height: 1.5em;
bottom: 1.962em;
left: 3.333em;
width: 4.629em;
background-color: black;
@ -213,6 +270,10 @@
border-radius: 0.555em 0;
}
.characterStatus/*.leader*/ .characterKOBar {
left: 5.555em;
}
.characterTurns {
position: absolute;
top: 0.125em;
@ -311,6 +372,10 @@
overflow-x: auto;
}
.characterStatus/*.leader*/ .characterStatuses {
left: 14.25em;
}
.characterStatusIcon {
position: relative;
flex: 0 0 1.8em;

@ -1,5 +1,5 @@
import {animated, useSpring} from "@react-spring/web";
import {ReactElement, useMemo} from "react";
import {ReactElement, useCallback, useMemo, useState} from "react";
import {evaluateResourceBarStyles, ResourceBarColors, ResourceBarStyles} from "./ResourceBarGradient";
import {isDefined} from "../types/type_check";
import {SpringyValueInterpolatables, useSpringyValue} from "./SpringyValueHook";
@ -19,6 +19,7 @@ import {
turnStateToTitle
} from "../model/Character";
import {StatusEffect} from "../model/GameState";
import React from "react";
export function healthToColor(health: CharacterHealth | undefined): string {
switch (health) {
@ -75,8 +76,14 @@ const ipBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
},
}
export function CharacterStatus({character, statuses, active}: {character: Character, statuses: readonly StatusEffect[], active: boolean}): ReactElement {
const {name, altName, minion, level, health, koText} = character
export function CharacterStatus({character, minions, statuses, activeId, collapsed, click}: {character: Character, minions: readonly Character[], statuses: readonly StatusEffect[], activeId: string, collapsed?: boolean, click?: () => void}): ReactElement {
const [showMinions, setShowMinions] = useState<boolean>(false)
const toggleMinions = useCallback(() => {
setShowMinions(!showMinions)
}, [showMinions, setShowMinions])
const {id, name, altName, leader, level, health, koText} = character
const {hp, maxHp} = character
const effectiveMaxHp = maxHp ?? 100
@ -199,7 +206,7 @@ export function CharacterStatus({character, statuses, active}: {character: Chara
return {
turnsState: CharacterTurnState.None
}
} else if (active) {
} else if (activeId === id) {
return {
turnsState: CharacterTurnState.Active,
turnsText: "🞂",
@ -228,7 +235,7 @@ export function CharacterStatus({character, statuses, active}: {character: Chara
turnsState: CharacterTurnState.Ready,
}
}
}, [active, effectiveHp, canAct, turnsLeft, turnsTotal])
}, [activeId, id, effectiveHp, canAct, turnsLeft, turnsTotal])
const {portraitUrl} = character
const effectivePortraitUrl = portraitUrl ?? DefaultPortrait
@ -308,120 +315,127 @@ export function CharacterStatus({character, statuses, active}: {character: Chara
</Tooltip>
return <div className={"characterStatus" + (minion ? " minion" : "")}>
<animated.div className={"characterPortrait"} style={characterPortraitStyleInterpolated} />
{isDefined(maxZp) && isDefined(zp) && <OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>Zero Charge</span>
<span className={"characterHelpValue"}>{(100 * zp / maxZp).toFixed(0)}% - {zp}/{maxZp}</span></div>
{<div className={"characterHelpDescription"}>
The amount of energy stored up towards unleashing the might of a Zero Power. When the gauge is full, let loose!
</div>}
</Tooltip>
} placement={"right"}>
<div className={"characterZeroGauge"}>
<div className={"characterZeroBarBack"} />
<animated.div className={"characterZeroBar"} style={zpStyle} />
<div className={"characterZeroBarPulse" + (zp >= maxZp ? " active" : "")} />
</div>
</OverlayTrigger>}
{isDefined(effectiveMaxHp) && effectiveHp < 1 && effectiveMaxHp > 0 && <animated.div className={"characterKOBar"} style={characterKOBarStyleInterpolated}>{koText ?? "KO"}</animated.div>}
{isDefined(turnsState) &&
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
return <React.Fragment>
<div className={"characterStatus" + (minions.length > 0 && !showMinions ? " leader" : leader ? " minion" : "") + (collapsed ? " collapsed" : "")} onClick={() => click ? click() : null}>
{minions.length > 0 && !showMinions && <CharacterStatus character={minions[0]} minions={[]} statuses={statuses} activeId={activeId} collapsed={true} click={toggleMinions} />}
<animated.div className={"characterPortrait"} style={characterPortraitStyleInterpolated} />
{isDefined(maxZp) && isDefined(zp) && <OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{turnStateToTitle(turnsState)}</span>
{isDefined(turnsTotal) && isDefined(turnsLeft) && turnsTotal > 1 && <span className={"characterHelpValue"}>{turnsLeft}/{turnsTotal}</span>}</div>
<span className={"characterHelpName"}>Zero Charge</span>
<span className={"characterHelpValue"}>{(100 * zp / maxZp).toFixed(0)}% - {zp}/{maxZp}</span></div>
{<div className={"characterHelpDescription"}>
{isDefined(turnsTotal) && isDefined(turnsLeft) &&
turnStateToDescription(turnsState)
.replaceAll("%c%", turnsLeft.toFixed(0))
.replaceAll("%m%", turnsTotal.toFixed(0))}
The amount of energy stored up towards unleashing the might of a Zero Power. When the gauge is full, let loose!
</div>}
</Tooltip>
} placement={"right"}>
<div className={"characterTurns characterTurns" + turnsState}>{turnsText}</div>
<div className={"characterZeroGauge"}>
<div className={"characterZeroBarBack"} />
<animated.div className={"characterZeroBar"} style={zpStyle} />
<div className={"characterZeroBarPulse" + (zp >= maxZp ? " active" : "")} />
</div>
</OverlayTrigger>}
<div className={"characterHeader"}>
<div className="characterLevel">
<span className="characterLevelLabel">Lv</span>
<span className="characterLevelValue">{level ?? "??"}</span>
{isDefined(effectiveMaxHp) && effectiveHp < 1 && effectiveMaxHp > 0 && <animated.div className={"characterKOBar"} style={characterKOBarStyleInterpolated}>{koText ?? "KO"}</animated.div>}
{isDefined(turnsState) &&
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{turnStateToTitle(turnsState)}</span>
{isDefined(turnsTotal) && isDefined(turnsLeft) && turnsTotal > 1 && <span className={"characterHelpValue"}>{turnsLeft}/{turnsTotal}</span>}</div>
{<div className={"characterHelpDescription"}>
{isDefined(turnsTotal) && isDefined(turnsLeft) &&
turnStateToDescription(turnsState)
.replaceAll("%c%", turnsLeft.toFixed(0))
.replaceAll("%m%", turnsTotal.toFixed(0))}
</div>}
</Tooltip>
} placement={"right"}>
<div className={"characterTurns characterTurns" + turnsState}>{turnsText}</div>
</OverlayTrigger>}
<div className={"characterHeader"}>
<div className="characterLevel">
<span className="characterLevelLabel">Lv</span>
<span className="characterLevelValue">{level ?? "??"}</span>
</div>
<div className={"characterName"}>{name ?? altName ?? "???"}</div>
</div>
<div className={"characterName"}>{name ?? altName ?? "???"}</div>
</div>
{isDefined(hpText) &&
<div className={"characterHp"}>
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={hpTooltip} placement={"top"}>
<animated.div className={"characterHpBar"} style={hpBarStyleInterpolated} />
</OverlayTrigger>
{isDefined(hp) && <animated.div
className={"characterHpValue"}
style={hpTextStyleInterpolated}>{hpText}</animated.div>}
</div>}
{isDefined(mpText) &&
<div className={"characterMp"}>
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={mpTooltip} placement={"top"}>
<animated.div className={"characterMpBar"} style={mpBarStyleInterpolated} />
</OverlayTrigger>
<animated.div className={"characterMpValue"}>{mpText}</animated.div>
</div>}
{isDefined(ipText) &&
<div className={"characterIp"}>
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={ipTooltip} placement={"top"}>
<animated.div className={"characterIpBar"} style={ipBarStyleInterpolated} />
{isDefined(hpText) &&
<div className={"characterHp"}>
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={hpTooltip} placement={"top"}>
<animated.div className={"characterHpBar"} style={hpBarStyleInterpolated} />
</OverlayTrigger>
<animated.div className={"characterIpValue"}>{ipText}</animated.div>
</div>
}
{isDefined(spText) &&
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{spType} Points</span>
<span className={"characterHelpValue"}>{sp}{typeof spBank === "number" ? ` / ${spBank} banked`: null}</span></div>
{isDefined(spType) && <div className={"characterHelpDescription"}>
{spTypeToDescription(spType)}
</div>}
</Tooltip>
} placement={"right"}>
<animated.div className={"characterSp characterSp" + spType}>
<animated.span className={"characterSpValue characterSpValue" + spType}>
{spText}</animated.span>
</animated.div>
</OverlayTrigger>}
{isDefined(bpText) &&
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>Blood Points</span>
<span className={"characterHelpValue"}>{bp}/{maxBp}</span></div>
{isDefined(spType) && <div className={"characterHelpDescription"}>
The current number of blood points, used for Vampire abilities.
</div>}
</Tooltip>
} placement={"right"}>
<animated.div className={"characterBp"}>
<animated.span className={"characterBpValue"}>
{bpText}</animated.span>
</animated.div>
</OverlayTrigger>}
{isDefined(effectiveStatuses) && effectiveStatuses.length > 0 &&
<div className={"characterStatuses"}>
{effectiveStatuses.map(({id, name, count, description, iconUrl}) =>
<OverlayTrigger key={id} delay={{show: 300, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterStatusHeader"}>
<span className={"characterStatusName"}>{name}</span>
{isDefined(count) && count > 0 && <span className={"characterStatusCount"}>{count}</span>}</div>
{isDefined(description) && <div className={"characterStatusDescription"}>
{count > 0 ? description.replaceAll("%c%", count.toFixed(0)) : description}
</div>}
</Tooltip>
} placement={"bottom"}>
<div className={"characterStatusIcon"} style={{backgroundImage: `url("${iconUrl ?? DefaultStatus}")`}}>{count > 0 && <span className={"characterStatusIconCountBadge"}>{count}</span>}</div>
{isDefined(hp) && <animated.div
className={"characterHpValue"}
style={hpTextStyleInterpolated}>{hpText}</animated.div>}
</div>}
{isDefined(mpText) &&
<div className={"characterMp"}>
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={mpTooltip} placement={"top"}>
<animated.div className={"characterMpBar"} style={mpBarStyleInterpolated} />
</OverlayTrigger>
)}
</div>}
</div>
<animated.div className={"characterMpValue"}>{mpText}</animated.div>
</div>}
{isDefined(ipText) &&
<div className={"characterIp"}>
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={ipTooltip} placement={"top"}>
<animated.div className={"characterIpBar"} style={ipBarStyleInterpolated} />
</OverlayTrigger>
<animated.div className={"characterIpValue"}>{ipText}</animated.div>
</div>
}
{isDefined(spText) &&
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{spType} Points</span>
<span className={"characterHelpValue"}>{sp}{typeof spBank === "number" ? ` / ${spBank} banked`: null}</span></div>
{isDefined(spType) && <div className={"characterHelpDescription"}>
{spTypeToDescription(spType)}
</div>}
</Tooltip>
} placement={"right"}>
<animated.div className={"characterSp characterSp" + spType}>
<animated.span className={"characterSpValue characterSpValue" + spType}>
{spText}</animated.span>
</animated.div>
</OverlayTrigger>}
{isDefined(bpText) &&
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>Blood Points</span>
<span className={"characterHelpValue"}>{bp}/{maxBp}</span></div>
{isDefined(spType) && <div className={"characterHelpDescription"}>
The current number of blood points, used for Vampire abilities.
</div>}
</Tooltip>
} placement={"right"}>
<animated.div className={"characterBp"}>
<animated.span className={"characterBpValue"}>
{bpText}</animated.span>
</animated.div>
</OverlayTrigger>}
{isDefined(effectiveStatuses) && effectiveStatuses.length > 0 &&
<div className={"characterStatuses"}>
{effectiveStatuses.map(({id, name, count, description, iconUrl}) =>
<OverlayTrigger key={id} delay={{show: 300, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterStatusHeader"}>
<span className={"characterStatusName"}>{name}</span>
{isDefined(count) && count > 0 && <span className={"characterStatusCount"}>{count}</span>}</div>
{isDefined(description) && <div className={"characterStatusDescription"}>
{count > 0 ? description.replaceAll("%c%", count.toFixed(0)) : description}
</div>}
</Tooltip>
} placement={"bottom"}>
<div className={"characterStatusIcon"} style={{backgroundImage: `url("${iconUrl ?? DefaultStatus}")`}}>{count > 0 && <span className={"characterStatusIconCountBadge"}>{count}</span>}</div>
</OverlayTrigger>
)}
</div>}
</div>
{minions.length > 0 && showMinions && minions.map((minion) =>
<CharacterStatus character={minion} minions={[]} statuses={statuses} key={minion.id}
activeId={activeId} collapsed={false} click={toggleMinions} />
)}
</React.Fragment>
}

Loading…
Cancel
Save