1
0
Fork 0
tsgames/src/common/rpg/utils/conditions.ts

81 lines
3.2 KiB
TypeScript

import type { RPGCondition, RPGVariables } from "../types";
import type { EvalContext } from "../core/world";
import { resolveVariable } from "./variables";
type ConditionOperator = '==' | '!=' | '>' | '<' | '>=' | '<=' | 'null' | '~null';
type ConditionValue = string | number | boolean | null;
export interface ParsedCondition {
variable: string;
negate: boolean;
operator?: ConditionOperator;
value?: ConditionValue;
}
export function parseCondition(s: RPGCondition): ParsedCondition {
// ~variable — falsy check, nothing else allowed
if (s.startsWith('~') && !s.includes(' '))
return { variable: s.slice(1), negate: true };
const spaceIdx = s.indexOf(' ');
if (spaceIdx === -1)
return { variable: s, negate: false };
const variable = s.slice(0, spaceIdx);
const rest = s.slice(spaceIdx + 1).trim();
if (rest === 'null') return { variable, negate: false, operator: 'null' };
if (rest === '~null') return { variable, negate: false, operator: '~null' };
const opMatch = rest.match(/^(==|!=|>=|<=|>|<)\s*(.+)$/);
if (!opMatch) throw new Error(`Invalid condition: "${s}"`);
const [, operator, rawValue] = opMatch;
let value: ConditionValue;
if (rawValue === 'null') value = null;
else if (rawValue === 'true') value = true;
else if (rawValue === 'false') value = false;
else if (!isNaN(Number(rawValue))) value = Number(rawValue);
else value = rawValue.replace(/^['"]|['"]$/g, '');
return { variable, negate: false, operator: operator as ConditionOperator, value };
}
function evalParsed({ negate, operator, value }: ParsedCondition, val: RPGVariables[string]): boolean {
if (operator === 'null') return val == null;
if (operator === '~null') return val != null;
if (operator === undefined)
return negate ? !Boolean(val) : Boolean(val);
if (operator === '==') return val === value;
if (operator === '!=') return val !== value;
if (typeof val !== 'number' || typeof value !== 'number') return false;
if (operator === '<') return val < value;
if (operator === '>') return val > value;
if (operator === '<=') return val <= value;
if (operator === '>=') return val >= value;
return false;
}
export function evaluateCondition(parsed: ParsedCondition, variables: RPGVariables): boolean;
export function evaluateCondition(parsed: ParsedCondition, ctx: EvalContext): boolean;
export function evaluateCondition(parsed: ParsedCondition, variablesOrCtx: RPGVariables | EvalContext): boolean {
const val = isEvalContext(variablesOrCtx)
? resolveVariable(parsed.variable, variablesOrCtx)
: variablesOrCtx[parsed.variable];
return evalParsed(parsed, val);
}
export function evaluateConditions(conditions: RPGCondition[], variables: RPGVariables): boolean;
export function evaluateConditions(conditions: RPGCondition[], ctx: EvalContext): boolean;
export function evaluateConditions(conditions: RPGCondition[], variablesOrCtx: RPGVariables | EvalContext): boolean {
return conditions.every(c => evaluateCondition(parseCondition(c), variablesOrCtx as RPGVariables));
}
function isEvalContext(v: RPGVariables | EvalContext): v is EvalContext {
return 'self' in v && 'world' in v;
}