From 5c7db0e77e7657cc343fbe6a08975d875ca60b81 Mon Sep 17 00:00:00 2001 From: markojozsef Date: Wed, 30 Sep 2026 11:56:08 +0200 Subject: [PATCH] =?UTF-8?q?Csatarendszer=20adatcentrikus=C3=ADt=C3=A1sa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/battle.py | 116 ++++++++++++++++++ src/battle_data.py | 97 +++++++++++++++ src/gamestates/battle.py | 228 +++--------------------------------- src/gamestates/overworld.py | 2 +- 4 files changed, 232 insertions(+), 211 deletions(-) create mode 100644 src/battle.py create mode 100644 src/battle_data.py diff --git a/src/battle.py b/src/battle.py new file mode 100644 index 0000000..c9571d9 --- /dev/null +++ b/src/battle.py @@ -0,0 +1,116 @@ +import math + +K_NORMAL = 100 +K_DEFUP = 50 +MIN_DMG = 1 +BASE_DELAY = 30000 + +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 = BASE_DELAY//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!") \ No newline at end of file diff --git a/src/battle_data.py b/src/battle_data.py new file mode 100644 index 0000000..2476827 --- /dev/null +++ b/src/battle_data.py @@ -0,0 +1,97 @@ +import random +from battle import Combatant, Action, Damage, StatusChange + +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): + 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), +} + +def enemy_turn_test(e,ep, ap,d): + living = [p for p in ap 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 ep if a.ko and a is not e] + hurt_allies = [a for a in ep 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) + +battles = [ + { "enemies": [ + Combatant("Six-Seven kid", 120, 30, 80, 20, 0, 50, False, False), + Combatant("Ice Sprite", 60, 15, 150, 10, 0, 200, False, True ), + Combatant("Magma Brute", 200, 40, 40, 60, 999, -50, True, False), + ], + "party_members": [ + Combatant("Terra", 100, 35, 100, 30, 30, 30, False, False), + Combatant("Locke", 80, 45, 130, 15, 10, 10, False, False), + Combatant("Celes", 110, 25, 90, 50, 40, 40, False, False), + Combatant("Cyan", 130, 38, 70, 40, 20, 20, False, False), + ], + "enemy_turn_func": enemy_turn_test, + "mus_id": "mus_fnafsux" + } +] \ No newline at end of file diff --git a/src/gamestates/battle.py b/src/gamestates/battle.py index ae0e514..ba53ba7 100644 --- a/src/gamestates/battle.py +++ b/src/gamestates/battle.py @@ -4,227 +4,35 @@ import controls import text import math import random - -K_NORMAL = 100 -K_DEFUP = 50 -MIN_DMG = 1 -BASE_DELAY = 60000 - -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 = BASE_DELAY//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!") -def move_attack(user, target): - return Action("Attack", user, target, Damage(user.attack, 0, 0, 0), StatusChange(0, 0, 0)) +import battle_data +from battle import Combatant, Action, Damage, StatusChange -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): - 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)) - -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), -] +enemies = [] +party_members = [] +enemy_turn = None +battle_music = None + +action_queue = [] +enemy_ai_data = {} party_text = text.TextRenderer(3, 206, 210) ene_text = text.TextRenderer(123, 206, 210) menu_text = text.TextRenderer(3, 36, 210) -action_queue = [] -battle_music = None menu_open = 0 menu_queue = [] +menu = [] -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), -} - -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) +battle_id = 0 def switch(game): - global battle_music - battle_music = assets.sounds["mus_fnafsux"].play(loops=True) + global enemies, party_members, battle_music, enemy_turn, enemy_ai_data + 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_music = assets.sounds[battle_data.battles[battle_id]["mus_id"]].play(loops=True) def update(game, dt): global menu_open @@ -234,7 +42,7 @@ def update(game, dt): continue e.atb -= 1 if e.atb <= 0: - act = enemy_turn(e) + act = enemy_turn(e, enemies, party_members, enemy_ai_data) if act: action_queue.append(act) e.atb = math.inf diff --git a/src/gamestates/overworld.py b/src/gamestates/overworld.py index 2f867b8..af89b4b 100644 --- a/src/gamestates/overworld.py +++ b/src/gamestates/overworld.py @@ -28,7 +28,7 @@ OVERWORLD_MAP = [ [7,4,4,8], ] -WALK_SPEED = 32 +WALK_SPEED = 48 class BalthasarRenderer: def __init__(self):