diff --git a/public/api/current.json b/public/api/current.json index b1e44a9..0a2cd81 100644 --- a/public/api/current.json +++ b/public/api/current.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": [] } \ No newline at end of file diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index 61a1247..b7e66c8 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -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 = "~" diff --git a/src/grammar/parser.ts b/src/grammar/parser.ts index 345f2de..3790f7b 100644 --- a/src/grammar/parser.ts +++ b/src/grammar/parser.ts @@ -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("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", { 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", { } }, - _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", { 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", { } return r }, +}) + +parser.addAttribute("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 { + return Operands() + }, + 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 { + return Operands((this as ParserNode).identifier) + }, + target(): Set { + return Operands(Target) + }, + source(): Set { + return Operands(Source); + }, + ["null"](): Set { + 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("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("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("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("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("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`) + } }) \ No newline at end of file diff --git a/src/model/GameState.ts b/src/model/GameState.ts index 6ae7a31..e5d42dc 100644 --- a/src/model/GameState.ts +++ b/src/model/GameState.ts @@ -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 diff --git a/src/model/Messages.ts b/src/model/Messages.ts index 18ed111..c35881b 100644 --- a/src/model/Messages.ts +++ b/src/model/Messages.ts @@ -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 +export type Operands = Set; +export function Operands(...items: readonly T[]): Set { + return new Set(items) +} +export function OperandsFrom(children: readonly Set[]): Set { + return new Set([...children.flatMap((child) => [...child.values()])]) +} export interface ParseContext { + readonly timestamp: number readonly source: readonly string[] readonly target: readonly string[] readonly game: GameState diff --git a/src/ui/AnimationHook.ts b/src/ui/AnimationHook.ts index e761cdd..1698ade 100644 --- a/src/ui/AnimationHook.ts +++ b/src/ui/AnimationHook.ts @@ -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) } diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 5b2a8fe..9eab966 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -77,6 +77,7 @@ function App() { )} @@ -87,6 +88,7 @@ function App() { )} diff --git a/src/ui/CharacterStatus.tsx b/src/ui/CharacterStatus.tsx index 18e811a..7fcd91d 100644 --- a/src/ui/CharacterStatus.tsx +++ b/src/ui/CharacterStatus.tsx @@ -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 = { }, } -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 =
{isDefined(maxHp) && isDefined(hp) ? "Health Points" : health} @@ -405,9 +410,9 @@ export function CharacterStatus({character, active}: {character: Character, acti {bpText} } - {isDefined(statuses) && + {isDefined(effectiveStatuses) && effectiveStatuses.length > 0 &&
- {statuses.map(({id, name, count, description, iconUrl}) => + {effectiveStatuses.map(({id, count, description, iconUrl}) =>
diff --git a/src/ui/TurnTimer.tsx b/src/ui/TurnTimer.tsx index d4c4e3d..e97e857 100644 --- a/src/ui/TurnTimer.tsx +++ b/src/ui/TurnTimer.tsx @@ -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) { diff --git a/tsconfig.json b/tsconfig.json index a273b0c..d48d3f4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2015", "lib": [ "dom", "dom.iterable",