/* SPDX-License-Identifier: MIT */ /* * konparty.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 "konparty.h" #include #include /*** actors ***/ void kon_partyInit(kon_party_t *p, const kon_database_t *db, kon_actor_t *actors, int actorCount, int *itemCounts) { kon_party_t zero = {0}; int i; *p = zero; p->db = db; p->actors = actors; p->actorCount = actors && actorCount > 0 ? actorCount : 0; p->itemCounts = itemCounts; p->battleMax = 4; if (itemCounts && db) { for (i = 0; i < db->itemCount; i++) itemCounts[i] = 0; } } static int validActor(const kon_party_t *p, int actor) { return actor >= 0 && actor < p->actorCount; } static const kon_class_t *classOf(const kon_party_t *p, const kon_actor_t *a) { return p->db && a->classId >= 0 && a->classId < p->db->classCount ? &p->db->classes[a->classId] : NULL; } static const kon_item_t *itemOf(const kon_party_t *p, int item) { return p->db && item >= 0 && item < p->db->itemCount ? &p->db->items[item] : NULL; } int kon_classExp(const kon_class_t *c, int level) { double acc, v; if (!c || level <= 1) return 0; if (level > c->maxLevel) level = c->maxLevel; if (level <= 1) return 0; acc = c->expAccB > 0 ? (double)c->expAccB : 1.0; v = (double)c->expBasis * pow((double)(level - 1), 0.9 + (double)c->expAccA / 250.0) * (double)level * (double)(level + 1) / (6.0 + (double)level * (double)level / 49.0 / acc) + (double)(level - 1) * (double)c->expExtra; if (!(v > 0.0)) return 0; if (v > 2000000000.0) return 2000000000; return (int)floor(v + 0.5); } void kon_actorRefresh(const kon_party_t *p, int actor) { kon_actor_t *a; const kon_class_t *c; int i, s; if (!validActor(p, actor)) return; a = &p->actors[actor]; c = classOf(p, a); for (i = 0; i < KON_PARAMS; i++) { int level = a->b.level; a->b.base[i] = 0; if (c && c->params[i]) { if (level > c->maxLevel) level = c->maxLevel; if (level < 1) level = 1; a->b.base[i] = c->params[i][level]; } a->b.plus[i] = 0; for (s = 0; s < KON_EQUIP_SLOTS; s++) { const kon_item_t *it = itemOf(p, a->equip[s]); if (it) a->b.plus[i] += it->plus[i]; } } { kon_battle_t scratch; int mhp, mmp; kon_battleInit(&scratch, NULL, 0, p->db ? p->db->states : NULL, p->db ? p->db->stateCount : 0, 1); mhp = kon_battlerStat(&scratch, &a->b, KON_STAT_MHP); mmp = kon_battlerStat(&scratch, &a->b, KON_STAT_MMP); if (a->b.hp > mhp) a->b.hp = mhp; if (a->b.mp > mmp) a->b.mp = mmp; } } void kon_actorRename(kon_party_t *p, int actor, const char *name) { kon_actor_t *a; size_t n; if (!validActor(p, actor)) return; a = &p->actors[actor]; n = name ? strlen(name) : 0; if (n > KON_ACTOR_NAME - 1) n = KON_ACTOR_NAME - 1; if (n) memmove(a->name, name, n); a->name[n] = '\0'; a->b.name = a->name; } int kon_actorLearn(kon_party_t *p, int actor, int skill) { kon_actor_t *a; int i; if (!validActor(p, actor) || !p->db || skill < 0 || skill >= p->db->skillCount) return 0; a = &p->actors[actor]; for (i = 0; i < a->skillCount; i++) { if (a->skills[i] == skill) return 0; } if (a->skillCount >= KON_ACTOR_SKILLS) return 0; a->skills[a->skillCount++] = skill; return 1; } int kon_actorInit(kon_party_t *p, int actor, const char *name, int classId, int level) { kon_actor_t *a; const kon_class_t *c; kon_battle_t scratch; int i; if (!validActor(p, actor) || !p->db || classId < 0 || classId >= p->db->classCount) return 0; c = &p->db->classes[classId]; if (level < 1) level = 1; if (level > c->maxLevel) level = c->maxLevel; a = &p->actors[actor]; memset(a, 0, sizeof *a); kon_battlerInit(&a->b, a->name, KON_PARTY, level); kon_actorRename(p, actor, name); a->classId = classId; for (i = 0; i < KON_EQUIP_SLOTS; i++) a->equip[i] = -1; a->exp = kon_classExp(c, level); for (i = 0; i < c->learnCount && i < KON_CLASS_LEARNINGS; i++) { if (c->learn[i].level <= level) kon_actorLearn(p, actor, c->learn[i].skill); } kon_actorRefresh(p, actor); kon_battleInit(&scratch, NULL, 0, p->db->states, p->db->stateCount, 1); a->b.hp = kon_battlerStat(&scratch, &a->b, KON_STAT_MHP); a->b.mp = kon_battlerStat(&scratch, &a->b, KON_STAT_MMP); return 1; } int kon_actorGainExp(kon_party_t *p, int actor, int exp, int *learned, int cap) { kon_actor_t *a; const kon_class_t *c; int gained = 0, top; if (!validActor(p, actor) || exp <= 0) return 0; a = &p->actors[actor]; c = classOf(p, a); if (!c) return 0; top = kon_classExp(c, c->maxLevel); if (a->exp > top - exp) a->exp = top; else a->exp += exp; while (a->b.level < c->maxLevel && a->exp >= kon_classExp(c, a->b.level + 1)) { int i; a->b.level++; gained++; for (i = 0; i < c->learnCount && i < KON_CLASS_LEARNINGS; i++) { if (c->learn[i].level == a->b.level && kon_actorLearn(p, actor, c->learn[i].skill) && learned && cap > 0) { *learned++ = c->learn[i].skill; cap--; } } } if (gained) kon_actorRefresh(p, actor); return gained; } int kon_actorEquip(kon_party_t *p, int actor, int slot, int item) { kon_actor_t *a; const kon_item_t *it = NULL; if (!validActor(p, actor) || slot < 0 || slot >= KON_EQUIP_SLOTS) return 0; a = &p->actors[actor]; if (item >= 0) { it = itemOf(p, item); if (!it || (it->kind != KON_ITEM_WEAPON && it->kind != KON_ITEM_ARMOR) || it->slot != slot) return 0; if (a->equip[slot] != item && kon_bagCount(p, item) < 1) return 0; } if (a->equip[slot] == item) return 1; if (a->equip[slot] >= 0) kon_bagAdd(p, a->equip[slot], 1); if (item >= 0) kon_bagRemove(p, item, 1); a->equip[slot] = item >= 0 ? item : -1; kon_actorRefresh(p, actor); return 1; } /*** party ***/ int kon_partyAdd(kon_party_t *p, int actor) { int i; if (!validActor(p, actor) || p->memberCount >= KON_PARTY_MAX) return 0; for (i = 0; i < p->memberCount; i++) { if (p->members[i] == actor) return 0; } p->members[p->memberCount++] = actor; return 1; } int kon_partyRemove(kon_party_t *p, int actor) { int i; for (i = 0; i < p->memberCount; i++) { if (p->members[i] == actor) { memmove(&p->members[i], &p->members[i + 1], (size_t)(p->memberCount - i - 1) * sizeof p->members[0]); p->memberCount--; return 1; } } return 0; } int kon_partySwap(kon_party_t *p, int a, int b) { int t; if (a < 0 || b < 0 || a >= p->memberCount || b >= p->memberCount) return 0; t = p->members[a]; p->members[a] = p->members[b]; p->members[b] = t; return 1; } /*** the bag ***/ int kon_bagCount(const kon_party_t *p, int item) { return itemOf(p, item) && p->itemCounts ? p->itemCounts[item] : 0; } int kon_bagAdd(kon_party_t *p, int item, int count) { int room; if (!itemOf(p, item) || !p->itemCounts || count <= 0) return 0; room = KON_ITEM_MAX - p->itemCounts[item]; if (count > room) count = room; p->itemCounts[item] += count; return count; } int kon_bagRemove(kon_party_t *p, int item, int count) { if (!itemOf(p, item) || !p->itemCounts || count <= 0) return 0; if (count > p->itemCounts[item]) count = p->itemCounts[item]; p->itemCounts[item] -= count; return count; } void kon_partyGold(kon_party_t *p, int amount) { long long g = (long long)p->gold + amount; if (g < 0) g = 0; if (g > KON_GOLD_MAX) g = KON_GOLD_MAX; p->gold = (int)g; } int kon_partyBuy(kon_party_t *p, int item, int count) { const kon_item_t *it = itemOf(p, item); int affordable, room; if (!it || it->price <= 0 || count <= 0) return 0; affordable = p->gold / it->price; room = KON_ITEM_MAX - kon_bagCount(p, item); if (count > affordable) count = affordable; if (count > room) count = room; if (count <= 0) return 0; kon_bagAdd(p, item, count); p->gold -= count * it->price; return count; } int kon_partySell(kon_party_t *p, int item, int count) { const kon_item_t *it = itemOf(p, item); int sold; if (!it || it->price <= 0 || it->kind == KON_ITEM_KEY) return 0; sold = kon_bagRemove(p, item, count); kon_partyGold(p, sold * (it->price / 2)); return sold; } /*** fights ***/ int kon_partyBattlers(const kon_party_t *p, kon_battler_t *out, int cap) { int i, n = 0; for (i = 0; i < p->memberCount && n < p->battleMax && n < cap; i++) { if (!validActor(p, p->members[i])) continue; out[n] = p->actors[p->members[i]].b; out[n].team = KON_PARTY; n++; } return n; } void kon_partyApply(kon_party_t *p, const kon_battler_t *in, int count) { int i, n = 0; for (i = 0; i < p->memberCount && n < p->battleMax && n < count; i++) { kon_actor_t *a; if (!validActor(p, p->members[i])) continue; a = &p->actors[p->members[i]]; a->b.hp = in[n].hp; a->b.mp = in[n].mp; a->b.tp = in[n].tp; memcpy(a->b.states, in[n].states, sizeof a->b.states); a->b.stateCount = in[n].stateCount; a->b.guarding = 0; kon_actorRefresh(p, p->members[i]); n++; } } int kon_partyUseItem(kon_party_t *p, int item, int user, int target, kon_result_t *out, int cap) { const kon_item_t *it = itemOf(p, item); kon_battler_t group[KON_PARTY_MAX]; kon_battle_t bt; int saved, n, i, u = -1, t = -1; if (!it || !p->db || it->kind != KON_ITEM_ITEM || it->skill < 0 || it->skill >= p->db->skillCount) return -1; if (it->occasion == 1 || it->occasion == 3 || kon_bagCount(p, item) < 1) return -1; saved = p->battleMax; p->battleMax = KON_PARTY_MAX; n = kon_partyBattlers(p, group, KON_PARTY_MAX); p->battleMax = saved; for (i = 0; i < p->memberCount && i < n; i++) { if (p->members[i] == user) u = i; if (p->members[i] == target) t = i; } if (u < 0 || t < 0) return -1; kon_battleInit(&bt, group, n, p->db->states, p->db->stateCount, (uint32_t)(p->steps * 2654435761u + (unsigned long)p->gold + 1u)); bt.vars = p->vars; bt.varCount = p->varCount; n = kon_battleUse(&bt, u, &p->db->skills[it->skill], t, out, cap); if (n < 0) return -1; saved = p->battleMax; p->battleMax = KON_PARTY_MAX; kon_partyApply(p, group, bt.count); p->battleMax = saved; if (it->consumable) kon_bagRemove(p, item, 1); return n; } /*** saving ***/ void kon_partySave(const kon_party_t *p, kon_saveWriter_t *w) { int i, j; kon_saveChunk(w, "PRTY"); kon_savePutI32(w, p->gold); kon_savePutU32(w, (uint32_t)(p->steps < 0 ? 0 : p->steps)); kon_savePutU32(w, p->playTime > 0.0 ? (p->playTime < 4294967295.0 ? (uint32_t)p->playTime : 4294967295u) : 0u); kon_savePutU8(w, (unsigned)p->battleMax); kon_savePutU8(w, (unsigned)p->memberCount); for (i = 0; i < p->memberCount; i++) kon_savePutU16(w, (unsigned)p->members[i]); kon_savePutU16(w, (unsigned)p->actorCount); for (i = 0; i < p->actorCount; i++) { const kon_actor_t *a = &p->actors[i]; kon_savePutString(w, a->name); kon_savePutI32(w, a->classId); kon_savePutI32(w, a->b.level); kon_savePutI32(w, a->exp); kon_savePutI32(w, a->b.hp); kon_savePutI32(w, a->b.mp); kon_savePutI32(w, a->b.tp); for (j = 0; j < KON_EQUIP_SLOTS; j++) kon_savePutI32(w, a->equip[j]); kon_savePutU8(w, (unsigned)a->skillCount); for (j = 0; j < a->skillCount; j++) kon_savePutU16(w, (unsigned)a->skills[j]); kon_savePutU8(w, (unsigned)a->b.stateCount); for (j = 0; j < a->b.stateCount; j++) { kon_savePutU16(w, (unsigned)a->b.states[j].id); kon_savePutI32(w, a->b.states[j].turns); } } kon_saveChunk(w, "BAGS"); kon_savePutU32(w, (uint32_t)(p->db ? p->db->itemCount : 0)); for (i = 0; p->db && i < p->db->itemCount; i++) kon_savePutI32(w, p->itemCounts ? p->itemCounts[i] : 0); } int kon_partyLoad(kon_party_t *p, const kon_saveReader_t *r) { kon_saveCursor_t c; unsigned members, battleMax, actors; uint32_t steps, seconds; int i, j; if (!p->db || !kon_saveFind(r, "PRTY", &c)) return 0; p->gold = kon_saveGetI32(&c); steps = kon_saveGetU32(&c); seconds = kon_saveGetU32(&c); battleMax = kon_saveGetU8(&c); members = kon_saveGetU8(&c); if (c.error || p->gold < 0 || p->gold > KON_GOLD_MAX || members > KON_PARTY_MAX || battleMax < 1 || battleMax > KON_BATTLE_MAX) return 0; p->steps = (long)(steps > 0x7FFFFFFFu ? 0x7FFFFFFFu : steps); p->playTime = (double)seconds; p->battleMax = (int)battleMax; p->memberCount = 0; for (i = 0; i < (int)members; i++) { unsigned m = kon_saveGetU16(&c); if (c.error || (int)m >= p->actorCount) return 0; p->members[p->memberCount++] = (int)m; } actors = kon_saveGetU16(&c); if (c.error || (int)actors > p->actorCount) return 0; for (i = 0; i < (int)actors; i++) { kon_actor_t *a = &p->actors[i]; char name[KON_ACTOR_NAME]; unsigned count; int classId, level; kon_saveGetString(&c, name, sizeof name); classId = kon_saveGetI32(&c); level = kon_saveGetI32(&c); if (c.error || classId < 0 || classId >= p->db->classCount || level < 1 || level > p->db->classes[classId].maxLevel) return 0; memset(a, 0, sizeof *a); kon_battlerInit(&a->b, a->name, KON_PARTY, level); kon_actorRename(p, i, name); a->classId = classId; a->exp = kon_saveGetI32(&c); a->b.hp = kon_saveGetI32(&c); a->b.mp = kon_saveGetI32(&c); a->b.tp = kon_saveGetI32(&c); for (j = 0; j < KON_EQUIP_SLOTS; j++) { a->equip[j] = kon_saveGetI32(&c); if (a->equip[j] < -1 || a->equip[j] >= p->db->itemCount) a->equip[j] = -1; } count = kon_saveGetU8(&c); if (c.error || count > KON_ACTOR_SKILLS) return 0; for (j = 0; j < (int)count; j++) kon_actorLearn(p, i, (int)kon_saveGetU16(&c)); count = kon_saveGetU8(&c); if (c.error || count > KON_BATTLER_STATES) return 0; for (j = 0; j < (int)count; j++) { unsigned id = kon_saveGetU16(&c); int turns = kon_saveGetI32(&c); if ((int)id < p->db->stateCount) { a->b.states[a->b.stateCount].id = (int)id; a->b.states[a->b.stateCount].turns = turns < 0 ? 0 : turns; a->b.stateCount++; } } if (c.error) return 0; kon_actorRefresh(p, i); if (a->b.tp < 0) a->b.tp = 0; if (a->b.tp > 100) a->b.tp = 100; if (a->b.hp < 0) a->b.hp = 0; if (a->b.mp < 0) a->b.mp = 0; } if (!kon_saveFind(r, "BAGS", &c)) return 0; { uint32_t saved = kon_saveGetU32(&c), k; for (k = 0; k < saved; k++) { int32_t n = kon_saveGetI32(&c); if (c.error) return 0; if (k < (uint32_t)p->db->itemCount && p->itemCounts) p->itemCounts[k] = n < 0 ? 0 : (n > KON_ITEM_MAX ? KON_ITEM_MAX : n); } for (k = saved; p->itemCounts && k < (uint32_t)p->db->itemCount; k++) p->itemCounts[k] = 0; } return 1; }