Files

480 lines
17 KiB
SourcePawn

// ============================================================
// ARCANEGAME Advertisement Plugin
// Конфиг: addons/sourcemod/configs/reklama.ini
// Зависимости: mapchooser / nextmap (для тега {NEXTMAP})
// Внешние includes НЕ нужны
// ============================================================
#pragma semicolon 1
#pragma newdecls required
#include <sourcemod>
#include <sdktools>
#include <mapchooser>
#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);
}