diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index 93d8d70..84dd17d 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -185,18 +185,29 @@ FabulaDSL { ClearOperationAlternate = Operands colon null Resource ClearOperationAlternate2 = Operands colon Resource null - // TODO: continue implementation list from here - + // StatusOrItemCounter/Unwrapped/Wrapped/DeltaWrapped/Delta: numberValue StatusOrItemCounterUnwrapped = "x"? integer StatusOrItemCounterWrapped = "(" StatusOrItemCounterUnwrapped ")" StatusOrItemCounter = StatusOrItemCounterWrapped | StatusOrItemCounterUnwrapped StatusOrItemDeltaWrapped = "(" Delta ")" StatusOrItemDelta = StatusOrItemDeltaWrapped | Delta + + // StatusOrItemAddOperation: operands, identifier, numberValue, evaluate, renderMarkdown StatusOrItemAddOperation = Operands colon plus identifier StatusOrItemCounter? + + // TODO: StatusOrItemRemoveOperation: operands, identifier, evaluate, renderMarkdown StatusOrItemRemoveOperation = Operands colon minus identifier - StatusOrItemCounterDeltaOperation = Operands colon identifier Delta - StatusOrItemCounterDeltaOperationAlternate = Operands colon Delta identifier + + // TODO: StatusOrItemDeltaOperation/Alternate: operands, identifier, numberValue, evaluate, renderMarkdown + StatusOrItemCounterDeltaOperation = Operands colon identifier StatusOrItemDelta + StatusOrItemCounterDeltaOperationAlternate = Operands colon StatusOrItemDelta identifier + + // TODO: Fix conflict with resource mutators (use brackets?) + // TODO: StatusOrItemSetOperation/Alternate: operands, identifier, numberValue, evaluate, renderMarkdown StatusOrItemCounterSetOperation = Operands colon identifier StatusOrItemCounter? + StatusOrItemCounterSetOperationAlternate = Operands colon StatusOrItemCounter identifier + + // TODO: continue implementation list from here notNewlineOrComment = ~newline ~lineCommentStart ~blockCommentStart any textToEndOfLine = notNewlineOrComment+ @@ -300,13 +311,14 @@ FabulaDSL { PrintOperation = printOperator textToEndOfLine PrintOperationWithOperands = Operands colon printOperator textToEndOfLine - Operation = DeltaOperation | DeltaOperationAlternate | DeltaOperationAlternate2 + Operation = StatusOrItemAddOperation | StatusOrItemRemoveOperation + | StatusOrItemCounterDeltaOperation | StatusOrItemCounterDeltaOperationAlternate + | StatusOrItemCounterSetOperation | StatusOrItemCounterSetOperationAlternate + | DeltaOperation | DeltaOperationAlternate | DeltaOperationAlternate2 | SetMeteredOperation | SetMeteredOperationAlternate | SetMeteredOperationAlternate2 | SetValueOperation | SetValueOperationAlternate | SetValueOperationAlternate2 | SetMaxOperation | SetMaxOperationAlternate | SetMaxOperationAlternate2 | ClearOperation | ClearOperationAlternate | ClearOperationAlternate2 - | StatusOrItemAddOperation | StatusOrItemRemoveOperation | StatusOrItemCounterSetOperation - | StatusOrItemCounterDeltaOperation | StatusOrItemCounterDeltaOperationAlternate | SetTargetOperation | SetSourceOperation | FailOperation | PrintOperationWithOperands | PrintOperation silentOperator = "~" diff --git a/src/grammar/interpreter.ts b/src/grammar/interpreter.ts index 7139c5b..0e344eb 100644 --- a/src/grammar/interpreter.ts +++ b/src/grammar/interpreter.ts @@ -24,7 +24,7 @@ import { CharacterStatuses, isZpReady } from "../model/Character"; -import {GameState, TimerDirection} from "../model/GameState"; +import {GameState, lookupIdentifier, TimerDirection} from "../model/GameState"; import { formatMeteredResource, formatResourceDelta, formatResourceMax, formatResourceValue, @@ -266,6 +266,16 @@ interpreter.addAttribute("numberValue", { StatusOrItemCounterWrapped(lParen: TerminalNode, counter: NonterminalNode, _rParen: TerminalNode): number { return (counter as InterpreterNode).numberValue }, + StatusOrItemCounter(counter: NonterminalNode): number { + return (counter as InterpreterNode).numberValue + }, + StatusOrItemDelta(delta: NonterminalNode): number { + return (delta as InterpreterNode).numberValue; + }, + StatusOrItemDeltaWrapped(_lParen: TerminalNode, delta: NonterminalNode, _rParen: TerminalNode): number { + return (delta as InterpreterNode).numberValue; + }, + StatusOrItemAddOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: IterationNode): number { if (counter.numChildren === 0) { return 0 @@ -311,6 +321,9 @@ interpreter.addAttribute("identifier", { StatusOrItemAddOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, _counter: NonterminalNode): string { return (identifier as InterpreterNode).identifier }, + StatusOrItemRemoveOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, identifier: NonterminalNode): string { + return (identifier as InterpreterNode).identifier; + }, StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, _delta: NonterminalNode): string { return (identifier as InterpreterNode).identifier }, @@ -598,6 +611,9 @@ interpreter.addAttribute("operands", { StatusOrItemAddOperation(operands: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _statusOrItem: NonterminalNode, _counter: IterationNode): Operands { return (operands as InterpreterNode).operands }, + StatusOrItemRemoveOperation(operands: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): Operands { + return (operands as InterpreterNode).operands + }, Operands(arg0: NonterminalNode): Operands { return (arg0.asIteration() as InterpreterNode).operands }, @@ -624,7 +640,7 @@ interpreter.addAttribute("operands", { }, _terminal(): never { throw Error(`No idea what to say ${this.ctorName} terminal node's operands value is`) - }, + } }) interpreter.addAttribute("targets", { @@ -1064,6 +1080,20 @@ interpreter.addOperation("evaluate(ctx)", { } } }, + StatusOrItemRemoveOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext { + const ctx = (this as EvaluationNode).args.ctx + const operands = evaluateOperands((this as InterpreterNode).operands, ctx) + const statusOrItemId = (this as InterpreterNode).identifier + + return { + ...ctx, + game: { + ...ctx.game, + characters: ctx.game.characters.map((c) => + operands.has(c.id) ? CharacterStatuses.removeStatus(c, statusOrItemId) : c), + } + } + }, ClearOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext { return EvaluateClear(this as EvaluationNode); }, @@ -1110,7 +1140,7 @@ function RenderDelta(node: MarkdownNode): MarkdownOutput { const affinity = node.affinity const element = node.element const operandsBefore = lookupOperands(node.operands, ctx) - const ctxAfter = EvaluateDelta(node) + const ctxAfter = node.evaluate(ctx) const operandsAfter = lookupOperands(node.operands, ctxAfter) for (const [id, after] of operandsAfter) { @@ -1184,7 +1214,7 @@ function RenderMeteredSet(node: MarkdownNode): MarkdownOutput { const newValue = node.currentValue const newMax = node.maxValue const operandsBefore = lookupOperands(node.operands, ctx) - const ctxAfter = EvaluateDelta(node) + const ctxAfter = node.evaluate(ctx) const operandsAfter = lookupOperands(node.operands, ctxAfter) for (const [id, after] of operandsAfter) { @@ -1261,7 +1291,7 @@ function RenderValueSet(node: MarkdownNode): MarkdownOutput { const resource = node.resource const newValue = node.currentValue const operandsBefore = lookupOperands(node.operands, ctx) - const ctxAfter = EvaluateDelta(node) + const ctxAfter = node.evaluate(ctx) const operandsAfter = lookupOperands(node.operands, ctxAfter) for (const [id, after] of operandsAfter) { @@ -1317,7 +1347,7 @@ function RenderValueSet(node: MarkdownNode): MarkdownOutput { result.push(`${clockText}: [${formatResourceValue(resource ?? MeteredResource.Segments, newValue)}`) break default: - throw Error(`Don't know how to render metered set on ${before.type} ${id}`) + throw Error(`Don't know how to render value set on ${before.type} ${id}`) } } return { @@ -1332,7 +1362,7 @@ function RenderMaxSet(node: MarkdownNode): MarkdownOutput { const resource = node.resource const newMax = node.maxValue const operandsBefore = lookupOperands(node.operands, ctx) - const ctxAfter = EvaluateDelta(node) + const ctxAfter = node.evaluate(ctx) const operandsAfter = lookupOperands(node.operands, ctxAfter) for (const [id, after] of operandsAfter) { @@ -1346,7 +1376,7 @@ function RenderMaxSet(node: MarkdownNode): MarkdownOutput { throw Error(`character ${id} somehow changed type between calls?`) } if (resource && !isMeteredResource(resource)) { - throw Error(`somehow got non-metered resource in metered set`) + throw Error(`somehow got non-metered resource in max set`) } const beforeChar = before.character const afterChar = after.character @@ -1389,12 +1419,12 @@ function RenderMaxSet(node: MarkdownNode): MarkdownOutput { const afterClock = after.clock const clockText = afterClock.text if (resource && !isMeteredResource(resource)) { - throw Error(`somehow got non-metered resource in metered set`) + throw Error(`somehow got non-metered resource in max set`) } result.push(`${clockText}: [${formatResourceMax(resource ?? MeteredResource.Segments, newMax)}`) break default: - throw Error(`Don't know how to render metered set on ${before.type} ${id}`) + throw Error(`Don't know how to render max set on ${before.type} ${id}`) } } return { @@ -1409,7 +1439,7 @@ function RenderClear(node: MarkdownNode): MarkdownOutput { const resource = node.resource const newMax = node.maxValue const operandsBefore = lookupOperands(node.operands, ctx) - const ctxAfter = EvaluateDelta(node) + const ctxAfter = node.evaluate(ctx) const operandsAfter = lookupOperands(node.operands, ctxAfter) for (const [id, after] of operandsAfter) { @@ -1437,6 +1467,44 @@ function RenderClear(node: MarkdownNode): MarkdownOutput { } result.push(`${clockText}: [${formatResourceMax(resource ?? MeteredResource.Segments, newMax)}`) break + default: + throw Error(`Don't know how to render resource clear on ${before.type} ${id}`) + } + } + return { + ...ctxAfter, + output: result.length > 0 ? result.join("\n") : null + } +} + +function RenderStatusAdd(node: MarkdownNode): MarkdownOutput { + const ctx = node.args.ctx + const result: string[] = [] + const identifier = node.identifier + const counter = node.numberValue + const operandsBefore = lookupOperands(node.operands, ctx) + const ctxAfter = node.evaluate(ctx) + const operandsAfter = lookupOperands(node.operands, ctxAfter) + const status = lookupIdentifier(ctxAfter.game, identifier) + const statusName = status.type === "status" ? status.status.name : identifier + + for (const [id, after] of operandsAfter) { + const before = operandsBefore.get(id) + if (!before) { + throw Error("Somehow an object appeared?") + } + switch (before.type) { + case "character": + if (after.type !== "character") { + throw Error(`character ${id} somehow changed type between calls?`) + } + const beforeChar = before.character + const afterChar = after.character + const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name + if (CharacterStatuses.hasStatus(beforeChar, identifier) !== CharacterStatuses.hasStatus(afterChar, identifier)) { + result.push(`${charName}: [+${statusName}${counter === 0 ? "" : `(${counter})`}]`) + } + break default: throw Error(`Don't know how to render metered set on ${before.type} ${id}`) } @@ -1501,6 +1569,9 @@ interpreter.addOperation("renderMarkdown(ctx)", { ClearOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput { return RenderClear(this as MarkdownNode); }, + StatusOrItemAddOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: IterationNode): MarkdownOutput { + return RenderStatusAdd(this as MarkdownNode); + }, _iter(...children: readonly Node[]): MarkdownOutput { let ctx = (this as MarkdownNode).args.ctx diff --git a/src/model/Character.ts b/src/model/Character.ts index 05b2bbc..3887b95 100644 --- a/src/model/Character.ts +++ b/src/model/Character.ts @@ -537,15 +537,19 @@ export function applyCharacterPrivacy(character: Character): Character|null { } export const CharacterStatuses = { + hasStatus(c: Character, id: string): number|null { + const status = c.statuses?.find((s) => s.id === id) + return status ? status.count : null + }, addStatus(c: Character, id: string, stacks: number): Character { - if (!c.statuses || c.statuses.some((s) => s.id === id)) { + if (!c.statuses || this.hasStatus(c, id) !== null) { return c } else { return this.setStatus(c, id, stacks) } }, setStatus(c: Character, id: string, stacks: number): Character { - if (!c.statuses) { + if (!c.statuses || this.hasStatus(c, id) === stacks) { return c } if (c.statuses.some((s) => s.id === id)) { @@ -579,12 +583,12 @@ export const CharacterStatuses = { } }, removeStatus(c: Character, id: string): Character { - if (!c.statuses || !c.statuses.some((s) => s.id === id)) { + if (this.hasStatus(c, id) === null) { return c } else { return { ...c, - statuses: c.statuses.filter((s) => s.id !== id) + statuses: c.statuses ? c.statuses.filter((s) => s.id !== id) : [] } } } diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 9001931..6b6271e 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -50,6 +50,12 @@ function App() { athetyz: - MP linnet: HP - &: Lv - + gale, echo, aelica: +Digesting + gale: -Digesting + flow: +Digesting 2 + echo, flow: +Digesting 3 + calor, flow, echo: Digesting +1 + gale, echo: +1 Digesting End`) } } catch (ex) {