summaryrefslogblamecommitdiff
path: root/battle/main.py
blob: d104db835cdb455612c11dd45d707b2ea55690e4 (plain) (tree)


















                                                                                        

                                                                       
                                                                              
                        
                              
             

                                                                           


                                                             
                     



                           



                                             
                                          





                                               
                                




                                


                                               



                                                     
                                      
           
                                         











                                                                       
                                  








                                                  
                             









                                          
                  

                  
                         
                                         
                                                          





















                                                                                                  
                                           

                                                     
                                            
                                                              

                                                         

































                                                       






                                                                                             
                                           
                           
                              

                           
                               
                             

                               
                               
                                               
                                                                   

                                                
                                    

                                                       
           








                                                                                
           

                                                       


                                                                                                                   

                      
                             



                                                                         

                                                 
 
       










                                                
       

                               
           
                                          
                           



















                                             
                                         


                                             
                                                                         

                                  
                                                    
                                  
                                     




                                       



                                                     
 
                                 
 

                
                                                      





                                             
                                                                 
                                                

                                                    

                              
                                                                                      
                                            





                                       



                                                     

 
                                 





                                                                      
                                                                                  








                                            

                                                                                                                              



                                     




                                                 
########################################################################################
#     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, compress, allunits, Player, Battle, allquests
from consts import (ERR_ERR, ERR_BAD, ST_TOWN, ST_QUEST, ACT_SPHERE,
    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.summons import summon # <-- Protocol?
#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"],
            "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



# 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))

    #######################################
    # 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, 2)
        if (bt["sphere"][idx] == 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"]

        # HOLD THAT! Handle victory/defeat conditions
        check = conditions(token, spheres)
        if check is not None:
            return check

    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