mirror of
https://github.com/yuukiwww/taiko-web.git
synced 2024-10-22 17:05:49 +02:00
f83fab41e3
- Fixed touch multiplayer drum being too small and nameplate drawing on top of it - Fixed #lyric lines being forced to be lowercase and appearing too early - Fixed exiting session while someone else is using netplay sending you to difficulty selection of the song that the netplay person has selected - Fixed ≠MM title on song select not having the first symbol rotated - Fixed nameplate cache in song select and in game not getting cleared - Increased ping timeout for multiplayer Admin page fixes - Add meta viewport tag so that the admin pages can be opened on mobile - On song list, songs that are not enabled are now marked - Disable tabbing through diverge notes checkboxes in courses when adding or editing a song - Add Hash to song adding - Display an error if a local chart file has not been found while calculating hash - If calculating the hash results in an error, do not discard all other changes
400 lines
13 KiB
Python
400 lines
13 KiB
Python
#!/usr/bin/env python
|
|
|
|
import asyncio
|
|
import websockets
|
|
import json
|
|
import random
|
|
import sys
|
|
|
|
server_status = {
|
|
"waiting": {},
|
|
"users": [],
|
|
"invites": {}
|
|
}
|
|
consonants = "bcdfghjklmnpqrstvwxyz"
|
|
|
|
def msgobj(msg_type, value=None):
|
|
if value == None:
|
|
return json.dumps({"type": msg_type})
|
|
else:
|
|
return json.dumps({"type": msg_type, "value": value})
|
|
|
|
def status_event():
|
|
value = []
|
|
for id, userDiff in server_status["waiting"].items():
|
|
value.append({
|
|
"id": id,
|
|
"diff": userDiff["diff"]
|
|
})
|
|
return msgobj("users", value)
|
|
|
|
def get_invite():
|
|
return "".join([random.choice(consonants) for x in range(5)])
|
|
|
|
async def notify_status():
|
|
ready_users = [user for user in server_status["users"] if "ws" in user and user["action"] == "ready"]
|
|
if ready_users:
|
|
sent_msg = status_event()
|
|
await asyncio.wait([user["ws"].send(sent_msg) for user in ready_users])
|
|
|
|
async def connection(ws, path):
|
|
# User connected
|
|
user = {
|
|
"ws": ws,
|
|
"action": "ready",
|
|
"session": False,
|
|
"name": None,
|
|
"don": None
|
|
}
|
|
server_status["users"].append(user)
|
|
try:
|
|
# Notify user about other users
|
|
await ws.send(status_event())
|
|
while True:
|
|
try:
|
|
message = await asyncio.wait_for(ws.recv(), timeout=10)
|
|
except asyncio.TimeoutError:
|
|
# Keep user connected
|
|
pong_waiter = await ws.ping()
|
|
try:
|
|
await asyncio.wait_for(pong_waiter, timeout=10)
|
|
except asyncio.TimeoutError:
|
|
# Disconnect
|
|
break
|
|
except websockets.exceptions.ConnectionClosed:
|
|
# Connection closed
|
|
break
|
|
else:
|
|
# Message received
|
|
try:
|
|
data = json.loads(message)
|
|
except json.decoder.JSONDecodeError:
|
|
data = {}
|
|
action = user["action"]
|
|
msg_type = data["type"] if "type" in data else None
|
|
value = data["value"] if "value" in data else None
|
|
if action == "ready":
|
|
# Not playing or waiting
|
|
if msg_type == "join":
|
|
if value == None:
|
|
continue
|
|
waiting = server_status["waiting"]
|
|
id = value["id"] if "id" in value else None
|
|
diff = value["diff"] if "diff" in value else None
|
|
user["name"] = value["name"] if "name" in value else None
|
|
user["don"] = value["don"] if "don" in value else None
|
|
if not id or not diff:
|
|
continue
|
|
if id not in waiting:
|
|
# Wait for another user
|
|
user["action"] = "waiting"
|
|
user["gameid"] = id
|
|
waiting[id] = {
|
|
"user": user,
|
|
"diff": diff
|
|
}
|
|
await ws.send(msgobj("waiting"))
|
|
else:
|
|
# Join the other user and start game
|
|
user["name"] = value["name"] if "name" in value else None
|
|
user["don"] = value["don"] if "don" in value else None
|
|
user["other_user"] = waiting[id]["user"]
|
|
waiting_diff = waiting[id]["diff"]
|
|
del waiting[id]
|
|
if "ws" in user["other_user"]:
|
|
user["action"] = "loading"
|
|
user["other_user"]["action"] = "loading"
|
|
user["other_user"]["other_user"] = user
|
|
user["other_user"]["player"] = 1
|
|
user["player"] = 2
|
|
await asyncio.wait([
|
|
ws.send(msgobj("gameload", {"diff": waiting_diff, "player": 2})),
|
|
user["other_user"]["ws"].send(msgobj("gameload", {"diff": diff, "player": 1})),
|
|
ws.send(msgobj("name", {
|
|
"name": user["other_user"]["name"],
|
|
"don": user["other_user"]["don"]
|
|
})),
|
|
user["other_user"]["ws"].send(msgobj("name", {
|
|
"name": user["name"],
|
|
"don": user["don"]
|
|
}))
|
|
])
|
|
else:
|
|
# Wait for another user
|
|
del user["other_user"]
|
|
user["action"] = "waiting"
|
|
user["gameid"] = id
|
|
waiting[id] = {
|
|
"user": user,
|
|
"diff": diff
|
|
}
|
|
await ws.send(msgobj("waiting"))
|
|
# Update others on waiting players
|
|
await notify_status()
|
|
elif msg_type == "invite":
|
|
if value and "id" in value and value["id"] == None:
|
|
# Session invite link requested
|
|
invite = get_invite()
|
|
server_status["invites"][invite] = user
|
|
user["action"] = "invite"
|
|
user["session"] = invite
|
|
user["name"] = value["name"] if "name" in value else None
|
|
user["don"] = value["don"] if "don" in value else None
|
|
await ws.send(msgobj("invite", invite))
|
|
elif value and "id" in value and value["id"] in server_status["invites"]:
|
|
# Join a session with the other user
|
|
user["name"] = value["name"] if "name" in value else None
|
|
user["don"] = value["don"] if "don" in value else None
|
|
user["other_user"] = server_status["invites"][value["id"]]
|
|
del server_status["invites"][value["id"]]
|
|
if "ws" in user["other_user"]:
|
|
user["other_user"]["other_user"] = user
|
|
user["action"] = "invite"
|
|
user["session"] = value["id"]
|
|
user["other_user"]["player"] = 1
|
|
user["player"] = 2
|
|
await asyncio.wait([
|
|
ws.send(msgobj("session", {"player": 2})),
|
|
user["other_user"]["ws"].send(msgobj("session", {"player": 1})),
|
|
ws.send(msgobj("invite")),
|
|
ws.send(msgobj("name", {
|
|
"name": user["other_user"]["name"],
|
|
"don": user["other_user"]["don"]
|
|
})),
|
|
user["other_user"]["ws"].send(msgobj("name", {
|
|
"name": user["name"],
|
|
"don": user["don"]
|
|
}))
|
|
])
|
|
else:
|
|
del user["other_user"]
|
|
await ws.send(msgobj("gameend"))
|
|
else:
|
|
# Session code is invalid
|
|
await ws.send(msgobj("gameend"))
|
|
elif action == "waiting" or action == "loading" or action == "loaded":
|
|
# Waiting for another user
|
|
if msg_type == "leave":
|
|
# Stop waiting
|
|
if user["session"]:
|
|
if "other_user" in user and "ws" in user["other_user"]:
|
|
user["action"] = "songsel"
|
|
await asyncio.wait([
|
|
ws.send(msgobj("left")),
|
|
user["other_user"]["ws"].send(msgobj("users", []))
|
|
])
|
|
else:
|
|
user["action"] = "ready"
|
|
user["session"] = False
|
|
await asyncio.wait([
|
|
ws.send(msgobj("gameend")),
|
|
ws.send(status_event())
|
|
])
|
|
else:
|
|
del server_status["waiting"][user["gameid"]]
|
|
del user["gameid"]
|
|
user["action"] = "ready"
|
|
await asyncio.wait([
|
|
ws.send(msgobj("left")),
|
|
notify_status()
|
|
])
|
|
if action == "loading":
|
|
if msg_type == "gamestart":
|
|
user["action"] = "loaded"
|
|
if user["other_user"]["action"] == "loaded":
|
|
user["action"] = "playing"
|
|
user["other_user"]["action"] = "playing"
|
|
sent_msg = msgobj("gamestart")
|
|
await asyncio.wait([
|
|
ws.send(sent_msg),
|
|
user["other_user"]["ws"].send(sent_msg)
|
|
])
|
|
elif action == "playing":
|
|
# Playing with another user
|
|
if "other_user" in user and "ws" in user["other_user"]:
|
|
if msg_type == "note"\
|
|
or msg_type == "drumroll"\
|
|
or msg_type == "branch"\
|
|
or msg_type == "gameresults":
|
|
await user["other_user"]["ws"].send(msgobj(msg_type, value))
|
|
elif msg_type == "songsel" and user["session"]:
|
|
user["action"] = "songsel"
|
|
user["other_user"]["action"] = "songsel"
|
|
sent_msg1 = msgobj("songsel")
|
|
sent_msg2 = msgobj("users", [])
|
|
await asyncio.wait([
|
|
ws.send(sent_msg1),
|
|
ws.send(sent_msg2),
|
|
user["other_user"]["ws"].send(sent_msg1),
|
|
user["other_user"]["ws"].send(sent_msg2)
|
|
])
|
|
elif msg_type == "gameend":
|
|
# User wants to disconnect
|
|
user["action"] = "ready"
|
|
user["other_user"]["action"] = "ready"
|
|
sent_msg1 = msgobj("gameend")
|
|
sent_msg2 = status_event()
|
|
await asyncio.wait([
|
|
ws.send(sent_msg1),
|
|
ws.send(sent_msg2),
|
|
user["other_user"]["ws"].send(sent_msg1),
|
|
user["other_user"]["ws"].send(sent_msg2)
|
|
])
|
|
del user["other_user"]["other_user"]
|
|
del user["other_user"]
|
|
else:
|
|
# Other user disconnected
|
|
user["action"] = "ready"
|
|
user["session"] = False
|
|
await asyncio.wait([
|
|
ws.send(msgobj("gameend")),
|
|
ws.send(status_event())
|
|
])
|
|
elif action == "invite":
|
|
if msg_type == "leave":
|
|
# Cancel session invite
|
|
if user["session"] in server_status["invites"]:
|
|
del server_status["invites"][user["session"]]
|
|
user["action"] = "ready"
|
|
user["session"] = False
|
|
if "other_user" in user and "ws" in user["other_user"]:
|
|
user["other_user"]["action"] = "ready"
|
|
user["other_user"]["session"] = False
|
|
sent_msg = status_event()
|
|
await asyncio.wait([
|
|
ws.send(msgobj("left")),
|
|
ws.send(sent_msg),
|
|
user["other_user"]["ws"].send(msgobj("gameend")),
|
|
user["other_user"]["ws"].send(sent_msg)
|
|
])
|
|
else:
|
|
await asyncio.wait([
|
|
ws.send(msgobj("left")),
|
|
ws.send(status_event())
|
|
])
|
|
elif msg_type == "songsel" and "other_user" in user:
|
|
if "ws" in user["other_user"]:
|
|
user["action"] = "songsel"
|
|
user["other_user"]["action"] = "songsel"
|
|
sent_msg = msgobj(msg_type)
|
|
await asyncio.wait([
|
|
ws.send(sent_msg),
|
|
user["other_user"]["ws"].send(sent_msg)
|
|
])
|
|
else:
|
|
user["action"] = "ready"
|
|
user["session"] = False
|
|
await asyncio.wait([
|
|
ws.send(msgobj("gameend")),
|
|
ws.send(status_event())
|
|
])
|
|
elif action == "songsel":
|
|
# Session song selection
|
|
if "other_user" in user and "ws" in user["other_user"]:
|
|
if msg_type == "songsel" or msg_type == "catjump":
|
|
# Change song select position
|
|
if user["other_user"]["action"] == "songsel" and type(value) is dict:
|
|
value["player"] = user["player"]
|
|
sent_msg = msgobj(msg_type, value)
|
|
await asyncio.wait([
|
|
ws.send(sent_msg),
|
|
user["other_user"]["ws"].send(sent_msg)
|
|
])
|
|
elif msg_type == "crowns" or msg_type == "getcrowns":
|
|
if user["other_user"]["action"] == "songsel":
|
|
sent_msg = msgobj(msg_type, value)
|
|
await asyncio.wait([
|
|
user["other_user"]["ws"].send(sent_msg)
|
|
])
|
|
elif msg_type == "join":
|
|
# Start game
|
|
if value == None:
|
|
continue
|
|
id = value["id"] if "id" in value else None
|
|
diff = value["diff"] if "diff" in value else None
|
|
if not id or not diff:
|
|
continue
|
|
if user["other_user"]["action"] == "waiting":
|
|
user["action"] = "loading"
|
|
user["other_user"]["action"] = "loading"
|
|
await asyncio.wait([
|
|
ws.send(msgobj("gameload", {"diff": user["other_user"]["gamediff"]})),
|
|
user["other_user"]["ws"].send(msgobj("gameload", {"diff": diff}))
|
|
])
|
|
else:
|
|
user["action"] = "waiting"
|
|
user["gamediff"] = diff
|
|
await user["other_user"]["ws"].send(msgobj("users", [{
|
|
"id": id,
|
|
"diff": diff
|
|
}]))
|
|
elif msg_type == "gameend":
|
|
# User wants to disconnect
|
|
user["action"] = "ready"
|
|
user["session"] = False
|
|
user["other_user"]["action"] = "ready"
|
|
user["other_user"]["session"] = False
|
|
sent_msg1 = msgobj("gameend")
|
|
sent_msg2 = status_event()
|
|
await asyncio.wait([
|
|
ws.send(sent_msg1),
|
|
user["other_user"]["ws"].send(sent_msg1)
|
|
])
|
|
await asyncio.wait([
|
|
ws.send(sent_msg2),
|
|
user["other_user"]["ws"].send(sent_msg2)
|
|
])
|
|
del user["other_user"]["other_user"]
|
|
del user["other_user"]
|
|
else:
|
|
# Other user disconnected
|
|
user["action"] = "ready"
|
|
user["session"] = False
|
|
await asyncio.wait([
|
|
ws.send(msgobj("gameend")),
|
|
ws.send(status_event())
|
|
])
|
|
finally:
|
|
# User disconnected
|
|
del user["ws"]
|
|
del server_status["users"][server_status["users"].index(user)]
|
|
if "other_user" in user and "ws" in user["other_user"]:
|
|
user["other_user"]["action"] = "ready"
|
|
user["other_user"]["session"] = False
|
|
await asyncio.wait([
|
|
user["other_user"]["ws"].send(msgobj("gameend")),
|
|
user["other_user"]["ws"].send(status_event())
|
|
])
|
|
del user["other_user"]["other_user"]
|
|
if user["action"] == "waiting":
|
|
del server_status["waiting"][user["gameid"]]
|
|
await notify_status()
|
|
elif user["action"] == "invite" and user["session"] in server_status["invites"]:
|
|
del server_status["invites"][user["session"]]
|
|
|
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 34802
|
|
print('Starting server on port %d' % port)
|
|
loop = asyncio.get_event_loop()
|
|
tasks = asyncio.gather(
|
|
websockets.serve(connection, "localhost", port)
|
|
)
|
|
try:
|
|
loop.run_until_complete(tasks)
|
|
loop.run_forever()
|
|
except KeyboardInterrupt:
|
|
print("Stopping server")
|
|
def shutdown_exception_handler(loop, context):
|
|
if "exception" not in context or not isinstance(context["exception"], asyncio.CancelledError):
|
|
loop.default_exception_handler(context)
|
|
loop.set_exception_handler(shutdown_exception_handler)
|
|
tasks = asyncio.gather(*asyncio.all_tasks(loop=loop), loop=loop, return_exceptions=True)
|
|
tasks.add_done_callback(lambda t: loop.stop())
|
|
tasks.cancel()
|
|
while not tasks.done() and not loop.is_closed():
|
|
loop.run_forever()
|
|
finally:
|
|
if hasattr(loop, "shutdown_asyncgens"):
|
|
loop.run_until_complete(loop.shutdown_asyncgens())
|
|
loop.close()
|
|
|