pep.py/objects/match.py

890 lines
24 KiB
Python
Raw Normal View History

2016-10-02 20:48:14 +00:00
import copy
import json
import threading
2018-03-23 19:59:04 +00:00
import time
2016-10-02 20:48:14 +00:00
from common.log import logUtils as log
from constants import dataTypes
from constants import matchModModes
2016-05-18 17:12:46 +00:00
from constants import matchScoringTypes
from constants import matchTeamTypes
from constants import matchTeams
2016-10-02 20:48:14 +00:00
from constants import serverPackets
from constants import slotStatuses
from helpers import chatHelper as chat
2016-10-02 20:48:14 +00:00
from objects import glob
2016-09-02 15:45:10 +00:00
class slot:
def __init__(self):
self.status = slotStatuses.FREE
self.team = matchTeams.NO_TEAM
self.userID = -1
self.user = None
self.mods = 0
self.loaded = False
self.skip = False
self.complete = False
self.score = 0
self.failed = False
self.passed = True
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
class match:
2017-08-01 22:22:57 +00:00
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID, isTourney=False):
2016-04-19 17:40:59 +00:00
"""
Create a new match object
:param matchID: match progressive identifier
:param matchName: match name, string
:param matchPassword: match md5 password. Leave empty for no password
:param beatmapID: beatmap ID
:param beatmapName: beatmap name, string
:param beatmapMD5: beatmap md5 hash, string
:param gameMode: game mode ID. See gameModes.py
:param hostUserID: user id of the host
2016-04-19 17:40:59 +00:00
"""
self.matchID = matchID
self.streamName = "multi/{}".format(self.matchID)
self.playingStreamName = "{}/playing".format(self.streamName)
2016-04-19 17:40:59 +00:00
self.inProgress = False
self.mods = 0
self.matchName = matchName
self.matchPassword = matchPassword
self.beatmapID = beatmapID
self.beatmapName = beatmapName
self.beatmapMD5 = beatmapMD5
self.hostUserID = hostUserID
self.gameMode = gameMode
self.matchScoringType = matchScoringTypes.SCORE # default values
self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD # default value
self.matchModMode = matchModModes.NORMAL # default value
2016-04-19 17:40:59 +00:00
self.seed = 0
self.matchDataCache = bytes()
2017-08-01 22:22:57 +00:00
self.isTourney = isTourney
2017-08-01 22:39:16 +00:00
self.isLocked = False # if True, users can't change slots/teams. Used in tourney matches
2017-08-03 23:04:26 +00:00
self.isStarting = False
self._lock = threading.Lock()
2018-03-23 19:59:04 +00:00
self.createTime = int(time.time())
self.vinseID = None
self.bloodcatAlert = False
2016-04-19 17:40:59 +00:00
# Create all slots and reset them
self.slots = []
for _ in range(0,16):
self.slots.append(slot())
2016-04-19 17:40:59 +00:00
# Create streams
glob.streams.add(self.streamName)
glob.streams.add(self.playingStreamName)
# Create #multiplayer channel
glob.channels.addHiddenChannel("#multi_{}".format(self.matchID))
2017-08-01 22:22:57 +00:00
log.info("MPROOM{}: {} match created!".format(self.matchID, "Tourney" if self.isTourney else "Normal"))
def getMatchData(self, censored = False):
2016-04-19 17:40:59 +00:00
"""
Return binary match data structure for packetHelper
Return binary match data structure for packetHelper
:return:
2016-04-19 17:40:59 +00:00
"""
# General match info
# TODO: Test without safe copy, the error might have been caused by outdated python bytecode cache
# safeMatch = copy.deepcopy(self)
2016-04-19 17:40:59 +00:00
struct = [
[self.matchID, dataTypes.UINT16],
[int(self.inProgress), dataTypes.BYTE],
2016-09-02 15:16:22 +00:00
[0, dataTypes.BYTE],
[self.mods, dataTypes.UINT32],
[self.matchName, dataTypes.STRING]
]
if censored and self.matchPassword:
struct.append(["redacted", dataTypes.STRING])
else:
struct.append([self.matchPassword, dataTypes.STRING])
struct.extend([
[self.beatmapName, dataTypes.STRING],
[self.beatmapID, dataTypes.UINT32],
[self.beatmapMD5, dataTypes.STRING]
])
2016-04-19 17:40:59 +00:00
# Slots status IDs, always 16 elements
for i in range(0,16):
struct.append([self.slots[i].status, dataTypes.BYTE])
2016-04-19 17:40:59 +00:00
# Slot teams, always 16 elements
for i in range(0,16):
struct.append([self.slots[i].team, dataTypes.BYTE])
2016-04-19 17:40:59 +00:00
# Slot user ID. Write only if slot is occupied
for i in range(0,16):
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens:
struct.append([glob.tokens.tokens[self.slots[i].user].userID, dataTypes.UINT32])
2016-04-19 17:40:59 +00:00
# Other match data
struct.extend([
[self.hostUserID, dataTypes.SINT32],
[self.gameMode, dataTypes.BYTE],
[self.matchScoringType, dataTypes.BYTE],
[self.matchTeamType, dataTypes.BYTE],
[self.matchModMode, dataTypes.BYTE],
2016-04-19 17:40:59 +00:00
])
# Slot mods if free mod is enabled
if self.matchModMode == matchModModes.FREE_MOD:
2016-04-19 17:40:59 +00:00
for i in range(0,16):
struct.append([self.slots[i].mods, dataTypes.UINT32])
2016-04-19 17:40:59 +00:00
# Seed idk
# TODO: Implement this, it should be used for mania "random" mod
struct.append([self.seed, dataTypes.UINT32])
2016-04-19 17:40:59 +00:00
return struct
def setHost(self, newHost):
"""
Set room host to newHost and send him host packet
:param newHost: new host userID
:return:
2016-04-19 17:40:59 +00:00
"""
slotID = self.getUserSlotID(newHost)
if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
2017-08-03 22:24:01 +00:00
return False
token = glob.tokens.tokens[self.slots[slotID].user]
2016-04-19 17:40:59 +00:00
self.hostUserID = newHost
token.enqueue(serverPackets.matchTransferHost())
self.sendUpdates()
log.info("MPROOM{}: {} is now the host".format(self.matchID, token.username))
2017-08-03 22:24:01 +00:00
return True
def removeHost(self):
"""
Removes the host (for tourney matches)
:return:
"""
self.hostUserID = -1
self.sendUpdates()
log.info("MPROOM{}: Removed host".format(self.matchID))
2016-04-19 17:40:59 +00:00
def setSlot(self, slotID, status = None, team = None, user = "", mods = None, loaded = None, skip = None, complete = None):
"""
Set data for a specific slot.
All fields but slotID are optional.
Skipped fields won't be edited.
:param slotID: slot ID
:param status: new status
:param team: new team
:param user: new user id
:param mods: new mods
:param loaded: new loaded status
:param skip: new skip value
:param complete: new completed value
:return:
"""
if status is not None:
self.slots[slotID].status = status
2016-04-19 17:40:59 +00:00
if team is not None:
self.slots[slotID].team = team
2016-04-19 17:40:59 +00:00
if user is not "":
self.slots[slotID].user = user
2016-04-19 17:40:59 +00:00
if mods is not None:
self.slots[slotID].mods = mods
2016-04-19 17:40:59 +00:00
if loaded is not None:
self.slots[slotID].loaded = loaded
2016-04-19 17:40:59 +00:00
if skip is not None:
self.slots[slotID].skip = skip
2016-04-19 17:40:59 +00:00
if complete is not None:
self.slots[slotID].complete = complete
2016-04-19 17:40:59 +00:00
def setSlotMods(self, slotID, mods):
"""
Set slotID mods. Same as calling setSlot and then sendUpdate
:param slotID: slot number
:param mods: new mods
:return:
2016-04-19 17:40:59 +00:00
"""
# Set new slot data and send update
self.setSlot(slotID, mods=mods)
self.sendUpdates()
log.info("MPROOM{}: Slot{} mods changed to {}".format(self.matchID, slotID, mods))
2016-04-19 17:40:59 +00:00
def toggleSlotReady(self, slotID):
"""
Switch slotID ready/not ready status
Same as calling setSlot and then sendUpdate
:param slotID: slot number
:return:
2016-04-19 17:40:59 +00:00
"""
# Update ready status and setnd update
2017-08-03 23:04:26 +00:00
if self.slots[slotID].user is None or self.isStarting:
return
oldStatus = self.slots[slotID].status
if oldStatus == slotStatuses.READY:
newStatus = slotStatuses.NOT_READY
2016-04-19 17:40:59 +00:00
else:
newStatus = slotStatuses.READY
self.setSlot(slotID, newStatus)
self.sendUpdates()
log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
2016-04-19 17:40:59 +00:00
2017-08-03 21:47:34 +00:00
def toggleSlotLocked(self, slotID):
2016-04-19 17:40:59 +00:00
"""
Lock a slot
Same as calling setSlot and then sendUpdate
:param slotID: slot number
:return:
2016-04-19 17:40:59 +00:00
"""
# Check if slot is already locked
if self.slots[slotID].status == slotStatuses.LOCKED:
newStatus = slotStatuses.FREE
2016-04-19 17:40:59 +00:00
else:
newStatus = slotStatuses.LOCKED
2016-04-19 17:40:59 +00:00
2018-12-09 17:46:26 +00:00
# Send updated settings to kicked user, so they will return to the lobby.
if self.slots[slotID].user is not None and self.slots[slotID].user in glob.tokens.tokens:
glob.tokens.tokens[self.slots[slotID].user].enqueue(serverPackets.updateMatch(self.matchID))
2016-04-19 17:40:59 +00:00
# Set new slot status
self.setSlot(slotID, status=newStatus, team=0, user=None, mods=0)
2016-04-19 17:40:59 +00:00
# Send updates to everyone else
self.sendUpdates()
log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.LOCKED else "unlocked"))
2016-04-19 17:40:59 +00:00
def playerLoaded(self, userID):
"""
Set a player loaded status to True
:param userID: ID of user
:return:
2016-04-19 17:40:59 +00:00
"""
slotID = self.getUserSlotID(userID)
2016-09-02 15:45:10 +00:00
if slotID is None:
2016-04-19 17:40:59 +00:00
return
# Set loaded to True
self.slots[slotID].loaded = True
log.info("MPROOM{}: User {} loaded".format(self.matchID, userID))
2016-04-19 17:40:59 +00:00
# Check all loaded
total = 0
loaded = 0
for i in range(0,16):
if self.slots[i].status == slotStatuses.PLAYING:
2016-04-19 17:40:59 +00:00
total+=1
2016-09-02 15:45:10 +00:00
if self.slots[i].loaded:
2016-04-19 17:40:59 +00:00
loaded+=1
if total == loaded:
self.allPlayersLoaded()
def allPlayersLoaded(self):
"""
Send allPlayersLoaded packet to every playing usr in match
:return:
"""
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded())
log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID))
2016-04-19 17:40:59 +00:00
def playerSkip(self, userID):
"""
Set a player skip status to True
:param userID: ID of user
:return:
2016-04-19 17:40:59 +00:00
"""
slotID = self.getUserSlotID(userID)
2016-09-02 15:45:10 +00:00
if slotID is None:
2016-04-19 17:40:59 +00:00
return
# Set skip to True
self.slots[slotID].skip = True
log.info("MPROOM{}: User {} skipped".format(self.matchID, userID))
2016-04-19 17:40:59 +00:00
# Send skip packet to every playing user
#glob.streams.broadcast(self.playingStreamName, serverPackets.playerSkipped(glob.tokens.tokens[self.slots[slotID].user].userID))
glob.streams.broadcast(self.playingStreamName, serverPackets.playerSkipped(slotID))
2016-04-19 17:40:59 +00:00
# Check all skipped
total = 0
skipped = 0
for i in range(0,16):
if self.slots[i].status == slotStatuses.PLAYING:
2016-04-19 17:40:59 +00:00
total+=1
2016-09-02 15:45:10 +00:00
if self.slots[i].skip:
2016-04-19 17:40:59 +00:00
skipped+=1
if total == skipped:
self.allPlayersSkipped()
def allPlayersSkipped(self):
"""
Send allPlayersSkipped packet to every playing usr in match
:return:
"""
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
log.info("MPROOM{}: All players have skipped!".format(self.matchID))
2016-04-19 17:40:59 +00:00
def updateScore(self, slotID, score):
"""
Update score for a slot
:param slotID: the slot that the user that is updating their score is in
:param score: the new score to update
:return:
"""
self.slots[slotID].score = score
def updateHP(self, slotID, hp):
"""
Update HP for a slot
:param slotID: the slot that the user that is updating their hp is in
:param hp: the new hp to update
:return:
"""
self.slots[slotID].failed = True if hp == 254 else False
2016-04-19 17:40:59 +00:00
def playerCompleted(self, userID):
"""
Set userID's slot completed to True
:param userID: ID of user
2016-04-19 17:40:59 +00:00
"""
slotID = self.getUserSlotID(userID)
2016-09-02 15:45:10 +00:00
if slotID is None:
2016-04-19 17:40:59 +00:00
return
self.setSlot(slotID, complete=True)
2016-04-19 17:40:59 +00:00
# Console output
log.info("MPROOM{}: User {} has completed his play".format(self.matchID, userID))
2016-04-19 17:40:59 +00:00
# Check all completed
total = 0
completed = 0
for i in range(0,16):
if self.slots[i].status == slotStatuses.PLAYING:
2016-04-19 17:40:59 +00:00
total+=1
2016-09-02 15:45:10 +00:00
if self.slots[i].complete:
2016-04-19 17:40:59 +00:00
completed+=1
if total == completed:
self.allPlayersCompleted()
def allPlayersCompleted(self):
"""
Cleanup match stuff and send match end packet to everyone
2016-04-19 17:40:59 +00:00
:return:
"""
# Collect some info about the match that just ended to send to the api
infoToSend = {
"id": self.matchID,
"name": self.matchName,
"beatmap_id": self.beatmapID,
"mods": self.mods,
"game_mode": self.gameMode,
"scores": {}
}
# Add score info for each player
for i in range(0,16):
if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING:
infoToSend["scores"][glob.tokens.tokens[self.slots[i].user].userID] = {
"score": self.slots[i].score,
"mods": self.slots[i].mods,
"failed": self.slots[i].failed,
"pass": self.slots[i].passed,
"team": self.slots[i].team
}
# Send the info to the api
glob.redis.publish("api:mp_complete_match", json.dumps(infoToSend))
2016-04-19 17:40:59 +00:00
# Reset inProgress
self.inProgress = False
# Reset slots
self.resetSlots()
2016-04-19 17:40:59 +00:00
# Send match update
self.sendUpdates()
2016-04-19 17:40:59 +00:00
# Send match complete
glob.streams.broadcast(self.streamName, serverPackets.matchComplete())
# Destroy playing stream
glob.streams.dispose(self.playingStreamName)
glob.streams.remove(self.playingStreamName)
2016-04-19 17:40:59 +00:00
# Console output
log.info("MPROOM{}: Match completed".format(self.matchID))
2016-04-19 17:40:59 +00:00
# Set vinse id if needed
chanName = "#multi_{}".format(self.matchID)
if self.vinseID is None:
self.vinseID = (int(time.time()) // (60 * 15)) << 32 | self.matchID
chat.sendMessage("FokaBot", chanName, "Match history available [{} here]".format(
"https://vinse.ripple.moe/match/{}".format(self.vinseID)
))
if not self.bloodcatAlert:
chat.sendMessage(
"FokaBot",
chanName,
"Oh by the way, in case you're playing unranked or broken maps "
"that are now available through ripple's osu!direct, you can "
"type '!bloodcat' in the chat to get a download link for the "
"currently selected map from Bloodcat!"
)
self.bloodcatAlert = True
# If this is a tournament match, then we send a notification in the chat
# saying that the match has completed.
if self.isTourney and (chanName in glob.channels.channels):
chat.sendMessage(glob.BOT_NAME, chanName, "Match has just finished.")
def resetSlots(self):
for i in range(0,16):
if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING:
self.slots[i].status = slotStatuses.NOT_READY
self.slots[i].loaded = False
self.slots[i].skip = False
self.slots[i].complete = False
self.slots[i].score = 0
self.slots[i].failed = False
self.slots[i].passed = True
2016-04-19 17:40:59 +00:00
def getUserSlotID(self, userID):
"""
Get slot ID occupied by userID
:return: slot id if found, None if user is not in room
2016-04-19 17:40:59 +00:00
"""
for i in range(0,16):
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens and glob.tokens.tokens[self.slots[i].user].userID == userID:
2016-04-19 17:40:59 +00:00
return i
return None
def userJoin(self, user):
2016-04-19 17:40:59 +00:00
"""
Add someone to users in match
2016-12-26 09:33:05 +00:00
:param user: user object of the user
:return: True if join success, False if fail (room is full)
2016-04-19 17:40:59 +00:00
"""
# Make sure we're not in this match
for i in range(0,16):
if self.slots[i].user == user.token:
# Set bugged slot to free
self.setSlot(i, slotStatuses.FREE, 0, None, 0)
2016-04-19 17:40:59 +00:00
# Find first free slot
for i in range(0,16):
if self.slots[i].status == slotStatuses.FREE:
2016-04-19 17:40:59 +00:00
# Occupy slot
team = matchTeams.NO_TEAM
if self.matchTeamType == matchTeamTypes.TEAM_VS or self.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
team = matchTeams.RED if i % 2 == 0 else matchTeams.BLUE
self.setSlot(i, slotStatuses.NOT_READY, team, user.token, 0)
2016-04-19 17:40:59 +00:00
# Send updated match data
self.sendUpdates()
2016-04-19 17:40:59 +00:00
# Console output
log.info("MPROOM{}: {} joined the room".format(self.matchID, user.username))
2016-04-19 17:40:59 +00:00
return True
return False
def userLeft(self, user, disposeMatch=True):
2016-04-19 17:40:59 +00:00
"""
Remove someone from users in match
2016-12-26 09:33:05 +00:00
:param user: user object of the user
:param disposeMatch: if `True`, will try to dispose match if there are no users in the room
:return:
2016-04-19 17:40:59 +00:00
"""
# Make sure the user is in room
slotID = self.getUserSlotID(user.userID)
2016-09-02 15:45:10 +00:00
if slotID is None:
2016-04-19 17:40:59 +00:00
return
# Set that slot to free
self.setSlot(slotID, slotStatuses.FREE, 0, None, 0)
2016-04-19 17:40:59 +00:00
# Check if everyone left
2017-08-01 22:22:57 +00:00
if self.countUsers() == 0 and disposeMatch and not self.isTourney:
2016-04-19 17:40:59 +00:00
# Dispose match
glob.matches.disposeMatch(self.matchID)
log.info("MPROOM{}: Room disposed because all users left".format(self.matchID))
2016-04-19 17:40:59 +00:00
return
# Check if host left
if user.userID == self.hostUserID:
2016-04-19 17:40:59 +00:00
# Give host to someone else
for i in range(0,16):
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens:
self.setHost(glob.tokens.tokens[self.slots[i].user].userID)
2016-04-19 17:40:59 +00:00
break
# Send updated match data
self.sendUpdates()
2016-04-19 17:40:59 +00:00
# Console output
log.info("MPROOM{}: {} left the room".format(self.matchID, user.username))
2016-04-19 17:40:59 +00:00
def userChangeSlot(self, userID, newSlotID):
"""
Change userID slot to newSlotID
:param userID: user that changed slot
:param newSlotID: slot id of new slot
:return:
2016-04-19 17:40:59 +00:00
"""
2017-08-01 22:39:16 +00:00
# Make sure the match is not locked
2017-08-03 23:04:26 +00:00
if self.isLocked or self.isStarting:
2017-08-03 22:24:01 +00:00
return False
2017-08-01 22:39:16 +00:00
2016-04-19 17:40:59 +00:00
# Make sure the user is in room
oldSlotID = self.getUserSlotID(userID)
2016-09-02 15:45:10 +00:00
if oldSlotID is None:
2017-08-03 22:24:01 +00:00
return False
2016-04-19 17:40:59 +00:00
# Make sure there is no one inside new slot
2017-08-03 22:09:50 +00:00
if self.slots[newSlotID].user is not None or self.slots[newSlotID].status != slotStatuses.FREE:
2017-08-03 22:24:01 +00:00
return False
2016-04-19 17:40:59 +00:00
# Get old slot data
#oldData = dill.copy(self.slots[oldSlotID])
oldData = copy.deepcopy(self.slots[oldSlotID])
2016-04-19 17:40:59 +00:00
# Free old slot
self.setSlot(oldSlotID, slotStatuses.FREE, 0, None, 0, False, False, False)
2016-04-19 17:40:59 +00:00
# Occupy new slot
self.setSlot(newSlotID, oldData.status, oldData.team, oldData.user, oldData.mods)
2016-04-19 17:40:59 +00:00
# Send updated match data
self.sendUpdates()
2016-04-19 17:40:59 +00:00
# Console output
log.info("MPROOM{}: {} moved to slot {}".format(self.matchID, userID, newSlotID))
2017-08-03 22:24:01 +00:00
return True
2016-04-19 17:40:59 +00:00
def changePassword(self, newPassword):
"""
Change match password to newPassword
:param newPassword: new password string
:return:
2016-04-19 17:40:59 +00:00
"""
self.matchPassword = newPassword
2016-04-19 17:40:59 +00:00
# Send password change to every user in match
glob.streams.broadcast(self.streamName, serverPackets.changeMatchPassword(self.matchPassword))
2016-04-19 17:40:59 +00:00
# Send new match settings too
self.sendUpdates()
2016-04-19 17:40:59 +00:00
# Console output
log.info("MPROOM{}: Password changed to {}".format(self.matchID, self.matchPassword))
2016-04-19 17:40:59 +00:00
def changeMods(self, mods):
2016-04-19 17:40:59 +00:00
"""
Set match global mods
:param mods: mods bitwise int thing
:return:
2016-04-19 17:40:59 +00:00
"""
# Set new mods and send update
self.mods = mods
self.sendUpdates()
log.info("MPROOM{}: Mods changed to {}".format(self.matchID, self.mods))
2016-04-19 17:40:59 +00:00
def userHasBeatmap(self, userID, has = True):
"""
Set no beatmap status for userID
:param userID: ID of user
:param has: True if has beatmap, false if not
:return:
2016-04-19 17:40:59 +00:00
"""
# Make sure the user is in room
slotID = self.getUserSlotID(userID)
2016-09-02 15:45:10 +00:00
if slotID is None:
2016-04-19 17:40:59 +00:00
return
# Set slot
self.setSlot(slotID, slotStatuses.NO_MAP if not has else slotStatuses.NOT_READY)
2016-04-19 17:40:59 +00:00
# Send updates
self.sendUpdates()
2016-04-19 17:40:59 +00:00
def transferHost(self, slotID):
"""
Transfer host to slotID
:param slotID: ID of slot
:return:
2016-04-19 17:40:59 +00:00
"""
# Make sure there is someone in that slot
if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens:
2016-04-19 17:40:59 +00:00
return
# Transfer host
self.setHost(glob.tokens.tokens[self.slots[slotID].user].userID)
2016-04-19 17:40:59 +00:00
# Send updates
2017-08-03 22:24:01 +00:00
# self.sendUpdates()
2016-04-19 17:40:59 +00:00
def playerFailed(self, userID):
"""
Send userID's failed packet to everyone in match
:param userID: ID of user
:return:
2016-04-19 17:40:59 +00:00
"""
# Make sure the user is in room
slotID = self.getUserSlotID(userID)
2016-09-02 15:45:10 +00:00
if slotID is None:
2016-04-19 17:40:59 +00:00
return
self.slots[slotID].passed = False
2016-04-19 17:40:59 +00:00
# Send packet to everyone
glob.streams.broadcast(self.playingStreamName, serverPackets.playerFailed(slotID))
2016-04-19 17:40:59 +00:00
# Console output
log.info("MPROOM{}: {} has failed!".format(self.matchID, userID))
2016-04-19 17:40:59 +00:00
def invite(self, fro, to):
"""
Fro invites to in this match.
:param fro: sender userID
:param to: receiver userID
:return:
2016-04-19 17:40:59 +00:00
"""
# Get tokens
froToken = glob.tokens.getTokenFromUserID(fro)
toToken = glob.tokens.getTokenFromUserID(to)
2016-09-02 15:45:10 +00:00
if froToken is None or toToken is None:
2016-04-19 17:40:59 +00:00
return
# FokaBot is too busy
if to == 999:
chat.sendMessage(glob.BOT_NAME, froToken.username, "I would love to join your match, but I'm busy keeping ripple up and running. Sorry. Beep Boop.")
2016-04-19 17:40:59 +00:00
# Send message
message = "Come join my multiplayer match: \"[osump://{}/{} {}]\"".format(self.matchID, self.matchPassword.replace(" ", "_"), self.matchName)
chat.sendMessage(token=froToken, to=toToken.username, message=message)
2016-04-19 17:40:59 +00:00
def countUsers(self):
"""
Return how many players are in that match
:return: number of users
2016-04-19 17:40:59 +00:00
"""
c = 0
for i in range(0,16):
if self.slots[i].user is not None:
2016-04-19 17:40:59 +00:00
c+=1
return c
2017-08-07 21:21:49 +00:00
def changeTeam(self, userID, newTeam=None):
2016-04-19 17:40:59 +00:00
"""
Change userID's team
:param userID: id of user
:return:
2016-04-19 17:40:59 +00:00
"""
2017-08-07 21:21:49 +00:00
# Make sure this match's mode has teams
2017-08-07 22:56:39 +00:00
if self.matchTeamType != matchTeamTypes.TEAM_VS and self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
2017-08-07 21:21:49 +00:00
return
2017-08-01 22:39:16 +00:00
# Make sure the match is not locked
2017-08-03 23:04:26 +00:00
if self.isLocked or self.isStarting:
2017-08-01 22:39:16 +00:00
return
2016-04-19 17:40:59 +00:00
# Make sure the user is in room
slotID = self.getUserSlotID(userID)
2016-09-02 15:45:10 +00:00
if slotID is None:
2016-04-19 17:40:59 +00:00
return
# Update slot and send update
2017-08-07 21:21:49 +00:00
if newTeam is None:
newTeam = matchTeams.BLUE if self.slots[slotID].team == matchTeams.RED else matchTeams.RED
2016-04-19 17:40:59 +00:00
self.setSlot(slotID, None, newTeam)
self.sendUpdates()
2016-04-19 17:40:59 +00:00
def sendUpdates(self):
"""
Send match updates packet to everyone in lobby and room streams
:return:
"""
self.matchDataCache = serverPackets.updateMatch(self.matchID)
censoredDataCache = serverPackets.updateMatch(self.matchID, censored=True)
if self.matchDataCache is not None:
glob.streams.broadcast(self.streamName, self.matchDataCache)
if censoredDataCache is not None:
glob.streams.broadcast("lobby", censoredDataCache)
else:
log.error("MPROOM{}: Can't send match update packet, match data is None!!!".format(self.matchID))
2016-04-19 17:40:59 +00:00
def checkTeams(self):
"""
Check if match teams are valid
:return: True if valid, False if invalid
:return:
2016-04-19 17:40:59 +00:00
"""
2017-08-07 22:56:39 +00:00
if self.matchTeamType != matchTeamTypes.TEAM_VS and self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
2016-04-19 17:40:59 +00:00
# Teams are always valid if we have no teams
return True
# We have teams, check if they are valid
firstTeam = -1
for i in range(0,16):
if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.NO_MAP) == 0:
2016-04-19 17:40:59 +00:00
if firstTeam == -1:
firstTeam = self.slots[i].team
elif firstTeam != self.slots[i].team:
log.info("MPROOM{}: Teams are valid".format(self.matchID))
2016-04-19 17:40:59 +00:00
return True
log.warning("MPROOM{}: Invalid teams!".format(self.matchID))
2016-04-19 17:40:59 +00:00
return False
def start(self):
"""
Start the match
:return:
"""
2017-08-03 23:04:26 +00:00
# Remove isStarting timer flag thingie
self.isStarting = False
# Make sure we have enough players
if self.countUsers() < 2 or not self.checkTeams():
2017-08-03 23:04:26 +00:00
return False
# Create playing channel
glob.streams.add(self.playingStreamName)
# Change inProgress value
self.inProgress = True
# Set playing to ready players and set load, skip and complete to False
# Make clients join playing stream
for i in range(0, 16):
if self.slots[i].user in glob.tokens.tokens:
self.slots[i].status = slotStatuses.PLAYING
self.slots[i].loaded = False
self.slots[i].skip = False
self.slots[i].complete = False
glob.tokens.tokens[self.slots[i].user].joinStream(self.playingStreamName)
# Send match start packet
glob.streams.broadcast(self.playingStreamName, serverPackets.matchStart(self.matchID))
# Send updates
self.sendUpdates()
2017-08-03 23:04:26 +00:00
return True
2017-08-07 18:51:16 +00:00
def forceSize(self, matchSize):
for i in range(0, matchSize):
if self.slots[i].status == slotStatuses.LOCKED:
self.toggleSlotLocked(i)
for i in range(matchSize, 16):
if self.slots[i].status != slotStatuses.LOCKED:
self.toggleSlotLocked(i)
def abort(self):
if not self.inProgress:
log.warning("MPROOM{}: Match is not in progress!".format(self.matchID))
return
self.inProgress = False
self.isStarting = False
self.resetSlots()
self.sendUpdates()
glob.streams.broadcast(self.playingStreamName, serverPackets.matchAbort())
glob.streams.dispose(self.playingStreamName)
glob.streams.remove(self.playingStreamName)
log.info("MPROOM{}: Match aborted".format(self.matchID))
def initializeTeams(self):
if self.matchTeamType == matchTeamTypes.TEAM_VS or self.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
# Set teams
for i, _slot in enumerate(self.slots):
_slot.team = matchTeams.RED if i % 2 == 0 else matchTeams.BLUE
else:
# Reset teams
for _slot in self.slots:
_slot.team = matchTeams.NO_TEAM
def resetMods(self):
for _slot in self.slots:
_slot.mods = 0
def resetReady(self):
for _slot in self.slots:
if _slot.status == slotStatuses.READY:
_slot.status = slotStatuses.NOT_READY
def sendReadyStatus(self):
chanName = "#multi_{}".format(self.matchID)
# Make sure match exists before attempting to do anything else
if chanName not in glob.channels.channels:
return
totalUsers = 0
readyUsers = 0
for slot in self.slots:
# Make sure there is a user in this slot
if slot.user is None:
continue
# In this slot there is a user, so we increase the amount of total users
# in this multi room.
totalUsers += 1
if slot.status == slotStatuses.READY:
readyUsers += 1
message = "{} users ready out of {}.".format(readyUsers, totalUsers)
if totalUsers == readyUsers:
message += " All users ready!"
# Check whether there is anyone left in this match.
if totalUsers == 0:
message = "The match is now empty."
chat.sendMessage(glob.BOT_NAME, chanName, message)
def __enter__(self):
# 🌚🌚🌚🌚🌚
self._lock.acquire()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._lock.release()