########################################################################################
# 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 ondata(self, msg, dtype, cont):
#print("INFO: data received\n")
"""
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
send_packet_now("PING")
def onerror(self, err):
if err == "Quit":
return
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
# Now it 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.")
#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
# FIXME: renpy.quit() throws an exception - which in a thread is obviously not gonna work...
# I wonder if we can hack like we did with display_msgbox? Mhm.
#renpy.quit(relaunch=True, status=1)
except:
pass
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
stdout("Sending: %s" % packet)
ws.send(get_token() + ";" + packet + ";" + args)
return
def wait_packet():
global tr_load, tr_val, tr_busy
timeout=0.0
print("Now waiting for packet!")
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:
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=""):
global tr_cmd, tr_busy, tr_load
schedule_packet()
send_packet_now(packet, args)
return wait_packet()
# 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