more parsing updates

main
Mari 1 year ago
parent e55f141cbe
commit 1afed85dbe
  1. 5
      public/api/current.json
  2. 34
      src/grammar/grammar.ohm
  3. 275
      src/grammar/parser.ts
  4. 18
      src/model/GameState.ts
  5. 13
      src/model/Messages.ts
  6. 2
      src/ui/AnimationHook.ts
  7. 2
      src/ui/App.tsx
  8. 21
      src/ui/CharacterStatus.tsx
  9. 10
      src/ui/TurnTimer.tsx
  10. 2
      tsconfig.json

@ -62,7 +62,7 @@
"maxMp": 62,
"ip": 4,
"maxIp": 12,
"zp": 5,
"zp": 6,
"maxZp": 6,
"sp": 3,
"spBank": 4,
@ -203,5 +203,6 @@
"turnsLeft": 3,
"turnsTotal": 3
}
]
],
"statuses": []
}

@ -8,8 +8,8 @@ FabulaDSL {
newline = "\n"
EmptyLines = operationTerminator+
nonNewlineSpace = ~newline space
spaces := (nonNewlineSpace|blockComment)*
wordSep = ~newline space
spaces := (wordSep|blockComment)*
target = "@"
source = "&"
@ -82,10 +82,10 @@ FabulaDSL {
LevelResource = caseInsensitive<"Level">|caseInsensitive<"lvl">|caseInsensitive<"lv">
money = caseInsensitive<"Zenit">|caseInsensitive<"z">|caseInsensitive<"gold">|caseInsensitive<"gil">|caseInsensitive<"gp">|caseInsensitive<"g">
money = caseInsensitive<"Zenit">|caseInsensitive<"z">|caseInsensitive<"gold">|caseInsensitive<"gil">|caseInsensitive<"gp">
MoneyResource = money
materials = caseInsensitive<"materials">|caseInsensitive<"mats">|caseInsensitive<"m">
MaterialsFull = money space materials
materials = caseInsensitive<"materials">|caseInsensitive<"mats">
MaterialsFull = money wordSep materials
MaterialsResource = MaterialsFull|materials
UnmeteredResource (the name of an unmetered resource) = SpResource|LevelResource|MaterialsResource|MoneyResource
@ -102,9 +102,9 @@ FabulaDSL {
lightningElement = caseInsensitive<"lightning">|caseInsensitive<"zap">|caseInsensitive<"electricity">
|caseInsensitive<"electrical">|caseInsensitive<"electric">|caseInsensitive<"elec">
|caseInsensitive<"shock">
earthElement = caseInsensitive<"earth">|caseInsensitive<"rock">|caseInsensitive<"dirt">
earthElement = caseInsensitive<"earth">|caseInsensitive<"rock">
iceElement = caseInsensitive<"ice">|caseInsensitive<"cold">
windElement = caseInsensitive<"wind">|caseInsensitive<"air">|caseInsensitive<"gust">
windElement = caseInsensitive<"wind">|caseInsensitive<"air">
lightElement = caseInsensitive<"light">|caseInsensitive<"holy">
darkElement = caseInsensitive<"dark">|caseInsensitive<"evil">
physicalElement = caseInsensitive<"physical">|caseInsensitive<"phys">
@ -128,19 +128,19 @@ FabulaDSL {
MeteredValue = integer MaxValue
MaxValue = meteredSeparator integer
DeltaOperation = Operands space Resource colon Delta affinity? elementalType?
DeltaOperation = Operands wordSep Resource colon Delta affinity? elementalType?
DeltaOperationAlternate = Operands colon Delta affinity? Resource? elementalType?
DeltaOperationAlternate2 = Operands colon Resource Delta affinity? elementalType?
SetMeteredOperation = Operands space MeteredResource colon MeteredValue
SetMeteredOperation = Operands wordSep MeteredResource colon MeteredValue
SetMeteredOperationAlternate = Operands colon MeteredValue MeteredResource
SetMeteredOperationAlternate2 = Operands colon MeteredResource MeteredValue
SetValueOperation = Operands space Resource colon integer
SetValueOperation = Operands wordSep Resource colon integer
SetValueOperationAlternate = Operands colon integer Resource
SetValueOperationAlternate2 = Operands colon Resource integer
SetMaxOperation = Operands space MeteredResource colon MaxValue
SetMaxOperation = Operands wordSep MeteredResource colon MaxValue
SetMaxOperationAlternate = Operands colon MaxValue MeteredResource
SetMaxOperationAlternate2 = Operands colon MeteredResource MaxValue
ClearOperation = Operands space Resource colon null
ClearOperation = Operands wordSep Resource colon null
ClearOperationAlternate = Operands colon null Resource
ClearOperationAlternate2 = Operands colon Resource null
@ -157,15 +157,19 @@ FabulaDSL {
SetTargetOperation = target (Operands | null)
SetSourceOperation = source (Operands | null)
avoid = caseInsensitive<"avoided">|caseInsensitive<"avoids">|caseInsensitive<"avoid">
dodge = caseInsensitive<"dodged">|caseInsensitive<"dodges">|caseInsensitive<"dodge">
miss = caseInsensitive<"missed">|caseInsensitive<"misses">|caseInsensitive<"miss">
resist = caseInsensitive<"resisted">|caseInsensitive<"resists">|caseInsensitive<"resist">
fail = caseInsensitive<"failed">|caseInsensitive<"fails">|caseInsensitive<"fail">
block = caseInsensitive<"blocked">|caseInsensitive<"blocks">|caseInsensitive<"block">
parry = caseInsensitive<"parried">|caseInsensitive<"parries">|caseInsensitive<"parry">
FailReason = dodge|miss|resist|fail|block|parry
FailReason = avoid|dodge|miss|resist|fail|block|parry
FailOperation = Operands colon FailReason
PrintOperation = ">" textToEndOfLine
printOperator = ">"
PrintOperation = printOperator textToEndOfLine
PrintOperationWithOperands = Operands colon printOperator textToEndOfLine
Operation = DeltaOperation | DeltaOperationAlternate | DeltaOperationAlternate2
| SetMeteredOperation | SetMeteredOperationAlternate | SetMeteredOperationAlternate2
@ -173,7 +177,7 @@ FabulaDSL {
| SetMaxOperation | SetMaxOperationAlternate | SetMaxOperationAlternate2
| ClearOperation | ClearOperationAlternate | ClearOperationAlternate2
| StatusOrItemDeltaOperation | StatusOrItemCounterDeltaOperation | StatusOrItemCounterSetOperation
| SetTargetOperation | SetSourceOperation | FailOperation | PrintOperation
| SetTargetOperation | SetSourceOperation | FailOperation | PrintOperationWithOperands | PrintOperation
silentOperator = "~"

@ -6,14 +6,14 @@ import {
MarkdownOutput,
MeteredResource,
NumberSign,
Operands,
Operands, OperandsFrom,
ParseContext,
Resource,
Resource, Source, Target,
UnmeteredResource,
} from "../model/Messages";
import {IterationNode, Node, NonterminalNode, TerminalNode} from "ohm-js";
import {Iter, IterationNode, Node, NonterminalNode, TerminalNode} from "ohm-js";
import {CharacterPrivacy, CharacterSide} from "../model/Character";
import {ClockMode} from "../model/GameState";
import {ClockMode, TimerDirection} from "../model/GameState";
export const parser = grammar.createSemantics()
@ -24,25 +24,28 @@ export interface ParserNode extends Node {
numberValue: number
identifier: string
resource: Resource|null
// TODO: Implement all of the things listed below.
operands: Operands
blockTargets: Operands
blockSources: Operands
currentValue: number|null
maxValue: number|null
textValue: string
silenced: boolean
currentValue: number
maxValue: number
// TODO: Implement all of the things listed below.
textValue: string
failReason: unknown // TODO: create an enum for this
// TODO: create rules for these to describe clocks/timers/items/statuses/characters and implement them
nameText: string
descriptionText: string
// TODO: use for portraits, status icons, backdrops, BGM, and SFX
url: string
side: CharacterSide
privacy: CharacterPrivacy
clockMode: ClockMode
timerDirection: unknown // TODO: create an enum for this
timerDirection: TimerDirection
timerDurationMs: number
// TODO: add backdrop and music change and sfx commands
// TODO: make sure that FP and UP spent gets saved in the appropriate counters
evaluate(ctx: ParseContext): ParseContext
renderMarkdown(ctx: MarkdownContext): MarkdownOutput
@ -98,7 +101,7 @@ parser.addAttribute<ElementalType|null>("element", {
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): ElementalType | null {
return (elementalType as ParserNode).element
},
_iter(...children: Node[]): ElementalType|null {
_iter(...children: readonly Node[]): ElementalType|null {
if (this.isOptional() && children.length === 0) {
return null
} else if (this.isOptional() && children.length === 1) {
@ -143,7 +146,7 @@ parser.addAttribute<Affinity>("affinity", {
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Affinity {
return (affinity as ParserNode).affinity
},
_iter(...children: Node[]): Affinity {
_iter(...children: readonly Node[]): Affinity {
if (this.isOptional() && children.length === 0) {
return Affinity.Normal
} else if (this.isOptional() && children.length === 1) {
@ -455,7 +458,7 @@ parser.addAttribute<Resource|null>("resource", {
}
},
_iter(...children: Node[]): Resource|null {
_iter(...children: readonly Node[]): Resource|null {
if (this.isOptional() && children.length === 0) {
return null
} else if (this.isOptional() && children.length === 1) {
@ -464,6 +467,12 @@ parser.addAttribute<Resource|null>("resource", {
throw Error(`No idea what to say ${this.ctorName} iteration node's resource is when there are multiple children`)
}
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's resource value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's resource value is`)
},
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): MeteredResource {
const r = (resource as ParserNode).resource;
switch (r) {
@ -533,4 +542,244 @@ parser.addAttribute<Resource|null>("resource", {
}
return r
},
})
parser.addAttribute<Operands>("operands", {
CompleteOperation(silence: IterationNode, operation: NonterminalNode, terminator: NonterminalNode): Operands {
return (operation as ParserNode).operands
},
Operation(operation: NonterminalNode): Operands {
return (operation as ParserNode).operands
},
ClearOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, nul: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
ClearOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, nul: NonterminalNode, resource: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
ClearOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, nul: NonterminalNode): Operands {
return (operands as ParserNode).operands;
},
DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, element: IterationNode): Operands {
return (operands as ParserNode).operands;
},
DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, element: IterationNode): Operands {
return (operands as ParserNode).operands;
},
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, element: IterationNode): Operands {
return (operands as ParserNode).operands;
},
FailOperation(operands: NonterminalNode, colon: NonterminalNode, fail: NonterminalNode): Operands {
return (operands as ParserNode).operands;
},
PrintOperation(): Set<never> {
return Operands<never>()
},
SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, max: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, max: NonterminalNode, resource: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, max: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, meter: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, meter: NonterminalNode, resource: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, meter: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetSourceOperation(source: NonterminalNode, operands: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetTargetOperation(target: NonterminalNode, operands: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, resource: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, statusOrItem: NonterminalNode, delta: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, statusOrItem: NonterminalNode, value: NonterminalNode): Operands {
return (operands as ParserNode).operands
},
StatusOrItemDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, statusOrItem: NonterminalNode, counter: IterationNode): Operands {
return (operands as ParserNode).operands
},
Operands(arg0: NonterminalNode): Operands {
return (arg0.asIteration() as ParserNode).operands
},
_iter(...children: readonly Node[]): Operands {
return OperandsFrom(children.map((child) => (child as ParserNode).operands))
},
operand(oper: NonterminalNode): Operands {
return (oper as ParserNode).operands
},
identifier(): Set<string> {
return Operands((this as ParserNode).identifier)
},
target(): Set<typeof Target> {
return Operands(Target)
},
source(): Set<typeof Source> {
return Operands(Source);
},
["null"](): Set<never> {
return Operands()
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's operands value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's operands value is`)
},
})
parser.addAttribute<Operands>("blockTargets", {
Block(start: NonterminalNode, terminator: NonterminalNode, code: NonterminalNode, end: NonterminalNode, terminator2: NonterminalNode): Operands {
return (start as ParserNode).blockTargets
},
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, target: IterationNode, colon: NonterminalNode, text: NonterminalNode): Operands {
return (target as ParserNode).operands
},
_iter(): Operands {
throw Error(`No idea what to say ${this.ctorName} iteration node's block targets value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's block targets value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's block targets value is`)
},
})
parser.addAttribute<Operands>("blockSources", {
Block(start: NonterminalNode, terminator: NonterminalNode, code: NonterminalNode, end: NonterminalNode, terminator2: NonterminalNode): Operands {
return (start as ParserNode).blockSources
},
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, target: IterationNode, colon: NonterminalNode, text: NonterminalNode): Operands {
return (source as ParserNode).operands
},
_iter(): Operands {
throw Error(`No idea what to say ${this.ctorName} iteration node's block sources value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's block sources value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's block sources value is`)
},
})
parser.addAttribute<boolean>("silenced", {
Block(start: NonterminalNode, terminator: NonterminalNode, code: NonterminalNode, end: NonterminalNode, terminator2: NonterminalNode): boolean {
return (start as ParserNode).silenced
},
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, target: IterationNode, colon: NonterminalNode, text: NonterminalNode): boolean {
return (target as ParserNode).silenced
},
CompleteOperation(silenced: IterationNode, operation: NonterminalNode, terminator: NonterminalNode): boolean {
return (silenced as ParserNode).silenced
},
silentOperator(): boolean {
return true
},
_iter(): boolean {
if (this.isOptional() && this.children.length === 0) {
return false
} else if (this.isOptional() && this.children.length === 1) {
return (this.children[0] as ParserNode).silenced
} else {
throw Error(`No idea what to say ${this.ctorName} iteration node's element is when there are multiple children`)
}
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's silenced status is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's silenced status is`)
}
})
parser.addAttribute<number>("currentValue", {
SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode): number {
return (integer as ParserNode).numberValue;
},
SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode, resource: NonterminalNode): number {
return (integer as ParserNode).numberValue;
},
SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, integer: NonterminalNode): number {
return (integer as ParserNode).numberValue;
},
MeteredValue(current: NonterminalNode, max: NonterminalNode): number {
return (current as ParserNode).numberValue
},
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): number {
return (value as ParserNode).currentValue;
},
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, resource: NonterminalNode): number {
return (value as ParserNode).currentValue;
},
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): number {
return (value as ParserNode).currentValue;
},
StatusOrItemDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
return (counter as ParserNode).numberValue
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
return (counter as ParserNode).numberValue
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's current value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's current value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's current value is`)
}
})
parser.addAttribute<number>("maxValue", {
SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode): number {
return (maxvalue as ParserNode).numberValue;
},
SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, resource: NonterminalNode): number {
return (maxvalue as ParserNode).numberValue;
},
SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, maxvalue: NonterminalNode): number {
return (maxvalue as ParserNode).numberValue;
},
MeteredValue(current: NonterminalNode, max: NonterminalNode): number {
return (max as ParserNode).numberValue
},
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): number {
return (value as ParserNode).maxValue;
},
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, resource: NonterminalNode): number {
return (value as ParserNode).maxValue;
},
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): number {
return (value as ParserNode).maxValue;
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's max value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's max value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's max value is`)
}
})

@ -23,24 +23,34 @@ export interface ConflictState {
readonly activeCharacterId: string|null
}
export enum TimerDirection {
Countup = "up",
Countdown = "down",
Stopped = "stop"
}
export interface BaseTimerState {
readonly type: string
readonly type: TimerDirection
readonly id: string
readonly text: string
}
export interface StoppedTimerState extends BaseTimerState {
readonly time: number
}
export interface CountupTimerState extends BaseTimerState {
readonly type: "up"
readonly type: TimerDirection.Countup
readonly timeStartAt: number
}
export interface CountdownTimerState extends BaseTimerState {
readonly type: "down"
readonly type: TimerDirection.Countdown
readonly timeEndAt: number
readonly timeStartAt: number|null
}
export type TimerState = CountupTimerState|CountdownTimerState;
export type TimerState = CountupTimerState|CountdownTimerState|StoppedTimerState;
export interface StatusEffect {
readonly id: string

@ -48,12 +48,19 @@ export enum UnmeteredResource {
export type Resource = MeteredResource|UnmeteredResource
export const Target = Symbol("target");
export const Source = Symbol("source");
export const Target: unique symbol = Symbol("target");
export const Source: unique symbol = Symbol("source");
export type Operands = Set<string|typeof Target|typeof Source>
export type Operands = Set<string|typeof Target|typeof Source>;
export function Operands<T extends string|typeof Target|typeof Source>(...items: readonly T[]): Set<T> {
return new Set<T>(items)
}
export function OperandsFrom<T extends string|typeof Target|typeof Source>(children: readonly Set<T>[]): Set<T> {
return new Set<T>([...children.flatMap((child) => [...child.values()])])
}
export interface ParseContext {
readonly timestamp: number
readonly source: readonly string[]
readonly target: readonly string[]
readonly game: GameState

@ -7,7 +7,7 @@ export function useAnimationFrame(callback: (delta: number, current: number) =>
const previousTimeRef = useRef(0);
const animate = useCallback(function animate(time: number) {
if (previousTimeRef.current != 0) {
if (previousTimeRef.current !== 0) {
const deltaTime = time - previousTimeRef.current;
callback(deltaTime, time)
}

@ -77,6 +77,7 @@ function App() {
<CharacterStatus
key={character.id}
character={character}
statuses={state.statuses}
active={character.id === state.conflict?.activeCharacterId} />)}
</Stack>
</Col>
@ -87,6 +88,7 @@ function App() {
<CharacterStatus
key={character.id}
character={character}
statuses={state.statuses}
active={character.id === state.conflict?.activeCharacterId} />)}
</Stack>
</Col>

@ -18,6 +18,7 @@ import {
hpToHealth, spTypeToDescription, turnStateToDescription,
turnStateToTitle
} from "../model/Character";
import {StatusEffect} from "../model/GameState";
export function healthToColor(health: CharacterHealth | undefined): string {
switch (health) {
@ -74,8 +75,8 @@ const ipBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
},
}
export function CharacterStatus({character, active}: {character: Character, active: boolean}): ReactElement {
const {name, altName, level, health, koText, statuses} = character
export function CharacterStatus({character, statuses, active}: {character: Character, statuses: readonly StatusEffect[], active: boolean}): ReactElement {
const {name, altName, level, health, koText} = character
const {hp, maxHp} = character
const effectiveMaxHp = maxHp ?? 100
@ -172,7 +173,7 @@ export function CharacterStatus({character, active}: {character: Character, acti
} else {
return {}
}
}, [bp, bpRecentSpring])
}, [bp, maxBp, bpRecentSpring])
const {zp, maxZp} = character
const {springs: [, , {v: zpRecentSpring}]} = useSpringyValue({
@ -195,7 +196,7 @@ export function CharacterStatus({character, active}: {character: Character, acti
} else {
return {}
}
}, [zp, zpRecentSpring])
}, [zp, maxZp, zpRecentSpring])
const {turnsLeft, turnsTotal, canAct} = character
const {turnsState, turnsText} = useMemo(() => {
if (!isDefined(turnsTotal) || !isDefined(turnsLeft)) {
@ -231,7 +232,7 @@ export function CharacterStatus({character, active}: {character: Character, acti
turnsState: CharacterTurnState.Ready,
}
}
}, [active, hp, maxHp, canAct, turnsLeft, turnsTotal])
}, [active, effectiveHp, canAct, turnsLeft, turnsTotal])
const {portraitUrl} = character
const effectivePortraitUrl = portraitUrl ?? DefaultPortrait
@ -252,7 +253,7 @@ export function CharacterStatus({character, active}: {character: Character, acti
}
return filter
})
}, [hpRecentSpring, effectiveMaxHp, turnsTotal, turnsLeft, canAct])
}, [hpRecentSpring, effectiveMaxHp, turnsTotal, canAct])
const {brightness: brightnessSpring, grayscale: grayscaleSpring} = useSpring({
grayscale: to([portraitFilterInterpolated], ({color}) => 100 - color),
brightness: to([portraitFilterInterpolated], ({brightness}) => brightness),
@ -276,6 +277,10 @@ export function CharacterStatus({character, active}: {character: Character, acti
}
}, [brightnessSpring, grayscaleSpring, hpFlashSpring, effectivePortraitUrl])
const characterStatuses = character.statuses ?? []
const effectiveStatuses = characterStatuses.map((statusInstance) => ({
...statuses.find(s => s.id === statusInstance.id), count: statusInstance.count}))
const hpTooltip = <Tooltip>
<div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{isDefined(maxHp) && isDefined(hp) ? "Health Points" : health}</span>
@ -405,9 +410,9 @@ export function CharacterStatus({character, active}: {character: Character, acti
{bpText}</animated.span>
</animated.div>
</OverlayTrigger>}
{isDefined(statuses) &&
{isDefined(effectiveStatuses) && effectiveStatuses.length > 0 &&
<div className={"characterStatuses"}>
{statuses.map(({id, name, count, description, iconUrl}) =>
{effectiveStatuses.map(({id, count, description, iconUrl}) =>
<OverlayTrigger key={id} delay={{show: 300, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip>
<div className={"characterStatusHeader"}>

@ -18,15 +18,17 @@ const DEFAULT_RESOLUTION_MS = 100;
export function TurnTimer({title, startTime, endTime, displayedTime, resolutionMs = DEFAULT_RESOLUTION_MS}: TurnTimerArgs): ReactElement {
const [currentTime, setCurrentTime] = useState(() => displayedTime ?? Date.now())
const accumulatedTime = useRef(0)
const animationCallback = useCallback(isDefined(displayedTime)
? () => null
: (delta: number) => {
const animationCallback = useCallback(
(delta: number) => {
if (!isDefined(displayedTime)) {
return
}
accumulatedTime.current += delta
if (accumulatedTime.current > resolutionMs) {
accumulatedTime.current %= resolutionMs
setCurrentTime(Date.now())
}
}, [displayedTime, setCurrentTime, accumulatedTime])
}, [displayedTime, setCurrentTime, resolutionMs, accumulatedTime])
useAnimationFrame(animationCallback)
if (isDefined(displayedTime) && displayedTime !== currentTime) {

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es2015",
"lib": [
"dom",
"dom.iterable",

Loading…
Cancel
Save