Initial commit: arcane-round-end-music plugin with documentation and config
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# Arcane Round End Music
|
||||
|
||||
Плагин для воспроизведения музыки в конце раунда на CS:GO серверах с SourceMod.
|
||||
|
||||
## Функции
|
||||
|
||||
- Музыка играет автоматически в конце каждого раунда
|
||||
- Каждый игрок может **включить/выключить** музыку персонально
|
||||
- Регулировка **громкости** от 0% до 100% (шаг 10%)
|
||||
- Настройки сохраняются через **ClientPrefs** (cookies)
|
||||
- Поддержка до **128 треков**, воспроизведение по очереди
|
||||
|
||||
## Зависимости
|
||||
|
||||
- [SourceMod](https://www.sourcemod.net/) 1.10+
|
||||
- [ClientPrefs](https://wiki.alliedmods.net/Client_Preferences_%28SourceMod%29) (входит в SourceMod)
|
||||
|
||||
## Установка
|
||||
|
||||
1. Скомпилировать `scripting/arcane_round_end_music.sp`
|
||||
2. Положить `.smx` в `addons/sourcemod/plugins/`
|
||||
3. Положить `configs/arcane_round_end_music.cfg` в `addons/sourcemod/configs/`
|
||||
4. Загрузить аудиофайлы в `sound/arcanegame/music/` на сервере
|
||||
5. Прописать треки в конфиге
|
||||
6. Перезапустить сервер или загрузить плагин: `sm plugins load arcane_round_end_music`
|
||||
|
||||
## Конфиг
|
||||
|
||||
Путь: `addons/sourcemod/configs/arcane_round_end_music.cfg`
|
||||
|
||||
```
|
||||
"RoundEndMusic"
|
||||
{
|
||||
"songs"
|
||||
{
|
||||
"1"
|
||||
{
|
||||
"title" "Название трека"
|
||||
"file" "arcanegame/music/track.mp3"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Путь к файлу указывается **относительно папки `sound/`**, без неё.
|
||||
|
||||
## Команды
|
||||
|
||||
| Команда | Доступ | Описание |
|
||||
|---|---|---|
|
||||
| `!res` / `sm_res` | Все игроки | Открыть меню управления музыкой |
|
||||
| `sm_res_reload` | Admin (generic) | Перезагрузить список треков из конфига |
|
||||
| `sm_res_test` | Admin (generic) | Воспроизвести тестовый трек |
|
||||
|
||||
## Версия
|
||||
|
||||
`1.3` — Автор: Codex
|
||||
@@ -0,0 +1,16 @@
|
||||
"RoundEndMusic"
|
||||
{
|
||||
"songs"
|
||||
{
|
||||
"1"
|
||||
{
|
||||
"title" "Название песни 1"
|
||||
"file" "arcanegame/music/song1.mp3"
|
||||
}
|
||||
"2"
|
||||
{
|
||||
"title" "Название песни 2"
|
||||
"file" "arcanegame/music/song2.mp3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
#pragma semicolon 1
|
||||
#pragma newdecls required
|
||||
|
||||
#include <sourcemod>
|
||||
#include <sdktools>
|
||||
#include <clientprefs>
|
||||
|
||||
#define PLUGIN_VERSION "1.3"
|
||||
#define MAX_SONGS 128
|
||||
#define MAX_TITLE_LENGTH 128
|
||||
#define CHAT_PREFIX "\x01ARCANE \x02GAME"
|
||||
|
||||
char g_SongTitles[MAX_SONGS][MAX_TITLE_LENGTH];
|
||||
char g_SongFiles[MAX_SONGS][PLATFORM_MAX_PATH];
|
||||
int g_SongCount;
|
||||
int g_NextSongIndex;
|
||||
|
||||
bool g_MusicEnabled[MAXPLAYERS + 1];
|
||||
int g_VolumePercent[MAXPLAYERS + 1];
|
||||
int g_LastSongIndex[MAXPLAYERS + 1];
|
||||
|
||||
Cookie g_CookieEnabled;
|
||||
Cookie g_CookieVolume;
|
||||
|
||||
public Plugin myinfo =
|
||||
{
|
||||
name = "Arcane Round End Music",
|
||||
author = "Codex",
|
||||
description = "End round music with personal toggle and volume control.",
|
||||
version = PLUGIN_VERSION,
|
||||
url = ""
|
||||
};
|
||||
|
||||
public void OnPluginStart()
|
||||
{
|
||||
RegConsoleCmd("sm_res", Command_ResMenu);
|
||||
RegAdminCmd("sm_res_reload", Command_ResReload, ADMFLAG_GENERIC);
|
||||
RegAdminCmd("sm_res_test", Command_ResTest, ADMFLAG_GENERIC);
|
||||
|
||||
HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);
|
||||
HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy);
|
||||
|
||||
g_CookieEnabled = RegClientCookie("arcane_music_enabled", "Round end music enabled", CookieAccess_Private);
|
||||
g_CookieVolume = RegClientCookie("arcane_music_volume", "Round end music volume", CookieAccess_Private);
|
||||
|
||||
for (int client = 1; client <= MaxClients; client++)
|
||||
{
|
||||
if (IsClientInGame(client))
|
||||
{
|
||||
ResetClientSettings(client);
|
||||
|
||||
if (!IsFakeClient(client) && AreClientCookiesCached(client))
|
||||
{
|
||||
LoadClientSettings(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadSongs();
|
||||
}
|
||||
|
||||
public void OnMapStart()
|
||||
{
|
||||
LoadSongs();
|
||||
}
|
||||
|
||||
public void OnClientPutInServer(int client)
|
||||
{
|
||||
ResetClientSettings(client);
|
||||
|
||||
if (!IsFakeClient(client) && AreClientCookiesCached(client))
|
||||
{
|
||||
LoadClientSettings(client);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClientDisconnect(int client)
|
||||
{
|
||||
g_LastSongIndex[client] = -1;
|
||||
}
|
||||
|
||||
public void OnClientCookiesCached(int client)
|
||||
{
|
||||
if (!IsValidMusicClient(client))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadClientSettings(client);
|
||||
}
|
||||
|
||||
public Action Command_ResMenu(int client, int args)
|
||||
{
|
||||
if (!IsValidMusicClient(client))
|
||||
{
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (g_SongCount <= 0)
|
||||
{
|
||||
PrintMusicChat(client, "Песни не загружены. Проверь конфиг и путь к wav.");
|
||||
}
|
||||
|
||||
ShowResMenu(client);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public Action Command_ResReload(int client, int args)
|
||||
{
|
||||
LoadSongs();
|
||||
ReplyToCommand(client, "[ArcaneGameMusic] Songs loaded: %d", g_SongCount);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public Action Command_ResTest(int client, int args)
|
||||
{
|
||||
if (!IsValidMusicClient(client))
|
||||
{
|
||||
ReplyToCommand(client, "[ArcaneGameMusic] Command is available only for real players in game.");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (g_SongCount <= 0)
|
||||
{
|
||||
ReplyToCommand(client, "[ArcaneGameMusic] No songs loaded. Check config and file paths.");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
int songIndex = GetNextSongIndex();
|
||||
PlaySongToClient(client, songIndex);
|
||||
ReplyToCommand(client, "[ArcaneGameMusic] Test song sent.");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
|
||||
{
|
||||
if (g_SongCount <= 0)
|
||||
{
|
||||
LogError("[ArcaneGameMusic] round_end fired, but no songs were loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
int songIndex = GetNextSongIndex();
|
||||
|
||||
for (int client = 1; client <= MaxClients; client++)
|
||||
{
|
||||
if (!IsValidMusicClient(client) || !g_MusicEnabled[client])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PlaySongToClient(client, songIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
|
||||
{
|
||||
for (int client = 1; client <= MaxClients; client++)
|
||||
{
|
||||
if (IsClientInGame(client))
|
||||
{
|
||||
StopClientSong(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int MenuHandler_Res(Menu menu, MenuAction action, int client, int item)
|
||||
{
|
||||
if (action == MenuAction_End)
|
||||
{
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action != MenuAction_Select || !IsValidMusicClient(client))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
char info[16];
|
||||
menu.GetItem(item, info, sizeof(info));
|
||||
|
||||
if (StrEqual(info, "toggle"))
|
||||
{
|
||||
g_MusicEnabled[client] = !g_MusicEnabled[client];
|
||||
|
||||
if (!g_MusicEnabled[client])
|
||||
{
|
||||
StopClientSong(client);
|
||||
PrintMusicChat(client, "Музыка \x02выключена");
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMusicChat(client, "Музыка \x04включена");
|
||||
}
|
||||
}
|
||||
else if (StrEqual(info, "down"))
|
||||
{
|
||||
int oldVolume = g_VolumePercent[client];
|
||||
g_VolumePercent[client] = ClampVolume(g_VolumePercent[client] - 10);
|
||||
|
||||
if (oldVolume == g_VolumePercent[client])
|
||||
{
|
||||
PrintMusicChat(client, "Громкость уже на минимуме");
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMusicChat(client, "Громкость: \x09%d%%", g_VolumePercent[client]);
|
||||
}
|
||||
}
|
||||
else if (StrEqual(info, "up"))
|
||||
{
|
||||
int oldVolume = g_VolumePercent[client];
|
||||
g_VolumePercent[client] = ClampVolume(g_VolumePercent[client] + 10);
|
||||
|
||||
if (oldVolume == g_VolumePercent[client])
|
||||
{
|
||||
PrintMusicChat(client, "Громкость уже на максимуме");
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMusicChat(client, "Громкость: \x09%d%%", g_VolumePercent[client]);
|
||||
}
|
||||
}
|
||||
|
||||
SaveClientSettings(client);
|
||||
ShowResMenu(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ShowResMenu(int client)
|
||||
{
|
||||
Menu menu = new Menu(MenuHandler_Res);
|
||||
|
||||
char title[128];
|
||||
FormatEx(title, sizeof(title), "ArcaneGameMusic\nВключено: %s\nГромкость: %d%%",
|
||||
g_MusicEnabled[client] ? "Да" : "Нет",
|
||||
g_VolumePercent[client]);
|
||||
|
||||
menu.SetTitle(title);
|
||||
menu.AddItem("toggle", g_MusicEnabled[client] ? "Выключить музыку" : "Включить музыку");
|
||||
menu.AddItem("down", "Уменьшить громкость (-10%)");
|
||||
menu.AddItem("up", "Увеличить громкость (+10%)");
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, 20);
|
||||
}
|
||||
|
||||
void ResetClientSettings(int client)
|
||||
{
|
||||
g_MusicEnabled[client] = true;
|
||||
g_VolumePercent[client] = 40;
|
||||
g_LastSongIndex[client] = -1;
|
||||
}
|
||||
|
||||
void LoadClientSettings(int client)
|
||||
{
|
||||
ResetClientSettings(client);
|
||||
|
||||
char value[16];
|
||||
|
||||
GetClientCookie(client, g_CookieEnabled, value, sizeof(value));
|
||||
if (value[0] != '\0')
|
||||
{
|
||||
g_MusicEnabled[client] = StringToInt(value) != 0;
|
||||
}
|
||||
|
||||
GetClientCookie(client, g_CookieVolume, value, sizeof(value));
|
||||
if (value[0] != '\0')
|
||||
{
|
||||
int volume = StringToInt(value);
|
||||
|
||||
if (volume >= 0 && volume <= 100)
|
||||
{
|
||||
g_VolumePercent[client] = volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SaveClientSettings(int client)
|
||||
{
|
||||
char value[16];
|
||||
|
||||
IntToString(g_MusicEnabled[client] ? 1 : 0, value, sizeof(value));
|
||||
SetClientCookie(client, g_CookieEnabled, value);
|
||||
|
||||
IntToString(g_VolumePercent[client], value, sizeof(value));
|
||||
SetClientCookie(client, g_CookieVolume, value);
|
||||
}
|
||||
|
||||
void LoadSongs()
|
||||
{
|
||||
g_SongCount = 0;
|
||||
|
||||
char configPath[PLATFORM_MAX_PATH];
|
||||
BuildPath(Path_SM, configPath, sizeof(configPath), "configs/arcane_round_end_music.cfg");
|
||||
|
||||
KeyValues kv = new KeyValues("RoundEndMusic");
|
||||
if (!kv.ImportFromFile(configPath))
|
||||
{
|
||||
LogError("[ArcaneGameMusic] Could not open config: %s", configPath);
|
||||
delete kv;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!kv.JumpToKey("songs"))
|
||||
{
|
||||
LogError("[ArcaneGameMusic] Missing section 'songs' in: %s", configPath);
|
||||
delete kv;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!kv.GotoFirstSubKey(false))
|
||||
{
|
||||
LogError("[ArcaneGameMusic] No songs found in: %s", configPath);
|
||||
delete kv;
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (g_SongCount >= MAX_SONGS)
|
||||
{
|
||||
LogError("[ArcaneGameMusic] Song limit reached (%d)", MAX_SONGS);
|
||||
break;
|
||||
}
|
||||
|
||||
kv.GetString("title", g_SongTitles[g_SongCount], MAX_TITLE_LENGTH);
|
||||
kv.GetString("file", g_SongFiles[g_SongCount], PLATFORM_MAX_PATH);
|
||||
|
||||
TrimString(g_SongTitles[g_SongCount]);
|
||||
TrimString(g_SongFiles[g_SongCount]);
|
||||
|
||||
if (g_SongTitles[g_SongCount][0] == '\0' || g_SongFiles[g_SongCount][0] == '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
char downloadPath[PLATFORM_MAX_PATH];
|
||||
FormatEx(downloadPath, sizeof(downloadPath), "sound/%s", g_SongFiles[g_SongCount]);
|
||||
|
||||
if (!FileExists(downloadPath))
|
||||
{
|
||||
LogError("[ArcaneGameMusic] File not found: %s", downloadPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PrecacheSound(g_SongFiles[g_SongCount], false))
|
||||
{
|
||||
LogError("[ArcaneGameMusic] Could not precache sound: %s", g_SongFiles[g_SongCount]);
|
||||
continue;
|
||||
}
|
||||
|
||||
AddFileToDownloadsTable(downloadPath);
|
||||
g_SongCount++;
|
||||
}
|
||||
while (kv.GotoNextKey(false));
|
||||
|
||||
if (g_SongCount <= 0 || g_NextSongIndex >= g_SongCount)
|
||||
{
|
||||
g_NextSongIndex = 0;
|
||||
}
|
||||
|
||||
LogMessage("[ArcaneGameMusic] Loaded songs: %d", g_SongCount);
|
||||
delete kv;
|
||||
}
|
||||
|
||||
void PlaySongToClient(int client, int songIndex)
|
||||
{
|
||||
if (songIndex < 0 || songIndex >= g_SongCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopClientSong(client);
|
||||
|
||||
float volume = float(g_VolumePercent[client]) / 100.0;
|
||||
EmitSoundToClient(client, g_SongFiles[songIndex], SOUND_FROM_PLAYER, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_NOFLAGS, volume);
|
||||
|
||||
g_LastSongIndex[client] = songIndex;
|
||||
PrintMusicChat(client, "Сейчас играет: \x09%s", g_SongTitles[songIndex]);
|
||||
PrintMusicChat(client, "Вы можете управлять музыкой по команде \x04!res");
|
||||
}
|
||||
|
||||
void StopClientSong(int client)
|
||||
{
|
||||
int songIndex = g_LastSongIndex[client];
|
||||
|
||||
if (songIndex < 0 || songIndex >= g_SongCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopSound(client, SNDCHAN_STATIC, g_SongFiles[songIndex]);
|
||||
g_LastSongIndex[client] = -1;
|
||||
}
|
||||
|
||||
bool IsValidMusicClient(int client)
|
||||
{
|
||||
return client > 0 && client <= MaxClients && IsClientInGame(client) && !IsFakeClient(client);
|
||||
}
|
||||
|
||||
int ClampVolume(int value)
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (value > 100)
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int GetNextSongIndex()
|
||||
{
|
||||
if (g_SongCount <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int songIndex = g_NextSongIndex;
|
||||
g_NextSongIndex++;
|
||||
|
||||
if (g_NextSongIndex >= g_SongCount)
|
||||
{
|
||||
g_NextSongIndex = 0;
|
||||
}
|
||||
|
||||
return songIndex;
|
||||
}
|
||||
|
||||
void PrintMusicChat(int client, const char[] format, any ...)
|
||||
{
|
||||
char message[256];
|
||||
VFormat(message, sizeof(message), format, 3);
|
||||
PrintToChat(client, "%s\x01 | %s", CHAT_PREFIX, message);
|
||||
}
|
||||
Reference in New Issue
Block a user