diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 912c0e7..b36aa61 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -490,18 +490,23 @@ class AOProtocol(asyncio.Protocol): return try: name, length = self.server.get_song_data(args[0]) - if len(args) > 2: - showname = args[2] - if len(showname) > 0 and not self.client.area.showname_changes_allowed: - self.client.send_host_message("Showname changes are forbidden in this area!") - return - self.client.area.play_music_shownamed(name, self.client.char_id, showname, length) - self.client.area.add_music_playing_shownamed(self.client, showname, name) + + if self.client.area.jukebox: + self.client.area.add_jukebox_vote(self.client, name, length) + logger.log_server('[{}][{}]Added a jukebox vote for {}.'.format(self.client.area.id, self.client.get_char_name(), name), self.client) else: - self.client.area.play_music(name, self.client.char_id, length) - self.client.area.add_music_playing(self.client, name) - logger.log_server('[{}][{}]Changed music to {}.' - .format(self.client.area.id, self.client.get_char_name(), name), self.client) + if len(args) > 2: + showname = args[2] + if len(showname) > 0 and not self.client.area.showname_changes_allowed: + self.client.send_host_message("Showname changes are forbidden in this area!") + return + self.client.area.play_music_shownamed(name, self.client.char_id, showname, length) + self.client.area.add_music_playing_shownamed(self.client, showname, name) + else: + self.client.area.play_music(name, self.client.char_id, length) + self.client.area.add_music_playing(self.client, name) + logger.log_server('[{}][{}]Changed music to {}.' + .format(self.client.area.id, self.client.get_char_name(), name), self.client) except ServerError: return except ClientError as ex: diff --git a/server/area_manager.py b/server/area_manager.py index 36ade64..d0ff1cb 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -26,7 +26,7 @@ from server.evidence import EvidenceList class AreaManager: class Area: - def __init__(self, area_id, server, name, background, bg_lock, evidence_mod = 'FFA', locking_allowed = False, iniswap_allowed = True, showname_changes_allowed = False, shouts_allowed = True): + def __init__(self, area_id, server, name, background, bg_lock, evidence_mod = 'FFA', locking_allowed = False, iniswap_allowed = True, showname_changes_allowed = False, shouts_allowed = True, jukebox = False, abbreviation = ''): self.iniswap_allowed = iniswap_allowed self.clients = set() self.invite_list = {} @@ -52,6 +52,7 @@ class AreaManager: self.locking_allowed = locking_allowed self.showname_changes_allowed = showname_changes_allowed self.shouts_allowed = shouts_allowed + self.abbreviation = abbreviation self.owned = False self.cards = dict() @@ -64,6 +65,9 @@ class AreaManager: self.is_locked = False + self.jukebox = jukebox + self.jukebox_votes = [] + def new_client(self, client): self.clients.add(client) @@ -109,6 +113,70 @@ class AreaManager: if client.get_char_name() in char_link and char in char_link: return False return True + + def add_jukebox_vote(self, client, music_name, length=-1): + if length <= 0: + self.remove_jukebox_vote(client, False) + else: + self.remove_jukebox_vote(client, True) + self.jukebox_votes.append(self.JukeboxVote(client, music_name, length)) + client.send_host_message('Your song was added to the jukebox.') + if len(self.jukebox_votes) == 1: + self.start_jukebox() + + def remove_jukebox_vote(self, client, silent): + for current_vote in self.jukebox_votes: + if current_vote.client.id == client.id: + self.jukebox_votes.remove(current_vote) + if not silent: + client.send_host_message('You removed your song from the jukebox.') + + def get_jukebox_picked(self): + if len(self.jukebox_votes) == 0: + return None + elif len(self.jukebox_votes) == 1: + return self.jukebox_votes[0] + else: + weighted_votes = [] + for current_vote in self.jukebox_votes: + i = 0 + while i < current_vote.chance: + weighted_votes.append(current_vote) + i += 1 + return random.choice(weighted_votes) + + def start_jukebox(self): + # There is a probability that the jukebox feature has been turned off since then, + # we should check that. + # We also do a check if we were the last to play a song, just in case. + if not self.jukebox: + if self.current_music_player == 'The Jukebox' and self.current_music_player_ipid == 'has no IPID': + self.current_music = '' + return + + vote_picked = self.get_jukebox_picked() + + if vote_picked is None: + self.current_music = '' + return + + self.send_command('MC', vote_picked.name, vote_picked.client.char_id) + + self.current_music_player = 'The Jukebox' + self.current_music_player_ipid = 'has no IPID' + self.current_music = vote_picked.name + + for current_vote in self.jukebox_votes: + # Choosing the same song will get your votes down to 0, too. + # Don't want the same song twice in a row! + if current_vote.name == vote_picked.name: + current_vote.chance = 0 + else: + current_vote.chance += 1 + + if self.music_looper: + self.music_looper.cancel() + self.music_looper = asyncio.get_event_loop().call_later(vote_picked.length, lambda: self.start_jukebox()) def play_music(self, name, cid, length=-1): self.send_command('MC', name, cid) @@ -188,18 +256,12 @@ class AreaManager: for client in self.clients: client.send_command('LE', *self.get_evidence_list(client)) - def get_abbreviation(self): - if self.name.lower().startswith("courtroom"): - return "CR" + self.name.split()[-1] - elif self.name.lower().startswith("area"): - return "A" + self.name.split()[-1] - elif len(self.name.split()) > 1: - return "".join(item[0].upper() for item in self.name.split()) - elif len(self.name) > 3: - return self.name[:3].upper() - else: - return self.name.upper() - + class JukeboxVote: + def __init__(self, client, name, length): + self.client = client + self.name = name + self.length = length + self.chance = 1 def __init__(self, server): self.server = server @@ -221,8 +283,12 @@ class AreaManager: item['showname_changes_allowed'] = False if 'shouts_allowed' not in item: item['shouts_allowed'] = True + if 'jukebox' not in item: + item['jukebox'] = False + if 'abbreviation' not in item: + item['abbreviation'] = self.get_generated_abbreviation(item['area']) self.areas.append( - self.Area(self.cur_id, self.server, item['area'], item['background'], item['bglock'], item['evidence_mod'], item['locking_allowed'], item['iniswap_allowed'], item['showname_changes_allowed'], item['shouts_allowed'])) + self.Area(self.cur_id, self.server, item['area'], item['background'], item['bglock'], item['evidence_mod'], item['locking_allowed'], item['iniswap_allowed'], item['showname_changes_allowed'], item['shouts_allowed'], item['jukebox'], item['abbreviation'])) self.cur_id += 1 def default_area(self): @@ -239,3 +305,15 @@ class AreaManager: if area.id == num: return area raise AreaError('Area not found.') + + def get_generated_abbreviation(self, name): + if name.lower().startswith("courtroom"): + return "CR" + name.split()[-1] + elif name.lower().startswith("area"): + return "A" + name.split()[-1] + elif len(name.split()) > 1: + return "".join(item[0].upper() for item in name.split()) + elif len(name) > 3: + return name[:3].upper() + else: + return name.upper() diff --git a/server/client_manager.py b/server/client_manager.py index e127880..62e141d 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -106,6 +106,8 @@ class ClientManager: return True def disconnect(self): + if self.area.jukebox: + self.area.remove_jukebox_vote(self, True) self.transport.close() def change_character(self, char_id, force=False): @@ -171,6 +173,10 @@ class ClientManager: if area.is_locked and not self.is_mod and not self.id in area.invite_list: #self.send_host_message('This area is locked - you will be unable to send messages ICly.') raise ClientError("That area is locked!") + + if self.area.jukebox: + self.area.remove_jukebox_vote(self, True) + old_area = self.area if not area.is_char_available(self.char_id): try: @@ -203,7 +209,7 @@ class ClientManager: for client in [x for x in area.clients if x.is_cm]: owner = 'MASTER: {}'.format(client.get_char_name()) break - msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(area.get_abbreviation(), area.name, len(area.clients), area.status, owner, lock[area.is_locked]) + msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(area.abbreviation, area.name, len(area.clients), area.status, owner, lock[area.is_locked]) if self.area == area: msg += ' [*]' self.send_host_message(msg) diff --git a/server/commands.py b/server/commands.py index c1143d2..d952d15 100644 --- a/server/commands.py +++ b/server/commands.py @@ -159,6 +159,76 @@ def ooc_cmd_currentmusic(client, arg): client.send_host_message('The current music is {} and was played by {}.'.format(client.area.current_music, client.area.current_music_player)) +def ooc_cmd_jukebox_toggle(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') + client.area.jukebox = not client.area.jukebox + client.area.send_host_message('A mod has set the jukebox to {}.'.format(client.area.jukebox)) + +def ooc_cmd_jukebox_skip(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') + if not client.area.jukebox: + raise ClientError('This area does not have a jukebox.') + if len(client.area.jukebox_votes) == 0: + raise ClientError('There is no song playing right now, skipping is pointless.') + client.area.start_jukebox() + if len(client.area.jukebox_votes) == 1: + client.area.send_host_message('A mod has forced a skip, restarting the only jukebox song.') + else: + client.area.send_host_message('A mod has forced a skip to the next jukebox song.') + logger.log_server('[{}][{}]Skipped the current jukebox song.'.format(client.area.id, client.get_char_name())) + +def ooc_cmd_jukebox(client, arg): + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') + if not client.area.jukebox: + raise ClientError('This area does not have a jukebox.') + if len(client.area.jukebox_votes) == 0: + client.send_host_message('The jukebox has no songs in it.') + else: + total = 0 + songs = [] + voters = dict() + chance = dict() + message = '' + + for current_vote in client.area.jukebox_votes: + if songs.count(current_vote.name) == 0: + songs.append(current_vote.name) + voters[current_vote.name] = [current_vote.client] + chance[current_vote.name] = current_vote.chance + else: + voters[current_vote.name].append(current_vote.client) + chance[current_vote.name] += current_vote.chance + total += current_vote.chance + + for song in songs: + message += '\n- ' + song + '\n' + message += '-- VOTERS: ' + + first = True + for voter in voters[song]: + if first: + first = False + else: + message += ', ' + message += voter.get_char_name() + ' [' + str(voter.id) + ']' + if client.is_mod: + message += '(' + str(voter.ipid) + ')' + message += '\n' + + if total == 0: + message += '-- CHANCE: 100' + else: + message += '-- CHANCE: ' + str(round(chance[song] / total * 100)) + + client.send_host_message('The jukebox has the following songs in it:{}'.format(message)) + def ooc_cmd_coinflip(client, arg): if len(arg) != 0: raise ArgumentError('This command has no arguments.') diff --git a/server/tsuserver.py b/server/tsuserver.py index a7aed5d..97b4b90 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -232,7 +232,7 @@ class TsuServer3: def broadcast_global(self, client, msg, as_mod=False): char_name = client.get_char_name() - ooc_name = '{}[{}][{}]'.format('G', client.area.get_abbreviation(), char_name) + ooc_name = '{}[{}][{}]'.format('G', client.area.abbreviation, char_name) if as_mod: ooc_name += '[M]' self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: not x.muted_global) @@ -242,7 +242,7 @@ class TsuServer3: def send_modchat(self, client, msg): name = client.name - ooc_name = '{}[{}][{}]'.format('M', client.area.get_abbreviation(), name) + ooc_name = '{}[{}][{}]'.format('M', client.area.abbreviation, name) self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: x.is_mod) if self.config['use_district']: self.district_client.send_raw_message( @@ -251,7 +251,7 @@ class TsuServer3: def broadcast_need(self, client, msg): char_name = client.get_char_name() area_name = client.area.name - area_id = client.area.get_abbreviation() + area_id = client.area.abbreviation self.send_all_cmd_pred('CT', '{}'.format(self.config['hostname']), '=== Advert ===\r\n{} in {} [{}] needs {}\r\n===============' .format(char_name, area_name, area_id, msg), pred=lambda x: not x.muted_adverts)