diff options
-rw-r--r-- | game/battle.rpy | 210 | ||||
-rw-r--r-- | game/gui/battle.rpy | 419 | ||||
-rw-r--r-- | game/quest.rpy | 166 |
3 files changed, 795 insertions, 0 deletions
diff --git a/game/battle.rpy b/game/battle.rpy new file mode 100644 index 0000000..745bef0 --- /dev/null +++ b/game/battle.rpy @@ -0,0 +1,210 @@ +######################################################################################## +# 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 + +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 + diff --git a/game/gui/battle.rpy b/game/gui/battle.rpy new file mode 100644 index 0000000..0142842 --- /dev/null +++ b/game/gui/battle.rpy @@ -0,0 +1,419 @@ +######################################################################################## +# 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: + ####################### 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 + +# FIXME: Allow to drag spheres instead of whole chars. +# More checks needed on client, but less on server +# And spheres are 1D, so much easier on RAM/CPU as well +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 + +############################################################################# +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) + diff --git a/game/quest.rpy b/game/quest.rpy new file mode 100644 index 0000000..76b89ce --- /dev/null +++ b/game/quest.rpy @@ -0,0 +1,166 @@ +######################################################################################## +# 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 +######################################################################################## +# Mission Selection & Quest Handling +init python: + ######################## 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 + +################################################################### +# 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 + |