C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Core.Libraries.Covalence;
using Rust;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.AI;
namespace Oxide.Plugins
{
[Info("PlaneCrash", "SkuliDropek", "0.2.2")]
[Description("Call cargo planes that can be shot down by players to score loot")]
class PlaneCrash : RustPlugin
{
#region Fields
[PluginReference] Plugin Clans, Friends, FancyDrop, LustyMap, Kits;
private StoredData storedData;
private DynamicConfigFile data;
private static PlaneCrash Instance { get; set; }
private float mapSize;
private Timer callTimer;
private bool initialized;
private Dictionary<ulong, InventoryData> deadMemberIds = new Dictionary<ulong, InventoryData>();
// Effects
private const string C4EXPLOSION_EFFECT = "assets/prefabs/tools/c4/effects/c4_explosion.prefab";
private const string HELIEXPLOSION_EFFECT = "assets/prefabs/npc/patrol helicopter/effects/heli_explosion.prefab";
private const string DEBRIS_EFFECT = "assets/prefabs/npc/patrol helicopter/damage_effect_debris.prefab";
private const string FIREBALL_EFFECT = "assets/bundled/prefabs/oilfireballsmall.prefab";
private const string SMOKE_EFFECT = "assets/bundled/prefabs/fx/smoke_signal_full.prefab";
// Prefabs
private const string CARGOPLANE_PREFAB = "assets/prefabs/npc/cargo plane/cargo_plane.prefab";
private const string GIBS_PREFAB = "assets/prefabs/npc/patrol helicopter/servergibs_patrolhelicopter.prefab";
private const string CRATE_PREFAB = "assets/prefabs/npc/patrol helicopter/heli_crate.prefab";
private const string SUPPLYDROP_PREFAB = "assets/prefabs/misc/supply drop/supply_drop.prefab";
private const string ZOMBIE_PREFAB = "assets/prefabs/npc/murderer/murderer.prefab";
private const string SCIENTIST_PREFAB = "assets/prefabs/npc/scientist/scientist.prefab";
private const string HEAVYSCIENTIST_PREFAB = "assets/rust.ai/agents/npcplayer/humannpc/heavyscientist/heavyscientist.prefab";
private const string CORPSE_PREFAB = "assets/prefabs/player/player_corpse.prefab";
private const string DEBRISMARKER_PREFAB = "assets/prefabs/tools/map/explosionmarker.prefab";
#endregion
#region Oxide Hooks
private void Loaded()
{
mapSize = TerrainMeta.Size.x;
data = Interface.Oxide.DataFileSystem.GetFile("planecrash_cooldowns");
foreach (KeyValuePair<string, int> kvp in configData.Cooldowns)
{
if (!kvp.Key.StartsWith("planecrash."))
permission.RegisterPermission("planecrash." + kvp.Key, this);
else permission.RegisterPermission(kvp.Key, this);
}
permission.RegisterPermission("planecrash.cancall", this);
lang.RegisterMessages(Messages, this);
}
private void OnServerInitialized()
{
Instance = this;
initialized = true;
LoadData();
if (configData.EventTimers.Random)
StartCrashTimer();
}
private void OnServerSave() => SaveData();
private void OnEntitySpawned(BaseNetworkable entity)
{
if (!initialized || entity == null)
return;
if (entity is CargoPlane)
{
if (!configData.Plane.ApplyToAll)
return;
NextTick(() =>
{
if (entity == null)
return;
object success = Interface.Call("isStrikePlane", entity as CargoPlane);
if (success is bool && (bool)success)
return;
object location = (entity as CargoPlane).dropPosition;
if (!(location is Vector3))
location = Vector3Ex.Range(-(mapSize / 6f), mapSize / 6f);
timer.In(2, () => AddCrashComponent(entity as CargoPlane, (Vector3)location, true));
});
}
if (entity is LootableCorpse)
{
if (!configData.NPCOptions.ReplaceCorpseLoot)
return;
LootableCorpse corpse = entity as LootableCorpse;
InventoryData inventoryData;
if (!deadMemberIds.TryGetValue(corpse.playerSteamID, out inventoryData))
return;
deadMemberIds.Remove(corpse.playerSteamID);
timer.In(2, () =>
{
if (inventoryData != null)
inventoryData.RestoreItemsTo(corpse);
corpse.ResetRemovalTime(configData.NPCOptions.DespawnTime);
});
}
}
private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || !configData.NPCOptions.ReplaceCorpseLoot)
return;
NPCController npcController = entity.GetComponent<NPCController>();
if (npcController != null)
{
if (!deadMemberIds.ContainsKey(npcController.Entity.userID))
deadMemberIds.Add(npcController.Entity.userID, new InventoryData(npcController.Entity));
StripInventory(npcController.Entity, true);
return;
}
}
private void OnEntityKill(BaseNetworkable entity)
{
if (entity == null)
return;
CrashPlane crashComponent = entity.GetComponent<CrashPlane>();
if (crashComponent != null)
UnityEngine.Object.Destroy(crashComponent);
}
private object CanLootEntity(BasePlayer player, LootContainer container)
{
LootLock lootLock = container.GetComponent<LootLock>();
if (lootLock != null && !lootLock.IsUnlocked())
{
if (player.userID == lootLock.ownerID)
return null;
if (configData.Loot.LockSettings.LockToPlayerTeam)
{
RelationshipManager.PlayerTeam ownerTeam = RelationshipManager.ServerInstance.FindPlayersTeam(lootLock.ownerID);
RelationshipManager.PlayerTeam playerTeam = RelationshipManager.ServerInstance.FindPlayersTeam(player.userID);
if (ownerTeam != null && playerTeam != null && ownerTeam == playerTeam)
return null;
}
if (configData.Loot.LockSettings.LockToPlayerClans && IsClanmate(lootLock.ownerID, player.userID))
return null;
if (configData.Loot.LockSettings.LockToPlayerFriends && AreFriends(lootLock.ownerID, player.userID))
return null;
player.ChatMessage(msg("LootLocked", player.UserIDString));
return false;
}
return null;
}
private void Unload()
{
CrashPlane[] planes = UnityEngine.Object.FindObjectsOfType<CrashPlane>();
for (int i = 0; i < planes?.Length; i++)
UnityEngine.Object.Destroy(planes[i]);
configData = null;
Instance = null;
}
#endregion
#region Components
private enum FlightStatus { Flying, Crashing }
private class CrashPlane : MonoBehaviour
{
private CargoPlane entity;
private Transform tr;
public FlightStatus status;
private BasePlayer lastAttacker;
private FireBall engineFireLeft;
private FireBall engineFireRight;
private Vector3 engineFireLeftOffset = new Vector3(-10, 6, 10);
private Vector3 engineFireRightOffset = new Vector3(10, 6, 10);
private Vector3 startPos;
private Vector3 endPos;
private int rocketHits;
private float speed;
private float currentSpeed;
private float crashTimeTaken;
private float crashTimeToTake;
private bool isDying;
private bool isSmoking;
private bool hasDropped;
private bool isDropPlane;
private bool isFancyDrop;
private bool willAutoCrash;
private float autoCrashAt;
private float randomCrashDirection = -1;
private float crashDelay = 0;
private Vector3 baseEulerAngles;
private Type[] interactableTypes = new Type[] { typeof(TreeEntity), typeof(ResourceEntity), typeof(BuildingBlock), typeof(SimpleBuildingBlock), typeof(BaseHelicopter) };
private void Awake()
{
entity = GetComponent<CargoPlane>();
entity.enabled = false;
tr = entity.transform;
gameObject.layer = (int)Layer.Reserved2;
gameObject.name = "CrashPlane";
AttachCollider();
speed = currentSpeed = configData.Plane.Speed;
crashTimeTaken = 0;
crashTimeToTake = 20;
status = FlightStatus.Flying;
willAutoCrash = UnityEngine.Random.Range(0, 100) < configData.Plane.AutoCrashChance;
autoCrashAt = UnityEngine.Random.Range(configData.Plane.CrashMinimumRange, configData.Plane.CrashMaximumRange);
if (configData.Plane.RandomizeCrashDirection)
randomCrashDirection = UnityEngine.Random.value;
}
private void Update()
{
if (tr.position.y <= 0)
{
if (!isDying)
Die();
return;
}
if (status == FlightStatus.Crashing)
{
crashTimeTaken = crashTimeTaken + Time.deltaTime;
float delta = Mathf.InverseLerp(0, crashTimeToTake, crashTimeTaken);
if (delta < 1)
{
currentSpeed = speed + Mathf.Lerp(0, 10, delta);
Vector3 direction = baseEulerAngles;
direction.x = Mathf.Lerp(0, 25f, delta);
if (randomCrashDirection != -1)
{
float yaw = Mathf.Lerp(0f, 25f, delta * 1.75f);
float roll = Mathf.Lerp(0f, 35f, delta * 2.25f);
if (randomCrashDirection <= 0.5f)
{
direction.y -= yaw;
direction.z += roll;
}
else
{
direction.y += yaw;
direction.z -= roll;
}
}
tr.eulerAngles = direction;
}
tr.position = Vector3.MoveTowards(tr.position, tr.position + (tr.forward * 10), currentSpeed * UnityEngine.Time.deltaTime);
}
else
{
tr.position = Vector3.MoveTowards(tr.position, endPos, currentSpeed * UnityEngine.Time.deltaTime);
float delta = InverseLerp(startPos, endPos, tr.position);
if (isDropPlane)
{
entity.secondsTaken = entity.secondsToTake * delta;
if (!hasDropped && delta >= 0.5f)
{
hasDropped = true;
if (!Instance.FancyDrop || !isFancyDrop)
{
BaseEntity drop = GameManager.server.CreateEntity(SUPPLYDROP_PREFAB, tr.position);
drop.globalBroadcast = true;
drop.Spawn();
}
}
}
if (willAutoCrash)
{
if (delta >= autoCrashAt && crashDelay < Time.time)
{
if (TerrainMeta.HeightMap.GetHeight(tr.position) < 0)
{
crashDelay = Time.time + 3f;
return;
}
SendChatMessage("AutoCrashMessage");
BeginCrash();
}
}
}
if (engineFireLeft != null)
engineFireLeft.transform.position = tr.TransformPoint(engineFireLeftOffset);
if (engineFireRight != null)
engineFireRight.transform.position = tr.TransformPoint(engineFireRightOffset);
tr.hasChanged = true;
}
private float InverseLerp(Vector3 a, Vector3 b, Vector3 value)
{
Vector3 ab = b - a;
Vector3 av = value - a;
return Vector3.Dot(av, ab) / Vector3.Dot(ab, ab);
}
private void OnDestroy()
{
enabled = false;
CancelInvoke();
if (engineFireLeft != null && !engineFireLeft.IsDestroyed)
engineFireLeft.Extinguish();
if (engineFireRight != null && !engineFireRight.IsDestroyed)
engineFireRight.Extinguish();
if (entity != null && !entity.IsDestroyed)
entity.Kill();
}
private void OnTriggerEnter(Collider col)
{
if (col is TerrainCollider)
{
Die();
return;
}
BasePlayer player = col.gameObject.GetComponentInParent<BasePlayer>();
if (player != null && col.gameObject.layer == 17)
{
if (player.IsNpc)
return;
player.Die();
return;
}
ServerProjectile serverProjectile = col.GetComponentInParent<ServerProjectile>();
if (serverProjectile != null)
{
SmallExplosion();
col.GetComponentInParent<TimedExplosive>()?.Explode();
rocketHits++;
BasePlayer attacker = col.GetComponent<BaseEntity>()?.creatorEntity?.ToPlayer();
if (configData.Loot.CrateHit > 0)
ServerMgr.Instance.StartCoroutine(SpawnLoot(configData.Loot.CrateHit, false));
if (configData.Loot.SupplyHit > 0)
ServerMgr.Instance.StartCoroutine(SpawnLoot(configData.Loot.SupplyHit, true));
if (rocketHits == 1)
AddFire(ref engineFireLeft);
if (rocketHits >= configData.Plane.DestroyHits)
{
if (attacker != null)
lastAttacker = attacker;
if (configData.Messages.DisplayAttacker && lastAttacker != null)
SendChatMessage("AttackerMessage2", lastAttacker.displayName);
Die();
return;
}
if (rocketHits >= configData.Plane.DownHits && status == FlightStatus.Flying)
{
if (attacker != null)
lastAttacker = attacker;
if (lastAttacker != null && configData.Messages.DisplayAttacker)
SendChatMessage("AttackerMessage1", lastAttacker.displayName);
BeginCrash();
return;
}
}
if (configData.Plane.Destruction)
{
for (int i = 0; i < interactableTypes.Length; i++)
{
if (col.GetComponentInParent(interactableTypes[i]) != null)
{
BaseEntity baseEntity = col.GetComponentInParent<BaseEntity>();
BaseCombatEntity baseCombatEntity = baseEntity as BaseCombatEntity;
if (baseCombatEntity != null)
{
baseCombatEntity.Die();
return;
}
if (baseEntity != null)
{
baseEntity.Kill(BaseNetworkable.DestroyMode.Gib);
return;
}
}
}
}
}
#region Initialization
public void SetFlightPath(Vector3 newDropPosition, bool isStandard = false)
{
if (entity == null)
{
print("[ERROR] Error in SetFlightPath, the plane is null.Something (likely another plugin) has destroyed it...");
return;
}
if (!isStandard)
{
if (configData.Plane.RandomDistance != 0)
newDropPosition += new Vector3(UnityEngine.Random.Range(-configData.Plane.RandomDistance, configData.Plane.RandomDistance), 0, UnityEngine.Random.Range(-configData.Plane.RandomDistance, configData.Plane.RandomDistance));
float size = TerrainMeta.Size.x;
startPos = Vector3Ex.Range(-1f, 1f);
startPos.y = 0f;
startPos.Normalize();
startPos = startPos * (size * 2f);
endPos = startPos * -1f;
startPos = startPos + newDropPosition;
endPos = endPos + newDropPosition;
startPos.y = 150 + configData.Plane.Height;
endPos.y = startPos.y;
if (configData.Plane.SmokeTrail)
RunEffect(SMOKE_EFFECT, entity, new Vector3(), Vector3.up * 3);
}
else
{
startPos = entity.startPos;
endPos = entity.endPos;
isDropPlane = true;
}
tr.position = startPos;
tr.rotation = Quaternion.LookRotation(endPos - startPos);
if (configData.Plane.RandomizeCrashDirection)
{
Vector3 perp = Vector3.Cross(tr.forward, Vector3.zero - tr.position);
float dir = Vector3.Dot(perp, Vector3.up);
randomCrashDirection = dir >= 0 ? 1 : 0f;
}
entity.secondsToTake = Vector3.Distance(startPos, endPos) / speed;
isFancyDrop = Instance.FancyDrop ? (bool)Instance.FancyDrop?.CallHook("IsFancyDrop", entity) : false;
if (isDropPlane && isFancyDrop)
Instance.FancyDrop?.CallHook("OverrideDropTime", entity, entity.secondsToTake);
Destroy(this, entity.secondsToTake);
}
private void AttachCollider()
{
Rigidbody rigidbody = gameObject.AddComponent<Rigidbody>();
rigidbody.useGravity = false;
rigidbody.isKinematic = true;
rigidbody.detectCollisions = true;
rigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete;
BoxCollider collider = gameObject.AddComponent<BoxCollider>();
collider.isTrigger = true;
collider.size = new Vector3(50, 10, 50);
collider.transform.localPosition = Vector3.up * 6;
}
public void BeginCrash()
{
endPos = new Vector3(endPos.x, 0, endPos.z);
status = FlightStatus.Crashing;
baseEulerAngles = tr.eulerAngles;
SmallExplosion();
AddFire(ref engineFireLeft);
AddFire(ref engineFireRight);
}
#endregion
#region Effects
private void BigExplosion()
{
RunEffect(HELIEXPLOSION_EFFECT, null, tr.position);
RunEffect(DEBRIS_EFFECT, null, tr.position);
}
private void SmallExplosion()
{
RunEffect(C4EXPLOSION_EFFECT, null, tr.position);
RunEffect(DEBRIS_EFFECT, null, tr.position);
}
private void AddFire(ref FireBall fireBall, float lifetime = 600)
{
if (fireBall != null)
return;
fireBall = GameManager.server.CreateEntity(FIREBALL_EFFECT, tr.position) as FireBall;
fireBall.enableSaving = false;
fireBall.Spawn();
Rigidbody rb = fireBall.GetComponent<Rigidbody>();
rb.isKinematic = true;
fireBall.CancelInvoke(fireBall.Extinguish);
fireBall.CancelInvoke(fireBall.TryToSpread);
fireBall.Invoke(fireBall.Extinguish, lifetime);
}
#endregion
private void Die()
{
if (isDying)
return;
isDying = true;
BigExplosion();
CreateLootSpawns();
if (configData.NPCOptions.Enabled)
ServerMgr.Instance.StartCoroutine(CreateNPCs(tr.position));
if (configData.LustyOptions.CrashIcon)
InvokeHandler.Invoke(this, UpdateCrashMarker, 1.5f);
if (configData.MapOptions.CrashIcon)
InvokeHandler.Invoke(this, UpdateMapMarker, 1.5f);
InvokeHandler.Invoke(this, SmallExplosion, 0.25f);
InvokeHandler.Invoke(this, SmallExplosion, 0.5f);
InvokeHandler.Invoke(this, BigExplosion, 1.25f);
InvokeHandler.Invoke(this, SmallExplosion, 1.75f);
InvokeHandler.Invoke(this, BigExplosion, 2.25f);
Destroy(this, 2.5f);
}
#region Loot
private void CreateLootSpawns()
{
List<ServerGib> serverGibs = ServerGib.CreateGibs(GIBS_PREFAB, gameObject, entity.gameObject, tr.forward * 2, 5f);
for (int i = 0; i < 12; i++)
{
BaseEntity fireBall = GameManager.server.CreateEntity(FIREBALL_EFFECT, tr.position, tr.rotation, true);
if (fireBall)
{
Vector3 randsphere = UnityEngine.Random.onUnitSphere;
fireBall.transform.position = (tr.position + new Vector3(0f, 1.5f, 0f)) + (randsphere * UnityEngine.Random.Range(-4f, 4f));
Collider collider = fireBall.GetComponent<Collider>();
fireBall.Spawn();
fireBall.SetVelocity(tr.forward + randsphere);
foreach (ServerGib serverGib in serverGibs)
Physics.IgnoreCollision(collider, serverGib.GetCollider(), true);
}
}
if (configData.Loot.CrateCrash > 0)
ServerMgr.Instance.StartCoroutine(SpawnLoot(configData.Loot.CrateCrash, false, true));
if (configData.Loot.SupplyCrash > 0)
ServerMgr.Instance.StartCoroutine(SpawnLoot(configData.Loot.SupplyCrash, true, true));
if (configData.Messages.DisplayDestroy)
{
if (configData.Messages.UseGrid)
SendChatMessage("DestroyMessage.Grid", GetGridString(tr.position));
else SendChatMessage("DestroyMessage", tr.position.x, tr.position.z);
}
}
private IEnumerator SpawnLoot(int amount, bool isDrop, bool isCrashing = false)
{
for (int j = 0; j < amount; j++)
{
Vector3 position = (tr.position + new Vector3(0f, 1.5f, 0f)) + (UnityEngine.Random.onUnitSphere * UnityEngine.Random.Range(-2f, 3f));
position.y = Mathf.Max(TerrainMeta.HeightMap.GetHeight(position), position.y);
string ent = isDrop ? SUPPLYDROP_PREFAB : CRATE_PREFAB;
LootContainer container = GameManager.server.CreateEntity(ent, position, Quaternion.LookRotation(UnityEngine.Random.onUnitSphere), true) as LootContainer;
container.enableSaving = false;
container.Spawn();
if (configData.Loot.LockSettings.LockToPlayer && lastAttacker != null)
LootLock.Initialize(container, lastAttacker.userID);
if (j == 0 && configData.Plane.Smoke && isCrashing && !isSmoking)
{
RunEffect(SMOKE_EFFECT, container);
isSmoking = true;
}
Rigidbody rigidbody;
if (!isDrop)
rigidbody = container.gameObject.AddComponent<Rigidbody>();
else
{
container.GetComponent<SupplyDrop>().RemoveParachute();
rigidbody = container.GetComponent<Rigidbody>();
}
if (rigidbody != null)
{
rigidbody.isKinematic = false;
rigidbody.useGravity = true;
rigidbody.mass = 1.25f;
rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
rigidbody.drag = 0.25f;
rigidbody.angularDrag = 0.1f;
rigidbody.AddForce((tr.forward + (tr.right * UnityEngine.Random.Range(-10f, 10f))) * 100);
}
FireBall fireBall = null;
AddFire(ref fireBall, configData.Loot.FireLife);
fireBall.SetParent(container, 0, false, true);
InvokeHandler.Invoke(container, () => FillLootContainer(container, isDrop), 2f);
if (configData.Loot.LootDespawnTime > 0)
{
container.Invoke(() =>
{
if (container.HasFlag(BaseEntity.Flags.Open))
return;
container?.Kill();
},
configData.NPCOptions.DespawnTime);
}
yield return CoroutineEx.waitForEndOfFrame;
}
}
#endregion
#region NPCs
private IEnumerator CreateNPCs(Vector3 position)
{
string type = configData.NPCOptions.Type.ToLower() == "murderer" ? ZOMBIE_PREFAB : configData.NPCOptions.Type.ToLower() == "heavyscientist" ? HEAVYSCIENTIST_PREFAB : SCIENTIST_PREFAB;
int amount = configData.NPCOptions.Amount + (configData.NPCOptions.CorpseEnabled ? configData.NPCOptions.CorpseAmount : 0);
for (int i = 0; i < amount; i++)
{
Vector3 newPosition = position + (UnityEngine.Random.onUnitSphere * 20);
object point = Instance.FindPointOnNavmesh(newPosition);
if (point is Vector3)
newPosition = (Vector3)point;
else newPosition.y = TerrainMeta.HeightMap.GetHeight(newPosition);
if (newPosition.y < -0.25f)
continue;
NPCPlayer npcPlayer = InstantiateEntity(type, newPosition);
npcPlayer.enableSaving = false;
npcPlayer.Spawn();
bool isCorpse = i >= configData.NPCOptions.Amount;
NPCController npcController = npcPlayer.gameObject.AddComponent<NPCController>();
npcController.Setup(lastAttacker, isCorpse);
yield return CoroutineEx.waitForEndOfFrame;
}
}
private NPCPlayer InstantiateEntity(string type, Vector3 position)
{
GameObject gameObject = Facepunch.Instantiate.GameObject(GameManager.server.FindPrefab(type), position, new Quaternion());
gameObject.name = type;
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(gameObject, Rust.Server.EntityScene);
Destroy(gameObject.GetComponent<Spawnable>());
if (!gameObject.activeSelf)
gameObject.SetActive(true);
NPCPlayer component = gameObject.GetComponent<NPCPlayer>();
return component;
}
#endregion
#region Map Markers
private void UpdateCrashMarker()
{
if (!Instance.LustyMap || entity == null)
return;
Instance.LustyMap.Call("AddTemporaryMarker", tr.position.x, tr.position.z, "Crashed Plane", configData.LustyOptions.IconURL, 0);
Instance.timer.In(configData.LustyOptions.CrashIconTime, () => Instance.LustyMap.Call("RemoveTemporaryMarker", "Crashed Plane"));
}
private void UpdateMapMarker()
{
if (entity == null)
return;
BaseEntity baseEntity = GameManager.server.CreateEntity(DEBRISMARKER_PREFAB, tr.position, Quaternion.identity, true);
baseEntity.Spawn();
baseEntity.SendMessage("SetDuration", configData.MapOptions.CrashIconTime, SendMessageOptions.DontRequireReceiver);
}
#endregion
}
public class LootLock : MonoBehaviour
{
internal LootContainer Container { get; private set; }
internal ulong ownerID;
private float lockExpiretime;
private void Awake()
{
Container = GetComponent<LootContainer>();
lockExpiretime = Time.time + configData.Loot.LockSettings.LockTime;
Destroy(this, configData.Loot.LockSettings.LockTime);
}
internal bool IsUnlocked() => Time.time >= lockExpiretime;
internal static void Initialize(LootContainer container, ulong ownerId)
{
LootLock lootLock = container.gameObject.AddComponent<LootLock>();
lootLock.ownerID = ownerId;
}
}
public class NPCController : MonoBehaviour
{
public NPCPlayer Entity { get; private set; }
private Vector3 basePosition;
private BasePlayer lastAttacker;
private float woundedDuration;
private float woundedStartTime;
private float secondsSinceWoundedStarted
{
get
{
return Time.realtimeSinceStartup - this.woundedStartTime;
}
}
private void Awake()
{
Entity = GetComponent<NPCPlayer>();
enabled = false;
basePosition = Entity.transform.position;
Entity.NavAgent.areaMask = 1;
Entity.NavAgent.agentTypeID = -1372625422;
//if (Entity is NPCPlayerApex)
// (Entity as NPCPlayerApex).CommunicationRadius = -1f;
Entity.displayName = configData.NPCOptions.Names?.Length > 0 ? configData.NPCOptions.Names.GetRandom() : Entity.ShortPrefabName;
Entity.InitializeHealth(configData.NPCOptions.Health, configData.NPCOptions.Health);
}
public void Setup(BasePlayer lastAttacker, bool isCorpse)
{
this.lastAttacker = lastAttacker;
if (!isCorpse)
{
if (UnityEngine.Random.Range(0, 100) <= configData.NPCOptions.WoundedChance)
{
woundedDuration = UnityEngine.Random.Range(300f, 600f);
woundedStartTime = Time.realtimeSinceStartup;
Entity.SetPlayerFlag(BasePlayer.PlayerFlags.Wounded, true);
Entity.ServerFall();
Entity.SendNetworkUpdateImmediate(false);
Entity.Invoke(WoundingTick, 1f);
}
InvokeHandler.Invoke(Entity, Despawn, configData.NPCOptions.DespawnTime);
}
string kit = isCorpse ? configData.NPCOptions.CorpseKit : configData.NPCOptions.Kit;
if (Entity is ScarecrowNPC && configData.NPCOptions.ReplaceCorpseLoot)
(Entity as ScarecrowNPC).LootSpawnSlots = new LootContainer.LootSpawnSlot[0];
if (Entity is global::HumanNPC && configData.NPCOptions.ReplaceCorpseLoot)
(Entity as global::HumanNPC).LootSpawnSlots = new LootContainer.LootSpawnSlot[0];
if (!string.IsNullOrEmpty(kit))
{
if (Entity.IsInvoking(Entity.EquipTest))
Entity.CancelInvoke(Entity.EquipTest);
StripInventory(Entity);
Instance.NextTick(() =>
{
Instance.Kits?.Call("GiveKit", Entity, kit);
if (isCorpse)
InvokeHandler.Invoke(Entity, () => Entity.Die(new HitInfo(lastAttacker ?? Entity, Entity, DamageType.Explosion, 1000f)), UnityEngine.Random.Range(1f, 3f));
else Instance.NextTick(() => Entity.EquipTest());
});
}
else
{
if (isCorpse)
InvokeHandler.Invoke(Entity, () => Entity.Die(new HitInfo(lastAttacker ?? Entity, Entity, DamageType.Explosion, 1000f)), UnityEngine.Random.Range(1f, 3f));
}
if (!isCorpse)
InvokeHandler.InvokeRandomized(this, CheckNPCLocation, 1f, 5f, 1f);
}
private void Despawn()
{
if (Entity == null || Entity.IsDestroyed || Entity.IsDead())
return;
Entity.inventory.Strip();
Entity.Die(new HitInfo(lastAttacker ?? Entity, Entity, DamageType.Explosion, 1000f));
}
private void WoundingTick()
{
if (!Entity.IsDead())
{
if (secondsSinceWoundedStarted < woundedDuration)
{
Entity.Invoke(WoundingTick, 1f);
}
else if (UnityEngine.Random.Range(0, 100) >= configData.NPCOptions.WoundedRecoveryChance)
{
Entity.Die(new HitInfo(lastAttacker ?? Entity, Entity, DamageType.Explosion, 1000f));
}
else
{
Entity.SetPlayerFlag(BasePlayer.PlayerFlags.Wounded, false);
}
}
}
public void CheckNPCLocation()
{
if (Entity == null || Entity.IsDestroyed)
{
Destroy(this);
return;
}
if (Vector3.Distance(Entity.transform.position, basePosition) > 30)
ResetPosition();
}
private void ResetPosition() => Entity.SetDestination(basePosition);
}
#endregion
#region Functions
private void StartCrashTimer()
{
callTimer = timer.In(UnityEngine.Random.Range(configData.EventTimers.Min, configData.EventTimers.Max) * 60, () =>
{
CargoPlane plane = (CargoPlane)GameManager.server.CreateEntity(CARGOPLANE_PREFAB, new Vector3(), new Quaternion(), true);
plane.enableSaving = false;
plane.Spawn();
CrashPlane crash = plane.gameObject.AddComponent<CrashPlane>();
crash.SetFlightPath(Vector3Ex.Range(-(mapSize / 6f), mapSize / 6f));
if (configData.Messages.DisplayIncoming)
SendChatMessage("IncomingMessage");
StartCrashTimer();
});
}
private static void RunEffect(string name, BaseEntity entity = null, Vector3 position = new Vector3(), Vector3 offset = new Vector3())
{
if (entity != null)
Effect.server.Run(name, entity, 0, offset, position, null, true);
else Effect.server.Run(name, position, Vector3.up, null, true);
}
private void AddCrashComponent(CargoPlane plane, Vector3 location, bool isStandard = false)
{
if (plane.GetComponent<CrashPlane>())
return;
CrashPlane crash = plane.gameObject.AddComponent<CrashPlane>();
crash.SetFlightPath(location, isStandard);
if (!isStandard && configData.Messages.DisplayIncoming)
SendChatMessage("IncomingMessage");
}
private static void FillLootContainer(BaseEntity entity, bool isDrop)
{
if (entity == null)
return;
ItemContainer container = isDrop ? entity.GetComponent<SupplyDrop>()?.inventory : entity.GetComponentInParent<LootContainer>()?.inventory;
ConfigData.LootSettings.LootTables lootTable = isDrop ? configData.Loot.SupplyLoot : configData.Loot.CrateLoot;
if (container == null || lootTable == null)
return;
if (lootTable.Enabled)
{
while (container.itemList.Count > 0)
{
Item item = container.itemList[0];
item.RemoveFromContainer();
item.Remove(0f);
}
List<ConfigData.LootSettings.LootTables.LootItem> items = new List<ConfigData.LootSettings.LootTables.LootItem>(lootTable.Items);
int count = UnityEngine.Random.Range(lootTable.Minimum, lootTable.Maximum);
for (int i = 0; i < count; i++)
{
ConfigData.LootSettings.LootTables.LootItem lootItem = items.GetRandom();
if (lootItem == null)
continue;
items.Remove(lootItem);
if (items.Count == 0)
items.AddRange(lootTable.Items);
Item item = ItemManager.CreateByName(lootItem.Shortname, UnityEngine.Random.Range(lootItem.Min, lootItem.Max));
if (item != null)
item.MoveToContainer(container);
}
}
if (configData.Loot.LockSettings.LockCrates)
{
entity.SetFlag(BaseEntity.Flags.Locked, true, false);
InvokeHandler.Invoke(entity, () => { entity.SetFlag(BaseEntity.Flags.Locked, false, false); }, configData.Loot.LockSettings.LockTimer);
}
}
private static void StripInventory(BasePlayer player, bool skipWear = false)
{
Item[] allItems = player.inventory.AllItems();
for (int i = allItems.Length - 1; i >= 0; i--)
{
Item item = allItems[i];
if (skipWear && item?.parent == player.inventory.containerWear)
continue;
item.RemoveFromContainer();
item.Remove();
}
}
private object FindPointOnNavmesh(Vector3 targetPosition)
{
NavMeshHit navHit;
if (NavMesh.SamplePosition(targetPosition, out navHit, 100, 1))
return navHit.position;
return null;
}
private static string GetGridString(Vector3 position)
{
Vector2 adjPosition = new Vector2((World.Size / 2) + position.x, (World.Size / 2) - position.z);
return $"{NumberToString((int)(adjPosition.x / 145))}{((int)(adjPosition.y / 145)) - 1}";
}
private static string NumberToString(int number)
{
bool a = number > 26;
Char c = (Char)(65 + (a ? number - 26 : number));
return a ? "A" + c : c.ToString();
}
private bool IsCrashPlane(CargoPlane plane) => plane.GetComponent<CrashPlane>();
private string FormatTime(double time)
{
TimeSpan dateDifference = TimeSpan.FromSeconds((float)time);
var days = dateDifference.Days;
var hours = dateDifference.Hours;
hours += (days * 24);
var mins = dateDifference.Minutes;
var secs = dateDifference.Seconds;
return string.Format("{0:00}:{1:00}:{2:00}", hours, mins, secs);
}
private static void SendChatMessage(string key, params object[] args)
{
for (int i = 0; i < BasePlayer.activePlayerList.Count; i++)
{
BasePlayer player = BasePlayer.activePlayerList[i];
player.ChatMessage(args != null ? string.Format(msg(key, player.UserIDString), args) : msg(key, player.UserIDString));
}
}
#endregion
#region Friends
private bool AreFriends(ulong playerId, ulong friendId)
{
if (Friends)
return (bool)Friends?.Call("AreFriends", playerId.ToString(), friendId.ToString());
return false;
}
private bool IsClanmate(ulong playerId, ulong friendId)
{
if (Clans)
{
object playerTag = Clans?.Call("GetClanOf", playerId);
object friendTag = Clans?.Call("GetClanOf", friendId);
if (playerTag is string && friendTag is string)
{
if (!string.IsNullOrEmpty((string)playerTag) && !string.IsNullOrEmpty((string)friendTag) && (playerTag == friendTag))
return true;
}
}
return false;
}
#endregion
#region Commands
[ConsoleCommand("callcrash")]
void ccmdSendCrash(ConsoleSystem.Arg arg)
{
if (arg.Connection != null)
return;
Vector3 location = Vector3.zero;
if (arg.Args != null)
{
if (arg.Args.Length == 1 && arg.Args[0].ToLower() == "help")
{
SendReply(arg, $"{Title} v{Version} - k1lly0u @ chaoscode.io");
SendReply(arg, "callcrash - Send a random crash plane");
SendReply(arg, "callcrash \"X\" \"Z\" - Send a crash plane towards the specified X and Z co-ordinates");
SendReply(arg, "callcrash \"playername\" - Send a crash plane towards the specified player's position");
SendReply(arg, "callcrash crashall - Force crash any active planes");
return;
}
if (arg.Args.Length > 0)
{
if (arg.Args.Length == 2)
{
float x;
float z;
if (float.TryParse(arg.Args[0], out x) && float.TryParse(arg.Args[1], out z))
{
location = new Vector3(x, 0, z);
SendReply(arg, $"Crash plane sent to X: {x}, Z: {z}");
}
}
if (arg.Args.Length == 1)
{
if (arg.Args[0].ToLower() == "crashall")
{
CrashPlane[] crashPlanes = UnityEngine.Object.FindObjectsOfType<CrashPlane>();
if (crashPlanes == null || crashPlanes.Length == 0)
{
SendReply(arg, "There are no planes currently active");
return;
}
for (int i = 0; i < crashPlanes.Length; i++)
{
CrashPlane crashPlane = crashPlanes[i];
if (crashPlane.status == FlightStatus.Flying)
crashPlane.BeginCrash();
}
SendReply(arg, $"Force crashing {crashPlanes.Length} planes!");
return;
}
else
{
IPlayer targetPlayer = covalence.Players.FindPlayer(arg.Args[0]);
if (targetPlayer != null && targetPlayer.IsConnected)
{
BasePlayer target = targetPlayer?.Object as BasePlayer;
if (target != null)
// ... [Code preview truncated due to length. Full source in attached file] ...