########################################################################################
# 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)
try:
ws.send(get_token() + ";" + packet + ";" + args)
except:
traceback.print_exc()
stdout("FATAL ERROR, packet was not sent!")
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+":61000")
else:
ws = GameClient("ws://"+HOST+":61000")
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