using Newtonsoft.Json; using Oxide.Core; using Oxide.Core.Configuration; using Oxide.Core.Plugins; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Oxide.Plugins { [Info("EventHelper", "imthenewguy", "1.0.0")] [Description("A plugin that devs can use to help get players into events.")] class EventHelper : RustPlugin { #region Config private Configuration config; public class Configuration { [JsonProperty("A list of items that cannot be taken into an event")] public List black_listed_items = new List() { "cassette", " cassette.medium", "cassette.short", "fun.casetterecorder", "boombox" }; [JsonProperty("The command to recover items")] public string redeem_items_command = "recoveritems"; [JsonProperty("How often should we cycle through events")] public float event_cycle_timer = 7200f; public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonConvert.DeserializeObject>(ToJson()); } protected override void LoadDefaultConfig() => config = new Configuration(); protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) { throw new JsonException(); } if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys)) { PrintToConsole("Configuration appears to be outdated; updating and saving"); SaveConfig(); } } catch { PrintToConsole($"Configuration file {Name}.json is invalid; using defaults"); LoadDefaultConfig(); } } protected override void SaveConfig() { PrintToConsole($"Configuration changes saved to {Name}.json"); Config.WriteObject(config, true); } #endregion #region classes public Dictionary Events = new Dictionary(); public Dictionary EventRunning = new Dictionary(); public class EventInfo { public bool automatic_start; public bool strip_items; public bool leaves_event_on_death; public bool full_health_on_join; public bool give_items_back_automatically; public bool full_metabolism_on_join; public Vector3 teleport_destination; public List participants = new List(); public bool manually_started; public string[] black_listed_commands; public ExternalPluginSettings external_plugin_settings = new ExternalPluginSettings(false, false, false, false, false, false); public EventInfo(bool automatic_start, bool strip_items, bool leaves_event_on_death, bool full_health_on_join, bool give_items_back_on_respawn, bool full_metabolism_on_join, Vector3 teleport_destination) { this.automatic_start = automatic_start; this.strip_items = strip_items; this.leaves_event_on_death = leaves_event_on_death; this.full_health_on_join = full_health_on_join; this.give_items_back_automatically = give_items_back_on_respawn; this.full_metabolism_on_join = full_metabolism_on_join; this.teleport_destination = teleport_destination; } public class ExternalPluginSettings { public bool canDropBackpack; public bool canEraseBackpack; public bool canOpenBackpack; public bool canBackpackAcceptItem; public bool canRedeemKit; public bool CanLoseXP; public ExternalPluginSettings(bool canDropBackpack, bool canEraseBackpack, bool canOpenBackpack, bool canBackpackAcceptItem, bool canRedeemKit, bool CanLoseXP) { this.canDropBackpack = canDropBackpack; this.canEraseBackpack = canEraseBackpack; this.canOpenBackpack = canOpenBackpack; this.canBackpackAcceptItem = canBackpackAcceptItem; this.canRedeemKit = canRedeemKit; this.CanLoseXP = CanLoseXP; } } } public enum ContainerType { Main, Belt, Wear } public class ItemInfo { public string shortname; public ulong skin; public int amount; public float condition; public float maxCondition; public int ammo; public string ammotype; public ContainerType container; public int position; public int frequency; public KeyInfo instanceData; public class KeyInfo { public bool ShouldPool; public int dataInt; public int blueprintTarget; public int blueprintAmount; public uint subEntity; } public ItemInfo[] contents; public string text; public string name; } #endregion #region Data PlayerEntity pcdData; private DynamicConfigFile PCDDATA; const string perm_admin = "eventhelper.admin"; void Init() { PCDDATA = Interface.Oxide.DataFileSystem.GetFile(this.Name); LoadData(); permission.RegisterPermission(perm_admin, this); } void Unload() { cmd.RemoveChatCommand(config.redeem_items_command, this); foreach (var kvp in pcdData.pEntity) { kvp.Value.AtEvent = false; kvp.Value.Event = null; } foreach (var e in Events.ToList()) { EMRemoveEvent(e.Key); } SaveData(); } void SaveData() { PCDDATA.WriteObject(pcdData); } void LoadData() { try { pcdData = Interface.Oxide.DataFileSystem.ReadObject(this.Name); } catch { Puts("Couldn't load player data, creating new Playerfile"); pcdData = new PlayerEntity(); } } class PlayerEntity { public Dictionary pEntity = new Dictionary(); } class PCDInfo { public string Event; public float Health; public float Food; public float Water; public bool AtEvent; public Vector3 Location; public List Items = new List(); } [PluginReference] private Plugin Backpacks, NoEscape; #endregion; #region Localization protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { ["test"] = "This is a plain text test message", ["test2"] = "This is a message with a parameter displayName: {0}" }, this); } #endregion #region Methods bool CanJoinEvent(BasePlayer player) { var playerData = GetPlayer(player.userID); if (playerData.AtEvent) { PrintToChat(player, "You are already at an event."); return false; } if (HasItemsStored(player.userID) && !playerData.AtEvent) { PrintToChat(player, $"You have items stored that must be claimed before joining another event. Type /{config.redeem_items_command} to recover your items (doing this will delete your current inventory)."); return false; } if (player.inventory.crafting.queue.Count != 0) { PrintToChat(player, "You must stop crafting in order to join this event."); return false; } if (NoEscape != null && Convert.ToBoolean(NoEscape.Call("IsEscapeBlocked", player.UserIDString))) { PrintToChat(player, "You cannot join the game while you are escape blocked."); return false; } if (HasProhibitedItems(player)) return false; return true; } bool StorePlayerItems(BasePlayer player) { var playerData = GetPlayer(player.userID); foreach (var item in player.inventory.AllItems()) { var item_save = new ItemInfo() { shortname = item.info.shortname, container = GetContainer(player, item.parent), position = item.position, amount = item.amount, ammo = item.GetHeldEntity() is BaseProjectile ? (item.GetHeldEntity() as BaseProjectile).primaryMagazine.contents : item.GetHeldEntity() is FlameThrower ? (item.GetHeldEntity() as FlameThrower).ammo : 0, ammotype = (item.GetHeldEntity() as BaseProjectile)?.primaryMagazine.ammoType.shortname ?? null, skin = item.skin, condition = item.condition, maxCondition = item.maxCondition, contents = item.contents?.itemList.Select(item1 => new ItemInfo { shortname = item1.info.shortname, amount = item1.amount, condition = item1.condition, maxCondition = item1.maxCondition }).ToArray(), }; if (item.instanceData != null) { item_save.instanceData = new ItemInfo.KeyInfo() { ShouldPool = item.instanceData.ShouldPool, dataInt = item.instanceData.dataInt, blueprintTarget = item.instanceData.blueprintTarget, blueprintAmount = item.instanceData.blueprintAmount, subEntity = item.instanceData.subEntity }; } if (item.text != null) item_save.text = item.text; if (item.name != null) item_save.name = item.name; playerData.Items.Add(item_save); } player.inventory.Strip(); return true; } bool RestoreItems(BasePlayer player, bool strip = true) { if (player.IsDead() || !player.IsConnected) return false; var playerData = GetPlayer(player.userID); if (strip) player.inventory.Strip(); if (playerData.Items == null || playerData.Items.Count == 0) return true; List unpositioned_items = new List(); foreach (var saved_item in playerData.Items) { var item = ItemManager.CreateByName(saved_item.shortname, saved_item.amount, saved_item.skin); if (saved_item.name != null) item.name = saved_item.name; item.condition = saved_item.condition; item.maxCondition = saved_item.maxCondition; BaseProjectile weapon = item.GetHeldEntity() as BaseProjectile; if (weapon != null) { if (!string.IsNullOrEmpty(saved_item.ammotype)) weapon.primaryMagazine.ammoType = ItemManager.FindItemDefinition(saved_item.ammotype); weapon.primaryMagazine.contents = saved_item.ammo; } FlameThrower flameThrower = item.GetHeldEntity() as FlameThrower; if (flameThrower != null) flameThrower.ammo = saved_item.ammo; if (saved_item.contents != null) { foreach (ItemInfo contentData in saved_item.contents) { Item newContent = ItemManager.CreateByName(contentData.shortname, contentData.amount); if (newContent != null) { newContent.condition = contentData.condition; newContent.MoveToContainer(item.contents); } } } if (saved_item.instanceData != null) { item.instanceData = new ProtoBuf.Item.InstanceData(); item.instanceData.ShouldPool = saved_item.instanceData.ShouldPool; item.instanceData.dataInt = saved_item.instanceData.dataInt; item.instanceData.blueprintTarget = saved_item.instanceData.blueprintTarget; item.instanceData.blueprintAmount = saved_item.instanceData.blueprintAmount; item.instanceData.subEntity = saved_item.instanceData.subEntity; } if (saved_item.text != null) item.text = saved_item.text; switch (saved_item.container) { case ContainerType.Belt: item.MoveToContainer(player.inventory.containerBelt, saved_item.position); break; case ContainerType.Wear: item.MoveToContainer(player.inventory.containerWear, saved_item.position); break; case ContainerType.Main: item.MoveToContainer(player.inventory.containerMain, saved_item.position); break; default: unpositioned_items.Add(item); break; } } if (unpositioned_items.Count > 0) { foreach (var item in unpositioned_items) { player.GiveItem(item); } } playerData.Items.Clear(); return true; } ContainerType GetContainer(BasePlayer player, ItemContainer container) { if (container.uid == player.inventory.containerBelt.uid) return ContainerType.Belt; else if (container.uid == player.inventory.containerWear.uid) return ContainerType.Wear; else return ContainerType.Main; } bool HasProhibitedItems(BasePlayer player) { if (config.black_listed_items == null || config.black_listed_items.Count == 0 || player.inventory.AllItems().Length == 0) return false; foreach (var item in player.inventory.AllItems()) { if (config.black_listed_items.Contains(item.info.shortname)) { PrintToChat(player, $"You cannot bring a {item.info.displayName.english} into a minigame."); return true; } if (item.contents != null && item.contents.itemList != null && item.contents.itemList.Count > 1) { foreach (var sub_item in item.contents.itemList) { if (config.black_listed_items.Contains(sub_item.info.shortname)) { PrintToChat(player, $"You cannot bring a {sub_item.info.displayName.english} into a minigame. This was found inside of your {item.info.displayName.english}"); return true; } } } } return false; } private bool HasItemsStored(ulong id) { var playerData = GetPlayer(id); if (playerData.Items.Count == 0) return false; return true; } bool IsPlayerSetup(ulong id) { return pcdData.pEntity.ContainsKey(id); } PCDInfo GetPlayer(ulong id) { if (!pcdData.pEntity.ContainsKey(id)) pcdData.pEntity.Add(id, new PCDInfo()); return pcdData.pEntity[id]; } void RestoreStats(BasePlayer player) { if (player.IsDead() || !player.IsConnected) return; var playerData = GetPlayer(player.userID); if (playerData.Food > 0) player.metabolism.calories.SetValue(playerData.Food); if (playerData.Water > 0) player.metabolism.hydration.SetValue(playerData.Water); if (playerData.Health > 0) player.SetHealth(playerData.Health); if (playerData.Location != Vector3.zero) TeleportToEvent(player, playerData.Location); playerData.Location = Vector3.zero; playerData.Food = 0; playerData.Water = 0; playerData.Health = 0; player.State.unHostileTimestamp = Network.TimeEx.currentTimestamp; player.MarkHostileFor(0); } void StorePlayerInfo(BasePlayer player, bool StoreHealth, bool StoreStats) { var playerData = GetPlayer(player.userID); if (StoreStats) { playerData.Food = player.metabolism.calories.value; playerData.Water = player.metabolism.hydration.value; player.metabolism.calories.SetValue(player.metabolism.calories.max); player.metabolism.hydration.SetValue(player.metabolism.hydration.max); } if (StoreHealth) { playerData.Health = player.health; player.SetHealth(100f); } playerData.Location = player.transform.position; } void TeleportToEvent(BasePlayer player, Vector3 loc) { if (loc == Vector3.zero) return; Player.Teleport(player, loc); player.StartSleeping(); player.SetPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot, true); player.ClientRPCPlayer(null, player, "StartLoading"); player.SendEntityUpdate(); player.UpdateNetworkGroup(); player.SendNetworkUpdateImmediate(false); } void RemoveFromEvent(BasePlayer player, string eventName) { if (!IsPlayerSetup(player.userID)) return; var playerData = GetPlayer(player.userID); playerData.AtEvent = false; EventInfo ei; if (eventName != null) Events.TryGetValue(eventName, out ei); else ei = null; ei.participants.Remove(player); } #endregion #region Event Stuff string LastEvent; List automatic_Events = new List(); void StartNextEvent() { if (Events == null || Events.Count == 0 || automatic_Events.Count == 0) return; if (LastEvent != null && Events.ContainsKey(LastEvent) && !Events[LastEvent].manually_started) EMEndEvent(LastEvent); if (LastEvent == null) { Interface.CallHook("EMStartNextEvent", automatic_Events.First()); LastEvent = automatic_Events.First(); } else { var foundLastEvent = false; var setNewEvent = false; foreach (var e in automatic_Events) { if (Events[e].manually_started) continue; if (foundLastEvent) { Interface.CallHook("EMStartNextEvent", e); LastEvent = e; setNewEvent = true; break; } else if (e == LastEvent) foundLastEvent = true; } if (!setNewEvent) { var e = automatic_Events.First(); if (!Events[e].manually_started) { Interface.CallHook("EMStartNextEvent", e); LastEvent = e; } else Puts("Skipping event start due to manual start."); } } } [HookMethod("EMManuallyStarted")] void EMManuallyStarted(string eventName) { EventInfo ei; if (Events.TryGetValue(eventName, out ei)) { ei.manually_started = true; Puts($"Manually started event {eventName}"); } } [HookMethod("EMRemoveEvent")] void EMRemoveEvent(string eventName) { Interface.CallHook("EMEndGame", eventName); EventInfo ei; if (Events.TryGetValue(eventName, out ei)) { if (ei.participants.Count > 0) { foreach (var player in ei.participants.ToList()) { EMPlayerLeaveEvent(player, eventName); } } if (ei.automatic_start) automatic_Events.Remove(eventName); } Events.Remove(eventName); Puts($"Removed Event: {eventName}"); } [HookMethod("EMCreateEvent")] private void EMCreateEvent(string eventName, bool automatic_start, bool stripItems, bool leaves_event_on_death, bool full_health_on_join, bool give_items_back_on_respawn, bool full_metabolism_on_join, Vector3 teleport_destination) { if (Events.ContainsKey(eventName)) EMRemoveEvent(eventName); EventInfo ei; Events.Add(eventName, ei = new EventInfo(automatic_start, stripItems, leaves_event_on_death, full_health_on_join, give_items_back_on_respawn, full_metabolism_on_join, teleport_destination)); Puts($"Setup Event: {eventName}"); if (automatic_start && !automatic_Events.Contains(eventName)) { automatic_Events.Add(eventName); } } [HookMethod("EMUpdateLobby")] private void EMUpdateLobby(string eventName, Vector3 pos) { EventInfo ei; if (!Events.TryGetValue(eventName, out ei)) return; ei.teleport_destination = pos; } [HookMethod("EMStartEvent")] private void EMStartEvent(string eventName) { EventInfo ei; if (!Events.TryGetValue(eventName, out ei)) return; if (!EventRunning.ContainsKey(eventName)) EventRunning.Add(eventName, true); else EventRunning[eventName] = true; } [HookMethod("EMEndEvent")] private void EMEndEvent(string eventName) { if (EventRunning.ContainsKey(eventName)) { EventInfo ei; if (eventName == null || !Events.TryGetValue(eventName, out ei)) return; if (ei.participants.Count > 0) { foreach (var player in ei.participants.ToList()) { EMPlayerLeaveEvent(player, eventName); } } ei.participants.Clear(); ei.manually_started = false; EventRunning.Remove(eventName); Interface.CallHook("EMEndGame", eventName); Puts("Ended the event"); } } [HookMethod("EMEnrollPlayer")] private bool EMEnrollPlayer(BasePlayer player, string eventName) { EventInfo ei; if (!Events.TryGetValue(eventName, out ei)) { Puts($"{eventName} has not been setup."); return false; } if (ei.participants.Contains(player)) { PrintToChat(player, "You are already enrolled in this event."); return false; } if (!CanJoinEvent(player)) return false; if (ei.strip_items && !StorePlayerItems(player)) { RestoreItems(player); pcdData.pEntity.Remove(player.userID); PrintToChat(player, "Failed to strip your items."); return false; } StorePlayerInfo(player, ei.full_health_on_join, ei.full_metabolism_on_join); ei.participants.Add(player); TeleportToEvent(player, ei.teleport_destination); var playerData = GetPlayer(player.userID); playerData.AtEvent = true; playerData.Event = eventName; return true; } [HookMethod("EMPlayerLeaveEvent")] private void EMPlayerLeaveEvent(BasePlayer player, string eventName = null) { if (!IsPlayerSetup(player.userID)) { return; } var playerData = GetPlayer(player.userID); EventInfo ei; if (eventName != null) Events.TryGetValue(eventName, out ei); else ei = null; RestoreStats(player); if (ei == null || (ei.give_items_back_automatically && playerData.Items.Count > 0)) { if (RestoreItems(player)) { pcdData.pEntity.Remove(player.userID); } } else { player.inventory.Strip(); if (playerData.Items.Count > 0) PrintToChat(player, $"You must type /{config.redeem_items_command} to recover them (doing this will delete your current inventory)."); } ei?.participants.Remove(player); } //Related to chat command void RestoreItemsAndStats(BasePlayer player, bool respawned = false) { if (!IsPlayerSetup(player.userID)) return; var playerData = GetPlayer(player.userID); if (playerData.AtEvent) { PrintToChat(player, "You cannot recover your items until you leave the event."); return; } RestoreStats(player); if (RestoreItems(player, respawned ? true : false)) { pcdData.pEntity.Remove(player.userID); } } #endregion #region API Stuff [HookMethod("EMExternalPluginSettings")] void EMExternalPluginSettings(string eventName, bool canDropBackpack = false, bool canEraseBackpack = false, bool canOpenBackpack = false, bool canBackpackAcceptItem = false, bool canRedeemKit = false, bool CanLoseXP= false) { EventInfo ei; if (!Events.TryGetValue(eventName, out ei)) { Puts($"Event: {eventName} has not been setup. Must call EMCreateEvent before EMExternalPluginSettings."); return; } ei.external_plugin_settings = new EventInfo.ExternalPluginSettings(canDropBackpack, canEraseBackpack, canOpenBackpack, canBackpackAcceptItem, canRedeemKit, CanLoseXP); } [HookMethod("EMBlackListCommands")] void EMBlackListCommands(string eventName, string[] commands) { if (commands == null) return; EventInfo ei; if (!Events.TryGetValue(eventName, out ei)) { Puts($"Event: {eventName} has not been setup. Must call EMCreateEvent before EMBlackListCommands."); return; } ei.black_listed_commands = commands; Puts($"Registered {ei.black_listed_commands.Length} black listed commands."); } [HookMethod("EMIsParticipating")] bool EMIsParticipating(BasePlayer player, string eventName) { EventInfo ei; if (!Events.TryGetValue(eventName, out ei)) return false; return ei.participants.Contains(player); } [HookMethod("EMAtEvent")] bool EMAtEvent(ulong id) { if (IsPlayerSetup(id) && pcdData.pEntity[id].AtEvent) return true; return false; } object canRedeemKit(BasePlayer player) { if (IsPlayerSetup(player.userID)) { var playerData = GetPlayer(player.userID); if (!playerData.AtEvent || string.IsNullOrEmpty(playerData.Event)) return null; if (!Events[playerData.Event].external_plugin_settings.canRedeemKit) return false; } return null; } object CanDropBackpack(ulong backpackOwnerID, Vector3 position) { if (IsPlayerSetup(backpackOwnerID)) { var playerData = GetPlayer(backpackOwnerID); if (!playerData.AtEvent || string.IsNullOrEmpty(playerData.Event)) return null; if (!Events[playerData.Event].external_plugin_settings.canDropBackpack) return false; } return null; } string CanOpenBackpack(BasePlayer player, ulong backpackOwnerID) { if (IsPlayerSetup(backpackOwnerID)) { var playerData = GetPlayer(backpackOwnerID); if (!playerData.AtEvent || string.IsNullOrEmpty(playerData.Event)) return null; if (!Events[playerData.Event].external_plugin_settings.canOpenBackpack) return "You cannot open your backpack during an event."; } return null; } object CanBackpackAcceptItem(ulong backpackOwnerID, ItemContainer backpackContainer, Item item) { if (IsPlayerSetup(backpackOwnerID)) { var playerData = GetPlayer(backpackOwnerID); if (!playerData.AtEvent || string.IsNullOrEmpty(playerData.Event)) return null; if (!Events[playerData.Event].external_plugin_settings.canBackpackAcceptItem) return false; } return null; } object CanEraseBackpack(ulong backpackOwnerID) { if (IsPlayerSetup(backpackOwnerID)) { var playerData = GetPlayer(backpackOwnerID); if (!playerData.AtEvent || string.IsNullOrEmpty(playerData.Event)) return null; if (!Events[playerData.Event].external_plugin_settings.canEraseBackpack) return false; } return null; } object STOnLoseXP(BasePlayer player) { if (IsPlayerSetup(player.userID)) { var playerData = GetPlayer(player.userID); if (!playerData.AtEvent || string.IsNullOrEmpty(playerData.Event)) return null; if (!Events[playerData.Event].external_plugin_settings.CanLoseXP) return false; } return null; } #endregion #region Hooks void OnServerSave() { SaveData(); } void OnNewSave(string filename) { pcdData.pEntity.Clear(); SaveData(); } object OnPlayerCommand(BasePlayer player, string command, string[] args) { if (IsPlayerSetup(player.userID)) { var playerData = GetPlayer(player.userID); EventInfo ei; if (Events.TryGetValue(playerData.Event, out ei) && ei.black_listed_commands.Contains(command)) { PrintToChat(player, $"You cannot use the {command} command at this event."); return false; } } return null; } void OnPlayerRespawned(BasePlayer player) { if (player.IsNpc || !player.userID.IsSteamId()) return; if (!IsPlayerSetup(player.userID)) return; var playerData = GetPlayer(player.userID); if (playerData.AtEvent || playerData.Items == null || playerData.Items.Count == 0) return; if (playerData.Event != null && Events.ContainsKey(playerData.Event)) { RestoreItemsAndStats(player, true); } else PrintToChat(player, $"You have unclaimed items. Type /{config.redeem_items_command} to recover them."); } void OnServerInitialized(bool initial) { cmd.AddChatCommand(config.redeem_items_command, this, "RestoreItemsAndStats"); timer.Every(config.event_cycle_timer, () => { StartNextEvent(); }); } void OnPlayerDeath(BasePlayer player, HitInfo info) { if (!IsPlayerSetup(player.userID)) return; var playerData = GetPlayer(player.userID); if (playerData.Event == null || !Events.ContainsKey(playerData.Event) || !Events[playerData.Event].leaves_event_on_death) return; RemoveFromEvent(player, playerData.Event); } #endregion } }