|
@@ -0,0 +1,249 @@
|
|
|
+/**
|
|
|
+ * [CSRD] Round End Music
|
|
|
+ *
|
|
|
+ * In-house rewrite of Round End Music. Hopefully it'll be much cleaner to work with.
|
|
|
+ */
|
|
|
+
|
|
|
+#pragma semicolon 1
|
|
|
+#include <sourcemod>
|
|
|
+
|
|
|
+#include <sdktools>
|
|
|
+
|
|
|
+#pragma newdecls required
|
|
|
+#include <round_end_music>
|
|
|
+
|
|
|
+#define PLUGIN_VERSION "0.0.0"
|
|
|
+public Plugin myinfo = {
|
|
|
+ name = "[CSRD] Round End Music",
|
|
|
+ author = "nosoop",
|
|
|
+ description = "A fresh rewrite of the Round End Music plugin.",
|
|
|
+ version = PLUGIN_VERSION,
|
|
|
+ url = "https://pika.nom-nom-nom.us/"
|
|
|
+}
|
|
|
+
|
|
|
+bool g_bQueueLocked = false;
|
|
|
+ArrayList g_QueuedSongs, g_ActiveSongs, g_PlayedSongs;
|
|
|
+
|
|
|
+Handle g_RequestSongForward;
|
|
|
+
|
|
|
+int g_nMaxActiveSongs = 5;
|
|
|
+
|
|
|
+public void OnPluginStart() {
|
|
|
+ g_QueuedSongs = new ArrayList();
|
|
|
+ g_ActiveSongs = new ArrayList();
|
|
|
+ g_PlayedSongs = new ArrayList();
|
|
|
+
|
|
|
+ g_RequestSongForward = CreateForward(ET_Ignore);
|
|
|
+
|
|
|
+ HookEvent("teamplay_round_win", OnRoundEnd, EventHookMode_PostNoCopy);
|
|
|
+
|
|
|
+ RegAdminCmd("sm_playsong", AdminCmd_PlaySong, ADMFLAG_ROOT);
|
|
|
+ RegAdminCmd("sm_peekqueue", AdminCmd_PeekQueue, ADMFLAG_ROOT);
|
|
|
+}
|
|
|
+
|
|
|
+public Action AdminCmd_PlaySong(int client, int argc) {
|
|
|
+ PlayRoundEndMusic();
|
|
|
+ return Plugin_Handled;
|
|
|
+}
|
|
|
+
|
|
|
+public Action AdminCmd_PeekQueue(int client, int argc) {
|
|
|
+ if (GetCmdReplySource() == SM_REPLY_TO_CHAT) {
|
|
|
+ ReplyToCommand(client, "See console for output.");
|
|
|
+ }
|
|
|
+
|
|
|
+ PrintSongList(client, "played songs", g_PlayedSongs);
|
|
|
+ PrintSongList(client, "active songs", g_ActiveSongs);
|
|
|
+ PrintSongList(client, "queued songs", g_QueuedSongs);
|
|
|
+
|
|
|
+ return Plugin_Handled;
|
|
|
+}
|
|
|
+
|
|
|
+void PrintSongList(int client, const char[] listName, ArrayList songList) {
|
|
|
+ char filePath[PLATFORM_MAX_PATH];
|
|
|
+ if (songList.Length > 0) {
|
|
|
+ PrintToConsole(client, "---- %s ----", listName);
|
|
|
+ for (int i = 0; i < songList.Length; i++) {
|
|
|
+ MusicEntry song = songList.Get(i);
|
|
|
+ song.GetFilePath(filePath, sizeof(filePath));
|
|
|
+ PrintToConsole(client, "%d. %s", i + 1, filePath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public void OnAutoConfigsBuffered() {
|
|
|
+ // TODO pull g_nMaxActiveSongs from ConVar -- it can't change during map
|
|
|
+
|
|
|
+ g_bQueueLocked = false;
|
|
|
+ Call_StartForward(g_RequestSongForward);
|
|
|
+ Call_Finish();
|
|
|
+ g_bQueueLocked = true;
|
|
|
+
|
|
|
+ if (g_nMaxActiveSongs < g_ActiveSongs.Length) {
|
|
|
+ // Put excess songs back in the head of the queue
|
|
|
+ // Used if fewer active songs are required for long maps
|
|
|
+ // (or disabled completely e.g. Arena)
|
|
|
+ while (g_ActiveSongs.Length > g_nMaxActiveSongs) {
|
|
|
+ int pos = g_ActiveSongs.Length - 1;
|
|
|
+ MusicEntry song = g_ActiveSongs.Get(pos);
|
|
|
+
|
|
|
+ g_QueuedSongs.ShiftUp(0);
|
|
|
+ g_QueuedSongs.Set(0, song);
|
|
|
+
|
|
|
+ g_ActiveSongs.Erase(0);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Take up to g_nMaxActiveSongs songs from queue and move them to g_ActiveSongs
|
|
|
+ while (g_ActiveSongs.Length < g_nMaxActiveSongs && g_QueuedSongs.Length > 0) {
|
|
|
+ MusicEntry song = g_QueuedSongs.Get(0);
|
|
|
+ g_ActiveSongs.Push(song);
|
|
|
+
|
|
|
+ g_QueuedSongs.Erase(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Do the Fisher-Yates.
|
|
|
+ // http://spin.atomicobject.com/2014/08/11/fisher-yates-shuffle-randomization-algorithm/
|
|
|
+ int nActiveSongs = g_ActiveSongs.Length < g_nMaxActiveSongs?
|
|
|
+ g_ActiveSongs.Length : g_nMaxActiveSongs;
|
|
|
+
|
|
|
+ for (int i = 0; i < nActiveSongs; i+= 1) {
|
|
|
+ int s = GetRandomInt(i, nActiveSongs - 1);
|
|
|
+ SwapArrayItems(g_ActiveSongs, i, s);
|
|
|
+
|
|
|
+ MusicEntry song = g_ActiveSongs.Get(i);
|
|
|
+
|
|
|
+ char title[64], source[64], filePath[PLATFORM_MAX_PATH];
|
|
|
+ song.GetTitle(title, sizeof(title));
|
|
|
+ song.GetSource(source, sizeof(source));
|
|
|
+ song.GetFilePath(filePath, sizeof(filePath));
|
|
|
+
|
|
|
+ char fileDownloadPath[PLATFORM_MAX_PATH];
|
|
|
+ Format(fileDownloadPath, sizeof(fileDownloadPath), "sound/%s", filePath);
|
|
|
+ AddFileToDownloadsTable(fileDownloadPath);
|
|
|
+
|
|
|
+ PrecacheSound(filePath);
|
|
|
+
|
|
|
+ PrintToServer("[rem] Added song %d: %s", i + 1, filePath);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Remove any already played songs from the queue
|
|
|
+ * (so on map start they only contain unplayed tracks).
|
|
|
+ */
|
|
|
+public void OnMapEnd() {
|
|
|
+ for (int i = 0; i < g_PlayedSongs.Length; i++) {
|
|
|
+ MusicEntry playedSong = g_PlayedSongs.Get(i);
|
|
|
+
|
|
|
+ int activePos = -1;
|
|
|
+ if ( (activePos = g_ActiveSongs.FindValue(playedSong)) != -1 ) {
|
|
|
+ g_ActiveSongs.Erase(activePos);
|
|
|
+ }
|
|
|
+ delete playedSong;
|
|
|
+ }
|
|
|
+ g_PlayedSongs.Clear();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Play pending endround music.
|
|
|
+ */
|
|
|
+void PlayRoundEndMusic() {
|
|
|
+ if (g_ActiveSongs.Length == 0) {
|
|
|
+ PrintToServer("no songs to play :(");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // retrieve head of g_ActiveSongs, erase and put into g_PlayedSongs
|
|
|
+ MusicEntry song = g_ActiveSongs.Get(0);
|
|
|
+
|
|
|
+ // mock play for testing
|
|
|
+ // TODO move into a function with shiny forwards
|
|
|
+ char filePath[PLATFORM_MAX_PATH];
|
|
|
+ song.GetFilePath(filePath, sizeof(filePath));
|
|
|
+
|
|
|
+ PrintToServer("mock play song %s", filePath);
|
|
|
+ EmitSoundToAll(filePath);
|
|
|
+
|
|
|
+ if (g_PlayedSongs.FindValue(song) == -1) {
|
|
|
+ g_PlayedSongs.Push(song);
|
|
|
+ }
|
|
|
+
|
|
|
+ g_ActiveSongs.Erase(0);
|
|
|
+
|
|
|
+ // if 'active' is empty, copy all back into 'active' without removing from 'played'
|
|
|
+ if (g_ActiveSongs.Length == 0) {
|
|
|
+ for (int i = 0; i < g_PlayedSongs.Length; i++) {
|
|
|
+ g_ActiveSongs.Push(g_PlayedSongs.Get(i));
|
|
|
+ }
|
|
|
+
|
|
|
+ int nActiveSongs = g_ActiveSongs.Length < g_nMaxActiveSongs?
|
|
|
+ g_ActiveSongs.Length : g_nMaxActiveSongs;
|
|
|
+ for (int i = 0; i < nActiveSongs; i+= 1) {
|
|
|
+ int s = GetRandomInt(i, nActiveSongs - 1);
|
|
|
+ SwapArrayItems(g_ActiveSongs, i, s);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public void OnRoundEnd(Event event, const char[] name, bool dontBroadcast) {
|
|
|
+ PlayRoundEndMusic();
|
|
|
+}
|
|
|
+
|
|
|
+// menu: read entries from g_PlayedSongs and then g_ActiveSongs if not empty
|
|
|
+// that *should* maintain initial play order
|
|
|
+
|
|
|
+/* Native function calls */
|
|
|
+public APLRes AskPluginLoad2(Handle self, bool late, char[] error, int err_max) {
|
|
|
+ RegPluginLibrary("round-end-music");
|
|
|
+
|
|
|
+ CreateNative("REM_RegisterSource", Native_RegisterSource);
|
|
|
+ CreateNative("REM_AddSong", Native_AddSong);
|
|
|
+}
|
|
|
+
|
|
|
+public int Native_RegisterSource(Handle hPlugin, int nArgs) {
|
|
|
+ Function callback = GetNativeFunction(1);
|
|
|
+
|
|
|
+ // preemptive removal because it could be forwarded multiple times?
|
|
|
+ for (int i = 0; i < GetForwardFunctionCount(g_RequestSongForward); i++) {
|
|
|
+ RemoveFromForward(g_RequestSongForward, hPlugin, callback);
|
|
|
+ }
|
|
|
+ AddToForward(g_RequestSongForward, hPlugin, callback);
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+public int Native_AddSong(Handle hPlugin, int nArgs) {
|
|
|
+ if (g_bQueueLocked) {
|
|
|
+ ThrowNativeError(1, "Queue is currently locked -- are you calling REM_AddSong outside "
|
|
|
+ ... "of the registered callback?");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ MusicEntry song = view_as<MusicEntry>(GetNativeCell(1));
|
|
|
+
|
|
|
+ // Ensure no more songs added than necessary
|
|
|
+ if (g_QueuedSongs.Length < g_nMaxActiveSongs) {
|
|
|
+ // Ensure no duplicates by file path in queue or active songs
|
|
|
+ bool existing = false;
|
|
|
+
|
|
|
+ char filePath[PLATFORM_MAX_PATH], existingFilePath[PLATFORM_MAX_PATH];
|
|
|
+ song.GetFilePath(filePath, sizeof(filePath));
|
|
|
+ for (int i = 0; i < g_QueuedSongs.Length; i++) {
|
|
|
+ (view_as<MusicEntry>(g_QueuedSongs.Get(i))).GetFilePath(
|
|
|
+ existingFilePath, sizeof(existingFilePath));
|
|
|
+
|
|
|
+ existing |= StrEqual(filePath, existingFilePath);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < g_ActiveSongs.Length; i++) {
|
|
|
+ (view_as<MusicEntry>(g_ActiveSongs.Get(i)))
|
|
|
+ .GetFilePath(existingFilePath, sizeof(existingFilePath));
|
|
|
+
|
|
|
+ existing |= StrEqual(filePath, existingFilePath);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!existing) {
|
|
|
+ g_QueuedSongs.Push(CloneHandle(song));
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|