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