summaryrefslogtreecommitdiff
path: root/kon/konparty.c
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-13 05:02:17 +0800
committerKonAki <konaki@anokonaki.org>2026-10-13 05:02:17 +0800
commit3198ac86cab5de4ec07964eaff358c8673b50457 (patch)
tree1a0a9817f9d755ade0ebeac8981d3f60014df63a /kon/konparty.c
parent99e2edf274a14d2a614c3e8995fcf23e37e765bf (diff)
add party module: actors, classes, equipment, bag, shops
Diffstat (limited to 'kon/konparty.c')
-rw-r--r--kon/konparty.c375
1 files changed, 375 insertions, 0 deletions
diff --git a/kon/konparty.c b/kon/konparty.c
new file mode 100644
index 0000000..c827255
--- /dev/null
+++ b/kon/konparty.c
@@ -0,0 +1,375 @@
+/* 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 <math.h>
+#include <string.h>
+
+/*** 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;
+ }
+}
+
+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, name, KON_PARTY, level);
+ 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;
+}