/*▄▄▄ ███▄ ▄███▓ ▄████ ▄▄▄██▀▀▀▓█████▄▄▄█████▓ ▓█████▄ ▓██▒▀█▀ ██▒ ██▒ ▀█▒ ▒██ ▓█ ▀▓ ██▒ ▓▒ ▒██▒ ▄██▓██ ▓██░▒██░▄▄▄░ ░██ ▒███ ▒ ▓██░ ▒░ ▒██░█▀ ▒██ ▒██ ░▓█ ██▓▓██▄██▓ ▒▓█ ▄░ ▓██▓ ░ ░▓█ ▀█▓▒██▒ ░██▒░▒▓███▀▒ ▓███▒ ░▒████▒ ▒██▒ ░ ░▒▓███▀▒░ ▒░ ░ ░ ░▒ ▒ ▒▓▒▒░ ░░ ▒░ ░ ▒ ░░ ▒░▒ ░ ░ ░ ░ ░ ░ ▒ ░▒░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░*/ using Newtonsoft.Json; using System; using System.Collections.Generic; using UnityEngine; namespace Oxide.Plugins { [Info("Downed", "bmgjet", "1.0.4")] [Description("Allow NPC Knock Downs")] class Downed : RustPlugin { private PluginConfig config; public Dictionary downedPlayers = new Dictionary { { (ulong)0, null } }; #region Configuration private class PluginConfig { [JsonProperty(PropertyName = "Bleed out timer: ")] public int Bleedout { get; set; } [JsonProperty(PropertyName = "How long before NPC gets back up: ")] public int NPCDownTimer { get; set; } [JsonProperty(PropertyName = "Percentage for NPC to bleedout: ")] public int NPCBleedOutChance { get; set; } [JsonProperty(PropertyName = "Block NPC Looting: ")] public bool NPCNoLoot { get; set; } [JsonProperty(PropertyName = "Ignore Scarecrow: ")] public bool IgnoreScarecrow { get; set; } [JsonProperty(PropertyName = "Ignore Murderer: ")] public bool IgnoreMurderer { get; set; } [JsonProperty(PropertyName = "SFX On Downed (Delete link to disable): ")] public string[] SFX { get; set; } [JsonProperty(PropertyName = "SFX PlayTime: ")] public float SFXPlayTime { get; set; } } private PluginConfig GetDefaultConfig() { return new PluginConfig { Bleedout = 120, NPCDownTimer = 20, NPCBleedOutChance = 50, NPCNoLoot = true, IgnoreScarecrow = false, IgnoreMurderer = false, SFX = new string[] { "https://github.com/bmgjet/Stations/raw/main/Help.Me.mp3", "https://github.com/bmgjet/Downed/raw/main/Help.Me.mp3" }, SFXPlayTime = 10, }; } protected override void LoadDefaultConfig() { Config.Clear(); Config.WriteObject(GetDefaultConfig(), true); config = Config.ReadObject(); } protected override void SaveConfig() { Config.WriteObject(config, true); } #endregion #region Hooks void Init() { config = Config.ReadObject(); if (config == null) { LoadDefaultConfig(); } } void OnPlayerRecover(BasePlayer player) { removedowned(player); } private object CanLootPlayer(BasePlayer target, BasePlayer looter) { if (target == null || looter == null || !config.NPCNoLoot) return null; if (target.IsWounded() && target.IsNpc) { NextTick(looter.EndLooting); return false; } return null; } private object OnNpcTarget(NPCPlayer npc, BaseEntity entity) { if (entity == null || npc == null) return null; if (npc.IsWounded() || npc.IsIncapacitated()) { return true; } return null; } object OnPlayerWound(BasePlayer player, HitInfo info) { if (!downedPlayers.ContainsKey(player.userID)) { try { CreateSound(player, config.SFX[Convert.ToInt32(Math.Round(Convert.ToDouble(UnityEngine.Random.Range(Convert.ToSingle(0), Convert.ToSingle(config.SFX.Length - 1)))))]); } catch { } //Creates a timer to switches to crawl downedPlayers.Add(player.userID, timer.Once(5, () => { if (player == null) { return; } if (!player.IsDead()) { player.StopWounded(); //Reset the wounded state downedPlayers[player.userID] = null; //Clear this timer LongDown(player, info); //Custom wounded state with cui player.SendNetworkUpdateImmediate(); } })); } return null; //Normal operation. } object OnPlayerDeath(BasePlayer player, HitInfo info) { if (player != null) { //Check if ScareCrow if (config.IgnoreScarecrow) { var combatEntity = player as BaseCombatEntity; if (combatEntity != null && combatEntity.ShortPrefabName != "scarecrow") { return null; //Disables Downed } } //Check if Murderer if (config.IgnoreMurderer) { var combatEntity = player as BaseCombatEntity; if (combatEntity != null && combatEntity.ShortPrefabName != "murderer") { return null; //Disables Downed } } //Always enter wounded if not already. if (!downedPlayers.ContainsKey(player.userID)) { if (player.IsNpc) //Is NPC { if(player.HasParent() && player.GetParentEntity() is Tugboat) //Stop tugboat flip { return null; } HeldEntity heldEntity = player.GetHeldEntity(); //Get NPC Gun if (heldEntity != null) { BaseProjectile currentgun = heldEntity as BaseProjectile; if (currentgun != null) { currentgun.primaryMagazine.contents = 0; var reloadloop = timer.Repeat(1.8f, config.NPCDownTimer, () => { try { if (currentgun != null) { currentgun.primaryMagazine.contents = 0; //Keep unloading so cant shoot. } } catch { } }); timer.Once(config.NPCDownTimer + 5f, () => { try { if (reloadloop != null) { reloadloop.Destroy(); } } catch { } }); } } timer.Once(config.NPCDownTimer, () => { if (player == null) { return; } if (player.IsAlive()) { //Chance of bleedout if (UnityEngine.Random.Range(0f, 100f) < config.NPCBleedOutChance) { player.StopWounded(); //NPC Gets up return; } player.DieInstantly(); } }); player.BecomeWounded(info); return false; } } } return null; //Allow die } #endregion #region Code private void LongDown(BasePlayer player, HitInfo info, bool downed = true) { if (player == null) { return; } if (downed) //Down player { player.BecomeWounded(info); player.ProlongWounding(config.Bleedout); //time too bleed out player.SendNetworkUpdate(); return; } player.StopWounded(); //End downed player. player.SendNetworkUpdate(); } public void removedowned(BasePlayer player) { if (downedPlayers.ContainsKey(player.userID)) { if (downedPlayers[player.userID] != null) { //Clear timer downedPlayers[player.userID].Destroy(); downedPlayers[player.userID] = null; } downedPlayers.Remove(player.userID); } } void DestroyMeshCollider(BaseEntity ent) { foreach (var mesh in ent.GetComponentsInChildren()) { UnityEngine.Object.DestroyImmediate(mesh); } } private void CreateSound(BasePlayer player, string url) { if (downedPlayers.Count > 2 || !player.IsAlive()) return; //Stops massive spam since only 3 can play at a time within 400f radius //Spawns a boom box if SFX string is set. Places it under players location and plays the set SFX mp3. //Despawns when player dies or after SFXPlayTime setting. if (url == "") return; DeployableBoomBox boombox = GameManager.server.CreateEntity("assets/prefabs/voiceaudio/boombox/boombox.deployed.prefab", default(Vector3), default(Quaternion), true) as DeployableBoomBox; DestroyMeshCollider(boombox); boombox.SetParent(player); boombox.networkEntityScale = true; boombox.Spawn(); boombox.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f); boombox.pickup.enabled = false; boombox.BoxController.ServerTogglePlay(false); boombox.BoxController.AssignedRadioBy = player.OwnerID; timer.Once(1f, () => { if (boombox == null) { return; } boombox.BoxController.CurrentRadioIp = url; boombox.ClientRPC(RpcTarget.NetworkGroup("OnRadioIPChanged"), boombox.BoxController.CurrentRadioIp); boombox.BoxController.ServerTogglePlay(true); }); } public object cleanup(BasePlayer player) { removedowned(player); //Remove from custom downed list return null; } #endregion } }