#include #include #include #pragma semicolon 1 #pragma newdecls required #define PLUGIN_VERSION "1.0" // Shortest automatic announcement interval, in seconds. Values below this are // raised to it; 0 or below disables the timer entirely. #define MIN_ANNOUNCE_INTERVAL 60.0 // Visible characters kept from the event name in chat. #define ANNOUNCE_NAME_MAX 64 // Visible characters allowed for the map list before " +N more" is appended. #define ANNOUNCE_MAPS_BUDGET 120 ConVar g_cvInterval; ConVar g_cvCount; Handle g_hAnnounceTimer; int g_iRotation; public Plugin myinfo = { name = "EventNotifier Announcements", author = "marisakirisame.net", description = "Periodically announces upcoming Unloze events in chat", version = PLUGIN_VERSION, url = "https://marisakirisame.net" }; public void OnPluginStart() { g_cvInterval = CreateConVar("sm_event_announce_interval", "120", "Seconds between event announcements in chat, -1 to disable automatic announcements (sm_event_announce still works)", _, true, -1.0); g_cvCount = CreateConVar("sm_event_announce_count", "3", "Rotate announcements through the next N upcoming events, one per announcement", _, true, 1.0, true, 20.0); // CreateConVar keeps the old bounds when the cvar already exists (plugin reload), // so apply the lower bound explicitly. g_cvInterval.SetBounds(ConVarBound_Lower, true, -1.0); g_cvInterval.AddChangeHook(OnIntervalChanged); AutoExecConfig(true, "EventNotifierAnnouncements"); RegAdminCmd("sm_event_announce", Command_Announce, ADMFLAG_GENERIC, "Posts the next event announcement in chat now"); } public void OnConfigsExecuted() { RestartAnnounceTimer(); } public void OnIntervalChanged(ConVar convar, const char[] oldValue, const char[] newValue) { RestartAnnounceTimer(); } void RestartAnnounceTimer() { delete g_hAnnounceTimer; float interval = g_cvInterval.FloatValue; if (interval <= 0.0) return; if (interval < MIN_ANNOUNCE_INTERVAL) interval = MIN_ANNOUNCE_INTERVAL; g_hAnnounceTimer = CreateTimer(interval, Timer_Announce, _, TIMER_REPEAT); } public Action Timer_Announce(Handle timer) { PostAnnouncement(); return Plugin_Continue; } public Action Command_Announce(int client, int args) { if (PostAnnouncement()) ReplyToCommand(client, "[Events] Announcement posted."); else ReplyToCommand(client, "[Events] No upcoming events to announce."); return Plugin_Handled; } // --------------------------------------------------------------------------- // Announcement // --------------------------------------------------------------------------- // Posts one event from the rotation window. Returns false when there is nothing to post. bool PostAnnouncement() { int count = EventNotifier_GetEventCount(); if (count <= 0) return false; int window = g_cvCount.IntValue; if (window > count) window = count; int index = g_iRotation % window; g_iRotation = (index + 1) % window; EventInfo ev; if (!EventNotifier_GetEvent(index, ev)) return false; char name[ANNOUNCE_NAME_MAX + 1]; strcopy(name, sizeof(name), ev.name); if (!name[0]) strcopy(name, sizeof(name), "Unnamed event"); int now = GetTime(); char span[32]; if (ev.start <= 0) { CPrintToChatAll("{darkorange}[Events] {yellow}#%d: {lightgreen}%s {grey}date TBA", ev.number, name); } else if (now < ev.start) { EventNotifier_FormatDuration(ev.start - now, span, sizeof(span)); CPrintToChatAll("{darkorange}[Events] {yellow}#%d: {lightgreen}%s {lime}starts in %s", ev.number, name, span); } else { EventNotifier_FormatDuration(now - ev.start, span, sizeof(span)); CPrintToChatAll("{darkorange}[Events] {yellow}#%d: {lightgreen}%s {orchid}started %s ago", ev.number, name, span); } if (ev.maps[0]) { char mapsLine[ANNOUNCE_MAPS_BUDGET + 132]; int more = BuildMapsLine(ev.maps, mapsLine, sizeof(mapsLine), ANNOUNCE_MAPS_BUDGET); if (more > 0) CPrintToChatAll("{darkorange}[Events] {white}Maps: {lightgreen}%s {grey}+%d more", mapsLine, more); else CPrintToChatAll("{darkorange}[Events] {white}Maps: {lightgreen}%s", mapsLine); } CPrintToChatAll("{darkorange}[Events] {white}Type {lime}!event %d {white}for details and the full map list.", ev.number); return true; } // Copies ", "-separated items from maps into out while the visible length stays // within budget. Always emits at least the first item. Returns how many items // were left out (0 when everything fit). int BuildMapsLine(const char[] maps, char[] out, int maxlen, int budget) { // Count from the raw string so "+N more" is right even past the explode limit. int total = 1; for (int i = 0; maps[i]; i++) { if (maps[i] == ',' && maps[i + 1] == ' ') total++; } char parts[32][128]; int count = ExplodeString(maps, ", ", parts, sizeof(parts), sizeof(parts[])); out[0] = '\0'; int used = 0; int shown = 0; for (int i = 0; i < count; i++) { int add = strlen(parts[i]) + ((shown > 0) ? 2 : 0); if (shown > 0 && used + add > budget) break; if (shown > 0) StrCat(out, maxlen, ", "); StrCat(out, maxlen, parts[i]); used += add; shown++; } return total - shown; }