From 3198ac86cab5de4ec07964eaff358c8673b50457 Mon Sep 17 00:00:00 2001 From: KonAki Date: Tue, 13 Oct 2026 05:02:17 +0800 Subject: add party module: actors, classes, equipment, bag, shops --- kon/konparty.c | 375 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ kon/konparty.h | 141 ++++++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 kon/konparty.c create mode 100644 kon/konparty.h 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 +#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; + } +} + +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; +} diff --git a/kon/konparty.h b/kon/konparty.h new file mode 100644 index 0000000..99b6829 --- /dev/null +++ b/kon/konparty.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konparty.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 KONPARTY_H +#define KONPARTY_H + +#include "konbattle.h" + +/*** definition ***/ + +/* the people in a role playing game and what they carry: classes with stats by level, levelling up, equipment, the +party, gold and the bag. fights are konbattle, this builds the battlers for it and takes the results back. +all the tables are yours and are only read, the changing parts (actors, how many of each item) are yours too */ + +#define KON_EQUIP_SLOTS 5 /* weapon, shield, head, body, accessory by default, the names are yours */ +#define KON_ACTOR_SKILLS 64 +#define KON_CLASS_LEARNINGS 32 +#define KON_PARTY_MAX 8 +#define KON_ITEM_MAX 99 +#define KON_GOLD_MAX 99999999 + +typedef enum { KON_ITEM_ITEM, KON_ITEM_KEY, KON_ITEM_WEAPON, KON_ITEM_ARMOR } kon_itemKind_t; + +/* where an item can be used: 0 anywhere, 1 in a fight only, 2 in the menu only, 3 never */ +typedef struct kon_item { + const char *name; + kon_itemKind_t kind; + int price; /* 0 is not for sale */ + int consumable; + int occasion; + int slot; /* for weapons and armour, which of the equipment slots */ + int plus[KON_PARAMS]; + int skill; /* what using it does, a number in the skills table, -1 for nothing */ +} kon_item_t; + +typedef struct kon_class { + const char *name; + int maxLevel; + const short *params[KON_PARAMS]; /* each as long as maxLevel + 1, the number for level 5 is params[i][5] */ + int expBasis, expExtra, expAccA, expAccB; /* the usual curve numbers, 30 20 30 30 for the common one */ + struct { int level; int skill; } learn[KON_CLASS_LEARNINGS]; + int learnCount; +} kon_class_t; + +typedef struct kon_database { + const kon_item_t *items; + int itemCount; + const kon_class_t *classes; + int classCount; + const kon_skill_t *skills; + int skillCount; + const kon_state_t *states; + int stateCount; +} kon_database_t; + +typedef struct kon_actor { + kon_battler_t b; /* the numbers as the fight uses them. hp, mp, tp and states stay here between fights */ + int classId; + int exp; + int equip[KON_EQUIP_SLOTS]; /* an item number or -1 */ + int skills[KON_ACTOR_SKILLS]; + int skillCount; +} kon_actor_t; + +typedef struct kon_party { + const kon_database_t *db; + kon_actor_t *actors; + int actorCount; + int *itemCounts; /* one for each item in the database */ + int32_t *vars; /* for formulas, can be the script's */ + int varCount; + int members[KON_PARTY_MAX]; /* actor numbers, the first is the leader */ + int memberCount; + int battleMax; /* how many of them fight at once */ + int gold; + long steps; + double playTime; +} kon_party_t; + +/*** actors ***/ + +void kon_partyInit(kon_party_t *p, const kon_database_t *db, kon_actor_t *actors, int actorCount, int *itemCounts); + +/* a new person at a level, with full hp and mp and what their class knows by then */ +int kon_actorInit(kon_party_t *p, int actor, const char *name, int classId, int level); + +/* stats follow class, level and equipment. call after changing any of them (the functions below do) */ +void kon_actorRefresh(const kon_party_t *p, int actor); +int kon_classExp(const kon_class_t *c, int level); +int kon_actorLearn(kon_party_t *p, int actor, int skill); + +/* returns levels gained, skills learned from them are written to learned */ +int kon_actorGainExp(kon_party_t *p, int actor, int exp, int *learned, int cap); + +/* an item number to wear, -1 to take it off. the bag gives and takes the items. 0 if it cannot be done */ +int kon_actorEquip(kon_party_t *p, int actor, int slot, int item); + +/*** party ***/ + +int kon_partyAdd(kon_party_t *p, int actor); +int kon_partyRemove(kon_party_t *p, int actor); +int kon_partySwap(kon_party_t *p, int a, int b); + +/* the bag */ +int kon_bagCount(const kon_party_t *p, int item); +int kon_bagAdd(kon_party_t *p, int item, int count); /* returns how many fitted */ +int kon_bagRemove(kon_party_t *p, int item, int count); /* returns how many were there to take */ +int kon_partyBuy(kon_party_t *p, int item, int count); +int kon_partySell(kon_party_t *p, int item, int count); +void kon_partyGold(kon_party_t *p, int amount); + +/* for a fight: the members that fight, copied into battlers, and the same back afterwards. both return how many */ +int kon_partyBattlers(const kon_party_t *p, kon_battler_t *out, int cap); +void kon_partyApply(kon_party_t *p, const kon_battler_t *in, int count); + +/* use an item from the menu on a member, like a potion. -1 if it cannot be used right now, otherwise what kon_battleUse says */ +int kon_partyUseItem(kon_party_t *p, int item, int user, int target, kon_result_t *out, int cap); + +#endif /* KONPARTY_H */ -- cgit