using CompanionServer.Handlers; using Facepunch; using Newtonsoft.Json; using Oxide.Core; using Oxide.Plugins.AnyMapVendorExtensionMethods; using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.AI; namespace Oxide.Plugins { [Info("Any Map Vendor", "Adem", "1.0.1")] class AnyMapVendor : RustPlugin { #region Variables static AnyMapVendor ins; CustomTravellingVendor customTravellingVendor; HashSet subscribeMetods = new HashSet { "CanUseGesture", "OnEntityEnter" }; static float minRoadLength = 100; Coroutine vendorSpawnCoroutine; bool isUnloading; #endregion Variables #region Hooks void Init() { Unsubscribes(); } void OnServerInitialized() { ins = this; if (_config.unloadIfRoundRoad && IsRoundRoad()) { PrintWarning("There is a ring road on the map, the plugin will not spawn the vendor"); ins.NextTick(() => Interface.Oxide.UnloadPlugin(ins.Name)); return; } LoadDefaultMessages(); UpdateConfig(); PathManager.StartCachingRouts(); CustomTravellingVendor.AutoSpawnVendor(); Subscribes(); } void Unload() { isUnloading = true; Unsubscribes(); if (ins.vendorSpawnCoroutine != null) ServerMgr.Instance.StopCoroutine(ins.vendorSpawnCoroutine); if (customTravellingVendor != null) customTravellingVendor.KillVehicle(); } void CanUseGesture(BasePlayer player, GestureConfig gesture) { if (customTravellingVendor != null && Vector3.Distance(player.transform.position, customTravellingVendor.transform.position) < 15f) customTravellingVendor.StopMoving(); } object OnEntityEnter(TriggerPath trigger, TravellingVendor travellingVendor) { if (travellingVendor == null || travellingVendor.net == null) return null; if (customTravellingVendor == null || !customTravellingVendor.IsMyTravelingVendor(travellingVendor.net.ID.Value)) return null; Rigidbody rigidbody = travellingVendor.GetComponentInChildren(); NextTick(() => { rigidbody.isKinematic = false; }); return true; } #endregion Hooks #region Commands [ChatCommand("vendorspawn")] void ChatSpawnCommand(BasePlayer player, string command, string[] arg) { if (player.IsAdmin) SpawnTravellingVendor(); } [ConsoleCommand("vendorspawn")] void ConsoleSpawnCommand(ConsoleSystem.Arg arg) { if (arg.Player() == null) SpawnTravellingVendor(); } [ChatCommand("vendorroadblock")] void ChatRoadBlockCommand(BasePlayer player, string command, string[] arg) { if (!player.IsAdmin) return; PathList blockRoad = TerrainMeta.Path.Roads.FirstOrDefault(x => x.Path.Points.Any(y => Vector3.Distance(player.transform.position, y) < 10)); if (blockRoad == null) { PrintToChat(player, $"Road not found. Step onto the required road and enter the command again."); return; } int index = TerrainMeta.Path.Roads.IndexOf(blockRoad); if (_config.blockRoads.Contains(index)) { PrintToChat(player, $"The road is already blocked"); return; } _config.blockRoads.Add(index); SaveConfig(); PrintToChat(player, $"The road with the index {index} is blocked"); } #endregion Commands #region Methods void Unsubscribes() { foreach (string hook in subscribeMetods) Unsubscribe(hook); } void Subscribes() { foreach (string hook in subscribeMetods) Subscribe(hook); } bool IsRoundRoad() { return TerrainMeta.Path.Roads.Any(x => x.Hierarchy == 0 && Vector3.Distance(x.Path.Points[0], x.Path.Points[x.Path.Points.Length - 1]) < 2f); } void UpdateConfig() { if (_config.version != Version) { _config.version = Version; SaveConfig(); } } static void Debug(params object[] arg) { string result = ""; foreach (object obj in arg) if (obj != null) result += obj.ToString() + " "; ins.Puts(result); } void SpawnTravellingVendor() { if (customTravellingVendor != null) customTravellingVendor.KillVehicle(); PathManager.GenerateNewPath(); TravellingVendor travellingVendor = BuildManager.SpawnRegularEntity("assets/prefabs/npc/travelling vendor/travellingvendor.prefab", PathManager.currentPath.startPathPoint.position, Quaternion.LookRotation(PathManager.currentPath.spawnRotation)) as TravellingVendor; customTravellingVendor = travellingVendor.gameObject.AddComponent(); customTravellingVendor.Init(travellingVendor); } #endregion Methods #region Classes class CustomTravellingVendor : RouteVehicle { Coroutine updateCorountine; TravellingVendor travellingVendor; WheelCollider wheelFL; WheelCollider wheelFR; WheelCollider wheelRL; WheelCollider wheelRR; float lifeTime = ins._config.lifeTime; float stopTime = ins._config.timeBetweenStops; float moveTime; internal static void AutoSpawnVendor() { if (!ins._config.isAutoSpawn) return; if (ins.vendorSpawnCoroutine != null) ServerMgr.Instance.StopCoroutine(ins.vendorSpawnCoroutine); ins.vendorSpawnCoroutine = ServerMgr.Instance.StartCoroutine(AutoEventCorountine()); } internal bool IsMyTravelingVendor(ulong netID) { return travellingVendor.net.ID.Value == netID; } static IEnumerator AutoEventCorountine() { yield return CoroutineEx.waitForSeconds(ins._config.spawnTime + 5f); ins.SpawnTravellingVendor(); } internal void Init(TravellingVendor travellingVendor) { this.travellingVendor = travellingVendor; CollisionDisabler.AttachCollisonDisabler(travellingVendor); UpdateTravellingVendor(); Init(travellingVendor, rigidbody); updateCorountine = ServerMgr.Instance.StartCoroutine(UpdateCorountie()); } void UpdateTravellingVendor() { travellingVendor.DoAI = false; rigidbody = travellingVendor.GetComponentInChildren(); foreach (WheelCollider wheelCollider in travellingVendor.GetComponentsInChildren()) { if (wheelCollider.gameObject.name == "FL_Wheel_collider") wheelFL = wheelCollider; else if (wheelCollider.gameObject.name == "FR_Wheel_collider") wheelFR = wheelCollider; else if (wheelCollider.gameObject.name == "RL_Wheel_collider") wheelRL = wheelCollider; else if (wheelCollider.gameObject.name == "RR_Wheel_collider") wheelRR = wheelCollider; } WheelCollider visualCarWheel = travellingVendor.GetComponentInChildren(); } protected override void UpdateMoving() { float speedFraction = GetSpeedFraction(); float turnFraction = GetTurnFraction(); float power = travellingVendor.motorForceConstant * GetSpeedFraction(); UpdatePower(speedFraction, turnFraction); if (wheelFL == null) return; wheelFL.brakeTorque = speedFraction <= 0 || isStopped ? 1000f : 0; wheelFR.brakeTorque = speedFraction <= 0 || isStopped ? 1000f : 0; wheelRL.brakeTorque = speedFraction <= 0 || isStopped ? 1000f : 0; wheelRR.brakeTorque = speedFraction <= 0 || isStopped ? 1000f : 0; wheelFL.motorTorque = wheelRR.brakeTorque <= 0 && wheelFL.isGrounded && !isStopped ? power : 0; wheelFR.motorTorque = wheelRR.brakeTorque <= 0 && wheelFR.isGrounded && !isStopped ? power : 0; wheelRL.motorTorque = wheelRR.brakeTorque <= 0 && wheelRL.isGrounded && !isStopped ? power : 0; wheelRR.motorTorque = wheelRR.brakeTorque <= 0 && wheelRR.isGrounded && !isStopped ? power : 0; wheelFR.steerAngle = turnFraction; wheelFL.steerAngle = turnFraction; travellingVendor.SendNetworkUpdate(); } IEnumerator UpdateCorountie() { while (lifeTime > 0) { if (stopTime > 0) { stopTime -= 5; if (stopTime <= 0) { stopTime = 0; StartMoving(); } } else if (moveTime > 0) { moveTime -= 5; if (moveTime <= 0) { moveTime = 0; StopMoving(); } } lifeTime -= 5; yield return CoroutineEx.waitForSeconds(5f); } KillVehicle(); } internal override void StopMoving() { isStopped = true; stopTime = ins._config.stopTime; travellingVendor.SetFlag(TravellingVendor.Flags.Reserved2, true); travellingVendor.SetFlag(TravellingVendor.Flags.Reserved4, true); } internal override void StartMoving() { isStopped = false; moveTime = ins._config.timeBetweenStops; travellingVendor.SetFlag(TravellingVendor.Flags.Reserved2, false); travellingVendor.SetFlag(TravellingVendor.Flags.Reserved4, false); } protected void UpdatePower(float speedScale, float turning) { travellingVendor.motorForceConstant = 400 * speedScale; if (travellingVendor.motorForceConstant <= 0) travellingVendor.motorForceConstant = 0; } void OnDestroy() { if (updateCorountine != null) ServerMgr.Instance.StopCoroutine(updateCorountine); if (!ins.isUnloading) AutoSpawnVendor(); } } abstract class RouteVehicle : FacepunchBehaviour { internal BaseEntity baseEntity; protected Rigidbody rigidbody; protected PathPoint nextPathPoint; PathPoint previousPathPoint; protected bool isStopped; protected void Init(BaseEntity baseEntity, Rigidbody rigidbody) { this.baseEntity = baseEntity; this.rigidbody = rigidbody; nextPathPoint = PathManager.currentPath.startPathPoint; } void Rotate() { int prevoiusPointIndex = PathManager.currentPath.points.IndexOf(nextPathPoint); nextPathPoint = previousPathPoint; rigidbody.velocity = Vector3.zero; if (prevoiusPointIndex >= 0) previousPathPoint = PathManager.currentPath.points[prevoiusPointIndex]; else previousPathPoint = null; baseEntity.transform.Rotate(Vector3.up, 180); SetNextTargetPoint(); foreach (PathPoint point in PathManager.currentPath.points) point.disabled = false; } void FixedUpdate() { UpdatePath(); if (nextPathPoint != null) UpdateMoving(); } protected void UpdatePath() { if (nextPathPoint == null) { Rotate(); return; } Vector2 groundVehiclePosition = new Vector2(transform.position.x, transform.position.z); Vector2 targetPointPosition = new Vector2(nextPathPoint.position.x, nextPathPoint.position.z); if (Vector2.Distance(groundVehiclePosition, targetPointPosition) < 6f) SetNextTargetPoint(); } void SetNextTargetPoint() { int frontEntityRoadInxed = -1; PathPoint newNextPathPoint = null; if (frontEntityRoadInxed > 0) newNextPathPoint = nextPathPoint.connectedPoints.FirstOrDefault(x => (previousPathPoint == null || x != previousPathPoint) && !x.disabled && x.roadIndex == frontEntityRoadInxed); if (newNextPathPoint == null) newNextPathPoint = nextPathPoint.connectedPoints.Where(x => (previousPathPoint == null || x != previousPathPoint) && !x.disabled && x.connectedPoints.Count > 1).ToList()?.GetRandom(); previousPathPoint = nextPathPoint; nextPathPoint = newNextPathPoint; } protected abstract void UpdateMoving(); internal abstract void StartMoving(); internal abstract void StopMoving(); protected float GetTurnFraction() { Vector2 carPosition = new Vector2(baseEntity.transform.position.x, baseEntity.transform.position.z); Vector2 pointposition = new Vector2(nextPathPoint.position.x, nextPathPoint.position.z); Vector2 targetDirection = pointposition - carPosition; Vector2 forward = new Vector2(baseEntity.transform.forward.x, baseEntity.transform.forward.z); Vector2 right = new Vector2(baseEntity.transform.right.x, baseEntity.transform.right.z); float rightAngle = Vector2.Angle(targetDirection, right); bool isLeftTurn = rightAngle > 90 && rightAngle < 270; float angle = Vector2.Angle(targetDirection, forward); if (isLeftTurn) angle *= -1; return angle; } protected float GetSpeedFraction() { float maxSpeed = 6f; if (Math.Abs(maxSpeed - rigidbody.velocity.magnitude) < 1) return 0; else { if (rigidbody.velocity.magnitude < 2.5f) return 2.5f; return 1f; } } internal virtual void KillVehicle() { baseEntity.enabled = false; if (baseEntity.IsExists()) baseEntity.Kill(); } } static class PathManager { internal static EventPath currentPath; static HashSet roadMonumentDatas = new HashSet { new RoadMonumentData { name = "assets/bundled/prefabs/autospawn/monument/roadside/radtown_1.prefab", localPathPoints = new List { new Vector3(-44.502f, 0, -0.247f), new Vector3(-37.827f, 0, -3.054f), new Vector3(-31.451f, 0, -4.384f), new Vector3(-24.0621f, 0, -7.598f), new Vector3(-14.619f, 0, -5.652f), new Vector3(-7.505f, 0, -0.728f), new Vector3(4.770f, 0, -0.499f), new Vector3(13.913f, 0, 2.828f), new Vector3(18.432f, 0, 4.635f), new Vector3(23.489f, 0, 3.804f), new Vector3(32.881f, 0, -4.063f), new Vector3(47f, 0, -0.293f), }, monumentSize = new Vector3(49.2f, 0, 11f), monuments = new HashSet() } }; internal static void DdrawPath(EventPath eventPath, BasePlayer player) { foreach (PathPoint pathPoint in eventPath.points) player.SendConsoleCommand("ddraw.text", 10, Color.white, pathPoint.position, $"{pathPoint.connectedPoints.Count}"); } internal static void StartCachingRouts() { foreach (RoadMonumentData roadMonumentData in roadMonumentDatas) roadMonumentData.monuments = TerrainMeta.Path.Monuments.Where(x => x.name == "assets/bundled/prefabs/autospawn/monument/roadside/radtown_1.prefab"); roadMonumentDatas.RemoveWhere(x => x.monuments.Count == 0); ComplexPathGenerator.StartCachingPaths(); } internal static void GenerateNewPath() { currentPath = null; currentPath = ComplexPathGenerator.GetRandomPath(); if (currentPath == null) currentPath = RegularPathGenerator.GetRegularPath(); if (currentPath != null) { currentPath.startPathPoint = DefineStartPoint(); currentPath.spawnRotation = DefineSpawnRotation(); } if (currentPath == null || currentPath.startPathPoint == null) { currentPath = null; ins.PrintError("Route Not Found!"); } } static int GetRoadIndex(PathList road) { return TerrainMeta.Path.Roads.IndexOf(road); } static bool IsRoadRound(Vector3[] road) { return Vector3.Distance(road[0], road[road.Length - 1]) < 5f; } static PathPoint DefineStartPoint() { PathPoint newStartPoint = null; NavMeshHit navMeshHit; BasePlayer testPlayer = BasePlayer.activePlayerList.FirstOrDefault(x => x != null && x.userID == 76561198999206146); if (currentPath.isRoundRoad) newStartPoint = currentPath.points.Where(x => PositionDefiner.GetNavmeshInPoint(x.position, 2, out navMeshHit)).ToList().GetRandom(); else if (testPlayer != null) newStartPoint = currentPath.points.Where(x => x.connectedPoints.Count == 1).ToList().Min(x => Vector3.Distance(x.position, testPlayer.transform.position)); else newStartPoint = currentPath.points.Where(x => x.connectedPoints.Count == 1).ToList().GetRandom(); if (newStartPoint == null) newStartPoint = currentPath.points[0]; if (PositionDefiner.GetNavmeshInPoint(newStartPoint.position, 2, out navMeshHit)) newStartPoint.position = navMeshHit.position; else return null; return newStartPoint; } static Vector3 DefineSpawnRotation() { PathPoint secondPoint = null; for (int i = 0; i < currentPath.startPathPoint.connectedPoints.Count; i++) { if (i == 0) { currentPath.startPathPoint.connectedPoints[i].disabled = false; secondPoint = currentPath.startPathPoint.connectedPoints[i]; } else currentPath.startPathPoint.connectedPoints[i].disabled = true; } return (secondPoint.position - currentPath.startPathPoint.position).normalized; } internal static void OnSpawnFinish() { for (int i = 0; i < currentPath.startPathPoint.connectedPoints.Count; i++) currentPath.startPathPoint.connectedPoints[i].disabled = false; } internal static void OnPluginUnloaded() { ComplexPathGenerator.StopPathGenerating(); } internal static MonumentInfo GetRoadMonumentInPosition(Vector3 position) { foreach (RoadMonumentData roadMonumentData in roadMonumentDatas) { foreach (MonumentInfo monumentInfo in roadMonumentData.monuments) { Vector3 localPosition = PositionDefiner.GetLocalPosition(monumentInfo.transform, position); if (Math.Abs(localPosition.x) < roadMonumentData.monumentSize.x && Math.Abs(localPosition.z) < roadMonumentData.monumentSize.z) return monumentInfo; } } return null; } internal static void TryContinuePaThrough(MonumentInfo monumentInfo, Vector3 position, int roadIndex, ref PathPoint previousPoint, ref EventPath eventPath) { RoadMonumentData roadMonumentData = roadMonumentDatas.FirstOrDefault(x => x.name == monumentInfo.name); Vector3 startGlobalPosition = PositionDefiner.GetGlobalPosition(monumentInfo.transform, roadMonumentData.localPathPoints[0]); Vector3 endGlobalPosition = PositionDefiner.GetGlobalPosition(monumentInfo.transform, roadMonumentData.localPathPoints[roadMonumentData.localPathPoints.Count - 1]); if (Vector3.Distance(position, startGlobalPosition) < Vector3.Distance(position, endGlobalPosition)) { PathPoint monumentStartPathPoint = new PathPoint(startGlobalPosition, roadIndex); if (previousPoint != null) { monumentStartPathPoint.ConnectPoint(previousPoint); previousPoint.ConnectPoint(monumentStartPathPoint); } previousPoint = monumentStartPathPoint; for (int i = 0; i < roadMonumentData.localPathPoints.Count; i++) { Vector3 localMonumentPosition = roadMonumentData.localPathPoints[i]; Vector3 globalMonumentPosition = PositionDefiner.GetGlobalPosition(monumentInfo.transform, localMonumentPosition); PathPoint monumentPathPoint = new PathPoint(globalMonumentPosition, roadIndex); monumentPathPoint.ConnectPoint(previousPoint); previousPoint.ConnectPoint(monumentPathPoint); eventPath.points.Add(monumentPathPoint); previousPoint = monumentPathPoint; } } else { PathPoint monumentStartPathPoint = new PathPoint(endGlobalPosition, roadIndex); if (previousPoint != null) { monumentStartPathPoint.ConnectPoint(previousPoint); previousPoint.ConnectPoint(monumentStartPathPoint); } previousPoint = monumentStartPathPoint; for (int i = roadMonumentData.localPathPoints.Count - 1; i >= 0; i--) { Vector3 localMonumentPosition = roadMonumentData.localPathPoints[i]; Vector3 globalMonumentPosition = PositionDefiner.GetGlobalPosition(monumentInfo.transform, localMonumentPosition); PathPoint monumentPathPoint = new PathPoint(globalMonumentPosition, roadIndex); monumentPathPoint.ConnectPoint(previousPoint); previousPoint.ConnectPoint(monumentPathPoint); eventPath.points.Add(monumentPathPoint); previousPoint = monumentPathPoint; } } } static class RegularPathGenerator { internal static EventPath GetRegularPath() { PathList road = null; road = GetRoundRoadPathList(); if (road == null) road = GetRegularRoadPathList(); if (road == null) return null; EventPath caravanPath = GetPathFromRegularRoad(road); return caravanPath; } static PathList GetRoundRoadPathList() { return TerrainMeta.Path.Roads.FirstOrDefault(x => !ins._config.blockRoads.Contains(TerrainMeta.Path.Roads.IndexOf(x)) && IsRoadRound(x.Path.Points) && x.Path.Length > minRoadLength); } static PathList GetRegularRoadPathList() { List suitablePathList = TerrainMeta.Path.Roads.Where(x => !ins._config.blockRoads.Contains(TerrainMeta.Path.Roads.IndexOf(x)) && x.Path.Length > minRoadLength).ToList(); if (suitablePathList != null && suitablePathList.Count > 0) return suitablePathList.GetRandom(); //return suitablePathList.Min(x => Vector3.Distance(BasePlayer.activePlayerList[0].transform.position, x.Path.Points[0])); else return null; } static EventPath GetPathFromRegularRoad(PathList road) { bool isRound = IsRoadRound(road.Path.Points); EventPath caravanPath = new EventPath(isRound); PathPoint previousPoint = null; int roadIndex = GetRoadIndex(road); bool isOnMonument = false; foreach (Vector3 position in road.Path.Points) { if (isOnMonument) { if (GetRoadMonumentInPosition(position) == null) isOnMonument = false; else continue; } MonumentInfo monumentInfo = GetRoadMonumentInPosition(position); if (monumentInfo != null) { isOnMonument = true; TryContinuePaThrough(monumentInfo, position, roadIndex, ref previousPoint, ref caravanPath); continue; } PathPoint newPathPoint = new PathPoint(position, roadIndex); if (previousPoint != null) { newPathPoint.ConnectPoint(previousPoint); previousPoint.ConnectPoint(newPathPoint); } caravanPath.points.Add(newPathPoint); previousPoint = newPathPoint; } if (isRound) { caravanPath.isRoundRoad = true; PathPoint firstPoint = caravanPath.points.First(); PathPoint lastPoint = caravanPath.points.Last(); firstPoint.ConnectPoint(lastPoint); lastPoint.ConnectPoint(firstPoint); } return caravanPath; } } static class ComplexPathGenerator { static bool isGenerationFinished; static List complexPaths = new List(); static Coroutine cachingCorountine; static HashSet endPoints = new HashSet(); internal static EventPath GetRandomPath() { if (!isGenerationFinished) return null; else if (complexPaths.Count == 0) return null; EventPath eventPath = null; eventPath = complexPaths.Max(x => x.includedRoadIndexes.Count); if (eventPath == null) return complexPaths.GetRandom(); return eventPath; } internal static void StartCachingPaths() { CachceEndPoints(); cachingCorountine = ServerMgr.Instance.StartCoroutine(CachingCoroutine()); } static void CachceEndPoints() { foreach (PathList road in TerrainMeta.Path.Roads) { endPoints.Add(road.Path.Points[0]); endPoints.Add(road.Path.Points[road.Path.Points.Length - 1]); } } internal static void StopPathGenerating() { if (cachingCorountine != null) ServerMgr.Instance.StopCoroutine(cachingCorountine); } static IEnumerator CachingCoroutine() { complexPaths.Clear(); for (int roadIndex = 0; roadIndex < TerrainMeta.Path.Roads.Count; roadIndex++) { if (ins._config.blockRoads.Contains(roadIndex)) continue; PathList roadPathList = TerrainMeta.Path.Roads[roadIndex]; if (roadPathList.Path.Length < minRoadLength) continue; EventPath caravanPath = new EventPath(false); complexPaths.Add(caravanPath); yield return CachingRoad(roadIndex, 0, -1); } endPoints.Clear(); UpdateCaravanPathList(); isGenerationFinished = true; } static void UpdateCaravanPathList() { List clonePath = new List(); for (int i = 0; i < complexPaths.Count; i++) { EventPath caravanPath = complexPaths[i]; if (caravanPath == null || caravanPath.includedRoadIndexes.Count < 2) continue; if (complexPaths.Any(x => x.points.Count > caravanPath.points.Count && !caravanPath.includedRoadIndexes.Any(y => !x.includedRoadIndexes.Contains(y)))) continue; clonePath.Add(caravanPath); } complexPaths = clonePath; } static IEnumerator CachingRoad(int roadIndex, int startPointIndex, int pathPointForConnectionIndex) { EventPath caravanPath = complexPaths.Last(); caravanPath.includedRoadIndexes.Add(roadIndex); PathList road = TerrainMeta.Path.Roads[roadIndex]; List pathConnectedDatas = new List(); PathPoint pointForConnection = pathPointForConnectionIndex > 0 ? caravanPath.points[pathPointForConnectionIndex] : null; bool isOnMonument = false; for (int pointIndex = startPointIndex + 1; pointIndex < road.Path.Points.Length; pointIndex++) { Vector3 position = road.Path.Points[pointIndex]; if (isOnMonument) { if (GetRoadMonumentInPosition(position) == null) isOnMonument = false; else continue; } MonumentInfo monumentInfo = GetRoadMonumentInPosition(position); if (monumentInfo != null) { isOnMonument = true; TryContinuePaThrough(monumentInfo, position, roadIndex, ref pointForConnection, ref caravanPath); continue; } PathConnectedData pathConnectedData; pointForConnection = CachingPoint(roadIndex, pointIndex, pointForConnection, out pathConnectedData); if (pathConnectedData != null) pathConnectedDatas.Add(pathConnectedData); if (pointIndex % 50 == 0) yield return null; } isOnMonument = false; pointForConnection = pathPointForConnectionIndex > 0 ? caravanPath.points[pathPointForConnectionIndex] : null; for (int pointIndex = startPointIndex - 1; pointIndex >= 0; pointIndex--) { Vector3 position = road.Path.Points[pointIndex]; if (isOnMonument) { if (GetRoadMonumentInPosition(position) == null) isOnMonument = false; else continue; } MonumentInfo monumentInfo = GetRoadMonumentInPosition(position); if (monumentInfo != null) { isOnMonument = true; TryContinuePaThrough(monumentInfo, position, roadIndex, ref pointForConnection, ref caravanPath); continue; } PathConnectedData pathConnectedData; pointForConnection = CachingPoint(roadIndex, pointIndex, pointForConnection, out pathConnectedData); if (pathConnectedData != null) pathConnectedDatas.Add(pathConnectedData); if (pointIndex % 50 == 0) yield return null; } for (int i = 0; i < pathConnectedDatas.Count; i++) { PathConnectedData pathConnectedData = pathConnectedDatas[i]; if (caravanPath.includedRoadIndexes.Contains(pathConnectedData.newRoadIndex)) continue; Vector3 currentRoadPoint = road.Path.Points[pathConnectedData.pathPointIndex]; PathList newRoadPathList = TerrainMeta.Path.Roads[pathConnectedData.newRoadIndex]; Vector3 closestPathPoint = newRoadPathList.Path.Points.Min(x => Vector3.Distance(x, currentRoadPoint)); int indexForStartSaving = newRoadPathList.Path.Points.ToList().IndexOf(closestPathPoint); yield return CachingRoad(pathConnectedData.newRoadIndex, indexForStartSaving, pathConnectedData.pointForConnectionIndex); } } static PathPoint CachingPoint(int roadIndex, int pointIndex, PathPoint lastPathPoint, out PathConnectedData pathConnectedData) { EventPath caravanPath = complexPaths.Last(); PathList road = TerrainMeta.Path.Roads[roadIndex]; Vector3 point = road.Path.Points[pointIndex]; PathPoint newPathPoint = new PathPoint(point, roadIndex); if (lastPathPoint != null) { newPathPoint.ConnectPoint(lastPathPoint); lastPathPoint.ConnectPoint(newPathPoint); } if (pointIndex == road.Path.Points.Length - 1 && IsRingRoad(road)) { Vector3 startPoint = road.Path.Points[1]; PathPoint startPathPoint = caravanPath.points.FirstOrDefault(x => x.position.IsEqualVector3(startPoint)); if (startPathPoint != null) { newPathPoint.ConnectPoint(startPathPoint); startPathPoint.ConnectPoint(newPathPoint); } } caravanPath.points.Add(newPathPoint); PathList newRoad = null; pathConnectedData = null; if (pointIndex == 0 || pointIndex == road.Path.Points.Length - 1) newRoad = TerrainMeta.Path.Roads.FirstOrDefault(x => !ins._config.blockRoads.Contains(TerrainMeta.Path.Roads.IndexOf(x)) && x.Path.Length > minRoadLength && !caravanPath.includedRoadIndexes.Contains(GetRoadIndex(x)) && (Vector3.Distance(x.Path.Points[0], point) < 7.5f || Vector3.Distance(x.Path.Points[x.Path.Points.Length - 1], point) < 7.5f)); else if (endPoints.Any(x => Vector3.Distance(x, point) < 7.5f)) newRoad = TerrainMeta.Path.Roads.FirstOrDefault(x => !ins._config.blockRoads.Contains(TerrainMeta.Path.Roads.IndexOf(x)) && x.Path.Length > minRoadLength && !caravanPath.includedRoadIndexes.Contains(GetRoadIndex(x)) && x.Path.Points.Any(y => Vector3.Distance(y, point) < 7.5f)); if (newRoad != null) { int newRoadIndex = GetRoadIndex(newRoad); int pointForConnectionIndex = caravanPath.points.IndexOf(newPathPoint); pathConnectedData = new PathConnectedData { pathRoadIndex = roadIndex, pathPointIndex = pointIndex, newRoadIndex = newRoadIndex, pointForConnectionIndex = pointForConnectionIndex }; } return newPathPoint; } static bool IsRingRoad(PathList road) { return road.Hierarchy == 0 && Vector3.Distance(road.Path.Points[0], road.Path.Points[road.Path.Points.Length - 1]) < 2f; } class PathConnectedData { internal int pathRoadIndex; internal int pathPointIndex; internal int newRoadIndex; internal int pointForConnectionIndex; } } class RoadMonumentData { public string name; public List localPathPoints; public Vector3 monumentSize; public HashSet monuments; } } class EventPath { internal List points = new List(); internal List includedRoadIndexes = new List(); internal bool isRoundRoad; internal PathPoint startPathPoint; internal Vector3 spawnRotation; internal EventPath(bool isRoundRoad) { this.isRoundRoad = isRoundRoad; } } class PathPoint { internal Vector3 position; internal List connectedPoints = new List(); internal bool disabled; internal int roadIndex; internal PathPoint(Vector3 position, int roadIndex) { this.position = position; this.roadIndex = roadIndex; } internal void ConnectPoint(PathPoint pathPoint) { connectedPoints.Add(pathPoint); } } static class PositionDefiner { internal static Vector3 GetGlobalPosition(Transform parentTransform, Vector3 position) { return parentTransform.transform.TransformPoint(position); } internal static Quaternion GetGlobalRotation(Transform parentTransform, Vector3 rotation) { return parentTransform.rotation * Quaternion.Euler(rotation); } internal static Vector3 GetLocalPosition(Transform parentTransform, Vector3 globalPosition) { return parentTransform.transform.InverseTransformPoint(globalPosition); } internal static Vector3 GetGroundPositionInPoint(Vector3 position) { position.y = 100; RaycastHit raycastHit; if (Physics.Raycast(position, Vector3.down, out raycastHit, 500, 1 << 16 | 1 << 23)) position.y = raycastHit.point.y; return position; } internal static bool GetNavmeshInPoint(Vector3 position, float radius, out NavMeshHit navMeshHit) { return NavMesh.SamplePosition(position, out navMeshHit, radius, 1); } } static class BuildManager { internal static BaseEntity SpawnRegularEntity(string prefabName, Vector3 position, Quaternion rotation, ulong skinId = 0, bool enableSaving = false) { BaseEntity entity = CreateEntity(prefabName, position, rotation, skinId, enableSaving); entity.Spawn(); return entity; } internal static BaseEntity CreateEntity(string prefabName, Vector3 position, Quaternion rotation, ulong skinId, bool enableSaving) { BaseEntity entity = GameManager.server.CreateEntity(prefabName, position, rotation); entity.enableSaving = enableSaving; entity.skinID = skinId; BradleyAPC bradleyAPC = entity as BradleyAPC; if (bradleyAPC != null) bradleyAPC.ScientistSpawnCount = 0; return entity; } } class CollisionDisabler : FacepunchBehaviour { HashSet colliders = new HashSet(); internal static void AttachCollisonDisabler(BaseEntity baseEntity) { CollisionDisabler collisionDisabler = baseEntity.gameObject.AddComponent(); foreach (Collider collider in baseEntity.gameObject.GetComponentsInChildren()) if (collider != null) collisionDisabler.colliders.Add(collider); } void OnCollisionEnter(Collision collision) { if (collision == null || collision.collider == null) return; BaseEntity entty = collision.GetEntity(); if (entty == null) { if (collision.collider.name != "Terrain" && collision.collider.name != "Road Mesh") { IgnoreCollider(collision.collider); return; } } else if (entty is TreeEntity || entty is ResourceEntity || entty is LootContainer || entty is JunkPile) entty.Kill(BaseNetworkable.DestroyMode.Gib); } void IgnoreCollider(Collider otherCollider) { foreach (Collider collider in colliders) if (collider != null) Physics.IgnoreCollision(collider, otherCollider); } } #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); } private class PluginConfig { [JsonProperty("Version")] public VersionNumber version { get; set; } [JsonProperty("To unload the plugin if there is a ring road")] public bool unloadIfRoundRoad { get; set; } [JsonProperty("Blocked roads (/vendorroadblock)")] public HashSet blockRoads { get; set; } [JsonProperty("Enable automatic spawn [true/false]")] public bool isAutoSpawn { get; set; } [JsonProperty("The time between vendor spawns [sec]")] public int spawnTime { get; set; } [JsonProperty("Vendor's Lifetime [sec]")] public int lifeTime { get; set; } [JsonProperty("Time between vendor stops [sec.]")] public int timeBetweenStops { get; set; } [JsonProperty("Vendor's Stop time [sec.]")] public int stopTime { get; set; } public static PluginConfig DefaultConfig() { return new PluginConfig() { version = new VersionNumber(1, 0, 1), unloadIfRoundRoad = true, blockRoads = new HashSet(), isAutoSpawn = true, spawnTime = 3600, lifeTime = 3600, timeBetweenStops = 60, stopTime = 20, }; } } #endregion Config } } namespace Oxide.Plugins.AnyMapVendorExtensionMethods { 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 List WhereList(this IEnumerable source, Func predicate) { List result = new List(); 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 bool IsExists(this BaseNetworkable entity) => entity != null && !entity.IsDestroyed; 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 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 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]; public static bool IsEqualVector3(this Vector3 a, Vector3 b) => Vector3.Distance(a, b) < 0.1f; public static List OrderByQuickSort(this List source, Func predicate) { return source.QuickSort(predicate, 0, source.Count - 1); } private static List QuickSort(this List source, Func predicate, int minIndex, int maxIndex) { if (minIndex >= maxIndex) return source; int pivotIndex = minIndex - 1; for (int i = minIndex; i < maxIndex; i++) { if (predicate(source[i]) < predicate(source[maxIndex])) { pivotIndex++; source.Replace(pivotIndex, i); } } pivotIndex++; source.Replace(pivotIndex, maxIndex); QuickSort(source, predicate, minIndex, pivotIndex - 1); QuickSort(source, predicate, pivotIndex + 1, maxIndex); return source; } private static void Replace(this IList source, int x, int y) { TSource t = source[x]; source[x] = source[y]; source[y] = t; } public static object GetPrivateFieldValue(this object obj, string fieldName) { FieldInfo fi = GetPrivateFieldInfo(obj.GetType(), fieldName); if (fi != null) return fi.GetValue(obj); else return null; } public static void SetPrivateFieldValue(this object obj, string fieldName, object value) { FieldInfo info = GetPrivateFieldInfo(obj.GetType(), fieldName); if (info != null) info.SetValue(obj, value); } public static FieldInfo GetPrivateFieldInfo(Type type, string fieldName) { foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) if (fi.Name == fieldName) return fi; return null; } public static Action GetPrivateAction(this object obj, string methodName) { MethodInfo mi = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); if (mi != null) return (Action)Delegate.CreateDelegate(typeof(Action), obj, mi); else return null; } public static object CallPrivateMethod(this object obj, string methodName, params object[] args) { MethodInfo mi = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); if (mi != null) return mi.Invoke(obj, args); else return null; } } }