diff options
author | Jesusaves <cpntb1@ymail.com> | 2020-12-18 18:52:58 -0300 |
---|---|---|
committer | Jesusaves <cpntb1@ymail.com> | 2020-12-18 18:52:58 -0300 |
commit | 228e91fe7e4f671dd593b5e288e812e287a65a00 (patch) | |
tree | c5e0f055c80a62d4abfbe80e24c8bbdbfaef1701 /battle | |
parent | 194af986ceeb72a2c0173e8e414b9e1a106495bf (diff) | |
download | server-228e91fe7e4f671dd593b5e288e812e287a65a00.tar.gz server-228e91fe7e4f671dd593b5e288e812e287a65a00.tar.bz2 server-228e91fe7e4f671dd593b5e288e812e287a65a00.tar.xz server-228e91fe7e4f671dd593b5e288e812e287a65a00.zip |
Initial battle version imported from Python2 and with pyflakes3 applied.
Divided in multiple files for ease. UNTESTED. (But server still runs)
Diffstat (limited to 'battle')
-rw-r--r-- | battle/__init__.py | 21 | ||||
-rw-r--r-- | battle/common.py | 126 | ||||
-rw-r--r-- | battle/main.py | 540 | ||||
-rw-r--r-- | battle/skills.py | 86 | ||||
-rw-r--r-- | battle/spheres.py | 79 | ||||
-rw-r--r-- | battle/summons.py | 110 |
6 files changed, 962 insertions, 0 deletions
diff --git a/battle/__init__.py b/battle/__init__.py new file mode 100644 index 0000000..90240ea --- /dev/null +++ b/battle/__init__.py @@ -0,0 +1,21 @@ +######################################################################################## +# This file is part of Spheres. +# Copyright (C) 2019 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 +######################################################################################## +# No idea what I'm doing + + diff --git a/battle/common.py b/battle/common.py new file mode 100644 index 0000000..ec0d9d7 --- /dev/null +++ b/battle/common.py @@ -0,0 +1,126 @@ +######################################################################################## +# This file is part of Spheres. +# Copyright (C) 2019 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 - Common functions +from utils import stdout +from consts import (SC_ATKUP, SC_DEFDOWN, SC_DEFUP, SC_ATKDOWN, Ele_Fire, + Ele_Water, Ele_Nature, Ele_Light, Ele_Shadow) +import random +# Check victory condition +def check_enemy_alive(token): + for i in Battle[token]["enemy"]: + if (i["hp"] > 0): + return True + return False + +# Check defeat condition +def check_player_alive(token): + for i in Battle[token]["party"]: + if (i["hp"] > 0): + return True + return False + +# Calculate damage. Unit is a member from Battle[token][scope]. Base is dmg value +# crit is a customizable critical chance +def calc_dmg(token, attacker, defender, base, crit=0.1): + sca=attacker["status_effects"] + scd=defender["status_effects"] + dmg=0+base + + # ATK/DEF up will double or not + if (sca & SC_ATKUP): + dmg*=2 + if (scd & SC_DEFDOWN): + dmg*=2 + if (scd & SC_DEFUP): + dmg/=2 + if (sca & SC_ATKDOWN): + dmg/=2 + + # Critical chance (Crit: +50% DMG) + if (random.random() < crit): + dmg*=1.5 + + # Elemental advantage: 30% + ea=attacker["ele"] + ed=defender["ele"] + if (ea == Ele_Fire and ed == Ele_Water): + dmg*=0.7 + elif (ea == Ele_Fire and ed == Ele_Nature): + dmg*=1.3 + elif (ea == Ele_Water and ed == Ele_Nature): + dmg*=0.7 + elif (ea == Ele_Water and ed == Ele_Fire): + dmg*=1.3 + elif (ea == Ele_Nature and ed == Ele_Water): + dmg*=0.7 + elif (ea == Ele_Nature and ed == Ele_Fire): + dmg*=1.3 + elif (ea == Ele_Light and ed == Ele_Shadow): + dmg*=1.3 + elif (ea == Ele_Shadow and ed == Ele_Light): + dmg*=1.3 + + # Impossible to move or miss is handled before + return int(dmg) + +# Attack all, scope can be: "enemy" or "party" +def attackall(token, atker, scope): + for i in Battle[token][scope]: + if (i["hp"] < 0): + continue + i["hp"]-=calc_dmg(token, atker, i, atker["atk"], 0.0) + + # If passed with a negative value... + if (i["hp"] > i["max_hp"]): + i["hp"]=0+i["max_hp"] + return False + +# Find and return target ID in Battle[token][scope]. +def find_target(token, scope): + global Battle + targets=[] + i=-1 + #stdout("Request to find a target on scope %s" % scope) + #stdout("Length is: %d" % len(Battle[token][scope])) + while (i+1) < len(Battle[token][scope]): + i+=1 + try: + if (Battle[token][scope][i]["hp"] < 0): + continue + except: + try: + print("(%d) Faulty structure: %s" % (i, str(Battle[token][scope][i]))) + except: + print("(%d) TOTALLY Faulty structure: %s" % (i, str(Battle[token][scope]))) + continue + continue + targets.append(i) + + #stdout("While loop has finished") + stdout("List of targets: %s" % str(targets)) + + # When a player selects an target, it should always be the first enemy in list + if scope == "enemy": + opt=targets[0] + else: + opt=random.choice(targets) + stdout("Selected: %d" % opt) + return opt + + diff --git a/battle/main.py b/battle/main.py new file mode 100644 index 0000000..10b8cdf --- /dev/null +++ b/battle/main.py @@ -0,0 +1,540 @@ +######################################################################################## +# This file is part of Spheres. +# Copyright (C) 2019 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, dl_search, compress, allunits +from consts import (CRYSTAL_MIN, CRYSTAL_MAX, EXPRATE_MIN, EXPRATE_MAX, + GPPRATE_MIN, GPPRATE_MAX, ERR_ERR, ERR_BAD, ST_TOWN, ST_QUEST, ACT_SPHERE, + SFLAG_CLEARGEMS, SFLAG_DOUBLEGEMS, SFLAG_SPECIAL, + SFLAG_FIRSTLOOT, SFLAG_DOUBLEEXP, SFLAG_DOUBLEGP, + SPH_WIDEATTACK, SPH_PIERCE, SPH_ASSAULT, SPH_HEAL, SPH_HEALALL, SPH_ATKUP, + SPH_DEFUP, SPH_NONE) +import json, random +import player +from battle.common import (find_target, check_enemy_alive, check_player_alive, + calc_dmg) +from battle.spheres import sphere_attack +#from battle.summons import summon # <-- Protocol? +#from battle.skills import skill_core, handle_skill # <-- WIP + +# Prepare a randomness seed +random.seed() + +############################################# +# Structural functions +############################################# +# Update enemy list with the next wave data. No checks are conducted. +def advance_wave(token, quest_id, next_wave): + global Battle, allquests + Battle[token]["enemy"]=[] + stdout("advance_wave was called") + + quest=dl_search(allquests, "quest_id", quest_id) + if quest == "ERROR": + print("ERROR, INVALID QUEST") + # TODO: HANDLE THIS ERROR (FIXME) + + for en in quest["waves"][next_wave]: + mil=quest["difficulty"]/10.0 + if (en["boss"]): + mil*=5.5 + + mil=mil + stdout("Recording new enemy with mil: %d" % mil) + Battle[token]["enemy"].append({ + "name": en["name"], + "unit_id": en["sprite"], + "max_hp": int(1000*mil), + "hp": int(1000*mil), + "atk": int(100*mil), + "ele": en["attribute"], + "status_effects": 0 + }) + + # Update wave + stdout("Advancing wave") + Battle[token]["wave"]+=1 + return True + +# get_result(str, bool, int) +def get_result(token, victory, quest_id): + global Player, Battle, allquests + result={ + "result": "", + "gp": 0, + "exp": 0, + "crystals": 0, + "loot": [], + "rank": 0 + } + + stdout("GR: Begin") + # You lost? + if not victory: + result["result"]="DEFEAT" + return result + + # Prepare data + result["result"]="VICTORY" + quest=dl_search(allquests, "quest_id", quest_id) + if quest == "ERROR": + print("ERROR, INVALID QUEST") + stdout("Quest %d is invalid", str(quest_id)) + # TODO: HANDLE THIS ERROR (FIXME) + return result + + stdout("GR: Rolling") + # Roll each wave + for wave in quest["waves"]: + for en in wave: + # Roll GP for each wave + if en["boss"]: + result["gp"]+=quest["difficulty"]*30 + result["exp"]+=quest["difficulty"]*50 + else: + result["gp"]+=quest["difficulty"]*10 + result["exp"]+=quest["difficulty"]*10 + + # TODO: Roll the loots for every enemy in every death + + stdout("GR: Looting") + # For now, loots are rolled for quest + # 2- Roll loot list + for loot, chance in quest["loot"]: + if (random.randint(0, 10000) < chance): + loot=int(loot) + # Crystals are... Tricky + if loot >= CRYSTAL_MIN and loot <= CRYSTAL_MAX: + result["crystals"]+=(loot-CRYSTAL_MIN) + elif loot >= EXPRATE_MIN and loot <= EXPRATE_MAX: + result["exp"]*=(loot-EXPRATE_MIN)/1000.0 + result["exp"]=int(result["exp"]) + elif loot >= GPPRATE_MIN and loot <= GPPRATE_MAX: + result["gp"]*=(loot-GPPRATE_MIN)/1000.0 + result["gp"]=int(result["gp"]) + else: + result["loot"].append(int(loot)*100) # Fix Unit ID from base to ID + + stdout("GR: Flagging") + # Mark the quest as complete and grant you crystals for first clear + # But this is based on the flags (we can have special quests) + if (not (quest["flags"] & SFLAG_SPECIAL) and Player[token]["quest"] < quest["quest_id"]): + Player[token]["quest"]=0+quest["quest_id"] + if (quest["flags"] & SFLAG_CLEARGEMS): + result["crystals"]+=100 + if (quest["flags"] & SFLAG_DOUBLEGEMS): + result["crystals"]+=200 + if (quest["flags"] & SFLAG_FIRSTLOOT): + loot=int(quest["loot"][0][0]) + # Crystals are... Tricky + if loot >= CRYSTAL_MIN and loot <= CRYSTAL_MAX: + result["crystals"]+=(loot-CRYSTAL_MIN) + else: + result["loot"].append(int(loot)*100) # Fix Unit ID from base to ID + if (quest["flags"] & SFLAG_DOUBLEEXP): + result["exp"]*=2 + if (quest["flags"] & SFLAG_DOUBLEGP): + result["gp"]*=2 + + stdout("GR: Applying") + # Apply the results to player data + Player[token]["gp"]+=result["gp"] + Player[token]["exp"]+=result["exp"] + Player[token]["crystals"]+=result["crystals"] + for it in result["loot"]: + ix=player.inventoryplace(token) + if (ix >= 0): + unit={ + "unit_id": it, + "level": 1, + "exp": 0 + } + Player[token]["inv"][ix]=unit + else: + result["loot"].remove(it) + # TODO: Send result ERR_FULL + + stdout("GR: EXPing") + # Grant to party the same amount of experience + pid=Battle[token]["party_id"] + for m in Player[token]["party_"+str(pid)]: + # This can happen normally + if m["unit_id"] <= 0: + continue + player.grant_exp(token, m["inv_id"], result["exp"]) + v5=player.check_level_up(token, m["inv_id"]) + # FIXME: Don't disregard result + stdout("Unit %d levelled up %d times" % (m["inv_id"], v5)) + + + stdout("GR: Ranking") + # Player rank up + rk=player.check_rank_up(token) + if rk["code"]: + result["rank"]=1+rk["ap"] + + stdout("GR: Complete") + + # Send data + return result + +############################################# +# Client commands +############################################# +# begin_quest( 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") + # Validation + tmp=int(bq["quest_id"]) + tmp=int(bq["party_id"]) + if (tmp < 1 or tmp > 3): + return ERR_BAD + tmp=int(Player[token]["quest"]) + for quest in allquests: + 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") + except: + # 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!") + + + 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]={ + "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": [], + "turn": 1} + + stdout("Data set") + # Write the current wave enemies data + advance_wave(token, 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") + + # How much HP/ATK bonus per level you'll get? + lvl=int(Player[token]["inv"][ux["inv_id"]]["level"])-1 + hpboost=player.readjust_status(un["rare"], un["job"], True)*max(lvl-1, 0) + akboost=player.readjust_status(un["rare"], un["job"], False)*max(lvl-1, 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"], + "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 + + + +# This is separate for loop logic reasons. Ends the turn. +# Gives sphere, handle rewards if needed and sends data to client +def battle_endturn(token, spheres): + global Battle + # We now have to handle turn end and conditions + Battle[token]["turn"]+=1 + stdout("Turn has ended") + + # Resave spheres + stdout("Sphere list: "+str(spheres)) + Battle[token]["spheres"]=[] + for i in spheres: + Battle[token]["spheres"].append(int(i)) + + # Remove an eventual none spheres + while (Battle[token]["spheres"].count(SPH_NONE)): + Battle[token]["spheres"].remove(SPH_NONE) + stdout("Exceeding spheres removed") + + # 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 + ])) + + stdout("Spheres added; Status before adjust: %s" % (str(Battle[token]["spheres"]))) + + # Remove exceeding spheres + if (len(Battle[token]["spheres"]) > 5): + del(Battle[token]["spheres"][4:]) + + # Add empty spheres to keep length + while (len(Battle[token]["spheres"]) < 5): + Battle[token]["spheres"].append(SPH_NONE) + + # Delete Summon data if exists + try: + del Battle["s"] + except: + pass + + # Send data to client + stdout("Sending data") + sjson=compress(Battle[token]) + stdout("Data sent: %s" % sjson) + return sjson + +# 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("vs: %d" % tmp) + tmp=int(Player[token]["quest"]) + 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") + except: + # Invalid data + return ERR_BAD + + ####################################### + # Reorder your party based on your request. We already know its valid + + 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 + tmp=[] + stdout("Party reordered") + + ####################################### + # Prepare temporary data + spheres=[] + for i in Battle[token]["spheres"]: + spheres.append(int(i)) + print("Sphere list: "+str(spheres)) + + ####################################### + # TODO: determine battle order + + ############ + # Friends + for bat in Battle[token]["party"]: + # The dead can't fight + if (bat["hp"] <= 0): + continue + + # Will you use a sphere? + idx=Battle[token]["party"].index(bat) + stdout("Check index %d" % idx) + if (bt["sphere"][idx] == ACT_SPHERE): + # Remove the sphere + spheres[idx]=SPH_NONE + sphere_attack(token, bat, Battle[token]["spheres"][idx]) + Battle[token]["bp"]+=2 + else: + sphere_attack(token, bat, SPH_NONE) + Battle[token]["bp"]+=1 + stdout("Attack performed") + + # If HP ends up broken + if (bat["hp"] > bat["max_hp"]): + bat["hp"]=0+bat["max_hp"] + + # HOLD THAT! Handle event + if (not check_enemy_alive(token)): + # You won! Do we have a next wave? + stdout("You won!") + if (Battle[token]["wave"] < Battle[token]["max_wave"]): + stdout("Next wave detected: %s, Quest ID: %s\nWave: %s" % (token, Battle[token]["quest_id"], Battle[token]["wave"])) + advance_wave(token, Battle[token]["quest_id"], Battle[token]["wave"]) + # Do not continue this loop: End the turn now + return battle_endturn(token, spheres) + else: + # You won! + stdout("Total Victory") + Player[token]["status"]=ST_TOWN + # TODO: enemy die = add reward + result=get_result(token, True, Battle[token]["quest_id"]) + del Battle[token] + return compress(result) + + stdout("Friends executed") + + # It never harms checking for suicide... + if (not check_player_alive(token)): + # You lost! + Player[token]["status"]=ST_TOWN + result=get_result(token, False, Battle[token]["quest_id"]) + del Battle[token] + return compress(result) + + ############ + # Enemies + for bat in 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) + target=Battle[token]["party"][target_id] + target["hp"]-=calc_dmg(token, bat, target, bat["atk"]) + Battle[token]["bp"]+=1 + + stdout("ENEMY: Attack performed") + + # If HP ends up broken + if (bat["hp"] > bat["max_hp"]): + bat["hp"]=0+bat["max_hp"] + continue + + # HOLD THAT! Handle event + if (not check_player_alive(token)): + # You lost! + Player[token]["status"]=ST_TOWN + result=get_result(token, False, Battle[token]["quest_id"]) + del Battle[token] + return compress(result) + + + stdout("Enemies executed") + 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) +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: + v1=begin_quest('{"quest_id": %d, "party_id": 1}' % Player[token]["status"], token, client_side=False) + stdout("Restarting quest...") + return v1 + + diff --git a/battle/skills.py b/battle/skills.py new file mode 100644 index 0000000..180ec9c --- /dev/null +++ b/battle/skills.py @@ -0,0 +1,86 @@ +######################################################################################## +# This file is part of Spheres. +# Copyright (C) 2019 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 - Skills +from utils import dbgprint +from consts import (SK_SINGLE_DMG, SK_MULTI_DMG, SK_ATK_UP, SK_DEF_UP, + SK_SINGLE_HEAL, SK_MULTI_HEAL, SC_ATKUP, SC_DEFUP) +from battle.common import find_target, calc_dmg, attackall +def skill_core(token, mask, force, element): + global Battle + + # Create a fake unit + dummy={ + "name": "Dummy", + "unit_id": 1, + "max_hp": 1, + "hp": 1, + "atk": force, + "ele": element, + "status_effects": 0 + } + + # Handle the mask to determine the action + # TODO: SK_CLEAR_SC, SK_RESSURECTION + if mask & SK_SINGLE_DMG: + # Single Damage skill: 1 strength = 1 damage + target_id=find_target(token, "enemy") + dbgprint("Enemy selected: %d" % target_id) + target=Battle[token]["enemy"][target_id] + target["hp"]-=calc_dmg(token, dummy, target, dummy["atk"]) + + if mask & SK_MULTI_DMG: + # Multi Damage skill: 1 strength = 1 damage + attackall(token, dummy, "enemy") + + if mask & SK_ATK_UP: + # Attack up skill: Add SC_ATKUP to all members (even dead ones) + for target in Battle[token]["party"]: + if not (target["status_effects"] & SC_ATKUP): + target["status_effects"]=target["status_effects"]|SC_ATKUP + + if mask & SK_DEF_UP: + # Defense up skill: Add SC_DEFUP to all members (even dead ones) + for target in Battle[token]["party"]: + if not (target["status_effects"] & SC_DEFUP): + target["status_effects"]=target["status_effects"]|SC_DEFUP + + # TODO: Is single heal the same as self heal? + if mask & SK_SINGLE_HEAL: + # Heal skill: 1 strength = 1% healing (FIXME?) + target_id=find_target(token, "party") + target=Battle[token]["party"][target_id] + target["hp"]+=int(target["max_hp"]*dummy["atk"]/100) + if (target["hp"] > target["max_hp"]): + target["hp"]=0+target["max_hp"] + + if mask & SK_MULTI_HEAL: + # Heal skill: 1 strength = 1 HP (FIXME?) + dummy["atk"]*=-1 + attackall(token, dummy, "party") + + # Victory/Defeat checks are not handled here + # This doesn't returns anything, actually. + # Errors must be handled as Exceptions + return + +def handle_skill(token, skill_id): + global Battle + # TODO + return + diff --git a/battle/spheres.py b/battle/spheres.py new file mode 100644 index 0000000..aa2e756 --- /dev/null +++ b/battle/spheres.py @@ -0,0 +1,79 @@ +######################################################################################## +# This file is part of Spheres. +# Copyright (C) 2019 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 - Spheres +from utils import stdout, Battle +from consts import (SPH_WIDEATTACK, SPH_PIERCE, SPH_ASSAULT, SPH_HEAL, SPH_HEALALL, + SPH_NONE, SPH_ATKUP, SPH_DEFUP, SC_ATKUP, SC_DEFUP) +from battle.common import find_target, attackall, calc_dmg + +# Handle sphere attacks. Unit is a member from Battle[token][scope] +def sphere_attack(token, unit, sphere): + stdout("%s is attacking" % str(unit)) + stdout("Sphere selected: %d" % sphere) + + # Select the enemy which should be attacked + target_id=find_target(token, "enemy") + stdout("Enemy selected: %d" % target_id) + target=Battle[token]["enemy"][target_id] + + # Now cycle though spheres + if (sphere == SPH_WIDEATTACK): + # Wide Attack sphere + attackall(token, unit, "enemy") + elif (sphere == SPH_PIERCE): + # Pierce sphere (100% critical chance) + target["hp"]-=calc_dmg(token, unit, target, unit["atk"], 1.0) + elif (sphere == SPH_ASSAULT): + # Assault sphere (50% critical chance) + target["hp"]-=calc_dmg(token, unit, target, unit["atk"], 0.5) + elif (sphere == SPH_HEAL): + # Heal sphere (30% healing). Need to redefine target + target_id=find_target(token, "party") + target=Battle[token]["party"][target_id] + target["hp"]+=int(target["max_hp"]*0.3) + if (target["hp"] > target["max_hp"]): + target["hp"]=0+target["max_hp"] + elif (sphere == SPH_HEALALL): + # Heal All (negative damage to party) + unit["atk"]*=-1 + attackall(token, unit, "party") + unit["atk"]*=-1 + elif (sphere == SPH_NONE): + # Normal Attack + stdout("It's a normal attack!") + target["hp"]-=calc_dmg(token, unit, target, unit["atk"]) + elif (sphere == SPH_ATKUP): + # Attack up sphere: Add SC_ATKUP to all members (even dead ones) + for target in Battle[token]["party"]: + if not (target["status_effects"] & SC_ATKUP): + target["status_effects"]=target["status_effects"]|SC_ATKUP + elif (sphere == SPH_DEFUP): + # Defense up sphere: Add SC_DEFUP to all members (even dead ones) + for target in Battle[token]["party"]: + if not (target["status_effects"] & SC_DEFUP): + target["status_effects"]=target["status_effects"]|SC_DEFUP + else: + # Something went wrong, report and do nothing + print ("\033[1;31mERROR, INVALID SPHERE ID: %d\033[0m\n" % sphere) + return False + + # We are done! + #stdout("Attack completed") + return True + diff --git a/battle/summons.py b/battle/summons.py new file mode 100644 index 0000000..ec30868 --- /dev/null +++ b/battle/summons.py @@ -0,0 +1,110 @@ +######################################################################################## +# This file is part of Spheres. +# Copyright (C) 2019 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 - Summons +import json +from utils import dbgprint, compress, allsummons, stdout, dl_search +from consts import (ERR_ERR, ERR_BAD, ST_TOWN) +from battle.skills import skill_core +from battle.common import check_enemy_alive, check_player_alive +from battle.main import advance_wave, battle_endturn, get_result + +def handle_summon(token, summon): + global Battle, Player + + try: + # Summon strength is based on player rank + force=summon["strength"]*Player[token]["level"] + + # Cast the skill, return error 500 on exception + skill_core(token, summon["type"], force, summon["attribute"]) + except: + return ERR_ERR + + # Check for victory/defeat conditions + # Huge code duplication + + dbgprint("Summon: Victory/Defeat Check") + # HOLD THAT! Handle event + if (not check_enemy_alive(token)): + # You won! Do we have a next wave? + dbgprint("You won!") + if (Battle[token]["wave"] < Battle[token]["max_wave"]): + dbgprint("Next wave detected: %s, Quest ID: %s\nWave: %s" % (token, Battle[token]["quest_id"], Battle[token]["wave"])) + advance_wave(token, Battle[token]["quest_id"], Battle[token]["wave"]) + # Do not continue this loop: End the turn now + return battle_endturn(token, Battle[token]["spheres"]) + else: + # You won! + dbgprint("Total Victory") + Player[token]["status"]=ST_TOWN + # TODO: enemy die = add reward + result=get_result(token, True, Battle[token]["quest_id"]) + del Battle[token] + return compress(result) + + # It never harms checking for suicide... + if (not check_player_alive(token)): + # You lost! + Player[token]["status"]=ST_TOWN + result=get_result(token, False, Battle[token]["quest_id"]) + del Battle[token] + return compress(result) + + # The check is: if not ERR_ERR or ERR_BAD, then reload battle/result + sjson=compress(Battle[token]) + return sjson + + +def summon(args, token): + # Data validation + try: + stdout("Summon: %s" % args) + ss=json.loads(args) + # [summon_id] + # Validation + summon_id=int(ss[0]) + + # Create summon object + summon=dl_search(allsummons, "summon_id", summon_id) + if summon == "ERROR": + stdout("ERROR, INVALID SUMMON ID %d" % summon_id) + raise Exception("Invalid summon") + + # Verify the cost + if Battle[token]["bp"] < summon["cost"]: + stdout("Cannot summon \"%s\": Insufficient BP (%d/%d)" % (summon["name"], Battle[token]["bp"], summon["cost"])) + raise Exception("Insufficient BP") + + stdout("All fine thus far") + except: + # Invalid data + return ERR_BAD + + # Already summoned (reverse logic) + try: + Battle["s"]+=1 + Battle["s"]-=1 + return ERR_BAD + except: + pass + + Battle[token]["bp"]-=summon["cost"] + Battle["s"]=1 + return handle_summon(token, summon) + |