diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-16 01:30:11 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-16 01:30:11 +0800 |
| commit | 7f76b57eb13bebb81b0e13875cf51114a2b9888c (patch) | |
| tree | 0af8bb892ec430a81eb786942dc33ca60ad3b027 | |
| parent | 3dbb48bb7ec9f52c01ac172f8d75e6d0126d244f (diff) | |
add rmfield: player and events walking an rpg maker map
| -rw-r--r-- | kon/konrmfield.c | 698 | ||||
| -rw-r--r-- | kon/konrmfield.h | 133 |
2 files changed, 831 insertions, 0 deletions
diff --git a/kon/konrmfield.c b/kon/konrmfield.c new file mode 100644 index 0000000..f165df7 --- /dev/null +++ b/kon/konrmfield.c @@ -0,0 +1,698 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konrmfield.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 "konrmfield.h" + +#include <stddef.h> +#include <string.h> + +/*** helpers ***/ + +static int validDir(int dir) +{ + return dir == 2 || dir == 4 || dir == 6 || dir == 8; +} + +static int clampInt(int v, int lo, int hi) +{ + return v < lo ? lo : v > hi ? hi : v; +} + +static int roll(kon_rmField_t *field, int n) +{ + uint32_t s = field->seed; + + s ^= s << 13; + s ^= s >> 17; + s ^= s << 5; + field->seed = s ? s : 1; + return n > 0 ? (int)(field->seed % (uint32_t)n) : 0; +} + +static int dirX(int dir) +{ + return dir == 6 ? 1 : dir == 4 ? -1 : 0; +} + +static int dirY(int dir) +{ + return dir == 2 ? 1 : dir == 8 ? -1 : 0; +} + +/* how far a is right of b, the short way round on a map that loops */ +static int deltaX(const kon_rmMap_t *map, int a, int b) +{ + int r = a - b; + + if ((map->scrollType == 2 || map->scrollType == 3) && (r < 0 ? -r : r) > map->width / 2) r += r < 0 ? map->width : -map->width; + return r; +} + +static int deltaY(const kon_rmMap_t *map, int a, int b) +{ + int r = a - b; + + if ((map->scrollType == 1 || map->scrollType == 3) && (r < 0 ? -r : r) > map->height / 2) r += r < 0 ? map->height : -map->height; + return r; +} + +static int moving(const kon_rmChar_t *c) +{ + return c->remain > 0 || c->jumpCount > 0; +} + +static void setDir(kon_rmChar_t *c, int dir) +{ + if (!c->dirFix && validDir(dir)) c->dir = dir; + c->stopCount = 0; +} + +/*** what blocks what ***/ + +static int collidedWithEvents(const kon_rmField_t *field, int self, int x, int y, int normalOnly) +{ + int i; + + for (i = 1; i < field->count; i++) { + const kon_rmChar_t *e = &field->chars[i]; + + if (i == self || !e->active || e->through || e->x != x || e->y != y) continue; + if (!normalOnly || e->priority == 1) return 1; + } + return 0; +} + +static int canPass(const kon_rmField_t *field, int index, int x, int y, int dir) +{ + const kon_rmChar_t *c = &field->chars[index]; + const kon_rmChar_t *player = &field->chars[KON_RM_PLAYER]; + int x2 = kon_rmRoundX(field->map, x + dirX(dir)); + int y2 = kon_rmRoundY(field->map, y + dirY(dir)); + + if (!kon_rmValid(field->map, x2, y2)) return 0; + if (c->through) return 1; + if (!kon_rmPassable(field->map, field->tileset, x, y, dir)) return 0; + if (!kon_rmPassable(field->map, field->tileset, x2, y2, 10 - dir)) return 0; + if (index == KON_RM_PLAYER) return !collidedWithEvents(field, -1, x2, y2, 1); + if (collidedWithEvents(field, index, x2, y2, 0)) return 0; + return !(c->priority == 1 && player->active && !player->through && player->x == x2 && player->y == y2); +} + +static int canPassDiagonally(const kon_rmField_t *field, int index, int x, int y, int horz, int vert) +{ + int x2 = kon_rmRoundX(field->map, x + dirX(horz)); + int y2 = kon_rmRoundY(field->map, y + dirY(vert)); + + if (canPass(field, index, x, y, vert) && canPass(field, index, x, y2, horz)) return 1; + return canPass(field, index, x, y, horz) && canPass(field, index, x2, y, vert); +} + +/*** starting events ***/ + +static void start(kon_rmField_t *field, int index) +{ + kon_rmChar_t *e = &field->chars[index]; + + if (!e->active || !e->page) return; + if (field->hasScript && !field->hasScript(e->page->script, field->user)) return; + e->starting = 1; + if (e->trigger <= 2 && !e->locked) { + const kon_rmChar_t *p = &field->chars[KON_RM_PLAYER]; + int sx = deltaX(field->map, e->x, p->x), sy = deltaY(field->map, e->y, p->y); + + e->preLockDir = e->dir; + if ((sx < 0 ? -sx : sx) > (sy < 0 ? -sy : sy)) setDir(e, sx > 0 ? 4 : 6); + else if (sy != 0) setDir(e, sy > 0 ? 8 : 2); + e->locked = 1; + } +} + +/* events on x, y that start with one of the triggers (bits of mask), the ones at the same height as the characters or the others */ +static int startAt(kon_rmField_t *field, int x, int y, int mask, int normal) +{ + int i, n = 0; + + if (field->busy || !kon_rmValid(field->map, x, y)) return 0; + for (i = 1; i < field->count; i++) { + kon_rmChar_t *e = &field->chars[i]; + + if (!e->active || e->x != x || e->y != y || !((mask >> e->trigger) & 1) || (e->priority == 1) != normal) continue; + start(field, i); + if (e->starting) n++; + } + return n; +} + +static void touchFront(kon_rmField_t *field, int index, int dir) +{ + const kon_rmChar_t *c = &field->chars[index]; + int x2 = kon_rmRoundX(field->map, c->x + dirX(dir)); + int y2 = kon_rmRoundY(field->map, c->y + dirY(dir)); + + if (field->busy) return; + if (index == KON_RM_PLAYER) { + startAt(field, x2, y2, 0x06, 1); + } else if (c->trigger == 2 && c->priority == 1 && c->jumpCount == 0) { + const kon_rmChar_t *p = &field->chars[KON_RM_PLAYER]; + + if (p->active && p->x == x2 && p->y == y2) start(field, index); + } +} + +/*** steps ***/ + +static int step(kon_rmField_t *field, int index, int dir) +{ + kon_rmChar_t *c = &field->chars[index]; + int ok = canPass(field, index, c->x, c->y, dir); + + c->success = ok; + setDir(c, dir); + if (ok) { + c->moveDx = dirX(dir); + c->moveDy = dirY(dir); + c->x = kon_rmRoundX(field->map, c->x + c->moveDx); + c->y = kon_rmRoundY(field->map, c->y + c->moveDy); + c->remain = 256; + c->stopCount = 0; + c->steps++; + if (index == KON_RM_PLAYER) field->steps++; + } else { + touchFront(field, index, dir); + } + return ok; +} + +static void stepDiagonal(kon_rmField_t *field, int index, int horz, int vert) +{ + kon_rmChar_t *c = &field->chars[index]; + int ok = canPassDiagonally(field, index, c->x, c->y, horz, vert); + + c->success = ok; + if (ok) { + c->moveDx = dirX(horz); + c->moveDy = dirY(vert); + c->x = kon_rmRoundX(field->map, c->x + c->moveDx); + c->y = kon_rmRoundY(field->map, c->y + c->moveDy); + c->remain = 256; + c->stopCount = 0; + c->steps++; + if (index == KON_RM_PLAYER) field->steps++; + } + if (c->dir == 10 - horz) setDir(c, horz); + if (c->dir == 10 - vert) setDir(c, vert); +} + +static void stepRandom(kon_rmField_t *field, int index) +{ + kon_rmChar_t *c = &field->chars[index]; + int dir = 2 + roll(field, 4) * 2; + + if (canPass(field, index, c->x, c->y, dir)) step(field, index, dir); +} + +static void stepToward(kon_rmField_t *field, int index, int away) +{ + kon_rmChar_t *c = &field->chars[index]; + const kon_rmChar_t *p = &field->chars[KON_RM_PLAYER]; + int sx = deltaX(field->map, c->x, p->x), sy = deltaY(field->map, c->y, p->y); + int h = (sx > 0) != away ? 4 : 6, v = (sy > 0) != away ? 8 : 2; + + if ((sx < 0 ? -sx : sx) > (sy < 0 ? -sy : sy)) { + step(field, index, h); + if (!c->success && sy != 0) step(field, index, v); + } else if (sy != 0) { + step(field, index, v); + if (!c->success && sx != 0) step(field, index, h); + } +} + +static void turnToward(kon_rmField_t *field, int index, int away) +{ + kon_rmChar_t *c = &field->chars[index]; + const kon_rmChar_t *p = &field->chars[KON_RM_PLAYER]; + int sx = deltaX(field->map, c->x, p->x), sy = deltaY(field->map, c->y, p->y); + int h = (sx > 0) != away ? 4 : 6, v = (sy > 0) != away ? 8 : 2; + + if ((sx < 0 ? -sx : sx) > (sy < 0 ? -sy : sy)) setDir(c, h); + else if (sy != 0) setDir(c, v); +} + +static void jump(kon_rmField_t *field, int index, int dx, int dy) +{ + kon_rmChar_t *c = &field->chars[index]; + int x2 = kon_rmRoundX(field->map, c->x + dx), y2 = kon_rmRoundY(field->map, c->y + dy); + int d2 = dx * dx + dy * dy, dist = 0; + + if (dx > 1000 || dx < -1000 || dy > 1000 || dy < -1000 || !kon_rmValid(field->map, x2, y2)) return; + if (dx != 0 || dy != 0) { + if ((dx < 0 ? -dx : dx) > (dy < 0 ? -dy : dy)) setDir(c, dx < 0 ? 4 : 6); + else if (dy != 0) setDir(c, dy < 0 ? 8 : 2); + } + while ((dist + 1) * (dist + 1) <= d2 + d2 / 4 + 1) dist++; /* about the length, rounded */ + c->x = x2; + c->y = y2; + c->jumpDx = dx; + c->jumpDy = dy; + c->jumpPeak = clampInt(10 + dist - c->moveSpeed, 1, 2000); + c->jumpCount = c->jumpPeak * 2; + c->stopCount = 0; + c->steps++; +} + +/*** routes ***/ + +static void runCommand(kon_rmField_t *field, int index, int code, int count, const int32_t *p) +{ + kon_rmChar_t *c = &field->chars[index]; + int a = count > 0 ? p[0] : 0; + + switch (code) { + case 1: case 2: case 3: case 4: step(field, index, code == 1 ? 2 : code == 2 ? 4 : code == 3 ? 6 : 8); break; + case 5: stepDiagonal(field, index, 4, 2); break; + case 6: stepDiagonal(field, index, 6, 2); break; + case 7: stepDiagonal(field, index, 4, 8); break; + case 8: stepDiagonal(field, index, 6, 8); break; + case 9: stepRandom(field, index); break; + case 10: stepToward(field, index, 0); break; + case 11: stepToward(field, index, 1); break; + case 12: step(field, index, c->dir); break; + case 13: { + int fix = c->dirFix; + + c->dirFix = 1; + step(field, index, 10 - c->dir); + c->dirFix = fix; + break; + } + case 14: jump(field, index, a, count > 1 ? p[1] : 0); break; + case 15: c->routeWait = a > 1 ? (a > 1000000 ? 1000000 : a) - 1 : 0; break; + case 16: setDir(c, 2); break; + case 17: setDir(c, 4); break; + case 18: setDir(c, 6); break; + case 19: setDir(c, 8); break; + case 20: setDir(c, c->dir == 2 ? 4 : c->dir == 4 ? 8 : c->dir == 8 ? 6 : 2); break; + case 21: setDir(c, c->dir == 2 ? 6 : c->dir == 6 ? 8 : c->dir == 8 ? 4 : 2); break; + case 22: setDir(c, 10 - c->dir); break; + case 23: setDir(c, roll(field, 2) ? (c->dir == 2 ? 6 : c->dir == 6 ? 8 : c->dir == 8 ? 4 : 2) : (c->dir == 2 ? 4 : c->dir == 4 ? 8 : c->dir == 8 ? 6 : 2)); break; + case 24: setDir(c, 2 + roll(field, 4) * 2); break; + case 25: turnToward(field, index, 0); break; + case 26: turnToward(field, index, 1); break; + case 27: case 28: if (field->switchSet && count > 0) field->switchSet(a, code == 27, field->user); break; + case 29: c->moveSpeed = clampInt(a, 1, 6); break; + case 30: c->moveFrequency = clampInt(a, 1, 5); break; + case 31: c->walkAnime = 1; break; + case 32: c->walkAnime = 0; break; + case 33: c->stepAnime = 1; break; + case 34: c->stepAnime = 0; break; + case 35: c->dirFix = 1; break; + case 36: c->dirFix = 0; break; + case 37: c->through = 1; break; + case 38: c->through = 0; break; + case 39: c->transparent = 1; break; + case 40: c->transparent = 0; break; + case 42: c->opacity = clampInt(a, 0, 255); break; + default: break; /* the image, blend, sound and script commands are not here */ + } +} + +static void routeStep(kon_rmField_t *field, int index) +{ + kon_rmChar_t *c = &field->chars[index]; + int code, count; + + if (c->routeWait > 0) { + c->routeWait--; + return; + } + c->success = 1; + if (!c->route || c->routeIndex < 0 || c->routeIndex + 2 > c->routeSize) { + /* the end of the route */ + if (c->routeRepeat) c->routeIndex = 0; + else if (c->routeForced) c->routeForced = 0; + return; + } + code = c->route[c->routeIndex]; + count = c->route[c->routeIndex + 1]; + if (count < 0 || count > 8 || c->routeIndex + 2 + count > c->routeSize) { + c->routeIndex = c->routeSize; + return; + } + runCommand(field, index, code, count, c->route + c->routeIndex + 2); + if (c->success || c->routeSkippable) c->routeIndex += 2 + count; +} + +/*** pages ***/ + +static void setupPage(kon_rmField_t *field, kon_rmChar_t *c, int pageIndex, const kon_rmPage_t *page) +{ + int dir = validDir(page->direction) ? page->direction : 2; + + c->pageIndex = pageIndex; + c->page = page; + c->active = 1; + if (c->originalDir != dir) { + c->originalDir = dir; + c->preLockDir = 0; + c->dirFix = 0; + c->dir = dir; + } + c->moveSpeed = clampInt(page->moveSpeed, 1, 6); + c->moveFrequency = clampInt(page->moveFrequency, 1, 5); + c->moveType = clampInt(page->moveType, 0, 3); + c->priority = clampInt(page->priority, 0, 2); + c->trigger = clampInt(page->trigger, 0, 4); + c->walkAnime = page->walkAnime; + c->stepAnime = page->stepAnime; + c->dirFix = page->directionFix; + c->through = page->through; + c->transparent = 0; + c->opacity = 255; + c->route = page->route; + c->routeSize = page->route ? page->routeSize : 0; + c->routeIndex = 0; + c->routeWait = 0; + c->routeRepeat = page->routeRepeat; + c->routeSkippable = page->routeSkippable; + c->routeForced = 0; + c->locked = 0; + c->starting = 0; + c->stopCount = 0; + (void)field; +} + +static void clearPage(kon_rmChar_t *c) +{ + c->pageIndex = -1; + c->page = NULL; + c->active = 0; + c->locked = 0; + c->starting = 0; + c->route = NULL; + c->routeSize = 0; + c->routeForced = 0; +} + +void kon_rmFieldRefresh(kon_rmField_t *field) +{ + int i; + + if (!field || !field->map) return; + 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); + + if (page == c->pageIndex) continue; + if (page < 0) clearPage(c); + else setupPage(field, c, page, &event->pages[page]); + } +} + +/*** the field ***/ + +int kon_rmFieldInit(kon_rmField_t *field, const kon_rmMap_t *map, const kon_rmTileset_t *tileset, const kon_rmState_t *state, kon_rmChar_t *chars, int capacity) +{ + int i; + + if (!field || !map || !chars || capacity < map->eventCount + 1 || map->eventCount < 0) return -1; + memset(field, 0, sizeof *field); + field->map = map; + field->tileset = tileset; + field->state = state; + field->chars = chars; + field->count = map->eventCount + 1; + field->seed = 2463534242u; + memset(chars, 0, sizeof *chars * (size_t)field->count); + for (i = 0; i < field->count; i++) { + kon_rmChar_t *c = &chars[i]; + + c->dir = 2; + c->originalDir = 2; + c->moveSpeed = 4; + c->moveFrequency = 3; + c->priority = 1; + c->opacity = 255; + c->pageIndex = -1; + if (i > 0) { + c->x = map->events[i - 1].x; + c->y = map->events[i - 1].y; + c->eventId = map->events[i - 1].id; + } + } + chars[KON_RM_PLAYER].active = 1; + chars[KON_RM_PLAYER].trigger = 0; + kon_rmFieldRefresh(field); + return 0; +} + +void kon_rmFieldSeed(kon_rmField_t *field, uint32_t seed) +{ + if (field) field->seed = seed ? seed : 1; +} + +void kon_rmFieldPlace(kon_rmField_t *field, int x, int y, int dir) +{ + kon_rmChar_t *p; + + if (!field || !field->chars) return; + p = &field->chars[KON_RM_PLAYER]; + if (kon_rmValid(field->map, x, y)) { + p->x = kon_rmRoundX(field->map, x); + p->y = kon_rmRoundY(field->map, y); + } + p->remain = 0; + p->jumpCount = 0; + if (validDir(dir)) p->dir = dir; +} + +static int stopThreshold(const kon_rmChar_t *c) +{ + return 30 * (5 - c->moveFrequency); +} + +static void selfMove(kon_rmField_t *field, int index) +{ + kon_rmChar_t *c = &field->chars[index]; + const kon_rmChar_t *p = &field->chars[KON_RM_PLAYER]; + + if (c->stopCount <= stopThreshold(c)) return; + switch (c->moveType) { + case 1: + switch (roll(field, 6)) { + case 0: case 1: stepRandom(field, index); break; + case 2: case 3: case 4: step(field, index, c->dir); break; + default: c->stopCount = 0; break; + } + break; + case 2: { + int sx = deltaX(field->map, c->x, p->x), sy = deltaY(field->map, c->y, p->y); + + if ((sx < 0 ? -sx : sx) + (sy < 0 ? -sy : sy) < 20) { + switch (roll(field, 6)) { + case 0: case 1: case 2: case 3: stepToward(field, index, 0); break; + case 4: stepRandom(field, index); break; + default: step(field, index, c->dir); break; + } + } else { + stepRandom(field, index); + } + break; + } + case 3: routeStep(field, index); break; + default: break; + } +} + +static void updateChar(kon_rmField_t *field, int index) +{ + kon_rmChar_t *c = &field->chars[index]; + + if (c->jumpCount > 0) { + c->jumpCount--; + return; + } + if (c->remain > 0) { + c->remain -= 1 << clampInt(c->moveSpeed + c->dashing, 1, 8); + if (c->remain <= 0) { + c->remain = 0; + c->dashing = 0; + } + return; + } + c->dashing = 0; + c->stopCount++; + if (c->routeForced) routeStep(field, index); + else if (index > 0 && !c->locked && c->moveType) selfMove(field, index); +} + +void kon_rmFieldUpdate(kon_rmField_t *field) +{ + kon_rmChar_t *p; + int i, wasMoving; + + if (!field || !field->chars) return; + p = &field->chars[KON_RM_PLAYER]; + wasMoving = moving(p); + for (i = 0; i < field->count; i++) { + if (field->chars[i].active) updateChar(field, i); + } + for (i = 1; i < field->count; i++) { + kon_rmChar_t *e = &field->chars[i]; + + if (e->active && e->trigger == 3 && !field->busy) start(field, i); + } + if (wasMoving && !moving(p) && !field->busy) startAt(field, p->x, p->y, 0x06, 0); +} + +/*** the player ***/ + +int kon_rmFieldPlayerStep(kon_rmField_t *field, int dir, int dash) +{ + kon_rmChar_t *p; + + if (!field || !field->chars || !validDir(dir)) return 0; + p = &field->chars[KON_RM_PLAYER]; + if (field->busy || moving(p) || p->routeForced) return 0; + step(field, KON_RM_PLAYER, dir); + if (p->success && dash && !field->map->disableDashing) p->dashing = 1; + return p->success; +} + +int kon_rmFieldAction(kon_rmField_t *field) +{ + const kon_rmChar_t *p; + int x, y, x2, y2, n; + + if (!field || !field->chars) return 0; + p = &field->chars[KON_RM_PLAYER]; + if (field->busy || moving(p)) return 0; + if (startAt(field, p->x, p->y, 0x01, 0)) return 1; + x = kon_rmRoundX(field->map, p->x + dirX(p->dir)); + y = kon_rmRoundY(field->map, p->y + dirY(p->dir)); + n = startAt(field, x, y, 0x07, 1); + if (n == 0 && kon_rmHasFlag(field->map, field->tileset, x, y, KON_RM_FLAG_COUNTER)) { + x2 = kon_rmRoundX(field->map, x + dirX(p->dir)); + y2 = kon_rmRoundY(field->map, y + dirY(p->dir)); + n = startAt(field, x2, y2, 0x07, 1); + } + return n > 0; +} + +/*** events that start ***/ + +int kon_rmFieldNextStart(kon_rmField_t *field, int *eventId, int *pageIndex) +{ + int i; + + if (!field || !field->chars) return 0; + for (i = 1; i < field->count; i++) { + kon_rmChar_t *e = &field->chars[i]; + + if (!e->active || !e->starting) continue; + e->starting = 0; + if (eventId) *eventId = e->eventId; + if (pageIndex) *pageIndex = e->pageIndex; + return 1; + } + return 0; +} + +int kon_rmFieldFind(const kon_rmField_t *field, int eventId) +{ + int i; + + if (!field || !field->chars) return -1; + for (i = 1; i < field->count; i++) { + if (field->chars[i].eventId == eventId) return i; + } + return -1; +} + +void kon_rmFieldUnlock(kon_rmField_t *field, int index) +{ + kon_rmChar_t *c; + + if (!field || !field->chars || index < 0 || index >= field->count) return; + c = &field->chars[index]; + if (!c->locked) return; + c->locked = 0; + if (validDir(c->preLockDir)) setDir(c, c->preLockDir); +} + +/*** moving by command ***/ + +void kon_rmFieldRoute(kon_rmField_t *field, int index, const int32_t *route, int size, int repeat, int skippable) +{ + kon_rmChar_t *c; + + if (!field || !field->chars || index < 0 || index >= field->count) return; + c = &field->chars[index]; + c->route = route; + c->routeSize = route && size > 0 ? size : 0; + c->routeIndex = 0; + c->routeWait = 0; + c->routeRepeat = repeat; + c->routeSkippable = skippable; + c->routeForced = 1; +} + +int kon_rmFieldRouteBusy(const kon_rmField_t *field, int index) +{ + if (!field || !field->chars || index < 0 || index >= field->count) return 0; + return field->chars[index].routeForced; +} + +/*** drawing ***/ + +int kon_rmCharX(const kon_rmChar_t *c) +{ + int jump = 0; + + if (c->jumpCount > 0) jump = c->jumpDx * 256 * c->jumpCount / (c->jumpPeak * 2); + return c->x * 256 - c->moveDx * c->remain - jump; +} + +int kon_rmCharY(const kon_rmChar_t *c) +{ + int jump = 0; + + if (c->jumpCount > 0) jump = c->jumpDy * 256 * c->jumpCount / (c->jumpPeak * 2); + return c->y * 256 - c->moveDy * c->remain - jump; +} + +int kon_rmCharJump(const kon_rmChar_t *c) +{ + int d; + + if (c->jumpCount <= 0) return 0; + d = c->jumpCount - c->jumpPeak; + return (c->jumpPeak * c->jumpPeak - d * d) / 2; +} + +int kon_rmCharMoving(const kon_rmChar_t *c) +{ + return moving(c); +} diff --git a/kon/konrmfield.h b/kon/konrmfield.h new file mode 100644 index 0000000..50a5607 --- /dev/null +++ b/kon/konrmfield.h @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konrmfield.h + * + * 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. + */ + +#ifndef KONRMFIELD_H +#define KONRMFIELD_H + +#include "konrmmap.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*** definition ***/ + +/* the player and the events of one RPG Maker map, walking around on it by RPG Maker's rules: a step at a time at the speed of + the character, events that wander or follow a route, what blocks what, and what starts an event. nothing is drawn and no + script runs here, the game asks who wants to start and runs the page's script itself. + a character is in chars[0] for the player, then one for each event of the map in the order of the map's events, so + 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 + +typedef struct kon_rmChar { + int active; /* the player always, an event when one of its pages is on */ + int x, y, dir; /* the tile it is on, or is walking to, and where it looks (2, 4, 6, 8) */ + int moveDx, moveDy, remain; /* a step still to do: its direction, and what is left of it in 256ths of a tile */ + int jumpCount, jumpPeak, jumpDx, jumpDy; + int moveSpeed, moveFrequency, dashing; + int priority, trigger, moveType; + int through, dirFix, walkAnime, stepAnime, transparent, opacity; + int pageIndex, eventId, originalDir; + const kon_rmPage_t *page; + int stopCount, locked, preLockDir, starting; + int steps; /* how many it has taken, for the walking animation */ + 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; +} kon_rmChar_t; + +typedef struct kon_rmField { + const kon_rmMap_t *map; + const kon_rmTileset_t *tileset; + const kon_rmState_t *state; /* what the pages need to know, see konrmmap.h */ + kon_rmChar_t *chars; + int count; + 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 */ + + /* 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 *user; +} kon_rmField_t; + +/* returns 0, or -1 when there is no room in chars or something is NULL. the events start on their first page that is on */ +int kon_rmFieldInit(kon_rmField_t *field, const kon_rmMap_t *map, const kon_rmTileset_t *tileset, const kon_rmState_t *state, kon_rmChar_t *chars, int capacity); + +void kon_rmFieldSeed(kon_rmField_t *field, uint32_t seed); +void kon_rmFieldPlace(kon_rmField_t *field, int x, int y, int dir); + +/* looks at the pages again, call it when a switch, variable, item or self switch has changed */ +void kon_rmFieldRefresh(kon_rmField_t *field); + +/* one frame (a 60th of a second) for everybody */ +void kon_rmFieldUpdate(kon_rmField_t *field); + +/*** the player ***/ + +/* tries a step. nothing happens while the player is still walking, while busy or in a forced route. it turns and returns 0 when + something is in the way, and then an event in front that starts by touch does. dash only counts where the map allows it */ +int kon_rmFieldPlayerStep(kon_rmField_t *field, int dir, int dash); + +/* the action button: an event on the player's tile or in front of it, or behind a counter. returns 1 when one starts */ +int kon_rmFieldAction(kon_rmField_t *field); + +/*** events that start ***/ + +/* the next event that wants its script to run, lowest first: the touched or talked to, and the autorun ones. call it when no + script is running. returns 1 and fills eventId and pageIndex (the script is the page's script label), or 0 for none. + events that started by touch or action look at the player until kon_rmFieldUnlock. parallel pages (trigger 4) never + come out of here, they are for the game to run on the side */ +int kon_rmFieldNextStart(kon_rmField_t *field, int *eventId, int *pageIndex); + +/* index into chars of the event with that id, or -1 */ +int kon_rmFieldFind(const kon_rmField_t *field, int eventId); + +/* lets an event go on walking and turn back the way it was looking. for the ones that started by touch or action */ +void kon_rmFieldUnlock(kon_rmField_t *field, int index); + +/*** moving by command ***/ + +/* makes a character follow a route (the same shape as a page's: code, count, count numbers). the page's own route waits + until it is done. kon_rmFieldRouteBusy says 1 until the route has run to its end, a repeating one never ends */ +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); + +/*** 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 + wraps it. the jump height is in pixels, for what is drawn over the ground */ +int kon_rmCharX(const kon_rmChar_t *c); +int kon_rmCharY(const kon_rmChar_t *c); +int kon_rmCharJump(const kon_rmChar_t *c); +int kon_rmCharMoving(const kon_rmChar_t *c); + +#ifdef __cplusplus +} +#endif +#endif |
