Class OsuRoom

Represents a multiplayer lobby in osu! Automatically does ratelimiting by not sending more than a message every 2 seconds.

class OsuRoom ;

All slot indices are 0 based.

Fields

NameTypeDescription
onBeatmapChanged void delegate(bancho.irc.BeatmapInfo)Host changed map
onBeatmapPending void delegate()Host is changing beatmap
onClosed void delegate()The room has been closed
onCountdownFinished void delegate()A timer finished
onMatchEnd void delegate()Match has ended (all players finished)
onMatchStart void delegate()Match has started
onMessage void delegate(Message)A message by anyone has been sent
onPlayerFinished void delegate(string, long, bool)A user finished playing. username + score + passed
onPlayersReady void delegate()emitted when all players are ready
onUserHost void delegate(string)username as argument
onUserJoin void delegate(string, ubyte)username + slot (0 based) as argument
onUserLeave void delegate(string)username as argument
onUserMove void delegate(string, ubyte)username + slot (0 based) as argument
onUserTeamChange void delegate(string, Team)username & team as argument
slots OsuRoom.Settings.Player[16]Automatically managed state of player slots, empty slots are Player.init

Properties

NameTypeDescription
channel[get] stringReturns the channel name as on IRC
host[set] stringGives host to a player
locked[set] boolProperty to lock slots (disallow changing slots & joining)
map[set] stringChanges the map to a beatmap ID (b/ url)
mods[set] Mod[]Changes the mods in this lobby (pass FreeMod first if you want FreeMod)
mpid[get] stringReturns the game ID as usable in osu://mp/ID urls
password[set] stringSets the match password (password will be visible to existing players)
room[get] stringReturns the room ID as usable in the mp history URL or IRC joinable via #mp_ID
settings[get] OsuRoom.SettingsReturns the current mp settings
size[set] ubyteChanges the slot limit of this lobby

Methods

NameDescription
abortMatch Aborts a running match
abortTimer Aborts any running countdown
clearhost Makes nobody host (make it system/bog managed)
close Closes the room
invite Invites a player to the room
kick Kicks a player from the room
move Moves a player to another slot
processClosed Processes a room closed event
processFinishPlaying Processes a user finish playing event & updates the state
processHost Processes a user host event & updates the state
processJoin Processes a user join event & updates the state
processLeave Processes a user leave event & updates the state
processMatchFinish Processes a match end event & updates the state
processMove Processes a user move event & updates the state
processSize Processes a room size change event & updates the state
processTeam Processes a user team switch event & updates the state
ratelimit Manually wait until you can send a message again
sendMessage Sends a message with a 2 second ratelimit
set Sets up teammode, scoremode & lobby size
setTeam Changes a user's team
setTimer Sets a timer using !mp timer
start Starts a match after a specified amount of seconds. If after is <= 0 the game will be started immediately. The timeout can be canceled using abortTimer.
waitForJoin Waits for a player to join the room & return the username
waitForTimer Waits for an existing timer/countdown to finish (wont start one)

Inner structs

NameDescription
Settings Returned by !mp settings

Example

BanchoBot banchoConnection = new BanchoBot("WebFreak", "");
bool running = true;
auto botTask = runTask({
	while (running)
	{
		banchoConnection.connect();
		logDiagnostic("Got disconnected from bancho...");
		sleep(2.seconds);
	}
});
sleep(3.seconds);
auto users = ["WebFreak", "Node"];
OsuRoom room = banchoConnection.createRoom("bob");
runTask({
	foreach (user; users)
		room.invite(user);
});
runTask({
	room.password = "123456";
	room.size = 8;
	room.mods = [Mod.Hidden, Mod.DoubleTime];
	room.map = "1158325";
});
runTask({
	int joined;
	try
	{
		while (true)
		{
			string user = room.waitForJoin(30.seconds);
			joined++;
			room.sendMessage("yay welcome " ~ user ~ "!", HighPriority.yes);
		}
	}
	catch (InterruptException)
	{
		if (joined == 0)
		{
			// forever alone
			room.close();
			return;
		}
	}
	room.sendMessage("This is an automated test, this room will close in 10 seconds on timer");
	room.setTimer(10.seconds);
	try
	{
		room.waitForTimer(15.seconds);
	}
	catch (InterruptException)
	{
		room.sendMessage("Timer didn't trigger :(");
		room.sendMessage("closing the room in 5s");
		sleep(5.seconds);
	}
	room.close();
}).join();
running = false;
banchoConnection.disconnect();
botTask.join();

Example

Simple auto host rotate bot

import std.range;

BanchoBot banchoConnection = new BanchoBot("WebFreak", "");
bool running = true;
auto botTask = runTask({
	while (running)
	{
		banchoConnection.connect();
		logDiagnostic("Got disconnected from bancho...");
		sleep(2.seconds);
	}
});
sleep(3.seconds);
OsuRoom room = banchoConnection.createRoom("4*+ auto host rotate (join time based)");
room.invite("WebFreak");
runTask({ room.password = ""; room.size = 8; room.mods = [Mod.FreeMod]; });
string[] hostOrder;
size_t currentHost;
room.onClosed ~= () {
	running = false;
	banchoConnection.disconnect();
	botTask.join();
};

bool beatmapChosen = false;
bool choosingMap = false;
bool startedChoosingTimer = false;
Timer choosingTimer;
string nextHost(bool deleteCurrent = false)
{
	if (deleteCurrent)
		hostOrder = hostOrder[0 .. currentHost] ~ hostOrder[currentHost + 1 .. $];
	if (hostOrder.length == 0)
	{
		// everyone left
		//room.close();
		return null;
	}
	else
	{
		if (!deleteCurrent)
			currentHost = (currentHost + 1) % hostOrder.length;
		else if (currentHost >= hostOrder.length)
			currentHost = 0;
		auto user = hostOrder[currentHost];
		try
		{
			room.playerByName(user);
			beatmapChosen = false;
			choosingMap = false;
			room.host = user;
			if (choosingTimer && choosingTimer.pending)
				choosingTimer.stop();
			choosingTimer = setTimer(40.seconds, {
				if (choosingMap || !hostOrder.length)
					return;
				room.sendMessage(
					hostOrder[currentHost] ~ ": please pick a map or next host will be picked!");
				room.setTimer(60.seconds);
				startedChoosingTimer = true;
			});
			if (hostOrder.length)
				room.sendMessage("Next hosts are " ~ hostOrder.cycle.drop(currentHost + 1)
						.take(min(3, hostOrder.length - 1)).join(", "));
			return user;
		}
		catch (Exception)
		{
			room.sendMessage("Tried to give host to " ~ user ~ " but they left?");
			return nextHost(true);
		}
	}
}

room.onBeatmapPending ~= () {
	if (beatmapChosen)
		return;
	if (choosingTimer && choosingTimer.pending)
		choosingTimer.stop();
	choosingMap = true;
	if (startedChoosingTimer)
	{
		startedChoosingTimer = false;
		room.abortTimer();
	}
	choosingTimer = setTimer(60.seconds, {
		if (beatmapChosen || !hostOrder.length)
			return;
		room.sendMessage(hostOrder[currentHost] ~ ": please pick a map or next host will be picked!");
		room.setTimer(30.seconds);
		startedChoosingTimer = true;
	});
};

room.onBeatmapChanged ~= (beatmap) {
	if (choosingTimer && choosingTimer.pending)
		choosingTimer.stop();
	beatmapChosen = true;
};

room.onCountdownFinished ~= () {
	if (!beatmapChosen)
		nextHost();
};

room.onUserHost ~= (user) {
	if (user != hostOrder[currentHost])
	{
		string s = nextHost();
		if (s != user)
			room.sendMessage("Host passed elsewhere, passing where it was supposed to go next");
	}
};
room.onUserJoin ~= (user, slot) {
	if (hostOrder.canFind(user))
		return;
	if (hostOrder.length)
	{
		// insert before current host
		hostOrder = hostOrder[0 .. currentHost] ~ user ~ hostOrder[currentHost .. $];
		currentHost++;
	}
	else
	{
		hostOrder = [user]; // first join!
		nextHost();
	}
};
room.onUserLeave ~= (user) {
	if (user == hostOrder[currentHost])
		nextHost(true);
};
bool startedLongTimer;
room.onBeatmapChanged ~= (beatmap) {
	if (!startedLongTimer)
	{
		room.start(3.minutes);
		startedLongTimer = true;
	}
};
bool startedTimer;
room.onPlayersReady ~= () {
	if (!startedTimer)
	{
		room.start(15.seconds);
		startedTimer = true;
	}
};
room.onMatchStart ~= () {
	startedTimer = false;
	startedLongTimer = false;
	room.password = "";
};
room.onMatchEnd ~= () { nextHost(); };
botTask.join();