480 lines
17 KiB
SourcePawn
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);
|
|
}
|