Files
rrttm/doc/CsataFormatum.md
markojozsef 54bc6a733a
Some checks failed
Build Windows EXE / build (push) Has been cancelled
Normális csata formátum dokumentáció
2026-10-20 17:28:58 +02:00

20 KiB
Raw Blame History

Csataadatok és csatafüggvények technikai leírása

Ez a dokumentum a csatamód (battle állapot) forráskódja alapján írja le, hogyan kell felépíteni a battle_data.battles bejegyzéseit, és milyen argumentumokkal hívódnak az MI-függvények, a képességek/tárgyak függvényei és az akcióanimációk.

Jelölések: Ami a csatamód forrásából közvetlenül kiolvasható, azt tényként írom le. Ami következtetés, vagy a nem látott fájlokban (battle.py, battle_data.py, menu.py, text.py) dől el, azt [Feltételezés] vagy TODO jelzi.


1. Terminológia

A kód belső nevei eltérnek a játékbeli fogalmaktól, ezért érdemes tisztán tartani:

Kód Játékbeli jelentés Képernyőpozíció
party_members A játékos által irányított karakter(ek), vagyis a megszállt főellenfél (és kísérői) Bal oldal
enemies A hősök, akik ellen a játékos harcol Jobb oldal
enemy_turn_func A hősök MI-függvénye
battle_id A csata azonosítója (a négy csataszoborhoz tartozik)

TODO: Melyik battle_id melyik szoborhoz tartozik, és ki állítja be az értékét a csata előtt (feltehetően az overworld).


2. A csata betöltése

A switch(game) a battle_data.battles[battle_id] bejegyzésből tölti be a csatát. A következő mezők mély másolatot (copy.deepcopy) kapnak, tehát a csata közbeni módosításaik nem maradnak meg a következő csatára, és az újraindítás mindig tiszta állapotot ad:

enemies, party_members, enemy_turn_func, init_ai_data, menus, spells, inventory.

A sprite-ok, a háttér és a zene nem másolódnak, csak hivatkozás történik rájuk.

Fontos: Mivel a másolat készül, az MI-függvények és képességfüggvények mindig a nekik átadott objektumokkal dolgozzanak, ne a battle_data modulban lévő eredetikre hivatkozzanak (pl. egy zárványon (closure) keresztül). Különben az eredeti, nem a csatában résztvevő példány módosulna. A damage_callback is csak olyan célponttal ad helyes pozíciót, amely a jelenlegi enemies vagy party_members listában van, különben a szám a (0, 0) pozícióban jelenik meg.


3. A battle_data.battles[battle_id] szerkezete

Egy bejegyzés egy szótár (dict) a következő kulcsokkal:

Kulcs Típus Leírás
"enemies" list[Combatant] A hősök listája
"party_members" list[Combatant] A játékos által irányított karakterek listája
"enemy_turn_func" függvény A hősök MI-függvénye (lásd 8. fejezet)
"init_ai_data" másolható objektum (általában dict) Az MI kezdeti állapota, a csata elején másolódik
"menus" list[list[tuple[str, str]]] Karakterenkénti parancsmenük (lásd 5. fejezet)
"spells" list[list[tuple]] Karakterenkénti képességlisták (lásd 6. fejezet)
"inventory" list[list] A csatában használható tárgyak, közösek (lásd 7. fejezet)
"enemy_sprites" list[list[azonosító]] Hősönként 3 sprite-azonosító (lásd 11. fejezet)
"party_sprites" list[list[azonosító]] Karakterenként 3 sprite-azonosító
"background" azonosító Kulcs az assets.sprites szótárba
"mus_id" azonosító Kulcs az assets.sounds szótárba, a zene ismétlődve (loops=True) szól

A menus, spells, party_sprites listák indexe a party_members indexével egyezik. Az enemy_sprites indexe az enemies indexével egyezik.

A csatát a battle_data.battles[battle_id] alapján választja ki a rendszer. A battles tehát olyan tároló, amely a battle_id értékkel indexelhető.

Váz példa

battles = [
    {
        "enemies": [Combatant(...), Combatant(...)],
        "party_members": [Combatant(...)],
        "enemy_turn_func": hero_ai_1,
        "init_ai_data": {"turn": 0},
        "menus": [
            [("Támadás", "phys_att"), ("Varázslat", "magic"), ("Tárgyak", "items")],
        ],
        "spells": [
            [("Tűzgolyó", 5, ("Tűzgolyó", fireball, "ene"))],
        ],
        "inventory": [
            ["Gyógyital", 3, ("Gyógyital", use_potion, "ally")],
        ],
        "enemy_sprites": [
            ["hero1_normal", "hero1_hurt", "hero1_ko"],
            ["hero2_normal", "hero2_hurt", "hero2_ko"],
        ],
        "party_sprites": [
            ["boss1_normal", "boss1_hurt", "boss1_ko"],
        ],
        "background": "bg_arena_1",
        "mus_id": "mus_battle_1",
    },
    # ... további 3 csata
]

4. Combatant

A Combatant osztály (battle.py) konstruktora nem látható a csatamód forrásában, ezért itt csak az onnan elérhető attribútumokat sorolom fel:

Attribútum Használat a csatamódban
name Megjelenített név (menü, célpontválasztó)
hp Aktuális életerő (célpontválasztóban kijelzett; sprite-váltás)
maxhp Maximális életerő. A 10%-os sprite-váltás küszöbe
ko Legyőzött-e (a szereplő nem cselekszik, a célpontválasztóból kimarad)
stone Kővé változott-e (nem cselekszik, szürke sprite)
def_up Védekezik-e (a célpontválasztóban „Védés” felirat)
atb Az akcióig hátralévő időzítő (lásd 9. és 12. fejezet)
attack Támadóerő, a „Támadás” parancs ezt adja a Damage-nek

TODO: A Combatant konstruktorának paraméterei és sorrendje, a kezdő atb értékek, további mezők (pl. MP, sebesség).


5. Menük (menus)

Karakterenként egy lista, amelynek elemei (felirat, művelet) párok:

("Támadás", "phys_att")

A művelet értéke a menu_action függvényben kerül feldolgozásra:

Művelet Hatás
"phys_att" Célpontválasztó ("ene"), majd egy Action("Támadás", user, target, Damage(user.attack, 0, 0, 0), StatusChange(0, 0, 0), battle_data.anim_default) kerül a sorba
"magic" Megnyitja a képességmenüt (spells[karakter_index])
"items" Megnyitja a tárgymenüt (inventory)
"unselectable" Nem csinál semmit, a kijelölés a menüben marad
bármi más Kiírja az Unknown action hibát a konzolra, és bezárja a menüt (lásd 12. fejezet)

A menüpontok x = 19, y = 64 - i * 13 pozícióban jelennek meg, ez a menüpanelen belül legfeljebb 5 sort jelent (lásd 12. fejezet).


6. Képességek (spells)

Karakterenként egy lista, amelynek elemei:

(név, mp_költség, (akció_név, függvény, célpont_típus))
Elem Leírás
név A képességmenüben megjelenő felirat
mp_költség A kódban kicsomagolódik, de a csatamód nem használja (nem ellenőrzi, nem vonja le)
akció_név A kódban kicsomagolódik, de a csatamód nem használja
függvény függvény(user, target) -> Action. Lásd lent
célpont_típus Lásd 8. fejezet

A kiválasztott célpont után a csatamód ezt hívja:

action_queue.append(függvény(user, targets[0]))

Vagyis a függvény két argumentumot kap: a cselekvő karaktert és a kiválasztott egyetlen célpontot, és egy Action objektumot kell visszaadnia.

TODO: Az MP-költség levonása és ellenőrzése. A csatamódban nincs ilyen, tehát vagy az Action/animáció végzi, vagy még nincs megvalósítva.


7. Tárgyak (inventory)

Egyetlen, a csata összes karaktere által közösen használt lista. Elemei módosítható listák (nem tuple-ök, mert a mennyiség csökken):

[név, mennyiség, (akció_név, függvény, célpont_típus)]
Elem Leírás
név A menüben név xMENNYISÉG formában jelenik meg (pl. Gyógyital x3)
mennyiség int. A 0 vagy kisebb értékű tárgyak nem jelennek meg
függvény Ugyanaz, mint a képességeknél: függvény(user, target) -> Action
célpont_típus Lásd 8. fejezet

A mennyiség a kiválasztáskor (az akció sorba állításakor) csökken eggyel, nem a végrehajtáskor.


8. Célpont-típusok

Az open_target_menu(user, aparty, eparty, target_type, on_pick, back) a target_type alapján állítja össze a választható célpontokat.

Dokumentáció és kód eltérése: A függvény docstringje az "enemy", "ally", "self", "all_enemies", "all_allies" értékeket említi, de a tényleges kód más szavakat vizsgál. A kód a mérvadó.

target_type Célpont-lista Legyőzöttek? Megjegyzés
"self" A cselekvő maga Nem releváns Nincs célpontválasztó, a menü azonnal bezárul
"ene" eparty (a hősök) Kimaradnak A „Támadás” is ezt használja
"ene_rev" eparty Benne vannak
"all_rev" aparty (a saját oldal) Benne vannak Újraélesztő képességekhez
bármilyen más szöveg aparty (a saját oldal) Kimaradnak Pl. "ally" ide esik

További tudnivalók:

  • Az aparty a játékos oldala (party_members), az eparty a hősöké (enemies).
  • Ha nincs választható célpont, a függvény visszatér, és az előző menü marad nyitva.
  • A célpont-visszahívás (on_pick) mindig egyelemű listát kap, így a csatamód többcélpontos kijelölést nem támogat. A célpontválasztó listája a célpont nevét és az aktuális életerejét mutatja, kiegészítve a Kő és Védés állapotokkal.
  • Ha területi hatást akarsz, azt a függvénynek/animációnak kell kezelnie. TODO: A függvény csak a user és target argumentumot kapja, így a többi szereplő eléréséhez a modul szintű listákra (battle.enemies, battle.party_members) lenne szükség. Ennek a tervezett módja még tisztázandó.

9. Az MI-függvény (enemy_turn_func)

Aláírás

def hero_ai(e, enemies, party_members, ai_data):
    ...
    return Action(...)  # vagy None
Argumentum Tartalom
e A cselekvésre kész hős (Combatant)
enemies A hősök (saját oldal) listája
party_members A játékos karaktereinek listája (az ellenfél, az MI szemszögéből)
ai_data A init_ai_data csatánkénti másolata. Az MI ebben tárolhat állapotot (pl. számlálót)

Visszatérési érték:

  • Action (vagy bármi igaz értékű): az akció a sor végére kerül, és a hős atb értéke math.inf lesz.
  • None / hamis érték: a hős nem csinál semmit ebben a képkockában, és az MI-függvény a következő képkockában újra meghívódik, amíg nem ad vissza akciót. Ez lehetővé teszi, hogy az MI egy feltételre várjon.

Mikor hívódik?

A függvény minden képkockában (update) meghívódik azokra a hősökre, akik:

  1. nincsenek legyőzve és nincsenek kővé változva (not ko and not stone), és
  2. az atb értékük <= 0.

Minden képkockában minden hős atb-ja eggyel csökken (képkockafüggő, nem dt-alapú. Lásd 12. fejezet).

Példa

def hero_ai_1(e, enemies, party_members, ai_data):
    targets = [p for p in party_members if not p.ko and not p.stone]
    if not targets:
        return None
    ai_data["turn"] += 1
    target = random.choice(targets)
    return Action(
        "Támadás", e, target,
        Damage(e.attack, 0, 0, 0), StatusChange(0, 0, 0),
        battle_data.anim_default,
    )

10. Az Action és az akcióanimáció

Az Action felépítése

A kódban látható hívás alapján:

Action(név, user, target, damage, status_change, animation)
Argumentum Leírás
név Az akció neve (a képernyő alján megjelenő panelhez)
user A cselekvő Combatant
target A célpont Combatant
damage Damage(a, b, c, d). Az első érték a támadásnál a user.attack
status_change StatusChange(a, b, c). A „Támadás” csupa nullát ad
animation Az animációs függvény (lásd lent)

A csatamód közvetlenül csak az animation mezőt olvassa. A többi attribútum nevét (pl. action.user, action.target) az animációknak kell ismerniük.

TODO: Az Action, Damage, StatusChange pontos attribútumnevei és mezőinek jelentése (a Damage és StatusChange további paraméterei). Ezek a battle.py-ban vannak. TODO: A battle_data.anim_default működése.

Az animációs függvény aláírása

def animation(action, action_text, timer, dt, damage_callback) -> bool:
    ...
Argumentum Tartalom
action Az éppen futó Action objektum
action_text A TextRenderer(122, 204, None, True) példány, az akciópanel szövege
timer Az animáció kezdete óta eltelt idő másodpercben. Az első híváskor pontosan 0
dt Az előző képkocka óta eltelt idő
damage_callback damage_callback(amount, target). Lebegő szám létrehozása (lásd lent)

Visszatérési érték:

  • True: az animáció véget ért. A csatamód törli az aktuális akciót, és elrejti az akciópanelt.
  • False: az animáció még fut. A timer a dt-vel nő, és a következő képkockában újra hívódik.

Az akciópanel (action_panel) a teljes animáció ideje alatt látható, de az action_text szövegét az animációnak kell beállítania (pl. action_text.reset(...), type_all()), ahogy a menü szövegét is a csatamód teszi a menüben. Az action_text.render() minden képkockában lefut, még ha a panel rejtett is, ezért az animáció végén érdemes kiüríteni a szöveget (action_text.reset("")) [Feltételezés].

damage_callback(amount, target)

Paraméter Jelentés
amount Negatív szám = sebzés (fehér számként, előjel nélkül jelenik meg). Nem negatív szám = gyógyulás (zöld szám)
target A célpont Combatant. A szám a sprite pozíciója fölött jelenik meg (a sprite_pos(target) alapján)

A callback csak a lebegő számot hozza létre. A HP tényleges módosítása és a ko beállítása nem itt történik, ezeket az animációnak/akciónak kell elvégeznie [Feltételezés].

A lebegő szám 0,5 másodpercig él, és képkockánként (0, 0.4) értékkel mozog felfelé.

Példa (szemléltető jellegű)

def anim_example(action, action_text, timer, dt, damage_callback):
    # Első képkocka: szöveg kiírása
    if timer == 0:
        action_text.reset(action.name)   # [Feltételezés] az attribútum neve
        action_text.type_all()

    # Egyszeri találat 0,6 mp-nél
    if timer >= 0.6 and not getattr(action, "_hit", False):
        action._hit = True
        action.target.hp -= 5            # [Feltételezés] a HP-módosítás az animáció dolga
        damage_callback(-5, action.target)

    # Vége 1,2 mp-nél
    if timer >= 1.2:
        action_text.reset("")
        return True
    return False

11. Egy akció életciklusa

A update(game, dt) minden képkockában a következő sorrendben fut:

  1. Hősök időzítője: minden él(ő), nem kővé vált hősnél atb -= 1. Ha atb <= 0, meghívódik az enemy_turn_func. Ha akciót ad vissza, az a sorba kerül, és atb = math.inf.
  2. Játékos karaktereinek időzítője: ugyanígy atb -= 1. Ha atb <= 0, a karakter a menu_queue-ba kerül, és atb = math.inf.
  3. Következő akció indítása: ha az action_queue nem üres és nincs futó akció, az első elem lesz az aktuális (cur_action), az akciópanel megjelenik, action_timer = 0.
  4. Futó akció léptetése: cur_action.animation(...) meghívása. True esetén vége az akciónak, különben action_timer += dt.
  5. Menü megnyitása: ha van várakozó karakter és nincs nyitott menü, megnyílik a következő karakter menüje. A szöveg: „név menüje”.
  6. Menüpontok és lebegő számok frissítése.
  7. Győzelem ellenőrzése: ha az összes hős ko vagy stone, a zene megáll, és a játék átvált az "overworld" állapotra.
  8. Vereség ellenőrzése: ha a játékos összes karaktere ko vagy stone, a zene megáll, és a játék átvált a "gameover" állapotra.

A győzelem-ellenőrzés a vereségé előtt fut, ezért ha mindkét feltétel egyszerre teljesül, a játékos nyer.


12. Elrendezés és sprite-ok

Pozíciók

Szereplő Pozíció (képernyőpont)
Hős (i. index) (185, 175 - i * 20)
Játékos karaktere (i. index) (40 + (i % 2) * 30, 165 - i * 25)

A sprite-ok középpontja ezen a ponton van. A pozíciók a lista sorrendjét követik. Az alsó menüpanel 230 × 100 méretű, ezért a lista végén lévő szereplők a panel mögé/fölé kerülhetnek (hősöknél kb. a 4. index után, a játékos karaktereinél kb. a 3. index után).

Három sprite az állapotokhoz

Az enemy_sprites[i] és party_sprites[i] pontosan 3 azonosítót tartalmaz, ebben a sorrendben:

Index Mikor látszik
[0] Normál állapot
[1] Ha hp <= maxhp * 0.1 (sebzett)
[2] Ha ko igaz (a KO sprite elsőbbséget élvez)

Ha a szereplő stone állapotban van (és nem ko), akkor az éppen kiválasztott sprite szürkeárnyalatos változata látszik. A szürke változatot a csatamód létrehozza és gyorsítótárazza.

Az azonosítók az assets.sprites szótár kulcsai. Az asset lehet közvetlenül textúra, vagy lehet .texture attribútuma.


13. Ismert korlátok és buktatók

Ezeket a csatamód forrásából olvastam ki. Érdemes őket a csatatervezés közben szem előtt tartani.

  1. Üres képesség- vagy tárgymenü beragadást okoz. Az open_spell_menu és az open_item_menu először kiüríti a menu listát, és csak utána ellenőrzi, hogy van-e elem. Ha nincs (pl. egy karakternek nincs képessége, vagy elfogytak a tárgyak), a menü üres marad, a menu_open pedig nem nullázódik, így a csata ettől a karaktertől kezdve megakadhat. Ezért:
    • ne szerepeljen "magic" a menüben, ha a karakternek nincs képessége,
    • ne szerepeljen "items", ha nincs tárgy a csatában, vagy gondoskodni kell róla, hogy a tárgyak ne fogyjanak el.
  2. Ismeretlen menüművelet elveszíti a kört. A menu_action az ismeretlen értéknél bezárja a menüt (close_menu()), ezért a karakter atb értéke math.inf marad, és nem fog újra cselekedni, hacsak más nem állítja vissza.
  3. Az atb visszaállítása nem a csatamódban történik. A csatamód az akciók sorba állításakor math.inf-re állítja az atb-t, de újra nem tölti fel. Ezért az akcióknak/animációknak kell a user.atb értékét pozitív számra visszaállítani a végrehajtás után [Feltételezés, ellenőrizendő a battle.py-ban]. Ha ez elmarad, a karakter többé nem cselekszik.
  4. Az atb képkockafüggő. Az atb -= 1 minden update híváskor lefut, nem a dt-től függ. A csata sebessége így a frissítési gyakoriságtól függ.
  5. Legfeljebb 5 menüsor fér el. A menüpontok y = 64 - i * 13 pozícióban vannak. A 6. elem már a képernyőn kívülre esik. Ez a képesség-, tárgy- és célpontlistára egyaránt vonatkozik.
  6. Nincs ellenőrzés az akció végrehajtásakor. A sorba állított akció akkor is lefut, ha közben a cselekvő vagy a célpont legyőzötté vált. Az animációnak/akciónak kell kezelnie ezt az esetet [Feltételezés].
  7. Többcélpontos akció nincs. A célpontválasztás mindig egyetlen célpontot ad át.
  8. Az MP nem érvényesül a csatamódban (lásd 6. fejezet).

14. Nyitott kérdések (TODO)

  • A Combatant, Action, Damage, StatusChange osztályok teljes definíciója (battle.py).
  • A battle_data.anim_default és az egyéb beépített animációk működése.
  • Az atb újratöltésének helye és értékei.
  • A HP-módosítás, a ko, stone, def_up állapotok beállításának helye.
  • Az MP-rendszer megléte és működése.
  • A TextRenderer és MenuItem pontos API-ja (a csatamódban használt hívások: reset, type_all, move, render, select, update).
  • A battle_id és a csataszobrok összerendelése.