Files
rrttm/src/gamestates/battle.py
2026-10-16 15:42:08 +02:00

346 lines
9.2 KiB
Python

import math
import copy
import random
import renderers
import arcade
import gamestate
from PIL import Image
from battle import Combatant, Action, Damage, StatusChange
from menu import MenuItem
from text import TextRenderer
import assets
import battle_data
enemies = []
party_members = []
enemy_turn = None
battle_music = None
enemy_ai_data = {}
party_menus = []
spells = []
inventory = []
enemy_sprites = []
party_sprites = []
gray_cache = {}
action_queue = []
cur_action = None
action_timer = 0
menu_text = TextRenderer(6, 76, 210)
action_text = TextRenderer(110, 215, 210)
menu_open = 0
menu_queue = []
menu = []
panel = renderers.NineSlicesPanel(0,0,230,100)
battle_id = 0
def enemy_pos(i):
return (185, 195 - i * 40)
def party_pos(i):
return (40 + (i % 2) * 30, 165 - i * 35)
def switch(game):
global enemies, party_members, battle_music, enemy_turn, enemy_ai_data, party_menus, spells, inventory, enemy_sprites, party_sprites
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"])
enemy_sprites = battle_data.battles[battle_id]["enemy_sprites"]
party_sprites = battle_data.battles[battle_id]["party_sprites"]
battle_music = assets.sounds[battle_data.battles[battle_id]["mus_id"]].play(loops=True)
close_menu()
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),
open_menu
)
)
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_name, func, target_type) = spell
open_target_menu(
user, aparty, eparty, target_type,
lambda targets: action_queue.append(func(user, targets[0])),
lambda *a: open_spell_menu(user, aparty, eparty))
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),
open_menu
)
)
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_name, func, target_type) = item
def use(targets):
action_queue.append(func(user, targets[0]))
item[1] -= 1
open_target_menu(user, aparty, eparty, target_type, use,
lambda *a: open_item_menu(user, aparty, eparty))
def open_target_menu(user, aparty, eparty, target_type, on_pick, back):
"""
target_type: "enemy", "ally", "self", "all_enemies", "all_allies"
on_pick: called with a LIST of targets
back: called when the player cancels (reopens the previous menu)
"""
global menu
# No selection needed
if target_type == "self":
on_pick([user]); close_menu(); return
pool = eparty if target_type in ["ene", "ene_rev"] else aparty
candidates = [c for c in pool if not c.ko] if target_type not in ["ene_rev", "all_rev"] else pool
if not candidates:
return
menu = []
for i, c in enumerate(candidates):
name = c.name + "(" + str(c.hp) + ("," if c.def_up or c.stone else "") + ("Kő" if c.stone else "") + (", " if c.def_up and c.stone else "") + ("Védés" if c.def_up else "") + ")"
menu.append(MenuItem(
name,
19, 64 - (i * 13),
None, None, None, None,
target_picked, (c, on_pick),
back
))
for i, m in enumerate(menu):
m.up = menu[(i - 1) % len(menu)]
m.down = menu[(i + 1) % len(menu)]
menu[0].select()
def target_picked(data):
target, on_pick = data
on_pick([target])
close_menu()
def open_menu(data):
global menu
whose, junk, aparty, eparty = data
menu = []
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, aparty, eparty, 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 menu_action(data):
user, aparty, eparty, action = data
match action:
case "unselectable":
return
case "phys_att":
def queue_attack(targets):
action_queue.append(Action(
"Támadás", user, targets[0],
Damage(user.attack, 0, 0, 0), StatusChange(0, 0, 0),
battle_data.anim_default))
open_target_menu(user, aparty, eparty, "ene", queue_attack,
lambda *a: open_menu((user, 0, aparty, eparty)))
case "magic":
open_spell_menu(user, aparty, eparty)
case "items":
open_item_menu(user, aparty, eparty)
case _:
print(f"Unknown action: {action}")
close_menu()
# !!! CHATGPT VÉGE !!!
def update(game, dt):
global menu_open, cur_action, action_timer
for e in enemies:
if e.ko or e.stone:
continue
e.atb -= 1
if e.atb <= 0:
act = enemy_turn(e, enemies, party_members, enemy_ai_data)
if act:
action_queue.append(act)
e.atb = math.inf
for p in party_members:
if p.ko or p.stone:
continue
p.atb -= 1
if p.atb <= 0:
print(f"{p.name} is ready to act!")
menu_queue.append(p)
p.atb = math.inf
if len(action_queue) != 0 and cur_action == None:
action = action_queue.pop(0)
print(f"Executing: {action}")
cur_action = action
action_timer = 0
if cur_action != None:
if cur_action.animation(cur_action, action_text, action_timer, dt):
cur_action = None
else:
action_timer += dt
if len(menu_queue) != 0 and menu_open == 0:
whose = menu_queue.pop(0)
menu_open = whose
menu_text.reset(f"{whose.name} menüje")
menu_text.type_all()
open_menu((whose, 67, party_members, enemies))
for m in menu:
m.update()
if len(enemies) != 0 and all(e.ko or e.stone 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 or a.stone for a in party_members):
print("\nGAME OVER! the party was defeated.")
battle_music.stop()
gamestate.switch_gamestate(game,"gameover")
return
# !!! CHATGPT !!!
def make_gray(tex, key):
img = tex.image.convert("RGBA")
a = img.getchannel("A")
l = img.convert("L")
out = Image.merge("RGBA", (l, l, l, a))
return arcade.Texture(out)
def get_texture(sprite_id, gray=False):
asset = assets.sprites[sprite_id]
tex = getattr(asset, "texture", asset)
if not gray:
return tex
if sprite_id not in gray_cache:
gray_cache[sprite_id] = make_gray(tex, sprite_id)
return gray_cache[sprite_id]
def pick_sprite_id(c, ids):
if c.ko:
return ids[2]
if c.hp <= c.maxhp * 0.1:
return ids[1]
return ids[0]
def draw_tex(tex, cx, cy):
arcade.draw_texture_rect(tex, arcade.LRBT(
cx - tex.width / 2, cx + tex.width / 2,
cy - tex.height / 2, cy + tex.height / 2))
def draw_battlers(combatants, sprite_table, pos_func):
for i, c in enumerate(combatants):
sid = pick_sprite_id(c, sprite_table[i])
tex = get_texture(sid, gray=bool(c.stone and not c.ko))
draw_tex(tex, *pos_func(i))
def draw(game):
draw_battlers(enemies, enemy_sprites, enemy_pos)
draw_battlers(party_members, party_sprites, party_pos)
panel.render()
for m in menu:
m.render()
menu_text.render()
action_text.render()