C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Steamworks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
#region patches
//v1.0.1 Patched forced events saying nobody participated.Updated Start Event Sound.Updated Colors on player and kill count announcements.//v1.0.1 Changed kills to bot kills
//v1.0.2 Fixed players with 0 kills after using /botkills from winning if they were the only ones with data.//v1.0.3 Added true or false on scheduling random events.//v1.0.4 Tie breaker wasnt breaking the tie, re added the check.//v1.0.5 Event wasnt rescheduling after a natural end.Added checks to get rescheduled.Added on player connected hook as a tester.//v1.0.6 Removed an else function, Some endings announce nobody played.Will find a workaround and update soon.//v1.0.7 Swapped around the else function I commented out, now the tie, single winner and no players seem to be announcing correctly.//v1.0.8 Did some troubleshooting tests on server, never saw a non participation message, looks good now.//v1.0.9 On some we saw no participants on event wins, Removed that message.Also Removed some redundant code.//v1.1.0 Added config option for start SFX.//v1.1.1 Added message for manually ended events.//v1.1.2 Changed second config option for null name to "", it was throwing errors in console at end due to incorrect format.Added more checks for UI kill.//v1.1.2 Removed some debugging.//v1.1.3 Added config option to disable or enable loot table, Added Economics Support, reworked winner announcement.//v1.1.4 Added ServerRewards Support.Can use either or together if needed.//v1.1.5 Added a Check and Catch if SR or Eco is not loaded.If not it will proceed with event end as needed.//v1.1.6 Cleaned up Timer for Auto sched.and Duration Removed some unused code.//v1.1.7 Tweaked the way the plugin detects if an event is able to be Scheduled depending on active players.//v1.1.8 Added a Min Max Item Randomizer for the Loot Table.//v1.1.9 Corrected a line if config option was null.//v1.2.0 Leaderboard UI - Ridamees
//v1.2.1 Added Event start and end hooks, for use with HUD.//v1.2.2 Changed Time Formatting to Display M / S Remaining.//v1.2.3 Corrected a check if enough players are on for scheduled events.//v1.2.4 Added a check, if the threshold for players needed for events, fell below during an event, it wouldn't schedule the next after that event ended, until threshold is reached again.//v1.2.5 Added new config options for commands and event messaging / toggle, ui positioning, also redid the entire lang for the plugin, now it can be customized as needed, cleaned up and reorganized code.//v1.2.51 Fixed Tie Message Key
//v1.2.6 Worked out every outcome if loot table, SR, or Eco are being used in diff combos, to print the correct output to chat of rewards given.//v1.2.7 Added option to run commands for the winner, added checks for pvp kills.//v1.2.8 Rewards Dist.Section cleaned up.#endregion
namespace Oxide.Plugins {
[Info("Bot Purge Event", "Wrecks", "1.2.8")]
[Description("Bot Killing event featuring a Leaderboard UI.")]
class BotPurgeEvent : RustPlugin {
[PluginReference]
private Plugin Economics, ServerRewards;
#region declarations
private class PlayerData {
public int KillCount {
get;
set;
}
}
private bool enableServerRewards;
private bool enableEconomicRewards;
private bool enableLootTable;
private bool isEventRunning;
private bool eventStarted;
private Dictionary<string, PlayerData> playerData = new Dictionary<string, PlayerData>();
private Timer botPurgeTimer;
private string winnerSteamID;
private int highestKillCount;
private int minPlayersToStart;
private int randomEventMin;
private int randomEventMax;
private int eventDuration;
private ItemDefinition rewardItemDef;
private int rewardQuantity;
private Dictionary<string, string> messages;
private List<Dictionary<string, object>> winnerRewards;
private string startEventSound = "assets/bundled/prefabs/fx/headshot.prefab";
private bool enableStartSfx;
private string winnerSoundEffect = "assets/prefabs/misc/xmas/presents/effects/unwrap.prefab";
private bool manuallyEnded;
private bool isEventSchedulingEnabled;
private bool chatCountdown;
private Timer endEventTimer;
private Timer eventScheduler;
private List<string> participatingPlayers = new List<string>();
private Dictionary<ulong, CuiElementContainer> playerUiContainers = new Dictionary<ulong, CuiElementContainer>();
private string purgeStartCommand;
private string purgeEndCommand;
private const string StartPermission = "botpurgeevent.start";
private const string EndPermission = "botpurgeevent.end";
private float scheduledTime;
private bool firstEventScheduled;
private bool rewardsGiven;
private bool EnableWinnerCommands;
private readonly List<string> killCountColors = new List<string> {
"0.7 0.7 0 1", "0.8 0.8 0 1", "0.9 0.9 0 1", "1 1 0 1", "1 1 0.2 1", "1 1 0.3 1",
}
;
#endregion
#region oxidehooks
void Init() {
CheckPluginLoaded("Economics");
CheckPluginLoaded("ServerRewards");
LoadConfig();
LoadMessages();
string botPurgeStartCommand = Config.Get<string>("BotPurgeStartCommand", "purge");
AddCovalenceCommand(purgeStartCommand, nameof(CmdStartBotPurgeEvent), StartPermission);
string botPurgeEndCommand = Config.Get<string>("BotPurgeEndCommand", "purgeEnd");
AddCovalenceCommand(purgeEndCommand, nameof(CmdEndBotPurgeEvent), EndPermission);
enableStartSfx = Config.Get<bool>("EnableStartSFX");
isEventSchedulingEnabled = Config.Get<bool>("EnableScheduledEvents");
chatCountdown = Config.Get<bool>("ChatCountdownEnabled");
EnableWinnerCommands = Config.Get<bool>("EnableWinnerCommands");
}
private void OnServerInitialized() {
if (CanScheduleNextBotPurge()) {
if (isEventSchedulingEnabled && !eventStarted) {
ScheduleNextBotPurge();
}
else if (eventStarted && isEventSchedulingEnabled) {
double timeRemaining = (scheduledTime - Time.realtimeSinceStartup);
double minutesRemaining = Math.Ceiling(timeRemaining / 60);
Puts($"The next Bot Purge Event will start in {minutesRemaining} minute(s).");
}
}
else {
Puts("Not enough players to schedule the Bot Purge Event.");
}
}
private void OnPlayerDeath(BasePlayer player, HitInfo hitInfo) {
if (!eventStarted || player == null) return;
if (hitInfo == null || !(hitInfo.Initiator is BasePlayer)) return;
BasePlayer attacker = hitInfo.InitiatorPlayer;
if (attacker == null) return;
if (!(attacker is BasePlayer) || attacker.net == null || attacker.net.connection == null) return;
string attackerID = attacker.UserIDString;
PlayerData attackerData = GetPlayerData(attackerID);
if (player.UserIDString == attackerID) return;
attackerData.KillCount++;
int killCount = attackerData.KillCount;
if (killCount > highestKillCount) {
highestKillCount = killCount;
winnerSteamID = attackerID;
}
if (!participatingPlayers.Contains(attackerID)) {
participatingPlayers.Add(attackerID);
}
if (eventStarted) {
// ShowKillCountUI for the attacker
ShowKillCountUI(attacker, killCount);
// ShowKillCountUI for all other participating players
foreach (string participantID in participatingPlayers) {
if (participantID != attackerID) {
BasePlayer participant = covalence.Players.FindPlayerById(participantID)?.Object as BasePlayer;
if (participant != null) {
ShowKillCountUI(participant, GetPlayerData(participantID).KillCount);
}
}
}
}
}
void OnPlayerConnected(BasePlayer player) {
// Check if there are enough players online and schedule the next event if needed
if (!isEventRunning && isEventSchedulingEnabled && CanScheduleNextBotPurge()) {
ScheduleNextBotPurge();
}
}
//Triggers KillTimer if not enough players online.void OnPlayerDisconnected(BasePlayer player, string reason)
{
timer.Once(1f, () => {
if (BasePlayer.activePlayerList.Count < minPlayersToStart && nextEventTimer != null && !nextEventTimer.Destroyed) {
CancelScheduledEvent();
}
}
);
}
void Unload() {
if (eventStarted) {
EndBotPurgeEvent();
}
if (eventScheduler != null) {
eventScheduler.Destroy();
eventScheduler = null;
}
if (botPurgeTimer != null) {
botPurgeTimer.Destroy();
botPurgeTimer = null;
}
foreach (var player in BasePlayer.activePlayerList) {
DestroyKillCountUI(player);
}
}
#endregion
#region config
// RectTransform = { AnchorMin = $"0.5 1", AnchorMax = $"0.5 1", OffsetMin = $"{250f} {-150f}", OffsetMax = $"{450f} {0f}" },
protected override void LoadDefaultConfig() {
Config["BotPurgeStartCommand"] = "purge";
Config["BotPurgeEndCommand"] = "purgeend";
Config["LeaderboardUIAnchorMin"] = "0.5 1";
Config["LeaderboardUIAnchorMax"] = "0.5 1";
Config["LeaderboardUIOffsetMin"] = "250 -150";
Config["LeaderboardUIOffsetMax"] = "450 0";
Config["EnableEconomicRewards"] = true;
Config["EnableServerRewards"] = true;
Config["EnableStartSFX"] = true;
Config["EnableScheduledEvents"] = true;
Config["ChatCountdownEnabled"] = true;
Config["ChatCountdownInterval(Minutes)"] = 1;
Config["Minimum Players To Start"] = 2;
Config["MinimumTimeBetweenEvents(Seconds)"] = 3600;
Config["MaximumTimeBetweenEvents(Seconds)"] = 7200;
Config["EventDuration(Seconds)"] = 600;
Config["EconomicRewards"] = 1000;
Config["ServerRewards"] = 1000;
Config["EnableLootTable"] = true;
Config["RandomItemsMin"] = 1;
Config["RandomItemsMax"] = 3;
Config["EnableWinnerCommands"] = false;
Config["WinnerCommands"] = GetConfigValue("WinnerCommands", new List<object> {
new Dictionary<string, object> {
{
"Command", "inventory.giveto {id} stones 100"
}
,
}
, new Dictionary<string, object> {
{
"Command", "inventory.giveto {id} wood 100"
}
,
}
}
);
Config["WinnerRewards"] = GetConfigValue("WinnerRewards", new List<object> {
new Dictionary<string, object> {
{
"shortname", "paper"
}
, {
"minQuantity", 1
}
, {
"maxQuantity", 5
}
, {
"skinid", null
}
, {
"name", "Blood Money"
}
}
, new Dictionary<string, object> {
{
"shortname", "scrap"
}
, {
"minQuantity", 5
}
, {
"maxQuantity", 15
}
, {
"skinid", null
}
, {
"name", ""
}
}
}
);
}
private void LoadConfig() {
try {
purgeStartCommand = GetConfigValue("BotPurgeStartCommand", "purge");
purgeEndCommand = GetConfigValue("BotPurgeEndCommand", "purgeend");
Config["LeaderboardUIAnchorMin"] = GetConfigValue("LeaderboardUIAnchorMin", "0.5 1");
Config["LeaderboardUIAnchorMax"] = GetConfigValue("LeaderboardUIAnchorMax", "0.5 1");
Config["LeaderboardUIOffsetMin"] = GetConfigValue("LeaderboardUIOffsetMin", "250 -150");
Config["LeaderboardUIOffsetMax"] = GetConfigValue("LeaderboardUIOffsetMax", "450 0");
Config["EnableServerRewards"] = GetConfigValue("EnableServerRewards", false);
Config["EnableEconomicRewards"] = GetConfigValue("EnableEconomicRewards", false);
Config["EnableStartSFX"] = GetConfigValue("EnableStartSFX", true);
Config["EnableScheduledEvents"] = GetConfigValue("EnableScheduledEvents", true);
Config["ChatCountdownEnabled"] = GetConfigValue("ChatCountdownEnabled", true);
Config["ChatCountdownInterval(Minutes)"] = GetConfigValue("ChatCountdownInterval(Minutes)", 1);
minPlayersToStart = GetConfigValue("Minimum Players To Start", 2);
randomEventMin = GetConfigValue("MinimumTimeBetweenEvents(Seconds)", 3600);
randomEventMax = GetConfigValue("MaximumTimeBetweenEvents(Seconds)", 7200);
Config["EventDuration(Seconds)"] = GetConfigValue("EventDuration(Seconds)", 600);
Config["EconomicRewards"] = GetConfigValue("EconomicRewards", 1000);
Config["ServerRewards"] = GetConfigValue("ServerRewards", 1000);
Config["EnableLootTable"] = GetConfigValue("EnableLootTable", true);
Config["RandomItemsMin"] = GetConfigValue("RandomItemsMin", 1);
Config["RandomItemsMax"] = GetConfigValue("RandomItemsMax", 3);
Config["EnableWinnerCommands"] = GetConfigValue("EnableWinnerCommands", false);
Config["WinnerCommands"] = GetConfigValue("WinnerCommands", new List<object> {
new Dictionary<string, object> {
{
"Command", "inventory.giveto {id} stones 100"
}
,
}
, new Dictionary<string, object> {
{
"Command", "inventory.giveto {id} wood 100"
}
,
}
}
);
Config["WinnerRewards"] = GetConfigValue("WinnerRewards", new List<object> {
new Dictionary<string, object> {
{
"shortname", "paper"
}
, {
"minQuantity", 1
}
, {
"maxQuantity", 5
}
, {
"skinid", null
}
, {
"name", "Blood Money"
}
}
, new Dictionary<string, object> {
{
"shortname", "scrap"
}
, {
"minQuantity", 5
}
, {
"maxQuantity", 15
}
, {
"skinid", null
}
, {
"name", ""
}
}
}
);
SaveConfig();
}
catch (Exception ex) {
PrintError("Error loading configuration: " + ex.Message);
}
}
T GetConfigValue<T>(string key, T defaultValue) {
if (Config[key] == null) {
Config[key] = defaultValue;
SaveConfig();
}
return (T)Config[key];
}
#endregion
#region localization
void LoadMessages() {
messages = new Dictionary<string, string> {
{
"EventStarted", "[<color=#880808>BOT PURGE EVENT</color>] The Bot Purge Event has started! <color=#884808>Kill</color> as many bots as you can!" //used
}
, {
"EventAlreadyRunning", "[<color=#880808>BOT PURGE EVENT</color>] The Event is already running."
}
, // used
{
"EventNotRunning", "[<color=#880808>BOT PURGE EVENT</color>] The Event is not currently running."
}
, // used
{
"NoPermissionStart", //used
"[<color=#880808>BOT PURGE EVENT</color>] You don't have permission to start the Bot Purge Event."
}
, {
"NoPermissionEnd", //used
"[<color=#880808>BOT PURGE EVENT</color>] You don't have permission to end the Bot Purge Event."
}
, {
"ManuallyEnded", "[<color=#880808>BOT PURGE EVENT</color>] The Event was manually Ended."
}
, {
"NoParticipants", "[<color=#880808>BOT PURGE EVENT</color>] The Event has ended! Unfortunately, no one participated."
}
, {
"CountdownText", "[<color=#880808>BOT PURGE EVENT</color>] The Bot Purge Event will end in <color=#880808>{0}</color> minute(s)."
}
, {
"TieMessage", "The Bot Purge Event has ended! There was a tie among <color=#880808>{0}</color> players with <color=#880808>{1}</color> kills."
}
, {
"TieWinnerMessage", "The winner was randomly selected: <color=#880808>{0}</color> with <color=#880808>{1}</color> kills!"
}
, {
"WinnerAnnounce", "[<color=#880808>BOT PURGE EVENT</color>] {0}"
}
, {
"HasEnded", "The Bot Purge Event Has Ended!"
}
, {
"EndMessage", "\n\nThe Bot Purge Event has ended!\n\nThe winner is <color=#880888>{0}</color> with <color=#884808>{1}</color> kill(s)!"
}
, {
"RewardMessage", "\n<color=#880848>{0}</color> <color=#888808>x</color> <color=#088848>{1}</color>"
}
, {
"EconomicRewardMessage", "\n<color=#880888>{0}</color> received <color=#bb9b65>$</color><color=#85bb65>{1}</color>!"
}
, {
"ServerRewardsMessage", "\n<color=#880888>{0}</color> also received <color=#85bb65>{1}</color><color=#bb9b65> RP</color>!"
}
, {
"PlayerEarnedMessage", "\n<color=#880888>{0}</color> earned:\n{1}"
}
}
;
lang.RegisterMessages(messages, this);
}
#endregion
#region helpers
void CheckPluginLoaded(string pluginName) {
Plugin plugin = plugins.Find(pluginName);
if (plugin == null) {
Puts($"The plugin {pluginName} is not loaded!");
}
}
// Scheduling.private string FormatTimeSpan(float seconds)
{
TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
int totalHours = (int)timeSpan.TotalHours;
int totalMinutes = (int)timeSpan.Minutes;
//Puts($"seconds: {seconds}, totalHours: {totalHours}, totalMinutes: {totalMinutes}"); // DebugSched.if (totalHours > 0 && totalMinutes > 0)
{
return $"{totalHours} hour(s) and {totalMinutes} minute(s)";
}
else if (totalHours > 0) {
return $"{totalHours} hour(s)";
}
else {
return $"{totalMinutes} minute(s)";
}
}
private double nextEventTime;
private Timer nextEventTimer;
private void ScheduleNextBotPurge() {
// Check if there's an existing timer and return if so
if (nextEventTimer != null && !nextEventTimer.Destroyed) {
// Puts("An event is already scheduled."); //Debug
return;
}
if (!isEventSchedulingEnabled) {
Puts("Scheduled events are disabled in the config.");
return;
}
if (!CanScheduleNextBotPurge()) {
Puts("Not enough players to schedule the event.");
return;
}
int randomSeconds = UnityEngine.Random.Range(randomEventMin, randomEventMax + 1);
nextEventTime = GrabCurrentTime() + randomSeconds;
Puts($"The next Bot Purge Event will start in {FormatTimeSpan(randomSeconds)}.");
nextEventTimer = timer.Once(randomSeconds, StartBotPurgeEvent);
}
private bool CanScheduleNextBotPurge() {
bool canStart = BasePlayer.activePlayerList.Count >= minPlayersToStart;
return canStart;
}
// Method above triggers the KillTimer
private void CancelScheduledEvent() {
if (nextEventTimer != null) {
//Puts("nextEventTimer is not null");
if (!nextEventTimer.Destroyed) {
// Puts("nextEventTimer is not destroyed, attempting to destroy");
nextEventTimer.Destroy();
nextEventTimer = null;
}
else {
// Puts("nextEventTimer is already destroyed");
}
}
else {
// Puts("nextEventTimer is null");
}
Puts("Bot Purge Event has been canceled due to insufficient players.");
}
private double GrabCurrentTime() {
return DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
}
private DateTime eventEndTime;
// Property to calculate and return minutesLeft
private int MinutesLeft {
get {
TimeSpan remainingTime = eventEndTime - DateTime.UtcNow;
int secondsRemaining = Mathf.Max(0, (int)remainingTime.TotalSeconds);
return Mathf.CeilToInt((float)secondsRemaining / 60f);
}
}
private string FormatTime(TimeSpan timeSpan) {
// Extract hours, minutes, and seconds
int hours = timeSpan.Hours;
int minutes = timeSpan.Minutes;
int seconds = timeSpan.Seconds;
// Create the formatted string
string formattedTime = "";
if (hours > 0) {
formattedTime += $"{hours:D1}h ";
}
if (minutes > 0 || hours > 0) {
formattedTime += $"{minutes:D1}m ";
}
formattedTime += $"{seconds:D1}s";
return formattedTime;
}
#endregion
#region ui
private string panelName = "KillCountPanel";
private void ShowKillCountUI(BasePlayer player, int playerKillCount) {
DestroyKillCountUI(player);
CuiElementContainer container = new CuiElementContainer();
// Customize the position and appearance of the UI elements as per your preference
string textElementName = "KillCountText";
string temptextElementName = "TempText";
container.Add(new CuiPanel {
Image = {
Color = "35 35 35 0.17"
}
, RectTransform = {
AnchorMin = GetConfigValue("LeaderboardUIAnchorMin", "0.5 1"), AnchorMax = GetConfigValue("LeaderboardUIAnchorMax", "0.5 1"), OffsetMin = GetConfigValue("LeaderboardUIOffsetMin", "250 -150"), OffsetMax = GetConfigValue("LeaderboardUIOffsetMax", "450 0")
}
,
}
, "Hud", panelName);
container.Add(new CuiElement {
Name = textElementName, Parent = panelName, Components = {
new CuiTextComponent {
Text = " <color=#880808>BOT </color><color=#880808>PURGE EVENT</color> ", FontSize = 17, Font = "permanentmarker.ttf", Align = TextAnchor.UpperCenter
}
, new CuiRectTransformComponent {
AnchorMin = "0 0", AnchorMax = "1 0.975"
}
, new CuiOutlineComponent {
Color = "0 0 0 0.4", Distance = "0.25 0.25", UseGraphicAlpha = true
}
}
}
);
// Check the number of entries in playerData
int numEntries = playerData.Count;
if (numEntries <= 4) {
// If there are 3 or fewer entries, add the temptextElementName
container.Add(new CuiElement {
Name = temptextElementName, Parent = panelName, Components = {
new CuiTextComponent {
Text = "<color=#884808>Kill the most bots!</color>", FontSize = 16, Font = "permanentmarker.ttf", Align = TextAnchor.UpperCenter
}
, new CuiRectTransformComponent {
AnchorMin = "0 0", AnchorMax = "1 0.3"
}
, new CuiOutlineComponent {
Color = "0 0 0 0.4", Distance = "0.25 0.25", UseGraphicAlpha = true
}
}
}
);
}
TimeSpan remainingTime = eventEndTime - DateTime.UtcNow;
string formattedTime = FormatTime(remainingTime);
container.Add(new CuiElement {
Name = timeLeftElementName, Parent = panelName, Components = {
new CuiTextComponent {
Text = $" <color=#880808>Time Left:<color=white> {FormatTime(remainingTime)}</color></color> ", FontSize = 15, Font = "permanentmarker.ttf", Align = TextAnchor.LowerCenter
}
, new CuiRectTransformComponent {
AnchorMin = "0 0.01", AnchorMax = "1 1"
}
, new CuiOutlineComponent {
Color = "0 0 0 0.4", Distance = "0.25 0.25", UseGraphicAlpha = true
}
}
}
);
// Display top 3 players' kill counts
int playerRank = 1;
float verticalPosition = 0.83f;
foreach (var entry in playerData.OrderByDescending(pair => pair.Value.KillCount).Take(5)) {
string textColor = GetKillCountColor(entry.Value.KillCount);
string playerName = covalence.Players.FindPlayerById(entry.Key)?.Name ?? "Unknown";
playerName = playerName.Length > 8 ? playerName.Substring(0, 8) : playerName;
// Limit name to 8 characters
string killCountText = $"{playerRank}.{playerName}<color=#880808> - </color><color=white>{entry.Value.KillCount}</color>";
if (entry.Key == player.UserIDString) {
killCountText += " <";
// Add the "<" symbol to the player's own entry
}
container.Add(new CuiElement {
Name = textElementName, Parent = panelName, Components = {
new CuiTextComponent {
Text = killCountText, Color = textColor, FontSize = 15, Font = "permanentmarker.ttf", Align = TextAnchor.UpperCenter
}
, new CuiRectTransformComponent {
AnchorMin = $"0 {verticalPosition - 0.25f}", AnchorMax = $"1 {verticalPosition}"
}
, new CuiOutlineComponent {
Color = "0 0 0 0.4", Distance = "0.25 0.25", UseGraphicAlpha = true
}
}
}
);
playerRank++;
verticalPosition -= 0.135f;
}
// Call AddTimeLeftElement with just the player
if (timeLeftElementName == null) {
AddTimeLeftElement(player);
}
CuiHelper.AddUi(player, container);
playerUiContainers[player.userID] = container;
}
private string timeLeftElementName = "TimeLeftText";
private void AddTimeLeftElement(BasePlayer player) {
TimeSpan remainingTime = eventEndTime - DateTime.UtcNow;
string formattedTime = FormatTime(remainingTime);
if (string.IsNullOrEmpty(timeLeftElementName)) {
// The required name is not properly set, cannot add the time left element.return;
}
// First, destroy the existing time left element if it exists
CuiHelper.DestroyUi(player, timeLeftElementName);
CuiElementContainer container = new CuiElementContainer();
container.Add(new CuiElement {
Name = timeLeftElementName, Parent = panelName, Components = {
new CuiTextComponent {
Text = $" <color=#880808>Time Left:<color=white> {FormatTime(remainingTime)}</color></color> ", FontSize = 15, Font = "permanentmarker.ttf", Align = TextAnchor.LowerCenter
}
, new CuiRectTransformComponent {
AnchorMin = "0 0.01", AnchorMax = "1 1"
}
, new CuiOutlineComponent {
Color = "0 0 0 0.4", Distance = "0.25 0.25", UseGraphicAlpha = true
}
}
}
);
CuiHelper.AddUi(player, container);
}
private string GetFormattedTime(int seconds) {
TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
return $"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
}
private string GetKillCountColor(int killCount) {
int colorIndex = killCount / 5;
colorIndex = Mathf.Clamp(colorIndex, 0, killCountColors.Count - 1);
return killCountColors[colorIndex];
}
private void DestroyKillCountUI(BasePlayer player) {
CuiElementContainer container;
if (playerUiContainers.TryGetValue(player.userID, out container)) {
// Get the panel name to destroy the UI
string panelName = "KillCountPanel";
CuiHelper.DestroyUi(player, timeLeftElementName);
CuiHelper.DestroyUi(player, panelName);
playerUiContainers.Remove(player.userID);
}
}
#endregion
#region eventmethods
private void StartBotPurgeEvent() {
eventStarted = true;
Interface.CallHook("OnBotPurgeEventStart");
//Start Event Hook
rewardsGiven = false;
playerData.Clear();
highestKillCount = 0;
winnerSteamID = null;
//Generate fake player data for leaderboard testing
//for (int i = 0; i < 3; i++)
//{
// ulong fakePlayerID = 76543210 + (ulong)i;
// int fakeKillCount = UnityEngine.Random.Range(1, 15);
//
// // Create a new PlayerData instance and add it to the playerData dictionary
// playerData[fakePlayerID.ToString()] = new PlayerData { KillCount = fakeKillCount };
//}
if (botPurgeTimer != null) {
botPurgeTimer.Destroy();
botPurgeTimer = null;
}
int eventDuration = GetConfigValue("EventDuration(Seconds)", 600);
eventEndTime = DateTime.UtcNow.AddSeconds(eventDuration);
// Set eventEndTime
Server.Broadcast(lang.GetMessage("EventStarted", this));
if (enableStartSfx) {
//SFX
foreach (var player in BasePlayer.activePlayerList) {
EffectNetwork.Send(new Effect(startEventSound, player.transform.position, Vector3.zero));
}
}
botPurgeTimer = timer.Every(1f, () => {
foreach (var player in BasePlayer.activePlayerList) {
CuiElementContainer container;
if (playerUiContainers.TryGetValue(player.userID, out container)) {
AddTimeLeftElement(player);
}
}
}
);
int countdownIntervalMinutes = GetConfigValue("ChatCountdownInterval(Minutes)", 1);
botPurgeTimer = timer.Every(countdownIntervalMinutes * 60f, () => {
int minutesLeft = MinutesLeft;
if (minutesLeft == 0) {
isEventRunning = false;
EndBotPurgeEvent();
return;
}
if (chatCountdown) {
string messageKey = lang.GetMessage("CountdownText", this);
string formattedMessage = string.Format(lang.GetMessage(messageKey, null), minutesLeft);
Server.Broadcast(formattedMessage);
}
}
);
}
private void OnEventStarted(DateTime eventEndTime) {
Puts(messages["EventStarted"]);
Server.Broadcast(lang.GetMessage("EventStarted", this));
foreach (var player in BasePlayer.activePlayerList) {
var playerData = GetPlayerData(player.UserIDString);
playerData.KillCount = 0;
}
botPurgeTimer = timer.Every(1f, () => {
TimeSpan remainingTime = eventEndTime - DateTime.UtcNow;
int secondsRemaining = Mathf.Max(0, (int)remainingTime.TotalSeconds);
if (secondsRemaining == 0) {
Server.Broadcast(lang.GetMessage("HasEnded", this));
isEventRunning = false;
EndBotPurgeEvent();
return;
}
}
);
}
private void CmdStartBotPurgeEvent(IPlayer player, string command, string[] args) {
if (player.HasPermission(StartPermission)) {
if (eventStarted) {
player.Message(lang.GetMessage("EventAlreadyRunning", this));
return;
}
if (eventScheduler != null) {
eventScheduler.Destroy();
eventScheduler = null;
}
Puts("EventStarted");
StartBotPurgeEvent();
}
else {
player.Message(lang.GetMessage("NoPermissionStart", this));
}
}
private void CmdEndBotPurgeEvent(IPlayer player, string command, string[] args) {
if (player.HasPermission(EndPermission)) {
if (eventStarted) {
manuallyEnded = true;
Server.Broadcast(lang.GetMessage("ManuallyEnded", this));
EndBotPurgeEvent();
}
else {
player.Message(lang.GetMessage("EventNotRunning", this));
}
}
else {
player.Message(lang.GetMessage("NoPermissionEnd", this));
}
}
private void EndBotPurgeEvent() {
if (botPurgeTimer != null) {
botPurgeTimer.Destroy();
botPurgeTimer = null;
}
eventStarted = false;
isEventRunning = false;
// Destroy the Kill Count UI for all connected players
foreach (var playerID in participatingPlayers) {
BasePlayer player = BasePlayer.FindByID(Convert.ToUInt64(playerID));
if (player != null) {
DestroyKillCountUI(player);
}
}
bool anyParticipantWithKills = false;
List<string> winners = new List<string>();
int highestKillCount = 0;
foreach (var playerID in participatingPlayers) {
PlayerData playerData = GetPlayerData(playerID);
if (playerData.KillCount > 0) {
anyParticipantWithKills = true;
int killCount = playerData.KillCount;
if (killCount > highestKillCount) {
highestKillCount = killCount;
winners.Clear();
winners.Add(playerID);
}
else if (killCount == highestKillCount) {
winners.Add(playerID);
}
}
}
if (anyParticipantWithKills) {
if (winners.Count > 1) {
// There is a tie among multiple players
StringBuilder winnerMessageBuilder = new StringBuilder();
string tieMessage = lang.GetMessage("TieMessage", this);
string formattedTieMessage = string.Format(tieMessage, winners.Count, highestKillCount);
winnerMessageBuilder.AppendLine(formattedTieMessage);
// Delays the announcement for the tiebreaker by 3 seconds for dramatic effect.timer.Once(3f, () =>
{
int randomIndex = UnityEngine.Random.Range(0, winners.Count);
string randomWinnerSteamID = winners[randomIndex];
string winnerName = covalence.Players.FindPlayerById(randomWinnerSteamID)?.Name ?? "Unknown";
ulong randomWinnerUlongID;
if (ulong.TryParse(randomWinnerSteamID, out randomWinnerUlongID)) {
ExecuteCommands(randomWinnerUlongID);
}
string rewardMessage = GiveWinnerRewards(randomWinnerSteamID, highestKillCount);
// Pass the randomly selected winner SteamID and the highestKillCount
string winnerMessage = lang.GetMessage("TieWinnerMessage", this);
string formattedWinnerMessage = string.Format(winnerMessage, winnerName, highestKillCount);
winnerMessageBuilder.AppendLine(formattedWinnerMessage);
winnerMessageBuilder.AppendLine(rewardMessage);
string winnerAnnounce = lang.GetMessage("WinnerAnnounce", this);
string formattedwinnerAnnounce = string.Format(winnerAnnounce, winnerMessageBuilder.ToString());
Server.Broadcast(formattedwinnerAnnounce);
}
);
}
else if (winners.Count == 1) {
// There is only one winner
string winnerID = winners[0];
string winnerName = covalence.Players.FindPlayerById(winnerID)?.Name ?? "Unknown";
ulong winnerUlongID;
if (ulong.TryParse(winnerID, out winnerUlongID)) {
ExecuteCommands(winnerUlongID);
}
StringBuilder winnerMessageBuilder = new StringBuilder();
string endMessage = lang.GetMessage("EndMessage", this);
string formattedEndMessage = string.Format(endMessage, winnerName, highestKillCount);
winnerMessageBuilder.AppendLine(formattedEndMessage);
string rewardMessage = GiveWinnerRewards(winnerID, highestKillCount);
winnerMessageBuilder.AppendLine(rewardMessage);
string winnerAnnounce = lang.GetMessage("WinnerAnnounce", this);
string formattedwinnerAnnounce = string.Format(winnerAnnounce, winnerMessageBuilder.ToString());
Server.Broadcast(formattedwinnerAnnounce);
}
}
else {
// No participants with kills
Server.Broadcast(lang.GetMessage("NoParticipants", this));
}
participatingPlayers.Clear();
playerData.Clear();
foreach (var player in BasePlayer.activePlayerList) {
DestroyKillCountUI(player);
}
ScheduleNextBotPurge();
Interface.CallHook("OnBotPurgeEventEnd");
//End Event Hook
}
private string GiveWinnerEconomicReward(string winnerSteamID, BasePlayer player) {
int economyRewardAmount = Config.Get<int>("EconomicRewards");
if (economyRewardAmount <= 0) {
return null;
}
bool economyRewardGiven = false;
try {
Economics.Call("Deposit", winnerSteamID, (double)economyRewardAmount);
economyRewardGiven = true;
string economicsMessage = lang.GetMessage("EconomicRewardMessage", this);
return string.Format(economicsMessage, player.displayName, economyRewardAmount);
}
catch {
Puts("Economics plugin not found or an error occurred while processing economic rewards.");
}
return null;
}
private string GiveWinnerServerReward(string winnerSteamID, BasePlayer player) {
int serverRewardsAmount = Config.Get<int>("ServerRewards");
if (serverRewardsAmount <= 0) {
return null;
}
bool serverRewardsGiven = false;
try {
ServerRewards.Call("AddPoints", winnerSteamID, serverRewardsAmount);
serverRewardsGiven = true;
string serverRewardsMessage = lang.GetMessage("ServerRewardsMessage", this);
return string.Format(serverRewardsMessage, player.displayName, serverRewardsAmount);
}
catch {
Puts("Server Rewards plugin not found or an error occurred while processing server rewards.");
}
return null;
}
private string GiveWinnerItemRewards(string winnerSteamID, BasePlayer player) {
List<Dictionary<string, object>> winnerRewards = Config.Get<List<Dictionary<string, object>>>("WinnerRewards");
int RandomItemsMin = Config.Get<int>("RandomItemsMin");
int RandomItemsMax = Config.Get<int>("RandomItemsMax");
if (winnerRewards == null || winnerRewards.Count == 0) {
return null;
// No item rewards to give
}
winnerRewards = winnerRewards.OrderBy(x => Guid.NewGuid()).ToList();
int numberOfItemsToGive = new System.Random().Next(RandomItemsMin, RandomItemsMax + 1);
StringBuilder rewardMessageBuilder = new StringBuilder();
int itemsGiven = 0;
foreach (var rewarddata in winnerRewards) {
if (itemsGiven >= numberOfItemsToGive) {
break;
}
string shortname = rewarddata.ContainsKey("shortname") ? rewarddata["shortname"].ToString() : null;
int minQuantity = rewarddata.ContainsKey("minQuantity") ? Convert.ToInt32(rewarddata["minQuantity"]) : 1;
int maxQuantity = rewarddata.ContainsKey("maxQuantity") ? Convert.ToInt32(rewarddata["maxQuantity"]) : 1;
int randomizedQuantity = new System.Random().Next(minQuantity, maxQuantity + 1);
ulong skinID = rewarddata.ContainsKey("skinid") ? Convert.ToUInt64(rewarddata["skinid"]) : 0;
string name = rewarddata.ContainsKey("name") ? rewarddata["name"].ToString() : null;
if (!string.IsNullOrEmpty(shortname) && randomizedQuantity > 0) {
ItemDefinition itemDef = ItemManager.FindItemDefinition(shortname);
if (itemDef != null) {
Item item;
if (skinID > 0) {
item = ItemManager.CreateByItemID(itemDef.itemid, randomizedQuantity, skinID);
}
else {
item = ItemManager.Create(itemDef, randomizedQuantity);
}
if (item != null) {
string displayName = !string.IsNullOrEmpty(name) ? name : itemDef.displayName.english;
if (!string.IsNullOrEmpty(name)) {
item.name = name;
}
player.GiveItem(item);
string rewardMessage = lang.GetMessage("RewardMessage", this);
string formattedRewardMessage = string.Format(rewardMessage, randomizedQuantity, displayName);
rewardMessageBuilder.AppendLine(formattedRewardMessage);
itemsGiven++;
}
}
}
}
if (itemsGiven > 0) {
if (Config.Get<bool>("EnableLootTable")) {
string playerEarnedMessage = lang.GetMessage("PlayerEarnedMessage", this);
return string.Format(playerEarnedMessage, player.displayName,
// ... [Code preview truncated due to length. Full source in attached file] ...