Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Jukebox Fix for #165 #175

Closed
wants to merge 9 commits into from
114 changes: 5 additions & 109 deletions server/area_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from server.exceptions import AreaError
from server.evidence import EvidenceList
from server.client_manager import ClientManager

from server.jukebox import Jukebox

class AreaManager:
@dataclass
Expand Down Expand Up @@ -94,6 +94,10 @@ def __init__(self,
self.is_locked = self.Locked.FREE
self.blankposting_allowed = True
self.non_int_pres_only = non_int_pres_only

if jukebox:
self.jukebox_obj = Jukebox(self)

self.jukebox = jukebox
self.jukebox_votes = []
self.jukebox_prev_char_id = -1
Expand Down Expand Up @@ -298,105 +302,6 @@ def is_iniswap(self, client: ClientManager.Client, preanim: str, anim: str, char
return False
return not self.server.char_emotes[char].validate(preanim, anim, sfx)

def add_jukebox_vote(self, client: ClientManager.Client, music_name: str, length: int = -1, showname: str = ''):
"""Cast a vote on the jukebox.
Args:
client (ClientManager.Client): Client that is requesting
music_name (str): track name
length (int, optional): length of track. Defaults to -1.
showname (str, optional): showname of voter. Defaults to ''.
"""
if not self.jukebox:
return
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, showname))
client.send_ooc('Your song was added to the jukebox.')
if len(self.jukebox_votes) == 1:
self.start_jukebox()

def remove_jukebox_vote(self, client: ClientManager.Client, silent: bool):
"""Removes a vote on the jukebox.
Args:
client (ClientManager.Client): client whose vote should be removed
silent (bool): do not notify client
"""

if not self.jukebox:
return
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_ooc(
'You removed your song from the jukebox.')

def get_jukebox_picked(self):
"""Randomly choose a track from the jukebox."""
if not self.jukebox:
return
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):
"""Initialize jukebox mode if needed and play the next track."""
# 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

if vote_picked.client.char_id != self.jukebox_prev_char_id or vote_picked.name != self.current_music or len(
self.jukebox_votes) > 1:
self.jukebox_prev_char_id = vote_picked.client.char_id
if vote_picked.showname == '':
self.send_command('MC', vote_picked.name,
vote_picked.client.char_id)
else:
self.send_command('MC', vote_picked.name,
vote_picked.client.char_id,
vote_picked.showname)
else:
self.send_command('MC', vote_picked.name, -1)

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: str, cid: int, loop: int = 0, showname: str ="", effects: int = 0):
"""Play a track.
Args:
Expand Down Expand Up @@ -761,15 +666,6 @@ def navigate_testimony(self, client: ClientManager.Client, command: str, index:
return False
self.send_command('MS', *self.testimony.statements[self.examine_index])
return True

class JukeboxVote:
"""Represents a single vote cast for the jukebox."""
def __init__(self, client, name, length, showname):
self.client = client
self.name = name
self.length = length
self.chance = 1
self.showname = showname

def __init__(self, server):
self.server = server
Expand Down
2 changes: 1 addition & 1 deletion server/client_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ def remove_client(self, client: Client):
client (Client): Disconnected client
"""
if client.area.jukebox:
client.area.remove_jukebox_vote(client, True)
client.area.jukebox_obj.remove_jukebox_vote(client)
for a in self.server.area_manager.areas:
if client in a.owners:
a.owners.remove(client)
Expand Down
2 changes: 2 additions & 0 deletions server/commands/music.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import re

from server.jukebox import Jukebox
from server import database
from server.constants import TargetType
from server.exceptions import ClientError, ServerError, ArgumentError
Expand Down Expand Up @@ -49,6 +50,7 @@ def ooc_cmd_jukebox_toggle(client, arg):
raise ArgumentError('This command has no arguments.')
client.area.jukebox = not client.area.jukebox
client.area.jukebox_votes = []
client.area.jukebox_obj = Jukebox(client.area)
client.area.broadcast_ooc('{} [{}] has set the jukebox to {}.'.format(
client.char_name, client.id, client.area.jukebox))
database.log_room('jukebox_toggle', client, client.area,
Expand Down
97 changes: 97 additions & 0 deletions server/jukebox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from server.client_manager import ClientManager
from dataclasses import dataclass
from collections import Counter
from typing import Union, Tuple
from threading import Timer
import random
import asyncio
@dataclass
class JukeboxVote:
"""Represents a single vote cast for the jukebox."""
client: ClientManager.Client
name: str
length: int
showname: str
chance: int = 1

class Jukebox:
def __init__(self, area):
self.area = area
self.votes = []
self.jukebox_prev_char_id = -1
self.music_looper = None
self.playing = False

def add_jukebox_vote(self, client: ClientManager.Client, music_name: str, length: int = -1, showname: str = ''):

# If a length for a song doesn't exist, the server
# doesn't know when to play the next song
if length == -1:
client.send_ooc(f'{music_name} needs a length set.')
return

# You can only have one vote
# in the queue at a time
self.remove_jukebox_vote(client)

self.votes.append(JukeboxVote(client, music_name, length, showname))
client.send_ooc('Your song was added to the jukebox.')

if len(self.votes) > 0:
self.play()

def play(self):
song_picked, song_length = self.get_next_song_in_queue()
if song_picked == '':
self.area.broadcast_ooc('No other songs in the voting queue.')
return

if self.playing is False:
self.area.send_command('MC', song_picked, song_length)
self.playing = True

# Set not playing once song is done running
asyncio.get_event_loop().call_later(song_length, self.set_not_playing)

def find_length(self, song_name: str) -> int:
vote: JukeboxVote

for vote in self.votes:
if vote.name == song_name:
return vote.length
return -1

def get_next_song_in_queue(self) -> Tuple[str, int]:
if len(self.votes) == 0:
return '', -1

picked_songs = [vote.name for vote in self.votes]
song_frequency_occurrence = Counter(picked_songs)
highest_occuring: tuple = song_frequency_occurrence.most_common(1)[0]
highest_occuring_song = highest_occuring[0]

if len(highest_occuring) == 0:
highest_occuring_song = ''
else:
highest_occuring_song: str = highest_occuring[0]

length = self.find_length(highest_occuring_song)
return highest_occuring_song, length

def set_not_playing(self):
self.playing = False
self.play()
self.votes = []



def remove_jukebox_vote(self, client: ClientManager.Client):
"""Removes a vote on the jukebox.
Args:
client (ClientManager.Client): client whose vote should be removed
"""
vote: JukeboxVote

for vote in self.votes:
if vote.client.id == client.id:
self.votes.remove(vote)
3 changes: 1 addition & 2 deletions server/network/aoprotocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,10 +868,9 @@ def net_cmd_mc(self, args):
effects = 0
if len(args) > 3:
effects = int(args[3])

# Jukebox check
if self.client.area.jukebox:
self.client.area.add_jukebox_vote(self.client, name,
self.client.area.jukebox_obj.add_jukebox_vote(self.client, name,
length, showname)
database.log_room('jukebox.vote', self.client,
self.client.area, message=name)
Expand Down