######################################################################################## # 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 (preferences.text_cps) 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_) # 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)) def async_loader(): print("Now loading images to RAM...") t = now() for c in persistent.allfiles: try: show_img(c[0], False) except: print("Unable to load %s" % str(c)) traceback.print_exc() print("Loaded %d images in %d seconds." % (len(persistent.allfiles), now() - t)) 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 →" if renpy.android: $ namai=renpy.call_screen("input_box", "What is the host of the server you want to add?\n\ Default: spheres.tmw2.org") else: $ namai=renpy.input("What is the host of the server you want to add?", default="spheres.tmw2.org") if (namai == ""): $ renpy.full_restart() return if renpy.android: $ porta=renpy.call_screen("input_box", "In which port is it listening to?\n\ Default: 61000") or "61000" else: $ 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 if renpy.android: $ human=renpy.call_screen("input_box", "What is the human readable name of the server?\n\ Default: Custom Server") or "Custom Server" else: $ 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 (and reset summon) HOST=persistent.host PORT=persistent.port persistent.summon=ifte(config.developer, 15, 0) 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 # # 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: statusmsg = "Fetching assets..." 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