aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCerapter <cerap@protonmail.com>2018-08-15 23:30:46 +0200
committerCerapter <cerap@protonmail.com>2018-08-15 23:30:46 +0200
commit9ce1d3fa40b48fb55ea94f7d833ee029e7d23a7b (patch)
treea50cd83c9c28298bfd9ba7b5253c988fdd836068
parent8c859398f1b1308633584103c334bb103bf6cf2c (diff)
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.
-rw-r--r--server/aoprotocol.py27
-rw-r--r--server/area_manager.py106
-rw-r--r--server/client_manager.py8
-rw-r--r--server/commands.py70
-rw-r--r--server/tsuserver.py6
5 files changed, 188 insertions, 29 deletions
diff --git a/server/aoprotocol.py b/server/aoprotocol.py
index 912c0e72..b36aa613 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 36ade649..d0ff1cb4 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 e127880c..62e141dd 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 c1143d29..d952d15a 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 a7aed5db..97b4b905 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('<dollar>G', client.area.get_abbreviation(), char_name)
+ ooc_name = '{}[{}][{}]'.format('<dollar>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('<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)
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)