summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/mzscript.c851
1 files changed, 851 insertions, 0 deletions
diff --git a/tools/mzscript.c b/tools/mzscript.c
new file mode 100644
index 0000000..53fcbff
--- /dev/null
+++ b/tools/mzscript.c
@@ -0,0 +1,851 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * mzscript.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.
+ */
+
+/*
+ * usage: mzscript <data folder> <output folder>
+ * turns the events of an exported RPG Maker MV or MZ game into konscript text.
+ *
+ * script.kons every common event (label common_N), troop page (tN pM) and map event page (mN eN pN) as one script
+ * speakers.tsv what the numbers after say mean: number, face file, face index, name, window background, window position
+ *
+ * what konscript has itself becomes konscript. everything else becomes host commands, where the host number is the
+ * RPG Maker command code and the parameters are b, c, d (the first three numbers) and a text with all of them split by
+ * tabs, in the order RPG Maker keeps them. a choice, loop or battle result is turned into labels and jumps.
+ * conditions and battle results that konscript can't test are answered by the host in scratch switch and scratch variable
+ */
+
+#define KONJSON_IMPLEMENTATION
+#include "konjson.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*** output ***/
+
+typedef struct buf {
+ char *p;
+ size_t n, cap;
+} buf_t;
+
+static void put(buf_t *b, const char *fmt, ...)
+{
+ va_list ap;
+ int need;
+
+ for (;;) {
+ va_start(ap, fmt);
+ need = vsnprintf(b->p ? b->p + b->n : NULL, b->cap - b->n, fmt, ap);
+ va_end(ap);
+ if (need >= 0 && b->p && (size_t)need < b->cap - b->n) {
+ b->n += (size_t)need;
+ return;
+ }
+ b->cap = b->cap ? b->cap * 2 : 1 << 16;
+ b->p = realloc(b->p, b->cap);
+ if (!b->p) {
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ }
+}
+
+#define MAX_LINE 3800
+
+static void putString(buf_t *b, const char *s)
+{
+ size_t i, n = strlen(s);
+
+ if (n > MAX_LINE) n = MAX_LINE;
+ put(b, "\"");
+ for (i = 0; i < n; i++) {
+ unsigned char c = (unsigned char)s[i];
+
+ if (c == '"') put(b, "\\\"");
+ else if (c == '\\') put(b, "\\\\");
+ else if (c == '\n') put(b, "\\n");
+ else if (c == '\t') put(b, "\\t");
+ else if (c < 0x20) put(b, " ");
+ else put(b, "%c", c);
+ }
+ put(b, "\"");
+}
+
+/*** counting ***/
+
+typedef struct tally {
+ int code;
+ int n;
+} tally_t;
+
+static tally_t hosts[256];
+static int hostKinds;
+static int structured, hostLines, warnings;
+
+static void countHost(int code)
+{
+ int i;
+
+ hostLines++;
+ for (i = 0; i < hostKinds; i++) {
+ if (hosts[i].code == code) {
+ hosts[i].n++;
+ return;
+ }
+ }
+ if (hostKinds < 256) {
+ hosts[hostKinds].code = code;
+ hosts[hostKinds].n = 1;
+ hostKinds++;
+ }
+}
+
+static void warn(const char *scope, const char *what)
+{
+ if (warnings++ < 20) fprintf(stderr, "%s: %s\n", scope, what);
+}
+
+/*** speakers ***/
+
+typedef struct speaker {
+ char face[96];
+ int index, background, position;
+ char name[96];
+} speaker_t;
+
+static speaker_t *speakers;
+static int speakerCount, speakerCap;
+
+static int speakerFor(const char *face, int index, const char *name, int background, int position)
+{
+ int i;
+
+ if (!face[0] && !name[0] && background == 0 && position == 2) return -1;
+ for (i = 0; i < speakerCount; i++) {
+ speaker_t *s = &speakers[i];
+
+ if (!strcmp(s->face, face) && s->index == index && !strcmp(s->name, name) && s->background == background && s->position == position) return i;
+ }
+ if (speakerCount == speakerCap) {
+ speakerCap = speakerCap ? speakerCap * 2 : 64;
+ speakers = realloc(speakers, (size_t)speakerCap * sizeof *speakers);
+ if (!speakers) exit(1);
+ }
+ snprintf(speakers[speakerCount].face, sizeof speakers[0].face, "%s", face);
+ snprintf(speakers[speakerCount].name, sizeof speakers[0].name, "%s", name);
+ speakers[speakerCount].index = index;
+ speakers[speakerCount].background = background;
+ speakers[speakerCount].position = position;
+ return speakerCount++;
+}
+
+/*** parameters ***/
+
+#define MAX_TOKENS 4096
+
+typedef struct tokens {
+ char **item;
+ int count;
+ int numbers[3], numberCount;
+ int hasString;
+} tokens_t;
+
+static void addToken(tokens_t *t, const char *text, int isNumber, double number)
+{
+ char *copy;
+ size_t i;
+
+ if (t->count >= MAX_TOKENS) return;
+ copy = malloc(strlen(text) + 1);
+ if (!copy) exit(1);
+ strcpy(copy, text);
+ for (i = 0; copy[i]; i++) {
+ if (copy[i] == '\t') copy[i] = ' ';
+ }
+ t->item[t->count++] = copy;
+ if (isNumber) {
+ if (t->numberCount < 3) t->numbers[t->numberCount++] = number > 2147483647.0 ? 2147483647 : number < -2147483647.0 ? -2147483647 : (int)number;
+ } else {
+ t->hasString = 1;
+ }
+}
+
+static void flatten(tokens_t *t, const kon_json_t *j)
+{
+ int i;
+ char text[64];
+
+ if (!j) return;
+ switch (j->type) {
+ case KON_JSON_NUMBER:
+ if (j->number == (double)(long long)j->number) snprintf(text, sizeof text, "%lld", (long long)j->number);
+ else snprintf(text, sizeof text, "%.6g", j->number);
+ addToken(t, text, 1, j->number);
+ break;
+ case KON_JSON_BOOL:
+ addToken(t, j->number ? "1" : "0", 1, j->number);
+ break;
+ case KON_JSON_STRING:
+ addToken(t, j->string, 0, 0);
+ break;
+ case KON_JSON_NULL:
+ addToken(t, "", 0, 0);
+ break;
+ default:
+ for (i = 0; i < j->count; i++) flatten(t, &j->items[i]);
+ break;
+ }
+}
+
+static void freeTokens(tokens_t *t)
+{
+ int i;
+
+ for (i = 0; i < t->count; i++) free(t->item[i]);
+ free(t->item);
+}
+
+static void host(buf_t *out, int code, const kon_json_t *parameters)
+{
+ tokens_t t;
+ int i;
+ size_t len = 0;
+
+ memset(&t, 0, sizeof t);
+ t.item = calloc(MAX_TOKENS, sizeof *t.item);
+ if (!t.item) exit(1);
+ flatten(&t, parameters);
+ countHost(code);
+
+ put(out, "\thost %d", code);
+ for (i = 0; i < t.numberCount; i++) put(out, " %d", t.numbers[i]);
+ if (t.hasString || t.count > 3) {
+ char *joined;
+
+ for (i = 0; i < t.count; i++) len += strlen(t.item[i]) + 1;
+ joined = malloc(len + 1);
+ if (!joined) exit(1);
+ joined[0] = '\0';
+ for (i = 0; i < t.count; i++) {
+ if (i) strcat(joined, "\t");
+ strcat(joined, t.item[i]);
+ }
+ if (t.numberCount < 3) {
+ while (t.numberCount < 3) {
+ put(out, " 0");
+ t.numberCount++;
+ }
+ }
+ put(out, " ");
+ putString(out, joined);
+ free(joined);
+ }
+ put(out, "\n");
+ freeTokens(&t);
+}
+
+/*** one event list ***/
+
+typedef struct frame {
+ int kind, indent, id, hasElse, branch, count;
+} frame_t;
+
+enum { F_COND, F_CHOICE, F_LOOP, F_BATTLE };
+
+typedef struct context {
+ buf_t *out;
+ const char *scope;
+ int counter;
+ int scratchSwitch, scratchVar;
+ char labelNames[512][64];
+ char labelDone[512];
+ int labelCount;
+ frame_t stack[48];
+ int depth;
+} context_t;
+
+static const char *labelName(context_t *c, char tag, int id, int n, char *out)
+{
+ if (tag == 'e') snprintf(out, 40, "%s_%d", c->scope, id);
+ else snprintf(out, 40, "%s_%d%c%d", c->scope, id, tag, n);
+ return out;
+}
+
+static int commandCode(const kon_json_t *list, int i)
+{
+ return kon_jsonInt(kon_jsonGet(kon_jsonAt(list, i), "code"), 0);
+}
+
+static int commandIndent(const kon_json_t *list, int i)
+{
+ return kon_jsonInt(kon_jsonGet(kon_jsonAt(list, i), "indent"), 0);
+}
+
+static const kon_json_t *commandParameters(const kon_json_t *list, int i)
+{
+ return kon_jsonGet(kon_jsonAt(list, i), "parameters");
+}
+
+static int nextAtIndent(const kon_json_t *list, int from, int indent)
+{
+ int n = kon_jsonCount(list), j;
+
+ for (j = from + 1; j < n; j++) {
+ if (commandIndent(list, j) == indent) return j;
+ if (commandIndent(list, j) < indent) return -1;
+ }
+ return -1;
+}
+
+static int hasElse(const kon_json_t *list, int from, int indent)
+{
+ int j = from;
+
+ for (;;) {
+ j = nextAtIndent(list, j, indent);
+ if (j < 0) return 0;
+ if (commandCode(list, j) == 411) return 1;
+ if (commandCode(list, j) == 412) return 0;
+ }
+}
+
+/* a call with nothing after it but the ends of blocks can be a jump, so a common event that calls itself doesn't pile up */
+static int tailCall(const kon_json_t *list, int i)
+{
+ int n = kon_jsonCount(list), j;
+
+ for (j = i + 1; j < n; j++) {
+ int code = commandCode(list, j);
+
+ if (code != 0 && code != 412 && code != 404) return 0;
+ }
+ return 1;
+}
+
+static void scriptLabels(context_t *c, const kon_json_t *list)
+{
+ int i, n = kon_jsonCount(list);
+
+ c->labelCount = 0;
+ memset(c->labelDone, 0, sizeof c->labelDone);
+ for (i = 0; i < n; i++) {
+ if (commandCode(list, i) == 118 && c->labelCount < 512) {
+ snprintf(c->labelNames[c->labelCount], sizeof c->labelNames[0], "%s", kon_jsonString(kon_jsonAt(commandParameters(list, i), 0), ""));
+ c->labelCount++;
+ }
+ }
+}
+
+static int labelIndex(context_t *c, const char *name)
+{
+ int i;
+
+ for (i = 0; i < c->labelCount; i++) {
+ if (!strcmp(c->labelNames[i], name)) return i;
+ }
+ return -1;
+}
+
+static frame_t *top(context_t *c, int indent)
+{
+ if (c->depth && c->stack[c->depth - 1].indent == indent) return &c->stack[c->depth - 1];
+ return NULL;
+}
+
+static frame_t *push(context_t *c, int kind, int indent)
+{
+ frame_t *f = &c->stack[c->depth < 47 ? c->depth++ : 47];
+
+ memset(f, 0, sizeof *f);
+ f->kind = kind;
+ f->indent = indent;
+ f->id = ++c->counter;
+ return f;
+}
+
+static const char *compare[] = {"==", ">=", "<=", ">", "<", "!="};
+
+static void condition(context_t *c, const kon_json_t *p, const char *elseLabel)
+{
+ buf_t *out = c->out;
+ int type = kon_jsonInt(kon_jsonAt(p, 0), -1);
+
+ if (type == 0) {
+ put(out, "\tif s%d %s else %s\n", kon_jsonInt(kon_jsonAt(p, 1), 0), kon_jsonInt(kon_jsonAt(p, 2), 0) == 0 ? "on" : "off", elseLabel);
+ structured++;
+ return;
+ }
+ if (type == 1) {
+ int cmp = kon_jsonInt(kon_jsonAt(p, 4), 0);
+ int operandVar = kon_jsonInt(kon_jsonAt(p, 2), 0) == 1;
+ double v = kon_jsonNumber(kon_jsonAt(p, 3), 0);
+
+ if (cmp >= 0 && cmp <= 5 && (operandVar || (v > -200000000 && v < 200000000))) {
+ put(out, "\tif v%d %s ", kon_jsonInt(kon_jsonAt(p, 1), 0), compare[cmp]);
+ if (operandVar) put(out, "v%d", kon_jsonInt(kon_jsonAt(p, 3), 0));
+ else put(out, "%d", (int)v);
+ put(out, " else %s\n", elseLabel);
+ structured++;
+ return;
+ }
+ }
+ host(out, 111, p);
+ put(out, "\tif s%d on else %s\n", c->scratchSwitch, elseLabel);
+}
+
+static void variables(context_t *c, const kon_json_t *p, const kon_json_t *whole)
+{
+ static const char *const ops[] = {"=", "+=", "-=", "*=", "/=", "%="};
+ int first = kon_jsonInt(kon_jsonAt(p, 0), 0), last = kon_jsonInt(kon_jsonAt(p, 1), 0);
+ int op = kon_jsonInt(kon_jsonAt(p, 2), 0), type = kon_jsonInt(kon_jsonAt(p, 3), 0);
+ double v = kon_jsonNumber(kon_jsonAt(p, 4), 0);
+ int id;
+
+ if (op >= 0 && op <= 5 && last >= first && last - first < 1000 && ((type == 0 && v > -200000000 && v < 200000000) || type == 1)) {
+ for (id = first; id <= last; id++) {
+ put(c->out, "\tset v%d %s ", id, ops[op]);
+ if (type == 1) put(c->out, "v%d\n", (int)v);
+ else put(c->out, "%d\n", (int)v);
+ }
+ structured++;
+ return;
+ }
+ host(c->out, 122, whole);
+}
+
+static void textBlock(const kon_json_t *list, int *i, int continuation, tokens_t *lines)
+{
+ int n = kon_jsonCount(list);
+
+ while (*i + 1 < n && commandCode(list, *i + 1) == continuation) {
+ (*i)++;
+ addToken(lines, kon_jsonString(kon_jsonAt(commandParameters(list, *i), 0), ""), 0, 0);
+ }
+}
+
+static char *joinLines(tokens_t *t, const char *first)
+{
+ size_t len = strlen(first) + 1;
+ char *s;
+ int i;
+
+ for (i = 0; i < t->count; i++) len += strlen(t->item[i]) + 1;
+ s = malloc(len + 1);
+ if (!s) exit(1);
+ strcpy(s, first);
+ for (i = 0; i < t->count; i++) {
+ if (i || first[0]) strcat(s, "\n");
+ strcat(s, t->item[i]);
+ }
+ return s;
+}
+
+static void convert(context_t *c, const kon_json_t *list)
+{
+ buf_t *out = c->out;
+ int n = kon_jsonCount(list), i;
+ char a[40], b[40];
+
+ c->depth = 0;
+ scriptLabels(c, list);
+
+ for (i = 0; i < n; i++) {
+ int code = commandCode(list, i), indent = commandIndent(list, i);
+ const kon_json_t *p = commandParameters(list, i);
+ frame_t *f;
+
+ switch (code) {
+ case 0:
+ break;
+ case 101: {
+ tokens_t lines;
+ char *text;
+ int who;
+
+ memset(&lines, 0, sizeof lines);
+ lines.item = calloc(MAX_TOKENS, sizeof *lines.item);
+ if (!lines.item) exit(1);
+ textBlock(list, &i, 401, &lines);
+ who = speakerFor(kon_jsonString(kon_jsonAt(p, 0), ""), kon_jsonInt(kon_jsonAt(p, 1), 0), kon_jsonString(kon_jsonAt(p, 4), ""), kon_jsonInt(kon_jsonAt(p, 2), 0), kon_jsonInt(kon_jsonAt(p, 3), 2));
+ text = joinLines(&lines, "");
+ if (who < 0) put(out, "\tsay - ");
+ else put(out, "\tsay %d ", who);
+ putString(out, text);
+ put(out, "\n");
+ free(text);
+ freeTokens(&lines);
+ structured++;
+ break;
+ }
+ case 102: {
+ const kon_json_t *choices = kon_jsonAt(p, 0);
+ int k, count = kon_jsonCount(choices);
+
+ f = push(c, F_CHOICE, indent);
+ f->count = count > 16 ? 16 : count;
+ if (count > 16) warn(c->scope, "more than 16 choices");
+ put(out, "\tmenu\n");
+ for (k = 0; k < f->count; k++) {
+ put(out, "\toption ");
+ putString(out, kon_jsonString(kon_jsonAt(choices, k), ""));
+ put(out, " %s\n", labelName(c, 'o', f->id, k, a));
+ }
+ structured++;
+ break;
+ }
+ case 402:
+ f = top(c, indent);
+ if (!f || f->kind != F_CHOICE) {
+ warn(c->scope, "choice branch without choices");
+ break;
+ }
+ if (f->branch) put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a));
+ f->branch++;
+ put(out, "%s:\n", labelName(c, 'o', f->id, kon_jsonInt(kon_jsonAt(p, 0), 0), a));
+ break;
+ case 403:
+ f = top(c, indent);
+ if (!f || f->kind != F_CHOICE) break;
+ put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a));
+ put(out, "%s_%dcancel:\n", c->scope, f->id);
+ warnings += 0;
+ break;
+ case 404:
+ f = top(c, indent);
+ if (!f || f->kind != F_CHOICE) break;
+ put(out, "%s:\n", labelName(c, 'e', f->id, 0, a));
+ c->depth--;
+ break;
+ case 111:
+ f = push(c, F_COND, indent);
+ f->hasElse = hasElse(list, i, indent);
+ condition(c, p, f->hasElse ? labelName(c, 'n', f->id, 0, a) : labelName(c, 'e', f->id, 0, b));
+ break;
+ case 411:
+ f = top(c, indent);
+ if (!f || f->kind != F_COND) break;
+ put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a));
+ put(out, "%s:\n", labelName(c, 'n', f->id, 0, a));
+ break;
+ case 412:
+ f = top(c, indent);
+ if (!f || f->kind != F_COND) break;
+ put(out, "%s:\n", labelName(c, 'e', f->id, 0, a));
+ c->depth--;
+ break;
+ case 112:
+ f = push(c, F_LOOP, indent);
+ put(out, "%s:\n", labelName(c, 't', f->id, 0, a));
+ break;
+ case 413:
+ f = top(c, indent);
+ if (!f || f->kind != F_LOOP) break;
+ put(out, "\tjump %s\n", labelName(c, 't', f->id, 0, a));
+ put(out, "%s:\n", labelName(c, 'e', f->id, 0, a));
+ c->depth--;
+ break;
+ case 113: {
+ int k;
+
+ for (k = c->depth - 1; k >= 0 && c->stack[k].kind != F_LOOP; k--) {}
+ if (k < 0) {
+ warn(c->scope, "break loop outside a loop");
+ break;
+ }
+ put(out, "\tjump %s\n", labelName(c, 'e', c->stack[k].id, 0, a));
+ break;
+ }
+ case 115:
+ put(out, "\treturn\n");
+ break;
+ case 117:
+ put(out, "\t%s common_%d\n", tailCall(list, i) ? "jump" : "call", kon_jsonInt(kon_jsonAt(p, 0), 0));
+ structured++;
+ break;
+ case 118: {
+ int k = labelIndex(c, kon_jsonString(kon_jsonAt(p, 0), ""));
+
+ if (k >= 0 && !c->labelDone[k]) {
+ c->labelDone[k] = 1;
+ put(out, "%s_L%d:\n", c->scope, k);
+ }
+ break;
+ }
+ case 119: {
+ int k = labelIndex(c, kon_jsonString(kon_jsonAt(p, 0), ""));
+
+ if (k < 0) {
+ put(out, "\t# jump to a label that is not there\n");
+ break;
+ }
+ put(out, "\tjump %s_L%d\n", c->scope, k);
+ structured++;
+ break;
+ }
+ case 121: {
+ int id, first = kon_jsonInt(kon_jsonAt(p, 0), 0), last = kon_jsonInt(kon_jsonAt(p, 1), 0);
+
+ if (last - first > 2000) last = first + 2000;
+ for (id = first; id <= last; id++) put(out, "\t%s s%d\n", kon_jsonInt(kon_jsonAt(p, 2), 0) == 0 ? "on" : "off", id);
+ structured++;
+ break;
+ }
+ case 122:
+ variables(c, p, p);
+ break;
+ case 108: {
+ tokens_t lines;
+ int k;
+
+ memset(&lines, 0, sizeof lines);
+ lines.item = calloc(MAX_TOKENS, sizeof *lines.item);
+ if (!lines.item) exit(1);
+ addToken(&lines, kon_jsonString(kon_jsonAt(p, 0), ""), 0, 0);
+ textBlock(list, &i, 408, &lines);
+ for (k = 0; k < lines.count; k++) {
+ char *s;
+
+ for (s = lines.item[k]; *s; s++) {
+ if ((unsigned char)*s < 0x20) *s = ' ';
+ }
+ if (strlen(lines.item[k]) > 400) lines.item[k][400] = '\0';
+ put(out, "\t# %s\n", lines.item[k]);
+ }
+ freeTokens(&lines);
+ break;
+ }
+ case 230:
+ put(out, "\twait %d\n", (int)(kon_jsonNumber(kon_jsonAt(p, 0), 0) * 1000.0 / 60.0 + 0.5));
+ structured++;
+ break;
+ case 301: {
+ int next = nextAtIndent(list, i, indent);
+
+ host(out, 301, p);
+ if (next >= 0 && (commandCode(list, next) == 601 || commandCode(list, next) == 602 || commandCode(list, next) == 603)) push(c, F_BATTLE, indent);
+ break;
+ }
+ case 601:
+ case 602:
+ case 603:
+ f = top(c, indent);
+ if (!f || f->kind != F_BATTLE) break;
+ if (f->branch) {
+ put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a));
+ put(out, "%s:\n", labelName(c, 'n', f->id, f->branch - 1, a));
+ }
+ put(out, "\tif v%d == %d else %s\n", c->scratchVar, code - 601, labelName(c, 'n', f->id, f->branch, a));
+ f->branch++;
+ break;
+ case 604:
+ f = top(c, indent);
+ if (!f || f->kind != F_BATTLE) break;
+ put(out, "\tjump %s\n", labelName(c, 'e', f->id, 0, a));
+ put(out, "%s:\n", labelName(c, 'n', f->id, f->branch - 1, a));
+ put(out, "%s:\n", labelName(c, 'e', f->id, 0, a));
+ c->depth--;
+ break;
+ case 105:
+ case 355: {
+ tokens_t lines;
+ char *text;
+ int number = 0;
+
+ memset(&lines, 0, sizeof lines);
+ lines.item = calloc(MAX_TOKENS, sizeof *lines.item);
+ if (!lines.item) exit(1);
+ textBlock(list, &i, code == 105 ? 405 : 655, &lines);
+ text = joinLines(&lines, code == 355 ? kon_jsonString(kon_jsonAt(p, 0), "") : "");
+ if (code == 105) number = kon_jsonInt(kon_jsonAt(p, 0), 2);
+ countHost(code);
+ put(out, "\thost %d %d 0 0 ", code, number);
+ putString(out, text);
+ put(out, "\n");
+ free(text);
+ freeTokens(&lines);
+ break;
+ }
+ case 205:
+ host(out, 205, p);
+ while (i + 1 < n && commandCode(list, i + 1) == 505) i++;
+ break;
+ case 357:
+ host(out, 357, p);
+ while (i + 1 < n && commandCode(list, i + 1) == 657) i++;
+ break;
+ case 401:
+ case 405:
+ case 408:
+ case 505:
+ case 655:
+ case 657:
+ break;
+ default:
+ host(out, code, p);
+ break;
+ }
+ }
+ while (c->depth) {
+ warn(c->scope, "a block was left open");
+ c->depth--;
+ }
+}
+
+/*** files ***/
+
+static kon_json_t *load(const char *folder, const char *name)
+{
+ char path[1024], error[120];
+ kon_json_t *j;
+
+ snprintf(path, sizeof path, "%s/%s", folder, name);
+ j = kon_jsonParseFile(path, error, sizeof error);
+ if (!j && strncmp(error, "cannot open", 11)) fprintf(stderr, "%s: %s\n", path, error);
+ return j;
+}
+
+int main(int argc, char **argv)
+{
+ buf_t out;
+ context_t c;
+ kon_json_t *system, *json;
+ char path[1024], scope[40];
+ int id, k, p, maxCommon = 0, entries = 0;
+ FILE *f;
+
+ if (argc < 3) {
+ fprintf(stderr, "usage: mzscript <data folder> <output folder>\n");
+ return 2;
+ }
+ memset(&out, 0, sizeof out);
+ memset(&c, 0, sizeof c);
+ c.out = &out;
+
+ system = load(argv[1], "System.json");
+ if (!system) return 1;
+ c.scratchSwitch = kon_jsonCount(kon_jsonGet(system, "switches")) + 1;
+ c.scratchVar = kon_jsonCount(kon_jsonGet(system, "variables")) + 1;
+ kon_jsonFree(system);
+ put(&out, "# scratch switch s%d and scratch variable v%d are the host's\n\n", c.scratchSwitch, c.scratchVar);
+
+ json = load(argv[1], "CommonEvents.json");
+ if (json) {
+ maxCommon = kon_jsonCount(json) - 1;
+ for (id = 1; id <= maxCommon; id++) {
+ const kon_json_t *event = kon_jsonAt(json, id);
+
+ snprintf(scope, sizeof scope, "c%d", id);
+ c.scope = scope;
+ c.counter = 0;
+ put(&out, "common_%d:\n", id);
+ if (event && event->type == KON_JSON_OBJECT) convert(&c, kon_jsonGet(event, "list"));
+ put(&out, "\treturn\n\n");
+ entries++;
+ }
+ kon_jsonFree(json);
+ }
+
+ json = load(argv[1], "Troops.json");
+ if (json) {
+ for (id = 1; id < kon_jsonCount(json); id++) {
+ const kon_json_t *pages = kon_jsonGet(kon_jsonAt(json, id), "pages");
+
+ for (p = 0; p < kon_jsonCount(pages); p++) {
+ snprintf(scope, sizeof scope, "t%dp%d", id, p + 1);
+ c.scope = scope;
+ c.counter = 0;
+ put(&out, "%s:\n", scope);
+ convert(&c, kon_jsonGet(kon_jsonAt(pages, p), "list"));
+ put(&out, "\treturn\n\n");
+ entries++;
+ }
+ }
+ kon_jsonFree(json);
+ }
+
+ for (id = 1; id < 2000; id++) {
+ char name[32];
+ const kon_json_t *events;
+
+ snprintf(name, sizeof name, "Map%03d.json", id);
+ json = load(argv[1], name);
+ if (!json) continue;
+ events = kon_jsonGet(json, "events");
+ for (k = 1; k < kon_jsonCount(events); k++) {
+ const kon_json_t *event = kon_jsonAt(events, k), *pages = kon_jsonGet(event, "pages");
+
+ if (!event || event->type != KON_JSON_OBJECT) continue;
+ for (p = 0; p < kon_jsonCount(pages); p++) {
+ snprintf(scope, sizeof scope, "m%de%dp%d", id, kon_jsonInt(kon_jsonGet(event, "id"), k), p + 1);
+ c.scope = scope;
+ c.counter = 0;
+ put(&out, "%s:\n", scope);
+ convert(&c, kon_jsonGet(kon_jsonAt(pages, p), "list"));
+ put(&out, "\treturn\n\n");
+ entries++;
+ }
+ }
+ kon_jsonFree(json);
+ }
+
+ snprintf(path, sizeof path, "%s/script.kons", argv[2]);
+ f = fopen(path, "wb");
+ if (!f) {
+ fprintf(stderr, "cannot write %s\n", path);
+ return 1;
+ }
+ fwrite(out.p, 1, out.n, f);
+ fclose(f);
+ free(out.p);
+
+ snprintf(path, sizeof path, "%s/speakers.tsv", argv[2]);
+ f = fopen(path, "wb");
+ if (!f) {
+ fprintf(stderr, "cannot write %s\n", path);
+ return 1;
+ }
+ for (k = 0; k < speakerCount; k++) fprintf(f, "%d\t%s\t%d\t%s\t%d\t%d\n", k, speakers[k].face, speakers[k].index, speakers[k].name, speakers[k].background, speakers[k].position);
+ fclose(f);
+
+ printf("%d scripts, %d speakers\n", entries, speakerCount);
+ printf("%d commands became konscript, %d became host commands", structured, hostLines);
+ printf(" (%d kinds)\n", hostKinds);
+ for (k = 0; k < hostKinds; k++) {
+ int best = k, j;
+ tally_t t;
+
+ for (j = k + 1; j < hostKinds; j++) {
+ if (hosts[j].n > hosts[best].n) best = j;
+ }
+ t = hosts[k];
+ hosts[k] = hosts[best];
+ hosts[best] = t;
+ if (k < 15) printf(" %6d host %d\n", hosts[k].n, hosts[k].code);
+ }
+ if (warnings) printf("%d warnings\n", warnings);
+ free(speakers);
+ return 0;
+}