You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1650 lines
76 KiB
1650 lines
76 KiB
import grammar from "./grammar.ohm-bundle";
|
|
import {
|
|
Affinity,
|
|
ElementalType,
|
|
evaluateOperands,
|
|
EvaluationContext,
|
|
FailReason,
|
|
lookupOperands,
|
|
MarkdownContext,
|
|
MarkdownOutput,
|
|
NumberSign,
|
|
OperandItems,
|
|
Operands,
|
|
OperandSets,
|
|
Source,
|
|
Target,
|
|
} from "../model/Messages";
|
|
import {IterationNode, Node, NonterminalNode, TerminalNode} from "ohm-js";
|
|
import {
|
|
applyCharacterPrivacy,
|
|
CharacterPrivacy,
|
|
CharacterResources,
|
|
CharacterSide,
|
|
CharacterStatuses,
|
|
isZpReady
|
|
} from "../model/Character";
|
|
import {GameState, lookupIdentifier, TimerDirection} from "../model/GameState";
|
|
import {
|
|
formatMeteredResource,
|
|
formatResourceDelta, formatResourceMax, formatResourceValue,
|
|
isMeteredResource,
|
|
isUnmeteredResource,
|
|
MeteredResource,
|
|
Resource,
|
|
UnmeteredResource
|
|
} from "../model/Resources";
|
|
import {ClockMode, ClockResources} from "../model/Clock";
|
|
import {isDefined} from "../types/type_check";
|
|
|
|
export const interpreter = grammar.createSemantics()
|
|
|
|
export interface InterpreterNode extends Node {
|
|
readonly element: ElementalType|null
|
|
readonly affinity: Affinity
|
|
readonly sign: NumberSign
|
|
readonly numberValue: number
|
|
readonly textValue: string
|
|
readonly identifier: string
|
|
readonly resource: Resource|null
|
|
readonly operands: Operands
|
|
readonly targets: Operands
|
|
readonly sources: Operands
|
|
readonly silenced: boolean
|
|
readonly currentValue: number
|
|
readonly maxValue: number
|
|
readonly failReason: FailReason
|
|
readonly side: CharacterSide
|
|
readonly privacy: CharacterPrivacy
|
|
|
|
// TODO: Implement all of the things listed below.
|
|
// TODO: create rules for these to describe clocks/timers/items/statuses/characters and implement them
|
|
readonly nameText: string
|
|
readonly descriptionText: string
|
|
// TODO: use for portraits, status icons, backdrops, BGM, and SFX
|
|
readonly url: string
|
|
readonly clockMode: ClockMode
|
|
readonly timerDirection: TimerDirection
|
|
readonly 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: EvaluationContext): EvaluationContext
|
|
renderMarkdown(ctx: MarkdownContext): MarkdownOutput
|
|
}
|
|
|
|
export interface EvaluationNode extends InterpreterNode {
|
|
readonly args: {readonly ctx: EvaluationContext}
|
|
}
|
|
|
|
export interface MarkdownNode extends InterpreterNode {
|
|
readonly args: {readonly ctx: MarkdownContext}
|
|
}
|
|
|
|
interpreter.addAttribute<ElementalType|null>("element", {
|
|
fireElement(_: TerminalNode): ElementalType.Fire {
|
|
return ElementalType.Fire
|
|
},
|
|
waterElement(_: TerminalNode): ElementalType.Water {
|
|
return ElementalType.Water
|
|
},
|
|
lightningElement(_: TerminalNode): ElementalType.Lightning {
|
|
return ElementalType.Lightning
|
|
},
|
|
iceElement(_: TerminalNode): ElementalType.Ice {
|
|
return ElementalType.Ice
|
|
},
|
|
earthElement(_: TerminalNode): ElementalType.Earth {
|
|
return ElementalType.Earth
|
|
},
|
|
windElement(_: TerminalNode): ElementalType.Wind {
|
|
return ElementalType.Wind
|
|
},
|
|
lightElement(_: TerminalNode): ElementalType.Light {
|
|
return ElementalType.Light
|
|
},
|
|
darkElement(_: TerminalNode): ElementalType.Dark {
|
|
return ElementalType.Dark
|
|
},
|
|
physicalElement(_: TerminalNode): ElementalType.Physical {
|
|
return ElementalType.Physical
|
|
},
|
|
nonElement(_: TerminalNode): ElementalType.Nonelemental {
|
|
return ElementalType.Nonelemental
|
|
},
|
|
healingElement(_: TerminalNode): ElementalType.Healing {
|
|
return ElementalType.Healing
|
|
},
|
|
elementalType(elementNode: NonterminalNode): ElementalType {
|
|
const element = (elementNode as InterpreterNode).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 InterpreterNode).element
|
|
},
|
|
DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): ElementalType | null {
|
|
return (elementalType as InterpreterNode).element
|
|
},
|
|
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): ElementalType | null {
|
|
return (elementalType as InterpreterNode).element
|
|
},
|
|
_iter(...children: readonly Node[]): ElementalType|null {
|
|
if (this.isOptional() && children.length === 0) {
|
|
return null
|
|
} else if (this.isOptional() && children.length === 1) {
|
|
return (children[0] as InterpreterNode).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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<Affinity>("affinity", {
|
|
absorbAffinity(_: TerminalNode): Affinity.Absorbs {
|
|
return Affinity.Absorbs
|
|
},
|
|
immuneAffinity(_: TerminalNode): Affinity.Immune {
|
|
return Affinity.Immune
|
|
},
|
|
resistAffinity(_: TerminalNode): Affinity.Resistant {
|
|
return Affinity.Resistant
|
|
},
|
|
vulnerableAffinity(_: TerminalNode): Affinity.Vulnerable {
|
|
return Affinity.Vulnerable
|
|
},
|
|
normalAffinity(_: TerminalNode): Affinity.Normal {
|
|
return Affinity.Normal
|
|
},
|
|
affinity(affinityNode: NonterminalNode): Affinity {
|
|
return (affinityNode as InterpreterNode).affinity
|
|
},
|
|
DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, _elementalType: IterationNode): Affinity {
|
|
return (affinity as InterpreterNode).affinity
|
|
},
|
|
DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, _resource: IterationNode, _elementalType: IterationNode): Affinity {
|
|
return (affinity as InterpreterNode).affinity
|
|
},
|
|
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, _elementalType: IterationNode): Affinity {
|
|
return (affinity as InterpreterNode).affinity
|
|
},
|
|
_iter(...children: readonly Node[]): Affinity {
|
|
if (this.isOptional() && children.length === 0) {
|
|
return Affinity.Normal
|
|
} else if (this.isOptional() && children.length === 1) {
|
|
return (children[0] as InterpreterNode).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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<NumberSign>("sign", {
|
|
deltaOperator(operator: NonterminalNode): NumberSign {
|
|
return (operator as InterpreterNode).sign
|
|
},
|
|
plus(_: TerminalNode): NumberSign.Positive {
|
|
return NumberSign.Positive
|
|
},
|
|
minus(_: TerminalNode): NumberSign.Negative {
|
|
return NumberSign.Negative
|
|
},
|
|
_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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<number>("numberValue", {
|
|
integer(digits: IterationNode): number {
|
|
return parseInt(digits.sourceString)
|
|
},
|
|
Delta(sign: NonterminalNode, integer: NonterminalNode): number {
|
|
const number = (integer as InterpreterNode).numberValue
|
|
switch ((sign as InterpreterNode).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 InterpreterNode).numberValue
|
|
},
|
|
DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, _affinity: IterationNode, _resource: IterationNode, _elementalType: IterationNode): number {
|
|
return (delta as InterpreterNode).numberValue
|
|
},
|
|
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, _affinity: IterationNode, _elementalType: IterationNode): number {
|
|
return (delta as InterpreterNode).numberValue
|
|
},
|
|
MaxValue(separator: NonterminalNode, integer: NonterminalNode): number {
|
|
return (integer as InterpreterNode).numberValue;
|
|
},
|
|
SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode): number {
|
|
return (integer as InterpreterNode).numberValue;
|
|
},
|
|
SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode, _resource: NonterminalNode): number {
|
|
return (integer as InterpreterNode).numberValue;
|
|
},
|
|
SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, integer: NonterminalNode): number {
|
|
return (integer as InterpreterNode).numberValue;
|
|
},
|
|
SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode): number {
|
|
return (maxvalue as InterpreterNode).numberValue;
|
|
},
|
|
SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, _resource: NonterminalNode): number {
|
|
return (maxvalue as InterpreterNode).numberValue;
|
|
},
|
|
SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, maxvalue: NonterminalNode): number {
|
|
return (maxvalue as InterpreterNode).numberValue;
|
|
},
|
|
StatusOrItemCounterUnwrapped(x: IterationNode, integer: NonterminalNode): number {
|
|
return (integer as InterpreterNode).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
|
|
}
|
|
return (counter.child(0)).numberValue
|
|
},
|
|
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, delta: NonterminalNode): number {
|
|
return (delta as InterpreterNode).numberValue
|
|
},
|
|
StatusOrItemCounterDeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, _identifier: NonterminalNode): number {
|
|
return (delta as InterpreterNode).numberValue
|
|
},
|
|
StatusOrItemCounterDeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): number {
|
|
return (delta as InterpreterNode).numberValue
|
|
},
|
|
StatusOrItemCounterSetOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, counter: NonterminalNode): number {
|
|
return (counter as InterpreterNode).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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<string>("textValue", {
|
|
identifier(_: TerminalNode, __: NonterminalNode): string {
|
|
return this.sourceString
|
|
},
|
|
_iter(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} iteration node's text value is`)
|
|
},
|
|
_nonterminal(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} nonterminal node's text value is`)
|
|
},
|
|
_terminal(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} terminal node's text value is`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<string>("identifier", {
|
|
identifier(_: TerminalNode, __: IterationNode): string {
|
|
return this.sourceString
|
|
},
|
|
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, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, _delta: NonterminalNode): string {
|
|
return (identifier as InterpreterNode).identifier
|
|
},
|
|
StatusOrItemCounterDeltaOperationAlternate(operands: NonterminalNode, _sep: NonterminalNode, _delta: NonterminalNode, identifier: NonterminalNode): string {
|
|
return (identifier as InterpreterNode).identifier
|
|
},
|
|
StatusOrItemCounterDeltaOperationAlternate2(operands: NonterminalNode, _sep: NonterminalNode, identifier: NonterminalNode, _delta: NonterminalNode): string {
|
|
return (identifier as InterpreterNode).identifier
|
|
},
|
|
StatusOrItemCounterSetOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, _delta: NonterminalNode): string {
|
|
return (identifier as InterpreterNode).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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<Resource|null>("resource", {
|
|
BpResource(_: NonterminalNode): MeteredResource.Blood {
|
|
return MeteredResource.Blood
|
|
},
|
|
FabulaResource(_: NonterminalNode): UnmeteredResource.Fabula {
|
|
return UnmeteredResource.Fabula;
|
|
},
|
|
HpResource(_: NonterminalNode): MeteredResource.Health {
|
|
return MeteredResource.Health;
|
|
},
|
|
IpResource(_: NonterminalNode): MeteredResource.Items {
|
|
return MeteredResource.Items;
|
|
},
|
|
LevelResource(_: NonterminalNode): UnmeteredResource.Level {
|
|
return UnmeteredResource.Level;
|
|
},
|
|
MaterialsResource(_: NonterminalNode): UnmeteredResource.Materials {
|
|
return UnmeteredResource.Materials;
|
|
},
|
|
MeteredResource(res: NonterminalNode): MeteredResource {
|
|
const r = (res as InterpreterNode).resource;
|
|
if (!r || !isMeteredResource(r)) {
|
|
throw Error(`unexpected null or unmetered resource ${r} in metered resource`)
|
|
}
|
|
return r
|
|
},
|
|
MoneyResource(_: NonterminalNode): UnmeteredResource.Zenit {
|
|
return UnmeteredResource.Zenit;
|
|
},
|
|
MpResource(_: NonterminalNode): MeteredResource.Magic {
|
|
return MeteredResource.Magic;
|
|
},
|
|
Resource(res: NonterminalNode): Resource {
|
|
const r = (res as InterpreterNode).resource;
|
|
if (r === null) {
|
|
throw Error("unexpected null resource in resource node")
|
|
}
|
|
return r
|
|
},
|
|
SegmentResource(_: NonterminalNode): MeteredResource.Segments {
|
|
return MeteredResource.Segments
|
|
},
|
|
SpResource(type: NonterminalNode): UnmeteredResource.Special|UnmeteredResource.Fabula|UnmeteredResource.Ultima {
|
|
const r = (type as InterpreterNode).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(_: TerminalNode): UnmeteredResource.Special {
|
|
return UnmeteredResource.Special;
|
|
},
|
|
TpResource(_: TerminalNode): MeteredResource.Turns {
|
|
return MeteredResource.Turns;
|
|
},
|
|
UltimaResource(_: TerminalNode): Resource {
|
|
return UnmeteredResource.Ultima;
|
|
},
|
|
OrderResource(_: TerminalNode): UnmeteredResource.Order {
|
|
return UnmeteredResource.Order
|
|
},
|
|
UnmeteredResource(res: NonterminalNode): UnmeteredResource {
|
|
const r = (res as InterpreterNode).resource;
|
|
if (!r || !isUnmeteredResource(r)) {
|
|
throw Error(`unexpected null or metered resource ${r} in max operation`)
|
|
}
|
|
return r
|
|
},
|
|
XpResource(_: TerminalNode): MeteredResource.Experience {
|
|
return MeteredResource.Experience;
|
|
},
|
|
ZpResource(_: TerminalNode): 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 InterpreterNode).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 InterpreterNode).resource
|
|
},
|
|
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _elementalType: IterationNode): Resource {
|
|
const r = (resource as InterpreterNode).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 InterpreterNode).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 InterpreterNode).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 InterpreterNode).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 InterpreterNode).resource;
|
|
if (!r || !isMeteredResource(r)) {
|
|
throw Error(`unexpected null or unmetered resource ${r} in max operation`)
|
|
}
|
|
return r
|
|
},
|
|
SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, resource: NonterminalNode): MeteredResource {
|
|
const r = (resource as InterpreterNode).resource;
|
|
if (!r || !isMeteredResource(r)) {
|
|
throw Error(`unexpected null or unmetered resource ${r} in max operation`)
|
|
}
|
|
return r
|
|
},
|
|
SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, _maxvalue: NonterminalNode): MeteredResource {
|
|
const r = (resource as InterpreterNode).resource;
|
|
if (!r || !isMeteredResource(r)) {
|
|
throw Error(`unexpected null or unmetered resource ${r} in max operation`)
|
|
}
|
|
return r
|
|
},
|
|
|
|
_iter(...children: readonly Node[]): Resource|null {
|
|
if (this.isOptional() && children.length === 0) {
|
|
return null
|
|
} else if (this.isOptional() && children.length === 1) {
|
|
return (children[0] as InterpreterNode).resource
|
|
} else {
|
|
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 InterpreterNode).resource;
|
|
if (!r || !isMeteredResource(r)) {
|
|
throw Error(`unexpected null or unmetered resource ${r} in metered set`)
|
|
}
|
|
return r
|
|
},
|
|
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, resource: NonterminalNode): MeteredResource {
|
|
const r = (resource as InterpreterNode).resource;
|
|
if (!r || !isMeteredResource(r)) {
|
|
throw Error(`unexpected null or unmetered resource ${r} in metered set`)
|
|
}
|
|
return r
|
|
},
|
|
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, _value: NonterminalNode): MeteredResource {
|
|
const r = (resource as InterpreterNode).resource;
|
|
if (!r || !isMeteredResource(r)) {
|
|
throw Error(`unexpected null or unmetered resource ${r} in metered set`)
|
|
}
|
|
return r
|
|
},
|
|
ClearOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, _colon: NonterminalNode, _nul: NonterminalNode): Resource {
|
|
const r = (resource as InterpreterNode).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 InterpreterNode).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 InterpreterNode).resource
|
|
if (r === null) {
|
|
throw Error("unexpected null resource in required resource node")
|
|
}
|
|
return r
|
|
},
|
|
})
|
|
|
|
interpreter.addAttribute<Operands>("operands", {
|
|
CompleteOperation(silence: IterationNode, operation: NonterminalNode, _terminator: NonterminalNode): Operands {
|
|
return (operation as InterpreterNode).operands
|
|
},
|
|
Operation(operation: NonterminalNode): Operands {
|
|
return (operation as InterpreterNode).operands
|
|
},
|
|
ClearOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, _colon: NonterminalNode, _nul: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
ClearOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _nul: NonterminalNode, _resource: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
ClearOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _nul: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands;
|
|
},
|
|
DeltaOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, colon: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _element: IterationNode): Operands {
|
|
return (operands as InterpreterNode).operands;
|
|
},
|
|
DeltaOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _resource: IterationNode, _element: IterationNode): Operands {
|
|
return (operands as InterpreterNode).operands;
|
|
},
|
|
DeltaOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _element: IterationNode): Operands {
|
|
return (operands as InterpreterNode).operands;
|
|
},
|
|
FailOperation(operands: NonterminalNode, _colon: NonterminalNode, _fail: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands;
|
|
},
|
|
PrintOperation(_: NonterminalNode, __: NonterminalNode): Set<never> {
|
|
return OperandItems<never>()
|
|
},
|
|
SetMaxOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, _colon: NonterminalNode, _max: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetMaxOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _max: NonterminalNode, _resource: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetMaxOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _max: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetMeteredOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, _colon: NonterminalNode, _meter: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetMeteredOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _meter: NonterminalNode, _resource: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetMeteredOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _meter: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetSourceOperation(source: NonterminalNode, operands: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetTargetOperation(target: NonterminalNode, operands: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetValueOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, _colon: NonterminalNode, _value: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetValueOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _value: NonterminalNode, _resource: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
SetValueOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _value: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, _wordSep: NonterminalNode, _statusOrItem: NonterminalNode, _sep: NonterminalNode, _delta: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
StatusOrItemCounterDeltaOperationAlternate(operands: NonterminalNode, _sep: NonterminalNode, _delta: NonterminalNode, _statusOrItem: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
StatusOrItemCounterDeltaOperationAlternate2(operands: NonterminalNode, _sep: NonterminalNode, _statusOrItem: NonterminalNode, _delta: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).operands
|
|
},
|
|
StatusOrItemCounterSetOperation(operands: NonterminalNode, _wordSep: NonterminalNode, _statusOrItem: NonterminalNode, _sep: NonterminalNode, _value: NonterminalNode): Operands {
|
|
return (operands as InterpreterNode).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
|
|
},
|
|
_iter(...children: readonly Node[]): Operands {
|
|
return OperandSets(children.map((child) => (child as InterpreterNode).operands))
|
|
},
|
|
operand(operand: TerminalNode): Operands {
|
|
return (operand as InterpreterNode).operands
|
|
},
|
|
identifier(_: TerminalNode, __: NonterminalNode): Set<string> {
|
|
return OperandItems((this as InterpreterNode).identifier)
|
|
},
|
|
target(_: TerminalNode): Set<typeof Target> {
|
|
return OperandItems(Target)
|
|
},
|
|
source(_: TerminalNode): Set<typeof Source> {
|
|
return OperandItems(Source);
|
|
},
|
|
null(_: TerminalNode): Set<never> {
|
|
return OperandItems()
|
|
},
|
|
_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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<Operands>("targets", {
|
|
Block(start: NonterminalNode, _terminator: NonterminalNode, _code: NonterminalNode, _end: NonterminalNode, _terminator2: NonterminalNode): Operands {
|
|
return (start as InterpreterNode).targets
|
|
},
|
|
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, target: IterationNode, _title: NonterminalNode): Operands {
|
|
return (target as InterpreterNode).operands
|
|
},
|
|
SetTargetOperation(_arg0: NonterminalNode, _arg1: NonterminalNode): Operands {
|
|
return (this as InterpreterNode).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`)
|
|
},
|
|
})
|
|
|
|
interpreter.addAttribute<Operands>("sources", {
|
|
Block(start: NonterminalNode, _terminator: NonterminalNode, _code: NonterminalNode, _end: NonterminalNode, _terminator2: NonterminalNode): Operands {
|
|
return (start as InterpreterNode).sources
|
|
},
|
|
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, _target: IterationNode, _title: NonterminalNode): Operands {
|
|
return (source as InterpreterNode).operands
|
|
},
|
|
SetSourceOperation(_arg0: NonterminalNode, _arg1: NonterminalNode): Operands {
|
|
return (this as InterpreterNode).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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<boolean>("silenced", {
|
|
Block(start: NonterminalNode, _terminator: NonterminalNode, _code: NonterminalNode, _end: NonterminalNode, _terminator2: NonterminalNode): boolean {
|
|
return (start as InterpreterNode).silenced
|
|
},
|
|
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, target: IterationNode, _title: NonterminalNode): boolean {
|
|
return (target as InterpreterNode).silenced
|
|
},
|
|
CompleteOperation(silenced: IterationNode, _operation: NonterminalNode, _terminator: NonterminalNode): boolean {
|
|
return (silenced as InterpreterNode).silenced
|
|
},
|
|
silentOperator(_: TerminalNode): 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 InterpreterNode).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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<number>("currentValue", {
|
|
SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode): number {
|
|
return (integer as InterpreterNode).numberValue;
|
|
},
|
|
SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode, _resource: NonterminalNode): number {
|
|
return (integer as InterpreterNode).numberValue;
|
|
},
|
|
SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, integer: NonterminalNode): number {
|
|
return (integer as InterpreterNode).numberValue;
|
|
},
|
|
MeteredValue(current: NonterminalNode, _max: NonterminalNode): number {
|
|
return (current as InterpreterNode).numberValue
|
|
},
|
|
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): number {
|
|
return (value as InterpreterNode).currentValue;
|
|
},
|
|
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, _resource: NonterminalNode): number {
|
|
return (value as InterpreterNode).currentValue;
|
|
},
|
|
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): number {
|
|
return (value as InterpreterNode).currentValue;
|
|
},
|
|
StatusOrItemAddOperation(operands: NonterminalNode, sep: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
|
|
return (counter as InterpreterNode).numberValue
|
|
},
|
|
StatusOrItemCounterSetOperation(operands: NonterminalNode, wordSep: NonterminalNode, identifier: NonterminalNode, sep: NonterminalNode, counter: NonterminalNode): number {
|
|
return (counter as InterpreterNode).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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<number>("maxValue", {
|
|
MaxValue(_sep: TerminalNode, _num: NonterminalNode) {
|
|
return (this as InterpreterNode).numberValue
|
|
},
|
|
SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode): number {
|
|
return (maxvalue as InterpreterNode).maxValue;
|
|
},
|
|
SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, _resource: NonterminalNode): number {
|
|
return (maxvalue as InterpreterNode).maxValue;
|
|
},
|
|
SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, maxvalue: NonterminalNode): number {
|
|
return (maxvalue as InterpreterNode).maxValue;
|
|
},
|
|
MeteredValue(current: NonterminalNode, max: NonterminalNode): number {
|
|
return (max as InterpreterNode).maxValue
|
|
},
|
|
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): number {
|
|
return (value as InterpreterNode).maxValue;
|
|
},
|
|
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, _resource: NonterminalNode): number {
|
|
return (value as InterpreterNode).maxValue;
|
|
},
|
|
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): number {
|
|
return (value as InterpreterNode).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`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<FailReason>("failReason", {
|
|
avoid(_: TerminalNode): FailReason.Avoid {
|
|
return FailReason.Avoid
|
|
},
|
|
dodge(_: TerminalNode): FailReason.Dodge {
|
|
return FailReason.Dodge
|
|
},
|
|
miss(_: TerminalNode): FailReason.Miss {
|
|
return FailReason.Miss
|
|
},
|
|
resist(_: TerminalNode): FailReason.Resist {
|
|
return FailReason.Resist
|
|
},
|
|
fail(_: TerminalNode): FailReason.Fail {
|
|
return FailReason.Fail
|
|
},
|
|
block(_: TerminalNode): FailReason.Block {
|
|
return FailReason.Block
|
|
},
|
|
parry(_: TerminalNode): FailReason.Parry {
|
|
return FailReason.Parry
|
|
},
|
|
FailReason(reason: NonterminalNode): FailReason {
|
|
return (reason as InterpreterNode).failReason
|
|
},
|
|
FailOperation(operands: NonterminalNode, colon: NonterminalNode, reason: NonterminalNode): FailReason {
|
|
return (reason as InterpreterNode).failReason
|
|
},
|
|
_iter(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} iteration node's fail reason is`)
|
|
},
|
|
_nonterminal(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} nonterminal node's fail reason is`)
|
|
},
|
|
_terminal(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} terminal node's fail reason is`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<CharacterSide>("side", {
|
|
characterSide(side: NonterminalNode): CharacterSide {
|
|
return (side as InterpreterNode).side
|
|
},
|
|
enemySide(_: TerminalNode): CharacterSide.Enemy {
|
|
return CharacterSide.Enemy
|
|
},
|
|
allySide(_: TerminalNode): CharacterSide.Ally {
|
|
return CharacterSide.Ally
|
|
},
|
|
_iter(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} iteration node's loyalties are`)
|
|
},
|
|
_nonterminal(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} nonterminal node's loyalties are`)
|
|
},
|
|
_terminal(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} terminal node's loyalties are`)
|
|
}
|
|
})
|
|
|
|
interpreter.addAttribute<CharacterPrivacy>("privacy", {
|
|
Privacy(level: NonterminalNode): CharacterPrivacy {
|
|
return (level as InterpreterNode).privacy
|
|
},
|
|
AllyPrivacy(_: TerminalNode): CharacterPrivacy.Friend {
|
|
return CharacterPrivacy.Friend
|
|
},
|
|
FullScanPrivacy(_: NonterminalNode, __: NonterminalNode, ___: NonterminalNode): CharacterPrivacy.FullyScannedEnemy {
|
|
return CharacterPrivacy.FullyScannedEnemy
|
|
},
|
|
ScanPrivacy(_: NonterminalNode, __: NonterminalNode, ___: NonterminalNode): CharacterPrivacy.ScannedEnemy {
|
|
return CharacterPrivacy.ScannedEnemy
|
|
},
|
|
LightScanPrivacy(_: NonterminalNode, __: NonterminalNode, ___: NonterminalNode): CharacterPrivacy.LightlyScannedEnemy {
|
|
return CharacterPrivacy.LightlyScannedEnemy
|
|
},
|
|
NotScannedPrivacy(_: NonterminalNode, __: NonterminalNode, ___: NonterminalNode): CharacterPrivacy.UnscannedEnemy {
|
|
return CharacterPrivacy.UnscannedEnemy
|
|
},
|
|
SecretPrivacy(_: TerminalNode): CharacterPrivacy.SecretiveEnemy {
|
|
return CharacterPrivacy.SecretiveEnemy
|
|
},
|
|
HiddenPrivacy(_: TerminalNode): CharacterPrivacy.Hidden {
|
|
return CharacterPrivacy.Hidden
|
|
},
|
|
_iter(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} iteration node's privacy is`)
|
|
},
|
|
_nonterminal(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} nonterminal node's privacy is`)
|
|
},
|
|
_terminal(): never {
|
|
throw Error(`No idea what to say ${this.ctorName} terminal node's privacy is`)
|
|
}
|
|
})
|
|
|
|
function EvaluateDelta(node: EvaluationNode): EvaluationContext {
|
|
const ctx = node.args.ctx
|
|
const operands = evaluateOperands(node.operands, ctx)
|
|
const resource = node.resource
|
|
const delta = node.numberValue
|
|
|
|
if (delta === 0 || operands.size === 0) {
|
|
return ctx
|
|
}
|
|
|
|
return {
|
|
...ctx,
|
|
game: {
|
|
...ctx.game,
|
|
characters: ctx.game.characters.map((c) =>
|
|
operands.has(c.id) ? CharacterResources.applyDelta(c, resource ?? MeteredResource.Health, delta) : c),
|
|
clocks: ctx.game.clocks.map((c) =>
|
|
operands.has(c.id) ? ClockResources.applyDelta(c, resource ?? MeteredResource.Segments, delta) : c),
|
|
}
|
|
}
|
|
}
|
|
|
|
function EvaluateMeteredSet(node: EvaluationNode): EvaluationContext {
|
|
const ctx = node.args.ctx
|
|
const operands = evaluateOperands(node.operands, ctx)
|
|
const resource = node.resource
|
|
const current = node.currentValue
|
|
const max = node.maxValue
|
|
|
|
if (operands.size === 0) {
|
|
return ctx
|
|
}
|
|
|
|
return {
|
|
...ctx,
|
|
game: {
|
|
...ctx.game,
|
|
characters: ctx.game.characters.map((c) =>
|
|
operands.has(c.id) ? CharacterResources.setMetered(c, resource ?? MeteredResource.Health, current, max) : c),
|
|
clocks: ctx.game.clocks.map((c) =>
|
|
operands.has(c.id) ? ClockResources.setMetered(c, resource ?? MeteredResource.Segments, current, max) : c),
|
|
}
|
|
}
|
|
}
|
|
|
|
function EvaluateValueSet(node: EvaluationNode): EvaluationContext {
|
|
const ctx = node.args.ctx
|
|
const operands = evaluateOperands(node.operands, ctx)
|
|
const resource = node.resource
|
|
const current = node.currentValue
|
|
|
|
if (operands.size === 0) {
|
|
return ctx
|
|
}
|
|
|
|
return {
|
|
...ctx,
|
|
game: {
|
|
...ctx.game,
|
|
characters: ctx.game.characters.map((c) =>
|
|
operands.has(c.id) ? CharacterResources.setValue(c, resource ?? MeteredResource.Health, current) : c),
|
|
clocks: ctx.game.clocks.map((c) =>
|
|
operands.has(c.id) ? ClockResources.setValue(c, resource ?? MeteredResource.Segments, current) : c),
|
|
}
|
|
}
|
|
}
|
|
|
|
function EvaluateMaxSet(node: EvaluationNode): EvaluationContext {
|
|
const ctx = node.args.ctx
|
|
const operands = evaluateOperands(node.operands, ctx)
|
|
const resource = node.resource
|
|
const max = node.maxValue
|
|
|
|
if (operands.size === 0) {
|
|
return ctx
|
|
}
|
|
|
|
return {
|
|
...ctx,
|
|
game: {
|
|
...ctx.game,
|
|
characters: ctx.game.characters.map((c) =>
|
|
operands.has(c.id) ? CharacterResources.setMax(c, resource ?? MeteredResource.Health, max) : c),
|
|
clocks: ctx.game.clocks.map((c) =>
|
|
operands.has(c.id) ? ClockResources.setMax(c, resource ?? MeteredResource.Segments, max) : c),
|
|
}
|
|
}
|
|
}
|
|
|
|
function EvaluateClear(node: EvaluationNode): EvaluationContext {
|
|
const ctx = node.args.ctx
|
|
const operands = evaluateOperands(node.operands, ctx)
|
|
const resource = node.resource
|
|
|
|
if (operands.size === 0 || !resource) {
|
|
return ctx
|
|
}
|
|
|
|
return {
|
|
...ctx,
|
|
game: {
|
|
...ctx.game,
|
|
characters: ctx.game.characters.map((c) =>
|
|
operands.has(c.id) ? CharacterResources.clear(c, resource) : c),
|
|
clocks: ctx.game.clocks.map((c) =>
|
|
operands.has(c.id) ? ClockResources.clear(c, resource) : c),
|
|
}
|
|
}
|
|
}
|
|
|
|
function EvaluateStatusCounterDelta(node: EvaluationNode): EvaluationContext {
|
|
const ctx = node.args.ctx
|
|
const operands = evaluateOperands(node.operands, ctx)
|
|
const statusOrItemId = node.identifier
|
|
const stacks = node.numberValue
|
|
|
|
return {
|
|
...ctx,
|
|
game: {
|
|
...ctx.game,
|
|
characters: ctx.game.characters.map((c) =>
|
|
operands.has(c.id) ? CharacterStatuses.applyStatusDelta(c, statusOrItemId, stacks) : c),
|
|
}
|
|
}
|
|
}
|
|
|
|
interpreter.addOperation<EvaluationContext>("evaluate(ctx)", {
|
|
CodeSegment(items: IterationNode): EvaluationContext {
|
|
let ctx = (this as EvaluationNode).args.ctx
|
|
return (items as InterpreterNode).evaluate(ctx)
|
|
},
|
|
EmptyLines(_: TerminalNode): EvaluationContext {
|
|
// Has no effect
|
|
return (this as EvaluationNode).args.ctx
|
|
},
|
|
Block(start: NonterminalNode, terminator: NonterminalNode, code: NonterminalNode,
|
|
_end: NonterminalNode, _finalTerminator: NonterminalNode): EvaluationContext {
|
|
let ctx = (this as EvaluationNode).args.ctx
|
|
ctx = (start as InterpreterNode).evaluate(ctx)
|
|
ctx = (code as InterpreterNode).evaluate(ctx)
|
|
return ctx
|
|
},
|
|
BlockStart(silentOperator: IterationNode, beginKeyword: NonterminalNode,
|
|
source: IterationNode, target: IterationNode,
|
|
_title: IterationNode): EvaluationContext {
|
|
let ctx = (this as EvaluationNode).args.ctx
|
|
ctx = (source as InterpreterNode).evaluate(ctx)
|
|
ctx = (target as InterpreterNode).evaluate(ctx)
|
|
return ctx
|
|
},
|
|
CompleteOperation(silenced: IterationNode, operation: NonterminalNode, _terminator: NonterminalNode): EvaluationContext {
|
|
let ctx = (this as EvaluationNode).args.ctx
|
|
ctx = (operation as InterpreterNode).evaluate(ctx)
|
|
return ctx
|
|
},
|
|
Operation(operator: NonterminalNode): EvaluationContext {
|
|
let ctx = (this as EvaluationNode).args.ctx
|
|
return (operator as InterpreterNode).evaluate(ctx)
|
|
},
|
|
DeltaOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode, _arg5: IterationNode, _arg6: IterationNode): EvaluationContext {
|
|
return EvaluateDelta(this as EvaluationNode)
|
|
},
|
|
DeltaOperationAlternate(_operators: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _res: IterationNode, _element: IterationNode): EvaluationContext {
|
|
return EvaluateDelta(this as EvaluationNode)
|
|
},
|
|
DeltaOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: IterationNode, _arg5: IterationNode): EvaluationContext {
|
|
return EvaluateDelta(this as EvaluationNode)
|
|
},
|
|
SetMeteredOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
|
|
return EvaluateMeteredSet(this as EvaluationNode)
|
|
},
|
|
SetMeteredOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateMeteredSet(this as EvaluationNode)
|
|
},
|
|
SetMeteredOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateMeteredSet(this as EvaluationNode)
|
|
},
|
|
SetValueOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
|
|
return EvaluateValueSet(this as EvaluationNode);
|
|
},
|
|
SetValueOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateValueSet(this as EvaluationNode);
|
|
},
|
|
SetValueOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateValueSet(this as EvaluationNode);
|
|
},
|
|
SetMaxOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
|
|
return EvaluateMaxSet(this as EvaluationNode);
|
|
},
|
|
SetMaxOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateMaxSet(this as EvaluationNode);
|
|
},
|
|
SetMaxOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateMaxSet(this as EvaluationNode);
|
|
},
|
|
StatusOrItemAddOperation(_operators: NonterminalNode, _colon: NonterminalNode, _sign: NonterminalNode, _identifier: NonterminalNode, _counter: IterationNode): EvaluationContext {
|
|
const ctx = (this as EvaluationNode).args.ctx
|
|
const operands = evaluateOperands((this as InterpreterNode).operands, ctx)
|
|
const statusOrItemId = (this as InterpreterNode).identifier
|
|
const stacks = (this as InterpreterNode).numberValue
|
|
|
|
return {
|
|
...ctx,
|
|
game: {
|
|
...ctx.game,
|
|
characters: ctx.game.characters.map((c) =>
|
|
operands.has(c.id) ? CharacterStatuses.addStatus(c, statusOrItemId, stacks) : c),
|
|
}
|
|
}
|
|
},
|
|
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),
|
|
}
|
|
}
|
|
},
|
|
StatusOrItemCounterDeltaOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
|
|
return EvaluateStatusCounterDelta(this as EvaluationNode)
|
|
},
|
|
StatusOrItemCounterDeltaOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateStatusCounterDelta(this as EvaluationNode)
|
|
},
|
|
StatusOrItemCounterDeltaOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateStatusCounterDelta(this as EvaluationNode)
|
|
},
|
|
|
|
ClearOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
|
|
return EvaluateClear(this as EvaluationNode);
|
|
},
|
|
ClearOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateClear(this as EvaluationNode);
|
|
},
|
|
ClearOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
|
|
return EvaluateClear(this as EvaluationNode);
|
|
},
|
|
SetSourceOperation(_: TerminalNode, __: NonterminalNode): EvaluationContext {
|
|
let ctx = (this as EvaluationNode).args.ctx
|
|
return {
|
|
...ctx,
|
|
source: [...evaluateOperands((this as InterpreterNode).operands, ctx)]
|
|
}
|
|
},
|
|
SetTargetOperation(_: TerminalNode, __: NonterminalNode): EvaluationContext {
|
|
let ctx = (this as EvaluationNode).args.ctx
|
|
return {
|
|
...ctx,
|
|
target: [...evaluateOperands((this as InterpreterNode).operands, ctx)]
|
|
}
|
|
},
|
|
_iter(...children: readonly Node[]): EvaluationContext {
|
|
let ctx = (this as EvaluationNode).args.ctx
|
|
for (const child of children) {
|
|
ctx = (child as InterpreterNode).evaluate(ctx)
|
|
}
|
|
return ctx
|
|
},
|
|
_nonterminal(): never {
|
|
throw Error(`No idea how to evaluate ${this.ctorName} nonterminal node.`)
|
|
},
|
|
_terminal(): never {
|
|
throw Error(`No idea how to evaluate ${this.ctorName} terminal node.`)
|
|
}
|
|
})
|
|
|
|
function RenderDelta(node: MarkdownNode): MarkdownOutput {
|
|
const ctx = node.args.ctx
|
|
const result: string[] = []
|
|
const resource = node.resource
|
|
const delta = node.numberValue
|
|
const affinity = node.affinity
|
|
const element = node.element
|
|
const operandsBefore = lookupOperands(node.operands, ctx)
|
|
const ctxAfter = node.evaluate(ctx)
|
|
const operandsAfter = lookupOperands(node.operands, ctxAfter)
|
|
|
|
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 (resource === MeteredResource.Zero && typeof afterChar.zp === "number" && typeof afterChar.maxZp === "number" && afterChar.maxZp > 0) {
|
|
result.push(`${charName}: [Zero Charge: _${(100 * afterChar.zp / afterChar.maxZp).toFixed(0)}%_]`)
|
|
} else {
|
|
result.push(`${charName}: [${formatResourceDelta(resource ?? MeteredResource.Health, affinity, delta)}]${
|
|
element !== null ? " " + element : ""}`)
|
|
}
|
|
switch (resource) {
|
|
case MeteredResource.Health:
|
|
case null:
|
|
if (beforeChar.health !== afterChar.health && typeof afterChar.health !== "undefined") {
|
|
result.push(`${charName}: [**${afterChar.health}**]`)
|
|
}
|
|
break
|
|
case MeteredResource.Zero:
|
|
if (isZpReady(beforeChar) === false && isZpReady(afterChar) === true) {
|
|
result.push(`${charName}: [**Zero Power Ready**]`)
|
|
}
|
|
break
|
|
case MeteredResource.Experience:
|
|
const oldLevel = beforeChar.level
|
|
const newLevel = afterChar.level
|
|
if (typeof oldLevel === "number" && typeof newLevel === "number" && oldLevel !== newLevel) {
|
|
const levelDelta = newLevel - oldLevel
|
|
if (levelDelta < 0) {
|
|
result.push(`${charName}: [_Level Down...${levelDelta < -1 ? ` x${-levelDelta}` : ""}_]`)
|
|
} else {
|
|
result.push(`${charName}: [**Level Up!!${levelDelta > 1 ? ` x${levelDelta}` : ""}**]`)
|
|
}
|
|
}
|
|
break
|
|
}
|
|
break
|
|
case "clock":
|
|
if (after.type !== "clock") {
|
|
throw Error(`clock ${id} somehow changed type between calls?`)
|
|
}
|
|
const afterClock = after.clock
|
|
const clockText = afterClock.text
|
|
result.push(`${clockText}: [${formatResourceDelta(resource ?? MeteredResource.Segments, affinity, delta)}]${
|
|
element !== null ? " " + element : ""}`)
|
|
break
|
|
default:
|
|
throw Error(`Don't know how to render delta on ${before.type} ${id}`)
|
|
}
|
|
}
|
|
return {
|
|
...ctxAfter,
|
|
output: result.length > 0 ? result.join("\n") : null
|
|
}
|
|
}
|
|
|
|
function RenderMeteredSet(node: MarkdownNode): MarkdownOutput {
|
|
const ctx = node.args.ctx
|
|
const result: string[] = []
|
|
const resource = node.resource
|
|
const newValue = node.currentValue
|
|
const newMax = node.maxValue
|
|
const operandsBefore = lookupOperands(node.operands, ctx)
|
|
const ctxAfter = node.evaluate(ctx)
|
|
const operandsAfter = lookupOperands(node.operands, ctxAfter)
|
|
|
|
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?`)
|
|
}
|
|
if (resource && !isMeteredResource(resource)) {
|
|
throw Error(`somehow got non-metered resource in metered set`)
|
|
}
|
|
const beforeChar = before.character
|
|
const afterChar = after.character
|
|
const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name
|
|
if (resource === MeteredResource.Zero && newMax > 0) {
|
|
result.push(`${charName}: [Zero Charge: _${(100 * newValue / newMax).toFixed(0)}%_]`)
|
|
} else {
|
|
result.push(`${charName}: [${formatMeteredResource(resource ?? MeteredResource.Health, newValue, newMax)}]`)
|
|
}
|
|
switch (resource) {
|
|
case MeteredResource.Health:
|
|
case null:
|
|
if (beforeChar.health !== afterChar.health && typeof afterChar.health !== "undefined") {
|
|
result.push(`${charName}: [**${afterChar.health}**]`)
|
|
}
|
|
break
|
|
case MeteredResource.Zero:
|
|
if (isZpReady(beforeChar) === false && isZpReady(afterChar) === true) {
|
|
result.push(`${charName}: [**Zero Power Ready**]`)
|
|
}
|
|
break
|
|
case MeteredResource.Experience:
|
|
const oldLevel = beforeChar.level
|
|
const newLevel = afterChar.level
|
|
if (typeof oldLevel === "number" && typeof newLevel === "number" && oldLevel !== newLevel) {
|
|
const levelDelta = newLevel - oldLevel
|
|
if (levelDelta < 0) {
|
|
result.push(`${charName}: [_Level Down...${levelDelta < -1 ? ` x${-levelDelta}` : ""}_]`)
|
|
} else {
|
|
result.push(`${charName}: [**Level Up!!${levelDelta > 1 ? ` x${levelDelta}` : ""}**]`)
|
|
}
|
|
}
|
|
break
|
|
}
|
|
break
|
|
case "clock":
|
|
if (after.type !== "clock") {
|
|
throw Error(`clock ${id} somehow changed type between calls?`)
|
|
}
|
|
const afterClock = after.clock
|
|
const clockText = afterClock.text
|
|
if (resource && !isMeteredResource(resource)) {
|
|
throw Error(`somehow got non-metered resource in metered set`)
|
|
}
|
|
result.push(`${clockText}: [${formatMeteredResource(resource ?? MeteredResource.Segments, newValue, newMax)}`)
|
|
break
|
|
default:
|
|
throw Error(`Don't know how to render metered set on ${before.type} ${id}`)
|
|
}
|
|
}
|
|
return {
|
|
...ctxAfter,
|
|
output: result.length > 0 ? result.join("\n") : null
|
|
}
|
|
}
|
|
|
|
function RenderValueSet(node: MarkdownNode): MarkdownOutput {
|
|
const ctx = node.args.ctx
|
|
const result: string[] = []
|
|
const resource = node.resource
|
|
const newValue = node.currentValue
|
|
const operandsBefore = lookupOperands(node.operands, ctx)
|
|
const ctxAfter = node.evaluate(ctx)
|
|
const operandsAfter = lookupOperands(node.operands, ctxAfter)
|
|
|
|
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 (resource === MeteredResource.Zero && isDefined(afterChar.maxZp) && afterChar.maxZp > 0) {
|
|
result.push(`${charName}: [Zero Charge: _${(100 * newValue / afterChar.maxZp).toFixed(0)}%_]`)
|
|
} else {
|
|
result.push(`${charName}: [${formatResourceValue(resource ?? MeteredResource.Health, newValue)}]`)
|
|
}
|
|
switch (resource) {
|
|
case MeteredResource.Health:
|
|
case null:
|
|
if (beforeChar.health !== afterChar.health && typeof afterChar.health !== "undefined") {
|
|
result.push(`${charName}: [**${afterChar.health}**]`)
|
|
}
|
|
break
|
|
case MeteredResource.Zero:
|
|
if (isZpReady(beforeChar) === false && isZpReady(afterChar) === true) {
|
|
result.push(`${charName}: [**Zero Power Ready**]`)
|
|
}
|
|
break
|
|
case MeteredResource.Experience:
|
|
const oldLevel = beforeChar.level
|
|
const newLevel = afterChar.level
|
|
if (typeof oldLevel === "number" && typeof newLevel === "number" && oldLevel !== newLevel) {
|
|
const levelDelta = newLevel - oldLevel
|
|
if (levelDelta < 0) {
|
|
result.push(`${charName}: [_Level Down...${levelDelta < -1 ? ` x${-levelDelta}` : ""}_]`)
|
|
} else {
|
|
result.push(`${charName}: [**Level Up!!${levelDelta > 1 ? ` x${levelDelta}` : ""}**]`)
|
|
}
|
|
}
|
|
break
|
|
}
|
|
break
|
|
case "clock":
|
|
if (after.type !== "clock") {
|
|
throw Error(`clock ${id} somehow changed type between calls?`)
|
|
}
|
|
const afterClock = after.clock
|
|
const clockText = afterClock.text
|
|
result.push(`${clockText}: [${formatResourceValue(resource ?? MeteredResource.Segments, newValue)}`)
|
|
break
|
|
default:
|
|
throw Error(`Don't know how to render value set on ${before.type} ${id}`)
|
|
}
|
|
}
|
|
return {
|
|
...ctxAfter,
|
|
output: result.length > 0 ? result.join("\n") : null
|
|
}
|
|
}
|
|
|
|
function RenderMaxSet(node: MarkdownNode): MarkdownOutput {
|
|
const ctx = node.args.ctx
|
|
const result: string[] = []
|
|
const resource = node.resource
|
|
const newMax = node.maxValue
|
|
const operandsBefore = lookupOperands(node.operands, ctx)
|
|
const ctxAfter = node.evaluate(ctx)
|
|
const operandsAfter = lookupOperands(node.operands, ctxAfter)
|
|
|
|
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?`)
|
|
}
|
|
if (resource && !isMeteredResource(resource)) {
|
|
throw Error(`somehow got non-metered resource in max set`)
|
|
}
|
|
const beforeChar = before.character
|
|
const afterChar = after.character
|
|
const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name
|
|
if (resource === MeteredResource.Zero && isDefined(afterChar.zp)) {
|
|
result.push(`${charName}: [Zero Charge: _${(100 * afterChar.zp / newMax).toFixed(0)}%_]`)
|
|
} else {
|
|
result.push(`${charName}: [${formatResourceMax(resource ?? MeteredResource.Health, newMax)}]`)
|
|
}
|
|
switch (resource) {
|
|
case MeteredResource.Health:
|
|
case null:
|
|
if (beforeChar.health !== afterChar.health && typeof afterChar.health !== "undefined") {
|
|
result.push(`${charName}: [**${afterChar.health}**]`)
|
|
}
|
|
break
|
|
case MeteredResource.Zero:
|
|
if (isZpReady(beforeChar) === false && isZpReady(afterChar) === true) {
|
|
result.push(`${charName}: [**Zero Power Ready**]`)
|
|
}
|
|
break
|
|
case MeteredResource.Experience:
|
|
const oldLevel = beforeChar.level
|
|
const newLevel = afterChar.level
|
|
if (typeof oldLevel === "number" && typeof newLevel === "number" && oldLevel !== newLevel) {
|
|
const levelDelta = newLevel - oldLevel
|
|
if (levelDelta < 0) {
|
|
result.push(`${charName}: [_Level Down...${levelDelta < -1 ? ` x${-levelDelta}` : ""}_]`)
|
|
} else {
|
|
result.push(`${charName}: [**Level Up!!${levelDelta > 1 ? ` x${levelDelta}` : ""}**]`)
|
|
}
|
|
}
|
|
break
|
|
}
|
|
break
|
|
case "clock":
|
|
if (after.type !== "clock") {
|
|
throw Error(`clock ${id} somehow changed type between calls?`)
|
|
}
|
|
const afterClock = after.clock
|
|
const clockText = afterClock.text
|
|
if (resource && !isMeteredResource(resource)) {
|
|
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 max set on ${before.type} ${id}`)
|
|
}
|
|
}
|
|
return {
|
|
...ctxAfter,
|
|
output: result.length > 0 ? result.join("\n") : null
|
|
}
|
|
}
|
|
|
|
function RenderClear(node: MarkdownNode): MarkdownOutput {
|
|
const ctx = node.args.ctx
|
|
const result: string[] = []
|
|
const resource = node.resource
|
|
const newMax = node.maxValue
|
|
const operandsBefore = lookupOperands(node.operands, ctx)
|
|
const ctxAfter = node.evaluate(ctx)
|
|
const operandsAfter = lookupOperands(node.operands, ctxAfter)
|
|
|
|
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 afterChar = after.character
|
|
const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name
|
|
result.push(`${charName}: [${resource} -]`)
|
|
break
|
|
case "clock":
|
|
if (after.type !== "clock") {
|
|
throw Error(`clock ${id} somehow changed type between calls?`)
|
|
}
|
|
const afterClock = after.clock
|
|
const clockText = afterClock.text
|
|
if (resource && !isMeteredResource(resource)) {
|
|
throw Error(`somehow got non-metered resource in metered set`)
|
|
}
|
|
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}`)
|
|
}
|
|
}
|
|
return {
|
|
...ctxAfter,
|
|
output: result.length > 0 ? result.join("\n") : null
|
|
}
|
|
}
|
|
|
|
interpreter.addOperation<MarkdownOutput>("renderMarkdown(ctx)", {
|
|
CodeSegment(items: IterationNode): MarkdownOutput {
|
|
let ctx = (this as MarkdownNode).args.ctx
|
|
return (items as InterpreterNode).renderMarkdown(ctx)
|
|
},
|
|
EmptyLines(_items: IterationNode): MarkdownOutput {
|
|
let ctx = (this as MarkdownNode).args.ctx
|
|
return {...ctx, output: null}
|
|
},
|
|
DeltaOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode, _arg5: IterationNode, _arg6: IterationNode): MarkdownOutput {
|
|
return RenderDelta(this as MarkdownNode)
|
|
},
|
|
DeltaOperationAlternate(_operators: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _res: IterationNode, _element: IterationNode): MarkdownOutput {
|
|
return RenderDelta(this as MarkdownNode)
|
|
},
|
|
DeltaOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: IterationNode, _arg5: IterationNode): MarkdownOutput {
|
|
return RenderDelta(this as MarkdownNode)
|
|
},
|
|
SetMeteredOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): MarkdownOutput {
|
|
return RenderMeteredSet(this as MarkdownNode)
|
|
},
|
|
SetMeteredOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
|
|
return RenderMeteredSet(this as MarkdownNode)
|
|
},
|
|
SetMeteredOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
|
|
return RenderMeteredSet(this as MarkdownNode)
|
|
},
|
|
SetValueOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): MarkdownOutput {
|
|
return RenderValueSet(this as MarkdownNode);
|
|
},
|
|
SetValueOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
|
|
return RenderValueSet(this as MarkdownNode);
|
|
},
|
|
SetValueOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
|
|
return RenderValueSet(this as MarkdownNode);
|
|
},
|
|
SetMaxOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): MarkdownOutput {
|
|
return RenderMaxSet(this as MarkdownNode);
|
|
},
|
|
SetMaxOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
|
|
return RenderMaxSet(this as MarkdownNode);
|
|
},
|
|
SetMaxOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
|
|
return RenderMaxSet(this as MarkdownNode);
|
|
},
|
|
ClearOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): MarkdownOutput {
|
|
return RenderClear(this as MarkdownNode);
|
|
},
|
|
ClearOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
|
|
return RenderClear(this as MarkdownNode);
|
|
},
|
|
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
|
|
const output = []
|
|
for (const child of children) {
|
|
const result = (child as InterpreterNode).renderMarkdown(ctx)
|
|
if (result.output !== null) {
|
|
output.push(result.output)
|
|
}
|
|
ctx = result
|
|
}
|
|
return {
|
|
...ctx,
|
|
output: output.join('\n')
|
|
}
|
|
},
|
|
_nonterminal(): never {
|
|
throw Error(`No idea how to evaluate ${this.ctorName} nonterminal node for markdown.`)
|
|
},
|
|
_terminal(): never {
|
|
throw Error(`No idea how to evaluate ${this.ctorName} terminal node for markdown.`)
|
|
}
|
|
})
|
|
|
|
export function evaluate(state: GameState, timestamp: number, code: string): GameState {
|
|
const codeMatch = grammar.match(code, "CodeSegment")
|
|
const context: EvaluationContext = {
|
|
timestamp,
|
|
game: state,
|
|
target: [],
|
|
source: [],
|
|
}
|
|
const result = (interpreter(codeMatch) as InterpreterNode).evaluate(context)
|
|
return result.game
|
|
} |