async def mp_unlock(p: Player, m: Match, msg: Sequence[str]) -> str: """Unlock locked slots in `m`.""" for slot in m.slots: if slot.status == SlotStatus.locked: slot.status = SlotStatus.open m.enqueue(packets.updateMatch(m)) return 'All locked slots unlocked.'
async def mp_start(p: Player, m: Match, msg: Sequence[str]) -> str: for s in m.slots: if s.status & SlotStatus.has_player \ and not s.status & SlotStatus.no_map: s.status = SlotStatus.playing m.in_progress = True m.enqueue(await packets.matchStart(m)) return 'Good luck!'
async def mp_abort(p: Player, m: Match, msg: Sequence[str]) -> str: if not m.in_progress: return 'Abort what?' for s in m.slots: if s.status & SlotStatus.playing: s.status = SlotStatus.not_ready m.in_progress = False m.enqueue(await packets.updateMatch(m)) m.enqueue(await packets.matchAbort()) return 'Match aborted.'
async def mp_teams(p: Player, m: Match, msg: Sequence[str]) -> str: if len(msg) != 1 or msg[0] not in ('head-to-head', 'tag-coop', 'team-vs', 'tag-team-vs'): return 'Invalid syntax: !mp teams <mode>' m.team_type = { 'head-to-head': MatchTeamTypes.head_to_head, 'tag-coop': MatchTeamTypes.tag_coop, 'team-vs': MatchTeamTypes.team_vs, 'tag-team-vs': MatchTeamTypes.tag_team_vs }[msg[0]] m.enqueue(packets.updateMatch(m)) return 'Match team type updated.'
async def mp_condition(p: Player, m: Match, msg: Sequence[str]) -> str: if len(msg) != 1 or msg[0] not in ('score', 'accuracy', 'combo', 'scorev2'): return 'Invalid syntax: !mp condition <mode>' m.match_scoring = { 'score': MatchScoringTypes.score, 'accuracy': MatchScoringTypes.accuracy, 'combo': MatchScoringTypes.combo, 'scorev2': MatchScoringTypes.scorev2 }[msg[0]] m.enqueue(packets.updateMatch(m)) return 'Match win condition updated.'
def join_match(self, m: Match, passwd: str) -> bool: if self.match: printlog(f'{self} tried to join multiple matches?') self.enqueue(packets.matchJoinFail(m)) return False if m.chat: # Match already exists, we're simply joining. if passwd != m.passwd: # eff: could add to if? or self.create_m.. printlog(f'{self} tried to join {m} with incorrect passwd.') self.enqueue(packets.matchJoinFail(m)) return False if (slotID := m.get_free()) is None: printlog(f'{self} tried to join a full match.') self.enqueue(packets.matchJoinFail(m)) return False
async def add(self, m: Match) -> None: if m in self.matches: await plog(f'{m} already in matches list!') return if (free := self.get_free()) is not None: m.id = free await plog(f'Adding {m} to matches list.') self.matches[free] = m
async def mp_mods(p: Player, m: Match, msg: Sequence[str]) -> str: """Set `m`'s mods, from string form.""" if len(msg) != 1 or not ~len(msg[0]) & 1: # len(msg[0]) % 2 == 0 return 'Invalid syntax: !mp mods <mods>' mods = Mods.from_str(msg[0]) if m.freemods: if p.id == m.host.id: # allow host to set speed-changing mods. m.mods = mods & Mods.SPEED_CHANGING # set slot mods m.get_slot(p).mods = mods & ~Mods.SPEED_CHANGING else: # not freemods, set match mods. m.mods = mods m.enqueue(packets.updateMatch(m)) return 'Match mods updated.'
def append(self, m: Match) -> bool: """Append `m` to the list.""" if (free := self.get_free()) is not None: # set the id of the match to the lowest available free. m.id = free self[free] = m if glob.app.debug: log(f'{m} added to matches list.') return True
async def add(self, m: Match) -> None: if m in self.matches: plog(f'{m} double-added to matches list?') return if (free := self.get_free()) is not None: # set the id of the match # to our free slot found. m.id = free self.matches[free] = m if glob.config.debug: plog(f'{m} added to matches list.')
async def mp_abort(p: Player, m: Match, msg: Sequence[str]) -> str: """Abort an in-progress multiplayer match.""" if not m.in_progress: return 'Abort what?' m.unready_players(expected=SlotStatus.playing) m.in_progress = False m.enqueue(packets.matchAbort()) m.enqueue(packets.updateMatch(m)) return 'Match aborted.'
async def mp_abort(_, __, match: Match) -> str: """Abort current multiplayer session""" if not match.in_prog: return match.unready_players(wanted=slotStatus.playing) match.in_prog = False match.enqueue(writer.matchAbort()) match.enqueue_state() return 'Match aborted.'
async def mp_mods(_, args: list, match: Match) -> str: """Set the mods of the lobby""" if len(args) < 1: return 'You must provide the mods to set!' if args[0].isdecimal(): mods = Mods(args[0]) elif isinstance(args[0], str): mods = Mods.convert_str(args[0]) else: return 'Invalid mods.' if match.fm: match.mods = mods & Mods.SPEED_MODS for slot in match.slots: if slot.status & slotStatus.has_player: slot.mods = mods & ~Mods.SPEED_MODS else: match.mods = match.mods = mods match.enqueue_state() return 'Updated mods.'
async def mp_start(_, args: list, match: Match) -> Optional[str]: """Starts the current match, either forcefully or on a timer""" if len(args) < 1: return "Please provide either a timer to start or cancel/force" if not args[0]: # start now if any([s.status == slotStatus.not_ready for s in match.slots]): return ( "Not all players are ready. You can force start with `!mp start force`" ) elif args[0] == "force": match.start() match.chat.send(glob.bot, "Starting match. Have fun!", send_self=False) elif args[0].isdecimal(): def start_timer(): match.start() match.chat.send(glob.bot, "Starting match. Have fun!", send_self=False) def alert_timer(remaining): match.chat.send( glob.bot, f"Starting match in {remaining} seconds!", send_self=False, ) loop = asyncio.get_event_loop() timer = int(args[0]) match.start_task = loop.call_later(timer, start_timer) match.alert_tasks = [ loop.call_later( timer - countdown, lambda countdown=countdown: alert_timer(countdown), ) for countdown in (30, 15, 5, 4, 3, 2, 1) if countdown < timer ] return f"Starting match in {timer} seconds" elif args[0] == "cancel": if not match.start_task: return match.start_task.cancel() for alert in match.alert_tasks: alert.cancel() match.start_task = None match.alert_tasks = None return "Cancelled timer." else: return "Unknown argument. Please use seconds/force/cancel"
async def mp_fm(user: Player, args: list, match: Match) -> str: if len(args) < 1: return 'Please provide whether to turn freemod on or off!' if args[0] == 'on': match.fm = True for slot in match.slots: if slot.status & slotStatus.has_player: slot.mods = match.mods & ~Mods.SPEED_MODS match.mods &= Mods.SPEED_MODS else: match.fm = False match.mods &= Mods.SPEED_MODS match.mods |= (match.get_slot(user)).mods for slot in match.slots: if slot.status & slotStatus.has_player: slot.mods = Mods.NOMOD match.enqueue_state() return 'Freemod state toggled.'
async def mp_freemods(p: Player, m: Match, msg: Sequence[str]) -> str: if len(msg) != 1 or msg[0] not in ('on', 'off'): return 'Invalid syntax: !mp freemods <on/off>' if msg[0] == 'on': # central mods -> all players mods. m.freemods = True for s in m.slots: if s.status & SlotStatus.has_player: s.mods = m.mods & ~Mods.SPEED_CHANGING m.mods = m.mods & Mods.SPEED_CHANGING else: # host mods -> central mods. m.freemods = False for s in m.slots: if s.player and s.player.id == m.host.id: m.mods = s.mods | (m.mods & Mods.SPEED_CHANGING) break m.enqueue(packets.updateMatch(m)) return 'Match freemod status updated.'
async def read_match(data: bytearray) -> Tuple[Match, int]: """ Read an osu! match from `data`. """ m = Match() # Ignore match id (i32) & inprogress (i8). offset = 3 # Read type & match mods. m.type, m.mods = struct.unpack('<bI', data[offset:offset + 5]) offset += 5 # Read match name & password. m.name, offs = await read_string(data[offset:]) offset += offs m.passwd, offs = await read_string(data[offset:]) offset += offs # Ignore map's name. offset += 1 if data[offset - 1] == 0x0b: offset += sum(await read_uleb128(data[offset:])) # Read beatmap information (id & md5). map_id = int.from_bytes(data[offset:offset + 4], 'little') offset += 4 map_md5, offs = await read_string(data[offset:]) offset += offs # Get beatmap object for map selected. m.bmap = await Beatmap.from_md5(map_md5) if not m.bmap and map_id != (1 << 32) - 1: # If they pick an unsubmitted map, # just give them Vivid [Insane] lol. vivid_md5 = '1cf5b2c2edfafd055536d2cefcb89c0e' m.bmap = await Beatmap.from_md5(vivid_md5) # Read slot statuses. for s in m.slots: s.status = data[offset] offset += 1 # Read slot teams. for s in m.slots: s.team = data[offset] offset += 1 for s in m.slots: if s.status & SlotStatus.has_player: # Dont think we need this? offset += 4 # Read match host. user_id = int.from_bytes(data[offset:offset + 4], 'little') m.host = await glob.players.get_by_id(user_id) offset += 4 # Read match mode, match scoring, # team type, and freemods. m.game_mode = data[offset] offset += 1 m.match_scoring = data[offset] offset += 1 m.team_type = data[offset] offset += 1 m.freemods = data[offset] offset += 1 # If we're in freemods mode, # read individual slot mods. if m.freemods: for s in m.slots: s.mods = int.from_bytes(data[offset:offset + 4], 'little') offset += 4 # Read the seed from multi. # XXX: Used for mania random mod. m.seed = int.from_bytes(data[offset:offset + 4], 'little') return m, offset + 4
async def read_match(self) -> Match: """Read an osu! match from the internal buffer.""" m = Match() # ignore match id (i16) and inprogress (i8). self._buf = self._buf[3:] m.type = MatchTypes(await self.read_i8()) m.mods = Mods(await self.read_i32()) m.name = await self.read_string() m.passwd = await self.read_string() # ignore the map's name, we're going # to get all it's info from the md5. await self.read_string() map_id = await self.read_i32() map_md5 = await self.read_string() m.bmap = await Beatmap.from_md5(map_md5) if not m.bmap and map_id != (1 << 32) - 1: # if they pick an unsubmitted map, # just give them vivid [insane] lol. vivid_md5 = '1cf5b2c2edfafd055536d2cefcb89c0e' m.bmap = await Beatmap.from_md5(vivid_md5) for slot in m.slots: slot.status = await self.read_i8() for slot in m.slots: slot.team = Teams(await self.read_i8()) for slot in m.slots: if slot.status & SlotStatus.has_player: # we don't need this, ignore it. self._buf = self._buf[4:] host_id = await self.read_i32() m.host = await glob.players.get_by_id(host_id) m.mode = GameMode(await self.read_i8()) m.match_scoring = MatchScoringTypes(await self.read_i8()) m.team_type = MatchTeamTypes(await self.read_i8()) m.freemods = await self.read_i8() == 1 # if we're in freemods mode, # read individual slot mods. if m.freemods: for slot in m.slots: slot.mods = Mods(await self.read_i32()) # read the seed (used for mania) m.seed = await self.read_i32() return m
def read_match(self) -> Match: """Read an osu! match from the internal buffer.""" m = Match() # ignore match id (i16) and inprogress (i8). self.body_view = self.body_view[3:] self.read_i8() # powerplay unused m.mods = Mods(self.read_i32()) m.name = self.read_string() m.passwd = self.read_string() m.map_name = self.read_string() m.map_id = self.read_i32() m.map_md5 = self.read_string() for slot in m.slots: slot.status = SlotStatus(self.read_i8()) for slot in m.slots: slot.team = MatchTeams(self.read_i8()) for slot in m.slots: if slot.status & SlotStatus.has_player: # we don't need this, ignore it. self.body_view = self.body_view[4:] host_id = self.read_i32() m.host = glob.players.get(id=host_id) m.mode = GameMode(self.read_i8()) m.win_condition = MatchWinConditions(self.read_i8()) m.team_type = MatchTeamTypes(self.read_i8()) m.freemods = self.read_i8() == 1 # if we're in freemods mode, # read individual slot mods. if m.freemods: for slot in m.slots: slot.mods = Mods(self.read_i32()) # read the seed (used for mania) m.seed = self.read_i32() return m
async def read_match(self) -> Match: """Read an osu! match from the internal buffer.""" m = Match() # ignore match id (i16) and inprogress (i8). self._buf = self._buf[3:] #m.type = MatchTypes(await self.read_i8()) if await self.read_i8() == 1: point_of_interest() # what is powerplay m.mods = Mods(await self.read_i32()) m.name = await self.read_string() m.passwd = await self.read_string() m.map_name = await self.read_string() m.map_id = await self.read_i32() m.map_md5 = await self.read_string() for slot in m.slots: slot.status = await self.read_i8() for slot in m.slots: slot.team = MatchTeams(await self.read_i8()) for slot in m.slots: if slot.status & SlotStatus.has_player: # we don't need this, ignore it. self._buf = self._buf[4:] host_id = await self.read_i32() m.host = await glob.players.get(id=host_id) m.mode = GameMode(await self.read_i8()) m.win_condition = MatchWinConditions(await self.read_i8()) m.team_type = MatchTeamTypes(await self.read_i8()) m.freemods = await self.read_i8() == 1 # if we're in freemods mode, # read individual slot mods. if m.freemods: for slot in m.slots: slot.mods = Mods(await self.read_i32()) # read the seed (used for mania) m.seed = await self.read_i32() return m
user.enqueue( writer.sendMessage(fromname=glob.bot.name, msg='Request accepted! Creating match...', tarname=user.name, fromid=glob.bot.id)) owner.enqueue( writer.sendMessage( fromname=glob.bot.name, msg= f'{user.name} accepted your request to battle their clan {user.clan.name}! Creating match...', tarname=owner.name, fromid=glob.bot.id)) match = Match() match.name = f'Clan Battle: ({clan.name}) vs ({user.clan.name})' match.clan_battle = True match.clan_1 = clan match.clan_2 = user.clan match.host = owner match.type = teamTypes.team glob.matches[match.id] = match mp_chan = Channel(name='#multiplayer', desc=f'Multiplayer channel for match ID {match.id}', auto=False,
async def mp_randpw(p: Player, m: Match, msg: Sequence[str]) -> str: """Randomize `m`'s password.""" m.passwd = cmyui.rstring(16) return 'Match password randomized.'
def read_match(data: bytearray) -> Tuple[Match, int]: """ Read an osu! match from `data`. """ m = Match() # Ignore match id (i32) & inprogress (i8). offset = 3 # Read type & match mods. m.type, m.mods = struct.unpack('<bI', data[offset:offset + 5]) offset += 5 # Read match name & password. m.name, offs = read_string(data[offset:]) offset += offs m.passwd, offs = read_string(data[offset:]) offset += offs # Ignore map's name. offset += 1 if data[offset - 1] == 0x0b: offset += sum(read_uleb128(data[offset:])) # Ignore map's id. offset += 4 # Read map's md5 (only thing needed to get map). map_md5, offs = read_string(data[offset:]) offset += offs # Get beatmap object for map selected. m.bmap = Beatmap.from_md5(map_md5) # Read slot statuses. for s in m.slots: s.status = data[offset] offset += 1 # Read slot teams. for s in m.slots: s.team = data[offset] offset += 1 for s in m.slots: if s.status & SlotStatus.has_player: # Dont think we need this? offset += 4 # Read match host. m.host = glob.players.get_by_id( int.from_bytes(data[offset:offset + 4], 'little')) offset += 4 # Read match mode, match scoring, # team type, and freemods. m.game_mode = data[offset] offset += 1 m.match_scoring = data[offset] offset += 1 m.team_type = data[offset] offset += 1 m.freemods = data[offset] offset += 1 # If we're in freemods mode, # read individual slot mods. if m.freemods: for s in m.slots: s.mods = int.from_bytes(data[offset:offset + 4], 'little') offset += 4 # Read the seed from multi. # XXX: Used for mania random mod. m.seed = int.from_bytes(data[offset:offset + 4], 'little') return m, offset + 4