########################################################################################
# This file is part of Castle.
# Copyright (C) 2015 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
########################################################################################
# Miscellaneous functions from another game, also under LGPL
init python:
# Core processing to be on a while loop
def hit_verbose(someone, damage, counting, dmgfactor, fixfactor):
someone["hp"] -= dmgfactor
counting += dmgfactor
if counting+fixfactor == damage:
someone["hp"]-=(fixfactor)
counting+=fixfactor
# TODO: What if something went wrong?
renpy.pause(0.001)
return someone, damage, counting, dmgfactor, fixfactor
# Main hit verbose function
def hit_someone_verbose(someone, damage, fast=False):
counting = 0
# We can use dmgfactor
if ((damage > 10) or (damage < 10)) and (not persistent.SkipHPAnimation) and (not fast):
fixfactor=damage%10
dmgfactor=int(damage/10)
dbg_interactions=0
dbg_srchp=0+someone["hp"]
# Which loop to use?
if damage > 0:
while counting < damage:
someone, damage, counting, dmgfactor, fixfactor=hit_verbose(someone, damage, counting, dmgfactor, fixfactor)
someone["hp"]=int(someone["hp"])
dbg_interactions+=1
# Something went wrong
if dbg_interactions > 15:
raise Exception("WARNING 15 interactions or more happened without hit_verbose concluding.\n\nBy all means report this bug\nBUG ID: HSV FAILED on hpbar_handler with params unit_id, damage, counting, dmgfactor, fixfactor being %s,%d,%d,%d.\n\nClick \"Ignore\" to continue." % (someone["unit_id"], damage, counting, dmgfactor, fixfactor))
counting=damage
someone["hp"]=dbg_srchp-damage
# So, healing it is
else:
while counting > damage:
someone, damage, counting, dmgfactor, fixfactor=hit_verbose(someone, damage, counting, dmgfactor, fixfactor)
someone["hp"]=int(someone["hp"])
dbg_interactions+=1
# Something went wrong
if dbg_interactions > 15:
raise Exception("WARNING 15 interactions or more happened without hit_verbose concluding.\n\nBy all means report this bug\nBUG ID: HSV FAILED on hpbar_handler with params unit_id, damage, counting, dmgfactor, fixfactor being %s,%d,%d,%d.\n\nClick \"Ignore\" to continue." % (someone["unit_id"], damage, counting, dmgfactor, fixfactor))
counting=damage
someone["hp"]=dbg_srchp-damage
# We cannot use dmgfactor
else:
someone["hp"] -= damage
if (someone["hp"] > someone["max_hp"]):
someone["hp"]=int(someone["max_hp"]) # Apply some corrections. TODO possible alias?
renpy.block_rollback() # No rollback from there.
"""
https://github.com/websocket-client/websocket-client/issues/532
Due to #532 we had to disable SSL... again
threading.enumerate() → Find one which name is "MainThread"
Create a threading.Event in an overlay .set()
To send the RenpyQuit exception??
https://grandsphere.fandom.com/wiki/Champion_Challenge
# Priority: Story.json option to call label instead (story.rpy - SQ<id>_)
# Priority: Summon Button
# Priority: Move "system header" to overlay, and hide during fights
# Priority: Tavern Persiana
# And also: Calculate chance for units (adding czoom_25) on draws
# Don't forget to quote price etc.
# Priority: Tavern: We could determine if a tavern is on/off in tavern.py
# Specially if we make "high ranking knight taverns", we could even use
# them during events, returning "107: Selected tavern is not yet open"
# But how can the client know about this and when to display?
# Eval() is not really safe. We could use persiana for this, though.
# Priority: charge GP for upgrade
# Priority: Unit Selling
# Priority: Daily Quest (Fairies and Mana Eggs)
# Priority: Daily Login reward screen
# Priority: Token rewrite
# Priority: Skill system
# Priority: Card swapping (Move cards sideways)
# Priority: AP potions
# Notes
# renpy.invoke_in_thread(?)
# raise KeyboardException → renpy.reload_script() or renpy.quit(relaunch=True)
# if (renpy.android) use androidID, but we could get MAC at server?
# or (renpy.mobile)
# TODO
# Login with already valid token: Currently, it returns the data associated
# to the token, instead of executing login sequence.
# Tokens are still a safety concern, specially with multi-device.
# salted MD5 is not exactly safe. It does the trick, but isn't safe.
# Maybe we should salt this further with account ID?
# Future Improvement:
# Server Protocol Version (compatibility for legacy clients/servers ?)
# Or just check in the client packet?
#
# Result[token] and Battle[token]
# Result[token] contains operation history, loot, gp
# Send two packages: update_battle at final
# battle retrieves Result[token]
# Result loot in icons
# Weekly event switcher (Raids, etc. → events.py)
"""
label msgbox_label(msgcode):
call screen msgbox(msgcode)
return
image spinner:
"gfx/spinner.png"
rotate 30.0
pause 0.05
"gfx/spinner.png"
rotate 60.0
pause 0.05
"gfx/spinner.png"
rotate 90.0
pause 0.05
"gfx/spinner.png"
rotate 120.0
pause 0.05
"gfx/spinner.png"
rotate 150.0
pause 0.05
"gfx/spinner.png"
rotate 180.0
pause 0.05
"gfx/spinner.png"
rotate 210.0
pause 0.05
"gfx/spinner.png"
rotate 240.0
pause 0.05
"gfx/spinner.png"
rotate 270.0
pause 0.05
"gfx/spinner.png"
rotate 300.0
pause 0.05
"gfx/spinner.png"
rotate 330.0
pause 0.05
"gfx/spinner.png"
#rotate 360.0
pause 0.05
repeat
########################################################################################
# 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
########################################################################################
# Player data internals
init python:
def ap_restore():
# Request AP Data from websocket
raw=send_packet("apdata", fg=False)
apdata=json_decode(raw)
try:
Player["ap"]=apdata["ap"]
Player["max_ap"]=apdata["max_ap"]
Player["aptime"]=apdata["aptime"]
except:
stdout("Failed to restore AP!")
traceback.print_exc()
pass
return
def update_ap():
# TODO: This aptime update might be wrong =/ But it will be overriden soon
Player["aptime"] = now()
renpy.invoke_in_thread(ap_restore)
return
# Techinically a battle function, readjusts a status value
# Must be exactly the same as server side function!!
def readjust_status(lvl, val):
return val+int(val*(max(lvl-1, 0)/100.0))
label add_server:
centered "This allows you to add a new custom server.\n\
This option is intended for {color=#0f0}advanced users only{/color}.\n\
\n\
We provide no warranty for custom servers, and they cannot be removed from the server list, so be careful, as they might harm your phone.\n\
\n\
Remember that selecting a new server will disconnect you from the one you are set to login automatically.\n\
Make sure to note down the password or have provided a valid email, or you'll lose access to your account!\n\n{fast}{w=2.0}Click to continue →"
$ namai=renpy.input("What is the host of the server you want to add?", default="spheres.tmw2.org")
if (namai == ""):
$ renpy.full_restart()
return
$ porta=renpy.input("In which port is it listening to?", default="61000", allow="0123456789") or 0
$ porta=int(porta)
if (porta > 65535 or porta < 1):
centered "Ports only go up to 65535."
$ renpy.full_restart()
return
$ human=renpy.input("What is the human readable name of the server?", default="Custom Server")
if (human == ""):
$ renpy.full_restart()
return
$ persistent.serverlist.append([human, namai, porta])
centered "The new server has been added successfully!"
$ renpy.full_restart()
return
label clear_all:
python:
rl=False
# Your version is no longer valid
if (HOST != persistent.host):
print("Host changed from %s to %s" % (str(HOST), persistent.host))
persistent.version=1
rl=True
# TODO: Clear cache
# Update host and delete password
HOST=persistent.host
PORT=persistent.port
renpy.notify("Deleting password: %s" % persistent.password)
persistent.password=None
if debug or config.developer:
"Spheres" "Techinical info:\n\nServer: [HOST]:[PORT]"
if rl:
"Spheres" "Server data purged, we will now relaunch the game."
$renpy.quit(relaunch=True)
else:
"Spheres" "You have logged out successfully."
$ renpy.full_restart()
# ???
jump start
label clear_cache:
python:
import os
# Your version is no longer valid
if persistent.host != "localhost":
persistent.version=1
# Find path to cached files
#if renpy.android:
root=get_path("")
#else:
# root=get_path("extra/")
# Remove cached files
for file in os.listdir(root):
if file.endswith('.png') or file.endswith('.mp3') or file.endswith('.jpg') or file.endswith('.webp') or file.endswith('.ogg'):
try:
os.remove(root+file)
stdout("[CC] Removing %s" % (root+file))
except:
stdout("[CC] Removing %s... FAILED" % root+file)
os.remove(get_path(root+file))
stdout("[CC] Removing %s" % get_path(root+file))
continue
# Erase file memory
persistent.allfiles=[]
allfiles=[]
"Spheres" "Cache deleted. Space was freed!"
$renpy.full_restart()
jump start
#################################################################################
# Everything below this line is part of Mana Launcher.
# Copyright (C) 2021 Jesusalva <jesusalva@tmw2.org>
#
# Distributed under the MIT license.
#################################################################################
screen loading():
zorder 100
fixed:
frame:
xfill True
yfill True
background Frame("gfx/bg/forest_sunset.png", 0, 0)
bar:
value progress
range max_value
xalign 0.5
yalign 0.45
xmaximum 0.75
label "{color=#fff}[statusmsg]{/color}":
xalign 0.5
yalign 0.55
init python:
import hashlib, zipfile
def md5(string):
return hashlib.md5(string.encode()).hexdigest()
def md5sum(f):
md5=hashlib.md5()
fp=open(f, "rb")
ct=fp.read()
md5.update(ct)
rt=copy.copy(md5.hexdigest())
fp.close()
del ct
return rt
def dall_fetch():
global pat, statusmsg, progress, max_value, status
try:
print("Fetching http://%s/assets%s ..." % (persistent.host, pat))
r = requests.get("http://%s/assets%s" % (persistent.host, pat), verify=False, stream=True)
if r.status_code / 100 != 2:
raise Exception("Aborted with status %d" % r.status_code)
## Download the file, slowly as it may be...
max_value = int(r.headers.get('content-length', 0))
print "Blocks: %d" % max_value
## TODO: use blocks of 1024 on DSL and 16384 on broadband?
## 16KiB is the default for Torrent
block_size = 8192
max_value = max_value / block_size
statusmsg = "Downloading the zip file..."
with open(get_path("allinone.zip"), 'wb') as f:
for data in r.iter_content(block_size):
f.write(data)
progress += 1
print "Finished OK"
status = 1
except:
traceback.print_exc()
print("Download All: Not successful!")
statusmsg = "Download failed.\n\nClick anywhere to continue." % r.status_code
status = -1
return
def dall_save():
global statusmsg, progress, max_value, status
try:
statusmsg = "Extracting..."; progress = 0
with zipfile.ZipFile(get_path("allinone.zip"), 'r') as zip_ref:
max_value=len(zip_ref.namelist())
print "Blocks: %d" % max_value
for f in zip_ref.namelist():
try:
# FIXME: Check if file already exists.
# `continue` if it is already in the allfiles
# (Right now, it overrides; Alright, but then it
# goes ahead and pollutes persistent.allfiles...)
print("Extracting %s..." % f)
monday = "extra_"+f
if "." in f:
pass
elif f.startswith("sfx"):
monday += ".mp3"
else:
monday += ".webp"
addr=get_path(monday)
## Work-around to save as
with open(addr, 'wb') as zf:
zf.write(zip_ref.read(f))
## ...Better always save path in full
path=((f, addr))
persistent.allfiles.append(path)
except:
traceback.print_exc()
print("[ERROR] Skipping %s..." % f)
progress+=1
print "Finished OK"
status = 1
except:
traceback.print_exc()
print("Extraction error!")
statusmsg = "Extraction error.\n\nClick anywhere to continue." % r.status_code
status = -1
return
label download_all:
$ statusmsg = "Preparing to download..."
$ progress = 0
$ max_value = 1000
$ status = 0
$ pat = "" # Download
$ md5 = "" # MD5 Hash
show screen loading with dissolve
python:
## Where the zip file is located?
try:
x = requests.get("http://%s/assets/fetch.txt" % persistent.host, verify=False, timeout=8.0)
assert x.status_code == 200, "Status Code Error"
print "%s" % str(x.text)
pat = x.text.split("#")[0]
md5 = x.text.split("#")[1].replace("\n", "")
except:
# Oh noes - something went *terribly* wrong
traceback.print_exc()
x=requests.Response()
x.status_code=403
## Did we manage to get the location?
if x.status_code / 100 != 2:
statusmsg = "Error downloading!\nResource unavailable for the selected host.\n\nClick anywhere to continue."; renpy.pause(); renpy.full_restart(); renpy.jump("start")
## Now we know what to download
# Must be done in thread, or Ren'Py will be blocked
renpy.invoke_in_thread(dall_fetch)
while not status:
sdelay()
if status < 0:
renpy.pause(); renpy.full_restart(); renpy.jump("start")
status = 0
## Verify if download was successful...
statusmsg = "Integrity Check...";
md4 = md5sum(get_path("allinone.zip"))
if (md4 != md5):
statusmsg = "{color=#f00}Download failed.{/color}\n%s != %s\nClick anywhere to retry." % (md4, md5); renpy.jump("download_all")
else:
print("Integrity check passed: %s" % md4)
## We downloaded, but we need to extract it
# Must be done in thread, or Ren'Py will be blocked
renpy.invoke_in_thread(dall_save)
while not status:
sdelay()
if status < 0:
renpy.pause(); renpy.full_restart(); renpy.jump("start")
status = 0
statusmsg = "Complete.\nClick anywhere to continue."
renpy.pause()
## TODO: Maybe delete `allinone.zip`? Waste of space...
renpy.full_restart()
jump start