######################################################################################## # 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: WebSocket init python: # FIXME: Drop dead if session_id mismatches (new "token") # REMINDER: onmsg() runs within the thread, not the main app! def onmsg(self, message): global tr_load, tr_val, tr_busy stdout("Server : " + str(message)) # Wait wait, it might have been a SERVNOTICE! # TODO: Validate the ServNotice with token or something try: if (message.split(':')[0] == "NOTICE"): display_msgbox(str(":".join(message.split(':')[1:]))) return except: stdout("Error displaying SERVNOTICE!!") return # It could also have been an APREFRESH token! try: if (message.split(':')[0] == "APREFRESH"): Player["ap"]=(int(":".join(message.split(':')[1:]))) # TODO: Reload timer (DynamicDisplayable(countdown, length=360.0)) Player["aptime"] = now() renpy.restart_interaction() # I hope it does not explodes return except: stdout("Error enacting APREFRESH!!") return # Inform whatever is waiting for us that a reply was obtained tr_load=True tr_val=str(message) # We do not clean tr_busy here 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 send_packet_now("PING") def onerror(self, err): global TERMINATE if err == "Quit": return elif err == "K-Lined": sdelay(30.0) # We are banned, so pretend it is just taking long... # TODO FIXME: ...Might be a very bad idea. stdout("ERROR RECEIVED") stdout("More details: %s" % repr(err)) stdout("An error happened: %s" % str(err)) # NOTE: Game might die suddenly... In case it is already exiting... try: renpy.call_screen("msgbox", # "An unrecoverable error happened.\nPlease close and re-open the app.") "An unrecoverable error happened.\nPress OK to return to main menu screen.") except: pass TERMINATE=True # Relay a TERM to overlay return class GameClient(WebSocketClient): def opened(self): onopen(self) # FIXME This is also onerror def closed(self, code, reason=None): print("Closed down", code, reason) if reason is None: reason="Connection died" if reason != "Quit": onerror(self, "%s" % str(reason)) def received_message(self, m): onmsg(self, str(m)) # Be mindful of where/when using this function # Or "onmsg" may accidentally not be cast =/ def send_packet_now(packet, args=""): global ws, tr_load, tr_val, tr_busy, TERMINATE, CLOSING stdout("Sending: %s" % packet) cnt = 0 while cnt < MAX_RETRIES: cnt+=1 try: ws.send(get_token() + ";" + packet + ";" + args) break # Success attained, continue except: traceback.print_exc() stdout("FATAL ERROR, packet was not sent!") if cnt >= MAX_RETRIES: try: renpy.call_screen("msgbox", "An unrecoverable error happened.\nPlease close and re-open the app.") except: pass TERMINATE=True if not CLOSING: renpy.quit(relaunch=True) return def wait_packet(fg=True): global tr_load, tr_val, tr_busy timeout=0.0 print("Now waiting for packet!") while not tr_load: if fg: sdelay() # FIXME: This can cause errors in mobile? else: time.sleep(0.02) 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 # TODO: Maybe we should, like, _resent_ the packet? # Or just allow user to be booted to main menu? # Or even yet, send ERR_INVALID and let the game handle it? # Sooo use confirm instead of msgbox? except: stdout("ERROR: Timeout and retry failure") break val=tr_val tr_busy=False stdout("Packet received successfully.") #print("value obtained: %s" % str(val)) if (val is None): return ERR_INVALID return val def schedule_packet(): 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 return def send_packet(packet, args="", fg=True): global tr_busy, tr_load schedule_packet() send_packet_now(packet, args) return wait_packet(fg) # In past, this would keep running, in hopes of catching the program quit # and being able to kill the threads... But well, it worked poorly. # Still called "supervisor" but all it does now is init. # sslopt={"cert_reqs": CERT_NONE}) def supervisor(use_ssl): global ws stdout(_("Opening new socket...")) done=False while not done: try: if use_ssl: ws = GameClient("wss://"+HOST+":"+PORT) else: ws = GameClient("ws://"+HOST+":"+PORT) ws.connect() # May be problematic. # Specially if exception is uncaught renpy.invoke_in_thread(ws.run_forever) done=True stdout("Connection established!") except: traceback.print_exc() stdout("CONNECTION FAILED, PLEASE TRY AGAIN") display_msgbox("Connection Error. Please check internet connection.") sdelay(10.0) # It will keep trying again, ever and ever. # FIXME: sdelay(), with fixed time is a bad idea. # We should allow user to retry (no hard pause) # And we should scale up the time between attempts # Just in case server is flooded, we do not want to DoS it =/ # (And incur in firewall's wrath while at that!) # The supervisor module is now uneeded as thread was cast, so return return