C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
//#define DEBUG
// Requires: PathFinding
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Rust;
using Network;
using Facepunch;
using Facepunch.Utility;
using System;
using System.Collections.Generic;
using System.Collections;
using System.Globalization;
using System.Linq;
using UnityEngine;
using Convert = System.Convert;
namespace Oxide.Plugins {
[Info("Human NPC", "Ts3Hosting", "0.3.53")]
[Description("Adds interactive human NPCs which can be modded by other plugins")]
public class HumanNPC : RustPlugin {
//////////////////////////////////////////////////////
/// Fields
//////////////////////////////////////////////////////
private static Collider[] colBuffer;
private int playerLayer;
private static int targetLayer;
private static Vector3 Vector3Down;
private static int groundLayer;
private static HumanNPC ins;
private bool NewTeam;
private int TeamCount = 0;
private BasePlayer TeamPlayer;
private Hash<ulong, RelationshipManager.PlayerTeam> PlayersTeams = new Hash<ulong, RelationshipManager.PlayerTeam>();
private Hash<ulong, HumanNPCInfo> humannpcs = new Hash<ulong, HumanNPCInfo>();
static int playerMask = LayerMask.GetMask("Player (Server)");
static int obstructionMask = LayerMask.GetMask(new[] {
"Player (Server)", "Construction", "Deployed", "Clutter"
}
);
static int constructionMask = LayerMask.GetMask(new[] {
"Construction", "Deployed", "Clutter"
}
);
static int terrainMask = LayerMask.GetMask(new[] {
"Terrain", "Tree"
}
);
private readonly Hash<ulong, RecordingData> Recording = new Hash<ulong, RecordingData>();
private readonly Hash<string, NpcSound> Sounds = new Hash<string, NpcSound>();
private readonly Hash<string, NpcSound> cached = new Hash<string, NpcSound>();
public List<ulong> NpcTalking = new List<ulong>();
private bool save;
private StoredData storedData;
private DynamicConfigFile data;
private Vector3 eyesPosition;
private string chat = "<color=#FA58AC>{0}:</color> ";
private bool TeamsNPC = true;
private float RadiusNPC = 5.0f;
[PluginReference]
private Plugin Kits, Waypoints, Vanish;
private static PathFinding PathFinding;
private class StoredData {
public HashSet<HumanNPCInfo> HumanNPCs = new HashSet<HumanNPCInfo>();
}
public class WaypointInfo {
public float Speed;
public Vector3 Position;
public WaypointInfo(Vector3 position, float speed) {
Speed = speed;
Position = position;
}
}
public static bool IsLayerBlocked(Vector3 position, float radius, int mask) {
var colliders = Pool.GetList<Collider>();
Vis.Colliders<Collider>(position, radius, colliders, mask, QueryTriggerInteraction.Collide);
bool blocked = colliders.Count > 0;
Pool.FreeList<Collider>(ref colliders);
return blocked;
}
[ChatCommand("npc_sound")]
private void ChatCommandNpctalk(BasePlayer player, string cmd, string[] args) {
string colorCode = "#FFFF00";
if (player.net.connection.authLevel < 1) {
SendReply(player, "You do not have access to this command.");
return;
}
if (args.Length == 0) {
SendReply(player, "Record sound that can be played back with Npc\n\n" + $"<color={colorCode}>/npc_sound add soundname</color> - Start recording a new sound.\n" + $"<color={colorCode}>/npc_sound save</color> - Save your recorded sound.\n" + $"<color={colorCode}>/npc_sound cancel</color> - Cancel your recording and not save.\n" + $"<color={colorCode}>/npc_sound</color> - View help text.");
return;
}
switch (args[0].ToLower()) {
case "add" : Add(player, args);
break;
case "cancel" : Reset(player);
break;
case "save" : Save(player);
break;
default : SendReply(player, "Record sound that can be played back with Npc\n\n" + $"<color={colorCode}>/npc_sound add soundname</color> - Start recording a new sound.\n" + $"<color={colorCode}>/npc_sound save</color> - Save your recorded sound.\n" + $"<color={colorCode}>/npc_sound cancel</color> - Cancel your recording and not save.\n" + $"<color={colorCode}>/npc_sound</color> - View help text.");
break;
}
}
private void Add(BasePlayer player, string[] args) {
if (Recording.ContainsKey(player.userID)) {
SendReply(player, "You already started a recording.");
return;
}
if (args.Length < 2) {
SendReply(player, "Incorrect usage");
return;
}
bool overwrite = args.Any(a => a.ToLower() == "overwrite");
string name = string.Join(" ", args.Skip(1).Where(a => a.ToLower() != "overwrite").ToArray());
if (!overwrite && FileExists(name)) {
SendReply(player, "There is a sound file with that name already");
return;
}
Recording[player.userID] = new RecordingData {
Name = name
}
;
SendReply(player, "Start talking ingame to record your voice and /npc_sound save when done.");
}
private void Save(BasePlayer player) {
RecordingData recording = Recording[player.userID];
if (recording == null) {
SendReply(player, "You are not recording so nothing to save");
return;
}
NpcSound data = new NpcSound {
Data = recording.Data,
}
;
SaveSoundData(recording.Name, data);
Recording.Remove(player.userID);
SendReply(player, "Saved your recording as " + recording.Name);
}
private void Reset(BasePlayer player) {
RecordingData recording = Recording[player.userID];
if (recording == null) {
SendReply(player, "You are not recording so nothing to cancel");
return;
}
recording.Data.Clear();
Recording.Remove(player.userID);
SendReply(player, "Recording canceled!");
}
public class NpcSound {
[JsonConverter(typeof(SoundFileConverter))]
public List<byte[]> Data = new List<byte[]>();
}
private class SoundFileConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
List<byte[]> data = (List<byte[]>) value;
writer.WriteValue(Convert.ToBase64String(Compression.Compress(ins.ToSaveData(data))));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JToken value = JToken.Load(reader);
return ins.FromSaveData(Compression.Uncompress(Convert.FromBase64String(value.ToString())));
}
public override bool CanConvert(Type objectType) {
return typeof(List<byte>) == objectType;
}
}
private byte[] ToSaveData(List<byte[]> data) {
return data.Select(cd => BitConverter.GetBytes(cd.Length)).SelectMany(cd => cd).Concat(data.SelectMany(cd => cd)).ToArray();
}
private NpcSound LoadDataSound(string name) {
NpcSound cache = cached[name];
if (cache != null) {
return cache;
}
NpcSound data = Interface.Oxide.DataFileSystem.ReadObject<NpcSound>(Name + "/Sounds/" + name);
if (data == null) return null;
cached[name] = data;
return data;
}
private List<byte[]> FromSaveData(byte[] bytes) {
List<int> dataSize = new List<int>();
List<byte[]> dataBytes = new List<byte[]>();
int offset = 0;
while (true) {
dataSize.Add(BitConverter.ToInt32(bytes, offset));
offset += 4;
int sum = dataSize.Sum();
if (sum == bytes.Length - offset) {
break;
}
if (sum > bytes.Length - offset) {
throw new ArgumentOutOfRangeException(nameof(dataSize), $"Voice Data is outside the saved range {dataSize.Sum()} > {bytes.Length - offset}");
}
}
foreach (int size in dataSize) {
dataBytes.Add(bytes.Skip(offset).Take(size).ToArray());
offset += size;
}
return dataBytes;
}
private NpcSound LoadData(string name) {
NpcSound cache = Sounds[name];
if (cache != null) {
return cache;
}
if (!FileExists(name)) {
return null;
}
NpcSound data = Interface.Oxide.DataFileSystem.ReadObject<NpcSound>($"{Name}/Sounds/{name}");
Sounds[name] = data;
return data;
}
private class RecordingData : NpcSound {
public string Name {
get;
set;
}
}
private bool FileExists(string name) {
return Interface.Oxide.DataFileSystem.ExistsDatafile($"{Name}/Sounds/{name}");
}
private void SaveSoundData(string name, NpcSound data) {
Interface.Oxide.DataFileSystem.WriteObject($"{Name}/Sounds/{name}", data);
Sounds[name] = data;
}
private void OnPlayerVoice(BasePlayer player, byte[] data) {
RecordingData recording = Recording[player.userID];
recording?.Data.Add(data);
}
//////////////////////////////////////////////////////
/// class SpawnInfo
/// Spawn information, position & rotation
/// public => will be saved in the data file
/// non public => won't be saved in the data file
//////////////////////////////////////////////////////
public class SpawnInfo {
public Vector3 position;
public Quaternion rotation;
public SpawnInfo(Vector3 position, Quaternion rotation) {
this.position = position;
this.rotation = rotation;
}
public string String() {
return $"Pos{position} - Rot{rotation}";
}
public string ShortString() {
return $"Pos({Math.Ceiling(position.x)},{Math.Ceiling(position.y)},{Math.Ceiling(position.z)})";
}
}
//////////////////////////////////////////////////////
/// class HumanTrigger
/// MonoBehaviour: managed by UnityEngine
/// This takes care of all collisions and area management of humanNPCs
//////////////////////////////////////////////////////
public class HumanTrigger : MonoBehaviour {
private HumanPlayer npc;
private readonly HashSet<BasePlayer> triggerPlayers = new HashSet<BasePlayer>();
public float collisionRadius;
private void Awake() {
npc = GetComponent<HumanPlayer>();
collisionRadius = npc.info.collisionRadius;
InvokeRepeating("UpdateTriggerArea", 2f, 1.5f);
}
private void OnDestroy() {
#if DEBUG
//Interface.Oxide.LogInfo("Destroy child: {0}", child?.name);
#endif
CancelInvoke("UpdateTriggerArea");
}
private void UpdateTriggerArea() {
var count = Physics.OverlapSphereNonAlloc(npc.player.transform.position, collisionRadius, colBuffer, targetLayer);
var collidePlayers = new HashSet<BasePlayer>();
for (int i = 0; i < count; i++) {
var collider = colBuffer[i];
colBuffer[i] = null;
var player = collider.GetComponentInParent<BasePlayer>();
if (player != null) {
if (player == npc.player) continue;
collidePlayers.Add(player);
if (triggerPlayers.Add(player)) OnEnterCollision(player);
continue;
}
//temp fix
/*var ai = collider.GetComponentInParent<NPCAI>();
if (ai != null && ai.decider.hatesHumans)
npc.StartAttackingEntity(collider.GetComponentInParent<BaseNpc>());*/
}
var removePlayers = new HashSet<BasePlayer>();
foreach (BasePlayer player in triggerPlayers) if (!collidePlayers.Contains(player)) removePlayers.Add(player);
foreach (BasePlayer player in removePlayers) {
triggerPlayers.Remove(player);
OnLeaveCollision(player);
}
}
private void OnEnterCollision(BasePlayer player) {
Interface.Oxide.CallHook("OnEnterNPC", npc.player, player);
}
private void OnLeaveCollision(BasePlayer player) {
Interface.Oxide.CallHook("OnLeaveNPC", npc.player, player);
}
}
//////////////////////////////////////////////////////
/// class HumanLocomotion
/// MonoBehaviour: managed by UnityEngine
/// This takes care of all movements and attacks of HumanNPCs
//////////////////////////////////////////////////////
public class HumanLocomotion : MonoBehaviour {
private HumanPlayer npc;
public Vector3 StartPos = new Vector3(0f, 0f, 0f);
public Vector3 EndPos = new Vector3(0f, 0f, 0f);
public Vector3 LastPos = new Vector3(0f, 0f, 0f);
public Dictionary<Vector3, float> NoMoveInfo = new Dictionary<Vector3, float>();
private Vector3 nextPos = new Vector3(0f, 0f, 0f);
private float waypointDone = 0f;
public float secondsTaken = 0f;
private float secondsToTake = 0f;
public List<WaypointInfo> cachedWaypoints;
private int currentWaypoint = -1;
public float followDistance = 3.5f;
private float lastHit = 0f;
public int noPath = 0;
public bool shouldMove = true;
private float startedReload = 0f;
private bool reloading = false;
public bool returning = false;
public bool sitting = false;
public BaseCombatEntity attackEntity = null;
public BaseEntity followEntity = null;
public Vector3 targetPosition = Vector3.zero;
public List<Vector3> pathFinding;
private HeldEntity firstWeapon = null;
public void Awake() {
npc = GetComponent<HumanPlayer>();
UpdateWaypoints();
npc.player.modelState.onground = true;
}
public void UpdateWaypoints() {
if (string.IsNullOrEmpty(npc.info.waypoint)) return;
var cwaypoints = Interface.Oxide.CallHook("GetWaypointsList", npc.info.waypoint);
if (cwaypoints == null) cachedWaypoints = null;
else {
cachedWaypoints = new List<WaypointInfo>();
var lastPos = npc.info.spawnInfo.position;
var speed = GetSpeed();
NoMoveInfo.Clear();
foreach (var cwaypoint in (List<object>)cwaypoints) {
foreach (var pair in (Dictionary<Vector3, float>)cwaypoint) {
foreach (var find in cachedWaypoints) {
if (find.Position == pair.Key) {
if (!NoMoveInfo.ContainsKey(pair.Key)) NoMoveInfo.Add(pair.Key, pair.Value);
}
}
if (HumanNPC.PathFinding == null) {
cachedWaypoints.Add(new WaypointInfo(pair.Key, pair.Value));
continue;
}
var temppathFinding = HumanNPC.PathFinding.Go(lastPos, pair.Key);
speed = pair.Value;
if (temppathFinding != null) {
lastPos = pair.Key;
foreach (var vector3 in temppathFinding) {
cachedWaypoints.Add(new WaypointInfo(vector3, speed));
}
}
else {
#if DEBUG
Interface.Oxide.LogInfo("Blocked waypoint? {0} for {1}", pair.Key, npc.player.displayName);
#endif
//cachedWaypoints.Add(new WaypointInfo(pair.Key, speed));
}
}
}
if (HumanNPC.PathFinding != null && lastPos != npc.info.spawnInfo.position) {
var temppathFinding = HumanNPC.PathFinding.Go(lastPos, npc.info.spawnInfo.position);
if (temppathFinding != null) {
foreach (var vector3 in temppathFinding) cachedWaypoints.Add(new WaypointInfo(vector3, speed));
}
else {
#if DEBUG
Interface.Oxide.LogInfo("Blocked waypoint to spawn? {0} for {1}", lastPos, npc.player.displayName);
#endif
}
}
if (cachedWaypoints.Count <= 0) cachedWaypoints = null;
#if DEBUG
Interface.Oxide.LogInfo("Waypoints: {0} for {1}", cachedWaypoints.Count, npc.player.displayName);
#endif
}
}
private void FixedUpdate() {
TryToMove();
}
public void TryToMove() {
if (npc.player.IsDead() || npc.player.IsWounded()) return;
if (targetPosition != Vector3.zero) {
#if DEBUG
Interface.Oxide.LogInfo("TryToMove: ProcessFollow(target)");
#endif
ProcessFollow(targetPosition);
}
if (attackEntity is BaseCombatEntity) {
#if DEBUG
Interface.Oxide.LogInfo("TryToMove: ProcessAttack(attackEntity)");
#endif
ProcessAttack(attackEntity);
}
else if (followEntity is BaseEntity) {
#if DEBUG
Interface.Oxide.LogInfo("TryToMove: ProcessFollow(followEntity)");
#endif
ProcessFollow(followEntity.transform.position);
}
else if (secondsTaken == 0f) GetNextPath();
if (StartPos != EndPos) Execute_Move();
if (waypointDone >= 1f) secondsTaken = 0f;
}
private void Execute_Move() {
if (!shouldMove) return;
secondsTaken += Time.deltaTime;
waypointDone = Mathf.InverseLerp(0f, secondsToTake, secondsTaken);
nextPos = Vector3.Lerp(StartPos, EndPos, waypointDone);
nextPos.y = GetMoveY(nextPos);
npc.player.MovePosition(nextPos);
//npc.player.eyes.position = nextPos + new Vector3(0, 1.6f, 0);
var newEyesPos = nextPos + new Vector3(0, 1.6f, 0);
npc.player.eyes.position.Set(newEyesPos.x, newEyesPos.y, newEyesPos.z);
npc.player.EnablePlayerCollider();
//npc.player.UpdatePlayerCollider(true);
npc.player.modelState.onground = !IsSwimming();
}
public void Evade() {
if(IsSwimming()) return;
if(npc.info.evade == false) return;
#if DEBUG
Interface.Oxide.LogInfo("Evading...");
#endif
// var ra = UnityEngine.Random.Range(0f, 100f);
// if(ra > 40f)
// {
// npc.player.modelState.ducked = true;
// }
// else
// {
// npc.player.modelState.ducked = false;
// }
Vector3 ev = new Vector3(UnityEngine.Random.Range(-npc.info.evdist, npc.info.evdist), UnityEngine.Random.Range(-1.5f, 1.5f));
Vector3 newpos = npc.player.transform.position + ev;
#if DEBUG
Interface.Oxide.LogInfo($" first trying new position {newpos.ToString()}");
#endif
RaycastHit hitinfo;
int i = 0;
while(Physics.OverlapSphere(newpos, npc.info.evdist, constructionMask) != null) {
newpos.x = newpos.x + UnityEngine.Random.Range(-0.2f, 0.2f);
newpos.y = newpos.y + UnityEngine.Random.Range(-0.1f, 0.1f);
newpos.z = newpos.z + UnityEngine.Random.Range(-0.2f, 0.2f);
#if DEBUG
Interface.Oxide.LogInfo($" trying new position {newpos.ToString()}");
#endif
if(Physics.Raycast(newpos, Vector3Down, out hitinfo, 0.1f, groundLayer)) {
#if DEBUG
Interface.Oxide.LogInfo($" found ground or construction at {newpos.ToString()}");
#endif
break;
}
else {
newpos.y = npc.locomotion.GetGroundY(newpos);
#if DEBUG
Interface.Oxide.LogInfo($" fell through floor, relocating to {newpos.ToString()}");
#endif
}
i++;
if(i > 100) break;
}
npc.player.MovePosition(newpos);
}
public bool IsSwimming() {
return WaterLevel.Test(npc.player.transform.position + new Vector3(0, 0.65f, 0));
}
private bool CanSit() {
if(sitting) {
return false;
}
return npc.info.allowsit;
}
public void Sit() {
npc.Invoke("AllowMove", 0);
// Find a place to sit
List<BaseChair> chairs = new List<BaseChair>();
Vis.Entities<BaseChair>(npc.player.transform.position, 15f, chairs);
foreach(var mountable in chairs.Distinct().ToList()) {
#if DEBUG
Interface.Oxide.LogInfo($"HumanNPC {npc.player.displayName} trying to sit...");
#endif
if(mountable.IsMounted()) {
#if DEBUG
Interface.Oxide.LogInfo($"Someone is sitting here.");
#endif
continue;
}
#if DEBUG
Interface.Oxide.LogInfo($"Found an empty chair.");
#endif
mountable.MountPlayer(npc.player);
//npc.player.MovePosition(mountable.mountAnchor.transform.position);
//npc.player.transform.rotation = mountable.mountAnchor.transform.rotation;
//npc.player.ServerRotation = mountable.mountAnchor.transform.rotation;
npc.player.OverrideViewAngles(mountable.mountAnchor.transform.rotation.eulerAngles);
npc.player.eyes.NetworkUpdate(mountable.mountAnchor.transform.rotation);
npc.player.ClientRPCPlayer<Vector3>(null, npc.player, "ForcePositionTo", npc.player.transform.position);
mountable.SetFlag(BaseEntity.Flags.Busy, true, false);
sitting = true;
break;
}
}
public void Stand() {
//if(CanSit() && sitting)
if(sitting) {
#if DEBUG
Interface.Oxide.LogInfo($"HumanNPC {npc.player.displayName} trying to stand...");
#endif
// npc.Invoke("AllowMove",0);
var mounted = npc.player.GetMounted();
mounted.DismountPlayer(npc.player);
mounted.SetFlag(BaseEntity.Flags.Busy, false, false);
sitting = false;
}
}
private float GetSpeed(float speed = -1) {
if (sitting) speed = 0;
if (returning) speed = 7;
else if (speed == -1) speed = npc.info.speed;
if (IsSwimming()) speed = speed / 2f;
return speed;
}
private void GetNextPath() {
if (npc == null) npc = GetComponent<HumanPlayer>();
if(CanSit() && sitting == false) {
Sit();
}
LastPos = Vector3.zero;
if (cachedWaypoints == null) {
shouldMove = false;
return;
}
shouldMove = true;
Interface.Oxide.CallHook("OnNPCPosition", npc.player, npc.player.transform.position);
if (currentWaypoint + 1 >= cachedWaypoints.Count) {
UpdateWaypoints();
currentWaypoint = -1;
}
if (cachedWaypoints == null) {
shouldMove = false;
return;
}
currentWaypoint++;
var wp = cachedWaypoints[currentWaypoint];
SetMovementPoint(npc.player.transform.position, wp.Position, GetSpeed(wp.Speed));
if (NoMoveInfo.ContainsKey(wp.Position)) {
npc.DisableMove();
npc.Invoke("AllowMove", NoMoveInfo[wp.Position]);
return;
}
}
public void SetMovementPoint(Vector3 startpos, Vector3 endpos, float s) {
StartPos = startpos;
if (endpos != Vector3.zero) {
EndPos = endpos;
EndPos.y = Math.Max(EndPos.y, TerrainMeta.HeightMap.GetHeight(EndPos));
if (StartPos != EndPos) secondsToTake = Vector3.Distance(EndPos, StartPos) / s;
npc.LookTowards(EndPos);
}
else {
if (IsInvoking("PathFinding")) {
CancelInvoke("PathFinding");
}
}
secondsTaken = 0f;
waypointDone = 0f;
}
private bool HitChance(float chance = -1f) {
if (chance < 0) chance = npc.info.hitchance;
return UnityEngine.Random.Range(1, 100) < (int)(chance * 100);
}
private void Move(Vector3 position, float speed = -1) {
if (speed == -1) {
speed = npc.info.speed;
}
if (waypointDone >= 1f) {
if (pathFinding != null && pathFinding.Count > 0) pathFinding.RemoveAt(pathFinding.Count - 1);
waypointDone = 0f;
}
if (pathFinding == null || pathFinding.Count < 1) return;
shouldMove = true;
if (waypointDone == 0f) SetMovementPoint(position, pathFinding[pathFinding.Count - 1], GetSpeed(speed));
}
private void ProcessAttack(BaseCombatEntity entity) {
#if DEBUG
Interface.Oxide.LogInfo("ProcessAttack: {0} -> {1}", npc.player.displayName, entity.name);
#endif
if (entity != null && entity.IsAlive()) {
//var c_attackDistance = Vector3.Distance(entity.transform.position, npc.player.transform.position);
var c_attackDistance = Vector3.Distance(entity.transform.position + new Vector3(0, 1.6f, 0), npc.player.transform.position + new Vector3(0, 1.6f, 0));
shouldMove = false;
bool validAttack = Vector3.Distance(LastPos, npc.player.transform.position) < npc.info.maxDistance && noPath < 5;
#if DEBUG
Interface.Oxide.LogInfo(" Entity: Type {0}, alive {1}, valid {2}", entity.GetType().FullName, entity.IsAlive(), validAttack);
#endif
if (validAttack) {
bool range = false;
if(npc.info.follow) {
range = c_attackDistance < npc.info.damageDistance;
}
else {
range = c_attackDistance < npc.info.maxDistance;
}
var see = CanSee(npc, entity);
#if DEBUG
Interface.Oxide.LogInfo(" validAttack Entity: Type {0}, ranged {1}, cansee {2}", entity.GetType().FullName, range, see);
#endif
if (range && see) {
AttemptAttack(entity);
return;
}
if (GetSpeed() <= 0) {
npc.EndAttackingEntity();
}
else if(!npc.info.follow) {
}
else {
Move(npc.player.transform.position);
}
}
else {
npc.EndAttackingEntity();
}
}
else {
npc.EndAttackingEntity();
}
}
public void ProcessFollow(Vector3 target) {
#if DEBUG
Interface.Oxide.LogInfo($"ProcessFollow() called for {target.ToString()}");
#endif
var c_followDistance = Vector3.Distance(target, npc.player.transform.position);
shouldMove = false;
#if DEBUG
Interface.Oxide.LogInfo($"ProcessFollow() distance {c_followDistance.ToString()}");
#endif
//if (c_followDistance > 0)// && Vector3.Distance(LastPos, npc.player.transform.position) < followDistance)// && noPath < 5)
if (c_followDistance > followDistance && Vector3.Distance(LastPos, npc.player.transform.position) < npc.info.maxDistance && noPath < 5) {
Move(npc.player.transform.position, npc.info.speed);
}
else {
if (followEntity is BaseEntity) {
#if DEBUG
Interface.Oxide.LogInfo($"ProcessFollow() bailing out - is BaseEntity");
#endif
npc.EndFollowingEntity(noPath < 5);
}
else if (targetPosition != Vector3.zero) {
#if DEBUG
Interface.Oxide.LogInfo($"ProcessFollow() bailing out");
#endif
npc.EndGo(noPath < 5);
}
}
}
public void PathFinding() {
Vector3 target = Vector3.zero;
if (attackEntity != null) {
//Vector3 diff = new Vector3(Core.Random.Range(-npc.info.attackDistance, npc.info.attackDistance), 0, Core.Random.Range(-npc.info.attackDistance, npc.info.attackDistance));
target = attackEntity.transform.position;
// + diff;
}
else if (followEntity != null) {
target = followEntity.transform.position;
}
else if (targetPosition != Vector3.zero) {
target = targetPosition;
}
if (target != Vector3.zero) {
PathFinding(new Vector3(target.x, GetMoveY(target), target.z));
}
}
public void PathFinding(Vector3 targetPos) {
if (gameObject == null) return;
if (IsInvoking("PathFinding")) {
CancelInvoke("PathFinding");
}
if (GetSpeed() <= 0) return;
var temppathFinding = HumanNPC.PathFinding?.Go(npc.player.transform.position, targetPos);
if (temppathFinding == null) {
if(pathFinding == null || pathFinding.Count == 0) {
noPath++;
}
else {
noPath = 0;
}
if(noPath < 5) {
Invoke("PathFinding", 2);
}
else if (returning) {
returning = false;
SetMovementPoint(npc.player.transform.position, LastPos, 7f);
secondsTaken = 0.01f;
}
}
else {
noPath = 0;
pathFinding = temppathFinding;
pathFinding.Reverse();
waypointDone = 0f;
Invoke("PathFinding", pathFinding.Count / GetSpeed(npc.info.speed));
}
}
public void GetBackToLastPos() {
if (npc.player.transform.position == LastPos) return;
if (LastPos == Vector3.zero) LastPos = npc.info.spawnInfo.position;
if (Vector3.Distance(npc.player.transform.position, LastPos) < 5) {
SetMovementPoint(npc.player.transform.position, LastPos, 7f);
secondsTaken = 0.01f;
return;
}
returning = true;
npc.StartGo(LastPos);
}
public void Enable() {
//if (GetSpeed() <= 0) return;
enabled = true;
}
public void Disable() {
enabled = false;
}
public float GetMoveY(Vector3 position) {
if (IsSwimming()) {
float point = TerrainMeta.WaterMap.GetHeight(position) - 0.65f;
float groundY = GetGroundY(position);
if (groundY > point) {
return groundY;
}
return point - 0.65f;
}
return GetGroundY(position);
}
public float GetGroundY(Vector3 position) {
position = position + Vector3.up;
RaycastHit hitinfo;
if (Physics.Raycast(position, Vector3Down, out hitinfo, 100f, groundLayer)) {
return hitinfo.point.y;
}
return position.y -.5f;
}
public void CreateProjectileEffect(BaseCombatEntity target, BaseProjectile baseProjectile, float dmg, bool miss = false) {
if (baseProjectile.primaryMagazine.contents <= 0) {
#if DEBUG
Interface.Oxide.LogInfo("Attack failed(empty): {0} - {1}", npc.player.displayName, attackEntity.name);
#endif
return;
}
var component = baseProjectile.primaryMagazine.ammoType.GetComponent<ItemModProjectile>();
if (component == null) {
#if DEBUG
Interface.Oxide.LogInfo("Attack failed(Component): {0} - {1}", npc.player.displayName, attackEntity.name);
#endif
return;
}
npc.LookTowards(target.transform.position);
var source = npc.player.transform.position + npc.player.GetOffset();
if(baseProjectile.MuzzlePoint != null) {
source += Quaternion.LookRotation(target.transform.position - npc.player.transform.position) * baseProjectile.MuzzlePoint.position;
}
var dir = (target.transform.position + npc.player.GetOffset() - source).normalized;
var vector32 = dir * (component.projectileVelocity * baseProjectile.projectileVelocityScale);
Vector3 hit;
RaycastHit raycastHit;
if(Vector3.Distance(npc.player.transform.position, target.transform.position) < 0.5) {
hit = target.transform.position + npc.player.GetOffset(true);
}
else if(!Physics.SphereCast(source, .01f, vector32, out raycastHit, float.MaxValue, targetLayer)) {
#if DEBUG
Interface.Oxide.LogInfo("Attack failed: {0} - {1}", npc.player.displayName, attackEntity.name);
#endif
return;
}
else {
hit = raycastHit.point;
// target = raycastHit.GetCollider().GetComponent<BaseCombatEntity>();
#if DEBUG
Interface.Oxide.LogInfo("Attack failed: {0} - {1}", raycastHit.GetCollider().name, (Rust.Layer)raycastHit.GetCollider().gameObject.layer);
#endif
miss = miss || target == null;
}
baseProjectile.primaryMagazine.contents--;
npc.ForceSignalAttack();
if(baseProjectile.MuzzlePoint != null) {
npc.LookTowards(target.transform.position);
Vector3 origin = baseProjectile.MuzzlePoint.transform.position - baseProjectile.MuzzlePoint.forward * 0.25f;
Vector3 vector3 = baseProjectile.MuzzlePoint.transform.forward;
vector32 = AimConeUtil.GetModifiedAimConeDirection(0.5f, vector3, true);
Vector3 targetPos = origin + vector32 * 300f;
baseProjectile.MuzzlePoint.transform.position = baseProjectile.MuzzlePoint.forward * 0.25f;
if (!miss) ApplyDamage(target, target.transform.position, vector32, npc.player);
baseProjectile.ServerUse(1f, baseProjectile.MuzzlePoint);
}
Vector3 dest;
if (miss) {
dmg = 0;
dest = hit;
}
else {
dest = target.transform.position;
}
}
private void ApplyDamage(BaseCombatEntity entity, Vector3 point, Vector3 normal, BaseCombatEntity target) {
float damageAmount = 15f * UnityEngine.Random.Range(0.9f, 1.1f);
if (entity is BasePlayer && (UnityEngine.Object) entity != (UnityEngine.Object) target) damageAmount *= 0.5f;
HitInfo info = new HitInfo((BaseEntity) target, (BaseEntity) entity, DamageType.Bullet, damageAmount, point);
entity.OnAttacked(info);
if (!(entity is BasePlayer) && !(entity is BaseNpc)) return;
Effect.server.ImpactEffect(new HitInfo() {
HitPositionWorld = point, HitNormalWorld = -normal, HitMaterial = StringPool.Get("Flesh")
}
);
}
public void AttemptAttack(BaseCombatEntity entity) {
var weapon = firstWeapon as BaseProjectile;
if (weapon != null) {
if (!reloading && weapon.primaryMagazine.contents <= 0) {
reloading = true;
npc.player.SignalBroadcast(BaseEntity.Signal.Reload, string.Empty);
startedReload = Time.realtimeSinceStartup;
return;
}
if (reloading && Time.realtimeSinceStartup > startedReload + (npc.info.reloadDuration > 0 ? npc.info.reloadDuration : weapon.reloadTime)) {
reloading = false;
if (npc.info.needsAmmo) {
weapon.primaryMagazine.Reload(npc.player);
npc.player.inventory.ServerUpdate(0f);
}
else {
weapon.primaryMagazine.contents = weapon.primaryMagazine.capacity;
}
}
if (reloading) return;
}
if (!(Time.realtimeSinceStartup > lastHit + npc.info.damageInterval)) return;
lastHit = Time.realtimeSinceStartup;
DoAttack(entity, !HitChance());
}
public void DoAttack(BaseCombatEntity target, bool miss = false) {
if (npc == null) return;
var weapon = firstWeapon as BaseProjectile;
if (firstWeapon == null || (firstWeapon != null && (firstWeapon.IsDestroyed || weapon != null && weapon.primaryMagazine.contents == 0))) {
firstWeapon = npc.EquipFirstWeapon();
weapon = firstWeapon as BaseProjectile;
npc.SetActive(0);
}
var attackitem = firstWeapon?.GetItem();
if (attackitem == null) {
npc.EndAttackingEntity();
return;
}
if (attackitem.uid != npc.player.svActiveItemID) npc.SetActive(attackitem.uid);
float dmg = npc.info.damageAmount * UnityEngine.Random.Range(0.8f, 1.2f);
if (target is BaseNpc) dmg *= 1.5f;
else if (target is AutoTurret) dmg *= 3f;
if (weapon != null) {
//npc.ForceSignalGesture();
CreateProjectileEffect(target, weapon, dmg, miss);
}
else {
var hitInfo = new HitInfo(npc.player, target, DamageType.Stab, dmg, target.transform.position) {
PointStart = npc.player.transform.position, PointEnd = target.transform.position
}
;
target.SendMessage("OnAttacked", hitInfo, SendMessageOptions.DontRequireReceiver);
npc.ForceSignalAttack();
}
}
}
//////////////////////////////////////////////////////
/// class HumanPlayer : MonoBehaviour
/// MonoBehaviour: managed by UnityEngine
/// Takes care of all the sub categories of the HumanNPCs
//////////////////////////////////////////////////////
public class HumanPlayer : MonoBehaviour {
public HumanNPCInfo info;
public HumanLocomotion locomotion;
public HumanTrigger trigger;
public ProtectionProperties protection;
private HeldEntity heldEntity;
private readonly List<NpcSound> _queuedSounds = new List<NpcSound>();
private Coroutine QueuedRoutine {
get;
set;
}
//public InstrumentKeyController instrument;
public BasePlayer player;
public float lastMessage;
private void Awake() {
player = GetComponent<BasePlayer>();
protection = ScriptableObject.CreateInstance<ProtectionProperties>();
}
public void SetInfo(HumanNPCInfo info, bool update = false) {
this.info = info;
if (info == null) return;
player.displayName = info.displayName;
SetViewAngle(info.spawnInfo.rotation);
player.syncPosition = true;
if (info.permission != null) {
if (info.permission != "" && info.permission.Contains("humannpc")) ins.permission.RegisterPermission(info.permission, ins);
}
if (!update) {
//player.xp = ServerMgr.Xp.GetAgent(info.userid);
player.stats = new PlayerStatistics(player);
player.userID = info.userid;
player.UserIDString = player.userID.ToString();
player.MovePosition(info.spawnInfo.position);
player.eyes = player.eyes ?? player.GetComponent<PlayerEyes>();
//player.eyes.position = info.spawnInfo.position + new Vector3(0, 1.6f, 0);
var newEyes = info.spawnInfo.position + new Vector3(0, 1.6f, 0);
player.eyes.position.Set(newEyes.x, newEyes.y, newEyes.z);
player.EndSleeping();
if (info.playTune) PlayTune();
protection.Clear();
foreach (var pro in info.protections) protection.Add(pro.Key, pro.Value);
}
if (locomotion != null) Destroy(locomotion);
locomotion = player.gameObject.AddComponent<HumanLocomotion>();
if (trigger != null) Destroy(trigger);
trigger = player.gameObject.AddComponent<HumanTrigger>();
lastMessage = Time.realtimeSinceStartup;
DisableMove();
AllowMove();
}
public void QueueSound(NpcSound data) {
_queuedSounds.Add(data);
if (QueuedRoutine == null) {
QueuedRoutine = InvokeHandler.Instance.StartCoroutine(RunTalker());
}
}
private IEnumerator RunTalker() {
while (_queuedSounds.Count != 0) {
NpcSound soundData = _queuedSounds[0];
_queuedSounds.RemoveAt(0);
foreach (byte[] data in soundData.Data) {
if (player == null) break;
SendSound(player.net.ID, data);
yield return new WaitForSeconds(0.07f);
}
yield return new WaitForSeconds(2f);
}
QueuedRoutine = null;
ins.NpcTalking.Remove(player.userID);
}
private void SendSound(uint netId, byte[] data) {
if (!Net.sv.write.Start()) {
return;
}
foreach (BasePlayer current in BasePlayer.activePlayerList) {
if (player == null) return;
if (current == null) continue;
float distance = Vector3.Distance(player.transform.position, current.transform.position);
if (distance > 100) continue;
if (current.IsConnected) {
Net.sv.write.PacketID(Message.Type.VoiceData);
Net.sv.write.UInt32(netId)
// ... [Code preview truncated due to length. Full source in attached file] ...