diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index d63e4cc..78c890b 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -20,43 +20,135 @@ FabulaDSL { comma = "," Operands (operands) = nonemptyListOf - experienceResource = caseInsensitive<"XP">|caseInsensitive<"EXP"> - meteredResource (the name of a metered resource) = caseInsensitive<"HP">|caseInsensitive<"MP">|caseInsensitive<"IP">|caseInsensitive<"ZP">|caseInsensitive<"BP">|caseInsensitive<"Turns">|experienceResource - spResource = caseInsensitive<"FP">|caseInsensitive<"UP">|caseInsensitive<"SP"> - moneyResource = caseInsensitive<"Zenit">|caseInsensitive<"z"> - materialsResource = caseInsensitive<"materials">|caseInsensitive<"mats">|caseInsensitive<"m"> - levelResource = caseInsensitive<"Level">|caseInsensitive<"lv"> - unmeteredResource (the name of an unmetered resource) = spResource|moneyResource|materialsResource|levelResource|caseInsensitive<"order"> - resource (the name of a resource) = meteredResource|unmeteredResource - - deltaOperator = "+"|"-" + segments = caseInsensitive<"segments">|caseInsensitive<"sections"> + |caseInsensitive<"segment">|caseInsensitive<"section"> + |caseInsensitive<"ticks">|caseInsensitive<"tick"> + |caseInsensitive<"clock"> + + points = caseInsensitive<"points">|caseInsensitive<"pts">|caseInsensitive<"power"> + + experience = caseInsensitive<"experience"> + xp = caseInsensitive<"XP">|caseInsensitive<"EXP"> + ExperienceFull = experience points + XpResource = ExperienceFull|experience|xp + + health = caseInsensitive<"health">|caseInsensitive<"life">|caseInsensitive<"hit"> + HealthFull = health points + hp = caseInsensitive<"HP"> + HpResource = HealthFull|health|hp + + mind = caseInsensitive<"mind">|caseInsensitive<"magic">|caseInsensitive<"mana"> + MindFull = mind points + mp = caseInsensitive<"MP"> + MpResource = MindFull|mind|mp + + item = caseInsensitive<"items">|caseInsensitive<"item">|caseInsensitive<"inventory"> + ItemFull = item points + ip = caseInsensitive<"IP"> + IpResource = ItemFull|item|ip + + zero = caseInsensitive<"zero"> + charge = points|caseInsensitive<"charge">|segments + ZeroFull = zero charge + zp = caseInsensitive<"ZP">|caseInsensitive<"ZC"> + ZpResource = ZeroFull|zero|zp + + blood = caseInsensitive<"blood">|caseInsensitive<"grave"> + BloodFull = blood points + bp = caseInsensitive<"BP"> + BpResource = BloodFull|blood|bp + + turns = caseInsensitive<"turns">|caseInsensitive<"turn">|caseInsensitive<"actions">|caseInsensitive<"action"> + TurnsFull = turns points + tp = caseInsensitive<"TP">|caseInsensitive<"AP"> + TpResource = TurnsFull|turns|tp + + MeteredResource (the name of a metered resource) = segments|XpResource|HpResource|MpResource + |IpResource|ZpResource|BpResource|TpResource + + fabula = caseInsensitive<"Fabula"> + ultima = caseInsensitive<"Ultima"> + special = caseInsensitive<"Special"> + fp = caseInsensitive<"FP"> + up = caseInsensitive<"UP"> + sp = caseInsensitive<"SP"> + FabulaFull = fabula points + UltimaFull = ultima points + SpecialFull = special points + FabulaResource = FabulaFull|fabula|fp + UltimaResource = UltimaFull|ultima|up + SpecialResource = SpecialFull|special|sp + SpResource = FabulaResource|UltimaResource|SpecialResource + + LevelResource = caseInsensitive<"Level">|caseInsensitive<"lvl">|caseInsensitive<"lv"> + + money = caseInsensitive<"Zenit">|caseInsensitive<"z">|caseInsensitive<"gold">|caseInsensitive<"gil">|caseInsensitive<"gp">|caseInsensitive<"g"> + MoneyResource = money + materials = caseInsensitive<"materials">|caseInsensitive<"mats">|caseInsensitive<"m"> + MaterialsFull = money space materials + MaterialsResource = MaterialsFull|materials + + UnmeteredResource (the name of an unmetered resource) = SpResource|LevelResource|MaterialsResource|MoneyResource + Resource (the name of a resource) = MeteredResource|UnmeteredResource + + plus = "+" + minus = "-" + deltaOperator = plus|minus integer (an integer) = digit+ Delta (a delta) = deltaOperator integer - element = caseInsensitive<"fire">|caseInsensitive<"water">|caseInsensitive<"poison">|caseInsensitive<"lightning">|caseInsensitive<"earth">|caseInsensitive<"ice">|caseInsensitive<"wind">|caseInsensitive<"light">|caseInsensitive<"dark">|caseInsensitive<"physical"> - affinity = "??"|"..."|"."|"!!" + fireElement = caseInsensitive<"fire">|caseInsensitive<"flame">|caseInsensitive<"burn"> + waterElement = caseInsensitive<"water">|caseInsensitive<"poison">|caseInsensitive<"acid"> + lightningElement = caseInsensitive<"lightning">|caseInsensitive<"zap">|caseInsensitive<"electricity"> + |caseInsensitive<"electrical">|caseInsensitive<"electric">|caseInsensitive<"elec"> + |caseInsensitive<"shock"> + earthElement = caseInsensitive<"earth">|caseInsensitive<"rock">|caseInsensitive<"dirt"> + iceElement = caseInsensitive<"ice">|caseInsensitive<"cold"> + windElement = caseInsensitive<"wind">|caseInsensitive<"air">|caseInsensitive<"gust"> + lightElement = caseInsensitive<"light">|caseInsensitive<"holy"> + darkElement = caseInsensitive<"dark">|caseInsensitive<"evil"> + physicalElement = caseInsensitive<"physical">|caseInsensitive<"phys"> + nonElement = caseInsensitive<"nonelemental">|caseInsensitive<"non-elemental">|caseInsensitive<"none">|caseInsensitive<"non"> + healingElement = caseInsensitive<"healing">|caseInsensitive<"heal">|caseInsensitive<"cure"> + elementalType (an element) = fireElement|waterElement + |lightningElement|earthElement + |iceElement|windElement + |lightElement|darkElement + |physicalElement|nonElement|healingElement + + absorbAffinity = "??"|caseInsensitive<"drained">|caseInsensitive<"drain">|caseInsensitive<"absorbed">|caseInsensitive<"absorb"> + immuneAffinity = "."|caseInsensitive<"immunity">|caseInsensitive<"blocked">|caseInsensitive<"immune">|caseInsensitive<"block"> + resistAffinity = "..."|caseInsensitive<"resistance">|caseInsensitive<"resisted">|caseInsensitive<"resist"> + vulnerableAffinity = "!!"|caseInsensitive<"vulnerable">|caseInsensitive<"weakness">|caseInsensitive<"weak"> + normalAffinity = caseInsensitive<"normal"> + affinity (an affinity indicator) = vulnerableAffinity|resistAffinity|immuneAffinity|absorbAffinity|normalAffinity meteredSeparator = ~lineCommentStart ~blockCommentStart "/" MeteredValue = integer MaxValue MaxValue = meteredSeparator integer - DeltaOperation = Operands space resource colon Delta affinity? element? - DeltaOperationAlternate = Operands colon Delta affinity? resource? element? - DeltaOperationAlternate2 = Operands colon resource Delta affinity? element? - SetMeteredOperation = Operands space meteredResource colon MeteredValue - SetMeteredOperationAlternate = Operands colon MeteredValue meteredResource - SetMeteredOperationAlternate2 = Operands colon meteredResource MeteredValue - SetValueOperation = Operands space resource colon integer - SetValueOperationAlternate = Operands colon integer resource - SetValueOperationAlternate2 = Operands colon resource integer - SetMaxOperation = Operands space meteredResource colon MaxValue - SetMaxOperationAlternate = Operands colon MaxValue meteredResource - SetMaxOperationAlternate2 = Operands colon meteredResource MaxValue - ClearOperation = Operands space resource colon null - ClearOperationAlternate = Operands colon null resource - ClearOperationAlternate2 = Operands colon resource null - - StatusDeltaOperation = Operands colon deltaOperator identifier + DeltaOperation = Operands space 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 + SetMeteredOperationAlternate = Operands colon MeteredValue MeteredResource + SetMeteredOperationAlternate2 = Operands colon MeteredResource MeteredValue + SetValueOperation = Operands space Resource colon integer + SetValueOperationAlternate = Operands colon integer Resource + SetValueOperationAlternate2 = Operands colon Resource integer + SetMaxOperation = Operands space MeteredResource colon MaxValue + SetMaxOperationAlternate = Operands colon MaxValue MeteredResource + SetMaxOperationAlternate2 = Operands colon MeteredResource MaxValue + ClearOperation = Operands space Resource colon null + ClearOperationAlternate = Operands colon null Resource + ClearOperationAlternate2 = Operands colon Resource null + + StatusOrItemCounterUnwrapped = "x"? integer + StatusOrItemCounterWrapped = "(" StatusOrItemCounterUnwrapped ")" + StatusOrItemCounter = StatusOrItemCounterWrapped | StatusOrItemCounterUnwrapped + StatusOrItemDeltaOperation = Operands colon deltaOperator identifier StatusOrItemCounter? + StatusOrItemCounterDeltaOperation = Operands colon identifier Delta + StatusOrItemCounterSetOperation = Operands colon identifier StatusOrItemCounter notNewlineOrComment = ~newline ~lineCommentStart ~blockCommentStart any textToEndOfLine = notNewlineOrComment+ @@ -64,15 +156,23 @@ FabulaDSL { SetTargetOperation = target (Operands | null) SetSourceOperation = source (Operands | null) + dodge = caseInsensitive<"dodged">|caseInsensitive<"dodge"> + miss = caseInsensitive<"missed">|caseInsensitive<"miss"> + resist = caseInsensitive<"resisted">|caseInsensitive<"resist"> + fail = caseInsensitive<"failed">|caseInsensitive<"fail"> + block = caseInsensitive<"blocked">|caseInsensitive<"block"> + parry = caseInsensitive<"parried">|caseInsensitive<"parry"> + FailReason = dodge|miss|resist|fail|block|parry + FailOperation = Operands colon FailReason PrintOperation = ">" textToEndOfLine Operation = DeltaOperation | DeltaOperationAlternate | DeltaOperationAlternate2 - | SetMeteredOperation | SetMeteredOperationAlternate | SetMeteredOperationAlternate2 - | SetValueOperation | SetValueOperationAlternate | SetValueOperationAlternate2 - | SetMaxOperation | SetMaxOperationAlternate | SetMaxOperationAlternate2 - | ClearOperation | ClearOperationAlternate | ClearOperationAlternate2 - | StatusDeltaOperation - | SetTargetOperation | SetSourceOperation | PrintOperation + | SetMeteredOperation | SetMeteredOperationAlternate | SetMeteredOperationAlternate2 + | SetValueOperation | SetValueOperationAlternate | SetValueOperationAlternate2 + | SetMaxOperation | SetMaxOperationAlternate | SetMaxOperationAlternate2 + | ClearOperation | ClearOperationAlternate | ClearOperationAlternate2 + | StatusOrItemDeltaOperation | StatusOrItemCounterDeltaOperation | StatusOrItemCounterSetOperation + | SetTargetOperation | SetSourceOperation | FailOperation | PrintOperation silentOperator = "~" @@ -92,7 +192,7 @@ FabulaDSL { beginKeyword = caseInsensitive<"begin"> endKeyword = caseInsensitive<"end"> - BlockStart = silentOperator? beginKeyword SetSourceOperation? SetTargetOperation? textToEndOfLine + BlockStart = silentOperator? beginKeyword SetSourceOperation? SetTargetOperation? colon textToEndOfLine BlockEnd = endKeyword textToEndOfLine? Block = BlockStart operationTerminator CodeSegment BlockEnd operationTerminatorOrEnd diff --git a/src/grammar/parser.ts b/src/grammar/parser.ts new file mode 100644 index 0000000..345f2de --- /dev/null +++ b/src/grammar/parser.ts @@ -0,0 +1,536 @@ +import grammar from "./grammar.ohm-bundle"; +import { + Affinity, + ElementalType, + MarkdownContext, + MarkdownOutput, + MeteredResource, + NumberSign, + Operands, + ParseContext, + Resource, + UnmeteredResource, +} from "../model/Messages"; +import {IterationNode, Node, NonterminalNode, TerminalNode} from "ohm-js"; +import {CharacterPrivacy, CharacterSide} from "../model/Character"; +import {ClockMode} from "../model/GameState"; + +export const parser = grammar.createSemantics() + +export interface ParserNode extends Node { + element: ElementalType|null + affinity: Affinity + sign: NumberSign + 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 + 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 + side: CharacterSide + privacy: CharacterPrivacy + clockMode: ClockMode + timerDirection: unknown // TODO: create an enum for this + timerDurationMs: number + + // TODO: make sure that FP and UP spent gets saved in the appropriate counters + evaluate(ctx: ParseContext): ParseContext + renderMarkdown(ctx: MarkdownContext): MarkdownOutput +} + +parser.addAttribute("element", { + fireElement(): ElementalType.Fire { + return ElementalType.Fire + }, + waterElement(): ElementalType.Water { + return ElementalType.Water + }, + lightningElement(): ElementalType.Lightning { + return ElementalType.Lightning + }, + iceElement(): ElementalType.Ice { + return ElementalType.Ice + }, + earthElement(): ElementalType.Earth { + return ElementalType.Earth + }, + windElement(): ElementalType.Wind { + return ElementalType.Wind + }, + lightElement(): ElementalType.Light { + return ElementalType.Light + }, + darkElement(): ElementalType.Dark { + return ElementalType.Dark + }, + physicalElement(): ElementalType.Physical { + return ElementalType.Physical + }, + nonElement(): ElementalType.Nonelemental { + return ElementalType.Nonelemental + }, + healingElement(): ElementalType.Healing { + return ElementalType.Healing + }, + elementalType(elementNode: NonterminalNode): ElementalType { + const element = (elementNode as ParserNode).element + if (element === null) { + throw Error("Unexpectedly null element when an element was specified") + } + return element + }, + DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): ElementalType | null { + return (elementalType as ParserNode).element + }, + DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): ElementalType | null { + return (elementalType as ParserNode).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 { + if (this.isOptional() && children.length === 0) { + return null + } else if (this.isOptional() && children.length === 1) { + return (children[0] as ParserNode).element + } 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 element is`) + }, + _terminal(): never { + throw Error(`No idea what to say ${this.ctorName} terminal node's element is`) + } +}) + +parser.addAttribute("affinity", { + absorbAffinity(): Affinity.Absorbs { + return Affinity.Absorbs + }, + immuneAffinity(): Affinity.Immune { + return Affinity.Immune + }, + resistAffinity(): Affinity.Resistant { + return Affinity.Resistant + }, + vulnerableAffinity(): Affinity.Vulnerable { + return Affinity.Vulnerable + }, + normalAffinity(): Affinity.Normal { + return Affinity.Normal + }, + affinity(affinityNode: NonterminalNode): Affinity { + return (affinityNode as ParserNode).affinity + }, + DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Affinity { + return (affinity as ParserNode).affinity + }, + DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): Affinity { + return (affinity as ParserNode).affinity + }, + DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Affinity { + return (affinity as ParserNode).affinity + }, + _iter(...children: Node[]): Affinity { + if (this.isOptional() && children.length === 0) { + return Affinity.Normal + } else if (this.isOptional() && children.length === 1) { + return (children[0] as ParserNode).affinity + } else { + throw Error(`No idea what to say ${this.ctorName} iteration node's affinity is when there are multiple children`) + } + }, + _nonterminal(): never { + throw Error(`No idea what to say ${this.ctorName} nonterminal node's affinity is`) + }, + _terminal(): never { + throw Error(`No idea what to say ${this.ctorName} terminal node's affinity is`) + } +}) + +parser.addAttribute("sign", { + deltaOperator(oper: NonterminalNode): NumberSign { + return (oper as ParserNode).sign + }, + plus(): NumberSign.Positive { + return NumberSign.Positive + }, + minus(): NumberSign.Negative { + return NumberSign.Negative + }, + StatusOrItemDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): NumberSign { + return (deltaOperator as ParserNode).sign + }, + StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): NumberSign { + return (delta as ParserNode).sign + }, + _iter(): never { + throw Error(`No idea what to say ${this.ctorName} iteration node's number sign is`) + }, + _nonterminal(): never { + throw Error(`No idea what to say ${this.ctorName} nonterminal node's number sign is`) + }, + _terminal(): never { + throw Error(`No idea what to say ${this.ctorName} terminal node's number sign is`) + } +}) + +parser.addAttribute("numberValue", { + integer(digits: IterationNode): number { + return parseInt(digits.sourceString) + }, + Delta(sign: NonterminalNode, integer: NonterminalNode): number { + const number = (integer as ParserNode).numberValue + switch ((sign as ParserNode).sign) { + case NumberSign.Negative: + return -number + case NumberSign.Positive: + return number + } + }, + DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): number { + return (delta as ParserNode).numberValue + }, + DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): number { + return (delta as ParserNode).numberValue + }, + DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): number { + return (delta as ParserNode).numberValue + }, + MaxValue(separator: NonterminalNode, integer: NonterminalNode): number { + return (integer as ParserNode).numberValue; + }, + 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; + }, + 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; + }, + StatusOrItemCounterUnwrapped(x: IterationNode, integer: NonterminalNode): number { + return (integer as ParserNode).numberValue + }, + StatusOrItemCounterWrapped(lParen: TerminalNode, counter: NonterminalNode, rParen: TerminalNode): number { + return (counter as ParserNode).numberValue + }, + StatusOrItemDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number { + return (counter as ParserNode).numberValue + }, + StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): number { + return (delta 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 number value is`) + }, + _nonterminal(): never { + throw Error(`No idea what to say ${this.ctorName} nonterminal node's number value is`) + }, + _terminal(): never { + throw Error(`No idea what to say ${this.ctorName} terminal node's number value is`) + } +}) + +parser.addAttribute("identifier", { + identifier(): string { + return this.sourceString + }, + StatusOrItemDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): string { + return (identifier as ParserNode).identifier + }, + StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): string { + return (identifier as ParserNode).identifier + }, + StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): string { + return (identifier as ParserNode).identifier + }, + _iter(): never { + throw Error(`No idea what to say ${this.ctorName} iteration node's identifier value is`) + }, + _nonterminal(): never { + throw Error(`No idea what to say ${this.ctorName} nonterminal node's identifier value is`) + }, + _terminal(): never { + throw Error(`No idea what to say ${this.ctorName} terminal node's identifier value is`) + } +}) + +parser.addAttribute("resource", { + BpResource(): MeteredResource.Blood { + return MeteredResource.Blood + }, + FabulaResource(): UnmeteredResource.Fabula { + return UnmeteredResource.Fabula; + }, + HpResource(): MeteredResource.Health { + return MeteredResource.Health; + }, + IpResource(): MeteredResource.Items { + return MeteredResource.Items; + }, + LevelResource(): UnmeteredResource.Level { + return UnmeteredResource.Level; + }, + MaterialsResource(): UnmeteredResource.Materials { + return UnmeteredResource.Materials; + }, + MeteredResource(res: NonterminalNode): MeteredResource { + const r = (res as ParserNode).resource; + switch (r) { + case MeteredResource.Health: + case MeteredResource.Magic: + case MeteredResource.Items: + case MeteredResource.Experience: + case MeteredResource.Zero: + case MeteredResource.Turns: + case MeteredResource.Segments: + case MeteredResource.Blood: + return r + default: + throw Error("unexpected unmetered resource in metered resource node") + } + }, + MoneyResource(): UnmeteredResource.Zenit { + return UnmeteredResource.Zenit; + }, + MpResource(): MeteredResource.Magic { + return MeteredResource.Magic; + }, + Resource(res: NonterminalNode): Resource { + const r = (res as ParserNode).resource; + if (r === null) { + throw Error("unexpected null resource in resource node") + } + return r + }, + SpResource(type: NonterminalNode): UnmeteredResource.Special|UnmeteredResource.Fabula|UnmeteredResource.Ultima { + const r = (type as ParserNode).resource; + switch (r) { + case UnmeteredResource.Special: + case UnmeteredResource.Fabula: + case UnmeteredResource.Ultima: + return r + default: + throw Error("unexpected non-SP resources in an SP node") + } + }, + SpecialResource(): UnmeteredResource.Special { + return UnmeteredResource.Special; + }, + TpResource(): MeteredResource.Turns { + return MeteredResource.Turns; + }, + UltimaResource(): Resource { + return UnmeteredResource.Ultima; + }, + UnmeteredResource(res: NonterminalNode): UnmeteredResource { + const r = (res as ParserNode).resource; + switch (r) { + case UnmeteredResource.Fabula: + case UnmeteredResource.Ultima: + case UnmeteredResource.Zenit: + case UnmeteredResource.Materials: + case UnmeteredResource.Special: + case UnmeteredResource.Level: + return r + default: + throw Error("unexpected metered resource in unmetered resource node") + } + }, + XpResource(): MeteredResource.Experience { + return MeteredResource.Experience; + }, + ZpResource(): MeteredResource.Zero { + return MeteredResource.Zero; + }, + DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Resource { + const r = (resource as ParserNode).resource + if (r === null) { + throw Error("unexpected null resource in required resource node") + } + return r + }, + DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): Resource|null { + return (resource as ParserNode).resource + }, + DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Resource { + const r = (resource as ParserNode).resource + if (r === null) { + throw Error("unexpected null resource in required resource node") + } + return r + }, + SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode): Resource { + const r = (resource as ParserNode).resource + if (r === null) { + throw Error("unexpected null resource in required resource node") + } + return r + }, + SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode, resource: NonterminalNode): Resource { + const r = (resource as ParserNode).resource + if (r === null) { + throw Error("unexpected null resource in required resource node") + } + return r + }, + SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, integer: NonterminalNode): Resource { + const r = (resource as ParserNode).resource + if (r === null) { + throw Error("unexpected null resource in required resource node") + } + return r + }, + SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode): MeteredResource { + const r = (resource as ParserNode).resource; + switch (r) { + case MeteredResource.Health: + case MeteredResource.Magic: + case MeteredResource.Items: + case MeteredResource.Experience: + case MeteredResource.Zero: + case MeteredResource.Turns: + case MeteredResource.Segments: + case MeteredResource.Blood: + return r + default: + throw Error("unexpected unmetered resource in metered resource node") + } + }, + SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, resource: NonterminalNode): MeteredResource { + const r = (resource as ParserNode).resource; + switch (r) { + case MeteredResource.Health: + case MeteredResource.Magic: + case MeteredResource.Items: + case MeteredResource.Experience: + case MeteredResource.Zero: + case MeteredResource.Turns: + case MeteredResource.Segments: + case MeteredResource.Blood: + return r + default: + throw Error("unexpected unmetered resource in metered resource node") + } + }, + SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, maxvalue: NonterminalNode): MeteredResource { + const r = (resource as ParserNode).resource; + switch (r) { + case MeteredResource.Health: + case MeteredResource.Magic: + case MeteredResource.Items: + case MeteredResource.Experience: + case MeteredResource.Zero: + case MeteredResource.Turns: + case MeteredResource.Segments: + case MeteredResource.Blood: + return r + default: + throw Error("unexpected unmetered resource in metered resource node") + } + }, + + _iter(...children: Node[]): Resource|null { + if (this.isOptional() && children.length === 0) { + return null + } else if (this.isOptional() && children.length === 1) { + return (children[0] as ParserNode).resource + } else { + throw Error(`No idea what to say ${this.ctorName} iteration node's resource is when there are multiple children`) + } + }, + SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): MeteredResource { + const r = (resource as ParserNode).resource; + switch (r) { + case MeteredResource.Health: + case MeteredResource.Magic: + case MeteredResource.Items: + case MeteredResource.Experience: + case MeteredResource.Zero: + case MeteredResource.Turns: + case MeteredResource.Segments: + case MeteredResource.Blood: + return r + default: + throw Error("unexpected unmetered resource in metered resource node") + } + }, + SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, resource: NonterminalNode): MeteredResource { + const r = (resource as ParserNode).resource; + switch (r) { + case MeteredResource.Health: + case MeteredResource.Magic: + case MeteredResource.Items: + case MeteredResource.Experience: + case MeteredResource.Zero: + case MeteredResource.Turns: + case MeteredResource.Segments: + case MeteredResource.Blood: + return r + default: + throw Error("unexpected unmetered resource in metered resource node") + } + }, + SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): MeteredResource { + const r = (resource as ParserNode).resource; + switch (r) { + case MeteredResource.Health: + case MeteredResource.Magic: + case MeteredResource.Items: + case MeteredResource.Experience: + case MeteredResource.Zero: + case MeteredResource.Turns: + case MeteredResource.Segments: + case MeteredResource.Blood: + return r + default: + throw Error("unexpected unmetered resource in metered resource node") + } + }, + ClearOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, nul: NonterminalNode): Resource { + const r = (resource as ParserNode).resource + if (r === null) { + throw Error("unexpected null resource in required resource node") + } + return r + }, + ClearOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, nul: NonterminalNode, resource: NonterminalNode): Resource { + const r = (resource as ParserNode).resource + if (r === null) { + throw Error("unexpected null resource in required resource node") + } + return r + }, + ClearOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, nul: NonterminalNode): Resource { + const r = (resource as ParserNode).resource + if (r === null) { + throw Error("unexpected null resource in required resource node") + } + return r + }, +}) \ No newline at end of file diff --git a/src/model/Character.ts b/src/model/Character.ts index 217e080..06877c7 100644 --- a/src/model/Character.ts +++ b/src/model/Character.ts @@ -173,26 +173,19 @@ export function spTypeToDescription(sp: SPType): string { return SPTypes[sp].description } -export type StatusId = string - -export interface StatusEffect { - readonly id: StatusId - readonly name: string +export interface StatusEffectInstance { + readonly id: string readonly count?: number - readonly iconUrl?: string - readonly description?: string } -export type CharacterId = string - export enum CharacterSide { Ally = "ally", Enemy = "enemy" } export interface Character { - readonly id: CharacterId - readonly side: CharacterSide + readonly id: string + readonly side?: CharacterSide readonly portraitUrl?: string readonly name?: string readonly altName?: string @@ -217,7 +210,8 @@ export interface Character { readonly turnsLeft?: number readonly turnsTotal?: number readonly canAct?: boolean - readonly statuses?: readonly StatusEffect[] + readonly statuses?: readonly StatusEffectInstance[] + readonly order?: number readonly privacy?: CharacterPrivacy } diff --git a/src/model/GameState.ts b/src/model/GameState.ts index 8556c82..6ae7a31 100644 --- a/src/model/GameState.ts +++ b/src/model/GameState.ts @@ -1,4 +1,4 @@ -import {Character, CharacterId, CharacterSide, SPType} from "./Character"; +import {Character, CharacterSide, SPType} from "./Character"; export enum ClockMode { HEROES_FILL = "fill", @@ -21,7 +21,6 @@ export interface ConflictState { readonly round: number readonly activeSide: CharacterSide readonly activeCharacterId: string|null - readonly timers: readonly TimerState[] } export interface BaseTimerState { @@ -43,20 +42,34 @@ export interface CountdownTimerState extends BaseTimerState { export type TimerState = CountupTimerState|CountdownTimerState; +export interface StatusEffect { + readonly id: string + readonly name: string + readonly description: string + readonly iconUrl: string +} + export interface GameState { readonly session: SessionState readonly conflict?: ConflictState readonly clocks: readonly Clock[] readonly characters: readonly Character[] + readonly statuses: readonly StatusEffect[] + readonly timers: readonly TimerState[] } -export interface PastState { - // The unix timestamp in ms when changing _away_ from this state. - readonly timestamp: number - readonly logMarkdown: string - readonly state: GameState +export function getClockById(state: GameState, id: string): Clock|undefined { + return state.clocks.find((clock) => clock.id === id) } -export function getCharacterById(state: GameState, id: CharacterId): Character|undefined { +export function getCharacterById(state: GameState, id: string): Character|undefined { return state.characters.find((character) => character.id === id) +} + +export function getStatusById(state: GameState, id: string): StatusEffect|undefined { + return state.statuses.find((status) => status.id === id) +} + +export function getTimerById(state: GameState, id: string): TimerState|undefined { + return state.timers.find((timer) => timer.id === id) } \ No newline at end of file diff --git a/src/model/Messages.ts b/src/model/Messages.ts new file mode 100644 index 0000000..18ed111 --- /dev/null +++ b/src/model/Messages.ts @@ -0,0 +1,66 @@ +import {GameState} from "./GameState"; + +export enum ElementalType { + Fire = "fire", + Water = "water", + Lightning = "lightning", + Ice = "ice", + Earth = "earth", + Wind = "wind", + Light = "light", + Dark = "dark", + Physical = "physical", + Nonelemental = "non-elemental", + Healing = "healing", +} + +export enum Affinity { + Absorbs = "absorbs", + Immune = "immune", + Resistant = "resistant", + Normal = "normal", + Vulnerable = "vulnerable", +} + +export enum NumberSign { + Positive = "+", + Negative = "-", +} + +export enum MeteredResource { + Experience = "EXP", + Health = "HP", + Magic = "MP", + Items = "IP", + Zero = "ZP", + Blood = "BP", + Turns = "TP", + Segments = "Segments", +} +export enum UnmeteredResource { + Fabula = "FP", + Ultima = "UP", + Special = "SP", + Level = "Level", + Materials = "Materials", + Zenit = "Zenit", +} + +export type Resource = MeteredResource|UnmeteredResource + +export const Target = Symbol("target"); +export const Source = Symbol("source"); + +export type Operands = Set + +export interface ParseContext { + readonly source: readonly string[] + readonly target: readonly string[] + readonly game: GameState +} + +export interface MarkdownContext extends ParseContext {} + +export interface MarkdownOutput extends MarkdownContext { + readonly output: string +} \ No newline at end of file