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