last upload from radzathan

main
Mari 4 months ago
parent 8ae259951b
commit 0a084a2107
  1. 14
      .idea/libraries/Generated_files.xml
  2. 25
      .idea/watcherTasks.xml
  3. 93
      public/api/current.json
  4. BIN
      public/backgrounds/desert-ground-storm.jpg
  5. BIN
      public/backgrounds/desert-sky-storm.jpg
  6. BIN
      public/backgrounds/empty.jpg
  7. BIN
      public/backgrounds/sewer.jpg
  8. BIN
      public/icons/accelerate.webp
  9. BIN
      public/icons/captive.webp
  10. BIN
      public/icons/dazed.webp
  11. BIN
      public/icons/default.webp
  12. BIN
      public/icons/devoured.webp
  13. BIN
      public/icons/enraged.webp
  14. BIN
      public/icons/poisoned.webp
  15. BIN
      public/icons/revived.webp
  16. BIN
      public/icons/shaken.webp
  17. BIN
      public/icons/slow.webp
  18. BIN
      public/icons/stop.webp
  19. BIN
      public/icons/super.webp
  20. BIN
      public/icons/weak.webp
  21. BIN
      public/portraits/aelica.png
  22. BIN
      public/portraits/airship.png
  23. BIN
      public/portraits/amita.png
  24. BIN
      public/portraits/athetyz-alt.png
  25. BIN
      public/portraits/athetyz-alt2.png
  26. BIN
      public/portraits/athetyz.png
  27. BIN
      public/portraits/blue-dragon.png
  28. BIN
      public/portraits/calor.png
  29. BIN
      public/portraits/echo.png
  30. BIN
      public/portraits/flow.png
  31. BIN
      public/portraits/gale.png
  32. BIN
      public/portraits/galvelle.png
  33. BIN
      public/portraits/gravitas.png
  34. BIN
      public/portraits/jalen.png
  35. BIN
      public/portraits/kaumari.png
  36. BIN
      public/portraits/linnet.png
  37. BIN
      public/portraits/portraits.zip
  38. BIN
      public/portraits/prandia.png
  39. BIN
      public/portraits/priva.png
  40. BIN
      public/portraits/selia.png
  41. BIN
      public/portraits/seychelle.png
  42. BIN
      public/portraits/soft-kaumari.png
  43. BIN
      public/portraits/terra.png
  44. BIN
      public/portraits/wing.png
  45. 54
      src/model/Character.ts
  46. 43
      src/ui/App.tsx
  47. 32
      src/ui/CharacterStatus.css
  48. 25
      src/ui/CharacterStatus.tsx

@ -0,0 +1,14 @@
<component name="libraryTable">
<library name="Generated files" type="javaScript">
<properties>
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/src/grammar/grammar.ohm-bundle.js" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/src/grammar/grammar.ohm-bundle.js" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions">
<TaskOptions isEnabled="true">
<option name="arguments" value="generateBundles -et $FilePath$" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="ohm" />
<option name="immediateSync" value="true" />
<option name="name" value="Ohm JS recompile" />
<option name="output" value="$FilePath$-bundle.d.ts:$FilePath$-bundle.js" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$PROJECT_DIR$/node_modules/.bin/ohm" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="false" />
<option name="workingDir" value="$PROJECT_DIR$" />
<envs />
</TaskOptions>
</component>
</project>

@ -30,7 +30,8 @@
"portraitUrl": "/portraits/aelica.png", "portraitUrl": "/portraits/aelica.png",
"turnsTotal": 1, "turnsTotal": 1,
"turnsLeft": 1, "turnsLeft": 1,
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "flow", "id": "flow",
@ -38,7 +39,8 @@
"leader": "aelica", "leader": "aelica",
"name": "Flow", "name": "Flow",
"portraitUrl": "/portraits/flow.png", "portraitUrl": "/portraits/flow.png",
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "athetyz", "id": "athetyz",
@ -59,7 +61,8 @@
"portraitUrl": "/portraits/athetyz.png", "portraitUrl": "/portraits/athetyz.png",
"turnsTotal": 1, "turnsTotal": 1,
"turnsLeft": 0, "turnsLeft": 0,
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "galvelle", "id": "galvelle",
@ -67,7 +70,8 @@
"leader": "athetyz", "leader": "athetyz",
"name": "Galvelle", "name": "Galvelle",
"portraitUrl": "/portraits/galvelle.png", "portraitUrl": "/portraits/galvelle.png",
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "echo", "id": "echo",
@ -88,7 +92,8 @@
"portraitUrl": "/portraits/echo.png", "portraitUrl": "/portraits/echo.png",
"turnsTotal": 1, "turnsTotal": 1,
"turnsLeft": 1, "turnsLeft": 1,
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "gale", "id": "gale",
@ -103,7 +108,8 @@
"portraitUrl": "/portraits/gale.png", "portraitUrl": "/portraits/gale.png",
"turnsTotal": 1, "turnsTotal": 1,
"turnsLeft": 1, "turnsLeft": 1,
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "gravitas", "id": "gravitas",
@ -124,7 +130,8 @@
"portraitUrl": "/portraits/gravitas.png", "portraitUrl": "/portraits/gravitas.png",
"turnsTotal": 1, "turnsTotal": 1,
"turnsLeft": 1, "turnsLeft": 1,
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "calor", "id": "calor",
@ -132,7 +139,8 @@
"leader": "gravitas", "leader": "gravitas",
"name": "Calor", "name": "Calor",
"portraitUrl": "/portraits/calor.png", "portraitUrl": "/portraits/calor.png",
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "linnet", "id": "linnet",
@ -153,7 +161,8 @@
"portraitUrl": "/portraits/linnet.png", "portraitUrl": "/portraits/linnet.png",
"turnsTotal": 1, "turnsTotal": 1,
"turnsLeft": 1, "turnsLeft": 1,
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "terra", "id": "terra",
@ -161,12 +170,13 @@
"leader": "linnet", "leader": "linnet",
"name": "Terra", "name": "Terra",
"portraitUrl": "/portraits/terra.png", "portraitUrl": "/portraits/terra.png",
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "prandia", "id": "priva",
"side": "ally", "side": "ally",
"name": "Prandia", "name": "Privá",
"level": 32, "level": 32,
"hp": 37, "hp": 37,
"maxHp": 90, "maxHp": 90,
@ -180,50 +190,53 @@
"sp": 3, "sp": 3,
"spBank": 4, "spBank": 4,
"spType": "Fabula", "spType": "Fabula",
"portraitUrl": "/portraits/prandia.png", "portraitUrl": "/portraits/priva.png",
"turnsTotal": 1, "turnsTotal": 1,
"turnsLeft": 1, "turnsLeft": 1,
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "selia", "id": "selia",
"side": "ally", "side": "ally",
"leader": "prandia", "leader": "priva",
"name": "Sélia", "name": "Sélia",
"portraitUrl": "/portraits/selia.png", "portraitUrl": "/portraits/selia.png",
"statuses": [] "statuses": [],
"privacy": "friend"
}, },
{ {
"id": "werespider", "id": "chelle",
"side": "enemy", "side": "ally",
"name": "Werespider", "name": "Seychelle",
"health": "KO", "level": 32,
"spType": "Ultima", "hp": 37,
"sp": 5, "maxHp": 90,
"turnsLeft": 3, "mp": 0,
"turnsTotal": 3 "maxMp": 70,
"ip": 8,
"maxIp": 8,
"sp": 3,
"spBank": 4,
"spType": "Fabula",
"portraitUrl": "/portraits/seychelle.png",
"turnsTotal": 1,
"turnsLeft": 1,
"statuses": [],
"privacy": "friend"
}, },
{ {
"id": "kaumari", "id": "jalen",
"side": "enemy", "side": "enemy",
"name": "Kaumari", "name": "Jalen",
"level": 600,
"health": "Full", "health": "Full",
"spType": "Ultima", "spType": "Ultima",
"sp": 15, "portraitUrl": "/portraits/jalen.png",
"turnsLeft": 6,
"turnsTotal": 6,
"portraitUrl": "/portraits/kaumari.png"
},
{
"id": "amita",
"side": "enemy",
"name": "Amita",
"level": 50, "level": 50,
"health": "Full", "sp": 5,
"turnsLeft": 6, "turnsLeft": 7,
"turnsTotal": 6, "turnsTotal": 7,
"portraitUrl": "/portraits/amita.png" "privacy": "unscanned enemy"
} }
], ],
"statuses": [{ "statuses": [{

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

@ -485,7 +485,7 @@ export const CharacterResources: ResourceManipulator<Character, Resource> = {
export function applyCharacterPrivacy(character: Character): Character|null { export function applyCharacterPrivacy(character: Character): Character|null {
const privacySettings = CharacterPrivacySettings[character.privacy ?? CharacterPrivacy.Hidden] const privacySettings = CharacterPrivacySettings[character.privacy ?? CharacterPrivacy.Hidden]
if (!privacySettings.showCharacter) { if (!privacySettings || !privacySettings.showCharacter) {
return null return null
} }
const out: {-readonly [Field in keyof Character]: Character[Field]} = Object.assign({}, character) const out: {-readonly [Field in keyof Character]: Character[Field]} = Object.assign({}, character)
@ -600,3 +600,55 @@ export const CharacterStatuses = {
} }
} }
} }
export interface CharacterWithMinions extends Character {
readonly minions: readonly CharacterWithMinions[]
}
export function getCharacterWithMinions(id: string, characterList: readonly Character[]): CharacterWithMinions {
const idsToRecurse: string[] = []
const idsToRevisit: string[] = []
const rootCharacter = characterList.find((c) => c.id === id)
if (!rootCharacter) {
throw Error(`no character with id ${id} was found`)
}
const characterMap: {[id: string]: Character} = {[id]: rootCharacter}
const withMinionsMap: {[id: string]: CharacterWithMinions} = {}
const minionIdMap: {[id: string]: string[]} = {}
for (let nextId:string|undefined = id; typeof nextId !== "undefined"; nextId = idsToRecurse.pop()) {
const character = characterMap[nextId]
const minions = characterList.filter((c) => c.leader === nextId)
if (minions.length === 0) {
console.log(`no minions: ${character.name}`)
withMinionsMap[nextId] = {
...character,
minions: []
}
continue
}
minionIdMap[nextId] = minions.map((c) => c.id)
for (const minion of minions) {
if (!(minion.id in characterMap) && !idsToRecurse.includes(minion.id)) {
characterMap[minion.id] = minion
idsToRecurse.push(minion.id)
}
}
idsToRevisit.push(nextId)
}
for (let nextId: string|undefined = idsToRevisit.pop(); typeof nextId !== "undefined"; nextId = idsToRevisit.pop()) {
const minionIds = minionIdMap[nextId]
if (!minionIds.every((c) => c in withMinionsMap)) {
throw Error(`Possible loop detected; can't satisfy dependencies for ${nextId}`)
}
withMinionsMap[nextId] = {
...characterMap[nextId],
minions: minionIds.map((cid) => withMinionsMap[cid]),
}
}
if (!withMinionsMap[id]) {
throw Error(`Could not construct list of minions for ${id}`)
}
return withMinionsMap[id]
}

@ -1,12 +1,12 @@
import React, {useEffect, useMemo, useState} from 'react'; import React, {useEffect, useMemo, useState} from 'react';
import './App.css'; import './App.css';
import {Col, Container, Row, Stack} from "react-bootstrap"; import {Col, Container, Row, Stack} from "react-bootstrap";
import {CharacterStatus} from "./CharacterStatus"; import {CharacterDisplayType, CharacterStatus} from "./CharacterStatus";
import {GameState} from "../model/GameState"; import {GameState} from "../model/GameState";
import OverlayTrigger from "react-bootstrap/OverlayTrigger"; import OverlayTrigger from "react-bootstrap/OverlayTrigger";
import Tooltip from "react-bootstrap/Tooltip"; import Tooltip from "react-bootstrap/Tooltip";
import {TurnTimer} from "./TurnTimer"; import {TurnTimer} from "./TurnTimer";
import {CharacterSide} from "../model/Character"; import {applyCharacterPrivacy, Character, CharacterSide, getCharacterWithMinions} from "../model/Character";
import {evaluate} from "../grammar/interpreter"; import {evaluate} from "../grammar/interpreter";
function useJson<T>(url: string): T | null { function useJson<T>(url: string): T | null {
@ -63,8 +63,11 @@ function App() {
console.log(ex) console.log(ex)
} }
}, [origState]) }, [origState])
const startTime = Date.now() const characters = state?.characters.map((c) => applyCharacterPrivacy(c))
const endTime = Date.now() + 2 * 60 * 1000 .filter((c: Character|null): c is Character => c !== null) ?? []
const statuses = state?.statuses ?? []
const conflict = state?.conflict ?? null
const session = state?.session ?? null
return <React.Fragment> return <React.Fragment>
<Container fluid> <Container fluid>
<Row> <Row>
@ -77,13 +80,13 @@ function App() {
<Tooltip> <Tooltip>
<div className={"appHelpHeader"}> <div className={"appHelpHeader"}>
<span className={"appHelpName"}>Fabula Points spent</span> <span className={"appHelpName"}>Fabula Points spent</span>
<span className={"appHelpValue"}>{state?.session.usedSp.Fabula ?? 0}</span></div> <span className={"appHelpValue"}>{session?.usedSp.Fabula ?? 0}</span></div>
<div className={"appHelpDescription"}> <div className={"appHelpDescription"}>
The party earns 1 EXP for each (#-players) Fabula Points spent during the session. The party earns 1 EXP for each (#-players) Fabula Points spent during the session.
</div> </div>
</Tooltip> </Tooltip>
} placement={"right"}> } placement={"right"}>
<div className={"totalFPSpent"}>{state?.session.usedSp.Fabula ?? 0}</div> <div className={"totalFPSpent"}>{session?.usedSp.Fabula ?? 0}</div>
</OverlayTrigger> </OverlayTrigger>
<div className={"mx-auto totalSPSpent"}>Points Spent</div> <div className={"mx-auto totalSPSpent"}>Points Spent</div>
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={ <OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
@ -100,33 +103,33 @@ function App() {
</OverlayTrigger> </OverlayTrigger>
</Stack> </Stack>
</Col> </Col>
<Col xs={{span: true}} xxl={{span: false}}> {<Col xs={{span: true}} xxl={{span: false}}></Col> && null}</Row>
<TurnTimer title={"Timer"} startTime={startTime} endTime={endTime} />
</Col></Row>
</Stack> </Stack>
</Col> </Col>
<Col xs={{span: true}} xxl={{span: true, order: "first"}}> <Col xs={{span: true}} xxl={{span: true, order: "first"}}>
<Stack direction="vertical" className={"align-items-center"}> <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> <h1 className={"ally-head " + (state?.conflict?.activeSide === CharacterSide.Ally ? "active" : state?.conflict?.activeSide === CharacterSide.Enemy ? "inactive" : "")}>Allies</h1>
{state && state.characters.filter((character) => !character.leader && character.side === CharacterSide.Ally).map((character) => {characters.filter((character) => !character.leader && character.side === CharacterSide.Ally).map((character) =>
<CharacterStatus <CharacterStatus
key={character.id} key={character.id}
character={character} character={getCharacterWithMinions(character.id, characters)}
minions={state.characters.filter((minion) => minion.leader === character.id)} statuses={statuses}
statuses={state.statuses} displayMode={CharacterDisplayType.NORMAL}
activeId={state.conflict?.activeCharacterId ?? ""} />)} activeId={conflict?.activeCharacterId ?? ""} />)}
</Stack> </Stack>
</Col> </Col>
<Col xs={{span: true}} xxl={{span: true, order: "last"}}> <Col xs={{span: true}} xxl={{span: true, order: "last"}}>
<Stack direction="vertical" className={"align-items-center"}> <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> <h1 className={"enemy-head " + (conflict?.activeSide === CharacterSide.Enemy ? "active" : conflict?.activeSide === CharacterSide.Ally ? "inactive" : "")}>Enemies</h1>
{state && state.characters.filter((character) => !character.leader && character.side === CharacterSide.Enemy).map((character) => {characters.filter((character) =>
!characters.some((c) => c.id === character.leader)
&& character.side === CharacterSide.Enemy).map((character) =>
<CharacterStatus <CharacterStatus
key={character.id} key={character.id}
character={character} character={getCharacterWithMinions(character.id, characters)}
minions={state.characters.filter((minion) => minion.leader === character.id)} statuses={statuses}
statuses={state.statuses} displayMode={CharacterDisplayType.NORMAL}
activeId={state.conflict?.activeCharacterId ?? ""} />)} activeId={conflict?.activeCharacterId ?? ""} />)}
</Stack> </Stack>
</Col> </Col>
</Row> </Row>

@ -8,14 +8,14 @@
.characterStatus.minion { .characterStatus.minion {
} }
.characterStatus.minion.collapsed { .characterStatus.portraitOnly {
font-size: 0.80em; font-size: 0.80em;
height: 6.25em; height: 6.25em;
width: 6.25em; width: 6.25em;
margin-left: 0; margin-left: 0;
} }
.characterStatus.minion.collapsed .characterPortrait { .characterStatus.portraitOnly .characterPortrait {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -23,24 +23,24 @@
bottom: 0; bottom: 0;
} }
.characterStatus.minion.collapsed .characterKOBar { .characterStatus.portraitOnly .characterKOBar {
left: 0; left: 0;
bottom: 1.406em; bottom: 1.406em;
} }
.characterStatus.minion.collapsed .characterHeader, .characterStatus.portraitOnly .characterHeader,
.characterStatus.minion.collapsed .characterTurns, .characterStatus.portraitOnly .characterTurns,
.characterStatus.minion.collapsed .characterSp, .characterStatus.portraitOnly .characterSp,
.characterStatus.minion.collapsed .characterBp, .characterStatus.portraitOnly .characterBp,
.characterStatus.minion.collapsed .characterHp, .characterStatus.portraitOnly .characterHp,
.characterStatus.minion.collapsed .characterMp, .characterStatus.portraitOnly .characterMp,
.characterStatus.minion.collapsed .characterIp, .characterStatus.portraitOnly .characterIp,
.characterStatus.minion.collapsed .characterZeroGauge, .characterStatus.portraitOnly .characterZeroGauge,
.characterStatus.minion.collapsed .characterStatuses { .characterStatus.portraitOnly .characterStatuses {
display: none; display: none;
} }
.characterStatus .characterStatus.minion { .characterStatus .characterStatus.portraitOnly {
position: absolute; position: absolute;
bottom: 1.5em; bottom: 1.5em;
left: 5.625em; left: 5.625em;
@ -51,6 +51,7 @@
left: 10.25em; left: 10.25em;
bottom: 2.75em; bottom: 2.75em;
z-index: 4; z-index: 4;
pointer-events: none;
} }
.characterStatus/*.leader*/ .characterHeader { .characterStatus/*.leader*/ .characterHeader {
@ -88,6 +89,10 @@
font-size: 0.75em; font-size: 0.75em;
} }
.characterHp, .characterMp, .characterIp {
pointer-events: none;
}
.characterHpBar, .characterMpBar, .characterIpBar { .characterHpBar, .characterMpBar, .characterIpBar {
box-shadow: 0.1em 0.1em 0.1em rgba(0, 0, 0, 0.5); box-shadow: 0.1em 0.1em 0.1em rgba(0, 0, 0, 0.5);
border: 0.1em solid black; border: 0.1em solid black;
@ -98,6 +103,7 @@
margin: 0.1em 0.1em 0.4em 0.25em; margin: 0.1em 0.1em 0.4em 0.25em;
bottom: 0; bottom: 0;
box-sizing: content-box; box-sizing: content-box;
pointer-events: auto;
} }
.characterHpValue, .characterMpValue, .characterIpValue, .characterHealthText { .characterHpValue, .characterMpValue, .characterIpValue, .characterHealthText {

@ -12,7 +12,7 @@ import {altTo as to} from "./FixedInterpolation";
import { import {
Character, Character,
CharacterHealth, CharacterHealth,
CharacterTurnState, CharacterTurnState, CharacterWithMinions,
healthToBounds, healthToBounds,
healthToFraction, healthToFraction,
hpToHealth, spTypeToDescription, turnStateToDescription, hpToHealth, spTypeToDescription, turnStateToDescription,
@ -21,6 +21,13 @@ import {
import {StatusEffect} from "../model/GameState"; import {StatusEffect} from "../model/GameState";
import React from "react"; import React from "react";
export enum CharacterDisplayType {
NORMAL = "normal",
MINION = "minion",
PORTRAIT_ONLY = "portraitOnly",
BOSS = "boss",
}
export function healthToColor(health: CharacterHealth | undefined): string { export function healthToColor(health: CharacterHealth | undefined): string {
switch (health) { switch (health) {
case CharacterHealth.Full: case CharacterHealth.Full:
@ -76,14 +83,14 @@ const ipBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
}, },
} }
export function CharacterStatus({character, minions, statuses, activeId, collapsed, click}: {character: Character, minions: readonly Character[], statuses: readonly StatusEffect[], activeId: string, collapsed?: boolean, click?: () => void}): ReactElement { export function CharacterStatus({character, statuses, activeId, displayMode, click}: {character: CharacterWithMinions, statuses: readonly StatusEffect[], activeId: string, displayMode: CharacterDisplayType, click?: () => void}): ReactElement {
const [showMinions, setShowMinions] = useState<boolean>(false) const [showMinions, setShowMinions] = useState<boolean>(false)
const toggleMinions = useCallback(() => { const toggleMinions = useCallback(() => {
setShowMinions(!showMinions) setShowMinions(!showMinions)
}, [showMinions, setShowMinions]) }, [showMinions, setShowMinions])
const {id, name, altName, leader, level, health, koText} = character const {id, name, altName, leader, level, health, koText, minions} = character
const {hp, maxHp} = character const {hp, maxHp} = character
const effectiveMaxHp = maxHp ?? 100 const effectiveMaxHp = maxHp ?? 100
@ -316,9 +323,9 @@ export function CharacterStatus({character, minions, statuses, activeId, collaps
return <React.Fragment> return <React.Fragment>
<div className={"characterStatus" + (minions.length > 0 && !showMinions ? " leader" : leader ? " minion" : "") + (collapsed ? " collapsed" : "")} onClick={() => click ? click() : null}> <div className={"characterStatus " + displayMode}>
{minions.length > 0 && !showMinions && <CharacterStatus character={minions[0]} minions={[]} statuses={statuses} activeId={activeId} collapsed={true} click={toggleMinions} />} {minions.length > 0 && !showMinions && <CharacterStatus character={minions[0]} statuses={statuses} activeId={activeId} displayMode={CharacterDisplayType.PORTRAIT_ONLY} click={toggleMinions} />}
<animated.div className={"characterPortrait"} style={characterPortraitStyleInterpolated} /> <animated.div className={"characterPortrait"} style={characterPortraitStyleInterpolated} onClick={click} />
{isDefined(maxZp) && isDefined(zp) && <OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={ {isDefined(maxZp) && isDefined(zp) && <OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip> <Tooltip>
<div className={"characterHelpHeader"}> <div className={"characterHelpHeader"}>
@ -335,7 +342,7 @@ export function CharacterStatus({character, minions, statuses, activeId, collaps
<div className={"characterZeroBarPulse" + (zp >= maxZp ? " active" : "")} /> <div className={"characterZeroBarPulse" + (zp >= maxZp ? " active" : "")} />
</div> </div>
</OverlayTrigger>} </OverlayTrigger>}
{isDefined(effectiveMaxHp) && effectiveHp < 1 && effectiveMaxHp > 0 && <animated.div className={"characterKOBar"} style={characterKOBarStyleInterpolated}>{koText ?? "KO"}</animated.div>} {isDefined(effectiveMaxHp) && effectiveHp < 1 && effectiveMaxHp > 0 && <animated.div className={"characterKOBar"} style={characterKOBarStyleInterpolated} onClick={click}>{koText ?? "KO"}</animated.div>}
{isDefined(turnsState) && {isDefined(turnsState) &&
<OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={ <OverlayTrigger delay={{show: 750, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip> <Tooltip>
@ -434,8 +441,8 @@ export function CharacterStatus({character, minions, statuses, activeId, collaps
</div>} </div>}
</div> </div>
{minions.length > 0 && showMinions && minions.map((minion) => {minions.length > 0 && showMinions && minions.map((minion) =>
<CharacterStatus character={minion} minions={[]} statuses={statuses} key={minion.id} <CharacterStatus character={minion} statuses={statuses} key={minion.id}
activeId={activeId} collapsed={false} click={toggleMinions} /> activeId={activeId} displayMode={CharacterDisplayType.MINION} click={toggleMinions} />
)} )}
</React.Fragment> </React.Fragment>
} }

Loading…
Cancel
Save