######################################################################################## # 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 ######################################################################################## # Definitions (critical appliances, classes, imports, renpy internals, updater) init -3 python: renpy.add_python_directory("python-extra") import requests, zlib, base64, sys, copy, uuid, time, json, traceback import os.path from ws4py.client.threadedclient import WebSocketClient # set PYTHON_VERSION variable (should be 2713, 3605 could fail) PYTHON_VERSION="%d%d%02d" % (sys.version_info.major, sys.version_info.minor, sys.version_info.micro) PYTHON_VERSION=int(PYTHON_VERSION) # Ren'Py should come with Python 2.7.10 (2710), but just in case # After all, I only tested with 2.7.10, 2.7.13 and 2.7.15 if (PYTHON_VERSION < 2700): raise Exception("WARNING: Python version is too old\nStrange bugs may happen on your client.\n\nClick on \"Ignore\" to continue.\nClick on \"Quit\" to exit.") # From Python 3.6.x up to Python 3.9.x supported elif (PYTHON_VERSION > 3600 and PYTHON_VERSION < 4000): pass else: raise Exception("WARNING: Python version is too recent\nStrange bugs may happen on your client.\n\nClick on \"Ignore\" to continue.\nClick on \"Quit\" to exit.") # Configuration config.autoreload = False config.save_on_mobile_background = False persistent.release_name = "Diamond Python VIII" if persistent.host is None: persistent.host="spheres.tmw2.org" persistent.serverlist=[["TMW2", "spheres.tmw2.org", 61000]] # FIXME: Set good defaults (=bad) for Android if renpy.android: persistent.ssl_enabled=False else: persistent.serverlist.append(["Localhost", "localhost", 61000]) if persistent.MyUID is None: persistent.MyUID=uuid.uuid4().hex.upper() if (persistent.allfiles is None): persistent.allfiles=[] allfiles=[] HOST=str(persistent.host) PORT=str(persistent.port) FAILUREMSG="Request failed, Please try again" OFFLINEMSG="401 Unauthorized" OKMSG="200 OK" TIMEOUT_INTERVAL=5.0 MAX_IRC_BUFFER=50 SSL_IS_BROKEN=False CERT_NONE=0 INT_MAX=2147483647 MAX_RETRIES = 3 debug=copy.copy(config.developer) TERMINATE=False CLOSING=False # Error Codes ERR_JSONDECODER=101 ERR_LOGIN_DEFAULT=102 ERR_INVALID=103 ERR_TIMEOUT=104 ERR_NOGEMS=105 ERR_INVFULL=106 ERR_SUMMON=108 ERR_OK=200 ERR_OUTDATED=401 # All error code library ERRNO=[FAILUREMSG, OFFLINEMSG, OKMSG, ERR_LOGIN_DEFAULT, ERR_INVALID, ERR_TIMEOUT, ERR_NOGEMS, ERR_INVFULL, ERR_OK] # Core musics/sfx MUSIC_OPENING="sfx/bgm01.mp3" MUSIC_TOWN="sfx/bgm02.mp3" # Battle actions ACT_NONE =0 ACT_TURN =1 ACT_SUMMON =2 # Spheres actions AP_NONE =False AP_SPHERE =1 AP_SKILL =2 # Status ST_TOWN =0 ST_QUEST =1 # Unit flags UF_NOLVL =1 UF_NOPART =2 UF_EXPUP =4 UF_EVOMAT =8 UF_SUPEREVO =64 # Jobs Job_Swordsman =1 Job_Undefined1 =2 Job_Mage =3 Job_Archer =4 Job_Undefined2 =5 Job_Special =6 # IRC flags IRC_AUTH_NONE =0 IRC_AUTH_USER =1 IRC_AUTH_NICK =2 IRC_AUTH_CHAN =3 # Spheres SPH_NONE =0 SPH_WIDEATTACK =1 SPH_PIERCE =2 SPH_ASSAULT =3 SPH_HEAL =4 SPH_HEALALL =5 SPH_ATKUP =6 SPH_DEFUP =7 # Combat code SRV_SKILL =1000 SRV_SERVER =9900 SRV_WAVE =9901 SRV_SPHERE =9902 SRV_SUMMON =9903 SRV_NOCAST =9904 # Special error structs ERR_MOBSTRUCT ={ "name": "ERROR", "unit_id": 0, "max_hp": 0, "hp": 0, "atk": 0, "ele": 0, "status_effects": 0 } ERR_PLAYERSTRUCT={ "unit_id": 0, "max_hp": 0, "hp": 0, "atk": 0, "ele": 0, "status_effects": 0 } # Returns human readable job def parse_job(JOB): if (JOB == Job_Swordsman): return _("Swordsmaster") elif (JOB == Job_Mage): return _("Wizard") elif (JOB == Job_Archer): return _("Ranger") elif (JOB == Job_Special): return _("Special") else: return _("???") # Smart Print command def stdout(message): if debug: if renpy.android: if not renpy.is_init_phase(): renpy.notify(message) else: print(message) renpy.write_log("[DEBUG] %s" % message) else: renpy.write_log("[GAME] %s" % message) return # Smart wait def sdelay(delta=0.02): try: renpy.pause(delta, hard=True) except: time.sleep(delta) return # IF Then Else (IFTE) def ifte(ifs, then, elses): if (ifs): return then else: return elses # Returns number of seconds since UNIX EPOCH def now(): return int(time.time()) # Global classes # We need to override standard list method. Original by Triptych (stackoverflow) class dlist(list): def __setitem__(self, index, value): size = len(self) if index >= size: self.extend(None for _ in range(size, index + 1)) list.__setitem__(self, index, value) class ExecuteOnCall(): def __init__(self, callable, *args, **kwargs): self.callable = callable self.args = args self.kwargs = kwargs def __call__(self): rv = self.callable(*self.args, **self.kwargs) return rv def id(self): return self.__call__() class RetString(): def __init__(self, string): self.string = string def __call__(self): return self.string def id(self): return self.__call__() # Screen Functions/class # Override class SpheresMainMenu(MainMenu): def __call__(self): if not self.get_sensitive(): return if self.confirm: if config.autosave_on_quit: renpy.force_autosave() layout.yesno_screen(layout.MAIN_MENU, SpheresMainMenu(False)) else: # Flush labels/sockets/timers as needed renpy.call_in_new_context("quit") # Restart renpy.full_restart(config.game_main_transition) class ExtraImage(renpy.display.im.Image): """ Custom image manipulator, based on bink's code, topic 11732 """ def __init__(self, loc, **properties): """ @param loc: Where the image really is (get_path already applied) """ super(ExtraImage, self).__init__(loc, **properties) self.loc = loc def load(self, unscaled=False): # W0221 try: #page = open(self.loc, "rb") #pd = page.read() #picdata = os.tmpfile() #picdata.write(pd) #picdata.seek(0) # reset seek position #stdout("Requested to open: %s" % repr(self.loc)) picdata = open(self.loc, "rb") #stdout("Picdata is open (%s)" % self.loc) if unscaled: surf = renpy.display.pgrender.load_image_unscaled(picdata, self.loc) else: surf = renpy.display.pgrender.load_image(picdata, self.loc) #stdout("Picdata was closed") picdata.close() #page.close() return surf except Exception as e: if renpy.config.missing_image_callback: im = renpy.config.missing_image_callback(self.loc) if im is None: raise e return im.load() raise def virtpos(posix): if isinstance(posix, float): return posix*1024 else: return posix/1024.0 # File Managment Functions def get_path(path): if not renpy.windows: path=path.replace("/", "_") #return renpy.loader.get_path(path) return renpy.config.savedir + "/" + path else: return renpy.loader.get_path(path) def get_path_if_exists(path): if not renpy.windows: path=path.replace("/", "_") #return renpy.loader.get_path(path) return renpy.config.savedir + "/" + path else: return renpy.loader.transfn(path) # Override music audio loader def SpheresLoadAudioFile(fn): """ Returns a file-like object for the given filename. """ try: fna = get_path_if_exists(fn) if not os.path.isfile(fna): raise Exception("Not a file") rv = renpy.loader.open_file(fna, "rb") except: rv = renpy.loader.load(fn) return rv renpy.audio.audio.load=SpheresLoadAudioFile ## Post setup if (persistent.summon is None): persistent.summon=ifte(config.developer, 15, 0) ############################################################################# # URL3 Function def GAME_UPDATER(): global tr_uptodate, tr_fatality tr_uptodate=False # If no version is provided, we are using default files # Default files version is "1" (Should never happen) if (persistent.version is None): persistent.version=1 # Download upstream version x=requests.get("http://"+HOST+'/version.txt', verify=False) try: ver=int(x.text) except: stdout("IMPOSSIBLE TO DETERMINE VERSION") raise Exception("Could not estabilish a connection to update server:\n%s is not valid." % repr(x.text)) # TODO: Show this beautifully? # TODO: Should we set a "ver"? if (int(persistent.version) < ver): try: # TODO: Check if the server have SSL support stdout("Downloading updated game data...") # Download quests.json f=open(get_path("quests.json"), "w") stdout("Downloading quests.json") x=requests.get("http://"+HOST+'/quests.json', verify=False) f.write(x.text) f.close() # Download units.json f=open(get_path("units.json"), "w") stdout("Downloading units.json") x=requests.get("http://"+HOST+'/units.json', verify=False) f.write(x.text) f.close() # Download story.json f=open(get_path("story.json"), "w") stdout("Downloading story.json") x=requests.get("http://"+HOST+'/story.json', verify=False) f.write(x.text) f.close() # Download world.json f=open(get_path("world.json"), "w") stdout("Downloading world.json") x=requests.get("http://"+HOST+'/world.json', verify=False) f.write(x.text) f.close() # Download bar.json f=open(get_path("bar.json"), "w") stdout("Downloading bar.json") x=requests.get("http://"+HOST+'/bar.json', verify=False) f.write(x.text) f.close() # Download summons.json f=open(get_path("summons.json"), "w") stdout("Downloading summons.json") x=requests.get("http://"+HOST+'/summons.json', verify=False) f.write(x.text) f.close() persistent.version=ver stdout("Update complete") except: tr_fatality = False traceback.print_exc() stdout("[FATAL] Unable to update JSON files; Aborted") # Download server news # Handled by GAME_LOADER tr_uptodate=True return tr_load