summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--game/01_init.rpy325
-rw-r--r--game/02_init.rpy247
-rw-r--r--game/03_init.rpy313
-rw-r--r--game/04_init.rpy75
-rw-r--r--game/05_init.rpy30
-rw-r--r--game/defs.rpy820
-rw-r--r--game/misc.rpy7
-rw-r--r--game/script.rpy77
8 files changed, 1008 insertions, 886 deletions
diff --git a/game/01_init.rpy b/game/01_init.rpy
new file mode 100644
index 0000000..ddc00f4
--- /dev/null
+++ b/game/01_init.rpy
@@ -0,0 +1,325 @@
+########################################################################################
+# 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
+
+init -3 python:
+ renpy.add_python_directory("python-extra")
+ import requests, zlib, base64, sys, copy, uuid, time, json
+ import websock as wsock
+
+ # 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 or PYTHON_VERSION > 3000):
+ raise Exception("WARNING: Python version is not 2.7\nStrange bugs may happen on your client.\n\nClick on \"Ignore\" to continue.\nClick on \"Quit\" to exit.")
+
+ # Why setting this...?
+ ApTimer=""
+
+ # Configuration
+ config.autoreload = False
+ config.save_on_mobile_background = False
+ persistent.release_name = "Christmas"
+ 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.nothreading=False
+ persistent.ssl_enabled=False
+ else:
+ persistent.serverlist.append(["Localhost", "localhost", 61000])
+ if (persistent.allfiles is None):
+ persistent.allfiles=[]
+ allfiles=[]
+ HOST=str(persistent.host)
+ PORT=str(persistent.port)
+ UPDP=10302
+ FAILUREMSG="Request failed, Please try again"
+ OFFLINEMSG="401 Unauthorized"
+ OKMSG="200 OK"
+ TIMEOUT_INTERVAL=3.0
+ MAX_IRC_BUFFER=50
+ SSL_IS_BROKEN=False
+ CERT_NONE=0
+ debug=copy.copy(config.developer)
+
+ # Error Codes
+ ERR_JSONDECODER=101
+ ERR_LOGIN_DEFAULT=102
+ ERR_INVALID=103
+ ERR_TIMEOUT=104
+ ERR_NOGEMS=105
+ ERR_INVFULL=106
+ ERR_OK=200
+
+ # 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"
+
+ # 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_Assassin =2
+ Job_Mage =3
+ Job_Archer =4
+ Job_Gunner =5
+
+ # IRC flags
+ IRC_AUTH_NONE =0
+ IRC_AUTH_USER =1
+ IRC_AUTH_NICK =2
+ IRC_AUTH_CHAN =3
+
+ # 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
+
+
+ # 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, 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 True or renpy.android:
+ 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 True or renpy.android:
+ path=path.replace("/", "_")
+ #return renpy.loader.get_path(path)
+ return renpy.config.savedir + "/" + path
+ else:
+ return renpy.loader.transfn(path)
+
+ # URL3 Function
+ def GAME_UPDATER():
+ global tr_load
+ tr_load=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')
+ 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." % x.text) # TODO: Show this beautifully?
+ # TODO: Should we set a "ver"?
+
+ if (int(persistent.version) < ver):
+ # 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')
+ 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')
+ 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')
+ 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')
+ 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')
+ 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')
+ f.write(x.text)
+ f.close()
+
+ persistent.version=ver
+ stdout("Update complete")
+
+ # Download server news
+ # Handled by GAME_LOADER
+
+ tr_uptodate=True
+ tr_load=True
+ return tr_load
+
diff --git a/game/02_init.rpy b/game/02_init.rpy
new file mode 100644
index 0000000..6186e32
--- /dev/null
+++ b/game/02_init.rpy
@@ -0,0 +1,247 @@
+########################################################################################
+# 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
+############################################################################
+init -1 python:
+ # Android might have special SSL problems
+ if renpy.android:
+ try:
+ import ssl
+ except ImportError:
+ SSL_IS_BROKEN=True
+ stdout("Android SSL misbehavior detected")
+ import _openssl as ssl
+ else:
+ import ssl
+ print("Using system-wide SSL implementation...")
+
+ # wsock already imported
+
+ stdout("======================= %s %s %s" % (config.name, config.version, persistent.release_name))
+ print "[STDBY] Loading Basic functions......."
+
+ # Search for array[?][key]==search in an array of dicts
+ # Returns the dictionary, or returns ERR_INVALID
+ def dl_search(array, key, search):
+ try:
+ r=(item for item in array if item[key] == search).next()
+ except:
+ r=ERR_INVALID
+ if r is None:
+ r=ERR_INVALID
+ stdout("dlsearch: r is None")
+ return r
+
+ def check_fail(raw):
+ global debug, FAILUREMSG
+
+ if (debug):
+ stdout(str(raw))
+ if (raw == FAILUREMSG):
+ return True
+ return False
+
+ def json_decode(raw):
+ global ERRNO
+
+ if (check_fail(raw)):
+ return ERR_LOGIN_DEFAULT
+
+ # TODO Move this to check_fail and rewrite check_fail
+ # ERR_OFF should be handled top-level no? With full_restart()
+ if raw in ERRNO:
+ return raw
+
+ try:
+ return int(raw)
+ except:
+ pass
+
+ # Maybe base 64
+ try:
+ rw=base64.b64decode(raw)
+ raw=rw
+ if (debug):
+ print "base64 decoded"
+ except:
+ pass
+
+ # Maybe zlib compressed
+ try:
+ rw=zlib.decompress(raw)
+ raw=rw
+ if (debug):
+ print str(raw)
+ except:
+ pass
+
+ # Decode JSON
+ try:
+ return json.loads(raw)
+ except:
+ return ERR_JSONDECODER
+
+ def get_token():
+ try:
+ t=Player['token']
+ except:
+ t="0" #"f528764d624db129b32c21fbca0cb8d6"
+ return t
+
+ def login():
+ global Player
+ stdout("Login requested for %s" % logindata())
+ raw=send_packet("login", logindata())
+
+ Player=json_decode(raw)
+ if (Player == ERR_JSONDECODER):
+ return ERR_JSONDECODER
+ if (Player == ERR_LOGIN_DEFAULT):
+ return ERR_LOGIN_DEFAULT
+
+ try:
+ Player["inv"]=dlist()
+ except:
+ pass
+ return Player["code"]
+
+ def send_packet_now(packet, args="", legacy=False):
+ global tr_load, tr_val, tr_busy
+ global ws
+
+ stdout("Sending: %s" % packet)
+ try:
+ ws.send(get_token() + ";" + packet + ";" + args)
+ except:
+ stdout("Failed to send message!!")
+ # FIXME set tr_val/tr_load/tr_busy to good values
+ return False
+ # TODO: Wait for onmsg
+ # TODO: ping packets
+ return True
+
+ def send_packet(packet, args=""):
+ global tr_load, tr_val, tr_busy
+ # TODO: if tr_busy already true, wait until it is made false
+ while tr_busy:
+ sdelay()
+
+ # Book processing space for ourselves
+ tr_busy=True
+ tr_load=False
+ tr_val=None
+
+ # This is a secret variable which disables threading and queue
+ # This may cause hangs and other issues.
+ if persistent.nothreading:
+ send_packet_now(packet, args)
+ tr_busy=False
+ val=tr_val
+ return val
+
+ # FIXME
+ timeout=0.0
+ renpy.show("spinner", at_list=[truecenter])
+ r = send_packet_now(packet, args)
+ if not r:
+ # Something went wrong
+ return ERR_INVALID
+
+ while not tr_load:
+ sdelay() # FIXME: This can cause errors in mobile?
+ timeout+=0.02
+
+ if timeout >= TIMEOUT_INTERVAL:
+ # FIXME: What if a screen is already being displayed? BUG
+ try:
+ renpy.call_screen("msgbox", "Error Code: %d\n\nApplication timeout, click to try again" % (ERR_TIMEOUT))
+ timeout=0.0
+ except:
+ if not "ping" in packet.lower():
+ stdout("WARNING, ILLEGAL PACKET ON SCREEN TIME: %s" % packet)
+ pass
+
+ renpy.hide("spinner")
+ val=tr_val
+ tr_busy=False
+ del tr_val
+
+ print "value obtained"
+ if (val is None):
+ return ERR_INVALID
+ return val
+
+ def GAME_LOADER():
+ global allunitsbase, allunits, allquests, allstory, allworld, alltaverns
+ global allnews, tr_load
+ tr_load=False
+
+ # Wait until everything is up to date
+ while not tr_uptodate:
+ sdelay()
+
+ # Load unit data
+ allunitsbase=json.loads(requests.get("http://"+HOST+'/units.json').text)
+ # f=open(get_path_if_exists("units.json"), "r")
+ # allunitsbase=json.load(f)
+ # f.close()
+
+ # Reorder unit data
+ allunits={}
+ for j in allunitsbase:
+ allunits[j["unit_id"]]=j
+
+ # Load summons data
+ f=open(get_path_if_exists("summons.json"), "r")
+ allworld=json.load(f)
+ f.close()
+
+ # Load quest data
+ f=open(get_path_if_exists("quests.json"), "r")
+ allquests=json.load(f)
+ f.close()
+
+ # Load story data
+ f=open(get_path_if_exists("story.json"), "r")
+ allstory=json.load(f)
+ f.close()
+
+ # Load world data
+ f=open(get_path_if_exists("world.json"), "r")
+ allworld=json.load(f)
+ f.close()
+
+ # Load tavern data
+ f=open(get_path_if_exists("bar.json"), "r")
+ alltaverns=json.load(f)
+ f.close()
+
+ # Load server news
+ try:
+ allnews=json.loads(requests.get("http://"+HOST+'/news.json', timeout=5.0).text)
+ except:
+ allnews=[]
+ pass
+
+ stdout("[OK] Game data loaded to memory")
+ tr_load=True
+ tr_memcheck=True
+ return tr_load
+
+
diff --git a/game/03_init.rpy b/game/03_init.rpy
new file mode 100644
index 0000000..1125462
--- /dev/null
+++ b/game/03_init.rpy
@@ -0,0 +1,313 @@
+########################################################################################
+# 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
+############################################################################
+# This is the JSON data formatter
+init python:
+ def logindata():
+ global password
+ # TODO: Obtain user id based on device
+ return """{
+ "passwd": "%s",
+ "version": "%s"
+ }""" % (password, config.version)
+
+
+ def recruitdata(t, a):
+ return """{
+ "tavern": %d,
+ "amount": %d
+ }""" % (t, a)
+
+
+ def questdata(q, p):
+ return """{
+ "quest_id": %d,
+ "party_id": %d
+ }""" % (q, p)
+
+
+ def battledata(u, s):
+ return """{
+ "unit": %s,
+ "sphere": %s
+ }""" % (json.dumps(u), json.dumps(s))
+
+ def card_composite(cid, path):
+ # We need to try to get rarity
+ try:
+ r=allunits[int(cid)]["rare"]
+ except:
+ r=4 # FIXME
+
+ # We need to try to get the element
+ try:
+ e=allunits[int(cid)]["attribute"]
+ print str(e)
+ except:
+ e=0
+
+ return Composite(
+ (640, 960),
+ (0, 0), "gfx/cards/bg.png",
+ (0, 0), path,
+ (0, 0), "gfx/cards/"+str(r)+".png",
+ (0, 0), "gfx/cards/ele/"+str(e)+".png")
+ # size,)
+
+ # TODO: square_ and gfx/square/units
+ # Converts regular image filepaths to displayables names and vice-versa
+ def img_regex(name, reverse=False):
+ if not reverse:
+ # Extension is ommited, add them yourself!
+ if name.startswith("unit_"):
+ return "gfx/units/%s" % name.replace("unit_")
+ elif name.startswith("mob_"):
+ return "gfx/mobs/%s" % name.replace("mob_")
+ elif name.startswith("dialog_"):
+ return "gfx/dialog/%s" % name.replace("dialog_")
+ elif name.startswith("bg "):
+ return "gfx/bg/%s" % name.replace("bg ")
+ elif name.startswith("summon_"):
+ return "gfx/summons/%s" % name.replace("summon_")
+ else:
+ print("ERROR: Not a regular filename")
+ return name
+ else:
+ if name.startswith("gfx/units/"):
+ return "unit_%s" % name
+ elif name.startswith("gfx/mobs/"):
+ return "mob_%s" % name
+ elif name.startswith("gfx/dialog/"):
+ return "dialog_%s" % name
+ elif name.startswith("gfx/bg/"):
+ return "bg %s" % name
+ elif name.startswith("gfx/summons/"):
+ return "summon_%s" % name
+ else:
+ print("ERROR: Not a regular display name")
+ return name
+
+ # Loads a sound called VAL
+ def get_sfx(val, ext):
+ # Search for the sound
+ show_img(val, False, ext=ext)
+
+ valb=dl_search(persistent.allfiles, 0, val)[1]
+ if valb == ERR_INVALID:
+ print ("Invalid Sound: %s (%s)" % (path, val))
+ return "sfx/regnum.mp3" # FIXME
+ return valb
+
+ # Load sprite images: "unit"
+ for file in renpy.list_files():
+ fn=file.replace('gfx/units/','').replace('/', ' ').replace('.png','')
+ if file.startswith('gfx/units/'):
+ if file.endswith('.png') or file.endswith('.webp'):
+ name = "unit_"+fn
+ #renpy.image(name, Image(file, yanchor=1.0))
+ renpy.image(name, card_composite(fn, file))
+
+ dl=dl_search(persistent.allfiles, 0, name)
+ if dl is not ERR_INVALID:
+ persistent.allfiles.append((name, file))
+ allfiles.append(name)
+ continue
+ continue
+
+ # Load sprite images: "mob"
+ for file in renpy.list_files():
+ fn=file.replace('gfx/mobs/','').replace('/', ' ').replace('.png','')
+ if file.startswith('gfx/mobs/'):
+ if file.endswith('.png') or file.endswith('.webp'):
+ name = "mob_"+fn
+ renpy.image(name, Image(file, yanchor=1.0))
+ if not name in persistent.allfiles:
+ persistent.allfiles.append((name, file))
+ allfiles.append(name)
+ continue
+ continue
+
+
+ # Load sprite images: "dialog"
+ for file in renpy.list_files():
+ fn=file.replace('gfx/dialog/','').replace('/', ' ').replace('.png','').replace('.webp','')
+ if file.startswith('gfx/dialog/'):
+ if file.endswith('.png') or file.endswith('.webp'):
+ name = "dialog_"+fn
+ renpy.image(name, Image(file, yanchor=1.0))
+ dl=dl_search(persistent.allfiles, 0, name)
+ if dl is not ERR_INVALID:
+ persistent.allfiles.append((name, file))
+ allfiles.append(name)
+ continue
+ continue
+
+
+ # Load background images: "bg"
+ for file in renpy.list_files():
+ fn=file.replace('gfx/bg/','').replace('/', ' ').replace('.png','').replace('.webp','')
+ if file.startswith('gfx/bg/'):
+ if file.endswith('.png') or file.endswith('.webp'):
+ name = "bg "+fn
+ renpy.image(name, Frame(file, 0, 0))
+ dl=dl_search(persistent.allfiles, 0, name)
+ if dl is not ERR_INVALID:
+ persistent.allfiles.append((name, file))
+ allfiles.append(name)
+ continue
+ continue
+
+
+ # Load summon images: "summon"
+ for file in renpy.list_files():
+ fn=file.replace('gfx/summons/','').replace('/', ' ').replace('.png','').replace('.webp','')
+ if file.startswith('gfx/summons/'):
+ if file.endswith('.png') or file.endswith('.webp'):
+ name = "summon_"+fn
+ renpy.image(name, Image(file, yanchor=1.0))
+ dl=dl_search(persistent.allfiles, 0, name)
+ if dl is not ERR_INVALID:
+ persistent.allfiles.append((name, file))
+ allfiles.append(name)
+ continue
+ continue
+
+ def star_write(am):
+ i, st = 0, ""
+ while i < am:
+ i+=1
+ st+="★"
+ return st
+
+
+ # Overrides renpy.image() method
+ def new_img(name, where):
+ # d: Downloaded path
+ if not isinstance(name, tuple):
+ name = tuple(name.split())
+
+ #d = renpy.renpy.easy.displayable(where)
+ if renpy.android:
+ d=ExtraImage(where)
+ else:
+ d = renpy.renpy.easy.displayable(where)
+ renpy.renpy.display.image.register_image(name, d)
+ return
+
+ # Retrieves Ren'Py displayable name associated to PATH
+ def get_img(path):
+ # Search for the image name
+ val=img_regex(path, True)
+ show_img(val, False)
+
+ valb=dl_search(persistent.allfiles, 0, val)[1]
+ if valb == ERR_INVALID:
+ print ("Invalid Image: %s (%s)" % (path, val))
+ return "gfx/spinner.png"
+ return valb
+
+ # Overrides renpy.show() and renpy.image() methods
+ # Missing: transient=False, munge_name=True
+ def show_img(img, show=True, at_list=[ ], tag=None, zorder=None, behind=[ ], atl=None, what=None, layer=None, ext=".png"):
+ global tr_loading
+ # Image exists, display it
+ if img in allfiles:
+ if show:
+ renpy.show(img, at_list=at_list, tag=tag, zorder=zorder, behind=behind, atl=atl, what=what, layer=layer)
+ return
+
+ # Have we downloaded this image previously?
+ path=dl_search(persistent.allfiles, 0, img)
+ print str(path)
+
+ # Image doesn't exists, we must download it
+ while (path == ERR_INVALID):
+ tr_loading=True
+ # Latest version converts these formats to WebP
+ if ext in [".png", ".jpg", ".jpeg"]:
+ ext=".webp"
+ # Otherwise, preserve extension.
+ if renpy.android:
+ addr="extra_%s%s" % (img.replace(" ", "_"), ext)
+ else:
+ addr="extra/%s%s" % (img.replace(" ", "_"), ext)
+
+ f=open(get_path(addr), "w")
+ stdout("Downloading additional file: %s" % img.replace(" ", "_"))
+ x=requests.get("http://%s:%d/%s?token=%s" % (HOST, UPDP, img.replace(" ", "_"), get_token())) # , timeout=8.0 → Need to handle sudden death
+ if x.status_code == 200:
+ f.write(x.content)
+ f.close()
+ # Android needs paths to be saved by full
+ if renpy.android:
+ addr=get_path(addr)
+ path=((img, addr))
+ persistent.allfiles.append(path)
+ else:
+ try:
+ retry=renpy.call_screen("confirm", "Error downloading file.\nError Code: %d\n\nRetry?" % x.status_code, Return(True), Return(False))
+ if not retry:
+ if tag is None:
+ tag=img
+ path=None
+ if show:
+ renpy.show("spinner", at_list=at_list, tag=tag, zorder=zorder, behind=behind, atl=atl, what=what, layer=layer) # TODO Show error
+ return
+ # TODO: “Retry?”
+ except:
+ print("Failed, trying again")
+
+ # Image exists, but wasn't loaded yet
+ if (path != ERR_INVALID and path is not None):
+ print "Detected not loaded image"
+ # Valid Image Extensions: PNG, JPG, JPEG, GIF, WEBP
+ if ext in [".png", ".jpg", ".jpeg", ".gif", ".webp"]:
+ # Maybe it is an unit
+ if img.startswith("unit_"):
+ new_img(img, card_composite(img.replace("unit_", ""), path[1]))
+ else:
+ new_img(img, path[1])
+
+ stdout("registered image: "+path[1])
+ allfiles.append(img)
+ if show:
+ renpy.show(img, at_list=at_list, tag=tag, zorder=zorder, behind=behind, atl=atl, what=what, layer=layer)
+ tr_loading=False
+ return
+
+ # Something went wrong
+ stdout("show_img reached abnormal ending")
+ return
+
+ ##########################################################
+ # Other Music
+ MUSIC_BATTLE=RetString("sfx/bgm03.mp3")
+ MUSIC_BOSS=RetString("sfx/bgm04.mp3")
+ MUSIC_PARTY=RetString("sfx/bgm02.mp3")
+ #MUSIC_PARTY=ExecuteOnCall(get_sfx, "sfx_bgm05", ".mp3")#"sfx/bgm05.mp3"
+ MUSIC_VICTORY=RetString("sfx/bgm06.mp3")
+ MUSIC_WORLDMAP=RetString("sfx/bgm02.mp3")
+ #MUSIC_WORLDMAP=ExecuteOnCall(get_sfx, "sfx_bgm07", ".mp3")#"sfx/bgm07.mp3"
+ MUSIC_PROLOGUE01=RetString("sfx/regnum.mp3")
+ MUSIC_PROLOGUE02=RetString("sfx/prologue.mp3")
+ MUSIC_PROLOGUE03=RetString("sfx/shining.mp3")
+
+ ws = None
+
diff --git a/game/04_init.rpy b/game/04_init.rpy
new file mode 100644
index 0000000..7cbf54a
--- /dev/null
+++ b/game/04_init.rpy
@@ -0,0 +1,75 @@
+########################################################################################
+# 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
+
+init python:
+ # FIXME: Drop dead if session_id mismatches (new "token")
+ # Same as ondata?
+ def onmsg(self, message):
+ global tr_load, tr_val, tr_busy
+ stdout("Server : " + message)
+
+ # Inform whatever is waiting for us that a reply was obtained
+ tr_load=True
+ tr_val=str(received)
+ # We do not clean tr_busy here
+ return
+
+ def ondata(self, msg, dtype, cont):
+ #print("data received")
+ """
+ on_data: callback object which is called when a message received.
+ This is called before on_message or on_cont_message,
+ and then on_message or on_cont_message is called.
+ on_data has 4 argument.
+ The 1st argument is this class object.
+ The 2nd argument is utf-8 string which we get from the server.
+ The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came.
+ The 4th argument is continue flag. if 0, the data continue
+ """
+ #renpy.notify(msg)
+ return
+
+ def onopen(self):
+ global tr_busy, tr_load, tr_val, HOST
+ print("Opening connection to %s" % HOST)
+ tr_load=True
+ tr_val=""
+ tr_busy=False
+ self.send("PING")
+
+ def onerror(self, err):
+ stdout("ERROR RECEIVED")
+ stdout("More details: %s" % repr(err))
+ stdout("An error happened: %s" % str(err))
+ # FIXME: If such error happen, the game never dies
+ # This is a huge problem o.o
+ while True:
+ renpy.call_screen("msgbox",
+ "An unrecoverable error happened.\nPress OK to return to main menu screen.")
+ #raise KeyboardException(str(err))
+ #raise err
+ # FIXME: Not working, and the loop also does not work
+ # Maybe because it causes an Exception?
+ # I wonder if I can/should run this in a non-daemon thread?
+ # But I sense the problem is not here but a level above
+ #renpy.quit(relaunch=True, status=1)
+ sdelay(1.0)
+ return 1
+
diff --git a/game/05_init.rpy b/game/05_init.rpy
new file mode 100644
index 0000000..15d24b6
--- /dev/null
+++ b/game/05_init.rpy
@@ -0,0 +1,30 @@
+########################################################################################
+# 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
+
+label prestart:
+ python:
+ tr_busy=True
+ tr_val=""
+ tr_load=False
+ tr_uptodate=False
+ tr_memcheck=False
+ session_id=uuid.uuid4().hex
+ return
+
diff --git a/game/defs.rpy b/game/defs.rpy
index 54b94fc..41afa76 100644
--- a/game/defs.rpy
+++ b/game/defs.rpy
@@ -18,832 +18,12 @@
########################################################################################
# Definitions
-init -3 python:
- renpy.add_python_directory("python-extra")
- import requests, zlib, base64, sys, copy, uuid, time
- import websock as wsock
- # 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 or PYTHON_VERSION > 3000):
- raise Exception("WARNING: Python version is not 2.7\nStrange bugs may happen on your client.\n\nClick on \"Ignore\" to continue.\nClick on \"Quit\" to exit.")
- # Why setting this...?
- ApTimer=""
- # Configuration
- config.autoreload = False
- config.save_on_mobile_background = False
- persistent.release_name = "Christmas"
- 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.nothreading=False
- persistent.ssl_enabled=False
- else:
- persistent.serverlist.append(["Localhost", "localhost", 61000])
- if (persistent.allfiles is None):
- persistent.allfiles=[]
- allfiles=[]
- HOST=str(persistent.host)
- PORT=str(persistent.port)
- UPDP=10302
- FAILUREMSG="Request failed, Please try again"
- OFFLINEMSG="401 Unauthorized"
- OKMSG="200 OK"
- TIMEOUT_INTERVAL=3.0
- MAX_IRC_BUFFER=50
- SSL_IS_BROKEN=False
- CERT_NONE=0
- debug=copy.copy(config.developer)
- # Error Codes
- ERR_JSONDECODER=101
- ERR_LOGIN_DEFAULT=102
- ERR_INVALID=103
- ERR_TIMEOUT=104
- ERR_NOGEMS=105
- ERR_INVFULL=106
- ERR_OK=200
- # 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"
- # 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_Assassin =2
- Job_Mage =3
- Job_Archer =4
- Job_Gunner =5
-
- # IRC flags
- IRC_AUTH_NONE =0
- IRC_AUTH_USER =1
- IRC_AUTH_NICK =2
- IRC_AUTH_CHAN =3
-
- # 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
-
-
- # 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, 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 True or renpy.android:
- 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 True or renpy.android:
- path=path.replace("/", "_")
- #return renpy.loader.get_path(path)
- return renpy.config.savedir + "/" + path
- else:
- return renpy.loader.transfn(path)
-
- # URL3 Function
- def GAME_UPDATER():
- global tr_load
- tr_load=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')
- 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." % x.text) # TODO: Show this beautifully?
- # TODO: Should we set a "ver"?
-
- if (int(persistent.version) < ver):
- # 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')
- 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')
- 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')
- 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')
- 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')
- 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')
- f.write(x.text)
- f.close()
-
- persistent.version=ver
- stdout("Update complete")
-
- # Download server news
- # Handled by GAME_LOADER
-
- tr_uptodate=True
- tr_load=True
- return tr_load
-
-
-
-
-
-############################################################################
-init -1 python:
- import socket, time, json
- # Android might have special SSL problems
- if renpy.android:
- try:
- import ssl
- except ImportError:
- SSL_IS_BROKEN=True
- stdout("Android SSL misbehavior detected")
- import _openssl as ssl
- else:
- import ssl
- print("Using system-wide SSL implementation...")
-
- # wsock already imported
-
- stdout("======================= %s %s %s" % (config.name, config.version, persistent.release_name))
- print "[STDBY] Loading Basic functions......."
-
- # Search for array[?][key]==search in an array of dicts
- # Returns the dictionary, or returns ERR_INVALID
- def dl_search(array, key, search):
- try:
- r=(item for item in array if item[key] == search).next()
- except:
- r=ERR_INVALID
- if r is None:
- r=ERR_INVALID
- stdout("dlsearch: r is None")
- return r
-
- def check_fail(raw):
- global debug, FAILUREMSG
-
- if (debug):
- stdout(str(raw))
- if (raw == FAILUREMSG):
- return True
- return False
-
- def json_decode(raw):
- global ERRNO
-
- if (check_fail(raw)):
- return ERR_LOGIN_DEFAULT
-
- # TODO Move this to check_fail and rewrite check_fail
- # ERR_OFF should be handled top-level no? With full_restart()
- if raw in ERRNO:
- return raw
-
- try:
- return int(raw)
- except:
- pass
-
- # Maybe base 64
- try:
- rw=base64.b64decode(raw)
- raw=rw
- if (debug):
- print "base64 decoded"
- except:
- pass
-
- # Maybe zlib compressed
- try:
- rw=zlib.decompress(raw)
- raw=rw
- if (debug):
- print str(raw)
- except:
- pass
-
- # Decode JSON
- try:
- return json.loads(raw)
- except:
- return ERR_JSONDECODER
-
- def get_token():
- try:
- t=Player['token']
- except:
- t="0" #"f528764d624db129b32c21fbca0cb8d6"
- return t
-
- def login():
- global Player
- stdout("Login requested for %s" % logindata())
- raw=send_packet("login", logindata())
-
- Player=json_decode(raw)
- if (Player == ERR_JSONDECODER):
- return ERR_JSONDECODER
- if (Player == ERR_LOGIN_DEFAULT):
- return ERR_LOGIN_DEFAULT
-
- try:
- Player["inv"]=dlist()
- except:
- pass
- return Player["code"]
-
- def send_packet_now(packet, args="", legacy=False):
- global tr_load, tr_val, tr_busy
- global ws
-
- stdout("Sending: %s" % packet)
- try:
- ws.send(get_token() + ";" + packet + ";" + args)
- except:
- stdout("Failed to send message!!")
- # FIXME set tr_val/tr_load/tr_busy to good values
- return False
- # TODO: Wait for onmsg
- # TODO: ping packets
- return True
-
- def send_packet(packet, args=""):
- global tr_load, tr_val, tr_busy
- # TODO: if tr_busy already true, wait until it is made false
- while tr_busy:
- sdelay()
-
- # Book processing space for ourselves
- tr_busy=True
- tr_load=False
- tr_val=None
-
- # This is a secret variable which disables threading and queue
- # This may cause hangs and other issues.
- if persistent.nothreading:
- send_packet_now(packet, args)
- tr_busy=False
- val=tr_val
- return val
-
- timeout=0.0
- renpy.show("spinner", at_list=[truecenter])
- renpy.invoke_in_thread(send_packet_now, packet, args)
- while not tr_load:
- sdelay() # FIXME: This can cause errors in mobile?
- timeout+=0.02
-
- if timeout >= TIMEOUT_INTERVAL:
- # FIXME: What if a screen is already being displayed? BUG
- try:
- renpy.call_screen("msgbox", "Error Code: %d\n\nApplication timeout, click to try again" % (ERR_TIMEOUT))
- timeout=0.0
- except:
- if not "ping" in packet.lower():
- stdout("WARNING, ILLEGAL PACKET ON SCREEN TIME: %s" % packet)
- pass
-
- renpy.hide("spinner")
- val=tr_val
- tr_busy=False
- del tr_val
-
- print "value obtained"
- if (val is None):
- return ERR_INVALID
- return val
-
- def GAME_LOADER():
- global allunitsbase, allunits, allquests, allstory, allworld, alltaverns
- global allnews, tr_load
- tr_load=False
-
- # Wait until everything is up to date
- while not tr_uptodate:
- sdelay()
-
- # Load unit data
- allunitsbase=json.loads(requests.get("http://"+HOST+'/units.json').text)
- # f=open(get_path_if_exists("units.json"), "r")
- # allunitsbase=json.load(f)
- # f.close()
-
- # Reorder unit data
- allunits={}
- for j in allunitsbase:
- allunits[j["unit_id"]]=j
-
- # Load summons data
- f=open(get_path_if_exists("summons.json"), "r")
- allworld=json.load(f)
- f.close()
-
- # Load quest data
- f=open(get_path_if_exists("quests.json"), "r")
- allquests=json.load(f)
- f.close()
-
- # Load story data
- f=open(get_path_if_exists("story.json"), "r")
- allstory=json.load(f)
- f.close()
-
- # Load world data
- f=open(get_path_if_exists("world.json"), "r")
- allworld=json.load(f)
- f.close()
-
- # Load tavern data
- f=open(get_path_if_exists("bar.json"), "r")
- alltaverns=json.load(f)
- f.close()
-
- # Load server news
- try:
- allnews=json.loads(requests.get("http://"+HOST+'/news.json', timeout=5.0).text)
- except:
- allnews=[]
- pass
-
- stdout("[OK] Game data loaded to memory")
- tr_load=True
- tr_memcheck=True
- return tr_load
-
-
-
-
-
-############################################################################
-# This is the JSON data formatter
-init python:
- import json
- def logindata():
- global password
- # TODO: Obtain user id based on device
- return """{
- "passwd": "%s",
- "version": "%s"
- }""" % (password, config.version)
-
-
- def recruitdata(t, a):
- return """{
- "tavern": %d,
- "amount": %d
- }""" % (t, a)
-
-
- def questdata(q, p):
- return """{
- "quest_id": %d,
- "party_id": %d
- }""" % (q, p)
-
-
- def battledata(u, s):
- return """{
- "unit": %s,
- "sphere": %s
- }""" % (json.dumps(u), json.dumps(s))
-
- def card_composite(cid, path):
- # We need to try to get rarity
- try:
- r=allunits[int(cid)]["rare"]
- except:
- r=4 # FIXME
-
- # We need to try to get the element
- try:
- e=allunits[int(cid)]["attribute"]
- print str(e)
- except:
- e=0
-
- return Composite(
- (640, 960),
- (0, 0), "gfx/cards/bg.png",
- (0, 0), path,
- (0, 0), "gfx/cards/"+str(r)+".png",
- (0, 0), "gfx/cards/ele/"+str(e)+".png")
- # size,)
-
- # TODO: square_ and gfx/square/units
- # Converts regular image filepaths to displayables names and vice-versa
- def img_regex(name, reverse=False):
- if not reverse:
- # Extension is ommited, add them yourself!
- if name.startswith("unit_"):
- return "gfx/units/%s" % name.replace("unit_")
- elif name.startswith("mob_"):
- return "gfx/mobs/%s" % name.replace("mob_")
- elif name.startswith("dialog_"):
- return "gfx/dialog/%s" % name.replace("dialog_")
- elif name.startswith("bg "):
- return "gfx/bg/%s" % name.replace("bg ")
- elif name.startswith("summon_"):
- return "gfx/summons/%s" % name.replace("summon_")
- else:
- print("ERROR: Not a regular filename")
- return name
- else:
- if name.startswith("gfx/units/"):
- return "unit_%s" % name
- elif name.startswith("gfx/mobs/"):
- return "mob_%s" % name
- elif name.startswith("gfx/dialog/"):
- return "dialog_%s" % name
- elif name.startswith("gfx/bg/"):
- return "bg %s" % name
- elif name.startswith("gfx/summons/"):
- return "summon_%s" % name
- else:
- print("ERROR: Not a regular display name")
- return name
-
- # Loads a sound called VAL
- def get_sfx(val, ext):
- # Search for the sound
- show_img(val, False, ext=ext)
-
- valb=dl_search(persistent.allfiles, 0, val)[1]
- if valb == ERR_INVALID:
- print ("Invalid Sound: %s (%s)" % (path, val))
- return "sfx/regnum.mp3" # FIXME
- return valb
-
- # Load sprite images: "unit"
- for file in renpy.list_files():
- fn=file.replace('gfx/units/','').replace('/', ' ').replace('.png','')
- if file.startswith('gfx/units/'):
- if file.endswith('.png') or file.endswith('.webp'):
- name = "unit_"+fn
- #renpy.image(name, Image(file, yanchor=1.0))
- renpy.image(name, card_composite(fn, file))
-
- dl=dl_search(persistent.allfiles, 0, name)
- if dl is not ERR_INVALID:
- persistent.allfiles.append((name, file))
- allfiles.append(name)
- continue
- continue
-
- # Load sprite images: "mob"
- for file in renpy.list_files():
- fn=file.replace('gfx/mobs/','').replace('/', ' ').replace('.png','')
- if file.startswith('gfx/mobs/'):
- if file.endswith('.png') or file.endswith('.webp'):
- name = "mob_"+fn
- renpy.image(name, Image(file, yanchor=1.0))
- if not name in persistent.allfiles:
- persistent.allfiles.append((name, file))
- allfiles.append(name)
- continue
- continue
-
-
- # Load sprite images: "dialog"
- for file in renpy.list_files():
- fn=file.replace('gfx/dialog/','').replace('/', ' ').replace('.png','').replace('.webp','')
- if file.startswith('gfx/dialog/'):
- if file.endswith('.png') or file.endswith('.webp'):
- name = "dialog_"+fn
- renpy.image(name, Image(file, yanchor=1.0))
- dl=dl_search(persistent.allfiles, 0, name)
- if dl is not ERR_INVALID:
- persistent.allfiles.append((name, file))
- allfiles.append(name)
- continue
- continue
-
-
- # Load background images: "bg"
- for file in renpy.list_files():
- fn=file.replace('gfx/bg/','').replace('/', ' ').replace('.png','').replace('.webp','')
- if file.startswith('gfx/bg/'):
- if file.endswith('.png') or file.endswith('.webp'):
- name = "bg "+fn
- renpy.image(name, Frame(file, 0, 0))
- dl=dl_search(persistent.allfiles, 0, name)
- if dl is not ERR_INVALID:
- persistent.allfiles.append((name, file))
- allfiles.append(name)
- continue
- continue
-
-
- # Load summon images: "summon"
- for file in renpy.list_files():
- fn=file.replace('gfx/summons/','').replace('/', ' ').replace('.png','').replace('.webp','')
- if file.startswith('gfx/summons/'):
- if file.endswith('.png') or file.endswith('.webp'):
- name = "summon_"+fn
- renpy.image(name, Image(file, yanchor=1.0))
- dl=dl_search(persistent.allfiles, 0, name)
- if dl is not ERR_INVALID:
- persistent.allfiles.append((name, file))
- allfiles.append(name)
- continue
- continue
-
- def star_write(am):
- i, st = 0, ""
- while i < am:
- i+=1
- st+="★"
- return st
-
-
- # Overrides renpy.image() method
- def new_img(name, where):
- # d: Downloaded path
- if not isinstance(name, tuple):
- name = tuple(name.split())
-
- #d = renpy.renpy.easy.displayable(where)
- if renpy.android:
- d=ExtraImage(where)
- else:
- d = renpy.renpy.easy.displayable(where)
- renpy.renpy.display.image.register_image(name, d)
- return
-
- # Retrieves Ren'Py displayable name associated to PATH
- def get_img(path):
- # Search for the image name
- val=img_regex(path, True)
- show_img(val, False)
-
- valb=dl_search(persistent.allfiles, 0, val)[1]
- if valb == ERR_INVALID:
- print ("Invalid Image: %s (%s)" % (path, val))
- return "gfx/spinner.png"
- return valb
-
- # Overrides renpy.show() and renpy.image() methods
- # Missing: transient=False, munge_name=True
- def show_img(img, show=True, at_list=[ ], tag=None, zorder=None, behind=[ ], atl=None, what=None, layer=None, ext=".png"):
- global tr_loading
- # Image exists, display it
- if img in allfiles:
- if show:
- renpy.show(img, at_list=at_list, tag=tag, zorder=zorder, behind=behind, atl=atl, what=what, layer=layer)
- return
-
- # Have we downloaded this image previously?
- path=dl_search(persistent.allfiles, 0, img)
- print str(path)
-
- # Image doesn't exists, we must download it
- while (path == ERR_INVALID):
- tr_loading=True
- # Latest version converts these formats to WebP
- if ext in [".png", ".jpg", ".jpeg"]:
- ext=".webp"
- # Otherwise, preserve extension.
- if renpy.android:
- addr="extra_%s%s" % (img.replace(" ", "_"), ext)
- else:
- addr="extra/%s%s" % (img.replace(" ", "_"), ext)
-
- f=open(get_path(addr), "w")
- stdout("Downloading additional file: %s" % img.replace(" ", "_"))
- x=requests.get("http://%s:%d/%s?token=%s" % (HOST, UPDP, img.replace(" ", "_"), get_token())) # , timeout=8.0 → Need to handle sudden death
- if x.status_code == 200:
- f.write(x.content)
- f.close()
- # Android needs paths to be saved by full
- if renpy.android:
- addr=get_path(addr)
- path=((img, addr))
- persistent.allfiles.append(path)
- else:
- try:
- retry=renpy.call_screen("confirm", "Error downloading file.\nError Code: %d\n\nRetry?" % x.status_code, Return(True), Return(False))
- if not retry:
- if tag is None:
- tag=img
- path=None
- if show:
- renpy.show("spinner", at_list=at_list, tag=tag, zorder=zorder, behind=behind, atl=atl, what=what, layer=layer) # TODO Show error
- return
- # TODO: “Retry?”
- except:
- print("Failed, trying again")
-
- # Image exists, but wasn't loaded yet
- if (path != ERR_INVALID and path is not None):
- print "Detected not loaded image"
- # Valid Image Extensions: PNG, JPG, JPEG, GIF, WEBP
- if ext in [".png", ".jpg", ".jpeg", ".gif", ".webp"]:
- # Maybe it is an unit
- if img.startswith("unit_"):
- new_img(img, card_composite(img.replace("unit_", ""), path[1]))
- else:
- new_img(img, path[1])
-
- stdout("registered image: "+path[1])
- allfiles.append(img)
- if show:
- renpy.show(img, at_list=at_list, tag=tag, zorder=zorder, behind=behind, atl=atl, what=what, layer=layer)
- tr_loading=False
- return
-
- # Something went wrong
- stdout("show_img reached abnormal ending")
- return
-
- ##########################################################
- # Other Music
- MUSIC_BATTLE=RetString("sfx/bgm03.mp3")
- MUSIC_BOSS=RetString("sfx/bgm04.mp3")
- MUSIC_PARTY=RetString("sfx/bgm02.mp3")
- #MUSIC_PARTY=ExecuteOnCall(get_sfx, "sfx_bgm05", ".mp3")#"sfx/bgm05.mp3"
- MUSIC_VICTORY=RetString("sfx/bgm06.mp3")
- MUSIC_WORLDMAP=RetString("sfx/bgm02.mp3")
- #MUSIC_WORLDMAP=ExecuteOnCall(get_sfx, "sfx_bgm07", ".mp3")#"sfx/bgm07.mp3"
- MUSIC_PROLOGUE01=RetString("sfx/regnum.mp3")
- MUSIC_PROLOGUE02=RetString("sfx/prologue.mp3")
- MUSIC_PROLOGUE03=RetString("sfx/shining.mp3")
diff --git a/game/misc.rpy b/game/misc.rpy
index 0313cc5..13e6756 100644
--- a/game/misc.rpy
+++ b/game/misc.rpy
@@ -227,7 +227,10 @@ label clear_cache:
$renpy.full_restart()
jump start
-image spinner:
+image spinner = "gfx/spinner.png"
+
+init 5 python:
+ """
"gfx/spinner.png"
rotate 30.0
pause 0.05
@@ -265,4 +268,6 @@ image spinner:
#rotate 360.0
pause 0.05
repeat
+ """
+ pass
diff --git a/game/script.rpy b/game/script.rpy
index 6009b2d..b224474 100644
--- a/game/script.rpy
+++ b/game/script.rpy
@@ -21,59 +21,6 @@ by sending an email to {a=mailto:dmca@tmw2.org}dmca@tmw2.org{/a}.{/color}\n\
No warranties.{/b}{fast}"
return
-init python:
- # FIXME: Drop dead if session_id mismatches (new "token")
- # Same as ondata?
- def onmsg(self, message):
- global tr_load, tr_val, tr_busy
- stdout("Server : " + message)
-
- # Inform whatever is waiting for us that a reply was obtained
- tr_load=True
- tr_val=str(received)
- # We do not clean tr_busy here
- return
-
- def ondata(self, msg, dtype, cont):
- #print("data received")
- """
- on_data: callback object which is called when a message received.
- This is called before on_message or on_cont_message,
- and then on_message or on_cont_message is called.
- on_data has 4 argument.
- The 1st argument is this class object.
- The 2nd argument is utf-8 string which we get from the server.
- The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came.
- The 4th argument is continue flag. if 0, the data continue
- """
- #renpy.notify(msg)
- return
-
- def onopen(self):
- global tr_busy, tr_load, tr_val, HOST
- print("Opening connection to %s" % HOST)
- tr_load=True
- tr_val=""
- tr_busy=False
- self.send("PING")
-
- def onerror(self, err):
- stdout("ERROR RECEIVED")
- stdout("More details: %s" % repr(err))
- stdout("An error happened: %s" % str(err))
- # FIXME: If such error happen, the game never dies
- # This is a huge problem o.o
- while True:
- renpy.call_screen("msgbox",
- "An unrecoverable error happened.\nPress OK to return to main menu screen.")
- #raise KeyboardException(str(err))
- #raise err
- # FIXME: Not working, and the loop also does not work
- # Maybe because it causes an Exception?
- # I wonder if I can/should run this in a non-daemon thread?
- #renpy.quit(relaunch=True, status=1)
- sdelay(1.0)
- return 1
# Inform you are now ingame
label start:
@@ -83,18 +30,11 @@ label start:
show spinner at truecenter
# Initial configuration
- python:
- tr_busy=True
- tr_val=""
- tr_load=False
- tr_uptodate=False
- tr_memcheck=False
- session_id=uuid.uuid4().hex
+ call prestart
#"We shall now begin testing a websocket"
python:
# FIXME: Broken code
- ws=persistent.ws
if (ws is not None):
stdout("Socket exists, trying to reuse connection...")
try:
@@ -128,8 +68,8 @@ label start:
renpy.invoke_in_thread(ws.run_forever)
except:
# Enter in infinite loop, this will never resolve
- tr_load=False
stdout("Unrecoverable error, the program is dead.")
+ tr_load=False
while not tr_load:
sdelay()
@@ -143,8 +83,6 @@ label start:
while not tr_load:
sdelay()
- hide spinner
-
# Open game
scene bg town with Dissolve(0.3)
pause 0.1
@@ -152,6 +90,16 @@ label start:
$ password=persistent.password
$ email=""
+ if config.developer:
+ menu:
+ "tr_load is [tr_load]\ntr_busy is [tr_busy]"
+ "Send ping":
+ $ send_packet_now('PING')
+ "Send formatted packet":
+ $ send_packet_now('TOKEN;PACKET;')
+ "Send invalid packet":
+ $ send_packet_now('INVALID PACKET')
+
# Check if we need to register
if persistent.password is None:
call screen welcome
@@ -219,7 +167,6 @@ label login:
python:
try:
message=int(message)
- import copy
dlcode=copy.copy(message)
except:
print "AN ERROR HAPPENED AT LOGIN TIME"