summaryrefslogtreecommitdiff
path: root/kon/konscript.c
diff options
context:
space:
mode:
Diffstat (limited to 'kon/konscript.c')
-rw-r--r--kon/konscript.c248
1 files changed, 248 insertions, 0 deletions
diff --git a/kon/konscript.c b/kon/konscript.c
new file mode 100644
index 0000000..dc706bf
--- /dev/null
+++ b/kon/konscript.c
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konscript.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "konscript.h"
+
+#include <stddef.h>
+
+/*** variables ***/
+
+int kon_scriptSwitch(const kon_script_t *vm, int id)
+{
+ return id >= 0 && id < vm->switchCount && vm->switches && vm->switches[id] ? 1 : 0;
+}
+
+int32_t kon_scriptVar(const kon_script_t *vm, int id)
+{
+ return id >= 0 && id < vm->varCount && vm->vars ? vm->vars[id] : 0;
+}
+
+void kon_scriptSetSwitch(kon_script_t *vm, int id, int on)
+{
+ if (id >= 0 && id < vm->switchCount && vm->switches) vm->switches[id] = on ? 1 : 0;
+}
+
+void kon_scriptSetVar(kon_script_t *vm, int id, int32_t value)
+{
+ if (id >= 0 && id < vm->varCount && vm->vars) vm->vars[id] = value;
+}
+
+/* done in 64 bits and clamped so a script can never overflow or divide by 0 */
+static int32_t arithmetic(int32_t x, int how, int32_t y)
+{
+ int64_t r;
+
+ switch (how) {
+ case KON_ADD: r = (int64_t)x + y; break;
+ case KON_SUB: r = (int64_t)x - y; break;
+ case KON_MUL: r = (int64_t)x * y; break;
+ case KON_DIV: r = y ? (int64_t)x / y : x; break;
+ case KON_MOD: r = y ? (int64_t)x % y : x; break;
+ default: r = y; break;
+ }
+ if (r > INT32_MAX) r = INT32_MAX;
+ if (r < INT32_MIN) r = INT32_MIN;
+ return (int32_t)r;
+}
+
+static int32_t operand(const kon_script_t *vm, int n)
+{
+ if (n >= KON_VARIABLE && n < KON_VARIABLE + 0x20000000) return kon_scriptVar(vm, n - KON_VARIABLE);
+ return n;
+}
+
+static int compare(int32_t x, int how, int32_t y)
+{
+ switch (how) {
+ case KON_IS_EQ: return x == y;
+ case KON_IS_NE: return x != y;
+ case KON_IS_GE: return x >= y;
+ case KON_IS_LE: return x <= y;
+ case KON_IS_GT: return x > y;
+ case KON_IS_LT: return x < y;
+ }
+ return 0;
+}
+
+/*** script ***/
+
+void kon_scriptInit(kon_script_t *vm, const kon_cmd_t *cmds, int count, uint8_t *switches, int switchCount, int32_t *vars, int varCount)
+{
+ kon_script_t zero = {0};
+
+ *vm = zero;
+ vm->cmds = cmds;
+ vm->count = cmds && count > 0 ? count : 0;
+ vm->switches = switches;
+ vm->switchCount = switches && switchCount > 0 ? switchCount : 0;
+ vm->vars = vars;
+ vm->varCount = vars && varCount > 0 ? varCount : 0;
+ vm->status = KON_SCRIPT_DONE;
+}
+
+void kon_scriptStart(kon_script_t *vm, int where)
+{
+ vm->pc = where;
+ vm->depth = 0;
+ vm->optionCount = 0;
+ vm->wait = 0.0f;
+ vm->status = where >= 0 && where < vm->count ? KON_SCRIPT_RUNNING : KON_SCRIPT_ERROR;
+}
+
+static int menuOptions(kon_script_t *vm, const kon_cmd_t *menu)
+{
+ int i, n = menu->a;
+
+ vm->optionCount = 0;
+ if (n > KON_SCRIPT_MAX_OPTIONS) n = KON_SCRIPT_MAX_OPTIONS;
+ for (i = 0; i < n; i++) {
+ int at = vm->pc + 1 + i;
+
+ if (at >= vm->count || vm->cmds[at].op != KON_OP_OPTION) return 0;
+ if (vm->cmds[at].b >= 0 && !kon_scriptSwitch(vm, vm->cmds[at].b)) continue;
+ vm->options[vm->optionCount++] = at;
+ }
+ return 1;
+}
+
+kon_scriptStatus_t kon_scriptRun(kon_script_t *vm)
+{
+ int steps = 0;
+
+ while (vm->status == KON_SCRIPT_RUNNING) {
+ const kon_cmd_t *c;
+ int next;
+
+ if (vm->pc < 0 || vm->pc >= vm->count || ++steps > 100000) {
+ vm->status = KON_SCRIPT_ERROR;
+ break;
+ }
+ c = &vm->cmds[vm->pc];
+ next = vm->pc + 1;
+ vm->cmd = vm->pc;
+
+ switch (c->op) {
+ case KON_OP_END:
+ vm->status = KON_SCRIPT_DONE;
+ break;
+ case KON_OP_SAY:
+ vm->status = KON_SCRIPT_SAY;
+ break;
+ case KON_OP_MENU:
+ if (!menuOptions(vm, c)) {
+ vm->status = KON_SCRIPT_ERROR;
+ break;
+ }
+ if (vm->optionCount == 0) {
+ next = vm->pc + 1 + (c->a > 0 ? c->a : 0);
+ break;
+ }
+ vm->status = KON_SCRIPT_MENU;
+ break;
+ case KON_OP_OPTION:
+ break;
+ case KON_OP_JUMP:
+ next = c->a;
+ break;
+ case KON_OP_SWITCH:
+ kon_scriptSetSwitch(vm, c->a, c->b == 2 ? !kon_scriptSwitch(vm, c->a) : c->b);
+ break;
+ case KON_OP_VAR:
+ kon_scriptSetVar(vm, c->a, arithmetic(kon_scriptVar(vm, c->a), c->b, operand(vm, c->c)));
+ break;
+ case KON_OP_IFSWITCH:
+ if (kon_scriptSwitch(vm, c->a) != (c->b ? 1 : 0)) next = c->c;
+ break;
+ case KON_OP_IFVAR:
+ if (!compare(kon_scriptVar(vm, c->a), c->b, operand(vm, c->c))) next = c->d;
+ break;
+ case KON_OP_WAIT:
+ if (c->a > 0) {
+ vm->wait = (float)c->a * 0.001f;
+ vm->status = KON_SCRIPT_WAIT;
+ }
+ break;
+ case KON_OP_HOST:
+ if (vm->host && vm->host(vm, c, vm->user)) vm->status = KON_SCRIPT_HOST;
+ break;
+ case KON_OP_CALL:
+ if (vm->depth >= KON_SCRIPT_MAX_CALLS) {
+ vm->status = KON_SCRIPT_ERROR;
+ break;
+ }
+ vm->stack[vm->depth++] = next;
+ next = c->a;
+ break;
+ case KON_OP_RETURN:
+ if (vm->depth == 0) vm->status = KON_SCRIPT_DONE;
+ else next = vm->stack[--vm->depth];
+ break;
+ default:
+ vm->status = KON_SCRIPT_ERROR;
+ break;
+ }
+ vm->pc = next;
+ }
+ return vm->status;
+}
+
+void kon_scriptUpdate(kon_script_t *vm, float dt)
+{
+ if (vm->status != KON_SCRIPT_WAIT || !(dt > 0.0f)) return;
+ vm->wait -= dt;
+ if (vm->wait <= 0.0f) {
+ vm->wait = 0.0f;
+ vm->status = KON_SCRIPT_RUNNING;
+ }
+}
+
+void kon_scriptResume(kon_script_t *vm)
+{
+ if (vm->status == KON_SCRIPT_SAY || vm->status == KON_SCRIPT_HOST) vm->status = KON_SCRIPT_RUNNING;
+}
+
+const kon_cmd_t *kon_scriptCurrent(const kon_script_t *vm)
+{
+ return vm->status == KON_SCRIPT_SAY || vm->status == KON_SCRIPT_HOST ? &vm->cmds[vm->cmd] : NULL;
+}
+
+int kon_scriptOptionCount(const kon_script_t *vm)
+{
+ return vm->status == KON_SCRIPT_MENU ? vm->optionCount : 0;
+}
+
+const char *kon_scriptOption(const kon_script_t *vm, int i)
+{
+ if (vm->status != KON_SCRIPT_MENU || i < 0 || i >= vm->optionCount) return NULL;
+ return vm->cmds[vm->options[i]].text;
+}
+
+void kon_scriptChoose(kon_script_t *vm, int i)
+{
+ if (vm->status != KON_SCRIPT_MENU || i < 0 || i >= vm->optionCount) return;
+ vm->pc = vm->cmds[vm->options[i]].a;
+ vm->optionCount = 0;
+ vm->status = KON_SCRIPT_RUNNING;
+}