// ============================================================ // ARCANEGAME Advertisement Plugin // Конфиг: addons/sourcemod/configs/reklama.ini // Зависимости: mapchooser / nextmap (для тега {NEXTMAP}) // Внешние includes НЕ нужны // ============================================================ #pragma semicolon 1 #pragma newdecls required #include #include #include #define PLUGIN_VERSION "1.0.0" #define MAX_ADS 128 #define MAX_MSG_LEN 1024 #define MAX_MAP_NAME 128 // CS:GO фиксированные коды цветов чата (работают с PrintToChatAll) #define CLR_DEFAULT "\x01" // белый #define CLR_RED "\x02" // красный (как в музыкальном плагине ARCANE GAME) #define CLR_LIGHTPURPLE "\x03" // цвет команды / фиолетовый #define CLR_GREEN "\x04" // зелёный #define CLR_OLIVE "\x05" // оливковый #define CLR_LIGHTGREEN "\x06" // светло-зелёный #define CLR_LIGHTRED "\x02" // светло-красный (аналог красного) #define CLR_LIME "\x06" // лаймовый #define CLR_GRAY "\x08" // серый #define CLR_PURPLE "\x03" // фиолетовый #define CLR_BLUE "\x0B" // синий #define CLR_LIGHTBLUE "\x0B" // светло-синий #define CLR_LIGHTOLIVE "\x05" // светло-оливковый // ── HUD-блок внутри объявления ──────────────────────────────── enum struct HudAd { float x; float y; int color[4]; float holdTime; char text[MAX_MSG_LEN]; } // ── Одно объявление ─────────────────────────────────────────── enum struct Advertisement { char chat [MAX_MSG_LEN]; char center[MAX_MSG_LEN]; char alert [MAX_MSG_LEN]; HudAd hud; bool hasChat; bool hasCenter; bool hasAlert; bool hasHud; } // ── Глобальные переменные ───────────────────────────────────── float g_fInterval = 25.0; Advertisement g_Ads[MAX_ADS]; int g_iAdCount; int g_iCurrentAd; StringMap g_MapNames; Handle g_hTimer = INVALID_HANDLE; Handle g_hHud = INVALID_HANDLE; // ── Информация о плагине ────────────────────────────────────── public Plugin myinfo = { name = "ARCANEGAME Advertisement", author = "deidara", description = "Рекламные сообщения ARCANEGAME", version = PLUGIN_VERSION, url = "arcanegame.ru" }; // ============================================================= // Старт плагина // ============================================================= public void OnPluginStart() { g_MapNames = new StringMap(); g_hHud = CreateHudSynchronizer(); LoadConfig(); RegAdminCmd("sm_reklama_reload", Cmd_Reload, ADMFLAG_ROOT, "Перезагрузить рекламные сообщения"); } // ============================================================= // Конец карты — обнуляем хэндл (SM уже освободил таймер) // ============================================================= public void OnMapEnd() { g_hTimer = INVALID_HANDLE; } // ============================================================= // Старт карты — запускаем таймер // ============================================================= public void OnMapStart() { delete g_hTimer; g_hTimer = INVALID_HANDLE; g_iCurrentAd = 0; if (g_iAdCount > 0) g_hTimer = CreateTimer(g_fInterval, Timer_ShowAd, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } // ============================================================= // Команда перезагрузки // ============================================================= public Action Cmd_Reload(int client, int args) { delete g_hTimer; g_hTimer = INVALID_HANDLE; LoadConfig(); if (g_iAdCount > 0) g_hTimer = CreateTimer(g_fInterval, Timer_ShowAd, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); ReplyToCommand(client, "[ARCANEGAME] Реклама перезагружена. Загружено объявлений: %d", g_iAdCount); return Plugin_Handled; } // ============================================================= // Загрузка конфига // ============================================================= void LoadConfig() { char sPath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sPath, sizeof(sPath), "configs/reklama.ini"); if (!FileExists(sPath)) { LogError("[ARCANEGAME AD] Конфиг не найден: %s", sPath); return; } KeyValues kv = new KeyValues("Реклама"); if (!kv.ImportFromFile(sPath)) { LogError("[ARCANEGAME AD] Ошибка чтения конфига: %s", sPath); delete kv; return; } g_fInterval = kv.GetFloat("Таймер объявлений", 25.0); if (g_fInterval < 1.0) g_fInterval = 1.0; g_iAdCount = 0; g_iCurrentAd = 0; // ── Названия карт ───────────────────────────────────────── delete g_MapNames; g_MapNames = new StringMap(); if (kv.JumpToKey("Названия карт")) { if (kv.GotoFirstSubKey(false)) { do { char sKey[MAX_MAP_NAME], sVal[MAX_MAP_NAME]; kv.GetSectionName(sKey, sizeof(sKey)); kv.GetString(NULL_STRING, sVal, sizeof(sVal)); if (sKey[0] && sVal[0]) g_MapNames.SetString(sKey, sVal); } while (kv.GotoNextKey(false)); kv.GoBack(); } kv.GoBack(); } // ── Список объявлений ───────────────────────────────────── if (kv.JumpToKey("Список объявлений")) { if (kv.GotoFirstSubKey()) { do { if (g_iAdCount >= MAX_ADS) break; Advertisement ad; char buf[MAX_MSG_LEN]; kv.GetString("Chat", buf, sizeof(buf)); if (buf[0]) { strcopy(ad.chat, sizeof(ad.chat), buf); ad.hasChat = true; } kv.GetString("Center", buf, sizeof(buf)); if (buf[0]) { strcopy(ad.center, sizeof(ad.center), buf); ad.hasCenter = true; } kv.GetString("Alert", buf, sizeof(buf)); if (buf[0]) { strcopy(ad.alert, sizeof(ad.alert), buf); ad.hasAlert = true; } if (kv.JumpToKey("HUD")) { kv.GetString("text", buf, sizeof(buf)); if (buf[0]) { strcopy(ad.hud.text, sizeof(ad.hud.text), buf); ad.hud.x = kv.GetFloat("x", -1.0); ad.hud.y = kv.GetFloat("y", -1.0); ad.hud.holdTime = kv.GetFloat("holdTime", g_fInterval); char sColor[32]; kv.GetString("color", sColor, sizeof(sColor), "255 255 255 255"); ParseRGBA(sColor, ad.hud.color); ad.hasHud = true; } kv.GoBack(); } if (ad.hasChat || ad.hasCenter || ad.hasAlert || ad.hasHud) g_Ads[g_iAdCount++] = ad; } while (kv.GotoNextKey()); kv.GoBack(); } kv.GoBack(); } delete kv; LogMessage("[ARCANEGAME AD] Загружено %d объявлений, интервал %.1f сек.", g_iAdCount, g_fInterval); } // ============================================================= // Парсинг строки "R G B A" → int[4] // ============================================================= void ParseRGBA(const char[] str, int rgba[4]) { char p[4][8]; int n = ExplodeString(str, " ", p, 4, 8); rgba[0] = (n > 0) ? StringToInt(p[0]) : 255; rgba[1] = (n > 1) ? StringToInt(p[1]) : 255; rgba[2] = (n > 2) ? StringToInt(p[2]) : 255; rgba[3] = (n > 3) ? StringToInt(p[3]) : 255; } // ============================================================= // Замена {COLOR} тегов на CS:GO escape-коды // ============================================================= void ApplyColors(char[] msg, int maxLen) { ReplaceString(msg, maxLen, "{DEFAULT}", CLR_DEFAULT); ReplaceString(msg, maxLen, "{GREEN}", CLR_GREEN); ReplaceString(msg, maxLen, "{OLIVE}", CLR_OLIVE); ReplaceString(msg, maxLen, "{LIGHTGREEN}", CLR_LIGHTGREEN); ReplaceString(msg, maxLen, "{RED}", CLR_RED); ReplaceString(msg, maxLen, "{LIGHTRED}", CLR_LIGHTRED); ReplaceString(msg, maxLen, "{LIGHTPURPLE}", CLR_LIGHTPURPLE); ReplaceString(msg, maxLen, "{PURPLE}", CLR_PURPLE); ReplaceString(msg, maxLen, "{GRAY}", CLR_GRAY); ReplaceString(msg, maxLen, "{BLUE}", CLR_BLUE); ReplaceString(msg, maxLen, "{LIGHTBLUE}", CLR_LIGHTBLUE); ReplaceString(msg, maxLen, "{LIME}", CLR_LIME); ReplaceString(msg, maxLen, "{LIGHTOLIVE}", CLR_LIGHTOLIVE); } // ============================================================= // Удаление {COLOR} тегов (для Center / Alert / HUD) // ============================================================= void StripColors(char[] msg, int maxLen) { ReplaceString(msg, maxLen, "{DEFAULT}", ""); ReplaceString(msg, maxLen, "{GREEN}", ""); ReplaceString(msg, maxLen, "{OLIVE}", ""); ReplaceString(msg, maxLen, "{LIGHTGREEN}", ""); ReplaceString(msg, maxLen, "{RED}", ""); ReplaceString(msg, maxLen, "{LIGHTRED}", ""); ReplaceString(msg, maxLen, "{LIGHTPURPLE}", ""); ReplaceString(msg, maxLen, "{PURPLE}", ""); ReplaceString(msg, maxLen, "{GRAY}", ""); ReplaceString(msg, maxLen, "{BLUE}", ""); ReplaceString(msg, maxLen, "{LIGHTBLUE}", ""); ReplaceString(msg, maxLen, "{LIME}", ""); ReplaceString(msg, maxLen, "{LIGHTOLIVE}", ""); } // ============================================================= // Таймер — показываем следующее объявление // ============================================================= public Action Timer_ShowAd(Handle timer) { if (g_iAdCount == 0) return Plugin_Continue; ShowAd(g_iCurrentAd); g_iCurrentAd = (g_iCurrentAd + 1) % g_iAdCount; return Plugin_Continue; } // ============================================================= // Отображение одного объявления // ============================================================= void ShowAd(int idx) { // ── Chat ────────────────────────────────────────────────── if (g_Ads[idx].hasChat) { char msg[MAX_MSG_LEN]; strcopy(msg, sizeof(msg), g_Ads[idx].chat); ProcessTags(msg, sizeof(msg)); // Конвертируем literal \n (два символа) в реальный перенос строки ReplaceString(msg, sizeof(msg), "\\n", "\n"); char lines[16][512]; int cnt = ExplodeString(msg, "\n", lines, sizeof(lines), sizeof(lines[])); for (int i = 0; i < cnt; i++) { TrimString(lines[i]); if (!lines[i][0]) continue; char line[512]; strcopy(line, sizeof(line), lines[i]); ApplyColors(line, sizeof(line)); PrintToChatAll(line); } } // ── Center ──────────────────────────────────────────────── if (g_Ads[idx].hasCenter) { char msg[MAX_MSG_LEN]; strcopy(msg, sizeof(msg), g_Ads[idx].center); ProcessTags(msg, sizeof(msg)); StripColors(msg, sizeof(msg)); PrintCenterTextAll(msg); } // ── Alert (hint) ────────────────────────────────────────── if (g_Ads[idx].hasAlert) { char msg[MAX_MSG_LEN]; strcopy(msg, sizeof(msg), g_Ads[idx].alert); ProcessTags(msg, sizeof(msg)); StripColors(msg, sizeof(msg)); PrintHintTextToAll(msg); } // ── HUD ─────────────────────────────────────────────────── if (g_Ads[idx].hasHud) { char msg[MAX_MSG_LEN]; strcopy(msg, sizeof(msg), g_Ads[idx].hud.text); ProcessTags(msg, sizeof(msg)); StripColors(msg, sizeof(msg)); SetHudTextParams( g_Ads[idx].hud.x, g_Ads[idx].hud.y, g_Ads[idx].hud.holdTime, g_Ads[idx].hud.color[0], g_Ads[idx].hud.color[1], g_Ads[idx].hud.color[2], g_Ads[idx].hud.color[3] ); for (int i = 1; i <= MaxClients; i++) if (IsClientInGame(i) && !IsFakeClient(i)) ShowSyncHudText(i, g_hHud, msg); } } // ============================================================= // Замена информационных тегов в строке // ============================================================= void ProcessTags(char[] msg, int maxLen) { // {IP:PORT} ConVar cvIP = FindConVar("ip"); ConVar cvPort = FindConVar("hostport"); if (cvIP && cvPort) { char sIP[64], sPort[8], sIPPort[80]; cvIP.GetString(sIP, sizeof(sIP)); cvPort.GetString(sPort, sizeof(sPort)); FormatEx(sIPPort, sizeof(sIPPort), "%s:%s", sIP, sPort); ReplaceString(msg, maxLen, "{IP:PORT}", sIPPort); } // {DATE} char sDate[16]; FormatTime(sDate, sizeof(sDate), "%d.%m.%Y"); ReplaceString(msg, maxLen, "{DATE}", sDate); // {TIME} char sTime[16]; FormatTime(sTime, sizeof(sTime), "%H:%M:%S"); ReplaceString(msg, maxLen, "{TIME}", sTime); // {MAP} char sRaw[MAX_MAP_NAME], sPretty[MAX_MAP_NAME]; GetCurrentMap(sRaw, sizeof(sRaw)); if (!g_MapNames.GetString(sRaw, sPretty, sizeof(sPretty))) strcopy(sPretty, sizeof(sPretty), sRaw); ReplaceString(msg, maxLen, "{MAP}", sPretty); // {PL} int pl = 0; for (int i = 1; i <= MaxClients; i++) if (IsClientConnected(i) && !IsFakeClient(i)) pl++; char sPL[8]; IntToString(pl, sPL, sizeof(sPL)); ReplaceString(msg, maxLen, "{PL}", sPL); // {TIC} char sTick[8]; IntToString(RoundFloat(1.0 / GetTickInterval()), sTick, sizeof(sTick)); ReplaceString(msg, maxLen, "{TIC}", sTick); // {SERVERNAME} ConVar cvHost = FindConVar("hostname"); if (cvHost) { char sHost[128]; cvHost.GetString(sHost, sizeof(sHost)); ReplaceString(msg, maxLen, "{SERVERNAME}", sHost); } // {TIMELEFT} int iLeft; if (GetMapTimeLeft(iLeft)) { char sTL[32]; if (iLeft > 0) FormatEx(sTL, sizeof(sTL), "%d:%02d", iLeft / 60, iLeft % 60); else strcopy(sTL, sizeof(sTL), "последний раунд"); ReplaceString(msg, maxLen, "{TIMELEFT}", sTL); } // {NEXTMAP} — требует плагин mapchooser или nextmap if (GetFeatureStatus(FeatureType_Native, "GetNextMap") == FeatureStatus_Available) { char sNext[MAX_MAP_NAME], sNextPretty[MAX_MAP_NAME]; if (GetNextMap(sNext, sizeof(sNext))) { if (!g_MapNames.GetString(sNext, sNextPretty, sizeof(sNextPretty))) strcopy(sNextPretty, sizeof(sNextPretty), sNext); ReplaceString(msg, maxLen, "{NEXTMAP}", sNextPretty); } } // {ADMINSONLINE1} — через запятую // {ADMINSONLINE2} — каждый с новой строки char sA1[512], sA2[512]; bool bFirst = true; for (int i = 1; i <= MaxClients; i++) { if (!IsClientInGame(i) || IsFakeClient(i)) continue; if (!(GetUserFlagBits(i) & ADMFLAG_GENERIC)) continue; char sName[MAX_NAME_LENGTH]; GetClientName(i, sName, sizeof(sName)); if (!bFirst) { StrCat(sA1, sizeof(sA1), ", "); StrCat(sA2, sizeof(sA2), "\n"); } StrCat(sA1, sizeof(sA1), sName); StrCat(sA2, sizeof(sA2), sName); bFirst = false; } ReplaceString(msg, maxLen, "{ADMINSONLINE1}", sA1); ReplaceString(msg, maxLen, "{ADMINSONLINE2}", sA2); }