######################################################################################## # 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 Interface init python: import json, copy ######################## Server Communications def loadquest(quest_id, party_id=1): import json raw=send_packet("begin_quest", questdata(quest_id, party_id)) bt=json_decode(raw) if (bt == ERR_JSONDECODER): return ERR_JSONDECODER return bt def loadbattle(new_arrange): raw=send_packet("battle", battledata(new_arrange, use_sphere)) bt=json_decode(raw) if (bt == ERR_JSONDECODER): return ERR_JSONDECODER return bt def reload_battle(): raw=send_packet("reload_battle", "") bt=json_decode(raw) if (bt == ERR_JSONDECODER): return ERR_JSONDECODER return bt ####################### Client rendering def c_dragid(dg): if dg == "party1": return 0 elif dg == "party2": return 1 elif dg == "party3": return 2 elif dg == "party4": return 3 elif dg == "party5": return 4 else: return 0# We can't return -1... def combat_action(drags, drop): global use_sphere if not drop: return # Define your char slot, and reset the sphere usage for it idn=c_dragid(drags[0].drag_name) use_sphere[idn]=AP_NONE # Check if you asked for a skill if (drop.drag_name == "Skill"): renpy.notify("You can't use a skill!") drags[0].snap(0, 30, delay=0.1) #use_sphere[idn]=AP_SKILL return # Check if you asked for a sphere if (drop.drag_name == "Sphere"): if (Battle["spheres"][idn]): # Mark to use the sphere - if it exists! drags[0].snap(0, 60, delay=0.1) use_sphere[idn]=AP_SPHERE return else: renpy.notify("You can't use a sphere!") drags[0].snap(0, 30, delay=0.1) return return screen battle(): # Background add "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={"hp": 0} try: en2=Battle["enemy"][1] show_img("mob_"+str(en2["unit_id"]), False) # Validate except: en2={"hp": 0} try: en3=Battle["enemy"][2] show_img("mob_"+str(en3["unit_id"]), False) # Validate except: en3={"hp": 0} #################################################### # Render HUD frame: xalign 0.0 yalign 0.0 xfill True ymaximum 60 yfill True background Frame("gui/frame.png", 0, 0) hbox: # Preferences button imagebutton auto "gfx/gui/cog_%s.png" action ShowMenu('preferences') textbutton _("Party") action Function(blayout) null width 20 text "%d " % (Battle["turn"]) text _(" Turn") null width 300 text _("Wave %d/%d" % (Battle["wave"], Battle["max_wave"])) null width 300 textbutton _("I'm ready") action Return() #################################################### # Render enemies # Enemy 1 if (en1["hp"] > 0): add At("mob_"+str(en1["unit_id"]), enemy1) vbox: xalign 0.5 yanchor 0.5 ypos 0.24 xmaximum 180 ymaximum 20 text en1["name"] bar value en1["hp"] range en1["max_hp"] # Enemy 2 if (en2["hp"] > 0): add At("mob_"+str(en2["unit_id"]), enemy2) vbox: xalign 1.0 yanchor 0.5 ypos 0.24 xmaximum 180 ymaximum 20 text en2["name"] bar value en2["hp"] range en2["max_hp"] # Enemy 3 if (en3["hp"] > 0): add At("mob_"+str(en3["unit_id"]), enemy3) vbox: xalign 0.0 yanchor 0.5 ypos 0.24 xmaximum 180 ymaximum 20 text en3["name"] bar value en3["hp"] range en3["max_hp"] #################################################### # Render allies # TODO: Gray out and unmovable if dead # One drag group per party member defined in Battle draggroup: xalign 0.2 yalign 1.0 xmaximum 160 ymaximum 300 # 240 + 60: 30 px each dimension # Display the background drag: child At("gfx/action.png", c_party1) draggable False droppable False # Display the card (if there's one) if (fx1): drag: drag_name "party1" child At(fx1, c_party1) droppable False if (Battle["party"][0]["hp"] > 0): dragged combat_action else: draggable False ypos 30 # The action areas drag: drag_name "Skill" child At("gfx/actionarea.png", c_party1) draggable False ypos 0.0 drag: drag_name "Sphere" child At("gfx/actionarea.png", c_party1) draggable False yalign 1.0 # Display the sphere drag: child ("gfx/sphere/"+str(Battle["spheres"][0])+".png") draggable False droppable False yalign 1.0 xalign 0.5 if (fx1 and Battle["party"][0]["hp"] <= 0): add At("gfx/off.png", c_party1) # One drag group per party member defined in Battle draggroup: xalign 0.4 yalign 1.0 xmaximum 160 ymaximum 300 drag: child At("gfx/action.png", c_party2) draggable False droppable False if (fx2): drag: drag_name "party2" child At(fx2, c_party2) droppable False if (Battle["party"][1]["hp"] > 0): dragged combat_action else: draggable False ypos 30 drag: drag_name "Skill" child At("gfx/actionarea.png", c_party2) draggable False ypos 0.0 drag: drag_name "Sphere" child At("gfx/actionarea.png", c_party2) draggable False yalign 1.0 drag: child ("gfx/sphere/"+str(Battle["spheres"][1])+".png") draggable False droppable False yalign 1.0 xalign 0.5 if (fx2 and Battle["party"][1]["hp"] <= 0): add At("gfx/off.png", c_party2) # One drag group per party member defined in Battle draggroup: xalign 0.6 yalign 1.0 xmaximum 160 ymaximum 300 drag: child At("gfx/action.png", c_party3) draggable False droppable False if (fx3): drag: drag_name "party3" child At(fx3, c_party3) droppable False if (Battle["party"][2]["hp"] > 0): dragged combat_action else: draggable False ypos 30 drag: drag_name "Skill" child At("gfx/actionarea.png", c_party3) draggable False ypos 0.0 drag: drag_name "Sphere" child At("gfx/actionarea.png", c_party3) draggable False yalign 1.0 drag: child ("gfx/sphere/"+str(Battle["spheres"][2])+".png") draggable False droppable False yalign 1.0 xalign 0.5 if (fx3 and Battle["party"][2]["hp"] <= 0): add At("gfx/off.png", c_party3) # One drag group per party member defined in Battle draggroup: xalign 0.8 yalign 1.0 xmaximum 160 ymaximum 300 drag: child At("gfx/action.png", c_party4) draggable False droppable False if (fx4): drag: drag_name "party4" child At(fx4, c_party4) droppable False if (Battle["party"][3]["hp"] > 0): dragged combat_action else: draggable False ypos 30 drag: drag_name "Skill" child At("gfx/actionarea.png", c_party4) draggable False ypos 0.0 drag: drag_name "Sphere" child At("gfx/actionarea.png", c_party4) draggable False yalign 1.0 drag: child ("gfx/sphere/"+str(Battle["spheres"][3])+".png") draggable False droppable False yalign 1.0 xalign 0.5 if (fx4 and Battle["party"][3]["hp"] <= 0): add At("gfx/off.png", c_party4) #################################################### # Render HPBARs if (fx1): frame: xalign 0.21 yalign 1.0 ymaximum 10 xmaximum 120 bar value Battle["party"][0]["hp"] range Battle["party"][0]["max_hp"] xmaximum 120 if (fx2): frame: xalign 0.4 yalign 1.0 ymaximum 10 xmaximum 120 bar value Battle["party"][1]["hp"] range Battle["party"][1]["max_hp"] xmaximum 120 if (fx3): frame: xalign 0.6 yalign 1.0 ymaximum 10 xmaximum 120 bar value Battle["party"][2]["hp"] range Battle["party"][2]["max_hp"] xmaximum 120 if (fx4): frame: xalign 0.8 yalign 1.0 ymaximum 10 xmaximum 120 bar value Battle["party"][3]["hp"] range Battle["party"][3]["max_hp"] xmaximum 120 screen battle_layout(lb="Select unit"): vbox: xalign 0.5 yalign 0.3 label _("{size=32}{color=#f00}%s{/color}{/size}" % lb) null height 20 hbox: for i, item in enumerate(Battle["party"]): imagebutton: if item["unit_id"] > 0: idle At(Composite( (340, 340), (0, 0), "gfx/square/bg.png", (0, 0), "gfx/square/units/%d.png" % item["unit_id"], (0, 0), "gfx/square/%d.png" % allunits[item["unit_id"]]["rare"], ), czoom_70) else: idle At("gfx/square/bg.png", czoom_70) action Return(i) null height 80 # The close button returns -1 and comes last (TODO) imagebutton: idle At("gfx/square/back_idle.png", czoom_75) hover At("gfx/square/back_hover.png", czoom_75) action Return(-1) init 2: python: def blayout(): renpy.call_in_new_context("bl_context") return def blayout2(): o1=renpy.call_screen("battle_layout", "Select unit to swap") o2=renpy.call_screen("battle_layout", "Select unit to swap with") if (o1 == o2): return tmp=copy.copy(Battle["party"][o2]) Battle["party"][o2]=copy.copy(Battle["party"][o1]) Battle["party"][o1]=tmp renpy.notify("Done!") return label bl_context: $ blayout2() return ################################################################### # TODO: Quest menus and selections should be auto-generated # World map structure: ["name", min_level, {quest1}, {quest2} ...] label quest_select: $ show_img("bg battle2", False, ext=".jpg") scene bg battle2 play music MUSIC_WORLDMAP.id() fadein 0.5 python: from copy import copy worldmap=[] areamap=[] for arx in allworld: arena=copy(arx) name=arena.pop(0) req=arena.pop(0) if Player["quest"] >= req: worldmap.append((name, arena)) # Display world map menu worldmap.append(("Return", -1)) mapselected=renpy.display_menu(worldmap) del worldmap if mapselected == -1: renpy.jump("restore") # Now we have the mapselected array, with dict for arx in mapselected: quest=copy(arx) name=quest["name"] qid=quest["quest_id"] # We also want to show cost and requeriments entry=dl_search(allquests, "quest_id", qid) if entry != ERR_INVALID: cost=entry["cost"] req=entry["requeriment"] else: cost=-1 req=99999 # Add entry (if valid) if Player["quest"] >= req: if Player["ap"] >= cost: areamap.append(("%s (%d AP)" % (name, cost), qid)) else: areamap.append(("{s}%s (%d AP){/s}" % (name, cost), None)) # Display area menu areamap.append(("Return", -1)) qid=renpy.display_menu(areamap) del areamap if qid == -1 or qid is None: renpy.jump("quest_select") jump quest_selected ################################################################### label quest_selected: # Get quest data python: quest=dl_search(allquests, "quest_id", qid) # Uhm, how did this happen? Means client-data is not fully updated! if (quest == ERR_INVALID): renpy.call_screen("msgbox", "ERROR:\n\nRequested Quest does not exist client-side\nAn update is required. We'll now close the app.") raise KeyboardInterrupt() # Confirm the quest cost $apmsg=_("Quest cost: %d/%d AP" % (quest["cost"], Player["ap"])) menu: "[apmsg]" "Accept Quest" if Player["ap"] >= quest["cost"]: pass "Decline Quest": jump quest_select # Begin the quest $ Battle=loadquest(qid) # Check for error if (Battle in [FAILUREMSG, OFFLINEMSG, ERR_JSONDECODER, ERR_LOGIN_DEFAULT]): $ renpy.call_screen("msgbox", "Error:\n\n%s\nYou'll be taken to town." % Battle) jump restore # Reduce the paid AP python: # Consume the AP update_ap(-(quest["cost"])) # Before fighting, should we perhaps show story? if Player["quest"] < qid: story=dl_search(allstory, "quest_id", qid) if (story != ERR_INVALID): hud_story() print ".:: Story logs (%d) ::." % qid if isinstance(story["pre_dialog"], str) or isinstance(story["pre_dialog"], unicode): renpy.call_in_new_context(story["pre_dialog"]) else: bg_is_showing=False for dial in story["pre_dialog"]: # Background if str(dial["bg"]) != "": if bg_is_showing: renpy.hide("sbg") show_img("bg "+dial["bg"], tag="sbg", ext=".jpg") bg_is_showing=True show_img("dialog_"+dial["left_sprite"], at_list=[left], tag="l") show_img("dialog_"+dial["center_sprite"], at_list=[center], tag="c") show_img("dialog_"+dial["right_sprite"], at_list=[right], 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 # Okay, story-telling time is over: To arms! play music MUSIC_BATTLE.id() fadein 0.5 $ renpy.free_memory() window hide label combat: # Implement combat view $ hud_clear() $ show_img("bg battle", False) scene bg battle $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] call screen battle $stdout("================= call ended") # TODO: Send to server new arrangement of units # Update Battle with server response $ 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 (x2) $ 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 # TODO The server should send a JSON of results... # Wait for server to confirm the fight end if (response["result"] != ""): $ renpy.free_memory() # TODO: Announce the result screen jump results # Fight continues # TODO this is suboptimal, and the loops are also wrong # Maybe we should get info about what was used and whatnot? show screen battle python: # TODO: Render sphere usage # 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 # 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 # 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!") $ Battle=response jump combat label results: $ 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, "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 update_ap(Player["max_ap"]-Player["ap"]) #Player["exp"]+=1 expmsg="PLAYER {b}RANK UP!{/b} (Max Ap: %d -> %d)" % (Player["max_ap"]-response["rank"]+1, Player["max_ap"]) # Report screen renpy.call_screen("msgbox", "Result:\n%s\n\nGp: %d\n%s\nLoot:\n%s" % ( _("VICTORY!"), response["gp"], expmsg, "{size=12}"+loot+"{/size}" )) # 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?! if Player["quest"] < qid: story=dl_search(allstory, "quest_id", qid) if (story != ERR_INVALID): hud_story() print ".:: Story logs (%d) ::." % qid if isinstance(story["post_dialog"], str) or isinstance(story["post_dialog"], unicode): 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", ext=".jpg") bg_is_showing=True show_img("dialog_"+dial["left_sprite"], at_list=[left], tag="l") show_img("dialog_"+dial["center_sprite"], at_list=[center], tag="c") show_img("dialog_"+dial["right_sprite"], at_list=[right], 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"]=Battle["quest_id"] jump restore