################################################################################# # This file is part of Spheres. # Copyright (C) 2019-2021 Jesusalva # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ################################################################################# # Battle Module - Main module from utils import stdout, compress, allunits, Player, Battle, allquests from consts import (ERR_ERR, ERR_BAD, ST_TOWN, ST_QUEST, ACT_SPHERE, ACT_SKILL, ACT_NONE, SPH_WIDEATTACK, SPH_PIERCE, SPH_ASSAULT, SPH_HEAL, SPH_HEALALL, SPH_ATKUP, SPH_DEFUP, SPH_NONE) import json, random, traceback import player from battle.common import (find_target, calc_dmg, conditions, advance_wave, battle_endturn) from battle.spheres import sphere_attack #from battle.skills import skill_core, handle_skill # <-- WIP from copy import copy # Prepare a randomness seed random.seed() ############################################# # Client commands ############################################# # begin_quest( world, quest_id, party_id ) def begin_quest(args, token, client_side=True): global allquests, Player, Battle # Data validation try: stdout("Begin Quest: %s" % args) bq=json.loads(args) stdout("JSON loaded", 2) # Validation tmp=int(bq["quest_id"]) tmp=int(bq["party_id"]) if (tmp < 1 or tmp > 3): return ERR_BAD tmpw=str(bq["world"]) tmp=int(Player[token]["quest"]) # FIXME for quest in allquests[tmpw]: if (quest["quest_id"] == bq["quest_id"]): break if (quest["quest_id"] != bq["quest_id"]): raise Exception("Quest not found") stdout("All fine thus far", 2) except: stdout(traceback.format_exc(), 0) # Invalid data return ERR_BAD # We have `quest` variable loaded and now we'll only use it, but... # Can you start a quest? (In Town, Requeriments met, etc.) if (Player[token]["status"] != ST_TOWN and client_side): return ERR_BAD if (Player[token]["quest"] < quest["requeriment"]): return ERR_BAD if (Player[token]["ap"] < quest["cost"] and client_side): return ERR_BAD stdout("You can begin it!", 2) Player[token]["status"]=int(quest["quest_id"]) # client_side is an internal flag if client_side: #Player[token]["ap"]-=quest["cost"] player.update_ap(token, -(quest["cost"])) Battle[token]={ "world": bq["world"], "quest_id": quest["quest_id"], "difficulty": quest["difficulty"], "result": "", "bp": 0, "wave": 0, "max_wave": len(quest["waves"]), "enemy": [], "party": [], "party_id": int(bq["party_id"]), "spheres": [], "log": [], "turn": 1} stdout("Data set", 2) # Write the current wave enemies data advance_wave(token, bq["world"], quest["quest_id"], 0) # Write the current party (and their HP bar) for ux in Player[token]["party_"+str(int(bq["party_id"]))]: # If you don't have anyone here: Don't append to party if ux["unit_id"] == 0: continue #stdout("FOUND AN UX: %s" % str(ux)) # Un: Entry in unit database. Ux: Entry on party. # TODO: Maybe use dl_search instead for un in allunits: if (un["unit_id"] == ux["unit_id"]): #stdout("[BEGIN_QUEST] Match found for un and ux unit_id (un is: %s)" % str(un)) break else: continue # Wait, what? if (un["unit_id"] != ux["unit_id"]): print("Error, un and ux differs: %s vs %s" % (str(un["unit_id"]), str(ux["unit_id"]))) return ERR_ERR stdout("Preparing party member", 2) # How much HP/ATK bonus per level you'll get? # Each level boosts your stats in 1% lvl=int(Player[token]["inv"][ux["inv_id"]]["level"])-1 hpboost=int(un["hp"]*(max(lvl-1, 0)/100.0)) akboost=int(un["strength"]*(max(lvl-1, 0)/100.0)) # generate unit data for battle Battle[token]["party"].append({ "unit_id": un["unit_id"], "max_hp": un["hp"]+hpboost, "hp": un["hp"]+hpboost, "atk": un["strength"]+akboost, "ele": un["attribute"], "job": un["job"], "status_effects": 0 }) # Add 1~3 random spheres i=random.randint(1,3) while i > 0: i-=1 Battle[token]["spheres"].append(random.choice([ SPH_WIDEATTACK, SPH_PIERCE, SPH_ASSAULT, SPH_HEAL, SPH_HEALALL, SPH_ATKUP, SPH_DEFUP ])) # Add empty spheres to keep length while (len(Battle[token]["spheres"]) < 5): Battle[token]["spheres"].append(SPH_NONE) sjson=compress(Battle[token]) return sjson ######################################################### # Battle Action # Could use a rewrite/optimization def _action(token, bat, bt, spheres, idx, act=ACT_NONE): global Battle # The dead can't fight if (bat["hp"] <= 0): return # Will they use a sphere? stdout("Execute index %d" % idx, 2) if act == ACT_SPHERE: # Remove the sphere spheres[idx]=SPH_NONE sphere_attack(token, bat, Battle[token]["spheres"][idx], idx) Battle[token]["bp"]+=2 else: sphere_attack(token, bat, SPH_NONE, idx) Battle[token]["bp"]+=1 stdout("Attack performed", 2) # If HP ends up broken if (bat["hp"] > bat["max_hp"]): bat["hp"]=0+bat["max_hp"] return # Advance turn button. Main turn logic # Receives a list of units position, and if they'll use sphere: # { "unit": [uid1, uid2 ....], "sphere": [True/False array, in future will save skill as 2] } def battle(args, token): global allquests, Player, Battle # Data validation try: #stdout("Advance round: %s" % args) bt=json.loads(args) #stdout("JSON loaded") # Validation tmp=len(bt["unit"]) #stdout("vu: %d" % tmp) tmp=len(bt["sphere"]) #stdout("vu: %d" % tmp) tmp=len(bt["action"]) #stdout("vs: %d" % tmp) tmp=int(Player[token]["quest"]) # FIXME #stdout("ptq: %d pts: %d" % (tmp, Player[token]["status"])) if (Player[token]["status"] < ST_QUEST): return ERR_BAD #stdout("validation step 1") # Check if all members you've told us are valid """ for j in bt["unit"]: i=False for k in Battle[token]["party"]: if (k["unit_id"] == j["unit_id"]): i=True break if not i: stdout("Supplied data is valid: Attempt to add invalid members") raise Exception("Attempted to add invalid members") """ # Check if all spheres you've told us are valid # FIXME: bt["sphere"] should be bt["action"] if sorted(bt["sphere"]) != sorted(Battle[token]["spheres"]): stdout("Spheres differ: %s != %s" % (str(sorted(bt["sphere"])), str(sorted(Battle[token]["spheres"])))) raise Exception("Different spheres") except: # Invalid data traceback.print_exc() return ERR_BAD ####################################### # Reorder your party based on your request. We already know its valid Battle[token]["spheres"] = copy(bt["sphere"]) bt["sphere"] = bt["action"] """ tmp=[] for bat in Battle[token]["party"]: tmp.append(bat) Battle[token]["party"]=[] for j in bt["unit"]: for k in tmp: if (j["unit_id"] == k["unit_id"]): Battle[token]["party"].append(k) break """ # Erase temporary variables del tmp stdout("Party reordered (SKIPPED)", 2) Battle[token]["log"]=[] ####################################### # Prepare temporary data spheres=[] for i in Battle[token]["spheres"]: spheres.append(int(i)) print("Sphere list: "+str(spheres)) ####################################### # Friends ############ ## Spheres for i, j in enumerate(bt["action"]): try: bat = Battle[token]["party"][i] if j == ACT_SPHERE: _action(token, bat, bt, spheres, i, j) # HOLD THAT! Handle victory/defeat conditions check = conditions(token, spheres) if check is not None: return check except IndexError: pass except: traceback.print_exc() ## Skills for i, j in enumerate(bt["action"]): try: bat = Battle[token]["party"][i] if j == ACT_SKILL: _action(token, bat, bt, spheres, i, j) # HOLD THAT! Handle victory/defeat conditions check = conditions(token, spheres) if check is not None: return check except IndexError: pass except: traceback.print_exc() ## Normal Attacks for i, j in enumerate(bt["action"]): try: bat = Battle[token]["party"][i] if j == ACT_NONE: _action(token, bat, bt, spheres, i, j) # HOLD THAT! Handle victory/defeat conditions check = conditions(token, spheres) if check is not None: return check except IndexError: pass except: traceback.print_exc() ## Anything unrecognized is ignored stdout("Friends executed", 2) ############ # Enemies for idx, bat in enumerate(Battle[token]["enemy"]): # The dead can't fight if (bat["hp"] <= 0): continue # Simple Attack target_id=find_target(token, "party") stdout("ENEMY: Perform attack against %d" % target_id, 2) target=Battle[token]["party"][target_id] dmg=calc_dmg(token, bat, target, bat["atk"]) target["hp"]-=dmg Battle[token]["bp"]+=1 Battle[token]["log"].append(["enemy", idx, SPH_NONE, dmg, "party", target_id]) stdout("ENEMY: Attack performed", 2) # If HP ends up broken if (bat["hp"] > bat["max_hp"]): bat["hp"]=0+bat["max_hp"] continue # HOLD THAT! Handle victory/defeat conditions check = conditions(token, spheres) if check is not None: return check stdout("Enemies executed", 2) return battle_endturn(token, spheres) # Reload battle will only inform the client again of the battle status # If in a battle, otherwise it returns: ERR_BAD # TODO FIXME: Do not default to PID 1. Probably save it as a mask. # eg. PID + QID as string. So ST_QUEST becomes 11 (PID 1 QID 1) # FIXME: Token might have changed, maybe we should save to userid instead of token def reload_battle(token): if (Player[token]["status"] < ST_QUEST): return ERR_BAD try: bq=0+int(Battle[token]["quest_id"]) del bq return compress(Battle[token]) except: # FIXME: Detect world v1=begin_quest('{"world": "Main", "quest_id": %d, "party_id": 1}' % Player[token]["status"], token, client_side=False) stdout("Restarting quest...") return v1 ################################################# # Handles CI false positives in a lame way def pyflakes(): return allquests