C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// Reference: RustBuild
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Data;
using UnityEngine;
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
namespace Oxide.Plugins {
[Info("PathFinding", "Reneb", "0.0.8")]
class PathFinding : RustPlugin {
public class Pathfinder {
public SortedList<float, List<PathfindNode>> SortNode;
public Hash<Vector3, PathfindNode> NodeList;
public Hash<Vector3, bool> ClosedList;
public List<PathfindNode> RunDetection;
public PathfindNode targetNode;
public float currentPriority;
public int Loops;
public bool shouldBreak;
public Pathfinder() {
SortNode = new SortedList<float, List<PathfindNode>>();
NodeList = new Hash<Vector3, PathfindNode>();
RunDetection = new List<PathfindNode>();
ClosedList = new Hash<Vector3, bool>();
}
public List<Vector3> FindPath(Vector3 sourcePos, Vector3 targetPos) {
Reset();
this.targetNode = new PathfindNode(this);
PathfindGoal(this, this.targetNode, targetPos);
PathfindFirst(this, new PathfindNode(this), sourcePos);
while (true) {
currentPriority = SortNode.Keys[0];
foreach (PathfindNode pathnode in (SortNode[currentPriority])) RunDetection.Add(pathnode);
SortNode.Remove(currentPriority);
foreach (PathfindNode pathnode in RunDetection) {
pathnode.DetectAdjacentNodes();
if (pathnode.isGoal) {
targetNode.parentNode = pathnode;
shouldBreak = true;
}
}
RunDetection.Clear();
Loops++;
if (Loops > MaxLoops) {
Reset();
return null;
}
if (shouldBreak) break;
}
PathfindNode parentnode = targetNode.parentNode;
List<Vector3> PlayerPath = new List<Vector3>();
while (true) {
PlayerPath.Add(parentnode.position);
parentnode = parentnode.parentNode;
if (parentnode == null) break;
}
PlayerPath.Reverse();
PlayerPath.RemoveAt(0);
Reset();
return PlayerPath;
}
public void Reset() {
currentPriority = 0f;
SortNode.Clear();
RunDetection.Clear();
NodeList.Clear();
Loops = 0;
shouldBreak = false;
}
public void AddToPriorityList(PathfindNode currentNode) {
if (!SortNode.ContainsKey(currentNode.F)) SortNode.Add(currentNode.F, new List<PathfindNode>());
((List<PathfindNode>)SortNode[currentNode.F]).Add(currentNode);
}
}
public class PathfindNode {
// FIELDS
public float H = 0f;
public float G = 0f;
public float F = 0f;
public PathfindNode parentNode = null;
public PathfindNode north = null;
public PathfindNode northeast = null;
public PathfindNode northwest = null;
public PathfindNode east = null;
public PathfindNode south = null;
public PathfindNode southeast = null;
public PathfindNode southwest = null;
public PathfindNode west = null;
public Vector3 position;
public Vector3 positionEyes;
public bool isGoal = false;
public Pathfinder pathfinder;
///
/// PathfindNode()
/// Raw pathfind creation
public PathfindNode(Pathfinder pathfinder) {
this.pathfinder = pathfinder;
}
///
/// PathfindNode(PathfindNode parentnode, Vector3 position, bool diagonal)
/// This is called by all new pathnodes
public PathfindNode(Pathfinder pathfinder, PathfindNode parentnode, Vector3 position, bool diagonal) {
this.pathfinder = pathfinder;
this.position = position;
this.positionEyes = new Vector3(position.x, Mathf.Ceil(position.y) + 0.5f, position.z);
this.parentNode = parentnode;
CalculateManhattanDistance(this, pathfinder.targetNode);
CalculateMovementCost(this, diagonal);
this.F = this.H + this.G;
pathfinder.AddToPriorityList(this);
}
// METHODS
// DetectAdjacentNodes()
// This automatically creates the surrounding pathnodes
public void DetectAdjacentNodes() {
if (!pathfinder.ClosedList[this.positionEyes + VectorForward]) if (!Physics.Linecast(this.positionEyes, this.positionEyes + VectorForward, blockLayer)) north = FindPathNodeOrCreate(this.pathfinder, this, this.positionEyes + VectorForward, false);
if (!pathfinder.ClosedList[this.positionEyes + VectorRight]) if (!Physics.Linecast(this.positionEyes, this.positionEyes + VectorRight, blockLayer)) east = FindPathNodeOrCreate(this.pathfinder, this, this.positionEyes + VectorRight, false);
if (!pathfinder.ClosedList[this.positionEyes + VectorBack]) if (!Physics.Linecast(this.positionEyes, this.positionEyes + VectorBack, blockLayer)) south = FindPathNodeOrCreate(this.pathfinder, this, this.positionEyes + VectorBack, false);
if (!pathfinder.ClosedList[this.positionEyes + VectorLeft]) if (!Physics.Linecast(this.positionEyes, this.positionEyes + VectorLeft, blockLayer)) west = FindPathNodeOrCreate(this.pathfinder, this, this.positionEyes + VectorLeft, false);
if (!pathfinder.ClosedList[this.positionEyes + VectorForwardRight]) if (!Physics.Linecast(this.positionEyes, this.positionEyes + VectorForwardRight, blockLayer)) northeast = FindPathNodeOrCreate(this.pathfinder, this, this.positionEyes + VectorForwardRight, true);
if (!pathfinder.ClosedList[this.positionEyes + VectorForwardLeft]) if (!Physics.Linecast(this.positionEyes, this.positionEyes + VectorForwardLeft, blockLayer)) northwest = FindPathNodeOrCreate(this.pathfinder, this, this.positionEyes + VectorForwardLeft, true);
if (!pathfinder.ClosedList[this.positionEyes + VectorBackLeft]) if (!Physics.Linecast(this.positionEyes, this.positionEyes + VectorBackLeft, blockLayer)) southeast = FindPathNodeOrCreate(this.pathfinder, this, this.positionEyes + VectorBackLeft, true);
if (!pathfinder.ClosedList[this.positionEyes + VectorBackRight]) if (!Physics.Linecast(this.positionEyes, this.positionEyes + VectorBackRight, blockLayer)) southwest = FindPathNodeOrCreate(this.pathfinder, this, this.positionEyes + VectorBackRight, true);
}
}
// Here we calculate the movement cost between 2 points.private static void CalculateMovementCost(PathfindNode currentNode, bool diagonal) { currentNode.G = currentNode.parentNode.G + (diagonal ? 14f : 10f); }
// Here we calculate the distance between the current node and the target node
private static void CalculateManhattanDistance(PathfindNode currentNode, PathfindNode targetNode) {
currentNode.H = ((Mathf.Abs(currentNode.positionEyes.x - targetNode.positionEyes.x) + Mathf.Abs(currentNode.positionEyes.z - targetNode.positionEyes.z) + Mathf.Abs(currentNode.positionEyes.y - targetNode.positionEyes.y)) * 10);
}
// Create a new node or get the node information
public static PathfindNode FindPathNodeOrCreate(Pathfinder pathfinder, PathfindNode parentnode, Vector3 position, bool diagonal) {
pathfinder.ClosedList[position] = true;
if (!FindGroundPosition(position, out GroundPosition, out FixedGroundPosition)) return null;
if (pathfinder.NodeList[FixedGroundPosition] == null) pathfinder.NodeList[FixedGroundPosition] = new PathfindNode(pathfinder, parentnode, GroundPosition, diagonal);
else if (pathfinder.NodeList[FixedGroundPosition].isGoal) {
pathfinder.targetNode.parentNode = parentnode;
parentnode.isGoal = true;
}
return null;
}
public static bool FindGroundPosition(Vector3 sourcePos, out Vector3 groundPos, out Vector3 fixedPos) {
groundPos = fixedPos = sourcePos;
if (Physics.Raycast(sourcePos, Vector3Down, out hitinfo, groundLayer)) {
groundPos.y = hitinfo.point.y;
fixedPos.y = Mathf.Ceil(hitinfo.point.y);
return true;
}
return false;
}
public static bool FindRawGroundPosition(Vector3 sourcePos, out Vector3 groundPos) {
groundPos = sourcePos;
if (Physics.Raycast(sourcePos, Vector3Down, out hitinfo, groundLayer)) {
groundPos.y = hitinfo.point.y;
return true;
}
return false;
}
public static bool FindRawGroundPositionUP(Vector3 sourcePos, out Vector3 groundPos) {
groundPos = sourcePos;
if (Physics.Raycast(sourcePos, Vector3UP, out hitinfo, groundLayer)) {
groundPos.y = hitinfo.point.y;
return true;
}
return false;
}
/// PathfindNode(Vector3 position, Quaternion rotation)
/// This is called by the First Path
public static void PathfindFirst(Pathfinder pathfinder, PathfindNode pathfindnode, Vector3 position) {
pathfindnode.position = position;
pathfindnode.positionEyes = new Vector3(Mathf.Floor(position.x), Mathf.Ceil(position.y) + 0.5f, Mathf.Floor(position.z));
pathfindnode.H = 0;
pathfindnode.G = 0;
pathfindnode.F = 0;
pathfinder.AddToPriorityList(pathfindnode);
}
///
/// PathfindNode(Vector3 position)
/// This is called by the Goal
public static void PathfindGoal(Pathfinder pathfinder, PathfindNode pathfindnode, Vector3 position) {
pathfindnode.position = position;
pathfindnode.positionEyes = new Vector3(Mathf.Floor(position.x), Mathf.Ceil(position.y) + 0.5f, Mathf.Floor(position.z));
pathfindnode.isGoal = true;
pathfinder.NodeList[pathfindnode.positionEyes - EyesPosition] = pathfindnode;
}
class PathFollower : MonoBehaviour {
public List<Vector3> Paths = new List<Vector3>();
public float secondsTaken;
public float secondsToTake;
public float waypointDone;
public float speed;
public Vector3 StartPos;
public Vector3 EndPos;
public Vector3 nextPos;
public BaseEntity entity;
public BasePlayer player;
void Awake() {
entity = GetComponent<BaseEntity>();
if (GetComponent<BasePlayer>() != null) player = GetComponent<BasePlayer>();
speed = 4f;
}
void Move() {
if (secondsTaken == 0f) FindNextWaypoint();
Execute_Move();
if (waypointDone >= 1f) secondsTaken = 0f;
}
void Execute_Move() {
if (StartPos != EndPos) {
secondsTaken += Time.deltaTime;
waypointDone = Mathf.InverseLerp(0f, secondsToTake, secondsTaken);
nextPos = Vector3.Lerp(StartPos, EndPos, waypointDone);
entity.transform.position = nextPos;
if (player != null) player.ClientRPCPlayer(null, player, "ForcePositionTo", nextPos);
entity.TransformChanged();
}
}
void FindNextWaypoint() {
if (Paths.Count == 0) {
StartPos = EndPos = Vector3.zero;
enabled = false;
return;
}
SetMovementPoint(Paths[0], 4f);
}
public void SetMovementPoint(Vector3 endpos, float s) {
StartPos = entity.transform.position;
if (endpos != StartPos) {
EndPos = endpos;
secondsToTake = Vector3.Distance(EndPos, StartPos) / s;
entity.transform.rotation = Quaternion.LookRotation(EndPos - StartPos);
if (player != null) SetViewAngle(player, entity.transform.rotation);
secondsTaken = 0f;
waypointDone = 0f;
}
Paths.RemoveAt(0);
}
void FixedUpdate() {
Move();
}
}
static void SetViewAngle(BasePlayer player, Quaternion ViewAngles) {
viewangles.SetValue(player, ViewAngles);
player.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
}
public static RaycastHit hitinfo;
public static Vector3 jumpPosition = new Vector3(0f, 1f, 0f);
public static Vector3 GroundPosition = new Vector3(0f, 0f, 0f);
public static Vector3 FixedGroundPosition = new Vector3(0f, 0f, 0f);
public static Vector3 EyesPosition = new Vector3(0f, 0.5f, 0f);
public static Vector3 Vector3Down = new Vector3(0f, -1f, 0f);
public static Vector3 Vector3UP = new Vector3(0f, 1f, 0f);
public static Vector3 VectorForward = new Vector3(0f, 0f, 1f);
public static Vector3 VectorBack = new Vector3(0f, 0f, -1f);
public static Vector3 VectorRight = new Vector3(1f, 0f, 0f);
public static Vector3 VectorLeft = new Vector3(-1f, 0f, 0f);
public static Vector3 VectorForwardRight = VectorForward + VectorRight;
public static Vector3 VectorForwardLeft = VectorForward + VectorLeft;
public static Vector3 VectorBackRight = VectorBack + VectorRight;
public static Vector3 VectorBackLeft = VectorBack + VectorLeft;
public static int groundLayer;
public static int blockLayer;
private Quaternion currentRot;
public Quaternion viewAngle;
private static FieldInfo serverinput;
private static FieldInfo viewangles;
public object closestEnt;
public Vector3 closestHitpoint;
private Oxide.Plugins.Timer PathfindingTimer;
private static int MaxLoops = 500;
void LoadDefaultConfig() {
}
private void CheckCfg<T>(string Key, ref T var) {
if (Config[Key] is T) var = (T)Config[Key];
else Config[Key] = var;
}
void Init() {
CheckCfg<int>("Max Loops", ref MaxLoops);
SaveConfig();
}
/////////////////////////////////////////////
/// OXIDE HOOKS
/////////////////////////////////////////////
void OnServerInitialized() {
groundLayer = UnityEngine.LayerMask.GetMask(new string[] {
"Terrain", "World", "Construction"
}
);
blockLayer = UnityEngine.LayerMask.GetMask(new string[] {
"World", "Construction", "Tree"
}
);
serverinput = typeof(BasePlayer).GetField("serverInput", (BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic));
viewangles = typeof(BasePlayer).GetField("viewAngles", (BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic));
PathfindingTimer = timer.Once(30f, () => ResetPathFollowers());
}
void Unload() {
var objects = GameObject.FindObjectsOfType(typeof(PathFollower));
if (objects != null) foreach (var gameObj in objects) GameObject.Destroy(gameObj);
}
/////////////////////////////////////////////
/// Outside Plugin Calls
/////////////////////////////////////////////
bool FindAndFollowPath(BaseEntity entity, Vector3 sourcePosition, Vector3 targetPosition) {
//var curtime = Time.realtimeSinceStartup;
var bestPath = FindBestPath(sourcePosition, targetPosition);
//Debug.Log((Time.realtimeSinceStartup - curtime).ToString());
if (bestPath == null) return false;
FollowPath(entity, bestPath);
return true;
}
void FollowPath(BaseEntity entity, List<Vector3> pathpoints) {
PathFollower pathfollower;
if (entity.GetComponent<PathFollower>() != null) pathfollower = entity.GetComponent<PathFollower>();
else pathfollower = entity.gameObject.AddComponent<PathFollower>();
pathfollower.Paths = pathpoints;
pathfollower.enabled = true;
}
List<Vector3> FindBestPath(Vector3 sourcePosition, Vector3 targetPosition) {
List<Vector3> bestPath = FindLinePath(sourcePosition, targetPosition);
if (bestPath == null) bestPath = FindPath(sourcePosition, targetPosition);
return bestPath;
}
List<Vector3> FindPath(Vector3 sourcePosition, Vector3 targetPosition) {
var path = new Pathfinder();
var FoundPath = path.FindPath(sourcePosition, targetPosition);
path = null;
return FoundPath;
}
List<Vector3> FindLinePath(Vector3 sourcePosition, Vector3 targetPosition) {
float distance = (int)Mathf.Ceil(Vector3.Distance(sourcePosition, targetPosition));
Hash<float, Vector3> StraightPath = new Hash<float, Vector3>();
StraightPath[0f] = sourcePosition;
Vector3 currentPos;
for (float i = 1f; i < distance; i++) {
currentPos = Vector3.Lerp(sourcePosition, targetPosition, i / distance);
if (!FindRawGroundPosition(currentPos, out GroundPosition)) if (!FindRawGroundPositionUP(currentPos, out GroundPosition)) return null;
if (Vector3.Distance(GroundPosition, StraightPath[i - 1f]) > 2) return null;
if (Physics.Linecast(StraightPath[i - 1f] + jumpPosition, GroundPosition + jumpPosition, blockLayer)) return null;
StraightPath[i] = GroundPosition;
}
if (Physics.Linecast(StraightPath[distance - 1f] + jumpPosition, targetPosition + jumpPosition, blockLayer)) return null;
StraightPath[distance] = targetPosition;
StraightPath.Remove(0f);
List<Vector3> straightPath = new List<Vector3>();
foreach (KeyValuePair<float, Vector3> pair in StraightPath) {
straightPath.Add(pair.Value);
}
StraightPath.Clear();
return straightPath;
}
/////////////////////////////////////////////
/// Reset part of the plugin
/////////////////////////////////////////////
void ResetPathFollowers() {
var objects = GameObject.FindObjectsOfType(typeof(PathFollower));
if (objects != null) foreach (PathFollower gameObj in objects) if (gameObj.Paths.Count == 0) GameObject.Destroy(gameObj);
}
/////////////////////////////////////////////
/// Debug Command
/////////////////////////////////////////////
[ChatCommand("path")]
void cmdChatPath(BasePlayer player, string command, string[] args) {
if (player.net.connection.authLevel < 1) return;
if (!TryGetPlayerView(player, out currentRot)) return;
if (!TryGetClosestRayPoint(player.transform.position, currentRot, out closestEnt, out closestHitpoint)) return;
FindAndFollowPath(player, player.transform.position, closestHitpoint);
}
bool TryGetPlayerView(BasePlayer player, out Quaternion viewAngle) {
viewAngle = new Quaternion(0f, 0f, 0f, 0f);
var input = serverinput.GetValue(player) as InputState;
if (input == null) return false;
if (input.current == null) return false;
viewAngle = Quaternion.Euler(input.current.aimAngles);
return true;
}
bool TryGetClosestRayPoint(Vector3 sourcePos, Quaternion sourceDir, out object closestEnt, out Vector3 closestHitpoint) {
Vector3 sourceEye = sourcePos + new Vector3(0f, 1.5f, 0f);
UnityEngine.Ray ray = new UnityEngine.Ray(sourceEye, sourceDir * Vector3.forward);
var hits = UnityEngine.Physics.RaycastAll(ray);
float closestdist = 999999f;
closestHitpoint = sourcePos;
closestEnt = false;
foreach (var hit in hits) if (hit.collider.GetComponentInParent<TriggerBase>() == null) if (hit.distance < closestdist) {
closestdist = hit.distance;
closestEnt = hit.collider;
closestHitpoint = hit.point;
}
if (closestEnt is bool) return false;
return true;
}
}
}