C#:
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Plugins.AnyMapVendorExtensionMethods;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
namespace Oxide.Plugins {
[Info("Any Map Vendor", "Adem", "1.0.2")]
public class AnyMapVendor : RustPlugin {
#region Variables
private static AnyMapVendor _ins;
private CustomTravellingVendor _customTravellingVendor;
private readonly HashSet<string> _subscribeMetods = new HashSet<string> {
"CanUseGesture", "OnEntityEnter"
}
;
private static readonly float MinRoadLength = 100;
private Coroutine _vendorSpawnCoroutine;
private bool _isUnloading;
private EventPath _currentPath;
#endregion Variables
#region Hooks
private void Init() {
Unsubscribes();
}
private 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();
}
private void Unload() {
_isUnloading = true;
Unsubscribes();
if (_ins._vendorSpawnCoroutine != null) ServerMgr.Instance.StopCoroutine(_ins._vendorSpawnCoroutine);
if (_customTravellingVendor != null) _customTravellingVendor.KillVehicle();
}
private void CanUseGesture(BasePlayer player, GestureConfig gesture) {
if (_customTravellingVendor != null && Vector3.Distance(player.transform.position, _customTravellingVendor.transform.position) < 15f) _customTravellingVendor.StopMoving();
}
private 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<Rigidbody>();
NextTick(() => {
rigidbody.isKinematic = false;
}
);
return true;
}
#endregion Hooks
#region Commands
[ChatCommand("vendorspawn")]
private void ChatSpawnCommand(BasePlayer player, string command, string[] arg) {
if (player.IsAdmin) SpawnTravellingVendor();
}
[ConsoleCommand("vendorspawn")]
private void ConsoleSpawnCommand(ConsoleSystem.Arg arg) {
if (arg.Player() == null) SpawnTravellingVendor();
}
[ChatCommand("vendorroadblock")]
private 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 <color=#ce3f27>not found</color>. Step onto the required road and enter the command again.");
return;
}
int index = TerrainMeta.Path.Roads.IndexOf(blockRoad);
if (!_config.BlockRoads.Add(index)) {
PrintToChat(player, $"The road is already <color=#ce3f27>blocked</color>");
return;
}
SaveConfig();
PrintToChat(player, $"The road with the index <color=#738d43>{index}</color> is <color=#ce3f27>blocked</color>");
}
#endregion Commands
#region Methods
private void Unsubscribes() {
foreach (string hook in _subscribeMetods) Unsubscribe(hook);
}
private void Subscribes() {
foreach (string hook in _subscribeMetods) Subscribe(hook);
}
private bool IsRoundRoad() {
return TerrainMeta.Path.Roads.Any(x => x.Hierarchy == 0 && Vector3.Distance(x.Path.Points[0], x.Path.Points[^1]) < 2f);
}
private void UpdateConfig() {
if (_config.Version != Version) {
_config.Version = Version;
SaveConfig();
}
}
private static void Debug(params object[] arg) {
string result = "";
foreach (object obj in arg) if (obj != null) result += obj.ToString() + " ";
_ins.Puts(result);
}
private void SpawnTravellingVendor() {
if (_customTravellingVendor != null) _customTravellingVendor.KillVehicle();
PathManager.GenerateNewPath();
TravellingVendor travellingVendor = BuildManager.SpawnRegularEntity("assets/prefabs/npc/travelling vendor/travellingvendor.prefab", _ins._currentPath.StartPathPoint.Position, Quaternion.LookRotation(_ins._currentPath.SpawnRotation)) as TravellingVendor;
_customTravellingVendor = travellingVendor.gameObject.AddComponent<CustomTravellingVendor>();
_customTravellingVendor.Init(travellingVendor);
}
#endregion Methods
#region Classes
private class CustomTravellingVendor : RouteVehicle {
private Coroutine _updateCorountine;
private TravellingVendor _travellingVendor;
private WheelCollider _wheelFl;
private WheelCollider _wheelFr;
private WheelCollider _wheelRl;
private WheelCollider _wheelRr;
private float _lifeTime = _ins._config.LifeTime;
private float _stopTime = _ins._config.TimeBetweenStops;
private float _moveTime;
public static void AutoSpawnVendor() {
if (!_ins._config.IsAutoSpawn) return;
if (_ins._vendorSpawnCoroutine != null) ServerMgr.Instance.StopCoroutine(_ins._vendorSpawnCoroutine);
_ins._vendorSpawnCoroutine = ServerMgr.Instance.StartCoroutine(AutoEventCorountine());
}
public bool IsMyTravelingVendor(ulong netID) {
return _travellingVendor.net.ID.Value == netID;
}
private static IEnumerator AutoEventCorountine() {
yield return CoroutineEx.waitForSeconds(_ins._config.SpawnTime + 5f);
_ins.SpawnTravellingVendor();
}
public void Init(TravellingVendor travellingVendor) {
this._travellingVendor = travellingVendor;
CollisionDisabler.AttachCollisonDisabler(travellingVendor);
UpdateTravellingVendor();
Init(travellingVendor, Rigidbody);
_updateCorountine = ServerMgr.Instance.StartCoroutine(UpdateCorountie());
}
private void UpdateTravellingVendor() {
_travellingVendor.DoAI = false;
Rigidbody = _travellingVendor.GetComponentInChildren<Rigidbody>();
foreach (WheelCollider wheelCollider in _travellingVendor.GetComponentsInChildren<WheelCollider>()) {
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;
}
}
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();
}
private 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();
}
public override void StopMoving() {
IsStopped = true;
_stopTime = _ins._config.StopTime;
_travellingVendor.SetFlagLocal(BaseEntity.Flags.Reserved2, true);
_travellingVendor.SetFlagLocal(BaseEntity.Flags.Reserved4, true);
_travellingVendor.SendNetworkUpdate();
}
protected override void StartMoving() {
IsStopped = false;
_moveTime = _ins._config.TimeBetweenStops;
_travellingVendor.SetFlagLocal(BaseEntity.Flags.Reserved2, false);
_travellingVendor.SetFlagLocal(BaseEntity.Flags.Reserved4, false);
_travellingVendor.SendNetworkUpdate();
}
private void UpdatePower(float speedScale, float turning) {
_travellingVendor.motorForceConstant = 400 * speedScale;
if (_travellingVendor.motorForceConstant <= 0) _travellingVendor.motorForceConstant = 0;
}
private void OnDestroy() {
if (_updateCorountine != null) ServerMgr.Instance.StopCoroutine(_updateCorountine);
if (!_ins._isUnloading) AutoSpawnVendor();
}
}
private abstract class RouteVehicle : FacepunchBehaviour {
public BaseEntity baseEntity;
protected Rigidbody Rigidbody;
private PathPoint _nextPathPoint;
private PathPoint _previousPathPoint;
protected bool IsStopped;
protected void Init(BaseEntity entity, Rigidbody rigidbody) {
baseEntity = entity;
Rigidbody = rigidbody;
_nextPathPoint = _ins._currentPath.StartPathPoint;
}
private void Rotate() {
int prevoiusPointIndex = _ins._currentPath.Points.IndexOf(_nextPathPoint);
_nextPathPoint = _previousPathPoint;
Rigidbody.linearVelocity = Vector3.zero;
if (prevoiusPointIndex >= 0) _previousPathPoint = _ins._currentPath.Points[prevoiusPointIndex];
else _previousPathPoint = null;
baseEntity.transform.Rotate(Vector3.up, 180);
SetNextTargetPoint();
foreach (PathPoint point in _ins._currentPath.Points) point.Disabled = false;
}
private void FixedUpdate() {
UpdatePath();
if (_nextPathPoint != null) UpdateMoving();
}
private 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();
}
private void SetNextTargetPoint() {
PathPoint 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();
protected abstract void StartMoving();
public 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.linearVelocity.magnitude) < 1) return 0;
if (Rigidbody.linearVelocity.magnitude < 2.5f) return 2.5f;
return 1f;
}
public void KillVehicle() {
baseEntity.enabled = false;
if (baseEntity.IsExists()) baseEntity.Kill();
}
}
private static class PathManager {
private static readonly HashSet<RoadMonumentData> RoadMonumentDatas = new HashSet<RoadMonumentData> {
new RoadMonumentData {
Name = "assets/bundled/prefabs/autospawn/monument/roadside/radtown_1.prefab", LocalPathPoints = new List<Vector3> {
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<MonumentInfo>()
}
}
;
public static void DdrawPath(EventPath eventPath, BasePlayer player) {
foreach (PathPoint pathPoint in eventPath.Points) player.SendConsoleCommand("ddraw.text", 10, Color.white, pathPoint.Position, $"<size=50>{pathPoint.ConnectedPoints.Count}</size>");
}
public 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();
}
public static void GenerateNewPath() {
_ins._currentPath = null;
_ins._currentPath = ComplexPathGenerator.GetRandomPath();
if (_ins._currentPath == null) _ins._currentPath = RegularPathGenerator.GetRegularPath();
if (_ins._currentPath != null) {
_ins._currentPath.StartPathPoint = DefineStartPoint();
_ins._currentPath.SpawnRotation = DefineSpawnRotation();
}
if (_ins._currentPath == null || _ins._currentPath.StartPathPoint == null) {
_ins._currentPath = null;
_ins.PrintError("Route Not Found!");
}
}
private static int GetRoadIndex(PathList road) {
return TerrainMeta.Path.Roads.IndexOf(road);
}
private static bool IsRoadRound(Vector3[] road) {
return Vector3.Distance(road[0], road[^1]) < 5f;
}
private static PathPoint DefineStartPoint() {
PathPoint newStartPoint;
NavMeshHit navMeshHit;
BasePlayer testPlayer = BasePlayer.activePlayerList.FirstOrDefault(x => x != null && x.userID == 76561198999206146);
if (_ins._currentPath.IsRoundRoad) newStartPoint = _ins._currentPath.Points.Where(x => PositionDefiner.GetNavmeshInPoint(x.Position, 2, out navMeshHit)).ToList().GetRandom();
else if (testPlayer != null) newStartPoint = _ins._currentPath.Points.Where(x => x.ConnectedPoints.Count == 1).ToList().Min(x => Vector3.Distance(x.Position, testPlayer.transform.position));
else newStartPoint = _ins._currentPath.Points.Where(x => x.ConnectedPoints.Count == 1).ToList().GetRandom();
if (newStartPoint == null) newStartPoint = _ins._currentPath.Points[0];
if (PositionDefiner.GetNavmeshInPoint(newStartPoint.Position, 2, out navMeshHit)) newStartPoint.Position = navMeshHit.position;
else return null;
return newStartPoint;
}
private static Vector3 DefineSpawnRotation() {
PathPoint secondPoint = null;
for (int i = 0; i < _ins._currentPath.StartPathPoint.ConnectedPoints.Count; i++) {
if (i == 0) {
_ins._currentPath.StartPathPoint.ConnectedPoints[i].Disabled = false;
secondPoint = _ins._currentPath.StartPathPoint.ConnectedPoints[i];
}
else _ins._currentPath.StartPathPoint.ConnectedPoints[i].Disabled = true;
}
return (secondPoint.Position - _ins._currentPath.StartPathPoint.Position).normalized;
}
public static void OnSpawnFinish() {
for (int i = 0; i < _ins._currentPath.StartPathPoint.ConnectedPoints.Count; i++) _ins._currentPath.StartPathPoint.ConnectedPoints[i].Disabled = false;
}
public static void OnPluginUnloaded() {
ComplexPathGenerator.StopPathGenerating();
}
public 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;
}
public 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[^1]);
if (Vector3.Distance(position, startGlobalPosition) < Vector3.Distance(position, endGlobalPosition)) {
PathPoint monumentStartPathPoint = new PathPoint(startGlobalPosition);
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);
monumentPathPoint.ConnectPoint(previousPoint);
previousPoint.ConnectPoint(monumentPathPoint);
eventPath.Points.Add(monumentPathPoint);
previousPoint = monumentPathPoint;
}
}
else {
PathPoint monumentStartPathPoint = new PathPoint(endGlobalPosition);
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);
monumentPathPoint.ConnectPoint(previousPoint);
previousPoint.ConnectPoint(monumentPathPoint);
eventPath.Points.Add(monumentPathPoint);
previousPoint = monumentPathPoint;
}
}
}
private static class RegularPathGenerator {
public static EventPath GetRegularPath() {
PathList road = GetRoundRoadPathList();
if (road == null) road = GetRegularRoadPathList();
if (road == null) return null;
EventPath caravanPath = GetPathFromRegularRoad(road);
return caravanPath;
}
private 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);
}
private static PathList GetRegularRoadPathList() {
List<PathList> 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;
}
private 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);
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;
}
}
private static class ComplexPathGenerator {
private static bool _isGenerationFinished;
private static List<EventPath> _complexPaths = new List<EventPath>();
private static Coroutine _cachingCorountine;
private static readonly HashSet<Vector3> EndPoints = new HashSet<Vector3>();
public static EventPath GetRandomPath() {
if (!_isGenerationFinished) return null;
if (_complexPaths.Count == 0) return null;
EventPath eventPath = _complexPaths.Max(x => x.IncludedRoadIndexes.Count);
if (eventPath == null) return _complexPaths.GetRandom();
return eventPath;
}
public static void StartCachingPaths() {
CachceEndPoints();
_cachingCorountine = ServerMgr.Instance.StartCoroutine(CachingCoroutine());
}
private static void CachceEndPoints() {
foreach (PathList road in TerrainMeta.Path.Roads) {
EndPoints.Add(road.Path.Points[0]);
EndPoints.Add(road.Path.Points[^1]);
}
}
public static void StopPathGenerating() {
if (_cachingCorountine != null) ServerMgr.Instance.StopCoroutine(_cachingCorountine);
}
private 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;
}
private static void UpdateCaravanPathList() {
List<EventPath> clonePath = new List<EventPath>();
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;
}
private static IEnumerator CachingRoad(int roadIndex, int startPointIndex, int pathPointForConnectionIndex) {
EventPath caravanPath = _complexPaths.Last();
caravanPath.IncludedRoadIndexes.Add(roadIndex);
PathList road = TerrainMeta.Path.Roads[roadIndex];
List<PathConnectedData> pathConnectedDatas = new List<PathConnectedData>();
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;
}
pointForConnection = CachingPoint(roadIndex, pointIndex, pointForConnection, out PathConnectedData 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;
}
pointForConnection = CachingPoint(roadIndex, pointIndex, pointForConnection, out PathConnectedData 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);
}
}
private 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);
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[^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 {
PathPointIndex = pointIndex, NewRoadIndex = newRoadIndex, PointForConnectionIndex = pointForConnectionIndex
}
;
}
return newPathPoint;
}
private static bool IsRingRoad(PathList road) {
return road.Hierarchy == 0 && Vector3.Distance(road.Path.Points[0], road.Path.Points[^1]) < 2f;
}
private class PathConnectedData {
public int PathPointIndex;
public int NewRoadIndex;
public int PointForConnectionIndex;
}
}
private class RoadMonumentData {
public string Name;
public List<Vector3> LocalPathPoints;
public Vector3 MonumentSize;
public HashSet<MonumentInfo> Monuments;
}
}
private class EventPath {
public readonly List<PathPoint> Points = new List<PathPoint>();
public readonly List<int> IncludedRoadIndexes = new List<int>();
public bool IsRoundRoad;
public PathPoint StartPathPoint;
public Vector3 SpawnRotation;
public EventPath(bool isRoundRoad) {
IsRoundRoad = isRoundRoad;
}
}
private class PathPoint {
public Vector3 Position;
public readonly List<PathPoint> ConnectedPoints = new List<PathPoint>();
public bool Disabled;
public PathPoint(Vector3 position) {
this.Position = position;
}
public void ConnectPoint(PathPoint pathPoint) {
ConnectedPoints.Add(pathPoint);
}
}
private static class PositionDefiner {
public static Vector3 GetGlobalPosition(Transform parentTransform, Vector3 position) {
return parentTransform.transform.TransformPoint(position);
}
public static Quaternion GetGlobalRotation(Transform parentTransform, Vector3 rotation) {
return parentTransform.rotation * Quaternion.Euler(rotation);
}
public static Vector3 GetLocalPosition(Transform parentTransform, Vector3 globalPosition) {
return parentTransform.transform.InverseTransformPoint(globalPosition);
}
public static Vector3 GetGroundPositionInPoint(Vector3 position) {
position.y = 100;
if (Physics.Raycast(position, Vector3.down, out RaycastHit raycastHit, 500, 1 << 16 | 1 << 23)) position.y = raycastHit.point.y;
return position;
}
public static bool GetNavmeshInPoint(Vector3 position, float radius, out NavMeshHit navMeshHit) {
return NavMesh.SamplePosition(position, out navMeshHit, radius, 1);
}
}
private static class BuildManager {
public 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;
}
private 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;
return entity;
}
}
private class CollisionDisabler : FacepunchBehaviour {
private readonly HashSet<Collider> _colliders = new HashSet<Collider>();
public static void AttachCollisonDisabler(BaseEntity baseEntity) {
CollisionDisabler collisionDisabler = baseEntity.gameObject.AddComponent<CollisionDisabler>();
foreach (Collider collider in baseEntity.gameObject.GetComponentsInChildren<Collider>()) if (collider != null) collisionDisabler._colliders.Add(collider);
}
private 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);
}
else if (entty is TreeEntity or ResourceEntity or LootContainer or JunkPile) entty.Kill(BaseNetworkable.DestroyMode.Gib);
}
private 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<PluginConfig>();
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<int> 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, 2), UnloadIfRoundRoad = true, BlockRoads = new HashSet<int>(), IsAutoSpawn = true, SpawnTime = 3600, LifeTime = 3600, TimeBetweenStops = 60, StopTime = 20,
}
;
}
}
#endregion Config
}
}
namespace Oxide.Plugins.AnyMapVendorExtensionMethods {
public static class ExtensionMethods {
public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (predicate(enumerator.Current)) return true;
return false;
}
public static HashSet<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
HashSet<TSource> result = new HashSet<TSource>();
using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (predicate(enumerator.Current)) result.Add(enumerator.Current);
return result;
}
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (predicate(enumerator.Current)) return enumerator.Current;
return default(TSource);
}
public static bool IsExists(this BaseNetworkable entity) => entity != null && !entity.IsDestroyed;
public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) {
List<TSource> result = new List<TSource>();
using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) result.Add(enumerator.Current);
return result;
}
public static TSource Max<TSource>(this IEnumerable<TSource> source, Func<TSource, float> 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<TSource>(this IEnumerable<TSource> source, Func<TSource, float> 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;
}
private static TSource ElementAt<TSource>(this IEnumerable<TSource> 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<TSource>(this IList<TSource> source) => source[0];
public static TSource Last<TSource>(this IList<TSource> source) => source[^1];
public static bool IsEqualVector3(this Vector3 a, Vector3 b) => Vector3.Distance(a, b) < 0.1f;
}
}