summaryrefslogblamecommitdiff
path: root/game/battle.rpy
blob: fa93370a2266b21b844a6618e8c194a1c1878c5f (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
########################################################################################
# Combat Functions
init python:
    ######################## Server Communications
    def reload_battle():
        raw=send_packet("reload_battle", "")

        bt=json_decode(raw)
        if (bt == ERR_JSONDECODER):
            return ERR_JSONDECODER
        return bt

    def bt_anim(e0, e1, st):
        global czoom_p1, czoom_p2, czoom_p3, czoom_p4
        global czoom_e1, czoom_e2, czoom_e3
        if e0 == "party":
            if e1 == 0:
                czoom_p1 = copy.copy(st)
            elif e1 == 1:
                czoom_p2 = copy.copy(st)
            elif e1 == 2:
                czoom_p3 = copy.copy(st)
            elif e1 == 3:
                czoom_p4 = copy.copy(st)
            else:
                stdout("Invalid party source: %d" % e1)
        elif entry[0] == "enemy":
            if e1 == 0:
                czoom_e1 = copy.copy(st)
            elif e1 == 1:
                czoom_e2 = copy.copy(st)
            elif e1 == 2:
                czoom_e3 = copy.copy(st)
            else:
                stdout("Invalid enemy source: %d" % e1)

    def bt_handlelog(entry):
        global bt_memory
        ## Animate action caster
        bt_anim(entry[0], entry[1], czoom_on)

        ## "Consume" the sphere (will be reset anyway)
        if entry[0] == "party" and entry[2] > SPH_NONE and entry[2] < SRV_SKILL:
            Battle["spheres"][entry[1]] = SPH_NONE

        ## Execute actions
        if entry[2] in [SRV_NOCAST]:
            bt_memory.append([entry[3], entry[4], entry[5]])
        elif entry[2] in [SPH_WIDEATTACK, SRV_SKILL]:
            for en in bt_memory:
                bt_handlelog([entry[0], entry[1], SPH_NONE, en[0], en[1], en[2]])
            bt_memory = []
        elif entry[2] in [SPH_NONE, SPH_PIERCE, SPH_ASSAULT, SPH_HEAL]:
            ## Play audio (WIP)
            ## FIXME: Play -2 versions as well, randomly
            try:
                audi="sfx/w/%d-1.ogg" % Battle[entry[0]][entry[1]]["job"]
            except:
                audi="invalid job on source"
            if renpy.loadable(audi):
                renpy.play(audi)
            else:
                print("WARNING: Audio file \"%s\" does not exist" % audi)
            ## Animate
            hit_someone_verbose(Battle[entry[4]][entry[5]],
            entry[3])
            sdelay(0.1)
            print("Attacked Battle[%s][%d]" % (entry[4], entry[5]))
        elif entry[2] == SRV_SUMMON:
            # FIXME
            print("Summoned %d to your aid! Show cutscene" % entry[3])
            show_img("summon_%d" % entry[3], False)
            renpy.show("summon_%d" % entry[3], at_list=[truecenter, tzoomin], tag="summon", zorder=29150, layer="overlay")
            renpy.pause(2.0)
            renpy.hide("summon", layer="overlay")
            #renpy.with_statement(Dissolve(1.0))

        ## De-Animate action caster
        bt_anim(entry[0], entry[1], czoom_dd)
        return

label combat:
    # Implement combat view
    $stdout("================= prepare for combat")
    $ battle_mode = True
    $ hud_clear()
    $ show_img("bg %s" % TMP_BACKG, False)
    scene bg battle
    # Load variables
    python:
        try:
            fx1="unit_"+str(Battle["party"][0]["unit_id"])
            show_img(fx1, False) # Validate
        except:
            fx1=""
        try:
            fx2="unit_"+str(Battle["party"][1]["unit_id"])
            show_img(fx2, False) # Validate
        except:
            fx2=""
        try:
            fx3="unit_"+str(Battle["party"][2]["unit_id"])
            show_img(fx3, False) # Validate
        except:
            fx3=""
        try:
            fx4="unit_"+str(Battle["party"][3]["unit_id"])
            show_img(fx4, False) # Validate
        except:
            fx4=""
        try:
            fx5="unit_"+str(Battle["party"][4]["unit_id"])
            show_img(fx5, False) # Validate
        except:
            fx5=""
        try:
            en1=Battle["enemy"][0]
            show_img("mob_"+str(en1["unit_id"]), False) # Validate
        except:
            en1=copy.copy(ERR_MOBSTRUCT)
        try:
            en2=Battle["enemy"][1]
            show_img("mob_"+str(en2["unit_id"]), False) # Validate
        except:
            en2=copy.copy(ERR_MOBSTRUCT)
        try:
            en3=Battle["enemy"][2]
            show_img("mob_"+str(en3["unit_id"]), False) # Validate
        except:
            en3=copy.copy(ERR_MOBSTRUCT)

        while len(Battle["party"]) < 4:
            Battle["party"].append(copy.copy(ERR_PLAYERSTRUCT))
    $stdout("================= call begin")

    # TODO: Swap units support
    # TODO: Display Spheres in the right place
    $ use_sphere=[AP_NONE, AP_NONE, AP_NONE, AP_NONE, AP_NONE]
    $ btl_ready = False
    $ do_action = ACT_NONE
    $ csid = None
    $ bt_memory = []
    python:
        try:
            do_action = renpy.call_screen("battle", _with_none=True)
        except:
            traceback.print_exc()
            stdout("FAILED to render battle screen, trying again...")
            renpy.hide_screen("battle")
            sdelay(1.5)
            if (debug):
                raise
            renpy.jump("combat")

        stdout("================= call ended")
        btl_ready=True
        renpy.show_screen("battle")

    # TODO: Send to server new arrangement of units

    # Update Battle with server response
    if (do_action == ACT_TURN):
        $ response=loadbattle(Battle["party"])
    elif (do_action == ACT_SUMMON):
        $ response=loadsummon()
    else:
        $ stdout("INVALID ACTION CODE: %d" % do_action)
        jump combat

    # Maybe nothing happened
    if response == ERR_SUMMON:
        $ response = Battle

    # Maybe we error'ed out and should relog
    if (response in [FAILUREMSG, OFFLINEMSG, ERR_JSONDECODER, ERR_LOGIN_DEFAULT]):
        $ renpy.call_screen("msgbox", "Error:\n\n%s" % response)
        # Try again (x2) (FIXME: If packet was malformated, might be a bad idea)
        $ response=loadbattle(Battle["party"])
        # Maybe we error'ed out and should relog
        if (response in [FAILUREMSG, OFFLINEMSG, ERR_JSONDECODER, ERR_LOGIN_DEFAULT]):
            $ renpy.call_screen("msgbox", "Error:\n\n%s" % response)
            # Try again (x3)
            $ response=loadbattle(Battle["party"])
            # Maybe we error'ed out and should relog
            if (response in [FAILUREMSG, OFFLINEMSG, ERR_JSONDECODER, ERR_LOGIN_DEFAULT]):
                $ renpy.call_screen("msgbox", "Error:\n\n%s\nYou'll be taken to main screen." % response)
                # Better no longer insist
                return


    # The server should send a JSON of results...
    # Which is being stored as `log` field in an array
    # [who, id, what, damage, target, id]
    # [  0,  1,    2,      3,      4,  5]
    python:
        print str(response["log"])
        print len(response["log"])
        for entry in response["log"]:
            # The most important is entry[2], which contains the action
            # Oh noes - I forgot the damage -.-
            try:
                print("ACTION %d (DMG %d)" % (entry[2], entry[3]))
                # TODO: Highlight caster
                # TODO: Unmark cards
                # TODO: SRV_WAVE (ends waves) SRV_SPHERE (updates spheres)
                # TODO: SRV_NONE (saves action in tmp memory)
                # (then at next non-none, bt_handlelog() tmp memory)
                bt_handlelog(entry)
            except:
                traceback.print_exc()

        ## Not everything can be calculated from the logs...
        ## Legacy render system cannot handle finished battles
        if (response["result"] == ""):
            stdout("Extrapolating...")
            # Extrapolate & Render enemy damage
            idx=0
            while idx < len(Battle["enemy"]):
                try:
                    # TODO: Draw the updates in "sequence"
                    if (response["wave"] != Battle["wave"]):
                        hit_someone_verbose(Battle["enemy"][idx],
                        Battle["enemy"][idx]["hp"])
                    else:
                        hit_someone_verbose(Battle["enemy"][idx],
                        Battle["enemy"][idx]["hp"]-response["enemy"][idx]["hp"])
                    idx+=1
                    renpy.pause(0.1)
                except:
                    idx+=1
                    pass

            # Extrapolate & Render party damage
            idx=0
            try:
                while idx < len(Battle["party"]):
                    hit_someone_verbose(Battle["party"][idx], Battle["party"][idx]["hp"]-response["party"][idx]["hp"])
                    idx+=1
                    renpy.pause(0.1)
            except:
                idx+=1
                pass

    # Maybe we received the victory code
    if (response["result"] != ""):
        $ renpy.free_memory()
        # TODO: Announce the result screen
        jump results

    # We may be going to boss fight
    if (response["wave"] != Battle["wave"] and response["wave"] == response["max_wave"]):
        play music MUSIC_BOSS.id() fadein 0.5
        $ renpy.notify("BOSS FIGHT!")

    # Continue the combat
    $ Battle=response
    $ Battle["log"]=[]
    jump combat

label results:
    $ battle_mode = False
    $ Player["status"]=ST_TOWN
    if (response["result"] == "DEFEAT"):
        $ renpy.call_screen("msgbox", "Result:\n%s" % _("DEFEAT"))
        jump restore

    play music MUSIC_VICTORY.id() fadein 0.5

    # TODO: Use small icons
    python:
        # Save current quest to 'quest' variable (wasn't it set before?)
        quest=dl_search(allquests[Battle["world"]], "quest_id", Battle["quest_id"])
        if (quest == ERR_INVALID):
            raise Exception("ERROR, QUEST NOT FOUND, %d" % Battle["quest_id"])

        # Update other data
        Player["crystals"]+=int(response["crystals"])
        Player["gp"]+=int(response["gp"])
        Player["exp"]+=int(response["exp"])
        # Write loot array
        loot="\n"
        if (response["crystals"]):
            loot+="%d Crystals\n" % response["crystals"]
        for unit in response["loot"]:
            try:
                loot+="%s %s\n" % (star_write(allunits[unit]["rare"]), allunits[unit]["name"])
            except:
                loot+="error"

        # Maybe you ranked up?
        expmsg="Exp: %d -> %d" % (Player["exp"]-response["exp"], Player["exp"])
        if (response["rank"]):
            Player["level"]+=1
            Player["max_ap"]+=response["rank"]-1
            # TODO: Update exp/max_exp fields
            update_ap()
            #Player["exp"]+=1
            expmsg="PLAYER {b}RANK UP!{/b} (Max Ap: %d -> %d)" % (Player["max_ap"]-response["rank"]+1, Player["max_ap"])
            Player["exp"]=response["rk_exp"]
            Player["max_exp"]=response["rk_mexp"]

        # Report screen
        try:
            renpy.call_screen("msgbox", "Result:\n%s\n\nGp: %d\n%s\nLoot:\n%s" % (
                                    _("VICTORY!"),
                                    response["gp"],
                                    expmsg,
                                    "{size=12}"+loot+"{/size}"
            ))
        except:
            stdout("ERROR SHOWING VICTORY BOX")
            pass

    # Update inventory data and restore
    $ inv=get_inventory()
    python:
        try:
            renpy.call_screen("msgbox", "Error: %d" % int(inv))
        except:
            Player["inv"]=dlist()
            for a in inv:
                Player["inv"].append(a)

        qid=Battle["quest_id"]
        # WAIT THERE! Perhaps we have some sort of history to show?!
        # TODO: Memory
        if Player["quest"] < qid:
            story=dl_search(allstory, "quest_id", qid)

            if (story != ERR_INVALID):
                try:
                    renpy.hide_screen("battle")
                except:
                    stdout("Failed to hide screen battle D:")
                    pass
                hud_story()
                print(".:: Story logs (%d) ::." % qid)

                if isinstance(story["post_dialog"], str) or isinstance(story["post_dialog"], unicode):
                    print("Calling in new context: %s" % (story["post_dialog"]))
                    renpy.call_in_new_context(story["post_dialog"])
                else:
                    bg_is_showing=False
                    for dial in story["post_dialog"]:
                        # Background
                        if str(dial["bg"]) != "":
                            if bg_is_showing:
                                renpy.hide("sbg")
                            show_img("bg "+dial["bg"], tag="sbg")
                            bg_is_showing=True

                        show_img("dialog_"+dial["left_sprite"], at_list=[tleft], tag="l")
                        show_img("dialog_"+dial["center_sprite"], at_list=[tcenter], tag="c")
                        show_img("dialog_"+dial["right_sprite"], at_list=[tright], tag="r")
                        renpy.say(dial["name"], dial["message"])
                        renpy.hide("l")
                        renpy.hide("c")
                        renpy.hide("r")
                        print("%s: %s" % (dial["name"], dial["message"]))
                    # Background Clean up
                    if bg_is_showing:
                        renpy.hide("sbg")
                    del bg_is_showing

        # Maybe we should update player quest
        if not (quest["flags"] & 4):
            if Player["quest"] < Battle["quest_id"]:
                Player["quest"]=copy.copy(Battle["quest_id"])

    # Cleaning up
    python:
        del Battle

    jump restore