C#:
#region License (GPL v2)
/*
Humanoids - NPC Players that can walk, fight, navigate, etc.
Copyright (c) 2023 RFC1920 <desolationoutpostpve@gmail.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License v2.0.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Optionally you can also view the license at <http://www.gnu.org/licenses/>.
*/
#endregion License (GPL v2)
// Requires: PathFinding
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Oxide.Plugins {
[Info("Humanoids", "RFC1920", "1.3.8")]
[Description("Adds interactive NPCs which can be modded by other plugins")]
internal class Humanoids : RustPlugin {
#region vars
[PluginReference]
private readonly Plugin Kits, RoadFinder, GUIShop, NPCShop, ChatBot;
private static readonly PathFinding PathFinding;
private DynamicConfigFile data;
private ConfigData configData;
public static Dictionary<string, AmmoTypes> ammoTypes = new();
private List<ulong> isopen = new();
private const string permNPCGuiUse = "humanoid.use";
private const string NPCGUI = "npc.editor";
private const string NPCGUK = "npc.kitselect";
private const string NPCGUL = "npc.locoselect";
private const string NPCGUM = "npc.monselect";
private const string NPCGUN = "npc.kitsetnum";
private const string NPCGUR = "npc.roadselect";
private const string NPCGUS = "npc.select";
private const string NPCGUV = "npc.setval";
private readonly List<string> guis = new() {
NPCGUI, NPCGUK, NPCGUL, NPCGUM, NPCGUN, NPCGUR, NPCGUS, NPCGUV
}
;
private bool newsave;
public static Humanoids Instance;
private static Dictionary<ulong, HumanoidInfo> npcs = new();
// This is critical to the speed of operations on FindHumanoidByID/Name
private Dictionary<ulong, HumanoidPlayer> hpcacheid = new();
private Dictionary<string, HumanoidPlayer> hpcachenm = new();
private static Dictionary<string, Road> roads = new();
private static SortedDictionary<string, Vector3> monPos = new();
private static SortedDictionary<string, Vector3> monSize = new();
private static SortedDictionary<string, Vector3> cavePos = new();
private static Vector3 Vector3Down;
private readonly static int playerMask = LayerMask.GetMask("Player (Server)");
private readonly static int groundLayer = LayerMask.GetMask("Construction", "Terrain", "World");
private readonly static int obstructionMask = LayerMask.GetMask(new[] {
"Construction", "Deployed", "Clutter"
}
);
//private readonly static int gatherMask = LayerMask.GetMask(new[] { "Construction", "Deployed", "World" });
private readonly static int constructionMask = LayerMask.GetMask(new[] {
"Construction", "Deployed"
}
);
//private readonly static int terrainMask = LayerMask.GetMask(new[] { "Terrain", "Tree" });
private static int targetLayer;
#endregion
#region Message
private string Lang(string key, string id = null, params object[] args) => string.Format(lang.GetMessage(key, this, id), args);
private void Message(IPlayer player, string key, params object[] args) => player.Reply(Lang(key, player.Id, args));
private void DoLog(string message) {
if (configData.Options.debug) Interface.GetMod().LogInfo(message);
}
#endregion
#region global
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
["npcgui"]
= "Humanoid GUI", ["npcguisel"] = "HumanoidGUI NPC Select ", ["npcguikit"] = "HumanoidGUI Kit Select", ["npcguimon"] = "HumanoidGUI Monument Select", ["npcguiroad"] = "HumanoidGUI Road Select", ["npcguiloco"] = "HumanoidGUI LocoMode Select", ["close"] = "Close", ["none"] = "None", ["noid"] = "noid", ["start"] = "Start", ["end"] = "End", ["debug"] = "Debug set to {0}", ["humanoids"] = "Humanoids", ["needselect"] = "Select NPC", ["select"] = "Select", ["editing"] = "Editing", ["mustselect"] = "Please press 'Select' to choose an NPC.", ["guihelp1"] = "For blue buttons, click to toggle true/false.", ["guihelp2"] = "For all values above in gray, you may type a new value and press enter.", ["guihelp3"] = "For kit, press the button to select a kit.", ["add"] = "Add", ["new"] = "Create New", ["remove"] = "Remove", ["spawnhere"] = "Spawn Here", ["spawnedat"] = "New NPC Spawned at {0} ({1})", ["tpto"] = "Teleport to NPC", ["name"] = "Name", ["online"] = "Online", ["offline"] = "Offline", ["deauthall"] = "DeAuthAll", ["remove"] = "Remove"
}
, this);
}
private void OnServerInitialized() {
LoadConfigVariables();
AddCovalenceCommand("noid", "cmdNPC");
targetLayer = LayerMask.GetMask("Player (Server)", "AI", "Deployed", "Construction");
Instance = this;
LoadData();
foreach (HumanoidPlayer obj in Resources.FindObjectsOfTypeAll<HumanoidPlayer>()) {
if (configData.Options.zeroOnWipe && newsave) {
obj.info.loc = Vector3.zero;
}
UnityEngine.Object.Destroy(obj);
}
newsave = false;
foreach (HumanoidMovement obj in Resources.FindObjectsOfTypeAll<HumanoidMovement>()) {
UnityEngine.Object.Destroy(obj);
}
Dictionary<ulong, HumanoidInfo> tmpnpcs = new(npcs);
foreach (KeyValuePair<ulong, HumanoidInfo> npc in tmpnpcs) {
if (npc.Value.userid == 0) continue;
DoLog($"Spawning npc {npc.Value.userid}");
SpawnNPC(npc.Value, out ulong nid);
}
tmpnpcs.Clear();
SaveData();
FindMonuments();
if (RoadFinder) {
object x = RoadFinder.CallHook("GetRoads");
string json = JsonConvert.SerializeObject(x);
roads = JsonConvert.DeserializeObject<Dictionary<string, Road>>(json);
}
foreach (BasePlayer player in BasePlayer.activePlayerList) {
foreach (string gui in guis) {
CuiHelper.DestroyUi(player, gui);
}
if (isopen.Contains(player.userID)) isopen.Remove(player.userID);
}
}
// 07 Jan 2023: Currently only called by Teleportication 1.3.8 and above
private void OnTownSet(Vector3 location) {
configData.Options.townLocation = location;
SaveConfig(configData);
if (GUIShop || NPCShop) {
DoLog("OnTownSet called!");
BuildingPrivlidge bp = null;
List<BaseEntity> entities = new();
List<BaseEntity> frames = new();
List<ulong> processedNPCs = new();
Vis.Entities(location, 100f, entities);
foreach (BaseEntity entity in entities.OrderBy(x => Vector3.Distance(location, x.transform.position))) {
if (entity.ShortPrefabName == configData.Options.townPrefab) {
DoLog($"Found a {configData.Options.townPrefab} at {entity.transform.position}");
frames.Add(entity);
}
if (entity is BuildingPrivlidge && bp == null) {
// Only get the first TC we find, which should be closest to the town set point
DoLog($"Found a TC at {entity.transform.position}");
bp = entity as BuildingPrivlidge;
}
}
DoLog($"Found {frames.Count} of {configData.Options.townPrefab}");
foreach (KeyValuePair<ulong, HumanoidInfo> npc in new Dictionary<ulong, HumanoidInfo>(npcs)) {
if (npc.Value.shopnpc && !processedNPCs.Contains(npc.Key)) {
foreach (BaseEntity frame in new List<BaseEntity>(frames)) {
if (bp != null && frame.GetBuildingPrivilege() != bp) {
continue;
}
// Place/move NPC here
SetHumanoidInfo(npc.Key, "spawn", GetPrefabPos(frame));
SetHumanoidInfo(npc.Key, "rot", frame.transform.rotation.ToString());
SetHumanoidInfo(npc.Key, "townrel", (location - frame.transform.position).ToString());
DoLog("Moved NPC to town");
frames.Remove(frame);
break;
}
RespawnNPC(npc.Key);
processedNPCs.Add(npc.Key);
}
}
}
}
private string GetPrefabPos(BaseEntity frame) {
Vector3 rear = -frame.transform.right;
// Yes, right is in front...
Vector3 newPos = frame.transform.position + new Vector3(rear.x, rear.y, rear.z + 0.5f);
DoLog($"{configData.Options.townPrefab} located at {frame.transform.position}. NPC will be at {newPos}");
return newPos.ToString();
}
private void OnNewSave() {
newsave = true;
}
private void Unload() {
foreach (BasePlayer player in BasePlayer.activePlayerList.Where(x => x.IsAdmin)) {
foreach (string gui in guis) {
CuiHelper.DestroyUi(player, gui);
}
if (isopen.Contains(player.userID)) isopen.Remove(player.userID);
}
foreach (HumanoidPlayer obj in Resources.FindObjectsOfTypeAll<HumanoidPlayer>()) {
DoLog($"Killing player object {obj.player.userID}");
obj.movement.Stand();
obj.GetComponent<BasePlayer>().Kill();
}
}
private void LoadData() {
DoLog("LoadData called");
data = Interface.GetMod().DataFileSystem.GetFile(Name + "/humanoids");
data.Settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
data.Settings.Converters = new JsonConverter[] {
new UnityQuaternionConverter(), new UnityVector3Converter()
}
;
npcs = data.ReadObject<Dictionary<ulong, HumanoidInfo>>();
data.Clear();
foreach (KeyValuePair<ulong, HumanoidInfo> pls in npcs) {
DoLog($"{pls.Value.userid}");
}
}
private void SaveData() {
Dictionary<ulong, HumanoidInfo> tmpnpcs = new();
foreach (KeyValuePair<ulong, HumanoidInfo> x in npcs) {
if (!x.Value.ephemeral) {
tmpnpcs.Add(x.Key, x.Value);
}
}
Interface.GetMod().DataFileSystem.WriteObject(Name + "/humanoids", tmpnpcs);
tmpnpcs.Clear();
}
#endregion
#region Oxide Hooks
private void OnPlayerInput(BasePlayer player, InputState input) {
if (player == null || input == null) return;
if (!player.userID.IsSteamId()) return;
//if (input.current.buttons > 0)
// Puts($"OnPlayerInput: {input.current.buttons}");
if (!input.WasJustPressed(BUTTON.USE)) return;
if (Physics.Raycast(player.eyes.HeadRay(), out RaycastHit hit, 3f, playerMask)) {
BasePlayer pl = hit.GetEntity().ToPlayer();
HumanoidPlayer hp = pl.GetComponent<HumanoidPlayer>();
if (hp == null) return;
if (hp.movement.sitting) {
DoLog($"Trying to stand {hp.info.displayName}");
hp.movement.Stand();
}
hp.LookToward(player.transform.position, true);
if (hp.info.entrypause) {
DoLog($"Trying to pause {hp.info.displayName}");
hp.movement.Stop(true);
}
hp.movement.Jump();
Interface.GetMod().CallHook("OnUseNPC", hp.player, player);
SaveData();
}
}
private object CanHelicopterTarget(PatrolHelicopterAI heli, BasePlayer player) {
if (player == null) return null;
if (player.userID.IsSteamId()) return null;
if (IsHumanoid(player)) return true;
return null;
}
private object CanHelicopterStrafeTarget(PatrolHelicopterAI heli, BasePlayer player) {
if (player == null) return null;
if (player.userID.IsSteamId()) return null;
if (IsHumanoid(player)) return true;
return null;
}
private object OnHelicopterTarget(HelicopterTurret turret, BaseCombatEntity entity) {
BasePlayer player = entity.ToPlayer();
if (player == null) return null;
if (player.userID.IsSteamId()) return null;
if (IsHumanoid(player)) return true;
return null;
}
private void OnEntityDeath(BaseCombatEntity entity, HitInfo hitinfo) {
if (entity == null) return;
HumanoidPlayer hp = entity.GetComponent<HumanoidPlayer>();
if (hp == null) return;
if (!hp.info.lootable) {
hp.player.inventory?.Strip();
}
else if (!hp.info.dropWeapon) {
hp.movement.firstWeapon?.Kill();
}
else {
BaseProjectile weapon = hp.movement.firstWeapon as BaseProjectile;
if (weapon != null) {
weapon.primaryMagazine.contents = 0;
}
}
if (hp?.info.respawn == true) {
timer.Once(hp.info.respawnTimer, () => RespawnNPC(hp.info.userid));
}
}
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitinfo) {
if (hitinfo == null) return null;
if (entity == null) return null;
//if (hitinfo?.damageTypes?.GetMajorityDamageType() == DamageType.Decay) return null;
HumanoidPlayer hp = entity?.GetComponent<HumanoidPlayer>();
if (hp != null) {
DoLog("Checking in");
BasePlayer atpl = hitinfo?.Initiator as BasePlayer;
if (atpl == hp?.player) {
//DoLog($"Humanoid {hp.info.displayName} self damage, skipping...");
return true;
}
DoLog($"Humanoid {hp?.info?.displayName} majority damage type == {hitinfo?.damageTypes?.GetMajorityDamageType().ToString()}");
if (atpl != null) {
DoLog($"Player {atpl?.displayName} attacking Humanoid {hp?.info?.displayName}");
}
else {
DoLog($"{hitinfo?.Initiator?.ShortPrefabName} attacking Humanoid {hp?.info?.displayName}");
}
if (hitinfo?.InitiatorPlayer != null && hitinfo?.Initiator is BaseProjectile && hp.info.defend) {
DoLog("Setting attacked to true.");
hp.movement.attacked = true;
hp.movement.attackEntity = hitinfo.InitiatorPlayer as BaseCombatEntity;
}
else if (hitinfo?.Initiator is BaseCombatEntity && !(hitinfo?.Initiator is Barricade) && hp.info.defend) {
DoLog("Setting attacked to true.");
hp.movement.attacked = true;
hp.movement.attackEntity = hitinfo.Initiator as BaseCombatEntity;
}
//if (hp.info.message_hurt != null && hp.info.message_hurt.Count != 0)
//{
// if (hitinfo.InitiatorPlayer != null))
// {
// SendMessage(hp, hitinfo.InitiatorPlayer, GetRandomMessage(hp.info.message_hurt));
// }
//}
Interface.GetMod().CallHook("OnHitNPC", entity.GetComponent<BaseCombatEntity>(), hitinfo);
if (hp.info.invulnerable) {
hitinfo.damageTypes = new DamageTypeList();
hitinfo.DoHitEffects = false;
hitinfo.HitMaterial = 0;
if (hp.movement.sitting && !hp.movement.riding) {
hp.movement.Stand();
}
return true;
}
else {
hp.protection.Scale(hitinfo.damageTypes);
}
if (hp.movement.sitting && !hp.movement.riding) {
hp.movement.Stand();
}
hp.movement.Evade();
}
return null;
}
private object CanLootPlayer(BasePlayer target, BasePlayer player) {
if (player == null || target == null) return null;
HumanoidPlayer hp = target.GetComponentInParent<HumanoidPlayer>();
if (hp?.info.lootable == false) {
DoLog($"Player {player.displayName}:{player.UserIDString} looting Humanoid {hp.info.displayName}");
NextTick(player.EndLooting);
return true;
}
return null;
}
private object CanLootEntity(BasePlayer player, LootableCorpse corpse) {
if (player == null || corpse == null) return null;
if (hpcacheid.ContainsKey(corpse.playerSteamID)) {
HumanoidPlayer hp = hpcacheid[corpse.playerSteamID];
if (hp == null) return null;
DoLog($"Player {player.displayName}:{player.UserIDString} looting Humanoid {corpse.name}:{corpse.playerSteamID}");
if (!hp.info.lootable) {
NextTick(player.EndLooting);
return true;
}
}
return null;
}
private void OnLootPlayer(BasePlayer looter, BasePlayer target) {
if (npcs.ContainsKey(target.userID)) {
Interface.GetMod().CallHook("OnLootNPC", looter.inventory.loot, target, target.userID);
}
}
private void OnLootEntity(BasePlayer looter, BaseEntity entity) {
if (looter == null || !(entity is PlayerCorpse)) return;
ulong userId = ((PlayerCorpse)entity).playerSteamID;
npcs.TryGetValue(userId, out HumanoidInfo hi);
if (hi != null) {
Interface.GetMod().CallHook("OnLootNPC", looter.inventory.loot, entity, userId);
}
}
private object OnUserCommand(IPlayer player, string command, string[] args) {
if (command != "noid" && isopen.Contains(ulong.Parse(player.Id))) return true;
return null;
}
private object OnPlayerCommand(BasePlayer player, string command, string[] args) {
if (command != "noid" && isopen.Contains(player.userID)) return true;
return null;
}
#endregion
#region commands
[Command("noid")]
private void cmdNPC(IPlayer iplayer, string command, string[] args) {
BasePlayer player = iplayer.Object as BasePlayer;
if (args.Length > 0) {
DoLog(string.Join(",", args));
switch (args[0]) {
case "show" : foreach (KeyValuePair<ulong, HumanoidInfo> pls in npcs) {
BasePlayer tmpl = iplayer.Object as BasePlayer;
tmpl?.SendConsoleCommand("ddraw.text", 30, Color.green, pls.Value.loc + new Vector3(0, 1.55f, 0), $"<size=20>{pls.Value.displayName}</size>");
tmpl?.SendConsoleCommand("ddraw.text", 30, Color.green, pls.Value.loc + new Vector3(0, 1.5f, 0), $"<size=20>{pls.Value.loc}</size>");
}
break;
case "gui" : case "select" : CuiHelper.DestroyUi(player, NPCGUI);
CuiHelper.DestroyUi(player, NPCGUS);
NPCSelectGUI(player);
break;
case "debug" : configData.Options.debug = !configData.Options.debug;
//SaveConfig();
Message(iplayer, "debug", configData.Options.debug.ToString());
break;
case "selclose" : CuiHelper.DestroyUi(player, NPCGUS);
IsOpen(player.userID, false);
break;
case "list" : foreach (KeyValuePair<ulong, HumanoidInfo> pls in npcs) {
string ns = pls.Value.displayName + "(" + pls.Key.ToString() + ")";
Message(iplayer, ns);
}
break;
case "spawn" : case "create" : case "new" : if (player?.transform != null) {
HumanoidInfo npc = new(0, player.transform.position, player.transform.rotation);
SpawnNPC(npc, out ulong x);
Message(iplayer, "spawnedat", player.transform.position.ToString(), x.ToString());
NPCSelectGUI(player);
}
else {
HumanoidInfo npc = new(0, Vector3.zero, new Quaternion());
SpawnNPC(npc, out ulong x);
Message(iplayer, "spawnedat", Vector3.zero.ToString(), x.ToString());
}
break;
case "edit" : {
if (args.Length > 1) {
ulong.TryParse(args[1], out ulong npcid);
if (npcid > 0) {
NpcEditGUI(player, npcid);
break;
}
else {
List<string> newarg = new(args);
newarg.RemoveAt(0);
string nom = string.Join(" ", newarg);
try {
ulong pl = npcs.FirstOrDefault(x => x.Value.displayName == nom).Key;
NpcEditGUI(player, pl);
}
catch {
}
break;
}
}
else {
DoLog("Looking for npc...");
if (Physics.Raycast(player.eyes.HeadRay(), out RaycastHit hit, 5f, playerMask)) {
BasePlayer pl = hit.GetEntity().ToPlayer();
try {
ulong plbyent = npcs.FirstOrDefault(x => x.Value.displayName == pl.displayName).Key;
NpcEditGUI(player, plbyent);
}
catch {
}
break;
}
}
}
break;
case "delete" : case "remove" : {
if (args.Length > 1) {
CuiHelper.DestroyUi(player, NPCGUI);
ulong npcid = ulong.Parse(args[1]);
foreach (KeyValuePair<ulong, HumanoidInfo> pls in npcs) {
if (pls.Value.displayName == args[1]) {
RemoveNPC(pls.Value);
break;
}
else if (npcid == pls.Value.userid) {
RemoveNPC(pls.Value);
break;
}
}
if (isopen.Contains(player.userID)) NPCSelectGUI(player);
}
else {
if (Physics.Raycast(player.eyes.HeadRay(), out RaycastHit hit, 3f, playerMask)) {
BasePlayer pl = hit.GetEntity().ToPlayer();
if (pl.userID > 0) {
HumanoidInfo isnpc = npcs[pl.userID];
if (isnpc != null) {
RemoveNPC(isnpc);
}
break;
}
}
}
}
break;
case "npcselkit" : if (args.Length > 2) {
NPCKitGUI(player, ulong.Parse(args[1]), args[2]);
}
break;
case "npcselmonstart" : if (args.Length > 2) {
NPCMonGUI(player, ulong.Parse(args[1]), args[2], "start");
}
break;
case "npcselmonend" : if (args.Length > 2) {
NPCMonGUI(player, ulong.Parse(args[1]), args[2], "end");
}
break;
case "npcselroad" : if (args.Length > 2) {
if (args.Length > 3) {
NPCRoadGUI(player, ulong.Parse(args[1]), args[2] + " " + args[3]);
}
else {
NPCRoadGUI(player, ulong.Parse(args[1]), args[2]);
}
}
break;
case "npcselloco" : if (args.Length > 1) {
NPCLocoGUI(player, ulong.Parse(args[1]));
}
break;
case "kitsel" : if (args.Length > 2) {
CuiHelper.DestroyUi(player, NPCGUK);
ulong userid = ulong.Parse(args[1]);
HumanoidInfo npc = npcs[userid];
npc.kit = args[2];
HumanoidPlayer hp = FindHumanoidByID(userid);
SaveData();
RespawnNPC(hp.player);
NpcEditGUI(player, userid);
}
break;
case "monsel" : if (args.Length > 2) {
CuiHelper.DestroyUi(player, NPCGUM);
ulong userid = ulong.Parse(args[1]);
HumanoidInfo npc = npcs[userid];
switch (args[3]) {
case "end" : npc.monend = args[2];
break;
default : npc.monstart = args[2];
break;
}
HumanoidPlayer hp = FindHumanoidByID(userid);
SaveData();
RespawnNPC(hp.player);
NpcEditGUI(player, userid);
}
break;
case "roadsel" : if (args.Length > 2) {
CuiHelper.DestroyUi(player, NPCGUR);
ulong userid = ulong.Parse(args[1]);
HumanoidInfo npc = npcs[userid];
if (args[2] == "none") {
npc.roadname = null;
}
else {
npc.roadname = args[2] + " " + args[3];
}
HumanoidPlayer hp = FindHumanoidByID(userid);
SaveData();
RespawnNPC(hp.player);
NpcEditGUI(player, userid);
}
break;
case "locomode" : if (args.Length > 2) {
CuiHelper.DestroyUi(player, NPCGUL);
ulong userid = ulong.Parse(args[1]);
HumanoidInfo npc = npcs[userid];
Enum.TryParse(args[2], out LocoMode locomode);
npc.locomode = locomode;
npc.defaultLoco = locomode;
HumanoidPlayer hp = FindHumanoidByID(userid);
SaveData();
RespawnNPC(hp.player);
NpcEditGUI(player, userid);
}
break;
case "spawnhere" : if (args.Length > 1) {
ulong npcid = ulong.Parse(args[1]);
string newSpawn = player.transform.position.x.ToString() + "," + player.transform.position.y + "," + player.transform.position.z.ToString();
//Vector3 ns = player.transform.position;
TryGetPlayerView(player, out Quaternion newRot);
//HumanoidPlayer hp = FindHumanoidByID(npcid);
SetHumanoidInfo(npcid, "spawn", newSpawn);
//Teleport(hp.player, ns);
//hp.player.EndSleeping();
//npcs[npcid].loc = ns;
npcs[npcid].rot = newRot;
SaveData();
//RespawnNPC(hp.player); // Already done by SetHumanoidInfo above
if (configData.Options.closeGUIOnSpawnHere) {
CuiHelper.DestroyUi(player, NPCGUI);
CuiHelper.DestroyUi(player, NPCGUK);
CuiHelper.DestroyUi(player, NPCGUL);
CuiHelper.DestroyUi(player, NPCGUN);
CuiHelper.DestroyUi(player, NPCGUR);
CuiHelper.DestroyUi(player, NPCGUS);
CuiHelper.DestroyUi(player, NPCGUV);
return;
}
NpcEditGUI(player, npcid);
}
break;
case "tpto" : if (args.Length > 1) {
ulong npcid = ulong.Parse(args[1]);
CuiHelper.DestroyUi(player, NPCGUI);
CuiHelper.DestroyUi(player, NPCGUK);
CuiHelper.DestroyUi(player, NPCGUL);
CuiHelper.DestroyUi(player, NPCGUN);
CuiHelper.DestroyUi(player, NPCGUR);
CuiHelper.DestroyUi(player, NPCGUS);
CuiHelper.DestroyUi(player, NPCGUV);
IsOpen(player.userID, false);
Teleport(player, npcs[npcid].loc);
}
break;
case "npctoggle" : if (args.Length > 3) {
ulong userid = ulong.Parse(args[1]);
string toset = args[2];
string newval = args[3] == "True" ? "false" : "true";
SetHumanoidInfo(userid, args[2], args[3]);
NpcEditGUI(player, userid);
SaveData();
}
break;
case "npcset" : if (args.Length > 1) {
ulong userid = ulong.Parse(args[1]);
SetHumanoidInfo(userid, args[2], args[4]);
}
break;
case "selkitclose" : CuiHelper.DestroyUi(player, NPCGUK);
break;
case "selmonclose" : CuiHelper.DestroyUi(player, NPCGUM);
break;
case "selroadclose" : CuiHelper.DestroyUi(player, NPCGUR);
break;
case "sellococlose" : CuiHelper.DestroyUi(player, NPCGUL);
break;
case "close" : IsOpen(player.userID, false);
CuiHelper.DestroyUi(player, NPCGUI);
CuiHelper.DestroyUi(player, NPCGUK);
CuiHelper.DestroyUi(player, NPCGUS);
break;
}
}
}
#endregion
#region Our Inbound Hooks
private bool IsHumanoid(BasePlayer player) => player?.GetComponentInParent<HumanoidPlayer>() != null;
private ulong SpawnHumanoid(Vector3 position, Quaternion currentRot, string name = "noid", bool ephemeral = false, ulong clone = 0) {
foreach (KeyValuePair<ulong, HumanoidInfo> pair in npcs) {
if (pair.Value.displayName == name && clone == 0) {
return pair.Key;
}
}
ulong npcid = 0;
HumanoidInfo hi = new(npcid, position, currentRot) {
displayName = name, ephemeral = ephemeral
}
;
SpawnNPC(hi, out npcid);
return npcid;
}
private string GetHumanoidName(ulong npcid) {
DoLog($"Looking for humanoid: {npcid}");
HumanoidPlayer hp = FindHumanoidByID(npcid);
if (hp == null) return null;
DoLog($"Found humanoid: {hp.info.displayName}");
return hp.info.displayName;
}
private bool RemoveHumanoidById(ulong npcid) {
DoLog($"RemoveHumanoidById called for {npcid}");
HumanoidPlayer hp = FindHumanoidByID(npcid);
if (hp == null) return false;
BasePlayer npc = hp.GetComponentInParent<BasePlayer>();
if (npc == null) return false;
KillNpc(npc);
npcs.Remove(hp.info.userid);
SaveData();
return true;
}
private bool RemoveHumanoidByName(string name) {
DoLog($"RemoveHumanoidByName called for {name}");
HumanoidPlayer hp = FindHumanoidByName(name);
if (hp == null) return false;
BasePlayer npc = hp.GetComponentInParent<BasePlayer>();
if (npc == null) return false;
KillNpc(npc);
npcs.Remove(hp.info.userid);
SaveData();
return true;
}
private void SetHumanoidInfo(ulong npcid, string toset, string data, string rot = null) {
DoLog($"SetHumanoidInfo called for {npcid} {toset},{data}");
HumanoidPlayer hp = FindHumanoidByID(npcid);
if (hp == null) return;
switch (toset) {
case "kit" : hp.info.kit = data;
break;
case "entrypause" : hp.info.entrypause = !GetBoolValue(data);
break;
case "entrypausetime" : hp.info.entrypausetime = Convert.ToSingle(data);
break;
case "health" : hp.info.health = Convert.ToSingle(data);
hp.UpdateHealth(hp.info);
break;
case "name" : case "displayName" : hp.info.displayName = data;
break;
case "invulnerable" : case "invulnerability" : hp.info.invulnerable = !GetBoolValue(data);
break;
case "lootable" : hp.info.lootable = !GetBoolValue(data);
break;
case "shopnpc" : hp.info.shopnpc = !GetBoolValue(data);
break;
case "ahostile" : hp.info.ahostile = !GetBoolValue(data);
break;
case "hostile" : hp.info.hostile = !GetBoolValue(data);
break;
case "gather" : hp.info.gather = !GetBoolValue(data);
hp.info.canmove = hp.info.defend;
break;
case "defend" : hp.info.defend = !GetBoolValue(data);
hp.info.canmove = hp.info.defend;
break;
case "evade" : hp.info.evade = !GetBoolValue(data);
hp.info.canmove = hp.info.evade;
break;
case "follow" : hp.info.follow = !GetBoolValue(data);
hp.info.canmove = hp.info.follow;
break;
case "canmove" : hp.info.canmove = !GetBoolValue(data);
break;
case "allowsit" : case "cansit" : hp.info.cansit = !GetBoolValue(data);
hp.info.canmove = hp.info.cansit;
hp.info.locomode = LocoMode.Sit;
break;
case "canride" : hp.info.canride = !GetBoolValue(data);
hp.info.canmove = hp.info.canride;
hp.info.locomode = LocoMode.Ride;
break;
case "needsammo" : case "needsAmmo" : hp.info.needsammo = !GetBoolValue(data);
break;
case "dropWeapon" : hp.info.dropWeapon = !GetBoolValue(data);
break;
case "respawn" : hp.info.respawn = !GetBoolValue(data);
break;
case "respawnTimer" : case "respawntimer" : hp.info.respawnTimer = Convert.ToSingle(data);
break;
case "attackdistance" : hp.info.attackDistance = Convert.ToSingle(data);
break;
case "maxdistance" : hp.info.maxDistance = Convert.ToSingle(data);
break;
case "damagedistance" : hp.info.damageDistance = Convert.ToSingle(data);
break;
case "locomode" : switch (Convert.ToInt32(data)) {
default : hp.info.locomode = LocoMode.Default;
break;
case 1 : hp.info.locomode = LocoMode.Follow;
break;
case 2 : hp.info.locomode = LocoMode.Sit;
break;
case 4 : hp.info.locomode = LocoMode.Stand;
break;
case 8 : hp.info.locomode = LocoMode.Road;
break;
case 16 : hp.info.locomode = LocoMode.Ride;
break;
case 32 : hp.info.locomode = LocoMode.Monument;
break;
}
break;
case "speed" : hp.info.speed = Convert.ToSingle(data);
break;
case "spawn" : case "loc" : hp.info.loc = StringToVector3(data);
break;
case "townrel" : hp.info.townRel = StringToVector3(data);
break;
case "rot" : hp.info.rot = StringToQuaternion(data);
break;
}
DoLog("Saving Data");
SaveData();
DoLog("Respawning");
RespawnNPC(hp.player);
}
private void GiveHumanoid(ulong npcid, string itemname, string loc = "wear", ulong skinid = 0, int count = 1) {
HumanoidPlayer npc = FindHumanoidByID(npcid);
DoLog($"GiveHumanoid called: {npc.info.displayName}, {itemname}, {loc}");
if (npc.player != null) {
switch (loc) {
case "kit" : npc.info.kit = itemname;
UpdateInventory(npc);
break;
case "belt" : {
Item item = ItemManager.CreateByName(itemname, 1, skinid);
item.MoveToContainer(npc.player.inventory.containerBelt, -1, true);
if (item.info.category == ItemCategory.Weapon) npc.EquipFirstWeapon();
}
break;
case "main" : {
// e.g. for ammo
Item item = ItemManager.CreateByName(itemname, count, skinid);
item.MoveToContainer(npc.player.inventory.containerMain, -1, true);
if (item.info.category == ItemCategory.Weapon) npc.EquipFirstWeapon();
}
break;
default : {
Item item = ItemManager.CreateByName(itemname, 1, skinid);
item.MoveToContainer(npc.player.inventory.containerWear, -1, true);
}
break;
}
npc.player.inventory.ServerUpdate(0f);
}
}
#endregion
#region GUI
// Determine open GUI to limit interruptions
private void IsOpen(ulong uid, bool set = false) {
if (set) {
DoLog($"Setting isopen for {uid}");
if (!isopen.Contains(uid)) isopen.Add(uid);
return;
}
DoLog($"Clearing isopen for {uid}");
isopen.Remove(uid);
}
private void NPCSelectGUI(BasePlayer player) {
if (player == null) return;
IsOpen(player.userID, true);
CuiHelper.DestroyUi(player, NPCGUS);
string description = Lang("npcguisel");
CuiElementContainer container = UI.Container(NPCGUS, UI.Color("242424", 1f), "0.1 0.1", "0.9 0.9", true, "Overlay");
UI.Label(ref container, NPCGUS, UI.Color("#ffffff", 1f), description, 18, "0.23 0.92", "0.7 1");
UI.Label(ref container, NPCGUS, UI.Color("#2244cc", 1f), Lang("standard"), 12, "0.79 0.92", "0.86 1");
UI.Button(ref container, NPCGUS, UI.Color("#d85540", 1f), Lang("close"), 12, "0.92 0.93", "0.985 0.98", "noid selclose");
int col = 0;
int row = 0;
foreach (KeyValuePair<ulong, HumanoidInfo> npc in npcs) {
if (row > 10) {
row = 0;
col++;
}
const string color = "#2244cc";
string hName = npc.Value.displayName;
float[] posb = GetButtonPositionP(row, col);
UI.Button(ref container, NPCGUS, UI.Color(color, 1f), hName, 12, $"{posb[0]} {posb[1]}", $"{posb[0] + ((posb[2] - posb[0]) / 2)} {posb[3]}", $"noid edit {npc.Value.displayName}");
row++;
}
float[] posn = GetButtonPositionP(row, col);
UI.Button(ref container, NPCGUS, UI.Color("#cc3333", 1f), Lang("new"), 12, $"{posn[0]} {posn[1]}", $"{posn[0] + ((posn[2] - posn[0]) / 2)} {posn[3]}", $"noid new");
CuiHelper.AddUi(player, container);
}
private void NpcEditGUI(BasePlayer player, ulong npc = 0) {
if (player == null) return;
IsOpen(player.userID, true);
CuiHelper.DestroyUi(player, NPCGUI);
string npcname = Lang("needselect");
if (npc > 0) {
npcname = Lang("editing") + " " + npcs[npc].displayName + "(" + npc.ToString() + ")";
}
CuiElementContainer container = UI.Container(NPCGUI, UI.Color("2b2b2b", 1f), "0.05 0.05", "0.95 0.95", true, "Overlay");
if (npc == 0) {
UI.Button(ref container, NPCGUI, UI.Color("#cc3333", 1f), Lang("new"), 12, "0.79 0.95", "0.85 0.98", $"noid new");
}
else {
UI.Button(ref container, NPCGUI, UI.Color("#cc3333", 1f), Lang("remove"), 12, "0.79 0.95", "0.85 0.98", $"noid remove {npc}");
}
UI.Button(ref container, NPCGUI, UI.Color("#d85540", 1f), Lang("select"), 12, "0.86 0.95", "0.92 0.98", $"noid select");
UI.Button(ref container, NPCGUI, UI.Color("#d85540", 1f), Lang("close"), 12, "0.93 0.95", "0.99 0.98", $"noid close");
UI.Label(ref container, NPCGUI, UI.Color("#ffffff", 1f), Lang("npcgui") + ": " + npcname, 24, "0.2 0.92", "0.7 1");
int col = 0;
int row = 0;
if (npc == 0) {
UI.Label(ref container, NPCGUI, UI.Color("#ffffff", 1f), Lang("mustselect"), 24, "0.2 0.47", "0.7 0.53");
}
else {
Dictionary<string, string> npcinfo = new() {
{
"displayName", npcs[npc].displayName
}
, {
"kit", npcs[npc].kit
}
, {
"loc", npcs[npc].loc.ToString()
}
, {
"health", npcs[npc].health.ToString()
}
, {
"invulnerable", npcs[npc].invulnerable.ToString()
}
, {
"lootable", npcs[npc].lootable.ToString()
}
, {
"shopnpc", npcs[npc].shopnpc.ToString()
}
, {
"ahostile", npcs[npc].ahostile.ToString()
}
, {
"hostile", npcs[npc].hostile.ToString()
}
, {
"defend", npcs[npc].defend.ToString()
}
, {
"evade", npcs[npc].evade.ToString()
}
, {
"follow", npcs[npc].follow.ToString()
}
, {
"followtime", npcs[npc].followTime.ToString()
}
, {
"canmove", npcs[npc].canmove.ToString()
}
, {
"cansit", npcs[npc].cansit.ToString()
}
, {
"canride", npcs[npc].canride.ToString()
}
, {
"needsAmmo", npcs[npc].needsammo.ToString()
}
, {
"dropWeapon", npcs[npc].dropWeapon.ToString()
}
, {
"respawn", npcs[npc].respawn.ToString()
}
, {
"respawnTimer", npcs[npc]
// ... [Code preview truncated due to length. Full source in attached file] ...