mirror of
https://github.com/solero/houdini.git
synced 2024-11-29 00:47:19 +00:00
Dance contest multiplayer handlers
This commit is contained in:
parent
de4946771c
commit
da566b2412
22
houdini.sql
22
houdini.sql
@ -414,8 +414,9 @@ DROP TABLE IF EXISTS dance_song;
|
|||||||
CREATE TABLE dance_song (
|
CREATE TABLE dance_song (
|
||||||
id INT NOT NULL,
|
id INT NOT NULL,
|
||||||
name VARCHAR (30) NOT NULL,
|
name VARCHAR (30) NOT NULL,
|
||||||
song_length INT NOT NULL DEFAULT 100000,
|
song_length_millis INT NOT NULL,
|
||||||
millis_per_bar INT NOT NULL DEFAULT 2000,
|
song_length INT NOT NULL,
|
||||||
|
millis_per_bar INT NOT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -423,7 +424,8 @@ COMMENT ON TABLE dance_song IS 'Dance contest multiplayer tracks';
|
|||||||
|
|
||||||
COMMENT ON COLUMN dance_song.id IS 'Unique song ID';
|
COMMENT ON COLUMN dance_song.id IS 'Unique song ID';
|
||||||
COMMENT ON COLUMN dance_song.name IS 'Name of song';
|
COMMENT ON COLUMN dance_song.name IS 'Name of song';
|
||||||
COMMENT ON COLUMN dance_song.song_length IS 'Length of song in milliseconds';
|
COMMENT ON COLUMN dance_song.song_length_millis IS 'Length of song in milliseconds';
|
||||||
|
COMMENT ON COLUMN dance_song.song_length IS 'Length of song in beats';
|
||||||
COMMENT ON COLUMN dance_song.millis_per_bar IS 'Milliseconds per song note';
|
COMMENT ON COLUMN dance_song.millis_per_bar IS 'Milliseconds per song note';
|
||||||
|
|
||||||
CREATE TABLE room_waddle (
|
CREATE TABLE room_waddle (
|
||||||
@ -11425,13 +11427,13 @@ INSERT INTO redemption_book_word (book_id, page, line, word_number, answer) VALU
|
|||||||
(26, 78, 11, 3, 'sunshine'),
|
(26, 78, 11, 3, 'sunshine'),
|
||||||
(26, 78, 8, 4, 'race');
|
(26, 78, 8, 4, 'race');
|
||||||
|
|
||||||
INSERT INTO dance_song (id, name, song_length, millis_per_bar) VALUES
|
INSERT INTO dance_song (id, name, song_length_millis, song_length, millis_per_bar) VALUES
|
||||||
(0, 'Penguin Band Boogie', 123000, 2000),
|
(0, 'Penguin Band Boogie', 123000, 241, 2000),
|
||||||
(1, 'The Generic Way', 117000, 2070),
|
(1, 'The Generic Way', 117000, 221, 2070),
|
||||||
(2, 'Epic Win', 124000, 2666),
|
(2, 'Epic Win', 124000, 176, 2666),
|
||||||
(3, 'Lets Bounce', 130000, 1714),
|
(3, 'Lets Bounce', 130000, 288, 1714),
|
||||||
(4, 'Go West', 139000, 2181),
|
(4, 'Go West', 139000, 248, 2181),
|
||||||
(5, 'Patrick''s Jig', 118000, 2790);
|
(5, 'Patrick''s Jig', 118000, 168, 2790);
|
||||||
|
|
||||||
INSERT INTO character (id, name, gift_id, stamp_id) VALUES
|
INSERT INTO character (id, name, gift_id, stamp_id) VALUES
|
||||||
(1, 'Rockhopper', 9215, 7),
|
(1, 'Rockhopper', 9215, 7),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from houdini.data import db
|
from houdini.data import db, AbstractDataCollection
|
||||||
|
|
||||||
|
|
||||||
class DanceSong(db.Model):
|
class DanceSong(db.Model):
|
||||||
@ -6,5 +6,12 @@ class DanceSong(db.Model):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(30), nullable=False)
|
name = db.Column(db.String(30), nullable=False)
|
||||||
song_length = db.Column(db.Integer, nullable=False, server_default=db.text("100000"))
|
song_length_millis = db.Column(db.Integer, nullable=False)
|
||||||
millis_per_bar = db.Column(db.Integer, nullable=False, server_default=db.text("2000"))
|
song_length = db.Column(db.Integer, nullable=False)
|
||||||
|
millis_per_bar = db.Column(db.Integer, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class DanceSongCollection(AbstractDataCollection):
|
||||||
|
__model__ = DanceSong
|
||||||
|
__indexby__ = 'id'
|
||||||
|
__filterby__ = 'id'
|
||||||
|
@ -0,0 +1,187 @@
|
|||||||
|
from houdini import handlers
|
||||||
|
from houdini.handlers import XTPacket
|
||||||
|
from houdini.data.dance import DanceSongCollection
|
||||||
|
from houdini.penguin import Penguin
|
||||||
|
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import asyncio
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Dancer:
|
||||||
|
penguin: Penguin
|
||||||
|
score: int
|
||||||
|
difficulty: int
|
||||||
|
|
||||||
|
|
||||||
|
class DanceFloor:
|
||||||
|
Easy = 0
|
||||||
|
Medium = 1
|
||||||
|
Difficult = 2
|
||||||
|
Expert = 3
|
||||||
|
|
||||||
|
Capacity = 15
|
||||||
|
|
||||||
|
DanceRoomId = 952
|
||||||
|
|
||||||
|
def __init__(self, server):
|
||||||
|
self.server = server
|
||||||
|
|
||||||
|
self._queue = {}
|
||||||
|
self._dancers = {}
|
||||||
|
|
||||||
|
self._tracks = itertools.cycle(server.dance_songs.values())
|
||||||
|
self._queued_track = next(self._tracks)
|
||||||
|
self._current_track = None
|
||||||
|
|
||||||
|
self._next_song_timestamp = 0
|
||||||
|
|
||||||
|
async def add_penguin(self, p):
|
||||||
|
if p.id not in self._queue and p.id not in self._dancers:
|
||||||
|
if len(self._queue) < DanceFloor.Capacity:
|
||||||
|
self._queue[p.id] = Dancer(penguin=p, score=0, difficulty=DanceFloor.Easy)
|
||||||
|
await p.send_xt('gz', 0, self._queued_track.id, self.get_time_to_next_song())
|
||||||
|
await p.send_xt('jz', 'true', self._queued_track.id, self.get_time_to_next_song())
|
||||||
|
else:
|
||||||
|
await p.send_xt('jz', 'false')
|
||||||
|
|
||||||
|
async def remove_penguin(self, p):
|
||||||
|
self._queue.pop(p.id, None)
|
||||||
|
|
||||||
|
if p.id in self._dancers:
|
||||||
|
self._dancers.pop(p.id)
|
||||||
|
await self.send_xt('zm', self.get_string())
|
||||||
|
|
||||||
|
async def next_round(self):
|
||||||
|
self._current_track = self._queued_track
|
||||||
|
|
||||||
|
self._dancers = self._queue
|
||||||
|
self._queue = {}
|
||||||
|
|
||||||
|
song_data = {
|
||||||
|
difficulty: DanceFloor._get_song_data(
|
||||||
|
self._current_track.song_length, self._current_track.millis_per_bar, difficulty
|
||||||
|
) for difficulty in [
|
||||||
|
DanceFloor.Easy, DanceFloor.Medium, DanceFloor.Difficult, DanceFloor.Expert
|
||||||
|
]}
|
||||||
|
|
||||||
|
for dancer in self._dancers.values():
|
||||||
|
note_types, note_times, note_lengths = song_data[dancer.difficulty]
|
||||||
|
await dancer.penguin.send_xt(
|
||||||
|
'sz',
|
||||||
|
','.join(map(str, note_times)),
|
||||||
|
','.join(map(str, note_types)),
|
||||||
|
','.join(map(str, note_lengths)),
|
||||||
|
self._current_track.millis_per_bar
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.send_xt('zm', self.get_string())
|
||||||
|
|
||||||
|
self._queued_track = next(self._tracks)
|
||||||
|
self._next_song_timestamp = int(round(time.time() * 1000)) + self._current_track.song_length_millis
|
||||||
|
|
||||||
|
await asyncio.sleep(self._current_track.song_length_millis // 1000)
|
||||||
|
await self.next_round()
|
||||||
|
|
||||||
|
async def send_xt(self, *data):
|
||||||
|
for dancer in self._dancers.values():
|
||||||
|
await dancer.penguin.send_xt(*data)
|
||||||
|
|
||||||
|
def set_difficulty(self, p, difficulty):
|
||||||
|
self._queue[p.id].difficulty = max(0, min(difficulty, DanceFloor.Expert))
|
||||||
|
|
||||||
|
def set_score(self, p, score):
|
||||||
|
self._dancers[p.id].score = max(self._dancers[p.id].score, score)
|
||||||
|
|
||||||
|
def get_string(self):
|
||||||
|
return ','.join(f'-1|{dancer.penguin.safe_name}|{dancer.score}' for dancer in self._dancers.values())
|
||||||
|
|
||||||
|
def get_time_to_next_song(self):
|
||||||
|
return self._next_song_timestamp - int(round(time.time() * 1000))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_song_data(cls, song_length, millis_per_bar, difficulty):
|
||||||
|
millis_per_beat = millis_per_bar // 4
|
||||||
|
|
||||||
|
note_types = []
|
||||||
|
note_times = []
|
||||||
|
note_lengths = []
|
||||||
|
last_note_times = [0, 0, 0, 0]
|
||||||
|
|
||||||
|
def add_note(beat_time, max_length):
|
||||||
|
note_type = random.randrange(4)
|
||||||
|
note_time = int(beat_time * millis_per_beat)
|
||||||
|
note_length = int(random.randint(0, max_length) * millis_per_beat)
|
||||||
|
if note_time > last_note_times[note_type]:
|
||||||
|
note_types.append(note_type)
|
||||||
|
note_times.append(note_time)
|
||||||
|
note_lengths.append(note_length)
|
||||||
|
last_note_times[note_type] = note_time + note_length
|
||||||
|
|
||||||
|
for song_time in range(4, song_length):
|
||||||
|
if not song_time % 8 and difficulty >= cls.Easy:
|
||||||
|
add_note(song_time, 4)
|
||||||
|
if random.randrange(2) == 0:
|
||||||
|
add_note(song_time, 0)
|
||||||
|
elif not song_time % 4 and difficulty >= cls.Medium:
|
||||||
|
add_note(song_time, 4)
|
||||||
|
if random.randrange(4) == 0:
|
||||||
|
add_note(song_time, 0)
|
||||||
|
elif not song_time % 2 and difficulty >= cls.Difficult:
|
||||||
|
add_note(song_time, 2)
|
||||||
|
elif random.randrange(4) > 0 and difficulty >= cls.Expert:
|
||||||
|
add_note(song_time, 0)
|
||||||
|
if random.randrange(4) == 0:
|
||||||
|
add_note(song_time + 0.5, 0)
|
||||||
|
|
||||||
|
return note_types, note_times, note_lengths
|
||||||
|
|
||||||
|
|
||||||
|
@handlers.boot
|
||||||
|
async def songs_load(server):
|
||||||
|
server.dance_songs = await DanceSongCollection.get_collection()
|
||||||
|
server.logger.info(f'Loaded {len(server.dance_songs)} dance tracks')
|
||||||
|
|
||||||
|
server.dance_floor = DanceFloor(server)
|
||||||
|
asyncio.create_task(server.dance_floor.next_round())
|
||||||
|
|
||||||
|
|
||||||
|
@handlers.handler(XTPacket('gz', ext='z'))
|
||||||
|
@handlers.player_in_room(DanceFloor.DanceRoomId)
|
||||||
|
async def handle_get_game(p):
|
||||||
|
await p.server.dance_floor.add_penguin(p)
|
||||||
|
|
||||||
|
|
||||||
|
@handlers.handler(XTPacket('zr', ext='z'))
|
||||||
|
@handlers.player_in_room(DanceFloor.DanceRoomId)
|
||||||
|
async def handle_get_game_again(p):
|
||||||
|
await p.server.dance_floor.add_penguin(p)
|
||||||
|
|
||||||
|
|
||||||
|
@handlers.handler(XTPacket('zd', ext='z'))
|
||||||
|
@handlers.player_in_room(DanceFloor.DanceRoomId)
|
||||||
|
async def handle_change_difficulty(p, difficulty: int):
|
||||||
|
p.server.dance_floor.set_difficulty(p, difficulty)
|
||||||
|
|
||||||
|
|
||||||
|
@handlers.handler(XTPacket('zm', ext='z'))
|
||||||
|
@handlers.player_in_room(DanceFloor.DanceRoomId)
|
||||||
|
async def handle_send_move(p, score: int):
|
||||||
|
p.server.dance_floor.set_score(p, score)
|
||||||
|
await p.server.dance_floor.send_xt('zm', p.server.dance_floor.get_string())
|
||||||
|
|
||||||
|
|
||||||
|
@handlers.handler(XTPacket('cz', ext='z'))
|
||||||
|
@handlers.player_in_room(DanceFloor.DanceRoomId)
|
||||||
|
async def handle_leave_game(p):
|
||||||
|
await p.server.dance_floor.remove_penguin(p)
|
||||||
|
|
||||||
|
|
||||||
|
@handlers.disconnected
|
||||||
|
@handlers.player_attribute(joined_world=True)
|
||||||
|
async def handle_disconnect_dance_floor(p):
|
||||||
|
await p.server.dance_floor.remove_penguin(p)
|
Loading…
Reference in New Issue
Block a user