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

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

@ -6,14 +6,14 @@ import {
MarkdownOutput, MarkdownOutput,
MeteredResource, MeteredResource,
NumberSign, NumberSign,
Operands, Operands, OperandsFrom,
ParseContext, ParseContext,
Resource, Resource, Source, Target,
UnmeteredResource, UnmeteredResource,
} from "../model/Messages"; } 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 {CharacterPrivacy, CharacterSide} from "../model/Character";
import {ClockMode} from "../model/GameState"; import {ClockMode, TimerDirection} from "../model/GameState";
export const parser = grammar.createSemantics() export const parser = grammar.createSemantics()
@ -24,25 +24,28 @@ export interface ParserNode extends Node {
numberValue: number numberValue: number
identifier: string identifier: string
resource: Resource|null resource: Resource|null
// TODO: Implement all of the things listed below.
operands: Operands operands: Operands
blockTargets: Operands blockTargets: Operands
blockSources: Operands blockSources: Operands
currentValue: number|null
maxValue: number|null
textValue: string
silenced: boolean silenced: boolean
currentValue: number
maxValue: number
// TODO: Implement all of the things listed below.
textValue: string
failReason: unknown // TODO: create an enum for this failReason: unknown // TODO: create an enum for this
// TODO: create rules for these to describe clocks/timers/items/statuses/characters and implement them // TODO: create rules for these to describe clocks/timers/items/statuses/characters and implement them
nameText: string nameText: string
descriptionText: string descriptionText: string
// TODO: use for portraits, status icons, backdrops, BGM, and SFX
url: string
side: CharacterSide side: CharacterSide
privacy: CharacterPrivacy privacy: CharacterPrivacy
clockMode: ClockMode clockMode: ClockMode
timerDirection: unknown // TODO: create an enum for this timerDirection: TimerDirection
timerDurationMs: number 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 // TODO: make sure that FP and UP spent gets saved in the appropriate counters
evaluate(ctx: ParseContext): ParseContext evaluate(ctx: ParseContext): ParseContext
renderMarkdown(ctx: MarkdownContext): MarkdownOutput 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 { DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): ElementalType | null {
return (elementalType as ParserNode).element return (elementalType as ParserNode).element
}, },
_iter(...children: Node[]): ElementalType|null { _iter(...children: readonly Node[]): ElementalType|null {
if (this.isOptional() && children.length === 0) { if (this.isOptional() && children.length === 0) {
return null return null
} else if (this.isOptional() && children.length === 1) { } 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 { DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Affinity {
return (affinity as ParserNode).affinity return (affinity as ParserNode).affinity
}, },
_iter(...children: Node[]): Affinity { _iter(...children: readonly Node[]): Affinity {
if (this.isOptional() && children.length === 0) { if (this.isOptional() && children.length === 0) {
return Affinity.Normal return Affinity.Normal
} else if (this.isOptional() && children.length === 1) { } 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) { if (this.isOptional() && children.length === 0) {
return null return null
} else if (this.isOptional() && children.length === 1) { } 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`) 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 { SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): MeteredResource {
const r = (resource as ParserNode).resource; const r = (resource as ParserNode).resource;
switch (r) { switch (r) {
@ -534,3 +543,243 @@ parser.addAttribute<Resource|null>("resource", {
return r 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 readonly activeCharacterId: string|null
} }
export enum TimerDirection {
Countup = "up",
Countdown = "down",
Stopped = "stop"
}
export interface BaseTimerState { export interface BaseTimerState {
readonly type: string readonly type: TimerDirection
readonly id: string readonly id: string
readonly text: string readonly text: string
} }
export interface StoppedTimerState extends BaseTimerState {
readonly time: number
}
export interface CountupTimerState extends BaseTimerState { export interface CountupTimerState extends BaseTimerState {
readonly type: "up" readonly type: TimerDirection.Countup
readonly timeStartAt: number readonly timeStartAt: number
} }
export interface CountdownTimerState extends BaseTimerState { export interface CountdownTimerState extends BaseTimerState {
readonly type: "down" readonly type: TimerDirection.Countdown
readonly timeEndAt: number readonly timeEndAt: number
readonly timeStartAt: number|null readonly timeStartAt: number|null
} }
export type TimerState = CountupTimerState|CountdownTimerState; export type TimerState = CountupTimerState|CountdownTimerState|StoppedTimerState;
export interface StatusEffect { export interface StatusEffect {
readonly id: string readonly id: string

@ -48,12 +48,19 @@ export enum UnmeteredResource {
export type Resource = MeteredResource|UnmeteredResource export type Resource = MeteredResource|UnmeteredResource
export const Target = Symbol("target"); export const Target: unique symbol = Symbol("target");
export const Source = Symbol("source"); 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 { export interface ParseContext {
readonly timestamp: number
readonly source: readonly string[] readonly source: readonly string[]
readonly target: readonly string[] readonly target: readonly string[]
readonly game: GameState readonly game: GameState

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

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

@ -18,6 +18,7 @@ import {
hpToHealth, spTypeToDescription, turnStateToDescription, hpToHealth, spTypeToDescription, turnStateToDescription,
turnStateToTitle turnStateToTitle
} from "../model/Character"; } from "../model/Character";
import {StatusEffect} from "../model/GameState";
export function healthToColor(health: CharacterHealth | undefined): string { export function healthToColor(health: CharacterHealth | undefined): string {
switch (health) { switch (health) {
@ -74,8 +75,8 @@ const ipBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
}, },
} }
export function CharacterStatus({character, active}: {character: Character, active: boolean}): ReactElement { export function CharacterStatus({character, statuses, active}: {character: Character, statuses: readonly StatusEffect[], active: boolean}): ReactElement {
const {name, altName, level, health, koText, statuses} = character const {name, altName, level, health, koText} = character
const {hp, maxHp} = character const {hp, maxHp} = character
const effectiveMaxHp = maxHp ?? 100 const effectiveMaxHp = maxHp ?? 100
@ -172,7 +173,7 @@ export function CharacterStatus({character, active}: {character: Character, acti
} else { } else {
return {} return {}
} }
}, [bp, bpRecentSpring]) }, [bp, maxBp, bpRecentSpring])
const {zp, maxZp} = character const {zp, maxZp} = character
const {springs: [, , {v: zpRecentSpring}]} = useSpringyValue({ const {springs: [, , {v: zpRecentSpring}]} = useSpringyValue({
@ -195,7 +196,7 @@ export function CharacterStatus({character, active}: {character: Character, acti
} else { } else {
return {} return {}
} }
}, [zp, zpRecentSpring]) }, [zp, maxZp, zpRecentSpring])
const {turnsLeft, turnsTotal, canAct} = character const {turnsLeft, turnsTotal, canAct} = character
const {turnsState, turnsText} = useMemo(() => { const {turnsState, turnsText} = useMemo(() => {
if (!isDefined(turnsTotal) || !isDefined(turnsLeft)) { if (!isDefined(turnsTotal) || !isDefined(turnsLeft)) {
@ -231,7 +232,7 @@ export function CharacterStatus({character, active}: {character: Character, acti
turnsState: CharacterTurnState.Ready, turnsState: CharacterTurnState.Ready,
} }
} }
}, [active, hp, maxHp, canAct, turnsLeft, turnsTotal]) }, [active, effectiveHp, canAct, turnsLeft, turnsTotal])
const {portraitUrl} = character const {portraitUrl} = character
const effectivePortraitUrl = portraitUrl ?? DefaultPortrait const effectivePortraitUrl = portraitUrl ?? DefaultPortrait
@ -252,7 +253,7 @@ export function CharacterStatus({character, active}: {character: Character, acti
} }
return filter return filter
}) })
}, [hpRecentSpring, effectiveMaxHp, turnsTotal, turnsLeft, canAct]) }, [hpRecentSpring, effectiveMaxHp, turnsTotal, canAct])
const {brightness: brightnessSpring, grayscale: grayscaleSpring} = useSpring({ const {brightness: brightnessSpring, grayscale: grayscaleSpring} = useSpring({
grayscale: to([portraitFilterInterpolated], ({color}) => 100 - color), grayscale: to([portraitFilterInterpolated], ({color}) => 100 - color),
brightness: to([portraitFilterInterpolated], ({brightness}) => brightness), brightness: to([portraitFilterInterpolated], ({brightness}) => brightness),
@ -276,6 +277,10 @@ export function CharacterStatus({character, active}: {character: Character, acti
} }
}, [brightnessSpring, grayscaleSpring, hpFlashSpring, effectivePortraitUrl]) }, [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> const hpTooltip = <Tooltip>
<div className={"characterHelpHeader"}> <div className={"characterHelpHeader"}>
<span className={"characterHelpName"}>{isDefined(maxHp) && isDefined(hp) ? "Health Points" : health}</span> <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> {bpText}</animated.span>
</animated.div> </animated.div>
</OverlayTrigger>} </OverlayTrigger>}
{isDefined(statuses) && {isDefined(effectiveStatuses) && effectiveStatuses.length > 0 &&
<div className={"characterStatuses"}> <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={ <OverlayTrigger key={id} delay={{show: 300, hide: 0}} trigger={["hover", "click", "focus"]} overlay={
<Tooltip> <Tooltip>
<div className={"characterStatusHeader"}> <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 { export function TurnTimer({title, startTime, endTime, displayedTime, resolutionMs = DEFAULT_RESOLUTION_MS}: TurnTimerArgs): ReactElement {
const [currentTime, setCurrentTime] = useState(() => displayedTime ?? Date.now()) const [currentTime, setCurrentTime] = useState(() => displayedTime ?? Date.now())
const accumulatedTime = useRef(0) const accumulatedTime = useRef(0)
const animationCallback = useCallback(isDefined(displayedTime) const animationCallback = useCallback(
? () => null (delta: number) => {
: (delta: number) => { if (!isDefined(displayedTime)) {
return
}
accumulatedTime.current += delta accumulatedTime.current += delta
if (accumulatedTime.current > resolutionMs) { if (accumulatedTime.current > resolutionMs) {
accumulatedTime.current %= resolutionMs accumulatedTime.current %= resolutionMs
setCurrentTime(Date.now()) setCurrentTime(Date.now())
} }
}, [displayedTime, setCurrentTime, accumulatedTime]) }, [displayedTime, setCurrentTime, resolutionMs, accumulatedTime])
useAnimationFrame(animationCallback) useAnimationFrame(animationCallback)
if (isDefined(displayedTime) && displayedTime !== currentTime) { if (isDefined(displayedTime) && displayedTime !== currentTime) {

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

Loading…
Cancel
Save