summaryrefslogtreecommitdiff
path: root/game/combat.rpy
diff options
context:
space:
mode:
Diffstat (limited to 'game/combat.rpy')
-rw-r--r--game/combat.rpy754
1 files changed, 754 insertions, 0 deletions
diff --git a/game/combat.rpy b/game/combat.rpy
new file mode 100644
index 0000000..9537146
--- /dev/null
+++ b/game/combat.rpy
@@ -0,0 +1,754 @@
+########################################################################################
+# 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
+