C#:
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using Oxide.Core;
using Oxide.Core.Plugins;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Facepunch;
using Rust;
using Rust.Ai;
using Rust.Ai.Gen2;
using HarmonyLib;
namespace Oxide.Plugins
{
[Info("Huntsman", "Krungh Crow", "1.5.4")]
[Description("The Hunter hunts you when u kill Game")]
#region Changelogs and ToDo
/**********************************************************************
* 1.5.0 Removed random skins methods
* Added skin list to the loottable instead of 1 skin
* Assigned skins have to be set again in the cfg
* Merged Death triggers
* Improved npc/animal lookup
* Remade npc base loadout (due to facepunch TOS)
* 1.5.1 Possible fix for reoccuring nre during startup
* 1.5.2 Harmony patch
* 1.5.3 Fix for Facepunch putting the ScientistNPC2 under the Animals.
**********************************************************************/
#endregion
class HuntsMan : RustPlugin
{
[PluginReference] Plugin BotReSpawn , ChickenBow, Kits;
public static Dictionary<BasePlayer, Hunters> SpawnedHunters { get; set; } = new Dictionary<BasePlayer, Hunters>();
#region Variables
private bool CanTrigger = true;
private bool Debug;
bool BlockSpawn;
bool IsSpawned;
bool UseBotRespawn;
const string hunter = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roam.prefab";
const ulong chaticon = 76561199188297846;
const string Trigger_Perm = "huntsman.cantrigger";
string HunterName;
string prefix;
private Dictionary<string, List<ulong>> Skins { get; set; } = new Dictionary<string, List<ulong>>();
private readonly List<ulong> _Hunter = new List<ulong>();
public static HuntsMan Instance { get; private set; }
System.Random Rand;
#endregion
#region HarmonyPatching
private Harmony harmony;
public static string sTitle = "";
public static string sNaming = "{Title} {RandomName} {}";
internal Dictionary<ulong , string> StoredNames = new Dictionary<ulong , string>();
[HarmonyPatch(typeof(ScientistNPC))]
[HarmonyPatch("displayName" , MethodType.Getter)]
public static class DisplayName
{
public static bool Prefix(ref string __result , ScientistNPC __instance)
{
var plugin = HuntsMan.Instance;
if (plugin == null || __instance == null) return true;
if (plugin.StoredNames.ContainsKey(__instance.userID))
{
__result = plugin.HunterName;
return false;
}
return true;
}
}
#endregion
#region Mono
public static HuntsMan instance;
void OnServerInitialized()
{
instance = this;
permission.RegisterPermission(Trigger_Perm , this);
}
public class Hunters : FacepunchBehaviour
{
public global::HumanNPC npc;
public bool ReturningToHome = false;
public Vector3 SpawnPoint;
void Start()
{
npc = GetComponent<global::HumanNPC>();
InvokeRepeating("GoHome", 1.0f, 1.0f);//1
Invoke(nameof(_UseBrain), 0.1f);//0.1
}
public void _UseBrain()
{
#region navigation
npc.Brain.Navigator.Agent.agentTypeID = -1372625422;
npc.Brain.Navigator.DefaultArea = "Walkable";
npc.Brain.Navigator.enabled = true;
npc.Brain.Navigator.CanUseNavMesh = true;
npc.Brain.Navigator.BestCoverPointMaxDistance = instance.configData.HunterData.HunterRoam / 2;//0
npc.Brain.Navigator.BestRoamPointMaxDistance = instance.configData.HunterData.HunterRoam;//0
npc.Brain.Navigator.MaxRoamDistanceFromHome = instance.configData.HunterData.HunterRoam;
npc.Brain.Navigator.Init(npc, npc.Brain.Navigator.Agent);
npc.Brain.Navigator.SetDestination(SpawnPoint, BaseNavigator.NavigationSpeed.Slow, 0f, 0f);
#endregion
#region senses & Targeting
npc.Brain.ForceSetAge(0);
npc.Brain.AllowedToSleep = false;
npc.Brain.sleeping = false;
npc.Brain.SenseRange = 30f;//30
npc.Brain.ListenRange = 40f;//40
npc.Brain.Senses.Init(npc, npc.Brain, 5f, 140f, 140f, -1f, true, true, true, 140f, false, false, true, EntityType.Player, false);
npc.Brain.TargetLostRange = 25f;//25
npc.Brain.HostileTargetsOnly = false;
npc.Brain.IgnoreSafeZonePlayers = true;
#endregion
}
void GoHome()
{
if (npc == null || npc.IsDestroyed || npc.isMounted)
return;
if (!npc.HasBrain)
return;
if (npc.Brain.Senses.Memory.Targets.Count > 0)
{
for (var i = 0; i < npc.Brain.Senses.Memory.Targets.Count; i++)
{
BaseEntity target = npc.Brain.Senses.Memory.Targets[i];
BasePlayer player = target as BasePlayer;
if (target == null || !player.IsAlive() || target.limitNetworking)
{
WipeMemory();
ReturningToHome = true;
return;
}
if (npc.Distance(player.transform.position) > 40f)
{
WipeMemory();
SettargetDestination(SpawnPoint);
ReturningToHome = true;
return;
}
}
}
var distanceHome = Vector3.Distance(npc.transform.position, SpawnPoint);
if (ReturningToHome == false)
{
if (distanceHome > instance.configData.HunterData.HunterRoam)
{
ReturningToHome = true;
return;
}
if (distanceHome < instance.configData.HunterData.HunterRoam)
{
Vector3 random = UnityEngine.Random.insideUnitCircle.normalized * instance.configData.HunterData.HunterRoam;
Vector3 newPos = instance.GetNavPoint(SpawnPoint + new Vector3(random.x, 0f, random.y));
SettargetDestination(newPos);
return;
}
}
if (ReturningToHome && distanceHome > 2)
{
if (npc.Brain.Navigator.Destination == SpawnPoint)
{
return;
}
WipeMemory();
SettargetDestination(SpawnPoint);
return;
}
ReturningToHome = false;
}
private void SettargetDestination(Vector3 position)
{
npc.Brain.Navigator.Destination = position;
npc.Brain.Navigator.SetDestination(position, BaseNavigator.NavigationSpeed.Slow, 0f, 0f);
}
void WipeMemory()
{
if (!npc.HasBrain)
{
return;
}
npc.Brain.Senses.Players.Clear();
npc.Brain.Senses.Memory.Players.Clear();
npc.Brain.Senses.Memory.Targets.Clear();
npc.Brain.Senses.Memory.Threats.Clear();
npc.Brain.Senses.Memory.LOS.Clear();
npc.Brain.Senses.Memory.All.Clear();
}
void OnDestroy()
{
if (npc != null && !npc.IsDestroyed)
{
npc.Kill();
}
instance._Hunter.Remove(npc.userID);
CancelInvoke("GoHome");
CancelInvoke(nameof(_UseBrain));
}
}
#endregion
#region Configuration
void Init()
{
if (!LoadConfigVariables())
{
Puts("Config file issue detected. Please delete file, or check syntax and fix.");
return;
}
Instance = this;
HunterName = configData.HunterData.Huntername;
Debug = configData.UseDebug;
UseBotRespawn = configData.BRS.UseBrs;
if (Debug) Puts("[Debug] Debug is active");
prefix = lang.GetMessage("Prefix" , this);
harmony = new Harmony("krunghcrow.huntsman");
var getter = AccessTools.PropertyGetter(typeof(ScientistNPC) , "displayName");
if (getter != null)
{
harmony.Patch(getter , prefix: new HarmonyMethod(typeof(DisplayName) , nameof(DisplayName.Prefix)));
}
else
{
PrintWarning("ScientistNPC.displayName getter not found; skipping name patch.");
}
sTitle = configData.HunterData.Huntername;
}
private ConfigData configData;
class ConfigData
{
[JsonProperty(PropertyName = "Use Debug")]
public bool UseDebug = false;
[JsonProperty(PropertyName = "BotRespawn Settings")]
public BotrespawnSettings BRS = new BotrespawnSettings();
[JsonProperty(PropertyName = "Animals to trigger HuntsMan")]
public AnimalSettings AnimalData = new AnimalSettings();
[JsonProperty(PropertyName = "Hunter Settings")]
public SettingsHunter HunterData = new SettingsHunter();
}
class BotrespawnSettings
{
[JsonProperty(PropertyName = "Use BotReSpawn")]
public bool UseBrs = false;
[JsonProperty(PropertyName = "BotReSpawn profile name")]
public string ProfileBrs = "huntsman";
}
class AnimalSettings
{
[JsonProperty(PropertyName = "Alpha Animal")]
public bool AlphaTrigger = true;
[JsonProperty(PropertyName = "Omega Animal")]
public bool OmegaTrigger = true;
[JsonProperty(PropertyName = "Bear")]
public bool BearTrigger = true;
[JsonProperty(PropertyName = "Polar Bear")]
public bool PBearTrigger = true;
[JsonProperty(PropertyName = "Boar")]
public bool BoarTrigger = true;
[JsonProperty(PropertyName = "Chicken")]
public bool ChickenTrigger = true;
[JsonProperty(PropertyName = "Stag")]
public bool StagTrigger = true;
[JsonProperty(PropertyName = "Wolf2")]
public bool WolfTrigger2 = true;
[JsonProperty(PropertyName = "Crocodile")]
public bool CrocTrigger = false;
[JsonProperty(PropertyName = "Panther")]
public bool PantherTrigger = false;
[JsonProperty(PropertyName = "Snake")]
public bool SnakeTrigger = false;
[JsonProperty(PropertyName = "Tiger")]
public bool TigerTrigger = false;
}
class SettingsHunter
{
[JsonProperty(PropertyName = "Custom Hunter Name")]
public string Huntername = "HuntsMan";
[JsonProperty(PropertyName = "spawn chance (1-100%)")]
public float HunterSpawnrate = 10f;
[JsonProperty(PropertyName = "Spawn Amount")]
public int HunterAmount = 1;
[JsonProperty(PropertyName = "spawn radius")]
public int Radius = 15;
[JsonProperty(PropertyName = "Health")]
public int HunterHealth = 250;
[JsonProperty(PropertyName = "Max Roam Distance")]
public int HunterRoam = 20;
[JsonProperty(PropertyName = "Damage multiplier")]
public float HunterDamageScale = 0.6f;
[JsonProperty(PropertyName = "Lifetime (minutes)")]
public float HunterLife = 10f;
[JsonProperty(PropertyName = "Use kit (clothing)")]
public bool UseKit = false;
[JsonProperty(PropertyName = "Kit ID")]
public List<string> KitName = new List<string>();
[JsonProperty(PropertyName = "Show messages")]
public bool ShowMsg = true;
[JsonProperty(PropertyName = "Hunters drop a Backpack with loot")]
public bool UseLoot = false;
[JsonProperty(PropertyName = "Spawn Min Amount Items")]
public int MinAmount { get; set; } = 2;
[JsonProperty(PropertyName = "Spawn Max Amount Items")]
public int MaxAmount { get; set; } = 6;
[JsonProperty(PropertyName = "Loot Table", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<LootProfile> Loot { get; set; } = DefaultLoot;
}
private bool LoadConfigVariables()
{
try
{
configData = Config.ReadObject<ConfigData>();
}
catch
{
return false;
}
SaveConf();
return true;
}
protected override void LoadDefaultConfig()
{
Puts("Creating new config file.");
configData = new ConfigData();
SaveConf();
}
void SaveConf() => Config.WriteObject(configData, true);
#endregion
#region LanguageAPI
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Hunter_Spawned"] = "You killed the {0} now pay the price !!!!",
["Hunter_Spawned_Backpack"] = "{0} Dropped his Backpack !",
["Prefix"] = "[<color=green>HuntsMan</color>] : ",
["info"] = "\nThe [HuntsMan] Gives u the luck (or not) to spawn a Hunter that wants to kill you for killing Wild Game during offseason",
}, this);
}
#endregion
#region Commands
[ChatCommand("hminfo")]
void cmdhmversion(BasePlayer player, string cmd, string[] args)
{
Player.Message(player, string.Format(msg("Current Version v", player.UserIDString)) + this.Version.ToString() + " By : " + this.Author.ToString() + msginfo("info"), chaticon);
}
#endregion
#region External Hooks
object OnNpcKits(BasePlayer player)
{
if (player?.gameObject?.GetComponent<Hunters>() != null)
return true;
return null;
}
void OnBotReSpawnNPCSpawned(ScientistNPC npc, string profile, string group)
{
if (profile == configData.BRS.ProfileBrs)
{
npc.displayName = HunterName;
_Hunter.Add(npc.userID);
npc.SendNetworkUpdate();
AddSuicideTimer(npc);
Effect.server.Run("assets/bundled/prefabs/fx/player/howl.prefab", npc.transform.position);
if (Debug) Puts($"BotReSpawn spawned {profile} with name ({npc.displayName})");
Interface.CallHook("OnHuntsmanSpawned", npc);
}
}
#endregion
#region Oxide Hooks
void Unload()
{
foreach (var hunter in SpawnedHunters.Values)
UnityEngine.Object.Destroy(hunter);
SpawnedHunters.Clear();
BotReSpawn?.Call("RemoveGroupSpawn" , "huntsman");
harmony?.UnpatchAll(harmony.Id);
}
object OnChickenBowHuntsManBlockSpawn(BasePlayer player)
{
BlockSpawn = true;
timer.Once(1 , () => { BlockSpawn = false; });
return null;
}
void OnEntityDeath(BaseAnimalNPC animal , HitInfo info)
{
if (animal == null || info?.InitiatorPlayer == null) return;
HandleAnimalDeath(animal , info.InitiatorPlayer , info);
}
void OnEntityDeath(BaseNPC2 animal , HitInfo info)
{
if (animal == null || info?.InitiatorPlayer == null || animal is ScientistNPC2) return;
HandleAnimalDeath(animal , info.InitiatorPlayer , info);
}
void OnEntityDeath(SnakeHazard animal , HitInfo info)
{
if (animal == null || info?.InitiatorPlayer == null) return;
HandleAnimalDeath(animal , info.InitiatorPlayer , info);
}
private void HandleAnimalDeath(BaseEntity animal , BasePlayer player , HitInfo info)
{
if (player.userID.IsSteamId() || info.Initiator is BasePet)
{
if (!HasPerm(player , Trigger_Perm)) return;
if (Interface.CallHook("CanSpawnHuntsman" , animal) != null)
{
return;
}
try
{
AnimalCheck(animal);
if (UnityEngine.Random.Range(0f , 1f) <= configData.HunterData.HunterSpawnrate / 100)
{
if (CanTrigger == true)
{
if (animal is BaseAnimalNPC && BlockSpawn)
{
if (Debug) Puts($"{player.displayName} shot down a ChickenBow Chicken and a HuntsMan was skipped");
return;
}
for (int i = 0; i < configData.HunterData.HunterAmount; i++)
{
if (UseBotRespawn)
{
Vector3 location = animal.transform.position;
string _Profile = configData.BRS.ProfileBrs;
string[] Spawn = BotReSpawn?.Call("AddGroupSpawn" , location , _Profile , _Profile , 1) as string[];
if (Debug) Puts($"[BotReSpawn] : {Spawn[1]}");
IsSpawned = true;
}
else if (!UseBotRespawn) Spawnnpc(animal.transform.position);
}
Puts($"{player.displayName} shot down a {animal.name} and a HuntsMan tried to arrest him/her");
if (configData.HunterData.ShowMsg && IsSpawned == true)
{
string AnimalName = animal.name;
if (AnimalName.Contains("assets/rust.ai/agents/") || AnimalName.Contains(".prefab"))
{
AnimalName = animal.ShortPrefabName;
if (AnimalName.Contains("wolf2"))
{
AnimalName = "wolf";
}
if (AnimalName.Contains("snake.entity"))
{
AnimalName = "snake";
}
}
Player.Message(player , string.Format(msg("Hunter_Spawned" , player.UserIDString) , AnimalName) , chaticon);
}
return;
}
return;
}
}
catch
{
}
}
return;
}
void OnEntityDeath(ScientistNPC hunter, HitInfo info)
{
if (hunter == null || info?.Initiator == null) return;
if (_Hunter.Contains(hunter.userID))
{
BasePlayer player = info.InitiatorPlayer;
if (player == null || !player.IsValid()) return;
if (info.InitiatorPlayer.userID.IsSteamId() || info.Initiator is BasePet) ;
{
if (configData.HunterData.UseLoot)
{
Player.Message(player, string.Format(msg("Hunter_Spawned_Backpack", player.UserIDString), hunter.displayName), chaticon);
string _displayname = hunter.displayName;
SpawnHunterLoot(hunter.transform.position + new Vector3(0f, 0.5f, 0f), hunter.transform.rotation, _displayname);
}
}
Interface.CallHook("OnHuntsmanKilled" , hunter);
}
}
private object OnNpcTarget(BasePlayer attacker, BasePlayer target)
{
if (attacker != null && _Hunter.Contains(attacker.userID))
{
if (target.IsSleeping() || !target.userID.IsSteamId() || !target is BasePet)
return true;
}
if (attacker != null && target != null && _Hunter.Contains(target.userID))
{
return true;
}
return null;
}
void OnEntitySpawned(NPCPlayerCorpse corpse)
{
if (corpse == null || corpse.IsDestroyed) return;
ulong id = corpse.playerSteamID;
if(!(UseBotRespawn) && _Hunter.Contains(corpse.playerSteamID))
{
try
{
NextTick(() =>
{
if (Debug) Puts($"NPC is a {HunterName} so corpse injection is possible");
Item hunterskull = ItemManager.CreateByName("skull.human", 1, 0);
ItemAmount HunterSkull = new ItemAmount() { itemDef = hunterskull.info, amount = 1, startAmount = 1 };
Item hunterflesh = ItemManager.CreateByName("humanmeat.raw", 1, 0);
ItemAmount HunterFlesh = new ItemAmount() { itemDef = hunterflesh.info, amount = 6, startAmount = 1 };
var dispenser = corpse.GetComponent<ResourceDispenser>();
if (dispenser != null)
{
dispenser.containedItems.Add(HunterSkull);
dispenser.containedItems.Add(HunterFlesh);
dispenser.Initialize();
if (Debug) Puts($"[Debug] Huntsman Corpse was injected with [Skull & Human flesh]");
}
});
return;
}
catch
{
return;
}
}
if (Debug && (UseBotRespawn) && _Hunter.Contains(corpse.playerSteamID)) Puts($"[Debug] BotReSpawn will cover the [Skull] injection");
}
#endregion
#region API
private bool IsHuntsmanNpc(global::HumanNPC npc)
{
if (npc != null)
{
if (_Hunter.Contains(npc.userID)) return true;
return false;
}
return false;
}
#endregion
#region Event Helpers
bool HasPerm(BasePlayer player , string perm) { return permission.UserHasPermission(player.UserIDString , perm); }
public Vector3 GetNavPoint(Vector3 position)
{
NavMeshHit hit;
if (!NavMesh.SamplePosition(position, out hit, 5, -1))
{
return position;
}
else if (Physics.RaycastAll(hit.position + new Vector3(0, 100, 0), Vector3.down, 99f, 1235288065).Any())
{
return position;
}
else if (hit.position.y < TerrainMeta.WaterMap.GetHeight(hit.position))
{
return position;
}
position = hit.position;
return position;
}
public static Vector3 CheckPos(Vector3 pos)
{
NavMeshHit navMeshHit;
if (!NavMesh.SamplePosition(pos, out navMeshHit, 2, 1))
pos = Vector3.zero;
else if (WaterLevel.GetWaterDepth(pos, true, false) > 0)
pos = Vector3.zero;
else if (Physics.RaycastAll(navMeshHit.position + new Vector3(0, 100, 0), Vector3.down, 99f, 1235288065).Any())
pos = Vector3.zero;
else
pos = navMeshHit.position;
return pos;
}
private void AnimalCheck(object animal)
{
CanTrigger = true;
if (animal is BaseAnimalNPC baseAnimal)
{
if (baseAnimal is Bear && !configData.AnimalData.BearTrigger) CanTrigger = false;
if (baseAnimal is Polarbear && !configData.AnimalData.PBearTrigger) CanTrigger = false;
if (baseAnimal is Boar && !configData.AnimalData.BoarTrigger) CanTrigger = false;
if (baseAnimal is Chicken)
{
if (!configData.AnimalData.ChickenTrigger)
{
CanTrigger = false;
}
else
{
if (ChickenBow != null && ChickenBow.Call<bool>("IsSpawnedChicken" , baseAnimal.net.ID))
CanTrigger = false;
}
}
if (baseAnimal is Stag && !configData.AnimalData.StagTrigger) CanTrigger = false;
}
else if (animal is BaseNPC2 baseAnimal2)
{
if (animal is Crocodile && !configData.AnimalData.CrocTrigger) CanTrigger = false;
if (animal is Panther && !configData.AnimalData.PantherTrigger) CanTrigger = false;
if (animal is Tiger && !configData.AnimalData.TigerTrigger) CanTrigger = false;
}
else if (animal is SnakeHazard)
{
CanTrigger = configData.AnimalData.SnakeTrigger;
}
else if (animal is Wolf2)
{
CanTrigger = configData.AnimalData.WolfTrigger2;
}
else
{
CanTrigger = false;
}
// Alpha/Omega override
if (animal is BaseNPC2 npc)
{
if (npc.name.Contains("Alpha"))
{
CanTrigger = configData.AnimalData.AlphaTrigger;
}
else if (npc.name.Contains("Omega"))
{
CanTrigger = configData.AnimalData.OmegaTrigger;
}
}
else
{
CanTrigger = CanTrigger;
}
if (Debug) Puts($"[Debug] {animal.GetType().Name} kill was triggered and [CanTrigger] is {CanTrigger}");
}
private void GiveLoadout(ScientistNPC npc)
{
if (npc != null)
{
var inv_belt = npc.inventory.containerBelt;
var inv_wear = npc.inventory.containerWear;
Item hat = ItemManager.CreateByName("hat.boonie" , 1 , 3468383925);//checked
Item hoodie = ItemManager.CreateByName("hoodie" , 1 , 3042985784);//checked
Item pants = ItemManager.CreateByName("pants" , 1 , 3042985200);//checked
Item boots = ItemManager.CreateByName("shoes.boots" , 1 , 1464542400);//checked
Item weapon1 = ItemManager.CreateByName("shotgun.pump" , 1 , 0);
inv_wear.Clear();
if (hat != null) hat.MoveToContainer(inv_wear);
if (hoodie != null) hoodie.MoveToContainer(inv_wear);
if (pants != null) pants.MoveToContainer(inv_wear);
if (boots != null) boots.MoveToContainer(inv_wear);
if (weapon1 != null) weapon1.MoveToContainer(inv_belt);
npc.inventory.UpdatedVisibleHolsteredItems();
npc.EquipWeapon();
string scientistSuitShortname = "hazmatsuit_scientist";
Item SuitItem = npc.inventory.containerMain.itemList.FirstOrDefault(item => item.info.shortname == scientistSuitShortname);
if (SuitItem != null)
{
SuitItem.RemoveFromContainer();
}
}
else
{
Puts("Huntsman was not fully spawned yet skipping [GiveLoadout]");
}
}
private void Spawnnpc(Vector3 position)
{
int radius = configData.HunterData.Radius;
Vector3 pos = position + UnityEngine.Random.onUnitSphere * radius;
pos.y = TerrainMeta.HeightMap.GetHeight(pos);
CheckPos(pos);
if (pos == Vector3.zero) return;
ScientistNPC npc = (ScientistNPC)GameManager.server.CreateEntity(hunter , pos , new Quaternion() , true);
npc.Spawn();
StoredNames[npc.userID] = HunterName;
Interface.CallHook("OnHuntsmanSpawn" , npc);
_Hunter.Add(npc.userID);
timer.Once(0.3f , () =>
{
if (npc == null) return;
var mono = npc.gameObject.AddComponent<Hunters>();
mono.SpawnPoint = pos;
SpawnedHunters[npc] = mono;
npc.startHealth = configData.HunterData.HunterHealth;
npc.InitializeHealth(configData.HunterData.HunterHealth , configData.HunterData.HunterHealth);
npc.damageScale = configData.HunterData.HunterDamageScale;
(npc as ScientistNPC).radioChatterType = ScientistNPC.RadioChatterType.NONE;
(npc as ScientistNPC).DeathEffects = new GameObjectRef[0];
if (configData.HunterData.UseKit && configData.HunterData.KitName.Count > 0)
{
object checkKit = Kits?.CallHook("GetKitInfo" , configData.HunterData.KitName[new System.Random().Next(configData.HunterData.KitName.Count())]);
if (checkKit == null)
{
GiveLoadout(npc);
PrintWarning($"Kit for {HunterName} does not exist - Using a default Hunter outfit.");
}
else
{
npc.inventory.Strip();
Kits?.Call($"GiveKit" , npc , configData.HunterData.KitName[new System.Random().Next(configData.HunterData.KitName.Count())]);
npc.inventory.UpdatedVisibleHolsteredItems();
npc.EquipWeapon();
}
}
if (!configData.HunterData.UseKit || configData.HunterData.KitName.Count == 0)
{
GiveLoadout(npc);
}
npc.EquipWeapon();
});
if (npc.IsHeadUnderwater())
{
npc.Kill();
if (Debug) timer.Once(1f , () => { Puts($"{npc} skipped its spawn (spawned under water)"); });
IsSpawned = false;
return;
}
if (!npc.IsOutside())
{
npc.Kill();
if (Debug) timer.Once(1f , () => { Puts($"{npc} skipped its spawn (No valid spawnpoint)"); });
IsSpawned = false;
return;
}
IsSpawned = true;
Interface.CallHook("OnHuntsmanSpawned" , npc);
var id = npc.userID;
timer.Once(1f , () => { Puts($"{npc} spawned"); });
Effect.server.Run("assets/bundled/prefabs/fx/player/howl.prefab" , npc.transform.position);
AddSuicideTimer(npc);
}
private void AddSuicideTimer(ScientistNPC npc)
{
timer.Once(configData.HunterData.HunterLife * 60 , () =>
{
if (npc != null)
{
_Hunter.Remove(npc.userID);
SpawnedHunters.Remove(npc);
npc.Kill();
if (Debug) Puts($"{npc} Died of natural causes!!!");
return;
}
if (Debug) Puts($"{npc} was killed before ending of timer");
return;
});
}
#endregion
#region Loot System
private static List<LootProfile> DefaultLoot
{
get
{
return new List<LootProfile>
{
new LootProfile { probability = 1f, shortname = "ammo.pistol", name = "", amountMin = 128, amount = 256, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "ammo.pistol.fire", name = "", amountMin = 128, amount = 256, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "ammo.rifle.explosive", name = "", amountMin = 128, amount = 256, skins = new List<ulong>() { 0 } },
new LootProfile { probability = 1f, shortname = "ammo.rifle.hv", name = "", amountMin = 128, amount = 256, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "ammo.rifle.incendiary", name = "", amountMin = 128, amount = 256, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "ammo.shotgun", name = "", amountMin = 128, amount = 256, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "ammo.shotgun", name = "", amountMin = 128, amount = 256, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "explosive.timed", name = "", amountMin = 1, amount = 5, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "explosives", name = "", amountMin = 200, amount = 200, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "pistol.m92", name = "", amountMin = 1, amount = 1, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "shotgun.spas12", name = "", amountMin = 1, amount = 1, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "pickaxe", name = "", amountMin = 1, amount = 1, skins = new List<ulong>() { 3163818649, 1962043729 } },
new LootProfile { probability = 1f, shortname = "hatchet", name = "", amountMin = 1, amount = 1, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "can.beans", name = "", amountMin = 3, amount = 5, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "can.tuna", name = "", amountMin = 3, amount = 5, skins = new List<ulong>() },
new LootProfile { probability = 1f, shortname = "black.raspberries", name = "", amountMin = 3, amount = 5, skins = new List<ulong>() },
new LootProfile { probability = 0.0f, shortname = "box.repair.bench", name = "Recycler", amountMin = 1, amount = 1, skins = new List<ulong>() { 1594245394 } },
new LootProfile { probability = 0.6f, shortname = "hmlmg", name = "", amountMin = 1, amount = 1, skins = new List<ulong>() },
new LootProfile { probability = 0.6f, shortname = "supply.signal", name = "", amountMin = 1, amount = 1, skins = new List<ulong>() },
};
}
}
public class LootProfile
{
public float probability { get; set; } = 1f;
public string shortname { get; set; }
public string name { get; set; } = "";
[JsonProperty(PropertyName = "Skin list")]
public List<ulong> skins { get; set; } = new List<ulong>();
public int amountMin { get; set; }
public int amount { get; set; }
}
private void SpawnHunterLoot(Vector3 pos, Quaternion rot,string _name)
{
var backpack = GameManager.server.CreateEntity(StringPool.Get(545786656) , pos, rot, true) as DroppedItemContainer;
if (backpack == null) return;
backpack.inventory = new ItemContainer();
backpack.inventory.ServerInitialize(null , 36);
backpack.inventory.GiveUID();
backpack.inventory.entityOwner = backpack;
backpack.inventory.SetFlag(ItemContainer.Flag.NoItemInput , true);
backpack.Spawn();
SpawnLoot(backpack.inventory, configData.HunterData.Loot.ToList());
}
private void SpawnLoot(ItemContainer container , List<LootProfile> loot)
{
int total = UnityEngine.Random.Range(Math.Min(loot.Count , configData.HunterData.MinAmount) , Math.Min(loot.Count , configData.HunterData.MaxAmount));
if (total == 0 || loot.Count == 0)
{
return;
}
container.capacity = total;
ItemDefinition def;
List<ulong> skins; // This variable is no longer needed in this scope
LootProfile lootItem;
for (int j = 0; j < total; j++)
{
if (loot.Count == 0)
{
break;
}
lootItem = loot.GetRandom();
loot.Remove(lootItem);
if (lootItem.amount <= 0)
{
continue;
}
string shortname = lootItem.shortname;
if (UnityEngine.Random.value > lootItem.probability)
{
if (!string.IsNullOrEmpty(lootItem.name)) Puts($"Skipping Custom({j + 1}) : {lootItem.name} : probability = {lootItem.probability}");
else if (string.IsNullOrEmpty(lootItem.name)) Puts($"Skipping Normal({j + 1}) : {lootItem.shortname} : probability = {lootItem.probability}");
j--;
continue;
}
bool isBlueprint = shortname.EndsWith(".bp");
if (isBlueprint)
{
shortname = shortname.Replace(".bp" , string.Empty);
}
def = ItemManager.FindItemDefinition(shortname);
if (def == null)
{
Puts("Invalid shortname: {0}" , lootItem.shortname);
continue;
}
ulong skin = 0;
if (lootItem.skins != null && lootItem.skins.Count > 0)
{
skin = lootItem.skins.GetRandom();
}
int amount = UnityEngine.Random.Range(lootItem.amountMin , lootItem.amount + 1);
if (amount <= 0)
{
continue;
}
Item item;
if (isBlueprint)
{
item = ItemManager.CreateByItemID(-996920608 , 1 , 0);
if (item == null) continue;
item.blueprintTarget = def.itemid;
item.amount = amount;
}
else item = ItemManager.Create(def , amount , skin);
if (!item.MoveToContainer(container , -1 , false))
{
item.Remove();
}
if (!string.IsNullOrEmpty(lootItem.name)) { item.name = lootItem.name; }
if (Debug)
{
if (string.IsNullOrEmpty(item.name))
{
Puts($"Normal({j + 1}) : {item.info.shortname} : Amount {item.amount} SkinID : {item.skin}");
}
if (!string.IsNullOrEmpty(item.name))
{
Puts($"Custom({j + 1}) : {item.name} : Amount : {item.amount} SkinID : {item.skin}");
}
}
}
}
#endregion
#region Message helper
private string msg(string key, string id = null) => prefix + lang.GetMessage(key, this, id);
private string msginfo(string key, string id = null) => lang.GetMessage(key, this, id);
#endregion
}
}