using System.Collections.Generic; using Newtonsoft.Json; using CompanionServer.Handlers; using Oxide.Plugins.UnderwaterGuardExtensionMethods; using UnityEngine; using System; using Oxide.Core.Plugins; using Rust; using System.Collections; using Newtonsoft.Json.Linq; using Oxide.Core; using System.Reflection; using Time = UnityEngine.Time; using System.IO; using static BaseEntity; using Rust.Ai; namespace Oxide.Plugins { [Info("UnderwaterGuard", "Adem", "1.0.5")] class UnderwaterGuard : RustPlugin { #region Variables const bool en = true; string currentMapName; static UnderwaterGuard ins; [PluginReference] Plugin NpcSpawn; HashSet subscribeMetods = new HashSet { "OnEntitySpawned", "CanExplosiveStick", "CanEntityTakeDamage" }; RespawnController respawnController; #endregion Variables #region Hooks void Init() { Unsubscribes(); } void OnServerInitialized() { ins = this; UpdateConfig(); PostLoadCheck(); MapConfig mapConfig = DataFilesManager.GetThisMapDataDile(); RespawnController.CreateRespawnController(mapConfig); Subscribes(); } void Unload() { if (respawnController != null) respawnController.DeleteController(); DataFilesManager.StopLoadDataFiles(); } object CanExplosiveStick(TimedExplosive timedExplosive, BaseSubmarine baseSubmarine) { if (!_config.mainConfig.allowStickC4 || baseSubmarine == null || baseSubmarine.net == null) return null; PatrolSubmarine patrolSubmarine = PatrolSubmarine.GetPatrolSubmarineByNetID(baseSubmarine.net.ID.Value); if (patrolSubmarine != null) return true; return null; } object OnEntityTakeDamage(SimpleShark shark, HitInfo info) { if (!shark.IsExists() || info == null || shark.net == null) return null; PatrolShark patrolShark = PatrolShark.GetPatrolSharkByNetId(shark.net.ID.Value); if (patrolShark != null) { if (info.Initiator == null) return true; } return null; } object OnEntityTakeDamage(ScientistNPC scientistNPC, HitInfo info) { if (!scientistNPC.IsExists() || info == null) return null; if (NpcSpawnManager.IsEventNpc(scientistNPC)) { if (info.Initiator == null) return true; } return null; } void OnEntityTakeDamage(BasePlayer player, HitInfo info) { if (player == null || info == null) return; if (info.Initiator == null && info.WeaponPrefab != null && info.WeaponPrefab.name == "TorpedoStraight") { info.damageTypes.ScaleAll(_config.mainConfig.submarineDamageScale); return; } else if (info.Initiator != null) { if (info.Initiator is SimpleShark) { PatrolShark patrolShark = PatrolShark.GetPatrolSharkByNetId(info.Initiator.net.ID.Value); if (patrolShark != null) info.damageTypes.ScaleAll(patrolShark.sharkConfig.damageScale); return; } } } object OnEntityTakeDamage(BaseSubmarine baseSubmarine, HitInfo info) { if (!baseSubmarine.IsExists() || info == null) return null; PatrolSubmarine patrolSubmarine = PatrolSubmarine.GetPatrolSubmarineByNetID(baseSubmarine.net.ID.Value); if (patrolSubmarine != null) { if (info.InitiatorPlayer.IsRealPlayer()) patrolSubmarine.OnAttackedByPlayer(info.InitiatorPlayer); else if (info.Initiator == null) return true; } return null; } #region SupportedPLugins object CanEntityTakeDamage(BaseSubmarine baseSubmarine, HitInfo info) { if (!baseSubmarine.IsExists() || info == null) return null; PatrolSubmarine patrolSubmarine = PatrolSubmarine.GetPatrolSubmarineByNetID(baseSubmarine.net.ID.Value); if (patrolSubmarine != null) { if (info.InitiatorPlayer.IsRealPlayer()) { patrolSubmarine.OnAttackedByPlayer(info.InitiatorPlayer); return true; } else if (info.Initiator == null) return false; } return null; } object CanEntityTakeDamage(SimpleShark shark, HitInfo info) { if (!shark.IsExists() || info == null || shark.net == null) return null; PatrolShark patrolShark = PatrolShark.GetPatrolSharkByNetId(shark.net.ID.Value); if (patrolShark != null) { if (info.Initiator == null) return false; else if (info.InitiatorPlayer.IsRealPlayer()) return true; } return null; } object CanEntityTakeDamage(ScientistNPC scientistNPC, HitInfo info) { if (!scientistNPC.IsExists() || info == null) return null; if (NpcSpawnManager.IsEventNpc(scientistNPC)) { if (info.Initiator == null) return false; else if (info.InitiatorPlayer.IsRealPlayer()) return true; } return null; } #endregion SupportedPLugins #endregion Hooks #region Commands [ChatCommand("ucreatepath")] void ChatCreatePathCommand(BasePlayer player, string command, string[] arg) { if (!player.IsAdmin) return; float step = 5; if (arg.Length > 0) { step = Convert.ToInt32(arg[0]); if (step <= 0) step = 5; } if (respawnController == null) { PrintToChat(player, "The date file for this map has not been detected! Create it!"); return; } PathRecorder.CreatePathRecorder(player, step); ins.PrintError($"Follow the route, and then enter the command usavepath or uancelpath!"); } [ChatCommand("usavepath")] void ChatSavePathCommand(BasePlayer player, string command, string[] arg) { if (!player.IsAdmin) return; PathRecorder pathRecorder = PathRecorder.GetPathRecorderByPlayerUID(player.userID); if (pathRecorder == null) return; if (arg.Length < 2) { PrintToChat(player, "You didn't write route name or entity preset name! (usavepath )"); return; } pathRecorder.SaveRout(arg[0], arg[1], player); ins.PrintError($"The route has been saved successfully!"); } [ChatCommand("uсancelpath")] void ChatCancelPathCommand(BasePlayer player, string command, string[] arg) { if (!player.IsAdmin) return; PathRecorder pathRecorder = PathRecorder.GetPathRecorderByPlayerUID(player.userID); if (pathRecorder == null) return; GameObject.Destroy(pathRecorder.gameObject); } [ChatCommand("ucreatemapfile")] void ChatCreateMapFileCommand(BasePlayer player, string command, string[] arg) { if (!player.IsAdmin) return; if (arg.Length == 0) { PrintToChat(player, "You didn't write the map Name!"); return; } MapConfig mapConfig = new MapConfig(); DataFilesManager.SaveDataFile(mapConfig, arg[0]); } #endregion Commands #region Methods void UpdateConfig() { if (_config.version == null || _config.version == new VersionNumber(0, 0, 0)) { ins.PrintError("The configuration file is corrupted!"); return; } if (_config.version != Version) { if (_config.version.Minor == 0) { if (_config.version.Patch <= 3) { _config.mainConfig = new MainConfig { submarineDamageScale = 1f, allowStickC4 = true, }; foreach (SharkConfig sharkConfig in _config.sharkConfigs) { sharkConfig.damageScale = 1f; } } } _config.version = Version; } SaveConfig(); } void PostLoadCheck() { if (!NpcSpawnManager.CheckNPCSpawn()) NextTick(() => Server.Command($"o.unload {Name}")); } void Unsubscribes() { foreach (string hook in subscribeMetods) Unsubscribe(hook); } void Subscribes() { foreach (string hook in subscribeMetods) Subscribe(hook); } Vector3 GetPatrolInitiatePosition(PatrolCustomConfig patrolCustomConfig) { Vector3 patrolInitiatePosition = Vector3.zero; if (patrolCustomConfig != null) patrolInitiatePosition = patrolCustomConfig.patrolPathList[0].ToVector3(); return patrolInitiatePosition; } #endregion Methods #region Classes static class DataFilesManager { static Coroutine loadCoroutine; static string dataPath = Interface.Oxide.DataDirectory + "/UnderwaterGuard/"; internal static MapConfig GetThisMapDataDile() { Dictionary allMapsConfig = LoadAndUpdateAllDataFiles(); if (allMapsConfig == null || allMapsConfig.Count == 0) return null; string mapName; foreach (RANDSwitch entity in BaseNetworkable.serverEntities.OfType()) { if (entity == null || entity.transform.position == Vector3.zero) continue; float entityID = entity.transform.position.x + entity.transform.position.y + entity.transform.position.z; mapName = allMapsConfig.FirstOrDefault(x => x.Value.mapID - entityID <= 0.01f).Key; if (mapName != null) { ins.currentMapName = mapName; return allMapsConfig[mapName]; } } return null; } internal static Dictionary LoadAndUpdateAllDataFiles() { if (!Directory.Exists(dataPath)) Directory.CreateDirectory(dataPath); Dictionary allMapsConfig = new Dictionary(); foreach (string name in Interface.Oxide.DataFileSystem.GetFiles("UnderwaterGuard/")) { string fileName = name.Split('/').Last().Split('.').First(); MapConfig mapConfig = Interface.Oxide.DataFileSystem.ReadObject($"UnderwaterGuard/{fileName}"); if (mapConfig == null) { ins.PrintError($"File {fileName} is corrupted and cannot be loaded!"); continue; } allMapsConfig.Add(fileName, mapConfig); } return allMapsConfig; } internal static void StopLoadDataFiles() { if (loadCoroutine != null) ServerMgr.Instance.StopCoroutine(loadCoroutine); } internal static void SaveDataFile(MapConfig mapConfig, string name) { Interface.Oxide.DataFileSystem.WriteObject($"UnderwaterGuard/{name}", mapConfig); } } sealed class RespawnController : FacepunchBehaviour { internal MapConfig mapConfig { get; private set; } Coroutine respawnCoroutine; HashSet sharks = new HashSet(); HashSet patrolNpcs = new HashSet(); internal static void CreateRespawnController(MapConfig mapConfig) { if (mapConfig == null) { ins.PrintWarning("Custom map not detected"); return; } ins.Puts("Custom map detected"); GameObject gameObject = new GameObject(); gameObject.transform.position = Vector3.zero; ins.respawnController = gameObject.AddComponent(); ins.respawnController.Init(mapConfig); } internal void Init(MapConfig mapConfig) { this.mapConfig = mapConfig; respawnCoroutine = ServerMgr.Instance.StartCoroutine(RespawnCorountine()); } IEnumerator RespawnCorountine() { while (true) { KillAllGuards(); foreach (PatrolCustomConfig patrolConfig in mapConfig.customPatrolRoutes) { CreatePatrolEntity(patrolConfig); yield return null; } yield return CoroutineEx.waitForSeconds(mapConfig.respawnTime); } } void CreatePatrolEntity(PatrolCustomConfig patrolConfig) { SharkConfig sharkConfig = ins._config.sharkConfigs.FirstOrDefault(x => x.presetName == patrolConfig.presetName); if (sharkConfig != null) { PatrolShark eventShark = PatrolShark.CreateShark(sharkConfig, patrolConfig); sharks.Add(eventShark); return; } SubmarineConfig submarineConfig = ins._config.submarineConfigs.FirstOrDefault(x => x.presetName == patrolConfig.presetName); if (submarineConfig != null) { PatrolSubmarine patrolSubmarine = PatrolSubmarine.CreatePatrolCubmarine(submarineConfig, patrolConfig); } NpcConfig npcConfig = ins._config.npcConfigs.FirstOrDefault(x => x.presetName == patrolConfig.presetName); if (npcConfig != null) { PatrolNpc patrolNpc = PatrolNpc.CreatePatrolNpc(npcConfig, patrolConfig); patrolNpcs.Add(patrolNpc); } } internal void DeleteController() { GameObject.Destroy(ins.respawnController.gameObject); } void KillAllGuards() { PatrolNpc.KillAllPatrolNpcs(); PatrolShark.KillAllSharks(); PatrolSubmarine.KillAllSubmarines(); } void OnDestroy() { if (respawnCoroutine != null) ServerMgr.Instance.StopCoroutine(respawnCoroutine); KillAllGuards(); PathRecorder.KillAllPathRecorders(); } } sealed class PatrolSubmarine : FacepunchBehaviour { BaseSubmarine baseSubmarine; SubmarineBrain submarineBrain; SirenLight sirenLight; static HashSet patrolSubmarines = new HashSet(); internal static PatrolSubmarine CreatePatrolCubmarine(SubmarineConfig submarineConfig, PatrolCustomConfig patrolConfig) { Vector3 spawnPosition = ins.GetPatrolInitiatePosition(patrolConfig); if (spawnPosition == Vector3.zero) return null; string submarinePrefab = submarineConfig.submarineType == 0 ? "assets/content/vehicles/submarine/submarinesolo.entity.prefab" : "assets/content/vehicles/submarine/submarineduo.entity.prefab"; BaseSubmarine baseSubmarine = BuildManager.CreateRegularEntity(submarinePrefab, spawnPosition, Quaternion.identity) as BaseSubmarine; baseSubmarine.buoyancy.buoyancyScale = 0; PatrolSubmarine patrolSubmarine = baseSubmarine.gameObject.AddComponent(); patrolSubmarine.Init(baseSubmarine, submarineConfig, patrolConfig); patrolSubmarines.Add(patrolSubmarine); return patrolSubmarine; } internal static PatrolSubmarine GetPatrolSubmarineByNetId(ulong netID) { return patrolSubmarines.FirstOrDefault(x => x != null && x.baseSubmarine.IsExists() && x.baseSubmarine.net.ID.Value == netID); } internal static void KillAllSubmarines() { foreach (PatrolSubmarine patrolSubmarine in patrolSubmarines) { if (patrolSubmarine != null) patrolSubmarine.KillSubmarine(); } patrolSubmarines.Clear(); } internal static PatrolSubmarine GetPatrolSubmarineByNetID(ulong netID) { return patrolSubmarines.FirstOrDefault(x => x != null && x.baseSubmarine.net.ID.Value == netID); } void Init(BaseSubmarine baseSubmarine, SubmarineConfig submarineConfig, PatrolCustomConfig patrolConfig) { this.baseSubmarine = baseSubmarine; UpdateBaseSubmarine(submarineConfig); submarineBrain = baseSubmarine.gameObject.AddComponent(); submarineBrain.Init(this, submarineConfig, patrolConfig); CreateSirenLight(); baseSubmarine.enabled = false; } void UpdateBaseSubmarine(SubmarineConfig submarineConfig) { baseSubmarine.InitializeHealth(submarineConfig.health, submarineConfig.health); baseSubmarine.rigidBody.useGravity = false; baseSubmarine.rigidBody.isKinematic = true; baseSubmarine.buoyancy = new Buoyancy(); baseSubmarine.CancelInvoke(baseSubmarine.UpdateClients); baseSubmarine.SetFlag(BaseSubmarine.Flags.Reserved5, true); baseSubmarine.SetFlag(BaseSubmarine.Flags.On, true); BaseMountable.AllMountables.Remove(baseSubmarine); } void CreateSirenLight() { Vector3 localPosition = baseSubmarine.PrefabName.Contains("solo") ? new Vector3(0, 1.828f, 0) : new Vector3(0, 1.828f, -0.271f); sirenLight = BuildManager.CreateChildEntity(baseSubmarine, "assets/prefabs/deployable/playerioents/lights/sirenlight/electric.sirenlight.deployed.prefab", localPosition, Vector3.zero) as SirenLight; sirenLight.UpdateFromInput(10, 0); } internal void OnAttackedByPlayer(BasePlayer player) { submarineBrain.OnAttackedByPlayer(player); } internal void KillSubmarine() { if (baseSubmarine.IsExists()) baseSubmarine.Kill(BaseNetworkable.DestroyMode.Gib); } void OnDestroy() { for (int i = 0; i < baseSubmarine.mountPoints.Count; i++) { BaseVehicle.MountPointInfo mountPointInfo = baseSubmarine.mountPoints[i]; if (mountPointInfo.mountable._mounted.IsExists()) mountPointInfo.mountable._mounted.Kill(); } } class SubmarineBrain : FacepunchBehaviour { PatrolSubmarine patrolSubmarine; SubmarineConfig submarineConfig; PathController pathController; BaseState[] states; BaseState currentState; BasePlayer target; float lastTargetVisible; bool isTargetVisible = false; const float targetUpdateTick = 2f; const int barrierLayers = 1 << 16 | 1 << 21 | 1 << 27; internal void Init(PatrolSubmarine patrolSubmarine, SubmarineConfig submarineConfig, PatrolCustomConfig patrolConfig) { this.patrolSubmarine = patrolSubmarine; this.submarineConfig = submarineConfig; states = new BaseState[2]; states[0] = new IdleState(this); states[1] = new AttackState(this); pathController = new PathController(patrolConfig, patrolSubmarine.transform.position, Vector3.zero); InvokeRepeating(() => UpdateTarget(), targetUpdateTick, targetUpdateTick); } void UpdateTarget() { if (target == null) FindNewTarget(); else CheckCurrentTarget(); } void FindNewTarget() { BasePlayer[] playerQueryResults = new BasePlayer[64]; int playersInSphere = Query.Server.GetPlayersInSphere(base.transform.position, submarineConfig.targetDetectionRange, playerQueryResults); for (int i = 0; i < playersInSphere; i++) { BasePlayer player = playerQueryResults[i]; if (!player.IsRealPlayer() || player.limitNetworking) continue; if (CheckDistance(player, submarineConfig.targetDetectionRange) && CheckVisionCone(player, submarineConfig.visionCone) && IsVisible(player)) { target = player; lastTargetVisible = Time.realtimeSinceStartup; return; } } } void CheckCurrentTarget() { if (IsVisible(target) && target.transform.position.y < 0) { lastTargetVisible = Time.realtimeSinceStartup; isTargetVisible = true; } else isTargetVisible = false; if (!CheckDistance(target, submarineConfig.targetLossRange)) target = null; else if (Time.realtimeSinceStartup - lastTargetVisible > submarineConfig.memoryDuration) target = null; } internal void OnAttackedByPlayer(BasePlayer player) { if (target == null || !IsVisible(target)) target = player; } bool IsVisible(BasePlayer player) { if (player.limitNetworking) return false; Vector3 direction = Vector3Ex.Direction(player.eyes.position, transform.position); float distance = Vector3.Distance(transform.position, player.eyes.position); RaycastHit hitInfo; bool isVisible = !Physics.Raycast(transform.position, direction, out hitInfo, distance, barrierLayers); return isVisible; } bool CheckDistance(BasePlayer target, float distance) { return Vector3.Distance(target.transform.position, gameObject.transform.position) <= distance; } bool CheckVisionCone(BasePlayer target, float angle) { Vector3 targetDirection = Vector3Ex.Direction(target.transform.position, patrolSubmarine.baseSubmarine.transform.position); return Vector3.Angle(patrolSubmarine.baseSubmarine.transform.forward, targetDirection) <= angle; } void FixedUpdate() { DefineCurrentState(); currentState.StateThink(); } void DefineCurrentState() { BaseState idealState = states.Max(x => x.GetStateWeight()); if (currentState != idealState) { if (currentState != null) currentState.StateLeave(); currentState = idealState; currentState.StateEnter(); } } class IdleState : BaseState { Vector3 destination; Vector3 lastSpeed; internal IdleState(SubmarineBrain submarineBrain) : base(submarineBrain) { } internal override float GetStateWeight() { return 50; } internal override void StateEnter() { submarineBrain.pathController.FindBestPatrolPoint(baseSubmarine.transform.position); } internal override void StateThink() { UpdatePosition(); UpdateRotation(); } void UpdatePosition() { destination = submarineBrain.pathController.GetNextPathPoint(baseSubmarine.transform.position); Vector3 direction = Vector3Ex.Direction(destination, baseSubmarine.transform.position); float destinationAngle2D = Vector2.Angle(new Vector2(baseSubmarine.transform.forward.x, baseSubmarine.transform.forward.z), new Vector2(direction.x, direction.z)); if (destinationAngle2D < 20) { Vector3 speed = direction * submarineBrain.submarineConfig.patrolSpeed; float distance = Vector3.Distance(baseSubmarine.transform.position, destination); if (distance < 5 && submarineBrain.pathController.isRandomPath) { float distanceSpeedScale = distance / 5; speed *= distanceSpeedScale; } if (lastSpeed.magnitude < speed.magnitude) { speed = Vector3.Lerp(lastSpeed, speed, 0.1f); } lastSpeed = speed; baseSubmarine.transform.position += speed / 5; } else if (submarineBrain.pathController.isRandomPath) { lastSpeed = Vector3.zero; } } internal void UpdateRotation() { Vector3 direction = Vector3Ex.Direction(destination, baseSubmarine.transform.position); if (submarineBrain.pathController.isRandomPath) direction.y = 0; float rotationSpeedScale = submarineBrain.pathController.isRandomPath ? 0.04f : 0.1f; baseSubmarine.transform.rotation = Quaternion.Lerp(baseSubmarine.transform.rotation, Quaternion.LookRotation(direction), rotationSpeedScale * submarineBrain.submarineConfig.turnSpeed); } } class AttackState : BaseState { Vector3 lastSpeed; float lastShotTime; float idealTargetDistance = 15; StorageContainer torpedoContainer; Vector3 lastTargetPosition; bool haveBarrier; float lastBarrierCheck; internal AttackState(SubmarineBrain submarineBrain) : base(submarineBrain) { if (submarineBrain.submarineConfig.torpedoCount > 0) AddTorpedoes(submarineBrain.submarineConfig.torpedoCount); torpedoContainer = baseSubmarine.GetTorpedoContainer(); torpedoContainer.dropsLoot = false; } internal override void StateEnter() { lastTargetPosition = Vector3.zero; } void AddTorpedoes(int count) { Item torpedoItem = ItemManager.CreateByName("submarine.torpedo.straight", count, 0); if (!torpedoItem.MoveToContainer(torpedoContainer.inventory)) torpedoItem.Remove(); } internal override float GetStateWeight() { return submarineBrain.target == null ? 0 : 100; } internal override void StateThink() { if (submarineBrain.target == null) return; UpdateTorpedo(); CheckBarriers(); FollowTarget(); lastTargetPosition = submarineBrain.target.transform.position; } void UpdateTorpedo() { Vector3 startTorpedoPosition = baseSubmarine.torpedoFiringPoint.position + baseSubmarine.transform.forward; float timeScinceLastShot = Time.realtimeSinceStartup - lastShotTime; if (timeScinceLastShot < submarineBrain.submarineConfig.timeBetweenShots) return; if (!submarineBrain.isTargetVisible || !submarineBrain.CheckDistance(submarineBrain.target, submarineBrain.submarineConfig.attackRange) || !submarineBrain.CheckVisionCone(submarineBrain.target, 10)) return; if (torpedoContainer.inventory.IsEmpty()) { if (submarineBrain.submarineConfig.torpedoCount == 0) AddTorpedoes(1); else return; } Vector3 nextTargetPosition = submarineBrain.target.eyes.transform.position; float distance = Vector3.Distance(submarineBrain.target.eyes.transform.position, startTorpedoPosition); if (lastTargetPosition != Vector3.zero) { nextTargetPosition += Vector3Ex.Direction(nextTargetPosition, lastTargetPosition) * distance / 5; } Vector3 direction = Vector3Ex.Direction(nextTargetPosition, startTorpedoPosition); ServerProjectile serverProjectile; if (baseSubmarine.TryFireProjectile(torpedoContainer, AmmoTypes.TORPEDO, startTorpedoPosition, direction, null, 1f, 0, out serverProjectile)) { serverProjectile.radius = 0.5f; serverProjectile.swimRandom = 0; lastShotTime = Time.realtimeSinceStartup; baseSubmarine.ClientRPC(null, "TorpedoFired"); } } void CheckBarriers() { if (Time.realtimeSinceStartup - lastBarrierCheck < 1) return; lastBarrierCheck = Time.realtimeSinceStartup; RaycastHit raycastHit; haveBarrier = Physics.SphereCast(baseSubmarine.transform.position, 1.5f, baseSubmarine.transform.forward, out raycastHit, 5, barrierLayers); } void FollowTarget() { UpdateRotation(); if (!haveBarrier && !submarineBrain.CheckDistance(submarineBrain.target, idealTargetDistance)) { MoveToTarget(); } } void MoveToTarget() { Vector3 destination = submarineBrain.target.eyes.position; Vector3 direction = Vector3Ex.Direction(destination, baseSubmarine.transform.position); float destinationAngle2D = Vector2.Angle(new Vector2(baseSubmarine.transform.forward.x, baseSubmarine.transform.forward.z), new Vector2(direction.x, direction.z)); if (destinationAngle2D < 20) { Vector3 speed = direction * submarineBrain.submarineConfig.patrolSpeed; float distance = Vector3.Distance(baseSubmarine.transform.position, destination); if (distance < 5) { float distanceSpeedScale = distance / 5; speed *= distanceSpeedScale; } if (lastSpeed.magnitude < speed.magnitude) { speed = Vector3.Lerp(lastSpeed, speed, 0.1f); } lastSpeed = speed; baseSubmarine.transform.position += speed / 5; if (baseSubmarine.transform.position.y > -1f) baseSubmarine.transform.position = new Vector3(baseSubmarine.transform.position.x, -1f, baseSubmarine.transform.position.z); } } internal void UpdateRotation() { Vector3 direction = Vector3Ex.Direction(submarineBrain.target.eyes.transform.position, baseSubmarine.transform.position); baseSubmarine.transform.rotation = Quaternion.Lerp(baseSubmarine.transform.rotation, Quaternion.LookRotation(direction), 0.08f * submarineBrain.submarineConfig.turnSpeed); } } abstract class BaseState { protected SubmarineBrain submarineBrain; protected BaseSubmarine baseSubmarine; protected float submarineSize = 5; internal BaseState(SubmarineBrain submarineBrain) { this.submarineBrain = submarineBrain; baseSubmarine = submarineBrain.patrolSubmarine.baseSubmarine; } internal abstract float GetStateWeight(); internal virtual void StateEnter() { } internal virtual void StateLeave() { } internal virtual void StateThink() { } internal void GoToPosition(Vector3 position) { } internal void LookAtPosition(Vector3 position) { Vector3 direction = Vector3Ex.Direction(position, baseSubmarine.transform.position); direction.y = 0; baseSubmarine.transform.rotation = Quaternion.Lerp(baseSubmarine.transform.rotation, Quaternion.LookRotation(direction), 0.04f * submarineBrain.submarineConfig.turnSpeed); } } } } sealed class PatrolShark : SimpleShark { static HashSet patrolSharks = new HashSet(); internal SharkConfig sharkConfig; CustomSimpleState[] states; CustomSimpleState currentState; float currentSpeed; BasePlayer target; float lastSeenTargetTime; float lastTargetSearchTime; bool isTargetVisible; float obstacleRotateScale; int barrierLayers = 1 << 0 | 1 << 16 | 1 << 21 | 1 << 27; float detectionDistance = 7f; internal static PatrolShark CreateShark(SharkConfig sharkConfig, PatrolCustomConfig patrolConfig) { Vector3 spawnPosition = ins.GetPatrolInitiatePosition(patrolConfig); if (spawnPosition == Vector3.zero) return null; SimpleShark simpleShark = BuildManager.CreateRegularEntity("assets/rust.ai/agents/fish/simpleshark.prefab", spawnPosition, Quaternion.identity) as SimpleShark; PatrolShark patrolShark = simpleShark.gameObject.AddComponent(); BuildManager.CopySerializableFields(simpleShark, patrolShark); UnityEngine.Object.DestroyImmediate(simpleShark, true); patrolShark.Spawn(); patrolShark.transform.position = spawnPosition; patrolShark.Init(sharkConfig, patrolConfig); patrolSharks.Add(patrolShark); return patrolShark; } internal static PatrolShark GetPatrolSharkByNetId(ulong netID) { return patrolSharks.FirstOrDefault(x => x.IsExists() && x.net.ID.Value == netID); } internal static void KillAllSharks() { foreach (PatrolShark shark in patrolSharks) { if (shark.IsExists()) shark.Kill(); } patrolSharks.Clear(); } void Init(SharkConfig sharkConfig, PatrolCustomConfig patrolConfig) { this.sharkConfig = sharkConfig; InitializeHealth(sharkConfig.health, sharkConfig.health); minSpeed = UnityEngine.Random.Range(sharkConfig.minSpeed, sharkConfig.minSpeed + 1); maxSpeed = sharkConfig.maxSpeed; minTurnSpeed = sharkConfig.minTurnSpeed; maxTurnSpeed = sharkConfig.maxTurnSpeed; aggroRange = sharkConfig.aggroRange; attackCooldown = sharkConfig.attackCooldown; states = new CustomSimpleState[2]; states[0] = new CustomIdleState(this, patrolConfig); states[1] = new CustomAttackState(this); } void FixedUpdate() { UpdateTarget(); DefineCurrentState(); currentState.StateThink(); UpdateObstacleAvoidance(); UpdateSpeed(); UpdatePosition(); UpdateDirection(); CheckHealthRecovery(); } void UpdateTarget() { if (target != null) CheckCurrentTarget(); else FindNewTarget(); } void CheckCurrentTarget() { if (IsVisible(target)) { lastSeenTargetTime = Time.realtimeSinceStartup; isTargetVisible = true; } if (!CheckDistance(target, sharkConfig.aggroRange * 1.5f) || Time.realtimeSinceStartup - lastSeenTargetTime > 2 || target.isMounted) target = null; } void FindNewTarget() { if (Time.realtimeSinceStartup - lastTargetSearchTime < 1) return; lastTargetSearchTime = Time.realtimeSinceStartup; if (!BaseNetworkable.HasCloseConnections(transform.position, sharkConfig.aggroRange)) return; BasePlayer[] playerQueryResults = new BasePlayer[64]; int playersInSphere = Query.Server.GetPlayersInSphere(base.transform.position, sharkConfig.aggroRange, playerQueryResults); for (int i = 0; i < playersInSphere; i++) { BasePlayer player = playerQueryResults[i]; if (!player.IsRealPlayer() || player.isMounted) continue; if (IsVisible(player)) { target = player; lastSeenTargetTime = Time.realtimeSinceStartup; isTargetVisible = true; break; } } } bool IsVisible(BasePlayer player) { if (player.limitNetworking) return false; Vector3 direction = Vector3Ex.Direction(player.eyes.position, transform.position); float distance = Vector3.Distance(transform.position, player.eyes.position); RaycastHit hitInfo; bool isVisible = !Physics.Raycast(transform.position, direction, out hitInfo, distance, barrierLayers); return isVisible; } bool CheckDistance(BasePlayer target, float distance) { return Vector3.Distance(target.transform.position, gameObject.transform.position) <= distance; } void DefineCurrentState() { CustomSimpleState idealState = states.Max(x => x.GetStateWeight()); if (currentState != idealState) { if (currentState != null) currentState.StateLeave(); currentState = idealState; currentState.StateEnter(); } } void UpdateDirection() { Vector3 direction = Vector3Ex.Direction(WaterClamp(destination), base.transform.position); if (obstacleRotateScale != 0) { direction = -transform.forward; } if (direction == Vector3.zero) return; Quaternion lookRotation = Quaternion.LookRotation(direction, Vector3.up); RaycastHit hitInfo; if (obstacleRotateScale == 1 && !Physics.SphereCast(transform.position, obstacleDetectionRadius, direction, out hitInfo, detectionDistance, barrierLayers)) { transform.rotation = lookRotation; return; } base.transform.rotation = Quaternion.Lerp(base.transform.rotation, lookRotation, GetTurnSpeed() * 0.05f); } private void UpdatePosition() { Vector3 point = transform.position + transform.forward * currentSpeed * 0.05f; point = WaterClamp(point); base.transform.position = point; } void UpdateObstacleAvoidance() { Vector3 forward = base.transform.forward; Vector3 position = base.transform.position; RaycastHit hitInfo; if (Physics.SphereCast(position, obstacleDetectionRadius, forward, out hitInfo, detectionDistance, barrierLayers)) { RaycastHit hitInfo1; if (!Physics.SphereCast(position, obstacleDetectionRadius / 2, Vector3.Lerp(transform.forward, transform.right, 0.5f), out hitInfo1, detectionDistance, barrierLayers)) { obstacleRotateScale = 0.5f; } else if (!Physics.SphereCast(position, obstacleDetectionRadius / 2, Vector3.Lerp(transform.forward, -transform.right, 0.5f), out hitInfo1, detectionDistance, barrierLayers)) { obstacleRotateScale = -0.5f; } else obstacleRotateScale = 1f; } else { obstacleRotateScale = 0; } } void UpdateSpeed() { if (IsStartled() && obstacleRotateScale == 0) currentSpeed = Mathf.Lerp(currentSpeed, sharkConfig.maxSpeed, 0.1f); else currentSpeed = Mathf.Lerp(currentSpeed, sharkConfig.minSpeed, 0.1f); } void CheckHealthRecovery() { if (sharkConfig.healtRecoverTime > 0 && healthFraction < 1f && TimeSinceAttacked() >= sharkConfig.healtRecoverTime) health = MaxHealth(); } public class CustomAttackState : CustomSimpleState { float lastAttackTime; public CustomAttackState(PatrolShark patrolShark) : base(patrolShark) { } public override void StateThink() { patrolShark.Startle(); patrolShark.SetFlag(Flags.Open, true); if (patrolShark.CheckDistance(patrolShark.target, 2)) DoAttack(); else patrolShark.destination = patrolShark.target.transform.position; base.StateThink(); } public override void StateLeave() { patrolShark.SetFlag(Flags.Open, false); base.StateLeave(); } void DoAttack() { patrolShark.target.Hurt(UnityEngine.Random.Range(30f, 70f), DamageType.Bite, patrolShark); Vector3 posWorld = patrolShark.WaterClamp(patrolShark.target.CenterPoint()); Effect.server.Run(patrolShark.bloodCloud.resourcePath, posWorld, Vector3.forward); lastAttackTime = Time.realtimeSinceStartup; } public override float GetStateWeight() { if (patrolShark.target != null && Time.realtimeSinceStartup - lastAttackTime >= patrolShark.sharkConfig.attackCooldown) { return 10f; } return 0f; } } public class CustomIdleState : CustomSimpleState { PathController pathController; public CustomIdleState(PatrolShark owner, PatrolCustomConfig patrolConfig) : base(owner) { pathController = new PathController(patrolConfig, owner.transform.position, Vector3.zero, 2, true); } public override void StateEnter() { pathController.FindBestPatrolPoint(patrolShark.transform.position); patrolShark.destination = pathController.GetNextPathPoint(patrolShark.transform.position); base.StateEnter(); } public override void StateLeave() { base.StateLeave(); } public override void StateThink() { patrolShark.destination = pathController.GetNextPathPoint(patrolShark.transform.position); } public override float GetStateWeight() { return 1f; } } public abstract class CustomSimpleState { protected PatrolShark patrolShark; public CustomSimpleState(PatrolShark patrolShark) { this.patrolShark = patrolShark; } public virtual void StateThink() { } public virtual void StateEnter() { } public virtual void StateLeave() { } public virtual float GetStateWeight() { return 1f; } } new void Update() { } } sealed class PatrolNpc : FacepunchBehaviour { internal static HashSet patrolNpcs = new HashSet(); MovableBaseMountable baseMountable; NpcConfig npcConfig; MovableDroppedItem movableDroppedItem; ScientistNPC scientistNPC; Vector3 destination; PathController pathController; BasePlayer target; float currentSpeed = 0; internal static PatrolNpc CreatePatrolNpc(NpcConfig npcConfig, PatrolCustomConfig patrolConfig) { Vector3 spawnPosition = ins.GetPatrolInitiatePosition(patrolConfig); ScientistNPC scientistNPC = NpcSpawnManager.CreateScientistNpc(npcConfig, spawnPosition); PatrolNpc patrolNpc = scientistNPC.gameObject.AddComponent(); patrolNpc.Init(npcConfig, scientistNPC, patrolConfig); patrolNpcs.Add(patrolNpc); return patrolNpc; } internal static void KillAllPatrolNpcs() { foreach (PatrolNpc patrolNpc in patrolNpcs) { if (patrolNpc != null) patrolNpc.KillNpc(); } patrolNpcs.Clear(); } void Init(NpcConfig npcConfig, ScientistNPC scientistNPC, PatrolCustomConfig patrolConfig) { this.npcConfig = npcConfig; this.scientistNPC = scientistNPC; movableDroppedItem = MovableDroppedItem.CreateMovableDroppedItem(scientistNPC.transform.position + new Vector3(0, 1.3f, 0), Quaternion.identity); Rigidbody rigidbody = movableDroppedItem.GetComponentInChildren(); rigidbody.isKinematic = true; baseMountable = MovableBaseMountable.CreateMovableBaseMountable(movableDroppedItem); baseMountable.MountPlayer(scientistNPC); pathController = new PathController(patrolConfig, scientistNPC.transform.position, new Vector3(0, 0.5f, 0), 2, false, 1.25f); InvokeRepeating(UpdateTarget, 1f, 1f); } internal ulong GetNpcNetId() { if (scientistNPC == null || scientistNPC.net == null) return 0; return scientistNPC.net.ID.Value; } void UpdateTarget() { target = (BasePlayer)ins.NpcSpawn.Call("GetCurrentTarget", scientistNPC); } void FixedUpdate() { UpdateSpeed(); UpdateMoving(); baseMountable.SendNetworkUpdate(); } void UpdateSpeed() { currentSpeed = Mathf.Lerp(currentSpeed, npcConfig.speed / 10, 0.1f); } void UpdateMoving() { UpdateDestination(); UpdatePosition(); UpdateRotation(); } void UpdateDestination() { if (target != null) { destination = pathController.GetBestPositionForHunting(movableDroppedItem.transform.position, target.transform.position) + new Vector3(0, 0.5f, 0); if (Vector3.Distance(target.transform.position, movableDroppedItem.transform.position) < Vector3.Distance(destination, movableDroppedItem.transform.position)) destination = Vector3.zero; } else { destination = pathController.GetNextPathPoint(movableDroppedItem.transform.position) + new Vector3(0, 0.5f, 0); } } void UpdatePosition() { if (destination == Vector3.zero) return; if (target != null && Vector3.Distance(destination, movableDroppedItem.transform.position) < 1) return; Vector3 direction = Vector3Ex.Direction(destination, scientistNPC.transform.position); movableDroppedItem.transform.position += direction * currentSpeed * 0.6f; } void UpdateRotation() { Vector3 targetPoint = target != null ? target.transform.position : destination; targetPoint.y = movableDroppedItem.transform.position.y; Vector3 direction = Vector3Ex.Direction(targetPoint, movableDroppedItem.transform.position); if (target == null) scientistNPC.SetAimDirection(direction); direction.y -= 1.2f; if (direction != Vector3.zero) movableDroppedItem.transform.rotation = Quaternion.Lerp(movableDroppedItem.transform.rotation, Quaternion.LookRotation(direction), 0.1f); } void KillNpc() { if (scientistNPC.IsExists()) scientistNPC.Kill(); } void OnDestroy() { if (baseMountable.IsExists()) baseMountable.Kill(); } sealed class MovableBaseMountable : BaseMountable { internal static MovableBaseMountable CreateMovableBaseMountable(BaseEntity parentEntity, string seatPrefab = "assets/prefabs/vehicle/seats/testseat.prefab") { BaseMountable baseMountable = GameManager.server.CreateEntity(seatPrefab, parentEntity.transform.position) as BaseMountable; baseMountable.enableSaving = false; baseMountable.skinID = 45124514; MovableBaseMountable movableBaseMountable = baseMountable.gameObject.AddComponent(); BuildManager.CopySerializableFields(baseMountable, movableBaseMountable); baseMountable.StopAllCoroutines(); UnityEngine.GameObject.DestroyImmediate(baseMountable, true); BuildManager.SetParent(parentEntity, movableBaseMountable, new Vector3(0, -1f, 0), Vector3.zero); movableBaseMountable.Spawn(); return movableBaseMountable; } public override void DismountAllPlayers() { } public override bool GetDismountPosition(BasePlayer player, out Vector3 res) { res = player.transform.position; return true; } } sealed class MovableDroppedItem : DroppedItem { internal static MovableDroppedItem CreateMovableDroppedItem(Vector3 position, Quaternion rotation, bool hide = true) { DroppedItem droppedItem = GameManager.server.CreateEntity("assets/prefabs/misc/burlap sack/generic_world.prefab", position, rotation) as DroppedItem; droppedItem.enableSaving = false; droppedItem.allowPickup = false; droppedItem.item = ItemManager.CreateByName("weapon.mod.muzzleboost"); if (hide) droppedItem.SetFlag(BaseEntity.Flags.Disabled, true); MovableDroppedItem movableDroppedItem = droppedItem.gameObject.AddComponent(); BuildManager.CopySerializableFields(droppedItem, movableDroppedItem); droppedItem.StopAllCoroutines(); UnityEngine.GameObject.DestroyImmediate(droppedItem, true); movableDroppedItem.Spawn(); return movableDroppedItem; } public override float MaxVelocity() { return 100; } public override float GetDespawnDuration() { return float.MaxValue; } } } sealed class PathController { List patrolPath = new List(); Vector3 destination; Vector3 entityOffset; bool ringRoute; float entitySize; float pointDelta; int currentPositionIndex = 0; const int randomPathLenght = 10; internal bool isRandomPath; internal PathController(PatrolCustomConfig patrolBaseConfig, Vector3 initiatePosition, Vector3 entityOffset, float entitySize = 5f, bool shark = false, float pointDelta = 2.5f) { this.entitySize = entitySize; this.entityOffset = entityOffset; this.pointDelta = pointDelta; GeneratePatrolPath(patrolBaseConfig, initiatePosition, shark); } void GeneratePatrolPath(PatrolCustomConfig patrolCustomConfig, Vector3 initiatePosition, bool shark) { patrolPath.Clear(); foreach (string stringVector in patrolCustomConfig.patrolPathList) { Vector3 localPosition = stringVector.ToVector3(); Vector3 globalPosition = localPosition; patrolPath.Add(globalPosition); } if (patrolPath.Count > 0) destination = patrolPath[0]; ringRoute = patrolCustomConfig.isRingRoute; } bool CheckPatrolPoint(Vector3 position, Vector3 previusPoint, Vector3 nextPoint) { if (HasObstacleOnWay(previusPoint, position)) return false; if (nextPoint != Vector3.zero && HasObstacleOnWay(position, nextPoint)) return false; return true; } bool HasObstacleOnWay(Vector3 startPosition, Vector3 endPosition) { Vector3 direction = Vector3Ex.Direction(endPosition, startPosition); float maxDistance = Vector3.Distance(startPosition, endPosition); RaycastHit hitInfo; return Physics.SphereCast(startPosition, entitySize, direction, out hitInfo, maxDistance, 10551553); } internal Vector3 GetNextPathPoint(Vector3 currentPosition) { if (Vector3.Distance(destination, currentPosition - entityOffset) < pointDelta) { int nextPatrolIndex = 0; if (currentPositionIndex >= patrolPath.Count - 1) { nextPatrolIndex = 0; if (!ringRoute) patrolPath.Reverse(); } else nextPatrolIndex = currentPositionIndex + 1; destination = patrolPath[nextPatrolIndex]; currentPositionIndex = patrolPath.IndexOf(destination); } return destination; } internal void FindBestPatrolPoint(Vector3 currentPosition) { Vector3 position = patrolPath.FirstOrDefault(x => !HasObstacleOnWay(currentPosition, x)); if (position != null && position != Vector3.zero) destination = position; else destination = patrolPath.Min(x => Vector3.Distance(currentPosition, x)); } internal Vector3 GetBestPositionForHunting(Vector3 currentPosition, Vector3 targetPosition) { int nextPointIndex = currentPositionIndex + 1; int previousPointIndex = currentPositionIndex - 1; if (nextPointIndex < patrolPath.Count) { Vector3 nextPathPoint = patrolPath[nextPointIndex]; if (Vector3.Distance(targetPosition, nextPathPoint) < Vector3.Distance(destination, targetPosition)) { destination = nextPathPoint; currentPositionIndex = nextPointIndex; } } if (previousPointIndex >= 0) { Vector3 previousPathPoint = patrolPath[previousPointIndex]; if (Vector3.Distance(targetPosition, previousPathPoint) < Vector3.Distance(destination, targetPosition)) { destination = previousPathPoint; currentPositionIndex = previousPointIndex; } } return destination; } } sealed class PathRecorder : FacepunchBehaviour { internal static HashSet pathRecorders = new HashSet(); BasePlayer player; float step; List positions = new List(); internal static void KillAllPathRecorders() { foreach (PathRecorder pathRecorder in pathRecorders) if (pathRecorder != null) DestroyImmediate(pathRecorder.gameObject); pathRecorders.Clear(); } internal static PathRecorder GetPathRecorderByPlayerUID(ulong userId) { return pathRecorders.FirstOrDefault(x => x != null && x.player != null && x.player != null && x.player.userID == userId); } internal static PathRecorder CreatePathRecorder(BasePlayer player, float step) { GameObject gameObject = new GameObject(); gameObject.transform.position = player.transform.position; PathRecorder pathRecorder = gameObject.AddComponent(); pathRecorder.Init(player, step); pathRecorders.Add(pathRecorder); return pathRecorder; } void Init(BasePlayer player, float step) { this.player = player; this.step = step; positions.Add(player.transform.position); } void FixedUpdate() { float distance = Vector3.Distance(player.transform.position, positions.Last()); if (distance > step) { positions.Add(player.transform.position); } foreach (Vector3 vector3 in positions) { player.SendConsoleCommand("ddraw.sphere", 1, Color.green, vector3, 0.2f); } } internal void SaveRout(string pathName, string presetName, BasePlayer player) { List stringPositions = new List(); bool ringRoute = Vector3.Distance(positions.First(), positions.Last()) < step * 2; foreach (Vector3 position in positions) { Vector3 localPosition = position; stringPositions.Add(localPosition.ToString()); } PatrolCustomConfig patrolCustomConfig = new PatrolCustomConfig { presetName = presetName, isRingRoute = ringRoute, patrolPathList = stringPositions, }; if (ins.respawnController == null) { ins.PrintToChat(player, "The date file for this map has not been detected! Create it!"); return; } ins.respawnController.mapConfig.customPatrolRoutes.Add(patrolCustomConfig); DataFilesManager.SaveDataFile(ins.respawnController.mapConfig, ins.currentMapName); Destroy(this); } } static class BuildManager { internal static BaseEntity CreateRegularEntity(string prefabName, Vector3 position, Quaternion rotation, bool enableSaving = false) { BaseEntity entity = CreateEntity(prefabName, position, rotation, enableSaving); entity.Spawn(); return entity; } internal static BaseEntity CreateChildEntity(BaseEntity parrentEntity, string prefabName, Vector3 localPosition, Vector3 localRotation) { BaseEntity entity = CreateEntity(prefabName, parrentEntity.transform.position, Quaternion.identity); SetParent(parrentEntity, entity, localPosition, localRotation); DestroyUnnessesaryComponents(entity); entity.Spawn(); return entity; } static BaseEntity CreateEntity(string prefabName, Vector3 position, Quaternion rotation, bool enableSaving = false) { BaseEntity entity = GameManager.server.CreateEntity(prefabName, position, rotation); entity.enableSaving = enableSaving; return entity; } internal static void SetParent(BaseEntity parrentEntity, BaseEntity childEntity, Vector3 localPosition, Vector3 localRotation) { childEntity.SetParent(parrentEntity, true, false); childEntity.transform.localPosition = localPosition; childEntity.transform.localEulerAngles = localRotation; } internal static void DestroyEntityConponent(BaseEntity entity) { T component = entity.GetComponent(); if (component != null) UnityEngine.GameObject.DestroyImmediate(component as UnityEngine.Object); } static void DestroyUnnessesaryComponents(BaseEntity entity) { DestroyEntityConponent(entity); DestroyEntityConponent(entity); DestroyEntityConponent(entity); } internal static void CopySerializableFields(T src, T dst) { FieldInfo[] srcFields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (FieldInfo field in srcFields) { object value = field.GetValue(src); field.SetValue(dst, value); } } } static class NpcSpawnManager { internal static bool CheckNPCSpawn() { if (!ins.plugins.Exists("NpcSpawn")) { ins.PrintError("NpcSpawn plugin doesn`t exist! Please read the file ReadMe.txt. NPCs will not spawn!"); return false; } else return true; } internal static bool IsEventNpc(ScientistNPC scientistNPC) { return scientistNPC != null && scientistNPC.transform.position.y < 0 && ins._config.npcConfigs.Any(x => x.name == scientistNPC.displayName); } internal static ScientistNPC CreateScientistNpc(NpcConfig npcConfig, Vector3 position) { JObject baseNpcConfigObj = GetBaseNpcConfig(npcConfig); ScientistNPC scientistNPC = (ScientistNPC)ins.NpcSpawn.Call("SpawnNpc", position, baseNpcConfigObj); return scientistNPC; } internal static ScientistNPC CreateDriverNpc(SubmarineConfig submarineConfig, Vector3 position) { JObject baseNpcConfigObj = GetDriverNpcConfig(submarineConfig); ScientistNPC scientistNPC = (ScientistNPC)ins.NpcSpawn.Call("SpawnNpc", position, baseNpcConfigObj); return scientistNPC; } static JObject GetBaseNpcConfig(NpcConfig config) { return new JObject { ["Name"] = config.name, ["WearItems"] = new JArray { config.wearItems.Select(x => new JObject { ["ShortName"] = x.shortName, ["SkinID"] = x.skinID }) }, ["BeltItems"] = new JArray { config.beltItems.Select(x => new JObject { ["ShortName"] = x.shortName, ["Amount"] = x.amount, ["SkinID"] = x.skinID, ["Mods"] = new JArray { x.Mods.ToHashSet() }, ["Ammo"] = x.ammo }) }, ["Kit"] = config.kit, ["Health"] = config.health, ["RoamRange"] = 0, ["ChaseRange"] = 0, ["SenseRange"] = config.senseRange, ["ListenRange"] = config.senseRange / 3, ["AttackRangeMultiplier"] = config.attackRangeMultiplier, ["VisionCone"] = config.visionCone, ["DamageScale"] = config.damageScale, ["TurretDamageScale"] = 1f, ["AimConeScale"] = 1, ["DisableRadio"] = config.disableRadio, ["CanRunAwayWater"] = false, ["CanSleep"] = false, ["Speed"] = 0, ["AreaMask"] = 1, ["AgentTypeID"] = -1372625422, ["HomePosition"] = string.Empty, ["MemoryDuration"] = config.memoryDuration, ["States"] = new JArray { "IdleState", "CombatStationaryState" } }; } static JObject GetDriverNpcConfig(SubmarineConfig submarineConfig) { return new JObject { ["Name"] = "Diver", ["WearItems"] = new JArray(), ["BeltItems"] = new JArray(), ["Kit"] = "", ["Health"] = 100, ["RoamRange"] = 0, ["ChaseRange"] = 0, ["SenseRange"] = submarineConfig.targetDetectionRange * 3, ["ListenRange"] = submarineConfig.targetDetectionRange * 3, ["AttackRangeMultiplier"] = 1, ["VisionCone"] = submarineConfig.visionCone, ["DamageScale"] = 1f, ["TurretDamageScale"] = 1f, ["AimConeScale"] = 1f, ["DisableRadio"] = true, ["CanRunAwayWater"] = false, ["CanSleep"] = false, ["Speed"] = 0, ["AreaMask"] = 1, ["AgentTypeID"] = -1372625422, ["HomePosition"] = string.Empty, ["MemoryDuration"] = submarineConfig.memoryDuration, ["States"] = new JArray { "IdleState", "CombatStationaryState" } }; } } #endregion Classes #region Config private PluginConfig _config; protected override void LoadDefaultConfig() { _config = PluginConfig.DefaultConfig(); } protected override void LoadConfig() { base.LoadConfig(); _config = Config.ReadObject(); Config.WriteObject(_config, true); } protected override void SaveConfig() { Config.WriteObject(_config); } #region DataConfig public class MapConfig { [JsonProperty("Map ID")] public float mapID { get; set; } [JsonProperty("Time between respawns of guards [sec]")] public int respawnTime { get; set; } [JsonProperty("Setting up custom patrol routes for submarines/sharks/npcs")] public List customPatrolRoutes { get; set; } internal MapConfig() { this.mapID = 0; respawnTime = 3600; customPatrolRoutes = new List(); } } public class PatrolCustomConfig { [JsonProperty("Name of the submarine/shark/npc preset", Order = 0)] public string presetName { get; set; } [JsonProperty("Enable [true/false]", Order = 1)] public bool enable { get; set; } [JsonProperty("Ring route [true/false]", Order = 2)] public bool isRingRoute { get; set; } [JsonProperty("List of points", Order = 3)] public List patrolPathList { get; set; } } #endregion DataConfig public class MainConfig { [JsonProperty(en ? "C4 will be sticked to submarines guarding the event [true/false]" : "C4 будут прикрепляться на подводные лодки, охраняющие ивент? [true/false]")] public bool allowStickC4 { get; set; } [JsonProperty(en ? "Event's Submarine Damage Multiplier" : "Множитель урона от подводных лодок ивента")] public float submarineDamageScale { get; set; } } public class SharkConfig { [JsonProperty(en ? "Preset Name" : "Название пресета")] public string presetName { get; set; } [JsonProperty(en ? "Health" : "Здоровье")] public float health { get; set; } [JsonProperty(en ? "Damage Multiplier" : "Множитель урона")] public float damageScale { get; set; } [JsonProperty(en ? "Minimum speed" : "Минимальная скорость")] public float minSpeed { get; set; } [JsonProperty(en ? "Maximum speed" : "Максимальная скорость")] public float maxSpeed { get; set; } [JsonProperty(en ? "Minimum turning speed" : "Минимальная скорость поворота")] public float minTurnSpeed { get; set; } [JsonProperty(en ? "Maximum turning speed" : "Максимальная скорость поворота")] public float maxTurnSpeed { get; set; } [JsonProperty(en ? "Time between attacks" : "Время между атаками")] public float attackCooldown { get; set; } [JsonProperty(en ? "Target search distance" : "Расстояния поиска цели")] public float aggroRange { get; set; } [JsonProperty(en ? "Time to recover health (0 - disable recovery) [sec]" : "Время до восстановления здоровья (0 - отключить восстановление) [sec]")] public int healtRecoverTime { get; set; } } public class SubmarineConfig { [JsonProperty(en ? "Preset Name" : "Название пресета")] public string presetName { get; set; } [JsonProperty(en ? "Type of submarine (0 - solo, 1 - duo)" : "Тип подводной лодки (0 - solo, 1 - duo)")] public int submarineType { get; set; } [JsonProperty(en ? "Health" : "Здоровье")] public float health { get; set; } [JsonProperty(en ? "Speed" : "Скорость")] public float patrolSpeed { get; set; } [JsonProperty(en ? "Turning speed" : "Скорость поворота")] public float turnSpeed { get; set; } [JsonProperty(en ? "Target detection range" : "Дальность обнаружения цели")] public float targetDetectionRange { get; set; } [JsonProperty(en ? "Angle of view" : "Угол обзора")] public float visionCone { get; set; } [JsonProperty(en ? "Target loss range" : "Дальность потери цели")] public float targetLossRange { get; set; } [JsonProperty(en ? "Attack range" : "Дальность атаки")] public float attackRange { get; set; } [JsonProperty(en ? "Memory duration [sec.]" : "Длительность памяти [sec.]")] public float memoryDuration { get; set; } [JsonProperty(en ? "Number of torpedoes (0 - infinite)" : "Количество торпед (0 - бесконечно)")] public int torpedoCount { get; set; } [JsonProperty(en ? "Time between shots" : "Время между выстрелами")] public int timeBetweenShots { get; set; } } public class NpcConfig { [JsonProperty(en ? "Preset name" : "Название пресета")] public string presetName { get; set; } [JsonProperty(en ? "Name" : "Название")] public string name { get; set; } [JsonProperty(en ? "Health" : "Кол-во ХП")] public float health { get; set; } [JsonProperty(en ? "Attack Range Multiplier" : "Множитель радиуса атаки")] public float attackRangeMultiplier { get; set; } [JsonProperty(en ? "Sense Range" : "Радиус обнаружения цели")] public float senseRange { get; set; } [JsonProperty(en ? "Memory duration [sec.]" : "Длительность памяти цели [sec.]")] public float memoryDuration { get; set; } [JsonProperty(en ? "Scale damage" : "Множитель урона")] public float damageScale { get; set; } [JsonProperty(en ? "Vision Cone" : "Угол обзора")] public float visionCone { get; set; } [JsonProperty(en ? "Speed" : "Скорость")] public float speed { get; set; } [JsonProperty(en ? "Wear items" : "Одежда")] public List wearItems { get; set; } [JsonProperty(en ? "Belt items" : "Быстрые слоты")] public List beltItems { get; set; } [JsonProperty(en ? "Kit" : "Kit")] public string kit { get; set; } [JsonProperty(en ? "Disable radio effects? [true/false]" : "Отключать эффекты рации? [true/false]")] public bool disableRadio { get; set; } } public class NpcWear { [JsonProperty(en ? "ShortName" : "ShortName")] public string shortName { get; set; } [JsonProperty(en ? "skinID (0 - default)" : "SkinID (0 - default)")] public ulong skinID { get; set; } } public class NpcBelt { [JsonProperty(en ? "ShortName" : "ShortName")] public string shortName { get; set; } [JsonProperty(en ? "Amount" : "Кол-во")] public int amount { get; set; } [JsonProperty(en ? "skinID (0 - default)" : "SkinID (0 - default)")] public ulong skinID { get; set; } [JsonProperty(en ? "Mods" : "Модификации на оружие")] public List Mods { get; set; } [JsonProperty(en ? "Ammo" : "Патроны")] public string ammo { get; set; } } public class LocationConfig { [JsonProperty(en ? "Position" : "Позиция")] public string position { get; set; } [JsonProperty(en ? "Rotation" : "Вращение")] public string rotation { get; set; } } private class PluginConfig { [JsonProperty(en ? "Version" : "Версия")] public VersionNumber version { get; set; } [JsonProperty(en ? "Main settings" : "Основные настройки")] public MainConfig mainConfig { get; set; } [JsonProperty(en ? "Shark presets" : "Пресеты акул")] public HashSet sharkConfigs { get; set; } [JsonProperty(en ? "Submarine presets" : "Пресеты подводных лодок")] public HashSet submarineConfigs { get; set; } [JsonProperty(en ? "NPC presets" : "Пресеты нпс")] public HashSet npcConfigs { get; set; } public static PluginConfig DefaultConfig() { return new PluginConfig() { version = new VersionNumber(1, 0, 5), mainConfig = new MainConfig { allowStickC4 = true, submarineDamageScale = 1, }, sharkConfigs = new HashSet { new SharkConfig { presetName = "shark_default", damageScale = 1, health = 150, minSpeed = 4, maxSpeed = 10, minTurnSpeed = 1.5f, maxTurnSpeed = 3.5f, aggroRange = 15, attackCooldown = 7 } }, submarineConfigs = new HashSet { new SubmarineConfig { presetName = "submarine_solo_default", submarineType = 0, attackRange = 40, health = 300, patrolSpeed = 1f, turnSpeed = 1, targetDetectionRange = 30, targetLossRange = 60, visionCone = 90, memoryDuration = 35, torpedoCount = 0, timeBetweenShots = 3, }, }, npcConfigs = new HashSet { new NpcConfig { presetName = "diver_1", name = en ? "Diver" : "Аквалангист", health = 10, wearItems = new List { new NpcWear { shortName = "hazmatsuit_scientist_nvgm", skinID = 0 } }, beltItems = new List { new NpcBelt { shortName = "speargun", amount = 1, skinID = 0, Mods = new List {}, ammo = "" }, new NpcBelt { shortName = "syringe.medical", amount = 10, skinID = 0, Mods = new List (), ammo = "" } }, kit = "", disableRadio = true, attackRangeMultiplier = 2.5f, senseRange = 30, memoryDuration = 60f, damageScale = 1f, visionCone = 135f, speed = 1 } }, }; } } #endregion Config } } namespace Oxide.Plugins.UnderwaterGuardExtensionMethods { public static class ExtensionMethods { public static bool Any(this IEnumerable source, Func predicate) { using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (predicate(enumerator.Current)) return true; return false; } public static HashSet Where(this IEnumerable source, Func predicate) { HashSet result = new HashSet(); using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (predicate(enumerator.Current)) result.Add(enumerator.Current); return result; } public static TSource FirstOrDefault(this IEnumerable source, Func predicate) { using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (predicate(enumerator.Current)) return enumerator.Current; return default(TSource); } public static HashSet Select(this IEnumerable source, Func predicate) { HashSet result = new HashSet(); using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) result.Add(predicate(enumerator.Current)); return result; } public static List Select(this IList source, Func predicate) { List result = new List(); for (int i = 0; i < source.Count; i++) { TSource element = source[i]; result.Add(predicate(element)); } return result; } public static HashSet OfType(this IEnumerable source) { HashSet result = new HashSet(); using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (enumerator.Current is T) result.Add((T)(object)enumerator.Current); return result; } public static bool IsExists(this BaseNetworkable entity) => entity != null && !entity.IsDestroyed; public static void ClearItemsContainer(this ItemContainer container) { for (int i = container.itemList.Count - 1; i >= 0; i--) { Item item = container.itemList[i]; item.RemoveFromContainer(); item.Remove(); } } public static bool IsRealPlayer(this BasePlayer player) => player != null && player.userID.IsSteamId(); public static List OrderBy(this IEnumerable source, Func predicate) { List result = source.ToList(); for (int i = 0; i < result.Count; i++) { for (int j = 0; j < result.Count - 1; j++) { if (predicate(result[j]) > predicate(result[j + 1])) { TSource z = result[j]; result[j] = result[j + 1]; result[j + 1] = z; } } } return result; } public static List ToList(this IEnumerable source) { List result = new List(); using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) result.Add(enumerator.Current); return result; } public static HashSet ToHashSet(this IEnumerable source) { HashSet result = new HashSet(); using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) result.Add(enumerator.Current); return result; } public static TSource Max(this IEnumerable source, Func predicate) { TSource result = source.ElementAt(0); float resultValue = predicate(result); using (var enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) { TSource element = enumerator.Current; float elementValue = predicate(element); if (elementValue > resultValue) { result = element; resultValue = elementValue; } } } return result; } public static TSource Min(this IEnumerable source, Func predicate) { TSource result = source.ElementAt(0); float resultValue = predicate(result); using (var enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) { TSource element = enumerator.Current; float elementValue = predicate(element); if (elementValue < resultValue) { result = element; resultValue = elementValue; } } } return result; } public static TSource ElementAt(this IEnumerable source, int index) { int movements = 0; using (var enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) { if (movements == index) return enumerator.Current; movements++; } } return default(TSource); } public static TSource First(this IList source) => source[0]; public static TSource Last(this IList source) => source[source.Count - 1]; } }