/* 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 #include /*** 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; }