summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-16 07:06:19 +0800
committerKonAki <konaki@anokonaki.org>2026-10-16 07:06:19 +0800
commitc821bab136a3fd46facf9a053359309ab2cc4d5c (patch)
treeffe2e848085930d5c729365a3cd16eb07777a879
parent54372d111804916459418e3ff8fb6d9c9e036fb1 (diff)
add script commands to rmfield: transfer, locate, route, self switch
-rw-r--r--README.md2
-rw-r--r--kon/konrmfield.c181
-rw-r--r--kon/konrmfield.h27
3 files changed, 208 insertions, 2 deletions
diff --git a/README.md b/README.md
index 47ea4cd..f3f1e2b 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ The engine modules are a `.h` and a `.c` pair, they depend on each other a bit,
| rmmap | the const data shapes mzmap writes for RPG Maker maps, events, pages and tilesets, which page an event shows, reading a tile, and walking: passability, terrain tags, regions |
| rmtile | where the pieces of an RPG Maker tile are on its sheet: autotile shapes for floors, walls and waterfalls, animation frames (adds rmmap) |
| rmdraw | draws an RPG Maker map from its tileset sheets: layers, shadows, tiles that go above the actors, looping maps (adds rmmap, rmtile) |
-| rmfield | the player and the events of an RPG Maker map walking around it: steps at the speed of the character, wandering and routes, what blocks what, and what starts an event by touch, action or autorun (adds rmmap) |
+| rmfield | the player and the events of an RPG Maker map walking around it: steps at the speed of the character, wandering and routes, what blocks what, what starts an event by touch, action or autorun, and the script commands for moving characters, map transfers and self switches (adds rmmap) |
| sound | RPG Maker style audio channels: BGM, BGS, ME and SE, fades, a saved BGM, loop points (adds audio) |
| model | glTF models and animation, converted to my own `.kmdl` format |
| ui | immediate mode ui |
diff --git a/kon/konrmfield.c b/kon/konrmfield.c
index f165df7..92401de 100644
--- a/kon/konrmfield.c
+++ b/kon/konrmfield.c
@@ -423,7 +423,7 @@ void kon_rmFieldRefresh(kon_rmField_t *field)
for (i = 0; i < field->map->eventCount && i + 1 < field->count; i++) {
const kon_rmEvent_t *event = &field->map->events[i];
kon_rmChar_t *c = &field->chars[i + 1];
- int page = kon_rmEventPage(event, field->map->id, field->state);
+ int page = c->erased ? -1 : kon_rmEventPage(event, field->map->id, field->state);
if (page == c->pageIndex) continue;
if (page < 0) clearPage(c);
@@ -445,6 +445,7 @@ int kon_rmFieldInit(kon_rmField_t *field, const kon_rmMap_t *map, const kon_rmTi
field->chars = chars;
field->count = map->eventCount + 1;
field->seed = 2463534242u;
+ field->waitIndex = -1;
memset(chars, 0, sizeof *chars * (size_t)field->count);
for (i = 0; i < field->count; i++) {
kon_rmChar_t *c = &chars[i];
@@ -665,6 +666,184 @@ int kon_rmFieldRouteBusy(const kon_rmField_t *field, int index)
return field->chars[index].routeForced;
}
+/*** commands from scripts ***/
+
+/* the numbers of a text the way mzscript writes them, apart by tabs. what is not a number is 0 */
+static int parseInts(const char *text, int *out, int max)
+{
+ int n = 0;
+
+ if (!text) return 0;
+ while (*text && n < max) {
+ long v = 0;
+ int neg = 0, any = 0;
+
+ if (*text == '-') {
+ neg = 1;
+ text++;
+ }
+ while (*text >= '0' && *text <= '9') {
+ if (v < 100000000) v = v * 10 + (*text - '0');
+ any = 1;
+ text++;
+ }
+ out[n++] = any ? (int)(neg ? -v : v) : 0;
+ while (*text && *text != '\t') text++;
+ if (*text == '\t') text++;
+ else break;
+ }
+ return n;
+}
+
+static int variable(const kon_rmField_t *field, int id)
+{
+ return field->state && field->state->variable ? field->state->variable(id, field->state->user) : 0;
+}
+
+/* -1 is the player, 0 the event the script belongs to, the others are events by id */
+static int charIndex(const kon_rmField_t *field, int id)
+{
+ int i;
+
+ if (id == -1) return KON_RM_PLAYER;
+ if (id == 0) id = field->thisEvent;
+ if (id <= 0) return -1;
+ i = kon_rmFieldFind(field, id);
+ return i >= 0 && field->chars[i].active ? i : -1;
+}
+
+static void locate(kon_rmField_t *field, int index, int x, int y, int dir)
+{
+ kon_rmChar_t *c = &field->chars[index];
+
+ if (!kon_rmValid(field->map, x, y)) return;
+ c->x = kon_rmRoundX(field->map, x);
+ c->y = kon_rmRoundY(field->map, y);
+ c->remain = 0;
+ c->jumpCount = 0;
+ c->stopCount = 0;
+ if (validDir(dir)) setDir(c, dir);
+}
+
+static void hostRoute(kon_rmField_t *field, int index, int flags, const char *text)
+{
+ kon_rmChar_t *c = &field->chars[index];
+ int size = 0;
+ const char *s = text;
+
+ /* the route is read command by command, the whole ones that fit */
+ while (s && *s) {
+ int head[2], numbers[8], n, i;
+ const char *next = s;
+
+ if (parseInts(s, head, 2) < 2 || head[1] < 0 || head[1] > 8 || size + 2 + head[1] > KON_RM_ROUTE_MAX) break;
+ for (i = 0; i < 2 && *next; i++) {
+ while (*next && *next != '\t') next++;
+ if (*next == '\t') next++;
+ }
+ n = parseInts(next, numbers, head[1]);
+ if (n < head[1]) break;
+ c->routeData[size++] = head[0];
+ c->routeData[size++] = head[1];
+ for (i = 0; i < head[1]; i++) c->routeData[size++] = numbers[i];
+ for (i = 0; i < head[1]; i++) {
+ while (*next && *next != '\t') next++;
+ if (*next == '\t') next++;
+ }
+ s = next;
+ }
+ kon_rmFieldRoute(field, index, c->routeData, size, flags & 1, (flags >> 1) & 1);
+ if ((flags & 4) && !(flags & 1)) field->waitIndex = index;
+}
+
+int kon_rmFieldHost(kon_rmField_t *field, int code, int b, int c, int d, const char *text)
+{
+ int n[8] = {0}, count, index;
+
+ if (!field || !field->chars) return -1;
+ count = parseInts(text, n, 8);
+ if (count == 0) {
+ n[0] = b;
+ n[1] = c;
+ n[2] = d;
+ }
+ switch (code) {
+ case 123:
+ if (field->selfSwitchSet && field->thisEvent > 0 && text && text[0] >= 'A' && text[0] <= 'D') {
+ field->selfSwitchSet(field->map->id, field->thisEvent, text[0], n[1] == 0, field->user);
+ kon_rmFieldRefresh(field);
+ }
+ return 0;
+ case 201: {
+ kon_rmTransfer_t *t = &field->transfer;
+
+ t->mapId = n[0] == 1 ? variable(field, n[1]) : n[1];
+ t->x = n[0] == 1 ? variable(field, n[2]) : n[2];
+ t->y = n[0] == 1 ? variable(field, n[3]) : n[3];
+ t->dir = validDir(n[4]) ? n[4] : 0;
+ t->fade = n[5] >= 0 && n[5] <= 2 ? n[5] : 0;
+ if (t->mapId <= 0) return 0;
+ field->transferPending = 1;
+ return 1;
+ }
+ case 203:
+ index = charIndex(field, n[0]);
+ if (index < 0) return 0;
+ if (n[1] == 2) {
+ int other = charIndex(field, n[2]);
+
+ if (other >= 0) {
+ int ox = field->chars[other].x, oy = field->chars[other].y;
+
+ locate(field, other, field->chars[index].x, field->chars[index].y, n[4]);
+ locate(field, index, ox, oy, n[4]);
+ }
+ } else if (n[1] == 1) {
+ locate(field, index, variable(field, n[2]), variable(field, n[3]), n[4]);
+ } else {
+ locate(field, index, n[2], n[3], n[4]);
+ }
+ return 0;
+ case 205:
+ index = charIndex(field, b);
+ if (index < 0) return 0;
+ hostRoute(field, index, c, text);
+ return field->waitIndex == index ? 1 : 0;
+ case 211:
+ field->chars[KON_RM_PLAYER].transparent = b == 0;
+ return 0;
+ case 214:
+ index = charIndex(field, 0);
+ if (index > 0) {
+ field->chars[index].erased = 1;
+ clearPage(&field->chars[index]);
+ }
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int kon_rmFieldBusy(const kon_rmField_t *field)
+{
+ if (!field || !field->chars) return 0;
+ if (field->transferPending) return 1;
+ if (field->waitIndex >= 0 && field->waitIndex < field->count) {
+ const kon_rmChar_t *c = &field->chars[field->waitIndex];
+
+ return c->active && c->routeForced;
+ }
+ return 0;
+}
+
+int kon_rmFieldTransfer(kon_rmField_t *field, kon_rmTransfer_t *transfer)
+{
+ if (!field || !field->transferPending) return 0;
+ field->transferPending = 0;
+ if (transfer) *transfer = field->transfer;
+ return 1;
+}
+
/*** drawing ***/
int kon_rmCharX(const kon_rmChar_t *c)
diff --git a/kon/konrmfield.h b/kon/konrmfield.h
index 50a5607..d2b358f 100644
--- a/kon/konrmfield.h
+++ b/kon/konrmfield.h
@@ -41,6 +41,7 @@ extern "C" {
chars[i + 1] is events[i]. you give kon_rmFieldInit the array, it has to have room for eventCount + 1 */
#define KON_RM_PLAYER 0
+#define KON_RM_ROUTE_MAX 256 /* the numbers a route given by a script can have, a longer one is cut after its last whole command */
typedef struct kon_rmChar {
int active; /* the player always, an event when one of its pages is on */
@@ -57,8 +58,16 @@ typedef struct kon_rmChar {
int success; /* did the last step work */
const int32_t *route; /* what it follows: a page's route, or one it was told to */
int routeSize, routeIndex, routeWait, routeRepeat, routeSkippable, routeForced;
+ int erased; /* taken off the map by a script until the map is entered again */
+ int32_t routeData[KON_RM_ROUTE_MAX]; /* where a route from a script is kept */
} kon_rmChar_t;
+/* where a script sends the player: map, tile, the way to look (2, 4, 6, 8, or 0 to keep looking the same way) and the fade
+ (0 black, 1 white, 2 none) */
+typedef struct kon_rmTransfer {
+ int mapId, x, y, dir, fade;
+} kon_rmTransfer_t;
+
typedef struct kon_rmField {
const kon_rmMap_t *map;
const kon_rmTileset_t *tileset;
@@ -68,11 +77,16 @@ typedef struct kon_rmField {
uint32_t seed;
int busy; /* set it while a script that stops the player is running */
int steps; /* the steps of the player, count them for encounters and take them back to 0 */
+ int thisEvent; /* the id of the event whose script is running, for the commands that mean "this event". set it before you run one */
+ int waitIndex; /* the character a script waits for, -1 for none */
+ int transferPending;
+ kon_rmTransfer_t transfer;
/* both can stay NULL. the first is how a route that turns a switch on or off reaches your game, the second says whether
the script a page names has anything in it, an event with an empty one does not start */
void (*switchSet)(int id, int on, void *user);
int (*hasScript)(const char *label, void *user);
+ void (*selfSwitchSet)(int mapId, int eventId, char letter, int on, void *user);
void *user;
} kon_rmField_t;
@@ -118,6 +132,19 @@ void kon_rmFieldUnlock(kon_rmField_t *field, int index);
void kon_rmFieldRoute(kon_rmField_t *field, int index, const int32_t *route, int size, int repeat, int skippable);
int kon_rmFieldRouteBusy(const kon_rmField_t *field, int index);
+/*** commands from scripts ***/
+
+/* the RPG Maker commands that are about the map, the way mzscript writes them: 201 move the player to another map, 203 put a
+ character somewhere, 205 give a character a route, 211 the player's transparency, 214 take this event off the map and 123
+ a self switch. returns -1 when the code is not one of those, 0 when it is done, 1 when the script is meant to wait. that
+ wait is over when kon_rmFieldBusy says 0, so the host function returns 1 and you call kon_scriptResume then.
+ a transfer is not done here: you take it with kon_rmFieldTransfer, load the map and start another field. */
+int kon_rmFieldHost(kon_rmField_t *field, int code, int b, int c, int d, const char *text);
+int kon_rmFieldBusy(const kon_rmField_t *field);
+
+/* 1 and the transfer when a script asked for one, once */
+int kon_rmFieldTransfer(kon_rmField_t *field, kon_rmTransfer_t *transfer);
+
/*** drawing ***/
/* where it is in 256ths of a tile, in the middle of a step too. a map that loops can give a bit past its edge, the picture