C#:
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Plugins.DangerousTreasuresExtensionMethods;
using Rust;
using Rust.Ai.Gen2;
using Rust.Ai.Gen2.Nav;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
namespace Oxide.Plugins {
[Info("Dangerous Treasures", "nivex", "3.0.0")]
[Description("Event with treasure chests.")]
internal class DangerousTreasures : RustPlugin {
[PluginReference]
Plugin ZoneManager, Economics, ServerRewards, GUIAnnouncements, MarkerManager, Kits, Duelist, RaidableBases, AbandonedBases, Notify, AdvancedAlerts, Clans, Friends;
private new const string Name = "Dangerous Treasures";
private bool wipeChestsSeed;
private StoredData data = new();
private List<int> BlockedLayers = new() {
(int)Layer.Water, (int)Layer.Construction, (int)Layer.Trigger, (int)Layer.Prevent_Building, (int)Layer.Deployed, (int)Layer.Tree, (int)Layer.Clutter
}
;
private Dictionary<ulong, HumanoidBrain> HumanoidBrains = new();
private List<MonumentInfoEx> _allowedMonuments = new();
private List<MonumentInfoEx> monuments = new();
private Dictionary<Vector3, ZoneInfo> managedZones = new();
private Dictionary<NetworkableId, TreasureChest> treasureChests = new();
private Dictionary<NetworkableId, string> looters = new();
private Dictionary<string, ItemDefinition> _definitions = new();
private Dictionary<string, SkinInfo> Skins = new();
private List<ulong> newmanProtections = new();
private List<ulong> indestructibleWarnings = new();
// indestructible messages limited to once every 10 seconds
private List<ulong> drawGrants = new();
// limit draw to once every 15 seconds by default
private List<int> obstructionLayers = new() {
Layers.Mask.Player_Server, Layers.Mask.Construction, Layers.Mask.Deployed
}
;
private List<string> _blockedColliders = new() {
"powerline_", "invisible_", "TopCol", "train", "swamp_", "floating_"
}
;
private List<string> underground = new() {
"Cave", "Sewer Branch", "Military Tunnel", "Underwater Lab", "Train Tunnel"
}
;
private List<Vector3> _gridPositions = new();
private List<Vector3> _gridPositionsSrc = new();
private bool IsGridReady;
private bool IsMonumentsReady;
private Coroutine _gridCo;
private RaycastHit[] sharedRockHits = new RaycastHit[256];
private Collider[] sharedRockColliders = new Collider[256];
private const int TARGET_MASK = 8454145;
private const int targetMask = Layers.Mask.World | Layers.Mask.Terrain | Layers.Mask.Default;
private const int visibleMask = Layers.Mask.Deployed | Layers.Mask.Construction | targetMask;
private const int obstructionLayer = Layers.Mask.Player_Server | Layers.Mask.Construction | Layers.Mask.Deployed;
private const int heightLayer = TARGET_MASK | Layers.Mask.Construction | Layers.Mask.Deployed | Layers.Mask.Clutter;
private StringBuilder _sb = new();
private Vector3 sd_customPos;
private ulong BotIdCounter = 624922525;
private Dictionary<int, double> eventRetries = new();
public class ZoneInfo {
public Vector3 Position;
public Vector3 Size;
public float Distance;
public OBB OBB;
}
public class SkinInfo {
public List<ulong> skins = new();
public List<ulong> workshopSkins = new();
public List<ulong> importedSkins = new();
public List<ulong> allSkins = new();
}
private class PlayerInfo {
public int StolenChestsTotal;
public int StolenChestsSeed;
public PlayerInfo() {
}
}
private class StoredData {
public Dictionary<string, PlayerInfo> Players = new();
[JsonProperty(PropertyName = "Seconds Until Event")]
public Dictionary<int, double> SecondsUntilEvent = new() {
[0]
= double.MinValue
}
;
public string CustomPosition;
public int TotalEvents = 0;
public StoredData() {
}
}
public class HumanoidNPC : ScientistNPC {
public new HumanoidBrain Brain;
public TreasureChest tc;
public string DisplayNameOverride;
public DifficultyLevel Options;
public Configuration config;
public DangerousTreasures Instance;
public new Translate.Phrase LootPanelTitle => displayName;
public override string Categorize() => "Humanoid";
public override bool ShouldDropActiveItem() => false;
public override string displayName => DisplayNameOverride;
public override void AttackerInfo(ProtoBuf.PlayerLifeStory.DeathInfo info) {
info.attackerName = displayName;
info.attackerSteamID = userID;
info.inflictorName = inventory?.containerBelt?.GetSlot(0)?.info?.shortname;
if (Brain != null) info.attackerDistance = Vector3.Distance(Brain.ServerPosition, Brain.AttackPosition);
}
public override void OnDied(HitInfo info) {
BasePlayer.bots.Remove(this);
if (Brain != null) Brain.DisableShouldThink();
if (Instance != null && tc != null) {
svActiveItemID = default;
SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
tc.npcs.Remove(this);
if (tc.whenNpcsDie && tc.npcs.Count == 0) tc.Unlock();
if (config.Unlock.LockToPlayerOnNpcDeath) tc.TrySetOwner(info);
if (Options.Event.DestructTimeResetsWhenKilled && info != null && info.Initiator.Is(out BasePlayer attacker) && attacker.userID.IsSteamId()) tc.SetDestructTime();
}
base.OnDied(info);
}
public override void DoServerDestroy() {
if (tc != null && tc.npcs != null) tc.npcs.Remove(this);
BasePlayer.bots.Remove(this);
if (Brain != null) {
AttackEntity attackEntity = Brain._attackEntity;
if (!attackEntity.IsKilled()) attackEntity.SetHeld(false);
Brain.DisableShouldThink();
}
base.DoServerDestroy();
BasePlayer.freeBotIds.Remove(userID);
}
public override BaseCorpse CreateCorpse(PlayerFlags flagsOnDeath, Vector3 posOnDeath, Quaternion rotOnDeath, List<TriggerBase> triggersOnDeath, bool forceServerSide = false) {
if (inventory == null || Brain == null) {
inventory.SafelyStrip();
CheckCorpse(null);
return null;
}
bool keepInventory = !(Brain.isMurderer ? Options.NPC.Murderers.DespawnInventory : Options.NPC.Scientists.DespawnInventory);
bool hasPrefabLoot = !LootSpawnSlots.IsNullOrEmpty();
if (!keepInventory && !hasPrefabLoot) {
inventory.SafelyStrip();
CheckCorpse(null);
return null;
}
if (keepInventory) inventory.containerWear.SafelyRemove("gloweyes");
else inventory.SafelyStrip();
if (!RemoveOwnershipPass() && !hasPrefabLoot) {
CheckCorpse(null);
return null;
}
PlayerCorpse corpse = DropCorpse("assets/prefabs/player/player_corpse.prefab") as PlayerCorpse;
if (corpse == null) {
CheckCorpse(null);
return null;
}
if (NavAgent != null) corpse.transform.position += Vector3.down * NavAgent.baseOffset;
corpse.TakeFrom(this, inventory.containerMain, inventory.containerWear, inventory.containerBelt);
corpse.playerName = displayName;
corpse.playerSteamID = userID;
corpse.skinID = 14922525;
corpse.Spawn();
if (corpse.IsKilled()) {
CheckCorpse(null);
return null;
}
corpse.TakeChildren(this);
var alternate = Brain.isMurderer ? Options.NPC.Murderers.Alternate : Options.NPC.Scientists.Alternate;
bool canPopulateLoot = !alternate.CallHook || Interface.CallHook("OnCorpsePopulate", this, corpse) == null;
if (corpse.IsKilled()) {
CheckCorpse(null);
return null;
}
if (canPopulateLoot && !LootSpawnSlots.IsNullOrEmpty()) {
foreach (var lootSpawnSlot in LootSpawnSlots) {
for (int k = 0; k < lootSpawnSlot.numberToSpawn; k++) {
if (UnityEngine.Random.Range(0f, 1f) <= lootSpawnSlot.probability) lootSpawnSlot.definition.SpawnIntoContainer(corpse.containers[0]);
}
}
}
CheckCorpse(corpse);
return corpse;
}
private void CheckCorpse(PlayerCorpse corpse) {
if (corpse != null && Brain != null) {
float time = Brain.isMurderer ? (Options.NPC.Murderers.DespawnInventory ? Options.NPC.Murderers.DespawnInventoryTime : Options.NPC.Murderers.CorpseDespawnTime) : (Options.NPC.Scientists.DespawnInventory ? Options.NPC.Scientists.DespawnInventoryTime : Options.NPC.Scientists.CorpseDespawnTime);
corpse.Invoke(corpse.SafelyKill, time);
corpse.playerName = displayName;
}
if (Brain != null) UnityEngine.Object.Destroy(Brain);
}
private bool RemoveOwnershipPass() {
if (!Instance.config.BlockPaidContent) return true;
using var itemList = Facepunch.Pool.Get<PooledList<Item>>();
inventory.GetAllItems(itemList);
bool hasItems = false;
for (int i = itemList.Count - 1; i >= 0; i--) {
Item item = itemList[i];
if (!Instance.RequiresOwnership(item.info, item.skin)) {
hasItems = true;
continue;
}
item.GetHeldEntity().SafelyKill();
item.RemoveFromContainer();
item.Remove(0f);
}
return hasItems;
}
}
public class HumanoidBrain : ScientistBrain {
public void DisableShouldThink() {
isDisabled = true;
lastWarpTime = float.MaxValue;
sleeping = true;
SetEnabled(false);
try {
CancelInvoke();
}
catch {
}
AIThinkManager._processQueue.Remove(npc);
if (HumanoidBrains.Remove(uid) && HumanoidBrains.Count == 0 && Instance.Manager != null) {
if (!config.NewmanMode.Harm) Instance.Unsubscribe(nameof(OnNpcTarget));
Instance.Unsubscribe(nameof(OnNpcResume));
Instance.Unsubscribe(nameof(OnNpcDestinationSet));
Instance.Unsubscribe(nameof(CanBradleyApcTarget));
}
if (Rust.Application.isQuitting) {
return;
}
if (npc != null && BaseEntity.Query.Server != null) {
BaseEntity.Query.Server.RemoveBrain(npc);
}
LeaveGroup();
}
internal DangerousTreasures Instance;
internal Dictionary<ulong, HumanoidBrain> HumanoidBrains;
internal enum AttackType {
BaseProjectile, FlameThrower, Melee, Water, None
}
internal string displayName;
internal Transform NpcTransform;
internal HumanoidNPC npc;
internal AttackEntity _attackEntity;
internal FlameThrower flameThrower;
internal LiquidWeapon liquidWeapon;
internal BaseMelee baseMelee;
internal BaseProjectile baseProjectile;
internal BasePlayer AttackTarget;
internal Transform AttackTransform;
internal TreasureChest tc;
internal NpcSettings Settings;
internal List<Vector3> positions;
internal Vector3 DestinationOverride;
internal bool isDisabled;
internal bool InitializedAI;
internal bool isMurderer;
internal ulong uid;
internal float lastWarpTime;
internal float softLimitSenseRange;
internal float nextAttackTime;
internal float attackRange;
internal float attackCooldown;
internal AttackType attackType = AttackType.None;
internal BaseNavigator.NavigationSpeed CurrentSpeed = BaseNavigator.NavigationSpeed.Normal;
internal DifficultyLevel Options => tc.Options;
internal Vector3 AttackPosition => AttackTransform == null ? default : AttackTransform.position;
internal Vector3 ServerPosition => NpcTransform == null ? default : NpcTransform.position;
internal Configuration config;
internal AttackEntity AttackEntity {
get {
if (_attackEntity.IsNull()) {
IdentifyWeapon();
}
return _attackEntity;
}
}
public void UpdateWeapon(AttackEntity attackEntity, ItemId uid) {
npc.UpdateActiveItem(uid);
if (attackEntity is Chainsaw cs) {
cs.ServerNPCStart();
}
npc.damageScale = 1f;
attackEntity.TopUpAmmo();
attackEntity.SetHeld(true);
}
internal void IdentifyWeapon() {
_attackEntity = GetEntity().GetAttackEntity();
attackRange = 0f;
attackCooldown = 99999f;
attackType = AttackType.None;
baseMelee = null;
flameThrower = null;
liquidWeapon = null;
if (_attackEntity.IsNull()) {
return;
}
Action action = _attackEntity.ShortPrefabName switch {
"double_shotgun.entity" or "shotgun_pump.entity" or "shotgun_waterpipe.entity" or "spas12.entity" or "blowpipe.entity" or "boomerang.entity" => () => {
SetAttackRestrictions(AttackType.BaseProjectile, 30f, 0f, 30f);
}
, "ak47u.entity" or "ak47u_ice.entity" or "bolt_rifle.entity" or "glock.entity" or "hmlmg.entity" or "l96.entity" or "lr300.entity" or "m249.entity" or "m39.entity" or "m92.entity" or "mp5.entity" or "nailgun.entity" or "pistol_eoka.entity" or "pistol_revolver.entity" or "pistol_semiauto.entity" or "python.entity" or "semi_auto_rifle.entity" or "thompson.entity" or "smg.entity" => () => {
SetAttackRestrictions(AttackType.BaseProjectile, 300f, 0f, 150f);
}
, "snowballgun.entity" => () => {
SetAttackRestrictions(AttackType.BaseProjectile, 15f, 0.1f, 15f);
}
, "chainsaw.entity" or "jackhammer.entity" => () => {
baseMelee = _attackEntity as BaseMelee;
SetAttackRestrictions(AttackType.Melee, 2.5f, (_attackEntity.animationDelay + _attackEntity.deployDelay) * 2f);
}
, "axe_salvaged.entity" or "bone_club.entity" or "butcherknife.entity" or "candy_cane.entity" or "hammer_salvaged.entity" or "hatchet.entity" or "icepick_salvaged.entity" or "knife.combat.entity" or "knife_bone.entity" or "longsword.entity" or "mace.baseballbat" or "mace.entity" or "machete.weapon" or "pickaxe.entity" or "pitchfork.entity" or "salvaged_cleaver.entity" or "salvaged_sword.entity" or "sickle.entity" or "spear_stone.entity" or "spear_wooden.entity" or "stone_pickaxe.entity" or "stonehatchet.entity" => () => {
baseMelee = _attackEntity as BaseMelee;
SetAttackRestrictions(AttackType.Melee, 2.5f, _attackEntity.animationDelay + _attackEntity.deployDelay);
}
, "flamethrower.entity" => () => {
flameThrower = _attackEntity as FlameThrower;
SetAttackRestrictions(AttackType.FlameThrower, 10f, (_attackEntity.animationDelay + _attackEntity.deployDelay) * 2f);
}
, "compound_bow.entity" or "crossbow.entity" or "speargun.entity" or "bow_hunting.entity" => () => {
SetAttackRestrictions(AttackType.BaseProjectile, 200f, (_attackEntity.animationDelay + _attackEntity.deployDelay) * 1.25f, 150f);
}
, "watergun.entity" or "waterpistol.entity" => () => {
if ((liquidWeapon = _attackEntity as LiquidWeapon) != null) {
liquidWeapon.AutoPump = true;
SetAttackRestrictions(AttackType.Water, 10f, 2f);
}
}
, _ => () => _attackEntity = null
}
;
action();
}
private void SetAttackRestrictions(AttackType attackType, float attackRange, float attackCooldown, float effectiveRange = 0f) {
if (attackType == AttackType.BaseProjectile) {
baseProjectile = _attackEntity as BaseProjectile;
if (baseProjectile != null) {
baseProjectile.MuzzlePoint ??= baseProjectile.transform;
}
}
if (effectiveRange != 0f) {
_attackEntity.effectiveRange = effectiveRange;
}
this.attackType = attackType;
this.attackRange = attackRange;
this.attackCooldown = attackCooldown;
}
public bool ValidTarget => AttackTransform != null && !AttackTarget.IsKilled() && !ShouldForgetTarget(AttackTarget);
public override void OnDestroy() {
DisableShouldThink();
if (InitializedAI) Count--;
}
public override void InitializeAI() {
if (isDisabled) {
return;
}
base.InitializeAI();
InitializedAI = true;
base.ForceSetAge(0f);
NpcTransform = GetEntity().transform;
Pet = false;
sleeping = false;
UseAIDesign = true;
AllowedToSleep = false;
HostileTargetsOnly = false;
AttackRangeMultiplier = 2f;
MaxGroupSize = 0;
Senses.Init(owner : GetEntity(), brain : this, memoryDuration : 5f, range : 50f, targetLostRange : 75f, visionCone : -1f, checkVision : false, checkLOS : true, ignoreNonVisionSneakers : true, listenRange : 15f, hostileTargetsOnly : false, senseFriendlies : false, ignoreSafeZonePlayers : false, senseTypes : EntityType.Player, refreshKnownLOS : true );
//senseTypes: config.Settings.Management.TargetNpcs? EntityType.Player | EntityType.BasePlayerNPC : EntityType.Player,
CanUseHealingItems = true;
}
public override void AddStates() {
if (isDisabled) {
return;
}
base.AddStates();
states[AIState.Attack] = new AttackState(this);
}
public class AttackState : BaseAttackState {
private new HumanoidBrain brain;
private global : : HumanNPC npc;
private Transform NpcTransform;
private new IAIAttack attack => brain.Senses.ownerAttack;
public AttackState(HumanoidBrain humanoidBrain) {
base.brain = brain = humanoidBrain;
base.AgrresiveState = true;
npc = brain.GetBrainBaseEntity() as global : : HumanNPC;
NpcTransform = npc.transform;
}
public override void StateEnter(BaseAIBrain _brain, BaseEntity _entity) {
if (_brain != null && NpcTransform != null && brain.ValidTarget) {
if (InAttackRange()) {
StartAttacking();
}
if (brain.CanUseNavMesh()) {
brain.Navigator.SetDestination(brain.DestinationOverride, BaseNavigator.NavigationSpeed.Fast, 0f, 0f);
}
}
}
public override void StateLeave(BaseAIBrain _brain, BaseEntity _entity) {
}
private new void StopAttacking() {
if (attack != null) {
attack.StopAttacking();
brain.AttackTarget = null;
brain.AttackTransform = null;
brain.Navigator.ClearFacingDirectionOverride();
}
}
public override StateStatus StateThink(float delta, BaseAIBrain _brain, BaseEntity _entity) {
if (_brain == null || NpcTransform == null || attack == null) {
return StateStatus.Error;
}
if (brain.isDisabled || !brain.ValidTarget) {
StopAttacking();
return StateStatus.Finished;
}
if (brain.Senses.ignoreSafeZonePlayers && brain.AttackTarget.InSafeZone()) {
return StateStatus.Error;
}
if (brain.CanUseNavMesh() && !brain.Navigator.SetDestination(brain.DestinationOverride, BaseNavigator.NavigationSpeed.Fast, 0f, 0f)) {
return StateStatus.Error;
}
if (!brain.CanShoot()) {
brain.Forget();
StopAttacking();
return StateStatus.Finished;
}
if (InAttackRange()) {
StartAttacking();
}
return StateStatus.Running;
}
private bool InAttackRange() {
return !npc.IsWounded() && brain.AttackTransform != null && attack.CanAttack(brain.AttackTarget) && brain.IsInAttackRange() && brain.CanSeeTarget(brain.AttackTarget);
}
private void StartAttacking() {
if (brain.AttackTransform == null) {
return;
}
brain.SetAimDirection();
if (!brain.CanShoot() || brain.IsAttackOnCooldown()) {
return;
}
if (brain.attackType == AttackType.BaseProjectile) {
RealisticShotTest();
}
else if (brain.attackType == AttackType.FlameThrower) {
brain.UseFlameThrower();
}
else if (brain.attackType == AttackType.Water) {
brain.UseWaterGun();
}
else brain.MeleeAttack();
}
private void RealisticShotTest() {
if (NpcTransform == null || brain.baseProjectile == null || brain.baseProjectile.primaryMagazine == null) {
return;
}
if (brain.AttackTarget.IsNpc) {
var faction = brain.AttackTarget.faction;
brain.AttackTarget.faction = BaseCombatEntity.Faction.Horror;
npc.ShotTest(brain.AttackPosition.Distance(brain.ServerPosition));
if (brain.AttackTarget != null) brain.AttackTarget.faction = faction;
}
else npc.ShotTest(brain.AttackPosition.Distance(brain.ServerPosition));
}
}
private bool init;
public void Init() {
if (init) return;
init = true;
lastWarpTime = Time.time;
npc.spawnPos = tc.containerPos;
npc.AdditionalLosBlockingLayer = visibleMask;
SetupNavigator(GetEntity(), GetComponent<BaseNavigator>(), tc.Radius);
}
private void Converge() {
foreach (var brain in HumanoidBrains.Values) {
if (brain != null && brain.NpcTransform != null && brain != this && brain.attackType == attackType && brain.CanConverge(npc)) {
brain.SetTarget(AttackTarget, false);
}
}
}
public void Forget() {
Senses.Players.Clear();
Senses.Memory.All.Clear();
Senses.Memory.Threats.Clear();
Senses.Memory.Targets.Clear();
Senses.Memory.Players.Clear();
Navigator.ClearFacingDirectionOverride();
DestinationOverride = GetRandomRoamPosition();
SenseRange = ListenRange = isMurderer ? Settings.Murderers.AggressionRange : Settings.Scientists.AggressionRange;
Senses.targetLostRange = TargetLostRange = SenseRange * 1.25f;
AttackTarget = null;
AttackTransform = null;
TryReturnHome();
}
private void RandomMove(float radius) {
var to = AttackPosition + UnityEngine.Random.onUnitSphere * radius;
to.y = TerrainMeta.HeightMap.GetHeight(to);
SetDestination(to);
}
public void SetupNavigator(BaseCombatEntity owner, BaseNavigator navigator, float distance) {
navigator.CanUseNavMesh = !Rust.Ai.AiManager.nav_disable;
navigator.MaxRoamDistanceFromHome = navigator.BestMovementPointMaxDistance = navigator.BestRoamPointMaxDistance = distance * 0.85f;
navigator.DefaultArea = "Walkable";
navigator.topologyPreference = ((TerrainTopology.Enum)TerrainTopology.EVERYTHING);
navigator.Agent.agentTypeID = NavMesh.GetSettingsByIndex(1).agentTypeID;
// 0:0, 1: -1372625422, 2: 1479372276, 3: -1923039037
navigator.MaxWaterDepth = 3f;
if (navigator.CanUseNavMesh) {
navigator.Init(owner, navigator.Agent);
}
}
public Vector3 GetAimDirection() {
if (Navigator.IsOverridingFacingDirection) {
return Navigator.FacingDirectionOverride;
}
if (InRange2D(AttackPosition, ServerPosition, 1f)) {
return npc.eyes.BodyForward();
}
return (AttackPosition - ServerPosition).normalized;
}
private void SetAimDirection() {
Navigator.SetFacingDirectionEntity(AttackTarget);
npc.SetAimDirection(GetAimDirection());
}
private void SetDestination() {
SetDestination(GetRandomRoamPosition());
}
private void SetDestination(Vector3 destination) {
if (!CanLeave(destination)) {
if (attackType != AttackType.BaseProjectile) {
destination = ((destination.XZ3D() - tc.containerPos.XZ3D()).normalized * (tc.Radius * 0.75f)) + tc.containerPos;
destination += UnityEngine.Random.onUnitSphere * (tc.Radius * 0.2f);
}
else {
destination = GetRandomRoamPosition();
}
CurrentSpeed = BaseNavigator.NavigationSpeed.Normal;
}
if (destination != DestinationOverride) {
destination.y = TerrainMeta.HeightMap.GetHeight(destination);
DestinationOverride = destination;
}
Navigator.SetCurrentSpeed(CurrentSpeed);
if (Navigator.CurrentNavigationType == BaseNavigator.NavigationType.None && !Rust.Ai.AiManager.ai_dormant && !Rust.Ai.AiManager.nav_disable) {
Navigator.SetCurrentNavigationType(BaseNavigator.NavigationType.NavMesh);
}
if (CanUseNavMesh() && !Navigator.SetDestination(destination, CurrentSpeed)) {
Navigator.Destination = destination;
npc.finalDestination = destination;
}
}
public bool CanUseNavMesh() => Navigator.CanUseNavMesh && !Navigator.StuckOffNavmesh;
public bool SetTarget(BasePlayer player, bool converge = true) {
if (isDisabled) {
return false;
}
if (NpcTransform == null) {
DisableShouldThink();
Destroy(this);
return false;
}
if (player.IsKilled() || player.limitNetworking) {
return false;
}
if (AttackTarget == player) {
return true;
}
if (npc.lastGunShotTime < Time.time + 2f) {
npc.nextTriggerTime = Time.time + 0.2f;
nextAttackTime = Time.realtimeSinceStartup + 0.2f;
}
Senses.Memory.SetKnown(player, npc, null);
npc.lastAttacker = player;
AttackTarget = player;
AttackTransform = player.transform;
if (!IsInSenseRange(AttackPosition)) {
SenseRange = ListenRange = (isMurderer ? Settings.Murderers.AggressionRange : Settings.Scientists.AggressionRange) + AttackPosition.Distance(ServerPosition);
TargetLostRange = SenseRange + (SenseRange * 0.25f);
}
else {
SenseRange = ListenRange = softLimitSenseRange;
TargetLostRange = softLimitSenseRange * 1.25f;
}
if (converge) {
Converge();
}
return true;
}
private void TryReturnHome() {
if (Settings.CanLeave && !IsInHomeRange()) {
CurrentSpeed = BaseNavigator.NavigationSpeed.Normal;
Warp();
}
}
private void TryToAttack() => TryToAttack(null);
private void TryToAttack(BasePlayer attacker) {
if (isDisabled) {
return;
}
if ((attacker ??= GetBestTarget()).IsNull()) {
return;
}
if (ShouldForgetTarget(attacker)) {
Forget();
return;
}
if (!SetTarget(attacker) || AttackTransform == null || !CanSeeTarget(attacker)) {
return;
}
if (attackType == AttackType.BaseProjectile) {
TryScientistActions();
}
else {
TryMurdererActions();
}
SwitchToState(AIState.Attack, -1);
}
private void TryMurdererActions() {
CurrentSpeed = BaseNavigator.NavigationSpeed.Fast;
if (!IsInReachableRange()) {
RandomMove(15f);
}
else if (!IsInAttackRange()) {
if (attackType == AttackType.FlameThrower) {
RandomMove(attackRange);
}
else {
SetDestination(AttackPosition);
}
}
}
private void TryScientistActions() {
CurrentSpeed = BaseNavigator.NavigationSpeed.Fast;
SetDestination();
}
public void SetupMovement(List<Vector3> positions) {
if (isDisabled || tc == null || tc.killed || tc.IsUnloading || npc.IsKilled() || npc.IsDead() || npc.Health() <= 0) {
return;
}
this.positions = positions;
InvokeRepeating(TryToRoam, 0f, 7.5f);
InvokeRepeating(TryToAttack, 1f, 1f);
}
private void TryToRoam() {
if (isDisabled) {
return;
}
if (Settings.KillUnderwater && npc.playerCollider != null && npc.IsSwimming()) {
npc.SafelyKill();
return;
}
if (ValidTarget) {
return;
}
if (IsStuck()) {
Warp();
Navigator.stuckTimer = 0f;
}
CurrentSpeed = BaseNavigator.NavigationSpeed.Normal;
SetDestination();
}
private bool IsStuck() => false;
//InRange(npc.transform.position, Navigator.stuckCheckPosition, Navigator.StuckDistance);
public void Warp() {
if (Time.time < lastWarpTime) {
return;
}
lastWarpTime = Time.time + 1f;
DestinationOverride = GetRandomRoamPosition();
Navigator.Warp(DestinationOverride);
}
private void UseFlameThrower() {
if (flameThrower.ammo < flameThrower.maxAmmo * 0.25) {
flameThrower.SetFlameState(false);
flameThrower.ServerReload();
return;
}
npc.triggerEndTime = Time.time + attackCooldown;
flameThrower.SetFlameState(true);
flameThrower.Invoke(() => flameThrower.SetFlameState(false), 2f);
}
private void UseWaterGun() {
if (Physics.Raycast(npc.eyes.BodyRay(), out var hit, 10f, 1218652417)) {
WaterBall.DoSplash(hit.point, 2f, ItemManager.FindItemDefinition("water"), 10);
DamageUtil.RadiusDamage(npc, liquidWeapon.LookupPrefab(), hit.point, 0.15f, 0.15f, new(), 131072, true);
}
}
private void UseChainsaw() {
AttackEntity.TopUpAmmo();
AttackEntity.ServerUse();
AttackTarget.Hurt(10f * AttackEntity.npcDamageScale, DamageType.Bleeding, npc);
}
private void MeleeAttack() {
if (baseMelee.IsNull()) {
return;
}
if (AttackEntity is Chainsaw) {
UseChainsaw();
return;
}
Vector3 position = AttackPosition;
AttackEntity.StartAttackCooldown(AttackEntity.repeatDelay * 2f);
npc.SignalBroadcast(BaseEntity.Signal.Attack, string.Empty, null);
if (baseMelee.swingEffect.isValid) {
Effect.server.Run(baseMelee.swingEffect.resourcePath, position, Vector3.forward, npc.Connection, false);
}
HitInfo hitInfo = new() {
damageTypes = new(), DidHit = true, Initiator = npc, HitEntity = AttackTarget, HitPositionWorld = position, HitPositionLocal = AttackTransform.InverseTransformPoint(position), HitNormalWorld = npc.eyes.BodyForward(), HitMaterial = StringPool.Get("Flesh"), PointStart = ServerPosition, PointEnd = position, Weapon = AttackEntity, WeaponPrefab = AttackEntity
}
;
hitInfo.damageTypes.Set(DamageType.Slash, baseMelee.TotalDamage() * AttackEntity.npcDamageScale);
Effect.server.ImpactEffect(hitInfo);
AttackTarget.OnAttacked(hitInfo);
}
private bool CanConverge(global : : HumanNPC other) {
if (ValidTarget || other.IsKilled() || other.IsDead()) return false;
return IsInTargetRange(other.transform.position);
}
private bool CanLeave(Vector3 destination) {
return Settings.CanLeave || IsInLeaveRange(destination);
}
private bool CanSeeTarget(BasePlayer target) {
if (Navigator.CurrentNavigationType == BaseNavigator.NavigationType.None && (attackType == AttackType.FlameThrower || attackType == AttackType.Melee)) {
return true;
}
if (ServerPosition.Distance(target.ServerPosition) < 10f || Senses.Memory.IsLOS(target)) {
return true;
}
nextAttackTime = Time.realtimeSinceStartup + 1f;
return false;
}
public bool CanRoam(Vector3 destination) {
return destination == DestinationOverride && IsInSenseRange(destination);
}
private bool CanShoot() {
if (attackType == AttackType.None) {
return false;
}
return true;
}
public BasePlayer GetBestTarget() {
if (npc.IsWounded()) {
return null;
}
float delta = -1f;
BasePlayer target = null;
foreach (var player in Senses.Memory.Targets.OfType<BasePlayer>()) {
if (ShouldForgetTarget(player) || !player.IsHuman() && !Options.NPC.TargetNpcs) continue;
float dist = player.transform.position.Distance(npc.transform.position);
float rangeDelta = 1f - Mathf.InverseLerp(1f, SenseRange, dist);
rangeDelta += (CanSeeTarget(player) ? 2f : 0f);
if (rangeDelta <= delta) continue;
target = player;
delta = rangeDelta;
}
return target;
}
private Vector3 GetRandomRoamPosition() {
return positions.GetRandom();
}
private bool IsAttackOnCooldown() {
if (attackType == AttackType.None || Time.realtimeSinceStartup < nextAttackTime) {
return true;
}
if (attackCooldown > 0f) {
nextAttackTime = Time.realtimeSinceStartup + attackCooldown;
}
return false;
}
private bool IsInAttackRange(float range = 0f) {
return InRange(ServerPosition, AttackPosition, range == 0f ? attackRange : range);
}
private bool IsInHomeRange() {
return InRange(ServerPosition, tc.containerPos, Mathf.Max(tc.Radius, TargetLostRange));
}
private bool IsInLeaveRange(Vector3 destination) {
return InRange(tc.containerPos, destination, tc.Radius);
}
private bool IsInReachableRange() {
if (AttackPosition.y - ServerPosition.y > attackRange) {
return false;
}
return attackType != AttackType.Melee || InRange(AttackPosition, ServerPosition, 15f);
}
private bool IsInSenseRange(Vector3 destination) {
return InRange2D(tc.containerPos, destination, SenseRange);
}
private bool IsInTargetRange(Vector3 destination) {
return InRange2D(tc.containerPos, destination, TargetLostRange);
}
private bool ShouldForgetTarget(BasePlayer target) {
return target.IsKilled() || target.health <= 0f || target.limitNetworking || target.IsDead() || target.skinID == 14922524 || !IsInTargetRange(target.transform.position);
}
}
private class GuidanceSystem : FacepunchBehaviour {
private TimedExplosive missile;
private ServerProjectile projectile;
private BaseEntity target;
private Vector3 launchPos;
private List<ulong> newmans = new();
internal DangerousTreasures Instance;
internal Configuration config;
internal DifficultyLevel Options;
private void Awake() {
missile = GetComponent<TimedExplosive>();
projectile = missile.GetComponent<ServerProjectile>();
launchPos = missile.transform.position;
launchPos.y = TerrainMeta.HeightMap.GetHeight(launchPos);
projectile.gravityModifier = 0f;
projectile.speed = 0.1f;
projectile.InitializeVelocity(Vector3.up);
missile.explosionRadius = 0f;
missile.damageTypes = new();
// no damage
}
public void SetTarget(BaseEntity target) {
this.target = target;
}
public void Launch(float targettingTime) {
missile.timerAmountMin = Options.MissileLauncher.Lifetime;
missile.timerAmountMax = Options.MissileLauncher.Lifetime;
missile.Spawn();
Instance.timer.Once(targettingTime, () => {
if (missile.IsKilled()) return;
using var list = Pool.Get<PooledList<BasePlayer>>();
using var players = FindEntitiesOfType<BasePlayer>(launchPos, Options.Event.Radius + Options.MissileLauncher.Distance, Layers.Mask.Player_Server);
for (int i = 0; i < players.Count; i++) {
var player = players[i];
if (player.IsKilled() || !player.IsHuman() || !player.CanInteract()) continue;
if (Options.MissileLauncher.IgnoreFlying && player.IsFlying) continue;
if (newmans.Contains(player.userID) || Instance.newmanProtections.Contains(player.userID)) continue;
list.Add(player);
// acquire a player target
}
if (list.Count > 0) {
target = list.GetRandom();
// pick a random player
}
else if (!Options.MissileLauncher.TargetChest) {
missile.SafelyKill();
return;
}
projectile.speed = config.Rocket.Speed * 2f;
InvokeRepeating(GuideMissile, 0.1f, 0.1f);
}
);
}
public void Exclude(List<ulong> newmans) {
if (newmans != null && newmans.Count > 0) {
this.newmans = newmans.ToList();
}
}
private void GuideMissile() {
if (target == null) return;
if (target.IsDestroyed) {
missile.SafelyKill();
return;
}
if (missile.IsKilled() || projectile == null) {
Destroy(this);
return;
}
if (InRange(target.transform.position, missile.transform.position, 1f)) {
missile.Explode();
return;
}
var direction = (target.transform.position - missile.transform.position) + Vector3.down;
// direction to guide the missile
projectile.InitializeVelocity(direction);
// guide the missile to the target's position
}
private void OnDestroy() {
try {
CancelInvoke();
}
catch {
}
Destroy(this);
}
}
public class TreasureChest : FacepunchBehaviour {
internal Dictionary<ulong, HumanoidBrain> HumanoidBrains;
internal DangerousTreasures Instance;
internal ulong userid;
internal GameObject go;
internal StorageContainer container;
internal Vector3 containerPos;
internal Vector3 lastFirePos;
internal int npcMaxAmountMurderers;
internal int npcMaxAmountScientists;
internal int npcSpawnedAmount;
internal int countdownTime;
internal bool started;
internal bool opened;
internal bool firstEntered;
internal bool markerCreated;
internal bool killed;
internal bool IsUnloading;
internal bool requireAllNpcsDie;
internal bool whenNpcsDie;
internal float claimTime;
internal float _radius;
internal long _unlockTime;
internal NetworkableId uid;
private Dictionary<string, List<string>> npcKits = Pool.Get<Dictionary<string, List<string>>>();
private Dictionary<ulong, float> fireticks = Pool.Get<Dictionary<ulong, float>>();
private List<FireBall> fireballs = Pool.Get<List<FireBall>>();
private List<ulong> newmans = Pool.Get<List<ulong>>();
private List<ulong> traitors = Pool.Get<List<ulong>>();
private List<ulong> protects = Pool.Get<List<ulong>>();
private List<ulong> players = Pool.Get<List<ulong>>();
private List<TimedExplosive> missiles = Pool.Get<List<TimedExplosive>>();
private List<int> times = Pool.Get<List<int>>();
private List<SphereE
// ... [Code preview truncated due to length. Full source in attached file] ...