summaryrefslogtreecommitdiff
path: root/kon/konbattle.c
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-13 04:58:34 +0800
committerKonAki <konaki@anokonaki.org>2026-10-13 04:58:34 +0800
commite30ee839142c2ed1466dd0991470c5528e717fcc (patch)
treec52c7e91cf6ecb1c5aa899fc55c2c58cc3e41705 /kon/konbattle.c
parentc3e0f2d163d5dc0b2a9e19b72d85c1611ef95270 (diff)
add battle module: stats, states, skills, formulas, turn order
Diffstat (limited to 'kon/konbattle.c')
-rw-r--r--kon/konbattle.c1089
1 files changed, 1089 insertions, 0 deletions
diff --git a/kon/konbattle.c b/kon/konbattle.c
new file mode 100644
index 0000000..e0ed33d
--- /dev/null
+++ b/kon/konbattle.c
@@ -0,0 +1,1089 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konbattle.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 "konbattle.h"
+
+#include <math.h>
+#include <string.h>
+
+/*** random numbers ***/
+
+static uint32_t next(kon_battle_t *bt)
+{
+ uint32_t x = bt->seed;
+
+ x ^= x << 13;
+ x ^= x >> 17;
+ x ^= x << 5;
+ bt->seed = x;
+ return x;
+}
+
+/* 0 up to but not including n */
+static int below(kon_battle_t *bt, int n)
+{
+ return n > 1 ? (int)(next(bt) % (uint32_t)n) : 0;
+}
+
+static int percent(kon_battle_t *bt, int chance)
+{
+ return below(bt, 100) < chance;
+}
+
+/*** formulas ***/
+
+enum {
+ F_NUM, F_A, F_B, F_VAR, F_NEG, F_NOT,
+ F_ADD, F_SUB, F_MUL, F_DIV, F_MOD, F_POW, F_LT, F_GT, F_LE, F_GE, F_EQ, F_NE, F_AND, F_OR, F_SELECT,
+ F_FLOOR, F_CEIL, F_ROUND, F_ABS, F_SQRT, F_MAX, F_MIN
+};
+
+#define FORMULA_STACK 32
+#define FORMULA_DEPTH 24
+
+typedef struct parser {
+ kon_formula_t *f;
+ const char *s;
+ int at, depth, stack, ok;
+} parser_t;
+
+static void fail(parser_t *p)
+{
+ if (p->ok) p->f->errorAt = p->at;
+ p->ok = 0;
+}
+
+static void emit(parser_t *p, int op, double n, int pushes)
+{
+ if (!p->ok) return;
+ if (p->f->count >= (int)(sizeof p->f->ops / sizeof p->f->ops[0])) {
+ fail(p);
+ return;
+ }
+ p->f->ops[p->f->count].op = op;
+ p->f->ops[p->f->count].n = n;
+ p->f->count++;
+ p->stack += pushes;
+ if (p->stack > FORMULA_STACK || p->stack < 1) fail(p);
+}
+
+static void space(parser_t *p)
+{
+ while (p->s[p->at] == ' ' || p->s[p->at] == '\t' || p->s[p->at] == '\n' || p->s[p->at] == '\r') p->at++;
+}
+
+static int accept(parser_t *p, const char *token)
+{
+ size_t n = strlen(token);
+
+ space(p);
+ if (strncmp(p->s + p->at, token, n) != 0) return 0;
+ p->at += (int)n;
+ return 1;
+}
+
+static int isWord(int c)
+{
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+}
+
+static int word(parser_t *p, char *out, int size)
+{
+ int n = 0;
+
+ space(p);
+ while (isWord((unsigned char)p->s[p->at])) {
+ if (n + 1 < size) out[n++] = p->s[p->at];
+ p->at++;
+ }
+ out[n] = '\0';
+ return n;
+}
+
+static void expression(parser_t *p);
+
+static int statNumber(const char *name)
+{
+ static const char *names[KON_STATS] = {"mhp", "mmp", "atk", "def", "mat", "mdf", "agi", "luk", "hp", "mp", "tp", "level"};
+ int i;
+
+ for (i = 0; i < KON_STATS; i++) {
+ if (strcmp(name, names[i]) == 0) return i;
+ }
+ return -1;
+}
+
+static void primary(parser_t *p)
+{
+ char w[32];
+
+ space(p);
+ if (!p->ok) return;
+ if (p->depth++ > FORMULA_DEPTH) {
+ fail(p);
+ return;
+ }
+ if (accept(p, "(")) {
+ expression(p);
+ if (!accept(p, ")")) fail(p);
+ } else if ((p->s[p->at] >= '0' && p->s[p->at] <= '9') || p->s[p->at] == '.') {
+ double v = 0.0, scale = 1.0;
+ int digits = 0;
+
+ while (p->s[p->at] >= '0' && p->s[p->at] <= '9') {
+ v = v * 10.0 + (double)(p->s[p->at++] - '0');
+ digits++;
+ }
+ if (p->s[p->at] == '.') {
+ p->at++;
+ while (p->s[p->at] >= '0' && p->s[p->at] <= '9') {
+ scale *= 0.1;
+ v += (double)(p->s[p->at++] - '0') * scale;
+ digits++;
+ }
+ }
+ if (!digits) fail(p);
+ emit(p, F_NUM, v, 1);
+ } else if (word(p, w, sizeof w)) {
+ if ((strcmp(w, "a") == 0 || strcmp(w, "b") == 0) && accept(p, ".")) {
+ char stat[32];
+ int n;
+
+ if (!word(p, stat, sizeof stat) || (n = statNumber(stat)) < 0) fail(p);
+ else emit(p, w[0] == 'a' ? F_A : F_B, (double)n, 1);
+ } else if (strcmp(w, "v") == 0 && accept(p, "[")) {
+ expression(p);
+ if (!accept(p, "]")) fail(p);
+ emit(p, F_VAR, 0.0, 0);
+ } else if (strcmp(w, "Math") == 0 && accept(p, ".")) {
+ static const struct { const char *name; int op, args; } fns[] = {
+ {"floor", F_FLOOR, 1}, {"ceil", F_CEIL, 1}, {"round", F_ROUND, 1}, {"abs", F_ABS, 1}, {"sqrt", F_SQRT, 1},
+ {"max", F_MAX, 2}, {"min", F_MIN, 2}, {"pow", F_POW, 2}
+ };
+ size_t i;
+ int found = -1;
+
+ if (!word(p, w, sizeof w)) fail(p);
+ for (i = 0; i < sizeof fns / sizeof fns[0]; i++) {
+ if (strcmp(w, fns[i].name) == 0) found = (int)i;
+ }
+ if (found < 0 || !accept(p, "(")) {
+ fail(p);
+ } else {
+ expression(p);
+ if (fns[found].args == 2) {
+ if (!accept(p, ",")) fail(p);
+ expression(p);
+ }
+ if (!accept(p, ")")) fail(p);
+ emit(p, fns[found].op, 0.0, fns[found].args == 2 ? -1 : 0);
+ }
+ } else {
+ fail(p);
+ }
+ } else {
+ fail(p);
+ }
+ p->depth--;
+}
+
+static void unary(parser_t *p)
+{
+ space(p);
+ if (!p->ok) return;
+ if (p->depth++ > FORMULA_DEPTH) {
+ fail(p);
+ return;
+ }
+ if (accept(p, "-")) {
+ unary(p);
+ emit(p, F_NEG, 0.0, 0);
+ } else if (accept(p, "+")) {
+ unary(p);
+ } else if (p->s[p->at] == '!' && p->s[p->at + 1] != '=') {
+ p->at++;
+ unary(p);
+ emit(p, F_NOT, 0.0, 0);
+ } else {
+ primary(p);
+ }
+ p->depth--;
+}
+
+/* ** goes right to left and binds tighter than the minus in front of it */
+static void power(parser_t *p)
+{
+ unary(p);
+ space(p);
+ if (p->s[p->at] == '*' && p->s[p->at + 1] == '*') {
+ p->at += 2;
+ if (p->depth++ > FORMULA_DEPTH) {
+ fail(p);
+ return;
+ }
+ power(p);
+ p->depth--;
+ emit(p, F_POW, 0.0, -1);
+ }
+}
+
+static void product(parser_t *p)
+{
+ power(p);
+ for (;;) {
+ int op;
+
+ space(p);
+ if (p->s[p->at] == '*' && p->s[p->at + 1] != '*') op = F_MUL;
+ else if (p->s[p->at] == '/') op = F_DIV;
+ else if (p->s[p->at] == '%') op = F_MOD;
+ else return;
+ p->at++;
+ power(p);
+ emit(p, op, 0.0, -1);
+ }
+}
+
+static void sum(parser_t *p)
+{
+ product(p);
+ for (;;) {
+ int op;
+
+ space(p);
+ if (p->s[p->at] == '+') op = F_ADD;
+ else if (p->s[p->at] == '-') op = F_SUB;
+ else return;
+ p->at++;
+ product(p);
+ emit(p, op, 0.0, -1);
+ }
+}
+
+static void relation(parser_t *p)
+{
+ sum(p);
+ for (;;) {
+ int op;
+
+ if (accept(p, "<=")) op = F_LE;
+ else if (accept(p, ">=")) op = F_GE;
+ else if (accept(p, "<")) op = F_LT;
+ else if (accept(p, ">")) op = F_GT;
+ else return;
+ sum(p);
+ emit(p, op, 0.0, -1);
+ }
+}
+
+static void equality(parser_t *p)
+{
+ relation(p);
+ for (;;) {
+ int op;
+
+ if (accept(p, "===") || accept(p, "==")) op = F_EQ;
+ else if (accept(p, "!==") || accept(p, "!=")) op = F_NE;
+ else return;
+ relation(p);
+ emit(p, op, 0.0, -1);
+ }
+}
+
+static void conjunction(parser_t *p)
+{
+ equality(p);
+ while (accept(p, "&&")) {
+ equality(p);
+ emit(p, F_AND, 0.0, -1);
+ }
+}
+
+static void disjunction(parser_t *p)
+{
+ conjunction(p);
+ while (accept(p, "||")) {
+ conjunction(p);
+ emit(p, F_OR, 0.0, -1);
+ }
+}
+
+static void expression(parser_t *p)
+{
+ disjunction(p);
+ if (accept(p, "?")) {
+ if (p->depth++ > FORMULA_DEPTH) {
+ fail(p);
+ return;
+ }
+ expression(p);
+ if (!accept(p, ":")) fail(p);
+ expression(p);
+ p->depth--;
+ emit(p, F_SELECT, 0.0, -2);
+ }
+}
+
+int kon_formulaCompile(kon_formula_t *f, const char *text)
+{
+ parser_t p;
+
+ memset(f, 0, sizeof *f);
+ f->errorAt = -1;
+ if (!text) {
+ f->errorAt = 0;
+ return 0;
+ }
+ p.f = f;
+ p.s = text;
+ p.at = p.depth = p.stack = 0;
+ p.ok = 1;
+ expression(&p);
+ space(&p);
+ if (p.ok && text[p.at] != '\0') fail(&p);
+ if (p.ok && p.stack != 1) fail(&p);
+ if (!p.ok) {
+ f->count = 0;
+ return 0;
+ }
+ return 1;
+}
+
+static double sane(double v)
+{
+ if (!(v == v)) return 0.0;
+ if (v > 1e12) return 1e12;
+ if (v < -1e12) return -1e12;
+ return v;
+}
+
+double kon_formulaEval(const kon_formula_t *f, const kon_battle_t *battle, const kon_battler_t *a, const kon_battler_t *b)
+{
+ double st[FORMULA_STACK + 2];
+ int sp = 0, i;
+
+ if (f->count <= 0) return 0.0;
+ for (i = 0; i < f->count; i++) {
+ int op = f->ops[i].op;
+ double x, y;
+
+ switch (op) {
+ case F_NUM:
+ st[sp++] = f->ops[i].n;
+ break;
+ case F_A:
+ case F_B: {
+ const kon_battler_t *who = op == F_A ? a : b;
+
+ st[sp++] = who ? (double)kon_battlerStat(battle, who, (int)f->ops[i].n) : 0.0;
+ break;
+ }
+ case F_VAR: {
+ double n = st[sp - 1];
+ int id = n >= 0.0 && n < 1e9 ? (int)n : -1;
+
+ st[sp - 1] = battle->vars && id >= 0 && id < battle->varCount ? (double)battle->vars[id] : 0.0;
+ break;
+ }
+ case F_NEG: st[sp - 1] = -st[sp - 1]; break;
+ case F_NOT: st[sp - 1] = st[sp - 1] == 0.0 ? 1.0 : 0.0; break;
+ case F_FLOOR: st[sp - 1] = floor(st[sp - 1]); break;
+ case F_CEIL: st[sp - 1] = ceil(st[sp - 1]); break;
+ case F_ROUND: st[sp - 1] = floor(st[sp - 1] + 0.5); break;
+ case F_ABS: st[sp - 1] = fabs(st[sp - 1]); break;
+ case F_SQRT: st[sp - 1] = st[sp - 1] > 0.0 ? sqrt(st[sp - 1]) : 0.0; break;
+ case F_SELECT:
+ x = st[sp - 3] != 0.0 ? st[sp - 2] : st[sp - 1];
+ sp -= 2;
+ st[sp - 1] = x;
+ break;
+ default:
+ y = st[--sp];
+ x = st[sp - 1];
+ switch (op) {
+ case F_ADD: x = x + y; break;
+ case F_SUB: x = x - y; break;
+ case F_MUL: x = x * y; break;
+ case F_DIV: x = y != 0.0 ? x / y : 0.0; break;
+ case F_MOD: x = y != 0.0 ? fmod(x, y) : 0.0; break;
+ case F_POW: x = pow(x, y); break;
+ case F_LT: x = x < y; break;
+ case F_GT: x = x > y; break;
+ case F_LE: x = x <= y; break;
+ case F_GE: x = x >= y; break;
+ case F_EQ: x = x == y; break;
+ case F_NE: x = x != y; break;
+ case F_AND: x = x != 0.0 && y != 0.0; break;
+ case F_OR: x = x != 0.0 || y != 0.0; break;
+ case F_MAX: x = x > y ? x : y; break;
+ case F_MIN: x = x < y ? x : y; break;
+ }
+ st[sp - 1] = x;
+ break;
+ }
+ st[sp - 1] = sane(st[sp - 1]);
+ }
+ return sp == 1 ? st[0] : 0.0;
+}
+
+/*** battlers ***/
+
+void kon_battlerInit(kon_battler_t *b, const char *name, int team, int level)
+{
+ kon_battler_t zero = {0};
+ int i;
+
+ *b = zero;
+ b->name = name;
+ b->team = team == KON_ENEMIES ? KON_ENEMIES : KON_PARTY;
+ b->level = level;
+ b->hit = 95;
+ b->pdr = b->mdr = 100;
+ for (i = 0; i < KON_ELEMENTS; i++) b->elementRate[i] = 100;
+}
+
+int kon_battlerHasState(const kon_battler_t *b, int id)
+{
+ int i;
+
+ for (i = 0; i < b->stateCount; i++) {
+ if (b->states[i].id == id) return 1;
+ }
+ return 0;
+}
+
+static const kon_state_t *stateOf(const kon_battle_t *battle, int id)
+{
+ return id >= 0 && id < battle->stateCount && battle->states ? &battle->states[id] : NULL;
+}
+
+int kon_battlerStat(const kon_battle_t *battle, const kon_battler_t *b, int stat)
+{
+ long value, change = 0;
+ int i;
+
+ switch (stat) {
+ case KON_STAT_HP: return b->hp;
+ case KON_STAT_MP: return b->mp;
+ case KON_STAT_TP: return b->tp;
+ case KON_STAT_LEVEL: return b->level;
+ }
+ if (stat < 0 || stat >= KON_PARAMS) return 0;
+ for (i = 0; i < b->stateCount; i++) {
+ const kon_state_t *s = stateOf(battle, b->states[i].id);
+
+ if (s) change += s->percent[stat];
+ }
+ if (change < -100) change = -100;
+ if (change > 10000) change = 10000;
+ value = ((long)b->base[stat] + b->plus[stat]) * (100 + change) / 100;
+ if (value < (stat == KON_STAT_MHP ? 1 : 0)) value = stat == KON_STAT_MHP ? 1 : 0;
+ if (value > 99999999L) value = 99999999L;
+ return (int)value;
+}
+
+int kon_battlerAlive(const kon_battle_t *battle, const kon_battler_t *b)
+{
+ int i;
+
+ if (b->hp <= 0) return 0;
+ for (i = 0; i < b->stateCount; i++) {
+ const kon_state_t *s = stateOf(battle, b->states[i].id);
+
+ if (s && s->death) return 0;
+ }
+ return 1;
+}
+
+static void clampBattler(const kon_battle_t *battle, kon_battler_t *b)
+{
+ int mhp = kon_battlerStat(battle, b, KON_STAT_MHP), mmp = kon_battlerStat(battle, b, KON_STAT_MMP);
+
+ if (b->hp > mhp) b->hp = mhp;
+ if (b->hp < 0) b->hp = 0;
+ if (b->mp > mmp) b->mp = mmp;
+ if (b->mp < 0) b->mp = 0;
+ if (b->tp > 100) b->tp = 100;
+ if (b->tp < 0) b->tp = 0;
+}
+
+static int deathState(const kon_battle_t *battle)
+{
+ int i;
+
+ for (i = 0; i < battle->stateCount; i++) {
+ if (battle->states[i].death) return i;
+ }
+ return -1;
+}
+
+static int removeState(kon_battler_t *b, int id)
+{
+ int i;
+
+ for (i = 0; i < b->stateCount; i++) {
+ if (b->states[i].id == id) {
+ b->states[i] = b->states[--b->stateCount];
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int addState(kon_battle_t *bt, kon_battler_t *b, int id)
+{
+ const kon_state_t *s = stateOf(bt, id);
+ int i, turns = 0;
+
+ if (!s) return 0;
+ if (s->death) {
+ b->hp = 0;
+ b->stateCount = 0;
+ } else if (!kon_battlerAlive(bt, b)) {
+ return 0;
+ }
+ if (s->removeAt && s->turnsMax > 0) {
+ int lo = s->turnsMin > 0 ? s->turnsMin : 1;
+
+ turns = s->turnsMax > lo ? lo + below(bt, s->turnsMax - lo + 1) : lo;
+ }
+ for (i = 0; i < b->stateCount; i++) {
+ if (b->states[i].id == id) {
+ b->states[i].turns = turns;
+ return 1;
+ }
+ }
+ if (b->stateCount >= KON_BATTLER_STATES) return 0;
+ b->states[b->stateCount].id = id;
+ b->states[b->stateCount].turns = turns;
+ b->stateCount++;
+ return 1;
+}
+
+/*** skills ***/
+
+int kon_skillCompile(kon_skill_t *skill)
+{
+ if (!skill->formulaText || !skill->formulaText[0]) {
+ memset(&skill->formula, 0, sizeof skill->formula);
+ skill->formula.errorAt = -1;
+ return 1;
+ }
+ return kon_formulaCompile(&skill->formula, skill->formulaText);
+}
+
+/*** battle ***/
+
+void kon_battleInit(kon_battle_t *bt, kon_battler_t *battlers, int count, const kon_state_t *states, int stateCount, uint32_t seed)
+{
+ kon_battle_t zero = {0};
+
+ *bt = zero;
+ bt->battlers = battlers;
+ bt->count = battlers && count > 0 ? (count > KON_BATTLE_MAX ? KON_BATTLE_MAX : count) : 0;
+ bt->states = states;
+ bt->stateCount = states && stateCount > 0 ? stateCount : 0;
+ bt->seed = seed ? seed : 0x9E3779B9u;
+ bt->escapeRatio = -1.0f;
+}
+
+static int aliveNumber(const kon_battle_t *bt, int i)
+{
+ return i >= 0 && i < bt->count && kon_battlerAlive(bt, &bt->battlers[i]);
+}
+
+/* the battlers that can be hit by a scope for a user. returns how many, written to out */
+static int friends(const kon_battle_t *bt, int user, int wantAlive, int *out)
+{
+ int i, n = 0;
+
+ for (i = 0; i < bt->count; i++) {
+ if (bt->battlers[i].team == bt->battlers[user].team && aliveNumber(bt, i) == wantAlive) out[n++] = i;
+ }
+ return n;
+}
+
+static int foes(const kon_battle_t *bt, int user, int *out)
+{
+ int i, n = 0;
+
+ for (i = 0; i < bt->count; i++) {
+ if (bt->battlers[i].team != bt->battlers[user].team && aliveNumber(bt, i)) out[n++] = i;
+ }
+ return n;
+}
+
+static int targets(kon_battle_t *bt, int user, const kon_skill_t *skill, int target, int *out)
+{
+ int list[KON_BATTLE_MAX], n, i, count = 0;
+
+ if (bt->forced > 0 && (skill->scope == KON_SCOPE_ONE_ENEMY || skill->scope == KON_SCOPE_ONE_ALLY || skill->scope == KON_SCOPE_RANDOM_ENEMY)) {
+ out[0] = bt->forced - 1;
+ return 1;
+ }
+ switch (skill->scope) {
+ case KON_SCOPE_USER:
+ out[count++] = user;
+ break;
+ case KON_SCOPE_ALL_ENEMIES:
+ count = foes(bt, user, out);
+ break;
+ case KON_SCOPE_ALL_ALLIES:
+ count = friends(bt, user, 1, out);
+ break;
+ case KON_SCOPE_ALL_ALLIES_DEAD:
+ count = friends(bt, user, 0, out);
+ break;
+ case KON_SCOPE_ONE_ENEMY:
+ n = foes(bt, user, list);
+ if (n == 0) break;
+ if (target >= 0 && target < bt->count && bt->battlers[target].team != bt->battlers[user].team && aliveNumber(bt, target)) out[count++] = target;
+ else out[count++] = list[below(bt, n)];
+ break;
+ case KON_SCOPE_RANDOM_ENEMY:
+ n = foes(bt, user, list);
+ for (i = 0; n > 0 && i < (skill->randomCount > 0 ? skill->randomCount : 1) && i < KON_BATTLE_MAX; i++) out[count++] = list[below(bt, n)];
+ break;
+ case KON_SCOPE_ONE_ALLY:
+ n = friends(bt, user, 1, list);
+ if (n == 0) break;
+ if (target >= 0 && target < bt->count && bt->battlers[target].team == bt->battlers[user].team && aliveNumber(bt, target)) out[count++] = target;
+ else out[count++] = list[below(bt, n)];
+ break;
+ case KON_SCOPE_ONE_ALLY_DEAD:
+ n = friends(bt, user, 0, list);
+ if (n == 0) break;
+ if (target >= 0 && target < bt->count && bt->battlers[target].team == bt->battlers[user].team && !aliveNumber(bt, target)) out[count++] = target;
+ else out[count++] = list[below(bt, n)];
+ break;
+ default:
+ break;
+ }
+ return count;
+}
+
+static void resultInit(kon_result_t *r, int user, int target)
+{
+ kon_result_t zero = {0};
+
+ *r = zero;
+ r->user = user;
+ r->target = target;
+}
+
+static void noteAdded(kon_result_t *r, int id)
+{
+ if (r->addedCount < KON_SKILL_STATES) r->added[r->addedCount++] = id;
+}
+
+static void noteRemoved(kon_result_t *r, int id)
+{
+ if (r->removedCount < KON_SKILL_STATES) r->removed[r->removedCount++] = id;
+}
+
+static int stateNoted(kon_battle_t *bt, kon_battler_t *t, int id, kon_result_t *r)
+{
+ int had = kon_battlerHasState(t, id);
+
+ if (addState(bt, t, id)) {
+ if (!had) noteAdded(r, id);
+ return 1;
+ }
+ return 0;
+}
+
+/* death follows hp: dying clears the other states, coming back removes death */
+static void settle(kon_battle_t *bt, kon_battler_t *t, kon_result_t *r)
+{
+ int death = deathState(bt);
+
+ clampBattler(bt, t);
+ if (death < 0) return;
+ if (t->hp <= 0 && !kon_battlerHasState(t, death)) {
+ stateNoted(bt, t, death, r);
+ r->died = 1;
+ } else if (t->hp > 0 && kon_battlerHasState(t, death)) {
+ removeState(t, death);
+ noteRemoved(r, death);
+ r->revived = 1;
+ }
+}
+
+static void hit(kon_battle_t *bt, int user, const kon_skill_t *skill, int target, kon_result_t *r)
+{
+ kon_battler_t *u = &bt->battlers[user], *t = &bt->battlers[target];
+ int success = skill->successRate > 0 ? skill->successRate : 100;
+ double value;
+ int i, dmg;
+
+ resultInit(r, user, target);
+
+ if (skill->hitType == KON_HIT_PHYSICAL) {
+ success = success * u->hit / 100;
+ if (!percent(bt, success)) {
+ r->missed = 1;
+ return;
+ }
+ if (percent(bt, t->eva)) {
+ r->evaded = 1;
+ return;
+ }
+ } else if (skill->hitType == KON_HIT_MAGICAL) {
+ if (!percent(bt, success)) {
+ r->missed = 1;
+ return;
+ }
+ if (percent(bt, t->mev)) {
+ r->evaded = 1;
+ return;
+ }
+ } else if (!percent(bt, success)) {
+ r->missed = 1;
+ return;
+ }
+
+ if (skill->damageType != KON_DAMAGE_NONE) {
+ int recover = skill->damageType == KON_DAMAGE_HP_RECOVER || skill->damageType == KON_DAMAGE_MP_RECOVER;
+ int amp;
+
+ value = kon_formulaEval(&skill->formula, bt, u, t);
+ if (skill->element > 0 && skill->element < KON_ELEMENTS) value = value * (double)t->elementRate[skill->element] / 100.0;
+ if (value > 0.0) {
+ if (skill->hitType == KON_HIT_PHYSICAL) value = value * (double)t->pdr / 100.0;
+ else if (skill->hitType == KON_HIT_MAGICAL) value = value * (double)t->mdr / 100.0;
+ }
+ if (recover) value = -value;
+ if (skill->critical && !recover && percent(bt, u->cri * (100 - t->cev) / 100)) {
+ value *= 3.0;
+ r->critical = 1;
+ }
+ if (value > 0.0 && t->guarding) value /= 2.0;
+ amp = (int)floor(fabs(value) * (double)skill->variance / 100.0);
+ if (amp > 0) value += (double)(below(bt, amp + 1) + below(bt, amp + 1) - amp);
+ dmg = (int)floor(value + 0.5);
+ if (dmg > 99999999) dmg = 99999999;
+ if (dmg < -99999999) dmg = -99999999;
+
+ switch (skill->damageType) {
+ case KON_DAMAGE_HP:
+ case KON_DAMAGE_HP_RECOVER:
+ case KON_DAMAGE_HP_DRAIN: {
+ int before = t->hp;
+
+ t->hp -= dmg;
+ clampBattler(bt, t);
+ r->hp = t->hp - before;
+ if (skill->damageType == KON_DAMAGE_HP_DRAIN) {
+ int ub = u->hp;
+
+ u->hp += -r->hp;
+ clampBattler(bt, u);
+ r->userHp = u->hp - ub;
+ }
+ break;
+ }
+ default: {
+ int before = t->mp;
+
+ t->mp -= dmg;
+ clampBattler(bt, t);
+ r->mp = t->mp - before;
+ if (skill->damageType == KON_DAMAGE_MP_DRAIN) {
+ int ub = u->mp;
+
+ u->mp += -r->mp;
+ clampBattler(bt, u);
+ r->userMp = u->mp - ub;
+ }
+ break;
+ }
+ }
+ if (r->hp < 0 && t->hp > 0) {
+ for (i = t->stateCount - 1; i >= 0; i--) {
+ const kon_state_t *s = stateOf(bt, t->states[i].id);
+
+ if (s && s->removeByDamage > 0 && percent(bt, s->removeByDamage)) {
+ noteRemoved(r, t->states[i].id);
+ removeState(t, t->states[i].id);
+ }
+ }
+ }
+ }
+ settle(bt, t, r);
+
+ for (i = 0; i < skill->addStateCount && i < KON_SKILL_STATES; i++) {
+ if (percent(bt, skill->addState[i].chance)) {
+ if (stateNoted(bt, t, skill->addState[i].id, r) && t->hp <= 0) r->died = 1;
+ }
+ }
+ for (i = 0; i < skill->removeStateCount && i < KON_SKILL_STATES; i++) {
+ if (removeState(t, skill->removeState[i])) noteRemoved(r, skill->removeState[i]);
+ }
+ settle(bt, t, r);
+ u->tp += skill->tpGain;
+ clampBattler(bt, u);
+}
+
+int kon_battleUse(kon_battle_t *bt, int user, const kon_skill_t *skill, int target, kon_result_t *out, int cap)
+{
+ int list[KON_BATTLE_MAX], n, i, rep, made = 0, repeats;
+ kon_battler_t *u;
+
+ if (!bt || !skill || user < 0 || user >= bt->count || !aliveNumber(bt, user)) return -1;
+ u = &bt->battlers[user];
+ if (u->mp < skill->mpCost || u->tp < skill->tpCost) return -1;
+ u->mp -= skill->mpCost;
+ u->tp -= skill->tpCost;
+
+ n = targets(bt, user, skill, target, list);
+ repeats = skill->repeats > 0 ? skill->repeats : 1;
+ for (i = 0; i < n; i++) {
+ for (rep = 0; rep < repeats; rep++) {
+ kon_result_t scratch;
+
+ hit(bt, user, skill, list[i], made < cap && out ? &out[made] : &scratch);
+ made++;
+ }
+ }
+ return made < cap ? made : (cap > 0 ? cap : 0);
+}
+
+/*** turns ***/
+
+int kon_battleQueue(kon_battle_t *bt, int kind, int battler, const kon_skill_t *skill, int target)
+{
+ kon_action_t *a;
+
+ if (!bt || bt->queued >= KON_BATTLE_QUEUE || battler < 0 || battler >= bt->count) return 0;
+ if (kind != KON_ACT_SKILL && kind != KON_ACT_GUARD && kind != KON_ACT_ESCAPE) return 0;
+ if (kind == KON_ACT_SKILL && !skill) return 0;
+ a = &bt->queue[bt->queued++];
+ a->kind = kind;
+ a->battler = battler;
+ a->skill = skill;
+ a->target = target;
+ a->speed = 0;
+ bt->sorted = 0;
+ return 1;
+}
+
+static void sortQueue(kon_battle_t *bt)
+{
+ int i, j;
+
+ for (i = 0; i < bt->queued; i++) {
+ kon_action_t *a = &bt->queue[i];
+ int agi = kon_battlerStat(bt, &bt->battlers[a->battler], KON_STAT_AGI);
+
+ a->speed = agi + below(bt, 5 + agi / 4);
+ if (a->kind == KON_ACT_GUARD) a->speed += 2000;
+ else if (a->kind == KON_ACT_SKILL) a->speed += a->skill->speed;
+ }
+ for (i = 1; i < bt->queued; i++) {
+ kon_action_t held = bt->queue[i];
+
+ for (j = i - 1; j >= 0 && bt->queue[j].speed < held.speed; j--) bt->queue[j + 1] = bt->queue[j];
+ bt->queue[j + 1] = held;
+ }
+ bt->sorted = 1;
+ bt->next = 0;
+}
+
+static int restriction(const kon_battle_t *bt, const kon_battler_t *b)
+{
+ int i;
+
+ for (i = 0; i < b->stateCount; i++) {
+ const kon_state_t *s = stateOf(bt, b->states[i].id);
+
+ if (s && s->restriction) return s->restriction;
+ }
+ return 0;
+}
+
+static float agility(const kon_battle_t *bt, int team)
+{
+ long total = 0;
+ int i, n = 0;
+
+ for (i = 0; i < bt->count; i++) {
+ if (bt->battlers[i].team == team && kon_battlerAlive(bt, &bt->battlers[i])) {
+ total += kon_battlerStat(bt, &bt->battlers[i], KON_STAT_AGI);
+ n++;
+ }
+ }
+ return n ? (float)total / (float)n : 0.0f;
+}
+
+static int escape(kon_battle_t *bt, int user, kon_result_t *out, int cap)
+{
+ kon_result_t r;
+ float party = agility(bt, KON_PARTY), enemies = agility(bt, KON_ENEMIES);
+
+ if (bt->escapeRatio < 0.0f) bt->escapeRatio = enemies > 0.0f ? 0.5f * party / enemies : 1.0f;
+ resultInit(&r, user, -1);
+ if ((float)below(bt, 10000) < bt->escapeRatio * 10000.0f) {
+ r.escape = 2;
+ bt->escaped = 1;
+ } else {
+ r.escape = 1;
+ bt->escapeRatio += 0.1f;
+ }
+ if (cap > 0 && out) out[0] = r;
+ return cap > 0 ? 1 : 0;
+}
+
+static void afterAction(kon_battle_t *bt, int user)
+{
+ kon_battler_t *b = &bt->battlers[user];
+ int i;
+
+ for (i = b->stateCount - 1; i >= 0; i--) {
+ const kon_state_t *s = stateOf(bt, b->states[i].id);
+
+ if (s && s->removeAt == 1 && b->states[i].turns > 0 && --b->states[i].turns == 0) removeState(b, b->states[i].id);
+ }
+}
+
+int kon_battleNext(kon_battle_t *bt, kon_result_t *out, int cap)
+{
+ if (!bt) return -1;
+ if (!bt->sorted) sortQueue(bt);
+ while (bt->next < bt->queued) {
+ kon_action_t a = bt->queue[bt->next++];
+ kon_battler_t *u = &bt->battlers[a.battler];
+ int n = 0, r;
+
+ if (!kon_battlerAlive(bt, u)) continue;
+ r = restriction(bt, u);
+ if (r == 4) continue;
+ if (r >= 1 && r <= 3 && a.kind != KON_ACT_GUARD) {
+ int pool[KON_BATTLE_MAX], count = 0, i;
+
+ for (i = 0; i < bt->count; i++) {
+ int same = bt->battlers[i].team == u->team;
+
+ if (!aliveNumber(bt, i)) continue;
+ if (r == 2 || (r == 1 && !same) || (r == 3 && same)) pool[count++] = i;
+ }
+ if (!bt->attack || count == 0) continue;
+ a.kind = KON_ACT_SKILL;
+ a.skill = bt->attack;
+ a.target = pool[below(bt, count)];
+ }
+ bt->last = a;
+ if (a.kind == KON_ACT_GUARD) {
+ u->guarding = 1;
+ } else if (a.kind == KON_ACT_ESCAPE) {
+ n = escape(bt, a.battler, out, cap);
+ } else {
+ bt->forced = r >= 1 && r <= 3 ? a.target + 1 : 0;
+ n = kon_battleUse(bt, a.battler, a.skill, a.target, out, cap);
+ bt->forced = 0;
+ if (n < 0) n = 0;
+ }
+ afterAction(bt, a.battler);
+ return n;
+ }
+ return -1;
+}
+
+int kon_battleEndTurn(kon_battle_t *bt, kon_result_t *out, int cap)
+{
+ int i, made = 0;
+
+ if (!bt) return 0;
+ for (i = 0; i < bt->count; i++) {
+ kon_battler_t *b = &bt->battlers[i];
+ kon_result_t r;
+ int mhp, mmp, j;
+
+ b->guarding = 0;
+ if (!kon_battlerAlive(bt, b)) continue;
+ resultInit(&r, i, i);
+ mhp = kon_battlerStat(bt, b, KON_STAT_MHP);
+ mmp = kon_battlerStat(bt, b, KON_STAT_MMP);
+ {
+ int hp = b->hp, mp = b->mp;
+
+ b->hp += (int)((long)mhp * b->hrg / 100);
+ b->mp += (int)((long)mmp * b->mrg / 100);
+ b->tp += b->trg;
+ clampBattler(bt, b);
+ r.hp = b->hp - hp;
+ r.mp = b->mp - mp;
+ }
+ for (j = b->stateCount - 1; j >= 0; j--) {
+ const kon_state_t *s = stateOf(bt, b->states[j].id);
+
+ if (s && s->removeAt == 2 && b->states[j].turns > 0 && --b->states[j].turns == 0) {
+ noteRemoved(&r, b->states[j].id);
+ removeState(b, b->states[j].id);
+ }
+ }
+ settle(bt, b, &r);
+ if (r.hp || r.mp || r.removedCount || r.died) {
+ if (made < cap && out) out[made] = r;
+ made++;
+ }
+ }
+ bt->turn++;
+ bt->queued = bt->next = bt->sorted = 0;
+ return made < cap ? made : (cap > 0 ? cap : 0);
+}
+
+void kon_battleEnd(kon_battle_t *bt)
+{
+ int i, j;
+
+ if (!bt) return;
+ for (i = 0; i < bt->count; i++) {
+ kon_battler_t *b = &bt->battlers[i];
+
+ b->guarding = 0;
+ for (j = b->stateCount - 1; j >= 0; j--) {
+ const kon_state_t *s = stateOf(bt, b->states[j].id);
+
+ if (s && s->removeAtBattleEnd && !s->death) removeState(b, b->states[j].id);
+ }
+ clampBattler(bt, b);
+ }
+ bt->queued = bt->next = bt->sorted = 0;
+}
+
+kon_outcome_t kon_battleOutcome(const kon_battle_t *bt)
+{
+ int i, party = 0, enemies = 0;
+
+ if (bt->escaped) return KON_BATTLE_ESCAPED;
+ for (i = 0; i < bt->count; i++) {
+ if (!kon_battlerAlive(bt, &bt->battlers[i])) continue;
+ if (bt->battlers[i].team == KON_PARTY) party++;
+ else enemies++;
+ }
+ if (party == 0) return KON_BATTLE_DEFEAT;
+ if (enemies == 0) return KON_BATTLE_VICTORY;
+ return KON_BATTLE_ONGOING;
+}