using Oxide.Core.Libraries.Covalence; using System; using System.Collections.Generic; using Newtonsoft.Json; using UnityEngine; using Oxide.Game.Rust.Cui; using Oxide.Core; using Oxide.Core.Plugins; namespace Oxide.Plugins { [Info("CustomStatusConditions", "mr01sam", "1.0.0")] [Description("Adds configuration options to add custom status conditions to Injuries And Diseases.")] internal class CustomStatusConditions : CovalencePlugin { #region Plugin Init [PluginReference] private Plugin InjuriesAndDiseases; [PluginReference] private Plugin ImageLibrary; void OnPluginLoaded(Plugin name) { /* * ## TIP ## * * THIS IS VERY IMPORTANT!!! * * You must detect if InjuriesAndDiseases has been reloaded, and if so, you will need to call the hook to * create your conditions again. * * If you do not do this, your condition will not be reloaded when InjuriesAndDiseases is reloaded. * */ if (name.Name == "InjuriesAndDiseases") { CreateConditions(); } } private void OnServerInitialized() { if (!InjuriesAndDiseases?.IsLoaded ?? true) { PrintError("The plugin InjuriesAndDiseases is required."); return; } RegisterLocalizationMessages(); RegisterImages(); CreateConditions(); Subscribe(nameof(OnEntityTakeDamage)); Subscribe(nameof(OnItemAction)); } void Unload() { Unsubscribe(nameof(OnEntityTakeDamage)); Unsubscribe(nameof(OnItemAction)); } #endregion #region Oxide Hooks void OnEntityTakeDamage(BasePlayer basePlayer, HitInfo hitInfo) { if (basePlayer == null || hitInfo == null || basePlayer.IsDead()) { return; } var initiator = hitInfo?.Initiator; if (initiator == null) { return; } foreach (var customCondition in config.CustomStatusConditions) { /* * ## TIP ## * * This code will inflict the player if attacked by certain entities. */ if (customCondition.InflictionEntities != null) { foreach (var kvp in customCondition.InflictionEntities) { if (initiator.ShortPrefabName == kvp.Key) { var chance = kvp.Value; var roll = UnityEngine.Random.Range(0f, 1f); if (chance >= roll) { InjuriesAndDiseases.Call("SetCondition", basePlayer, customCondition.ID, true); } } } } } } void OnItemAction(Item item, string action, BasePlayer basePlayer) { if (basePlayer == null || item == null) { return; } foreach (var customCondition in config.CustomStatusConditions) { /* * ## TIP ## * * This code handles all the items that can cause the player to become inflicted. */ if (customCondition.InflictionItems != null) { var chance = GetItemChance(customCondition.InflictionItems, item); if (chance.HasValue) { var roll = UnityEngine.Random.Range(0f, 1f); if (chance >= roll) { InjuriesAndDiseases.Call("SetCondition", basePlayer, customCondition.ID, true); } } } /* * ## TIP ## * * This code handles all the items that can cure the player of a condition. */ if (customCondition.CureItems != null) { var chance = GetItemChance(customCondition.CureItems, item); if (chance.HasValue) { var roll = UnityEngine.Random.Range(0f, 1f); if (chance >= roll) { InjuriesAndDiseases.Call("RemoveCondition", basePlayer, customCondition.ID, true); } } } } } #endregion #region Custom Conditions Setup private void RegisterLocalizationMessages() { /* * ## TIP ## * * Custom condition plugins are responsible for maintaining localization messages. * At minimum, there MUST exist the four messages being set below, as IaD will attempt to access them. */ foreach (var customCondition in config.CustomStatusConditions) { LocalizationMessages[$"{customCondition.ID}"] = customCondition.LocalizationConfig.NameEnglishText; LocalizationMessages[$"{customCondition.ID} reveal"] = customCondition.LocalizationConfig.RevealEnglishText; LocalizationMessages[$"{customCondition.ID} diagnosis"] = customCondition.LocalizationConfig.DiagnosisEnglishText; LocalizationMessages[$"{customCondition.ID} cured"] = customCondition.LocalizationConfig.CuredEnglishText; var allActions = new List(); if (customCondition.BeginActions != null) { allActions.Add(customCondition.BeginActions); } if (customCondition.IntervalActions != null) { allActions.Add(customCondition.IntervalActions); } foreach (var action in allActions) { if (action.MessagePlayer != null) { var message = action.MessagePlayer; LocalizationMessages[message.LocalizationID] = message.DefaultEnglishText; } } } LoadDefaultMessages(); } private void RegisterImages() { /* * ## TIP ## * * Custom condition plugins must add the icon image for the custom condition. * The icon image name should be in the format '.icon' as show below. */ foreach (var customCondition in config.CustomStatusConditions) { ImageLibrary.Call("AddImage", customCondition.IconUrl, $"{customCondition.ID}.icon", 0UL); } } private void CreateConditions() { foreach(var customCondition in config.CustomStatusConditions) { Action onBegin = (basePlayer) => { /* * ## TIP ## * * Any code here will occur when the player first is inflicted with the condition. * */ if (customCondition.BeginActions != null) { DoActions(customCondition.BeginActions, basePlayer); } }; Action onInterval = (basePlayer) => { /* ## TIP ## * * Any code here will occur whenever the symtom interval occurs. * */ if (customCondition.IntervalActions != null) { DoActions(customCondition.IntervalActions, basePlayer); } }; InjuriesAndDiseases.Call("CreateCondition", this, customCondition.ID, // Unique identifier for this condition (cannot conflict with another condition) $"{customCondition.ID}.icon", // (Optional) ImageLibary icon name customCondition.MinIntervalSeconds, // (Optional) The min number of seconds for the symptom interval loop. customCondition.MaxIntervalSeconds, // (Optional) The max number of seconds for the symptom interval loop. customCondition.MinDurationSeconds, // Min duration for the condition. customCondition.MaxDurationSeconds, // Max duration for the condition. customCondition.ShowDuration, // Set to 'true' if the duration countdown should be shown. customCondition.ShowIndicator, // Set to 'true' if this status should be visible at all. onBegin, // (Optional) Method that contains the code that will run when the player first is inflicted by the condition. onInterval // (Optional) Method that contains the code that runs whenever the symptom interval occurs (if applicable). ); } } #endregion #region Helper Methods private float? GetItemChance(Dictionary dict, Item item) { if (dict == null || item == null) { return null; } float value; if (dict.TryGetValue($"{item.info.shortname}#{item.skin}", out value) || dict.TryGetValue(item.info.shortname, out value)) { return value; } return null; } private void DoActions(ActionConfig action, BasePlayer basePlayer) { if (action.DamagePlayer != null) { ApplyMetabolismChanges(action.DamagePlayer, basePlayer, true); } if (action.HealPlayer != null) { ApplyMetabolismChanges(action.HealPlayer, basePlayer, false); } if (action.MessagePlayer != null) { Message(basePlayer, action.MessagePlayer.LocalizationID); } if (action.PlayEffects != null) { foreach(var effect in action.PlayEffects) { PlayEffect(basePlayer, effect.EffectPrefab, effect.Private); } } } private void ApplyMetabolismChanges(StatModsConfig statsMod, BasePlayer basePlayer, bool inverted) { var mod = inverted ? -1 : 1; if (statsMod.Health != null) { if (inverted) { basePlayer.Hurt(GetRandomValue(statsMod.Health), Rust.DamageType.Poison); } else { basePlayer.Heal(GetRandomValue(statsMod.Health)); } } if (statsMod.Calories != null) { basePlayer.metabolism.ApplyChange(MetabolismAttribute.Type.Calories, GetRandomValue(statsMod.Calories, mod), 1); } if (statsMod.Hydration != null) { basePlayer.metabolism.ApplyChange(MetabolismAttribute.Type.Hydration, GetRandomValue(statsMod.Hydration, mod), 1); } if (statsMod.Radiation != null) { basePlayer.metabolism.ApplyChange(MetabolismAttribute.Type.Radiation, GetRandomValue(statsMod.Radiation, -mod), 1); } if (statsMod.Bleeding != null) { basePlayer.metabolism.ApplyChange(MetabolismAttribute.Type.Bleeding, GetRandomValue(statsMod.Bleeding, -mod), 1); } } private void Message(BasePlayer basePlayer, string lang, params object[] args) { ConsoleNetwork.SendClientCommand(basePlayer.Connection, "chat.add", 2, config.MessagesIconID, Lang(lang, basePlayer.UserIDString, args)); } public void PlayEffect(BasePlayer player, string effectString, bool local) { if (string.IsNullOrEmpty(effectString)) { return; } var effect = new Effect(effectString, player, 0, Vector3.zero, Vector3.forward); if (local) EffectNetwork.Send(effect, player.net.connection); else EffectNetwork.Send(effect); } private int GetRandomValue(MinMaxConfig mm, int mod=1) { return mod * UnityEngine.Random.Range(mm.MinAmount, mm.MaxAmount); } #endregion #region Configuration private Configuration config; public class Configuration { [JsonProperty(PropertyName = "Message Icon ID")] public long MessagesIconID = 0; [JsonProperty(PropertyName = "Custom Status Conditions")] public CustomConditionConfig[] CustomStatusConditions = new CustomConditionConfig[] { new CustomConditionConfig { ID = "terrible", IconUrl = "", LocalizationConfig = new LocalizationConfig { NameEnglishText = "Feel Terrible", RevealEnglishText = "You have contracted a terrible condition.", DiagnosisEnglishText = "The patient {0} seems to be suffering from a terrible condition. Strangely enough, consuming cactus flesh is a well known treatment for it.", CuredEnglishText = "You are cured of your terrible condition." }, InflictionItems = new Dictionary { ["can.tuna"] = 1f }, CureItems = new Dictionary { ["cactusflesh"] = 1f }, InflictionEntities = new Dictionary { ["wolf"] = 0.5f }, MinIntervalSeconds = 10, MaxIntervalSeconds = 20, MinDurationSeconds = 400, MaxDurationSeconds = 600, BeginActions = new ActionConfig { DamagePlayer = new StatModsConfig { Health = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 }, Calories = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 }, Hydration = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 }, Radiation = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 }, Bleeding = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 } }, MessagePlayer = new MessageConfig { LocalizationID = "bad time", DefaultEnglishText = "You are in for a bad time..." }, PlayEffects = new List() { new EffectConfig { EffectPrefab = "", Private = true } } }, IntervalActions = new ActionConfig { DamagePlayer = new StatModsConfig { Health = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 }, Calories = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 }, Hydration = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 }, Radiation = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 }, Bleeding = new MinMaxConfig { MinAmount = 1, MaxAmount = 2 } }, MessagePlayer = new MessageConfig { LocalizationID = "hurt", DefaultEnglishText = "Everything hurts!" }, PlayEffects = new List() { new EffectConfig { EffectPrefab = "", Private = true } } } } }; } public class CustomConditionConfig { [JsonProperty(PropertyName = "ID")] public string ID; [JsonProperty(PropertyName = "Icon URL")] public string IconUrl; [JsonProperty(PropertyName = "Enabled")] public bool Enabled = true; [JsonProperty(PropertyName = "Localization")] public LocalizationConfig LocalizationConfig = new LocalizationConfig(); [JsonProperty(PropertyName = "Infliction Items")] public Dictionary InflictionItems = new Dictionary(); [JsonProperty(PropertyName = "Infliction Entities")] public Dictionary InflictionEntities = new Dictionary(); [JsonProperty(PropertyName = "Interval Min Seconds")] public int? MinIntervalSeconds; [JsonProperty(PropertyName = "Interval Max Seconds")] public int? MaxIntervalSeconds; [JsonProperty(PropertyName = "Duration Min Seconds")] public int MinDurationSeconds; [JsonProperty(PropertyName = "Duration Max Seconds")] public int MaxDurationSeconds; [JsonProperty(PropertyName = "Cure Items")] public Dictionary CureItems = new Dictionary(); [JsonProperty(PropertyName = "Show Duration")] public bool ShowDuration = true; [JsonProperty(PropertyName = "Show Indicator")] public bool ShowIndicator = true; [JsonProperty(PropertyName = "Do on Interval")] public ActionConfig IntervalActions = new ActionConfig(); [JsonProperty(PropertyName = "Do on Begin")] public ActionConfig BeginActions = new ActionConfig(); } public class ActionConfig { [JsonProperty(PropertyName = "Damage Player Stats")] public StatModsConfig DamagePlayer = new StatModsConfig(); [JsonProperty(PropertyName = "Heal Player Stats")] public StatModsConfig HealPlayer = new StatModsConfig(); [JsonProperty(PropertyName = "Play Effects")] public List PlayEffects = new List(); [JsonProperty(PropertyName = "Message Player")] public MessageConfig MessagePlayer = new MessageConfig(); } public class MessageConfig { [JsonProperty(PropertyName = "Localization ID")] public string LocalizationID; [JsonProperty(PropertyName = "Default English Text")] public string DefaultEnglishText; } public class LocalizationConfig { [JsonProperty(PropertyName = "Name English Text")] public string NameEnglishText; [JsonProperty(PropertyName = "Reveal English Text")] public string RevealEnglishText; [JsonProperty(PropertyName = "Diagnosis English Text")] public string DiagnosisEnglishText; [JsonProperty(PropertyName = "Cured English Text")] public string CuredEnglishText; } public class StatModsConfig { [JsonProperty(PropertyName = "Health")] public MinMaxConfig Health = new MinMaxConfig(); [JsonProperty(PropertyName = "Calories")] public MinMaxConfig Calories = new MinMaxConfig(); [JsonProperty(PropertyName = "Hydration")] public MinMaxConfig Hydration = new MinMaxConfig(); [JsonProperty(PropertyName = "Radiation")] public MinMaxConfig Radiation = new MinMaxConfig(); [JsonProperty(PropertyName = "Bleeding")] public MinMaxConfig Bleeding = new MinMaxConfig(); } public class MinMaxConfig { public int MinAmount; public int MaxAmount; } public class EffectConfig { [JsonProperty(PropertyName = "Effect prefab")] public string EffectPrefab; [JsonProperty(PropertyName = "Private")] public bool Private; } protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) throw new Exception(); } catch { LoadDefaultConfig(); } SaveConfig(); } protected override void SaveConfig() => Config.WriteObject(config); protected override void LoadDefaultConfig() => config = new Configuration(); #endregion #region Localization private Dictionary LocalizationMessages = new Dictionary(); protected override void LoadDefaultMessages() { lang.RegisterMessages(LocalizationMessages, this); } private string Lang(string key, string userIdString, params object[] args) => string.Format(lang.GetMessage(key, this, userIdString), args); #endregion } }