Jukebox + Area abbreviation finetuning.

- An area can now have a custom `abbreviation: XXX` set in `areas.yaml`.
- Areas can have jukebox mode on by `jukebox: true` in `areas.yaml`.
  - When this mode is on, music changing is actually voting for the next
music.
  - If no music is playing, or there is only your vote in there, it
behaves as normal music changing.
  - In case of multiple votes, your vote gets added to a list, and may
have a chance of being picked.
  - Check this list with `/jukebox`.
  - If not your music is picked, your voting power increases, making
your music being picked next more likely.
  - If yours is picked, your voting power is reset to 0.
  - No matter how many people select the same song, if the song gets
picked, all of them will have their voting power reset to 0.
  - Leaving an area, or picking a not-really-a-song (like 'PRELUDE',
which is a category marker, basically), will remove your vote.
  - If there are no votes left (because every left, for example), the
jukebox stops playing songs.
  - Mods can force a skip by `/jukebox_skip`.
  - Mods can also toggle an area's jukebox with `/jukebox_toggle`.
  - Mods can also still play songs with `/play`, though they might get
cucked by the Jukebox.
This commit is contained in:
Cerapter 2018-08-15 23:30:46 +02:00
parent 8c859398f1
commit 9ce1d3fa40
5 changed files with 188 additions and 29 deletions

View File

@ -490,6 +490,11 @@ class AOProtocol(asyncio.Protocol):
return return
try: try:
name, length = self.server.get_song_data(args[0]) name, length = self.server.get_song_data(args[0])
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:
if len(args) > 2: if len(args) > 2:
showname = args[2] showname = args[2]
if len(showname) > 0 and not self.client.area.showname_changes_allowed: if len(showname) > 0 and not self.client.area.showname_changes_allowed:

View File

@ -26,7 +26,7 @@ from server.evidence import EvidenceList
class AreaManager: class AreaManager:
class Area: 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.iniswap_allowed = iniswap_allowed
self.clients = set() self.clients = set()
self.invite_list = {} self.invite_list = {}
@ -52,6 +52,7 @@ class AreaManager:
self.locking_allowed = locking_allowed self.locking_allowed = locking_allowed
self.showname_changes_allowed = showname_changes_allowed self.showname_changes_allowed = showname_changes_allowed
self.shouts_allowed = shouts_allowed self.shouts_allowed = shouts_allowed
self.abbreviation = abbreviation
self.owned = False self.owned = False
self.cards = dict() self.cards = dict()
@ -64,6 +65,9 @@ class AreaManager:
self.is_locked = False self.is_locked = False
self.jukebox = jukebox
self.jukebox_votes = []
def new_client(self, client): def new_client(self, client):
self.clients.add(client) self.clients.add(client)
@ -110,6 +114,70 @@ class AreaManager:
return False return False
return True 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): def play_music(self, name, cid, length=-1):
self.send_command('MC', name, cid) self.send_command('MC', name, cid)
if self.music_looper: if self.music_looper:
@ -188,18 +256,12 @@ class AreaManager:
for client in self.clients: for client in self.clients:
client.send_command('LE', *self.get_evidence_list(client)) client.send_command('LE', *self.get_evidence_list(client))
def get_abbreviation(self): class JukeboxVote:
if self.name.lower().startswith("courtroom"): def __init__(self, client, name, length):
return "CR" + self.name.split()[-1] self.client = client
elif self.name.lower().startswith("area"): self.name = name
return "A" + self.name.split()[-1] self.length = length
elif len(self.name.split()) > 1: self.chance = 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()
def __init__(self, server): def __init__(self, server):
self.server = server self.server = server
@ -221,8 +283,12 @@ class AreaManager:
item['showname_changes_allowed'] = False item['showname_changes_allowed'] = False
if 'shouts_allowed' not in item: if 'shouts_allowed' not in item:
item['shouts_allowed'] = True 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.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 self.cur_id += 1
def default_area(self): def default_area(self):
@ -239,3 +305,15 @@ class AreaManager:
if area.id == num: if area.id == num:
return area return area
raise AreaError('Area not found.') 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()

View File

@ -106,6 +106,8 @@ class ClientManager:
return True return True
def disconnect(self): def disconnect(self):
if self.area.jukebox:
self.area.remove_jukebox_vote(self, True)
self.transport.close() self.transport.close()
def change_character(self, char_id, force=False): 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: 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.') #self.send_host_message('This area is locked - you will be unable to send messages ICly.')
raise ClientError("That area is locked!") raise ClientError("That area is locked!")
if self.area.jukebox:
self.area.remove_jukebox_vote(self, True)
old_area = self.area old_area = self.area
if not area.is_char_available(self.char_id): if not area.is_char_available(self.char_id):
try: try:
@ -203,7 +209,7 @@ class ClientManager:
for client in [x for x in area.clients if x.is_cm]: for client in [x for x in area.clients if x.is_cm]:
owner = 'MASTER: {}'.format(client.get_char_name()) owner = 'MASTER: {}'.format(client.get_char_name())
break 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: if self.area == area:
msg += ' [*]' msg += ' [*]'
self.send_host_message(msg) self.send_host_message(msg)

View File

@ -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.send_host_message('The current music is {} and was played by {}.'.format(client.area.current_music,
client.area.current_music_player)) 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): def ooc_cmd_coinflip(client, arg):
if len(arg) != 0: if len(arg) != 0:
raise ArgumentError('This command has no arguments.') raise ArgumentError('This command has no arguments.')

View File

@ -232,7 +232,7 @@ class TsuServer3:
def broadcast_global(self, client, msg, as_mod=False): def broadcast_global(self, client, msg, as_mod=False):
char_name = client.get_char_name() char_name = client.get_char_name()
ooc_name = '{}[{}][{}]'.format('<dollar>G', client.area.get_abbreviation(), char_name) ooc_name = '{}[{}][{}]'.format('<dollar>G', client.area.abbreviation, char_name)
if as_mod: if as_mod:
ooc_name += '[M]' ooc_name += '[M]'
self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: not x.muted_global) 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): def send_modchat(self, client, msg):
name = client.name name = client.name
ooc_name = '{}[{}][{}]'.format('<dollar>M', client.area.get_abbreviation(), name) ooc_name = '{}[{}][{}]'.format('<dollar>M', client.area.abbreviation, name)
self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: x.is_mod) self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: x.is_mod)
if self.config['use_district']: if self.config['use_district']:
self.district_client.send_raw_message( self.district_client.send_raw_message(
@ -251,7 +251,7 @@ class TsuServer3:
def broadcast_need(self, client, msg): def broadcast_need(self, client, msg):
char_name = client.get_char_name() char_name = client.get_char_name()
area_name = client.area.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']), self.send_all_cmd_pred('CT', '{}'.format(self.config['hostname']),
'=== Advert ===\r\n{} in {} [{}] needs {}\r\n===============' '=== Advert ===\r\n{} in {} [{}] needs {}\r\n==============='
.format(char_name, area_name, area_id, msg), pred=lambda x: not x.muted_adverts) .format(char_name, area_name, area_id, msg), pred=lambda x: not x.muted_adverts)