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 return False
# !!! CHATGPT !!!
def move_attack(user, target): def move_attack(user, target):
return Action("Attack", user, target, Damage(user.attack, 0, 0, 0), StatusChange(0, 0, 0), anim_default) 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})") print(f" ({e.name} chooses {act} -> {target.name})")
return builder(e, target) return builder(e, target)
# !!! CHATGPT VÉGE !!!
battles = [ battles = [
{ "enemies": [ { "enemies": [
Combatant("Six-Seven kid", 120, 30, 80, 20, 0, 50, False, False), 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.title
import gamestates.overworld import gamestates.overworld
import gamestates.battle import gamestates.battle
import gamestates.gameover
gamestates = { gamestates = {
"none": gamestates.none, "none": gamestates.none,
"title": gamestates.title, "title": gamestates.title,
"overworld": gamestates.overworld, "overworld": gamestates.overworld,
"battle": gamestates.battle "battle": gamestates.battle,
"gameover": gamestates.gameover,
} }
gamestate = "none" gamestate = "none"

View File

@@ -1,6 +1,8 @@
import math import math
import copy
import random import random
import arcade import arcade
import gamestate
from battle import Combatant, Action, Damage, StatusChange from battle import Combatant, Action, Damage, StatusChange
from menu import MenuItem from menu import MenuItem
@@ -35,30 +37,157 @@ battle_id = 0
def switch(game): def switch(game):
global enemies, party_members, battle_music, enemy_turn, enemy_ai_data, party_menus, spells, inventory global enemies, party_members, battle_music, enemy_turn, enemy_ai_data, party_menus, spells, inventory
enemies = battle_data.battles[battle_id]["enemies"] print("asdfasdfasdf")
party_members = battle_data.battles[battle_id]["party_members"] enemies = copy.deepcopy(battle_data.battles[battle_id]["enemies"])
enemy_turn = battle_data.battles[battle_id]["enemy_turn_func"] party_members = copy.deepcopy(battle_data.battles[battle_id]["party_members"])
enemy_ai_data = battle_data.battles[battle_id]["init_ai_data"] enemy_turn = copy.deepcopy(battle_data.battles[battle_id]["enemy_turn_func"])
party_menus = battle_data.battles[battle_id]["menus"] enemy_ai_data = copy.deepcopy(battle_data.battles[battle_id]["init_ai_data"])
spells = battle_data.battles[battle_id]["spells"] party_menus = copy.deepcopy(battle_data.battles[battle_id]["menus"])
inventory = battle_data.battles[battle_id]["inventory"] 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) 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(): def close_menu():
global menu, menu_open global menu, menu_open
menu = [] menu = []
menu_open = 0 menu_open = 0
menu_text.reset("") 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): def menu_action(data):
user, aparty, eparty, action = data user, aparty, eparty, action = data
match action: match action:
case "unselectable": case "unselectable":
return return
case "phys_att": 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() close_menu()
case "magic":
open_spell_menu(user, aparty, eparty)
case "items":
open_item_menu(user, aparty, eparty)
case _: case _:
print(f"Unknown action: {action}") print(f"Unknown action: {action}")
close_menu() close_menu()
@@ -113,20 +242,23 @@ def update(game, dt):
menu_open = whose menu_open = whose
menu_text.reset(f"{whose.name}'s Menu") menu_text.reset(f"{whose.name}'s Menu")
menu_text.type_all() menu_text.type_all()
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[1].select()
for m in menu: for m in menu:
m.update() 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) party_text.reset(partytext)
ene_text.reset(enetext) ene_text.reset(enetext)
party_text.type_all() 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: if btrans_timer >= BTRANS_DURATION:
game.mos_enabled = False game.mos_enabled = False
btrans_active = False
btrans_sound.stop() btrans_sound.stop()
gamestate.switch_gamestate(game,"battle") gamestate.switch_gamestate(game,"battle")

View File

@@ -8,6 +8,7 @@ import text
SCREEN_WIDTH = 256 SCREEN_WIDTH = 256
SCREEN_HEIGHT = 224 SCREEN_HEIGHT = 224
# !!! CHATGPT !!!
class Game(arcade.Window): class Game(arcade.Window):
def __init__(self): def __init__(self):
super().__init__( 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 # 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: 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.left = left
self.right = right self.right = right
self.up = up self.up = up
self.down = down self.down = down
self.action = action self.action = action
self.back = back_func
self.data = data self.data = data
self.x = x self.x = x
self.y = y self.y = y
@@ -30,6 +31,10 @@ class MenuItem:
self.action(self.data) self.action(self.data)
return 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 if controls.is_control_pressed(0) and self.up: # Fel
controls.depress(0) controls.depress(0)
self.selected = False self.selected = False