Compare commits

..

8 Commits

Author SHA1 Message Date
markojozsef
b77b110eb9 Nem kell már a bullshit.py 2026-10-07 13:10:49 +02:00
markojozsef
d8ed2afa60 🤦 2026-10-07 13:08:39 +02:00
markojozsef
68a8e44388 még egy screenshot 2026-10-07 13:06:29 +02:00
markojozsef
2a29f318b4 csata menük, csata nyerés, game over képernyő 2026-10-07 13:02:35 +02:00
markojozsef
7ac1a8259f vissza függvény menuItemben 2026-10-07 12:14:49 +02:00
markojozsef
75fc03a88d csata formátum dokumentáció 2026-10-07 12:13:48 +02:00
markojozsef
4de57827b0 AI használat annotálása 2026-10-07 12:01:27 +02:00
markojozsef
b30a7c291b MenuItem és TextRenderer dokumentáció 2026-10-07 11:56:14 +02:00
13 changed files with 1112 additions and 326 deletions

View File

@@ -1,305 +0,0 @@
import math
K_NORMAL = 100
K_DEFUP = 50
MIN_DMG = 1
class Combatant:
def __init__(self, name, maxhp, attack, speed, def_phys, def_fire, def_ice, abs_fire, abs_ice):
self.name = name
self.max_atb = 1000/speed
self.atb = self.max_atb
self.maxhp = maxhp
self.hp = maxhp
self.attack = attack
self.ko = False
self.stone = False
self.def_up = False
self.def_phys = def_phys
self.def_fire = def_fire
self.def_ice = def_ice
self.abs_fire = abs_fire
self.abs_ice = abs_ice
# POTENTIAL damage before defense effects
class Damage:
def __init__(self, phys, fire, ice, healing):
self.phys= phys
self.fire= fire
self.ice= ice
self.healing = healing
# 0 = keep, 1 = inflict, 2 = remove
class StatusChange:
def __init__(self, ko, stone, def_up):
self.ko = ko
self.stone = stone
self.def_up = def_up
# where = reference to the target, who = reference to the inflictor
class Action:
def __init__(self, name, who, where, damage, status):
self.name = name
self.who = who
self.where = where
self.damage = damage
self.status = status
def __str__(self):
return (
f"[{self.name}] {self.who.name} -> {self.where.name} "
f"(HP influence: ({self.damage.phys},{self.damage.fire},{self.damage.ice},{self.damage.healing}), Status: {self.status.ko}-{self.status.stone}-{self.status.def_up})"
)
def __repr__(self):
return self.__str__()
def execute(self):
target = self.where
if self.who.ko or self.who.stone:
print(f" -> {self.who.name} cannot move!")
return
self.who.atb = self.who.max_atb
if target.ko and self.status.ko != 2:
print(f" -> {target.name} is KO'd, move cancelled!")
return
K = K_DEFUP if target.def_up else K_NORMAL
delta_hp = self.damage.healing
if self.damage.phys:
delta_hp += -max(MIN_DMG, self.damage.phys * (K / (K + target.def_phys)))
if self.damage.fire:
delta_hp += self.damage.fire if target.abs_fire else -max(MIN_DMG, self.damage.fire * (K / (K + target.def_fire)))
if self.damage.ice:
delta_hp += self.damage.ice if target.abs_ice else -max(MIN_DMG, self.damage.ice * (K / (K + target.def_ice )))
if self.status.ko == 1:
delta_hp = -target.hp
elif self.status.ko == 2 and target.ko:
target.ko = False
print(f" -> {target.name}'s consciousness returned!")
delta_hp = round(delta_hp)
if delta_hp > 0:
print(f" -> {target.name} got healed for {delta_hp} HP!")
elif delta_hp < 0:
print(f" -> {target.name} suffered {-delta_hp} damage!")
target.hp = max(0, min(target.maxhp, target.hp + delta_hp))
if self.status.stone == 1 and not target.stone:
target.stone = True
print(f" -> {target.name}'s body petrified!")
elif self.status.stone == 2 and target.stone:
target.stone = False
print(f" -> {target.name}'s body softened back up!")
if self.status.def_up == 1 and not target.def_up:
target.def_up = True
print(f" -> {target.name}'s defense was boosted!")
elif self.status.def_up == 2 and target.def_up:
target.def_up = False
print(f" -> {target.name}'s defense returned to normal!")
if target.hp == 0:
target.ko = True
target.stone = False
target.def_up = False
print(f" -> {target.name} got hurt and collapsed!")
# !!! CHATGPT !!! CSAK TESZT !!!
enemies = [
# name hp atk spd dphys dfire dice absF absI
Combatant("Six-Seven kid", 120, 30, 80, 20, 0, 50, False, False),
Combatant("Ice Sprite", 60, 15, 150, 10, 0, 200, False, True ), # ice-immune, fast, fragile
Combatant("Magma Brute", 200, 40, 40, 60, 999, -50, True, False), # absorbs fire, weak to ice, slow tank
]
party_members = [
# name hp atk spd dphys dfire dice absF absI
Combatant("Terra", 100, 35, 100, 30, 30, 30, False, False),
Combatant("Locke", 80, 45, 130, 15, 10, 10, False, False), # glass cannon, fast, low def
Combatant("Celes", 110, 25, 90, 50, 40, 40, False, False), # tanky
Combatant("Cyan", 130, 38, 70, 40, 20, 20, False, False),
]
tick_counter = 0
action_queue = []
# ---------------------------------------------------------------------------
# Move library — a spread of physical/elemental/status/healing effects
# ---------------------------------------------------------------------------
def move_attack(user, target):
return Action("Attack", user, target, Damage(user.attack, 0, 0, 0), StatusChange(0, 0, 0))
def move_fire(user, target):
return Action("Fira", user, target, Damage(0, 45, 0, 0), StatusChange(0, 0, 0))
def move_ice(user, target):
return Action("Blizzara", user, target, Damage(0, 0, 45, 0), StatusChange(0, 0, 0))
def move_cure(user, target):
return Action("Cure", user, target, Damage(0, 0, 0, 42), StatusChange(0, 0, 0))
def move_defend(user, target):
return Action("Defend", user, target, Damage(0, 0, 0, 0), StatusChange(0, 0, 1))
def move_break(user, target):
return Action("Break", user, target, Damage(0, 0, 0, 0), StatusChange(0, 1, 0))
def move_death(user, target):
return Action("Death", user, target, Damage(0, 0, 0, 0), StatusChange(1, 0, 0))
def move_phoenix(user, target):
# revive w/ modest healing; execute() floors it at +1 HP if this isn't enough
return Action("Phoenix Down", user, target, Damage(0, 0, 0, 30), StatusChange(2, 0, 0))
def move_softenup(user, target):
return Action("Esuna (remove stoneskin)", user, target, Damage(0, 0, 0, 0), StatusChange(2, 0, 0))
MOVES = {
"attack": ("Physical attack", move_attack, True),
"fire": ("Fira (fire magic)", move_fire, True),
"ice": ("Blizzara (ice magic)", move_ice, True),
"cure": ("Cure (heal ally)", move_cure, False),
"defend": ("Defend (self def_up)", move_defend, None), # self-only, forced
"break": ("Break (inflict stone)", move_break, True),
"death": ("Death (instant KO)", move_death, True),
"phoenix": ("Phoenix Down (revive ally)",move_phoenix, False),
"unstone": ("Esuna (cure stone)", move_softenup,False),
}
HELP_TEXT = (
"\nAvailable moves:\n" +
"\n".join(f" {k:8s} - {v[0]}" for k, v in MOVES.items()) +
"\n help - show this list\n"
)
def choose_target(user, candidates, prompt):
alive = [c for c in candidates]
print(prompt)
for i, c in enumerate(alive):
tags = []
if c.ko: tags.append("KO")
if c.stone: tags.append("STONE")
if c.def_up: tags.append("DEF_UP")
tagstr = f" [{', '.join(tags)}]" if tags else ""
print(f" {i}: {c.name} ({c.hp}/{c.maxhp} HP){tagstr}")
while True:
raw = input("Target #: ").strip()
if raw.isdigit() and 0 <= int(raw) < len(alive):
return alive[int(raw)]
print("Invalid target index.")
def player_turn(p):
print(f"\n{p.name} is ready to act! ({p.hp}/{p.maxhp} HP, def_up={p.def_up}, stone={p.stone})")
while True:
act = input("Action (or 'help'): ").strip().lower()
if act == "help":
print(HELP_TEXT)
continue
if act not in MOVES:
print("Unknown move. Type 'help' for the list.")
continue
label, builder, needs_enemy_target = MOVES[act]
if act == "defend":
return builder(p, p)
if needs_enemy_target is True:
target = choose_target(p, enemies, "Choose an enemy target:")
elif needs_enemy_target is False:
target = choose_target(p, party_members, "Choose a party target:")
else:
target = p
return builder(p, target)
import random
def enemy_turn(e):
living = [p for p in party_members if not p.ko]
if not living:
return None
# weighted move pool -- enemies get the same toolkit as the party
offensive = ["attack", "fire", "ice", "death"]
support = ["break"]
self_only = ["defend"]
# if an ally is KO'd, occasionally try to revive/cure instead of attacking
ko_allies = [a for a in enemies if a.ko and a is not e]
hurt_allies = [a for a in enemies if not a.ko and a.hp < a.maxhp]
roll = random.random()
if ko_allies and roll < 0.15:
act = "phoenix"
target = random.choice(ko_allies)
elif hurt_allies and roll < 0.30:
act = "cure"
target = random.choice(hurt_allies)
elif not e.def_up and roll < 0.40:
act = "defend"
target = e
elif roll < 0.55:
act = "break"
target = random.choice(living)
elif roll < 0.65:
act = "death"
target = random.choice(living)
else:
act = random.choice(["attack", "fire", "ice"])
target = random.choice(living)
_, builder, _ = MOVES[act]
print(f" ({e.name} chooses {act} -> {target.name})")
return builder(e, target)
while True:
print(f"\n=== Tick #{tick_counter} ===")
for e in enemies:
if e.ko or e.stone:
continue
e.atb -= 1
if e.atb <= 0:
act = enemy_turn(e)
if act:
action_queue.append(act)
for p in party_members:
if p.ko or p.stone:
continue
p.atb -= 1
if p.atb <= 0:
action_queue.append(player_turn(p))
if len(action_queue) != 0:
action = action_queue.pop(0)
print(f"Executing: {action}")
action.execute()
if all(e.ko for e in enemies):
print("\nVICTORY! all enemies were defeated.")
break
if all(p.ko for p in party_members):
print("\nGAME OVER! the party was defeated.")
break
tick_counter += 1

22
doc/AIHasznalat.md Normal file
View File

@@ -0,0 +1,22 @@
# AI használat a kódban
## Jelző
Teljesen AI-generált szakaszokat egy `# !!! CHATGPT !!!` komment jelzi
## AI generálta komponensek:
### main.py
* Game
### renderers.py
* TextureRenderer
* NineSlicesPanel
### battle_data.py
* csata függvények
### gamestates/battle.py
* menük
## Dokumentáció
Ezen a fájlon kívül minden dokumentáció AI generálta.

444
doc/CsataFormatum.md Normal file
View File

@@ -0,0 +1,444 @@
# Csata objektum
## Szerkezet
```python
{
"enemies": [...],
"party_members": [...],
"enemy_turn_func": ...,
"mus_id": "...",
"init_ai_data": {...},
"menus": [...],
"spells": [...],
"inventory": [...]
}
```
---
## enemies
Az ellenséges egységek listája.
### Típus
```python
list[Combatant]
```
### Példa
```python
"enemies": [
Combatant("Six-Seven kid", 120, 30, 80, 20, 0, 50, False, False),
Combatant("Ice Sprite", 60, 15, 150, 10, 0, 200, False, True),
]
```
Minden elem egy `Combatant` objektum.
---
## party_members
A játékos által irányított karakterek listája.
### Típus
```python
list[Combatant]
```
### Példa
```python
"party_members": [
Combatant("Locke", 80, 45, 130, 15, 10, 10, False, False),
Combatant("Terra", 100, 35, 100, 30, 30, 30, False, False),
]
```
A lista sorrendje határozza meg:
- a karakter pozícióját a csatában,
- a menük hozzárendelését,
- a varázslatok hozzárendelését.
---
## enemy_turn_func
Az ellenséges köröket kezelő függvény.
### Típus
```python
callable
```
### Példa
```python
"enemy_turn_func": enemy_turn_test
```
A függvényt a rendszer az ellenfelek körének feldolgozására használja.
---
## mus_id
A csata alatt lejátszandó zene azonosítója.
### Típus
```python
str
```
### Példa
```python
"mus_id": "mus_fnafsux"
```
---
## init_ai_data
Az MI számára átadott inicializáló adatok.
### Típus
```python
dict
```
---
### moves
Az MI által használható képességek gyűjteménye.
#### Típus
```python
dict[str, tuple]
```
#### Formátum
```python
"azonosító": (
név,
függvény,
célpont_szükséges
)
```
#### Paraméterek
- `név` (`str`)
A képesség megjelenő neve.
- `függvény` (`callable`)
A végrehajtandó függvény.
- `célpont_szükséges`
- `True` → ellenséges célpont szükséges
- `False` → szövetséges célpont szükséges
- `None` → nincs célpont
#### Példa
```python
"attack": (
"Physical attack",
move_attack,
True
)
```
---
### Teljes példa
```python
"moves": {
"attack": ("Physical attack", move_attack, True),
"fire": ("Fira", move_fire, True),
"ice": ("Blizzara", move_ice, True),
"cure": ("Cure", move_cure, False),
"defend": ("Defend", move_defend, None)
}
```
---
## menus
Karakterenként meghatározza a harci főmenüt.
### Típus
```python
list[list]
```
A külső lista a karaktereket reprezentálja.
A sorrendnek meg kell egyeznie a `party_members` listával.
---
### Egy karakter menüje
```python
[
["Attack", "phys_att"],
["Magic", "magic"],
["Item", "items"]
]
```
---
### Menüpont formátuma
```python
[
megjelenő_szöveg,
parancs
]
```
#### Paraméterek
- `megjelenő_szöveg` (`str`)
- `parancs` (`str`)
---
### Speciális parancsok
#### Fizikai támadás
```python
["Attack", "phys_att"]
```
#### Mágia
```python
["Magic", "magic"]
```
#### Tárgyak
```python
["Item", "items"]
```
#### Nem választható hely
```python
["", "unselectable"]
```
Ez egy üres, nem kiválasztható menüpont.
---
## spells
Karakterenként meghatározza az elérhető varázslatokat.
### Típus
```python
list[list]
```
A lista sorrendje megegyezik a `party_members` sorrendjével.
---
### Varázslat formátuma
```python
[
név,
mp_költség,
(
név,
függvény,
célpont_szükséges
)
]
```
#### Paraméterek
- `név` (`str`)
A varázslat megjelenő neve.
- `mp_költség` (`int`)
A varázslat MP költsége.
- `akció` (`tuple`)
A végrehajtandó harci művelet.
---
### Példa
```python
[
"Fira",
15,
("Fira", move_fire, True)
]
```
---
### Teljes példa
```python
"spells": [
[],
[
["Fira", 0, ("Fira", move_fire, True)],
["Blizzara", 0, ("Blizzara", move_ice, True)]
],
[
["Fira", 0, ("Fira", move_fire, True)],
["Blizzara", 0, ("Blizzara", move_ice, True)]
],
[]
]
```
Ebben az esetben:
- Locke nem rendelkezik varázslatokkal,
- Terra használhatja a **Fira** és **Blizzara** varázslatokat,
- Celes használhatja a **Fira** és **Blizzara** varázslatokat,
- Cyan nem rendelkezik varázslatokkal.
---
## inventory
A csatában használható tárgyak listája.
### Típus
```python
list
```
---
### Tárgy formátuma
```python
[
név,
mennyiség,
(
név,
függvény,
célpont_szükséges
)
]
```
#### Paraméterek
- `név` (`str`)
A tárgy megjelenő neve.
- `mennyiség` (`int`)
A rendelkezésre álló darabszám.
- `akció` (`tuple`)
A tárgy használatakor meghívandó művelet.
---
### Példa
```python
[
"Phoenix Down",
67,
(
"Phoenix Down",
move_phoenix,
False
)
]
```
---
### Teljes példa
```python
"inventory": [
[
"Phoenix Down",
67,
(
"Phoenix Down",
move_phoenix,
False
)
]
]
```
---
## Indexkapcsolatok
A következő listák indexei összetartoznak:
```python
party_members[i]
menus[i]
spells[i]
```
Példa:
```python
party_members[1] # Terra
menus[1] # Terra menüje
spells[1] # Terra varázslatai
```
Ez biztosítja, hogy minden karakter saját menüvel és saját varázslistával rendelkezzen.
---
## Összefoglaló
```python
{
"enemies": list[Combatant],
"party_members": list[Combatant],
"enemy_turn_func": callable,
"mus_id": str,
"init_ai_data": {
"moves": dict[str, tuple]
},
"menus": list[list],
"spells": list[list],
"inventory": list
}
```
Egy csata teljes egészében leírható egyetlen szótárral, amely tartalmazza az összes résztvevőt, a harci lehetőségeket, az MI által használható képességeket, valamint a játékos számára elérhető menüket, varázslatokat és tárgyakat.

225
doc/MenuItem.md Normal file
View File

@@ -0,0 +1,225 @@
# MenuItem
## Áttekintés
A `MenuItem` osztály egy játék menürendszerének egyetlen választható elemét reprezentálja. Az osztály kezeli:
- a menüpont szövegének megjelenítését,
- a kijelölést,
- a menüpontok közötti navigációt,
- a kiválasztáskor meghívandó függvényt (akciót),
- valamint a kurzor kirajzolását.
A menüpontok egy irányított hálózatot alkotnak, ahol minden elem ismeri a szomszédait (`up`, `down`, `left`, `right`).
---
## Konstruktor
```python
MenuItem(
label,
x,
y,
left,
right,
up,
down,
action,
data=None,
back_func=None
)
```
### Paraméterek
- `label` (`str`)
A menüpont megjelenő szövege.
- `x` (`int`)
A menüpont vízszintes pozíciója.
- `y` (`int`)
A menüpont függőleges pozíciója.
- `left` (`MenuItem | None`)
A bal oldali szomszédos menüpont.
- `right` (`MenuItem | None`)
A jobb oldali szomszédos menüpont.
- `up` (`MenuItem | None`)
A felső szomszédos menüpont.
- `down` (`MenuItem | None`)
Az alsó szomszédos menüpont.
- `action` (`callable`)
A menüpont kiválasztásakor meghívandó függvény.
- `data` (`any`, opcionális)
A kiválasztáskor az akciónak átadott adat.
- `back_func` (`callable`)
A menüpont kiválasztásakor meghívandó függvény.
---
## Attribútumok
### Navigáció
```python
self.left
self.right
self.up
self.down
```
A szomszédos menüpontokra mutató referenciák.
Ha egy adott irányban nincs további menüpont, akkor az attribútum értéke:
```python
None
```
---
### Akció
```python
self.action
```
A kiválasztás során meghívandó függvény.
Példa:
```python
def start_game(data):
print("Játék indul!")
```
---
### Vissza
```python
self.back
```
A visszalépés során meghívandó függvény.
Példa:
```python
def vissza(data):
open_menu()
```
---
### Adat
```python
self.data
```
A menüelemhez kapcsolódó opcionális adat, amelyet az akció- és visszafüggvény paraméterként kap meg.
Példa:
```python
MenuItem(
"Nehéz",
100, 100,
None, None, None, None,
set_difficulty,
"hard"
)
```
Kiválasztáskor:
```python
set_difficulty("hard")
```
fut le.
---
### Pozíció
```python
self.x
self.y
```
A menüpont képernyőn elfoglalt pozíciója.
---
### Szövegmegjelenítő
```python
self.text
```
Egy `TextRenderer` objektum, amely a felirat megjelenítéséért felel.
---
### Kijelölt állapot
```python
self.selected
```
Logikai érték:
- `True`: a menüpont jelenleg ki van jelölve;
- `False`: a menüpont nincs kijelölve.
---
## Metódusok
### `select()`
```python
select()
```
Kijelöli a menüpontot.
#### Működés
```python
self.selected = True
```
#### Példa
```python
start_button.select()
```
---
### `update()`
```python
update()
```
A menüpont állapotának frissítése.
Ha az elem nincs kijelölve, a függvény azonnal visszatér:
```python
if not self.selected:
return
```

218
doc/TextRenderer.md Normal file
View File

@@ -0,0 +1,218 @@
# TextRenderer
A `TextRenderer` osztály egy egyszerű, karakterenként gépelődő (typewriter effektusú) szövegmegjelenítő. A szöveget sprite-okként tárolja és rendereli, támogatja a sortörést meghatározott szélesség esetén, valamint speciális karakterek kezelését egyedi `display()` függvényeken keresztül.
---
## Konstruktor
```python
TextRenderer(x, y, width=None)
```
Új szövegmegjelenítőt hoz létre.
### Paraméterek
- `x` (`int`) - A szöveg kezdő X koordinátája.
- `y` (`int`) - A szöveg kezdő Y koordinátája.
- `width` (`int | None`) - Opcionális maximális szélesség pixelekben. Túllépés esetén automatikus sortörés történik.
### Attribútumok
- `sprites` - A renderelendő karaktersprite-ok listája.
- `base_x` - Kezdő X koordináta.
- `base_y` - Kezdő Y koordináta.
- `width` - Maximális szövegszélesség.
- `text` - A megjelenítendő szöveg.
- `textptr` - Az aktuálisan feldolgozott karakter indexe.
- `x` - Aktuális rajzolási pozíció X tengelyen.
- `y` - Aktuális rajzolási pozíció Y tengelyen.
---
## `reset(text)`
```python
reset(text)
```
Visszaállítja a renderelőt alapállapotba, és új szöveget tölt be.
### Paraméterek
- `text` (`str`) - A megjelenítendő szöveg.
### Működés
- Törli az összes korábbi sprite-ot.
- Visszaállítja a rajzolási pozíciót a kezdőkoordinátákra.
- Nullázza a karaktermutatót (`textptr`).
### Példa
```python
renderer.reset("Helló világ!")
```
---
## `amend(text)`
```python
amend(text)
```
Hozzáfűz további szöveget a már meglévő szöveghez.
### Paraméterek
- `text` (`str`) - A hozzáfűzendő szöveg.
### Példa
```python
renderer.reset("Helló")
renderer.amend(" világ!")
```
Eredmény:
```text
Helló világ!
```
---
## `type_tick()`
```python
type_tick()
```
Egyetlen karakter feldolgozását végzi el. A függvény elsősorban gépelési animációk megvalósítására használható.
### Visszatérési érték
```python
bool
```
- `True` - a teljes szöveg feldolgozása befejeződött.
- `False` - még vannak feldolgozatlan karakterek.
### Működés
1. Lekéri az aktuális karakterhez tartozó definíciót a `texttable` szótárból.
2. Ismeretlen karakter esetén a `'?'` definíciót használja.
3. Látható karakter esetén:
- kivágja a megfelelő textúrát a font atlaszból,
- létrehozza a sprite adatait,
- hozzáadja azokat a `sprites` listához.
4. Ha a megadott szélesség elfogyott:
- új sorba lép,
- az Y koordinátát 13 pixellel csökkenti.
5. Nem látható karakter esetén meghívja a karakter:
```python
display(renderer)
```
metódusát.
### Példa
```python
while not renderer.type_tick():
pass
```
---
## `type_all()`
```python
type_all()
```
A teljes szöveget azonnal feldolgozza.
### Működés
A `type_tick()` függvényt hívja ismételten addig, amíg minden karakter feldolgozásra nem kerül.
### Példa
```python
renderer.reset("Szia!")
renderer.type_all()
```
A teljes szöveg egyetlen frame alatt elkészül.
---
## `render()`
```python
render()
```
Kirajzolja az összes létrehozott karaktersprite-ot.
### Működés
A `sprites` listán végighaladva minden karakterhez meghívja:
```python
arcade.draw_texture_rect()
```
függvényt pixelpontos (`pixelated=True`) rendereléssel.
### Példa
```python
def on_draw(self):
arcade.start_render()
renderer.render()
```
---
# Tipikus használat
## Gépelési effekt
```python
renderer = TextRenderer(10, 100, width=200)
renderer.reset(
"Ez egy hosszabb szöveg, amely több sorba törhet."
)
renderer.type_tick()
renderer.render()
```
## Azonnali megjelenítés
```python
renderer = TextRenderer(10, 100)
renderer.reset("Helló világ!")
renderer.type_all()
renderer.render()
```
---
# Megjegyzések
- Az osztály nem hoz létre külön `Sprite` objektumot minden karakterhez, hanem csak a szükséges textúrát és pozíciót tárolja.
- Ismeretlen karakter esetén automatikusan a `'?'` karakter kerül megjelenítésre.
- A sortörés kizárólag a megadott `width` túllépésekor történik.
- Speciális vezérlőkarakterek létrehozhatók `visible=False` karakterdefiníciókkal.
- Ezek a karakterek tetszőleges működést valósíthatnak meg a `display(renderer)` metóduson keresztül.

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -13,6 +13,7 @@ def anim_default(action, text, timer, dt):
return False
# !!! CHATGPT !!!
def move_attack(user, target):
return Action("Attack", user, target, Damage(user.attack, 0, 0, 0), StatusChange(0, 0, 0), anim_default)
@@ -79,6 +80,8 @@ def enemy_turn_test(e,ep, ap,d):
print(f" ({e.name} chooses {act} -> {target.name})")
return builder(e, target)
# !!! CHATGPT VÉGE !!!
battles = [
{ "enemies": [
Combatant("Six-Seven kid", 120, 30, 80, 20, 0, 50, False, False),

View File

@@ -2,12 +2,14 @@ import gamestates.none
import gamestates.title
import gamestates.overworld
import gamestates.battle
import gamestates.gameover
gamestates = {
"none": gamestates.none,
"title": gamestates.title,
"overworld": gamestates.overworld,
"battle": gamestates.battle
"battle": gamestates.battle,
"gameover": gamestates.gameover,
}
gamestate = "none"

View File

@@ -1,6 +1,8 @@
import math
import copy
import random
import arcade
import gamestate
from battle import Combatant, Action, Damage, StatusChange
from menu import MenuItem
@@ -35,30 +37,157 @@ battle_id = 0
def switch(game):
global enemies, party_members, battle_music, enemy_turn, enemy_ai_data, party_menus, spells, inventory
enemies = battle_data.battles[battle_id]["enemies"]
party_members = battle_data.battles[battle_id]["party_members"]
enemy_turn = battle_data.battles[battle_id]["enemy_turn_func"]
enemy_ai_data = battle_data.battles[battle_id]["init_ai_data"]
party_menus = battle_data.battles[battle_id]["menus"]
spells = battle_data.battles[battle_id]["spells"]
inventory = battle_data.battles[battle_id]["inventory"]
print("asdfasdfasdf")
enemies = copy.deepcopy(battle_data.battles[battle_id]["enemies"])
party_members = copy.deepcopy(battle_data.battles[battle_id]["party_members"])
enemy_turn = copy.deepcopy(battle_data.battles[battle_id]["enemy_turn_func"])
enemy_ai_data = copy.deepcopy(battle_data.battles[battle_id]["init_ai_data"])
party_menus = copy.deepcopy(battle_data.battles[battle_id]["menus"])
spells = copy.deepcopy(battle_data.battles[battle_id]["spells"])
inventory = copy.deepcopy(battle_data.battles[battle_id]["inventory"])
battle_music = assets.sounds[battle_data.battles[battle_id]["mus_id"]].play(loops=True)
def open_menu(whose):
party_idx = party_members.index(whose)
for i, m in enumerate(party_menus[party_idx]):
menu.append(MenuItem(m[0], 19, 64-(i*13), None, None, None, None, menu_action, (whose, party_members, enemies, m[1])))
for i, m in enumerate(menu):
m.up = menu[(i - 1) % len(menu)]
m.down = menu[(i + 1) % len(menu)]
menu[0].select()
def close_menu():
global menu, menu_open
menu = []
menu_open = 0
menu_text.reset("")
# !!! CHATGPT !!!
def open_spell_menu(user, aparty, eparty):
global menu
party_idx = party_members.index(user)
spell_list = spells[party_idx]
menu = []
for i, spell in enumerate(spell_list):
menu.append(
MenuItem(
spell[0],
19,
64 - (i * 13),
None,
None,
None,
None,
spell_action,
(user, spell, aparty, eparty)
)
)
if len(menu) == 0:
return
for i, m in enumerate(menu):
m.up = menu[(i - 1) % len(menu)]
m.down = menu[(i + 1) % len(menu)]
menu[0].select()
def spell_action(data):
user, spell, aparty, eparty = data
name, mp_cost, action = spell
action_name, func, target_type = action
action_queue.append(func(user, [p for p in eparty if not p.ko][0]))
close_menu()
def open_item_menu(user, aparty, eparty):
global menu
menu = []
for i, item in enumerate(inventory):
if item[1] <= 0:
continue
menu.append(
MenuItem(
f"{item[0]} x{item[1]}",
19,
64 - (i * 13),
None,
None,
None,
None,
item_action,
(user, item, aparty, eparty)
)
)
if len(menu) == 0:
return
for i, m in enumerate(menu):
m.up = menu[(i - 1) % len(menu)]
m.down = menu[(i + 1) % len(menu)]
menu[0].select()
def item_action(data):
user, item, aparty, eparty = data
name, amount, action = item
action_name, func, target_type = action
action_queue.append(
func(user, user)
)
item[1] -= 1
close_menu()
# !!! CHATGPT VÉGE !!!
def menu_action(data):
user, aparty, eparty, action = data
match action:
case "unselectable":
return
case "phys_att":
action_queue.append(Action("Physical Attack", user, eparty[0], Damage(166, 0, 0, 0), StatusChange(0, 0, 0), battle_data.anim_default))
action_queue.append(
Action(
"Physical Attack",
user,
[p for p in eparty if not p.ko][0],
Damage(166, 0, 0, 0),
StatusChange(0, 0, 0),
battle_data.anim_default
)
)
close_menu()
case "magic":
open_spell_menu(user, aparty, eparty)
case "items":
open_item_menu(user, aparty, eparty)
case _:
print(f"Unknown action: {action}")
close_menu()
@@ -113,20 +242,23 @@ def update(game, dt):
menu_open = whose
menu_text.reset(f"{whose.name}'s Menu")
menu_text.type_all()
party_idx = party_members.index(whose)
for i, m in enumerate(party_menus[party_idx]):
menu.append(MenuItem(m[0], 19, 64-(i*13), None, None, None, None, menu_action, (whose, party_members, enemies, m[1])))
for i, m in enumerate(menu):
m.up = menu[(i - 1) % len(menu)]
m.down = menu[(i + 1) % len(menu)]
menu[1].select()
open_menu(whose)
for m in menu:
m.update()
if len(enemies) != 0 and all(e.ko for e in enemies):
print("\nVICTORY! all enemies were defeated.")
battle_music.stop()
gamestate.switch_gamestate(game,"overworld")
return
if len(party_members) != 0 and all(a.ko for a in party_members):
print("\nGAME OVER! the party was defeated.")
battle_music.stop()
gamestate.switch_gamestate(game,"gameover")
return
party_text.reset(partytext)
ene_text.reset(enetext)
party_text.type_all()

View File

@@ -0,0 +1,38 @@
import menu
import text
import renderers
import gamestate
text = text.TextRenderer(90, 180, 210)
panel = renderers.NineSlicesPanel(84,100,64,100)
m = []
def retry(g):
gamestate.switch_gamestate(g, "title")
def switch(game):
global m
m = [
menu.MenuItem("Ujrapróba", 98, 150, None, None, None, None, retry, game),
menu.MenuItem("Kilépés", 98, 130, None, None, None, None, quit)
]
m[0].down = m[1]
m[1].up = m[0]
m[0].select()
text.reset("Játék vége!")
text.type_all()
def update(game, dt):
for i in m:
i.update()
def draw(game):
panel.render()
text.render()
for i in m:
i.render()

View File

@@ -34,6 +34,7 @@ def update(game, dt):
if btrans_timer >= BTRANS_DURATION:
game.mos_enabled = False
btrans_active = False
btrans_sound.stop()
gamestate.switch_gamestate(game,"battle")

View File

@@ -8,6 +8,7 @@ import text
SCREEN_WIDTH = 256
SCREEN_HEIGHT = 224
# !!! CHATGPT !!!
class Game(arcade.Window):
def __init__(self):
super().__init__(

View File

@@ -5,12 +5,13 @@ import controls
# Választható menüelem több menüelem közt, ha nincs valamelyik irányba másik menüelem, az irányra None értéket adunk
class MenuItem:
def __init__(self, label, x, y, left, right, up, down, action, data=None):
def __init__(self, label, x, y, left, right, up, down, action, data=None, back_func=None):
self.left = left
self.right = right
self.up = up
self.down = down
self.action = action
self.back = back_func
self.data = data
self.x = x
self.y = y
@@ -30,6 +31,10 @@ class MenuItem:
self.action(self.data)
return
if controls.is_control_pressed(5) and self.back: # Vissza
self.back(self.data)
return
if controls.is_control_pressed(0) and self.up: # Fel
controls.depress(0)
self.selected = False