import type { RPGCondition, RPGVariables } from "../types"; import { isEvalContext, 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; } type Cond = ParsedCondition | RPGCondition; export function evaluateCondition(condition: Cond, variables: RPGVariables): boolean; export function evaluateCondition(condition: Cond, ctx: EvalContext): boolean; export function evaluateCondition(condition: Cond, variablesOrCtx: RPGVariables | EvalContext): boolean { const parsed = typeof condition === 'string' ? parseCondition(condition) : condition; const val = isEvalContext(variablesOrCtx) ? resolveVariable(parsed.variable, variablesOrCtx) : variablesOrCtx[parsed.variable]; return evalParsed(parsed, val); } export function evaluateConditions(conditions: Cond[], variables: RPGVariables): boolean; export function evaluateConditions(conditions: Cond[], ctx: EvalContext): boolean; export function evaluateConditions(conditions: Cond[], variablesOrCtx: RPGVariables | EvalContext): boolean { return conditions.every(c => evaluateCondition(c, variablesOrCtx as RPGVariables)); }