pep.py/objects/match.py

660 lines
17 KiB
Python
Raw Normal View History

2016-04-19 17:40:59 +00:00
# TODO: Enqueue all
2016-10-02 20:48:14 +00:00
import copy
from common import generalUtils
from common.constants import gameModes
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 = 0
self.userID = -1
self.mods = 0
self.loaded = False
self.skip = False
self.complete = False
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
class match:
2016-04-19 17:40:59 +00:00
"""Multiplayer match object"""
matchID = 0
inProgress = False
mods = 0
matchName = ""
matchPassword = ""
beatmapName = ""
beatmapID = 0
beatmapMD5 = ""
slots = []
2016-04-19 17:40:59 +00:00
hostUserID = 0
2016-10-02 20:48:14 +00:00
gameMode = gameModes.STD
2016-04-19 17:40:59 +00:00
matchScoringType = matchScoringTypes.score
matchTeamType = matchTeamTypes.headToHead
matchModMode = matchModModes.normal
seed = 0
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
2016-04-19 17:40:59 +00:00
"""
Create a new match object
matchID -- match progressive identifier
matchName -- match name, string
matchPassword -- match md5 password. Leave empty for no password
beatmapID -- beatmap ID
beatmapName -- beatmap name, string
beatmapMD5 -- beatmap md5 hash, string
gameMode -- game mode ID. See gameModes.py
hostUserID -- user id of the host
2016-04-19 17:40:59 +00:00
"""
self.matchID = matchID
2016-04-19 17:40:59 +00:00
self.inProgress = False
self.mods = 0
self.matchName = matchName
if matchPassword != "":
2016-10-02 20:48:14 +00:00
self.matchPassword = generalUtils.stringMd5(matchPassword)
else:
self.matchPassword = ""
self.beatmapID = beatmapID
self.beatmapName = beatmapName
self.beatmapMD5 = beatmapMD5
self.hostUserID = hostUserID
self.gameMode = gameMode
2016-04-19 17:40:59 +00:00
self.matchScoringTypes = matchScoringTypes.score # default values
self.matchTeamType = matchTeamTypes.headToHead # default value
self.matchModMode = matchModModes.normal # default value
self.seed = 0
# 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 #multiplayer channel
glob.channels.addTempChannel("#multi_{}".format(self.matchID))
2016-04-19 17:40:59 +00:00
def getMatchData(self):
"""
Return binary match data structure for packetHelper
"""
# General match info
struct = [
2016-09-02 15:16:22 +00:00
[self.matchID, dataTypes.UINT16],
[int(self.inProgress), dataTypes.BYTE],
[0, dataTypes.BYTE],
[self.mods, dataTypes.UINT32],
[self.matchName, dataTypes.STRING],
[self.matchPassword, dataTypes.STRING],
[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):
2016-09-02 15:16:22 +00:00
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):
2016-09-02 15:16:22 +00:00
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):
uid = self.slots[i].userID
2016-04-19 17:40:59 +00:00
if uid > -1:
2016-09-02 15:16:22 +00:00
struct.append([uid, dataTypes.UINT32])
2016-04-19 17:40:59 +00:00
# Other match data
struct.extend([
2016-09-02 15:16:22 +00:00
[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.freeMod:
for i in range(0,16):
2016-09-02 15:16:22 +00:00
struct.append([self.slots[i].mods, dataTypes.UINT32])
2016-04-19 17:40:59 +00:00
# Seed idk
2016-09-02 15:16:22 +00:00
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
newHost -- new host userID
"""
self.hostUserID = newHost
# Send host packet to new host
token = glob.tokens.getTokenFromUserID(newHost)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
token.enqueue(serverPackets.matchTransferHost())
log.info("MPROOM{}: {} is now the host".format(self.matchID, newHost))
2016-04-19 17:40:59 +00:00
def setSlot(self, slotID, slotStatus = None, slotTeam = None, slotUserID = None, slotMods = None, slotLoaded = None, slotSkip = None, slotComplete = None):
"""
Set a slot to a specific userID and status
slotID -- id of that slot (0-15)
slotStatus -- see slotStatuses.py
slotTeam -- team id
slotUserID -- user ID of user in that slot
slotMods -- mods enabled in that slot. 0 if not free mod.
slotLoaded -- loaded status True/False
slotSkip -- skip status True/False
slotComplete -- completed status True/False
If Null is passed, that value won't be edited
"""
2016-09-02 15:45:10 +00:00
if slotStatus is not None:
self.slots[slotID].status = slotStatus
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
if slotTeam is not None:
self.slots[slotID].team = slotTeam
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
if slotUserID is not None:
self.slots[slotID].userID = slotUserID
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
if slotMods is not None:
self.slots[slotID].mods = slotMods
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
if slotLoaded is not None:
self.slots[slotID].loaded = slotLoaded
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
if slotSkip is not None:
self.slots[slotID].skip = slotSkip
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
if slotComplete is not None:
self.slots[slotID].complete = slotComplete
2016-04-19 17:40:59 +00:00
def setSlotMods(self, slotID, mods):
"""
Set slotID mods. Same as calling setSlot and then sendUpdate
slotID -- slot number
mods -- new mods
"""
# Set new slot data and send update
self.setSlot(slotID, None, None, None, mods)
self.sendUpdate()
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
slotID -- slot number
"""
# Update ready status and setnd update
oldStatus = self.slots[slotID].status
2016-04-19 17:40:59 +00:00
if oldStatus == slotStatuses.ready:
newStatus = slotStatuses.notReady
else:
newStatus = slotStatuses.ready
self.setSlot(slotID, newStatus, None, None, None)
self.sendUpdate()
log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
2016-04-19 17:40:59 +00:00
def toggleSlotLock(self, slotID):
"""
Lock a slot
Same as calling setSlot and then sendUpdate
slotID -- slot number
"""
# Get token of user in that slot (if there's someone)
if self.slots[slotID].userID > -1:
token = glob.tokens.getTokenFromUserID(self.slots[slotID].userID)
2016-04-19 17:40:59 +00:00
else:
token = None
# Check if slot is already locked
if self.slots[slotID].status == slotStatuses.locked:
2016-04-19 17:40:59 +00:00
newStatus = slotStatuses.free
else:
newStatus = slotStatuses.locked
# Set new slot status
self.setSlot(slotID, newStatus, 0, -1, 0)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
# Send updated settings to kicked user, so he returns to lobby
token.enqueue(serverPackets.updateMatch(self.matchID))
# Send updates to everyone else
self.sendUpdate()
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
userID -- ID of user
"""
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"""
for i in range(0,16):
if self.slots[i].userID > -1 and self.slots[i].status == slotStatuses.playing:
token = glob.tokens.getTokenFromUserID(self.slots[i].userID)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
token.enqueue(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
userID -- ID of user
"""
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
2016-04-19 17:40:59 +00:00
for i in range(0,16):
uid = self.slots[i].userID
if (self.slots[i].status & slotStatuses.playing > 0) and uid > -1:
2016-04-19 17:40:59 +00:00
token = glob.tokens.getTokenFromUserID(uid)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
token.enqueue(serverPackets.playerSkipped(uid))
# 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"""
for i in range(0,16):
if self.slots[i].userID > -1 and self.slots[i].status == slotStatuses.playing:
token = glob.tokens.getTokenFromUserID(self.slots[i].userID)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
token.enqueue(serverPackets.allPlayersSkipped())
log.info("MPROOM{}: All players have skipped!".format(self.matchID))
2016-04-19 17:40:59 +00:00
def playerCompleted(self, userID):
"""
Set userID's slot completed to True
userID -- ID of user
"""
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, None, None, None, None, None, None, True)
# 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"""
# Reset inProgress
self.inProgress = False
# Reset slots
for i in range(0,16):
if self.slots[i].userID > -1 and self.slots[i].status == slotStatuses.playing:
self.slots[i].status = slotStatuses.notReady
self.slots[i].loaded = False
self.slots[i].skip = False
self.slots[i].complete = False
2016-04-19 17:40:59 +00:00
# Send match update
self.sendUpdate()
# Send match complete
for i in range(0,16):
if self.slots[i].userID > -1:
token = glob.tokens.getTokenFromUserID(self.slots[i].userID)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
token.enqueue(serverPackets.matchComplete())
# Console output
log.info("MPROOM{}: Match completed".format(self.matchID))
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
"""
for i in range(0,16):
if self.slots[i].userID == userID:
2016-04-19 17:40:59 +00:00
return i
return None
def userJoin(self, userID):
"""
Add someone to users in match
userID -- user id of the user
return -- True if join success, False if fail (room is full)
"""
# Make sure we're not in this match
for i in range(0,16):
if self.slots[i].userID == userID:
# Set bugged slot to free
self.setSlot(i, slotStatuses.free, 0, -1, 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
self.setSlot(i, slotStatuses.notReady, 0, userID, 0)
# Send updated match data
self.sendUpdate()
# Console output
log.info("MPROOM{}: {} joined the room".format(self.matchID, userID))
2016-04-19 17:40:59 +00:00
return True
return False
def userLeft(self, userID):
"""
Remove someone from users in match
userID -- user if of the user
"""
# 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 that slot to free
self.setSlot(slotID, slotStatuses.free, 0, -1, 0)
# Check if everyone left
if self.countUsers() == 0:
# Dispose match
glob.matches.disposeMatch(self.matchID)
log.info("MPROOM{}: Room disposed".format(self.matchID))
2016-04-19 17:40:59 +00:00
return
# Check if host left
if userID == self.hostUserID:
# Give host to someone else
for i in range(0,16):
uid = self.slots[i].userID
2016-04-19 17:40:59 +00:00
if uid > -1:
self.setHost(uid)
break
# Send updated match data
self.sendUpdate()
# Console output
log.info("MPROOM{}: {} left the room".format(self.matchID, userID))
2016-04-19 17:40:59 +00:00
def userChangeSlot(self, userID, newSlotID):
"""
Change userID slot to newSlotID
userID -- user that changed slot
newSlotID -- slot id of new slot
"""
# Make sure the user is in room
oldSlotID = self.getUserSlotID(userID)
2016-09-02 15:45:10 +00:00
if oldSlotID is None:
2016-04-19 17:40:59 +00:00
return
# Make sure there is no one inside new slot
if self.slots[newSlotID].userID > -1 and self.slots[newSlotID].status != slotStatuses.free:
2016-04-19 17:40:59 +00:00
return
# Get old slot data
oldData = copy.deepcopy(self.slots[oldSlotID])
2016-04-19 17:40:59 +00:00
# Free old slot
self.setSlot(oldSlotID, slotStatuses.free, 0, -1, 0)
# Occupy new slot
self.setSlot(newSlotID, oldData.status, oldData.team, oldData.userID, oldData.mods)
2016-04-19 17:40:59 +00:00
# Send updated match data
self.sendUpdate()
# Console output
log.info("MPROOM{}: {} moved to slot {}".format(self.matchID, userID, newSlotID))
2016-04-19 17:40:59 +00:00
def changePassword(self, newPassword):
"""
Change match password to newPassword
newPassword -- new password string
"""
if newPassword != "":
2016-10-02 20:48:14 +00:00
self.matchPassword = generalUtils.stringMd5(newPassword)
else:
self.matchPassword = ""
2016-04-19 17:40:59 +00:00
# Send password change to every user in match
for i in range(0,16):
if self.slots[i].userID > -1:
token = glob.tokens.getTokenFromUserID(self.slots[i].userID)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
token.enqueue(serverPackets.changeMatchPassword(self.matchPassword))
# Send new match settings too
self.sendUpdate()
# Console output
log.info("MPROOM{}: Password changed to {}".format(self.matchID, self.matchPassword))
2016-04-19 17:40:59 +00:00
def changeMatchMods(self, mods):
"""
Set match global mods
mods -- mods bitwise int thing
"""
# Set new mods and send update
self.mods = mods
self.sendUpdate()
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
userID -- ID of user
has -- True if has beatmap, false if not
"""
# 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.noMap if not has else slotStatuses.notReady)
# Send updates
self.sendUpdate()
def transferHost(self, slotID):
"""
Transfer host to slotID
slotID -- ID of slot
"""
# Make sure there is someone in that slot
uid = self.slots[slotID].userID
2016-04-19 17:40:59 +00:00
if uid == -1:
return
# Transfer host
self.setHost(uid)
# Send updates
self.sendUpdate()
def playerFailed(self, userID):
"""
Send userID's failed packet to everyone in match
userID -- ID of user
"""
# 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
# Send packet to everyone
for i in range(0,16):
uid = self.slots[i].userID
2016-04-19 17:40:59 +00:00
if uid > -1:
token = glob.tokens.getTokenFromUserID(uid)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
token.enqueue(serverPackets.playerFailed(slotID))
# 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.
fro -- sender userID
to -- receiver userID
"""
# 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("FokaBot", 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
"""
c = 0
for i in range(0,16):
if self.slots[i].userID > -1:
2016-04-19 17:40:59 +00:00
c+=1
return c
def changeTeam(self, userID):
"""
Change userID's team
userID -- id of user
"""
# 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
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.sendUpdate()
def sendUpdate(self):
# Send to users in room
for i in range(0,16):
if self.slots[i].userID > -1:
token = glob.tokens.getTokenFromUserID(self.slots[i].userID)
2016-09-02 15:45:10 +00:00
if token is not None:
2016-04-19 17:40:59 +00:00
token.enqueue(serverPackets.updateMatch(self.matchID))
# Send to users in lobby
glob.streams.broadcast("lobby", serverPackets.updateMatch(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
"""
if match.matchTeamType != matchTeamTypes.teamVs or matchTeamTypes != matchTeamTypes.tagTeamVs:
# 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].userID > -1 and (self.slots[i].status&slotStatuses.noMap) == 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