C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// Requires: PathFinding
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Game.Rust;
using Rust;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UnityEngine;
using Convert = System.Convert;
namespace Oxide.Plugins {
[Info("HumanNPC", "Reneb/Nogrod/Calytic", "0.3.18", ResourceId = 856)]
[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 Hash<ulong, HumanNPCInfo> humannpcs = new Hash<ulong, HumanNPCInfo>();
private bool save;
private StoredData storedData;
private DynamicConfigFile data;
private Vector3 eyesPosition;
private string chat = "<color=#FA58AC>{0}:</color> ";
[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;
}
}
//////////////////////////////////////////////////////
/// 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() {
//Interface.Oxide.LogInfo("Destroy child: {0}", child?.name);
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);
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 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();
foreach (var cwaypoint in (List<object>)cwaypoints) {
foreach (var pair in (Dictionary<Vector3, float>)cwaypoint) {
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 {
Interface.Oxide.LogInfo("Blocked waypoint? {0} for {1}", pair.Key, npc.player.displayName);
//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 {
Interface.Oxide.LogInfo("Blocked waypoint to spawn? {0} for {1}", lastPos, npc.player.displayName);
}
}
if (cachedWaypoints.Count <= 0) cachedWaypoints = null;
//Interface.Oxide.LogInfo("Waypoints: {0} for {1}", cachedWaypoints.Count, npc.player.displayName);
}
}
private void FixedUpdate() {
TryToMove();
}
public void TryToMove() {
if (npc.player.IsDead() || npc.player.IsWounded()) return;
if (targetPosition != Vector3.zero) ProcessFollow(targetPosition);
else if (attackEntity is BaseCombatEntity) ProcessAttack(attackEntity);
else if (followEntity is BaseEntity) 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.UpdatePlayerCollider(true);
npc.player.modelState.onground = !IsSwimming();
}
public bool IsSwimming() {
return WaterLevel.Test(npc.player.transform.position + new Vector3(0, 0.65f, 0));
}
private float GetSpeed(float speed = -1) {
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>();
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 (npc.player.transform.position == wp.Position) {
npc.DisableMove();
npc.Invoke("AllowMove", GetSpeed(wp.Speed));
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) {
//Interface.Oxide.LogInfo("ProcessAttack: {0} - {1}", npc.player.displayName, entity.name);
if (entity != null && entity.IsAlive()) {
var c_attackDistance = Vector3.Distance(entity.transform.position, npc.player.transform.position);
shouldMove = false;
bool validAttack = Vector3.Distance(LastPos, npc.player.transform.position) < npc.info.maxDistance && noPath < 5;
//Interface.Oxide.LogInfo("Entity: {0} {1} {2}", entity.GetType().FullName, entity.IsAlive(), validAttack);
if (validAttack) {
var range = c_attackDistance < npc.info.damageDistance;
var see = CanSee(npc, entity);
//Interface.Oxide.LogInfo("Entity: {0} {1} {2}", entity.GetType().FullName, range, see);
if (range && see) {
AttemptAttack(entity);
return;
}
if (GetSpeed() <= 0) npc.EndAttackingEntity();
else Move(npc.player.transform.position);
}
else npc.EndAttackingEntity();
}
else npc.EndAttackingEntity();
}
public void ProcessFollow(Vector3 target) {
var c_followDistance = Vector3.Distance(target, npc.player.transform.position);
shouldMove = false;
if (c_followDistance > followDistance && Vector3.Distance(LastPos, npc.player.transform.position) < npc.info.maxDistance && noPath < 5) {
Move(npc.player.transform.position);
}
else {
if (followEntity is BaseEntity) {
npc.EndFollowingEntity(noPath < 5);
}
else if (targetPosition != Vector3.zero) {
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) {
//Interface.Oxide.LogInfo("Attack failed(empty): {0} - {1}", npc.player.displayName, attackEntity.name);
return;
}
var component = baseProjectile.primaryMagazine.ammoType.GetComponent<ItemModProjectile>();
if (component == null) {
//Interface.Oxide.LogInfo("Attack failed(Component): {0} - {1}", npc.player.displayName, attackEntity.name);
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)) {
//Interface.Oxide.LogInfo("Attack failed: {0} - {1}", npc.player.displayName, attackEntity.name);
return;
}
else {
hit = raycastHit.point;
target = raycastHit.GetCollider().GetComponent<BaseCombatEntity>();
//Interface.Oxide.LogInfo("Attack failed: {0} - {1}", raycastHit.GetCollider().name, (Rust.Layer)raycastHit.GetCollider().gameObject.layer);
miss = miss || target == null;
}
baseProjectile.primaryMagazine.contents--;
npc.ForceSignalAttack();
if (miss) {
var aimCone = baseProjectile.GetAimCone();
vector32 += Quaternion.Euler(UnityEngine.Random.Range((float)(-aimCone * 0.5), aimCone * 0.5f), UnityEngine.Random.Range((float)(-aimCone * 0.5), aimCone * 0.5f), UnityEngine.Random.Range((float)(-aimCone * 0.5), aimCone * 0.5f)) * npc.player.eyes.HeadForward();
}
Effect.server.Run(baseProjectile.attackFX.resourcePath, baseProjectile, StringPool.Get(baseProjectile.handBone), Vector3.zero, Vector3.forward);
var effect = new Effect();
effect.Init(Effect.Type.Projectile, source, vector32.normalized);
effect.scale = vector32.magnitude;
effect.pooledString = component.projectileObject.resourcePath;
effect.number = UnityEngine.Random.Range(0, 2147483647);
EffectNetwork.Send(effect);
Vector3 dest;
if (miss) {
dmg = 0;
dest = hit;
}
else {
dest = target.transform.position;
}
var hitInfo = new HitInfo(npc.player, target, DamageType.Bullet, dmg, dest) {
DidHit = !miss, HitEntity = target, PointStart = source, PointEnd = hit, HitPositionWorld = dest, HitNormalWorld = -dir, WeaponPrefab = GameManager.server.FindPrefab(StringPool.Get(baseProjectile.prefabID)).GetComponent<AttackEntity>(), Weapon = (AttackEntity)firstWeapon, HitMaterial = StringPool.Get("Flesh")
}
;
target?.OnAttacked(hitInfo);
Effect.server.ImpactEffect(hitInfo);
}
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;
public BasePlayer player;
public float lastMessage;
public List<TuneNote> tunetoplay = new List<TuneNote>();
public int currentnote = 0;
private Effect effectP = new Effect("assets/prefabs/instruments/guitar/effects/guitarpluck.prefab", new Vector3(0, 0, 0), Vector3.forward);
private Effect effectS = new Effect("assets/prefabs/instruments/guitar/effects/guitarpluck.prefab", new Vector3(0, 0, 0), Vector3.forward);
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 (!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.minstrel != null) 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 UpdateHealth(HumanNPCInfo info) {
player.InitializeHealth(info.health, info.health);
player.health = info.health;
}
public void AllowMove() {
locomotion?.Enable();
}
public void DisableMove() {
locomotion?.Disable();
}
public void TemporaryDisableMove(float thetime = -1f) {
if (thetime == -1f) thetime = info.stopandtalkSeconds;
DisableMove();
if (gameObject == null) return;
if (IsInvoking("AllowMove")) CancelInvoke("AllowMove");
Invoke("AllowMove", thetime);
}
public void EndAttackingEntity(bool trigger = true) {
if (locomotion.gameObject != null && locomotion.IsInvoking("PathFinding")) locomotion.CancelInvoke("PathFinding");
locomotion.noPath = 0;
locomotion.shouldMove = true;
if (trigger) {
Interface.Oxide.CallHook("OnNPCStopTarget", player, locomotion.attackEntity);
}
locomotion.attackEntity = null;
player.health = info.health;
locomotion.GetBackToLastPos();
SetActive(0);
}
public void EndFollowingEntity(bool trigger = true) {
if (locomotion.IsInvoking("PathFinding")) locomotion.CancelInvoke("PathFinding");
locomotion.noPath = 0;
locomotion.shouldMove = true;
if (trigger) {
Interface.Oxide.CallHook("OnNPCStopTarget", player, locomotion.followEntity);
}
locomotion.followEntity = null;
}
public void EndGo(bool trigger = true) {
if (locomotion.IsInvoking("PathFinding")) locomotion.CancelInvoke("PathFinding");
locomotion.noPath = 0;
locomotion.shouldMove = true;
if (trigger) {
Interface.Oxide.CallHook("OnNPCStopGo", player, locomotion.targetPosition);
}
if (locomotion.returning) {
locomotion.returning = false;
locomotion.SetMovementPoint(player.transform.position, locomotion.LastPos, 7f);
locomotion.secondsTaken = 0.01f;
}
locomotion.targetPosition = Vector3.zero;
}
public void PlayTune() {
if (info.minstrel == null || gameObject == null) return;
if (tunetoplay.Count == 0) GetTune(this);
if (tunetoplay.Count == 0) return;
Invoke("PlayNote", 1);
}
public void PlayNote() {
if (tunetoplay[currentnote].Pluck) {
effectP.worldPos = player.transform.position;
effectP.origin = player.transform.position;
effectP.scale = tunetoplay[currentnote].NoteScale;
EffectNetwork.Send(effectP);
}
else {
effectS.worldPos = player.transform.position;
effectS.origin = player.transform.position;
effectS.scale = tunetoplay[currentnote].NoteScale;
EffectNetwork.Send(effectS);
}
currentnote++;
if (currentnote >= tunetoplay.Count) currentnote = 0;
Invoke("PlayNote", tunetoplay[currentnote].Delay);
}
public void StartAttackingEntity(BaseCombatEntity entity) {
if (locomotion.attackEntity != null && UnityEngine.Random.Range(0f, 1f) < 0.75f) return;
if (Interface.Oxide.CallHook("OnNPCStartTarget", player, entity) == null) {
//var item = GetFirstWeaponItem();
//if (item != null)
// SetActive(item.uid);
locomotion.attackEntity = entity;
locomotion.pathFinding = null;
if (locomotion.LastPos == Vector3.zero) locomotion.LastPos = player.transform.position;
if (gameObject != null && IsInvoking("AllowMove")) {
CancelInvoke("AllowMove");
AllowMove();
}
locomotion.Invoke("PathFinding", 0);
}
}
public void StartFollowingEntity(BaseEntity entity) {
if (locomotion.targetPosition != Vector3.zero) {
EndGo(false);
}
player.SendNetworkUpdate();
locomotion.followEntity = entity;
locomotion.pathFinding = null;
if (locomotion.LastPos == Vector3.zero) locomotion.LastPos = player.transform.position;
if (IsInvoking("AllowMove")) {
CancelInvoke("AllowMove");
AllowMove();
}
locomotion.Invoke("PathFinding", 0);
}
public void StartGo(Vector3 position) {
if (locomotion.followEntity != null) {
EndFollowingEntity(false);
}
player.SendNetworkUpdate();
locomotion.targetPosition = position;
locomotion.pathFinding = null;
if (locomotion.LastPos == Vector3.zero) locomotion.LastPos = player.transform.position;
if (IsInvoking("AllowMove")) {
CancelInvoke("AllowMove");
AllowMove();
}
locomotion.Invoke("PathFinding", 0);
}
public HeldEntity GetCurrentWeapon() {
foreach (Item item in player.inventory.containerBelt.itemList) {
BaseEntity heldEntity = item.GetHeldEntity();
if (heldEntity is HeldEntity && !heldEntity.HasFlag(BaseEntity.Flags.Disabled)) return (HeldEntity)heldEntity;
}
return null;
}
public Item GetFirstWeaponItem() {
return GetFirstWeapon()?.GetItem();
}
public HeldEntity GetFirstWeapon() {
foreach (Item item in player.inventory.containerBelt.itemList) {
if (item.CanBeHeld() && HasAmmo(item) && (item.info.category == ItemCategory.Weapon)) return item.GetHeldEntity() as HeldEntity;
}
return null;
}
public HeldEntity GetFirstTool() {
foreach (Item item in player.inventory.containerBelt.itemList) {
if (item.CanBeHeld() && item.info.category == ItemCategory.Tool) return item.GetHeldEntity() as HeldEntity;
}
return null;
}
public HeldEntity GetFirstMisc() {
foreach (Item item in player.inventory.containerBelt.itemList) {
if (item.CanBeHeld() && item.info.category != ItemCategory.Tool && item.info.category != ItemCategory.Weapon) return item.GetHeldEntity() as HeldEntity;
}
return null;
}
public List<Item> GetAmmo(Item item) {
var ammos = new List<Item>();
AmmoTypes ammoType;
if (!ammoTypes.TryGetValue(item.info.shortname, out ammoType)) return ammos;
player.inventory.FindAmmo(ammos, ammoType);
return ammos;
}
public bool HasAmmo(Item item) {
if (!info.needsAmmo) return true;
var weapon = item.GetHeldEntity() as BaseProjectile;
if (weapon == null) return true;
return weapon.primaryMagazine.contents > 0 || weapon.primaryMagazine.CanReload(player);
}
public void UnequipAll() {
if (player.inventory?.containerBelt == null) return;
foreach (Item item in player.inventory.containerBelt.itemList) {
if (item.CanBeHeld()) (item.GetHeldEntity() as HeldEntity)?.SetHeld(false);
}
}
public HeldEntity EquipFirstWeapon() {
HeldEntity weapon = GetFirstWeapon();
if (weapon != null) {
UnequipAll();
weapon.SetHeld(true);
}
return weapon;
}
public HeldEntity EquipFirstTool() {
HeldEntity tool = GetFirstTool();
if (tool != null) {
UnequipAll();
tool.SetHeld(true);
}
return tool;
}
public HeldEntity EquipFirstMisc() {
HeldEntity misc = GetFirstMisc();
if (misc != null) {
UnequipAll();
misc.SetHeld(true);
}
return misc;
}
public void SetActive(uint id) {
player.svActiveItemID = id;
player.SendNetworkUpdate();
player.SignalBroadcast(BaseEntity.Signal.Reload, string.Empty);
}
private void OnDestroy() {
Destroy(locomotion);
Destroy(trigger);
Destroy(protection);
}
public void LookTowards(Vector3 pos) {
if (pos != player.transform.position) SetViewAngle(Quaternion.LookRotation(pos - player.transform.position));
}
public void ForceSignalGesture() {
player.SignalBroadcast(BaseEntity.Signal.Gesture, "pickup_item");
}
public void ForceSignalAttack() {
player.SignalBroadcast(BaseEntity.Signal.Attack, string.Empty);
}
public void SetViewAngle(Quaternion viewAngles) {
if (viewAngles.eulerAngles == default(Vector3)) return;
player.viewAngles = viewAngles.eulerAngles;
player.SendNetworkUpdate();
}
}
//////////////////////////////////////////////////////
/// class HumanNPCInfo
/// NPC information that will be saved inside the datafile
/// public => will be saved in the data file
/// non public => won't be saved in the data file
//////////////////////////////////////////////////////
public class HumanNPCInfo {
public ulong userid;
public string displayName;
public bool invulnerability;
public float health;
public bool respawn;
public float respawnSeconds;
public SpawnInfo spawnInfo;
public string waypoint;
public float collisionRadius;
public string spawnkit;
public float damageAmount;
public float damageDistance;
public float damageInterval;
public float attackDistance;
public float maxDistance;
public string minstrel;
public bool hostile;
public float speed;
public bool stopandtalk;
public float stopandtalkSeconds;
public bool enable;
public bool lootable;
public float hitchance;
public float reloadDuration;
public bool needsAmmo;
public bool defend;
public List<string> message_hello;
public List<string> message_bye;
public List<string> message_use;
public List<string> message_hurt;
public List<string> message_kill;
public Dictionary<DamageType, float> protections = new Dictionary<DamageType, float>();
public HumanNPCInfo(ulong userid, Vector3 position, Quaternion rotation) {
this.userid = userid;
displayName = "NPC";
invulnerability = true;
health = 50;
hostile = false;
needsAmmo = true;
respawn = true;
respawnSeconds = 60;
spawnInfo = new SpawnInfo(position, rotation);
collisionRadius = 10;
damageDistance = 3;
damageAmount = 10;
attackDistance = 100;
maxDistance = 200;
hitchance = 0.75f;
speed = 3;
stopandtalk = true;
stopandtalkSeconds = 3;
enable = true;
lootable = true;
damageInterval = 2;
for (var i = 0; i < (int)DamageType.LAST;
i++) protections[(DamageType)i] = 0f;
}
public HumanNPCInfo Clone(ulong userid) {
return new HumanNPCInfo(userid, spawnInfo.position, spawnInfo.rotation) {
displayName = displayName, invulnerability = invulnerability, health = health, respawn = respawn, respawnSeconds = respawnSeconds, waypoint = waypoint, collisionRadius = collisionRadius, spawnkit = spawnkit, damageAmount = damageAmount, damageDistance = damageDistance, attackDistance = attackDistance, maxDistance = maxDistance, hostile = hostile, speed = speed, stopandtalk = stopandtalk, stopandtalkSeconds = stopandtalkSeconds, lootable = lootable, defend = defend, damageInterval = damageInterval, minstrel = minstrel, message_hello = message_hello?.ToList(), message_bye = message_bye?.ToList(), message_use = message_use?.ToList(), message_hurt = message_hurt?.ToList(), message_kill = message_kill?.ToList(), needsAmmo = needsAmmo, hitchance = hitchance, reloadDuration = reloadDuration, protections = protections?.ToDictionary(p => p.Key, p => p.Value)
}
;
}
}
private class NPCEditor : MonoBehaviour {
public BasePlayer player;
public HumanPlayer targetNPC;
private void Awake() {
player = GetComponent<BasePlayer>();
}
}
public static Dictionary<string, AmmoTypes> ammoTypes = new Dictionary<string, AmmoTypes>();
//{
// {"bow.hunting", AmmoTypes.BOW_ARROW},
// {"crossbow", AmmoTypes.BOW_ARROW},
// {"pistol.eoka", AmmoTypes.HANDMADE_SHELL},
// {"pistol.semiauto", AmmoTypes.PISTOL_9MM},
// {"pistol.revolver", AmmoTypes.PISTOL_9MM},
// {"rifle.ak", AmmoTypes.RIFLE_556MM},
// {"rifle.bolt", AmmoTypes.RIFLE_556MM},
// {"shotgun.pump", AmmoTypes.SHOTGUN_12GUAGE},
// {"shotgun.waterpipe", AmmoTypes.HANDMADE_SHELL},
// {"smg.2", AmmoTypes.PISTOL_9MM},
// {"smg.thompson", AmmoTypes.PISTOL_9MM}
//};
private static Dictionary<string, BaseProjectile> weaponProjectile = new Dictionary<string, BaseProjectile>();
protected override void LoadDefaultConfig() {
}
private void CheckCfg<T>(string Key, ref T var) {
if (Config[Key] is T) var = (T)Config[Key];
else Config[Key] = var;
}
private void Init() {
ammoTypes = new Dictionary<string, AmmoTypes>();
weaponProjectile = new Dictionary<string, BaseProjectile>();
CheckCfg("Chat", ref chat);
SaveConfig();
}
private static bool GetBoolValue(string value) {
if (value == null) return false;
value = value.Trim().ToLower();
switch (value) {
case "t" : case "true" : case "1" : case "yes" : case "y" : case "on" : return true;
default : return false;
}
}
private void Loaded() {
LoadData();
var filter = RustExtension.Filter.ToList();
filter.Add("Look rotation viewing vector is zero");
RustExtension.Filter = filter.ToArray();
}
private void Unload() {
foreach (BasePlayer player in Resources.FindObjectsOfTypeAll<BasePlayer>()) {
if (player.userID >= 76560000000000000L || player.userID <= 0L || player.IsDestroyed) continue;
player.KillMessage();
}
var npcEditors = UnityEngine.Object.FindObjectsOfType<NPCEditor>();
foreach (var gameObj in npcEditors) UnityEngine.Object.Destroy(gameObj);
SaveData();
}
private void SaveData() {
if (storedData == null || !save) return;
data.WriteObject(storedData);
save = false;
}
private void LoadData() {
data = Interface.Oxide.DataFileSystem.GetFile(nameof(HumanNPC));
data.Settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
data.Settings.Converters = new JsonConverter[] {
new SpawnInfoConverter(), new UnityQuaternionConverter(), new UnityVector3Converter()
}
;
try {
storedData = data.ReadObject<StoredData>();
}
catch {
storedData = new StoredData();
}
data.Clear();
foreach (var thenpc in storedData.HumanNPCs) humannpcs[thenpc.userid] = thenpc;
}
public class TuneNote {
public float NoteScale, Delay;
public bool Pluck;
public TuneNote() {
}
}
private static void GetTune(HumanPlayer hp) {
var tune = Interface.Oxide.CallHook("getTune", hp.info.minstrel);
if (tune
// ... [Code preview truncated due to length. Full source in attached file] ...