C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Plugins.MonumentAddonsExtensions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using VLB;
using static IOEntity;
using static WireTool;
using CustomSpawnCallback = System.Func<UnityEngine.Vector3, UnityEngine.Quaternion, Newtonsoft.Json.Linq.JObject, UnityEngine.Component>;
using CustomKillCallback = System.Action<UnityEngine.Component>;
using CustomUpdateCallback = System.Action<UnityEngine.Component, Newtonsoft.Json.Linq.JObject>;
using CustomAddDisplayInfoCallback = System.Action<UnityEngine.Component, Newtonsoft.Json.Linq.JObject, System.Text.StringBuilder>;
using CustomSetDataCallback = System.Action<UnityEngine.Component, object>;
using System.Text.RegularExpressions;
using Facepunch;
namespace Oxide.Plugins {
[Info("Monument Addons", "WhiteThunder", "0.15.0")]
[Description("Allows adding entities, spawn points and more to monuments.")]
internal class MonumentAddons : CovalencePlugin {
#region Fields
[PluginReference]
private Plugin CopyPaste, CustomVendingSetup, EntityScaleManager, MonumentFinder, SignArtist;
private MonumentAddons _plugin;
private Configuration _config;
private StoredData _data;
private ProfileStateData _profileStateData;
private const float MaxRaycastDistance = 100;
private const float TerrainProximityTolerance = 0.001f;
private const float MaxFindDistanceSquared = 4;
private const float ShowVanillaDuration = 60;
private const string PermissionAdmin = "monumentaddons.admin";
private const string WireToolPlugEffect = "assets/prefabs/tools/wire/effects/plugeffect.prefab";
private const string CargoShipShortName = "cargoshiptest";
private const string DefaultProfileName = "Default";
private const string DefaultUrlPattern = "https://github.com/WheteThunger/MonumentAddons/blob/master/Profiles/{0}.json?raw=true";
private static readonly int HitLayers = Rust.Layers.Solid | Rust.Layers.Mask.Water;
private static readonly Dictionary<string, string> JsonRequestHeaders = new Dictionary<string, string> {
{
"Content-Type", "application/json"
}
}
;
private readonly ProfileStore _profileStore = new ProfileStore();
private readonly OriginalProfileStore _originalProfileStore = new OriginalProfileStore();
private readonly ProfileManager _profileManager;
private readonly CoroutineManager _coroutineManager = new CoroutineManager();
private readonly MonumentEntityTracker _entityTracker = new MonumentEntityTracker();
private readonly AdapterListenerManager _adapterListenerManager;
private readonly ControllerFactory _controllerFactory;
private readonly CustomAddonManager _customAddonManager;
private readonly UniqueNameRegistry _uniqueNameRegistry = new UniqueNameRegistry();
private readonly AdapterDisplayManager _adapterDisplayManager;
private readonly MonumentHelper _monumentHelper;
private readonly WireToolManager _wireToolManager;
private readonly IOManager _ioManager = new IOManager();
private readonly UndoManager _undoManager = new UndoManager();
private readonly Color[] _distinctColors = {
Color.HSVToRGB(0, 1, 1), Color.HSVToRGB(0.1f, 1, 1), Color.HSVToRGB(0.2f, 1, 1), Color.HSVToRGB(0.35f, 1, 1), Color.HSVToRGB(0.55f, 1, 1), Color.HSVToRGB(0.8f, 1, 1), new Color(1, 1, 1),
}
;
private readonly object False = false;
private ItemDefinition _waterDefinition;
private ProtectionProperties _immortalProtection;
private ActionDebounced _saveProfileStateDebounced;
private Coroutine _startupCoroutine;
private bool _serverInitialized;
private bool _isLoaded = true;
public MonumentAddons() {
_profileManager = new ProfileManager(this, _originalProfileStore, _profileStore);
_adapterDisplayManager = new AdapterDisplayManager(this, _uniqueNameRegistry);
_adapterListenerManager = new AdapterListenerManager(this);
_customAddonManager = new CustomAddonManager(this);
_controllerFactory = new ControllerFactory(this);
_monumentHelper = new MonumentHelper(this);
_wireToolManager = new WireToolManager(this, _profileStore, _entityTracker);
_saveProfileStateDebounced = new ActionDebounced(timer, 1, () => {
if (!_isLoaded) return;
_profileStateData.Save();
}
);
}
#endregion
#region Hooks
private void Init() {
_plugin = this;
_data = StoredData.Load(_profileStore);
_profileStateData = ProfileStateData.Load(_data);
_config.Init();
// Ensure the profile folder is created to avoid errors._profileStore.EnsureDefaultProfile();
permission.RegisterPermission(PermissionAdmin, this);
Unsubscribe(nameof(OnEntitySpawned));
_adapterListenerManager.Init();
}
private void OnServerInitialized() {
_waterDefinition = ItemManager.FindItemDefinition("water");
_immortalProtection = ScriptableObject.CreateInstance<ProtectionProperties>();
_immortalProtection.name = "MonumentAddonsProtection";
_immortalProtection.Add(1);
_uniqueNameRegistry.OnServerInitialized();
_adapterListenerManager.OnServerInitialized();
_monumentHelper.OnServerInitialized();
_ioManager.OnServerInitialized();
var entitiesToKill = _profileStateData.CleanDisabledProfileState();
if (entitiesToKill.Count > 0) {
CoroutineManager.StartGlobalCoroutine(KillEntitiesRoutine(entitiesToKill));
}
if (CheckDependencies()) {
StartupRoutine();
}
_serverInitialized = true;
}
private void Unload() {
_coroutineManager.Destroy();
_profileManager.UnloadAllProfiles();
_saveProfileStateDebounced.Flush();
_wireToolManager.Unload();
UnityEngine.Object.Destroy(_immortalProtection);
_isLoaded = false;
}
private void OnNewSave() {
_profileStateData.Reset();
}
private void OnPluginLoaded(Plugin plugin) {
// Check whether initialized to detect only late (re)loads.// Note: We are not dynamically subscribing to OnPluginLoaded since that interferes with [PluginReference] for some reason.if (_serverInitialized && plugin == MonumentFinder)
{
StartupRoutine();
}
}
private void OnPluginUnloaded(Plugin plugin) {
if (plugin.Name == Name) return;
_customAddonManager.UnregisterAllForPlugin(plugin);
}
private void OnEntitySpawned(CargoShip cargoShip) {
var cargoShipMonument = new DynamicMonument(cargoShip, isMobile : true);
_coroutineManager.StartCoroutine(_profileManager.PartialLoadForLateMonumentRoutine(cargoShipMonument));
}
// This hook is exposed by plugin: Remover Tool (RemoverTool).private object canRemove(BasePlayer player, BaseEntity entity)
{
if (_entityTracker.IsMonumentEntity(entity)) return False;
return null;
}
private object CanChangeGrade(BasePlayer player, BuildingBlock block, BuildingGrade.Enum grade) {
if (_entityTracker.IsMonumentEntity(block) && !HasAdminPermission(player)) return False;
return null;
}
private object CanUpdateSign(BasePlayer player, ISignage signage) {
if (_entityTracker.IsMonumentEntity(signage as BaseEntity) && !HasAdminPermission(player)) {
ChatMessage(player, LangEntry.ErrorNoPermission);
return False;
}
return null;
}
private void OnSignUpdated(ISignage signage, BasePlayer player) {
if (!_entityTracker.IsMonumentEntity(signage as BaseEntity)) return;
var component = MonumentEntityComponent.GetForEntity(signage.NetworkID);
if (component == null) return;
var adapter = component.Adapter as SignAdapter;
if (adapter == null) return;
var controller = adapter.Controller as SignController;
if (controller == null) return;
controller.UpdateSign(signage.GetTextureCRCs());
}
// This hook is exposed by plugin: Sign Arist (SignArtist).private void OnImagePost(BasePlayer player, string url, bool raw, ISignage signage, uint textureIndex = 0)
{
SignController controller;
if (!_entityTracker.IsMonumentEntity(signage as BaseEntity, out controller)) return;
if (controller.EntityData.SignArtistImages == null) {
controller.EntityData.SignArtistImages = new SignArtistImage[signage.TextureCount];
}
else if (controller.EntityData.SignArtistImages.Length < signage.TextureCount) {
Array.Resize(ref controller.EntityData.SignArtistImages, signage.TextureCount);
}
controller.EntityData.SignArtistImages[textureIndex] = new SignArtistImage {
Url = url, Raw = raw,
}
;
_profileStore.Save(controller.Profile);
}
private object OnSprayRemove(SprayCanSpray spray, BasePlayer player) {
if (_entityTracker.IsMonumentEntity(spray)) return False;
return null;
}
private void OnEntityScaled(BaseEntity entity, float scale) {
EntityController controller;
if (!_entityTracker.IsMonumentEntity(entity, out controller) || controller.EntityData.Scale == scale) return;
controller.EntityData.Scale = scale;
controller.StartHandleChangesRoutine();
_profileStore.Save(controller.Profile);
}
// This hook is exposed by plugin: Telekinesis.private BaseEntity OnTelekinesisFindFailed(BasePlayer player)
{
if (!HasAdminPermission(player)) return null;
return FindAdapter<EntityAdapter>(player).Adapter?.Entity;
}
// This hook is exposed by plugin: Telekinesis.private object CanStartTelekinesis(BasePlayer player, BaseEntity moveEntity)
{
if (_entityTracker.IsMonumentEntity(moveEntity) && !HasAdminPermission(player)) return False;
return null;
}
// This hook is exposed by plugin: Telekinesis.private void OnTelekinesisStarted(BasePlayer player, BaseEntity moveEntity, BaseEntity rotateEntity)
{
if (_entityTracker.IsMonumentEntity(moveEntity)) {
_adapterDisplayManager.ShowAllRepeatedly(player);
}
if (GetSpawnPointAdapter(moveEntity) != null) {
_adapterDisplayManager.ShowAllRepeatedly(player);
var spawnedVehicleComponent = moveEntity.GetComponent<SpawnedVehicleComponent>();
if (spawnedVehicleComponent != null) {
spawnedVehicleComponent.CancelInvoke(spawnedVehicleComponent.CheckPositionTracked);
}
}
}
// This hook is exposed by plugin: Telekinesis.private void OnTelekinesisStopped(BasePlayer player, BaseEntity moveEntity, BaseEntity rotateEntity)
{
EntityAdapter adapter;
EntityController controller;
int adapterCount;
string profileName;
var spawnPointAdapter = GetSpawnPointAdapter(moveEntity);
if (spawnPointAdapter != null) {
var moveEntityTransform = moveEntity.transform;
var moveEntityPosition = moveEntityTransform.position;
if (!spawnPointAdapter.Monument.IsInBounds(moveEntityPosition)) return;
var localPosition = spawnPointAdapter.Monument.InverseTransformPoint(moveEntityPosition);
spawnPointAdapter.SpawnPointData.Position = localPosition;
spawnPointAdapter.SpawnPointData.RotationAngles = (Quaternion.Inverse(spawnPointAdapter.Monument.Rotation) * moveEntityTransform.rotation).eulerAngles;
spawnPointAdapter.SpawnPointData.SnapToTerrain = IsOnTerrain(moveEntityPosition);
_profileStore.Save(spawnPointAdapter.Profile);
var spawnGroupController = spawnPointAdapter.Controller as SpawnGroupController;
spawnGroupController.UpdateSpawnGroups();
adapterCount = spawnGroupController.Adapters.Count;
profileName = spawnPointAdapter.Profile.Name;
}
else if (_entityTracker.IsMonumentEntity(moveEntity, out adapter, out controller)) {
if (!adapter.TrySaveAndApplyChanges()) return;
adapterCount = controller.Adapters.Count;
profileName = controller.Profile.Name;
}
else return;
if (player != null) {
_adapterDisplayManager.ShowAllRepeatedly(player);
ChatMessage(player, LangEntry.SaveSuccess, adapterCount, profileName);
}
}
// This hook is exposed by plugin: Custom Vending Setup (CustomVendingSetup).private Dictionary<string, object> OnCustomVendingSetupDataProvider(NPCVendingMachine vendingMachine)
{
EntityController controller;
if (!_entityTracker.IsMonumentEntity(vendingMachine, out controller)) return null;
var vendingProfile = controller.EntityData.VendingProfile;
if (vendingProfile == null) {
// Check if there's one to be migrated.var migratedVendingProfile = MigrateVendingProfile(vendingMachine);
if (migratedVendingProfile != null) {
controller.EntityData.VendingProfile = migratedVendingProfile;
_profileStore.Save(controller.Profile);
LogWarning($"Successfully migrated vending machine settings from CustomVendingSetup to MonumentAddons profile '{controller.Profile.Name}'.");
}
}
return controller.GetVendingDataProvider();
}
#endregion
#region Dependencies
private bool CheckDependencies() {
if (MonumentFinder == null) {
LogError("MonumentFinder is not loaded, get it at https://umod.org.");
return false;
}
return true;
}
private float GetEntityScale(BaseEntity entity) {
if (EntityScaleManager == null) return 1;
return Convert.ToSingle(EntityScaleManager?.Call("API_GetScale", entity));
}
private bool TryScaleEntity(BaseEntity entity, float scale) {
var result = EntityScaleManager?.Call("API_ScaleEntity", entity, scale);
return result is bool && (bool)result;
}
private void SkinSign(ISignage signage, SignArtistImage[] signArtistImages) {
if (SignArtist == null) return;
var apiName = signage is Signage ? "API_SkinSign" : signage is PhotoFrame ? "API_SkinPhotoFrame" : signage is CarvablePumpkin ? "API_SkinPumpkin" : null;
if (apiName == null) {
LogError($"Unrecognized sign type: {signage.GetType()}");
return;
}
for (uint textureIndex = 0; textureIndex < signArtistImages.Length; textureIndex++) {
var imageInfo = signArtistImages[textureIndex];
if (imageInfo == null) continue;
SignArtist.Call(apiName, null, signage, imageInfo.Url, imageInfo.Raw, textureIndex);
}
}
private JObject MigrateVendingProfile(NPCVendingMachine vendingMachine) {
return CustomVendingSetup?.Call("API_MigrateVendingProfile", vendingMachine) as JObject;
}
private void RefreshVendingProfile(NPCVendingMachine vendingMachine) {
CustomVendingSetup?.Call("API_RefreshDataProvider", vendingMachine);
}
private static class PasteUtils {
private static readonly string[] CopyPasteArgs = {
"stability", "false", "checkplaced", "false",
}
;
private static VersionNumber _requiredVersion = new VersionNumber(4, 1, 32);
public static bool IsCopyPasteCompatible(Plugin copyPaste) {
return copyPaste != null && copyPaste.Version >= _requiredVersion;
}
public static bool DoesPasteExist(string filename) {
return Interface.Oxide.DataFileSystem.ExistsDatafile("copypaste/" + filename);
}
public static Action PasteWithCancelCallback(Plugin copyPaste, PasteData pasteData, Vector3 position, float yRotation, Action<BaseEntity> onEntityPasted, Action onPasteCompleted) {
if (copyPaste == null) return null;
var result = copyPaste.Call("TryPasteFromVector3Cancellable", position, yRotation, pasteData.Filename, CopyPasteArgs, onPasteCompleted, onEntityPasted);
if (!(result is ValueTuple<object, Action>)) {
LogError($"CopyPaste returned an unexpected response for paste \"{pasteData.Filename}\": {result}.Is CopyPaste up-to-date?");
return null;
}
var pasteResult = (ValueTuple<object, Action>)result;
if (!true.Equals(pasteResult.Item1)) {
LogError($"CopyPaste returned an unexpected response for paste \"{pasteData.Filename}\": {pasteResult.Item1}.");
return null;
}
return pasteResult.Item2;
}
}
#endregion
#region API
[HookMethod(nameof(API_IsMonumentEntity))]
public object API_IsMonumentEntity(BaseEntity entity) {
return ObjectCache.Get(_entityTracker.IsMonumentEntity(entity));
}
[HookMethod(nameof(API_GetMonumentEntityGuid))]
public object API_GetMonumentEntityGuid(BaseEntity entity) {
if (!_entityTracker.IsMonumentEntity(entity)) return null;
var adapter = MonumentEntityComponent.GetForEntity(entity).Adapter as BaseAdapter;
if (adapter == null) return null;
return ObjectCache.Get(adapter.Data.Id);
}
#endregion
#region Exposed Hooks
private static class ExposedHooks {
public static void OnMonumentAddonsInitialized() {
Interface.CallHook("OnMonumentAddonsInitialized");
}
public static void OnMonumentEntitySpawned(BaseEntity entity, MonoBehaviour monument, Guid guid) {
Interface.CallHook("OnMonumentEntitySpawned", entity, monument, ObjectCache.Get(guid));
}
}
#endregion
#region Commands
private enum PuzzleOption {
PlayersBlockReset, PlayerDetectionRadius, SecondsBetweenResets,
}
private enum SpawnGroupOption {
Name, MaxPopulation, RespawnDelayMin, RespawnDelayMax, SpawnPerTickMin, SpawnPerTickMax, InitialSpawn, PreventDuplicates, PauseScheduleWhileFull, RespawnWhenNearestPuzzleResets,
}
private enum SpawnPointOption {
Exclusive, SnapToGround, CheckSpace, RandomRotation, RandomRadius, PlayerDetectionRadius,
}
[Command("maspawn")]
private void CommandSpawn(IPlayer player, string cmd, string[] args) {
ProfileController profileController;
string prefabName;
Vector3 position;
BaseMonument monument;
CustomAddonDefinition addonDefinition;
ulong skinId;
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player) || !VerifyMonumentFinderLoaded(player) || !VerifyProfileSelected(player, out profileController) || !VerifyValidEntityPrefabOrDeployable(player, args, out prefabName, out addonDefinition, out skinId) || !VerifyHitPosition(player, out position) || !VerifyAtMonument(player, position, out monument)) return;
Vector3 localPosition;
Vector3 localRotationAngles;
bool isOnTerrain;
DetermineLocalTransformData(position, basePlayer, monument, out localPosition, out localRotationAngles, out isOnTerrain);
BaseData addonData;
if (prefabName != null) {
// Found a valid prefab name.var shortPrefabName = GetShortName(prefabName);
if (shortPrefabName == "big_wheel") {
localRotationAngles.y -= 90;
localRotationAngles.z = 270;
}
else if (shortPrefabName == "boatspawner") {
if (position.y == TerrainMeta.WaterMap.GetHeight(position)) {
// Set the boatspawner to -1.5 like the vanilla ones.localPosition.y -= 1.5f;
}
}
else if (shortPrefabName == "spray.decal") {
localRotationAngles.x += 270;
localRotationAngles.y -= 90;
}
var entityData = new EntityData {
Id = Guid.NewGuid(), Skin = skinId, PrefabName = prefabName, Position = localPosition, RotationAngles = localRotationAngles, SnapToTerrain = isOnTerrain,
}
;
if (shortPrefabName.StartsWith("generator.static")) {
entityData.Puzzle = new PuzzleData {
PlayersBlockReset = true, PlayerDetectionRadius = 30, SecondsBetweenResets = 1800,
}
;
}
addonData = entityData;
}
else {
// Found a custom addon definition.addonData = new CustomAddonData
{
Id = Guid.NewGuid(), AddonName = addonDefinition.AddonName, Position = localPosition, RotationAngles = localRotationAngles, SnapToTerrain = isOnTerrain,
}
;
}
var matchingMonuments = GetMonumentsByAliasOrShortName(monument.AliasOrShortName);
profileController.Profile.AddData(monument.AliasOrShortName, addonData);
_profileStore.Save(profileController.Profile);
profileController.SpawnNewData(addonData, matchingMonuments);
ReplyToPlayer(player, LangEntry.PrefabSuccess, matchingMonuments.Count, profileController.Profile.Name, monument.AliasOrShortName);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("maprefab")]
private void CommandPrefab(IPlayer player, string cmd, string[] args) {
ProfileController profileController;
string prefabName;
Vector3 position;
BaseMonument monument;
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player) || !VerifyMonumentFinderLoaded(player) || !VerifyProfileSelected(player, out profileController) || !VerifyValidModderPrefab(player, args, out prefabName) || !VerifyHitPosition(player, out position) || !VerifyAtMonument(player, position, out monument)) return;
if (FindBaseEntityForPrefab(prefabName) != null) {
ReplyToPlayer(player, LangEntry.PrefabErrorIsEntity, prefabName);
return;
}
Vector3 localPosition;
Vector3 localRotationAngles;
bool isOnTerrain;
DetermineLocalTransformData(position, basePlayer, monument, out localPosition, out localRotationAngles, out isOnTerrain);
var prefabData = new PrefabData {
Id = Guid.NewGuid(), PrefabName = prefabName, Position = localPosition, RotationAngles = localRotationAngles, SnapToTerrain = isOnTerrain,
}
;
var matchingMonuments = GetMonumentsByAliasOrShortName(monument.AliasOrShortName);
profileController.Profile.AddData(monument.AliasOrShortName, prefabData);
_profileStore.Save(profileController.Profile);
profileController.SpawnNewData(prefabData, matchingMonuments);
ReplyToPlayer(player, LangEntry.SpawnSuccess, matchingMonuments.Count, profileController.Profile.Name, monument.AliasOrShortName);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("masave")]
private void CommandSave(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player)) return;
EntityAdapter adapter;
EntityController controller;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
if (!adapter.TrySaveAndApplyChanges()) {
ReplyToPlayer(player, LangEntry.SaveNothingToDo);
return;
}
ReplyToPlayer(player, LangEntry.SaveSuccess, controller.Adapters.Count, controller.Profile.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("makill")]
private void CommandKill(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player)) return;
BaseController controller;
TransformAdapter adapter;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
// Capture adapter count before killing the controller.var numAdapters = controller.Adapters.Count;
string monumentAliasOrShortName;
controller.Profile.RemoveData(adapter.Data, out monumentAliasOrShortName);
var profile = controller.Profile;
_profileStore.Save(profile);
var profileController = controller.ProfileController;
var killRoutine = controller.Kill(adapter.Data);
if (killRoutine != null) {
profileController.StartCallbackRoutine(killRoutine, profileController.SetupIO);
}
var spawnGroupData = controller.Data as SpawnGroupData;
var spawnPointData = adapter.Data as SpawnPointData;
if (spawnGroupData != null && spawnPointData != null) {
_undoManager.AddUndo(basePlayer, new UndoKillSpawnPoint(this, profileController, monumentAliasOrShortName, spawnGroupData, spawnPointData));
}
else {
_undoManager.AddUndo(basePlayer, new UndoKill(this, profileController, monumentAliasOrShortName, controller.Data));
}
ReplyToPlayer(player, LangEntry.KillSuccess, GetAddonName(player, adapter.Data), numAdapters, profile.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("maundo")]
private void CommandUndo(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player)) return;
if (!_undoManager.TryUndo(basePlayer)) {
ReplyToPlayer(player, LangEntry.UndoNotFound);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
}
[Command("masetid")]
private void CommandSetId(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player)) return;
if (args.Length < 1 || !ComputerStation.IsValidIdentifier(args[0])) {
ReplyToPlayer(player, LangEntry.CCTVSetIdSyntax, cmd);
return;
}
CCTVController controller;
if (!VerifyLookingAtAdapter(player, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
if (controller.EntityData.CCTV == null) {
controller.EntityData.CCTV = new CCTVInfo();
}
var hadIdentifier = !string.IsNullOrEmpty(controller.EntityData.CCTV.RCIdentifier);
controller.EntityData.CCTV.RCIdentifier = args[0];
_profileStore.Save(controller.Profile);
controller.StartHandleChangesRoutine();
ReplyToPlayer(player, LangEntry.CCTVSetIdSuccess, args[0], controller.Adapters.Count, controller.Profile.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer, immediate : hadIdentifier);
}
[Command("masetdir")]
private void CommandSetDirection(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player)) return;
CCTVEntityAdapter adapter;
CCTVController controller;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
var cctv = adapter.Entity as CCTV_RC;
var direction = Vector3Ex.Direction(basePlayer.eyes.position, cctv.transform.position);
direction = cctv.transform.InverseTransformDirection(direction);
var lookAngles = BaseMountable.ConvertVector(Quaternion.LookRotation(direction).eulerAngles);
if (controller.EntityData.CCTV == null) {
controller.EntityData.CCTV = new CCTVInfo();
}
controller.EntityData.CCTV.Pitch = lookAngles.x;
controller.EntityData.CCTV.Yaw = lookAngles.y;
_profileStore.Save(controller.Profile);
controller.StartHandleChangesRoutine();
ReplyToPlayer(player, LangEntry.CCTVSetDirectionSuccess, controller.Adapters.Count, controller.Profile.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("maskull")]
private void CommandSkull(IPlayer player, string cmd, string[] args) {
if (player.IsServer || !VerifyHasPermission(player)) return;
EntityController controller;
EntityAdapter adapter;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
var skullTrophy = adapter.Entity as SkullTrophy;
if (skullTrophy == null) {
ReplyToPlayer(player, LangEntry.ErrorNoSuitableAddonFound);
return;
}
if (args.Length == 0) {
ReplyToPlayer(player, LangEntry.SkullNameSyntax, cmd);
return;
}
var skullName = args[0];
var updatedSkullName = controller.EntityData.SkullName != skullName;
controller.EntityData.SkullName = skullName;
_profileStore.Save(controller.Profile);
controller.StartHandleChangesRoutine();
ReplyToPlayer(player, LangEntry.SkullNameSetSuccess, skullName, controller.Adapters.Count, controller.Profile.Name);
var basePlayer = player.Object as BasePlayer;
_adapterDisplayManager.ShowAllRepeatedly(basePlayer, immediate : !updatedSkullName);
}
[Command("matrophy")]
private void CommandTrophy(IPlayer player, string cmd, string[] args) {
T GetSubEntity<T>(Item item) where T : BaseEntity {
var entityId = item.instanceData?.subEntity ?? new NetworkableId(0);
if (entityId.Value == 0) return null;
return BaseNetworkable.serverEntities.Find(entityId) as T;
}
if (player.IsServer || !VerifyHasPermission(player)) return;
EntityController controller;
EntityAdapter adapter;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
var huntingTrophy = adapter.Entity as HuntingTrophy;
if (huntingTrophy == null) {
ReplyToPlayer(player, LangEntry.ErrorNoSuitableAddonFound);
return;
}
var basePlayer = player.Object as BasePlayer;
var heldItem = basePlayer.GetActiveItem();
var headEntity = heldItem != null ? GetSubEntity<HeadEntity>(heldItem) : null;
if (headEntity == null) {
ReplyToPlayer(player, LangEntry.SetHeadNoHeadItem);
return;
}
if (!huntingTrophy.CanSubmitHead(headEntity)) {
ReplyToPlayer(player, LangEntry.SetHeadMismatch);
return;
}
controller.EntityData.HeadData = HeadData.FromHeadEntity(headEntity);
_profileStore.Save(controller.Profile);
controller.StartHandleChangesRoutine();
ReplyToPlayer(player, LangEntry.SetHeadSuccess, controller.Adapters.Count, controller.Profile.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("maskin")]
private void CommandSkin(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player)) return;
EntityAdapter adapter;
EntityController controller;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
if (args.Length == 0) {
ReplyToPlayer(player, LangEntry.SkinGet, adapter.Entity.skinID, cmd);
return;
}
ulong skinId;
if (!ulong.TryParse(args[0], out skinId)) {
ReplyToPlayer(player, LangEntry.SkinSetSyntax, cmd);
return;
}
string alternativeShortName;
if (IsRedirectSkin(skinId, out alternativeShortName)) {
ReplyToPlayer(player, LangEntry.SkinErrorRedirect, skinId, alternativeShortName);
return;
}
var updatedExistingSkin = (controller.EntityData.Skin == 0) != (skinId == 0);
controller.EntityData.Skin = skinId;
_profileStore.Save(controller.Profile);
controller.StartHandleChangesRoutine();
ReplyToPlayer(player, LangEntry.SkinSetSuccess, skinId, controller.Adapters.Count, controller.Profile.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer, immediate : !updatedExistingSkin);
}
private void AddProfileDescription(StringBuilder sb, IPlayer player, ProfileController profileController) {
foreach (var summaryEntry in GetProfileSummary(player, profileController.Profile)) {
sb.AppendLine(GetMessage(player.Id, LangEntry.ProfileDescribeItem, summaryEntry.AddonType, summaryEntry.AddonName, summaryEntry.Count, summaryEntry.MonumentName));
}
}
[Command("macardlevel")]
private void CommandLevel(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player)) return;
EntityAdapter adapter;
EntityController controller;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
var cardReader = adapter.Entity as CardReader;
if ((object)cardReader == null) {
ReplyToPlayer(player, LangEntry.ErrorNoSuitableAddonFound);
return;
}
int accessLevel;
if (args.Length < 1 || !int.TryParse(args[0], result : out accessLevel) || accessLevel < 1 || accessLevel > 3) {
ReplyToPlayer(player, LangEntry.CardReaderSetLevelSyntax, cmd);
return;
}
if (cardReader.accessLevel != accessLevel) {
adapter.EntityData.CardReaderLevel = (ushort)accessLevel;
_profileStore.Save(controller.Profile);
controller.StartHandleChangesRoutine();
}
ReplyToPlayer(player, LangEntry.CardReaderSetLevelSuccess, adapter.EntityData.CardReaderLevel);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("mapuzzle")]
private void CommandPuzzle(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player)) return;
if (args.Length == 0) {
SubCommandPuzzleHelp(player, cmd);
return;
}
switch (args[0].ToLower()) {
case "reset" : {
EntityAdapter adapter;
EntityController controller;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound)) return;
var ioEntity = adapter.Entity as IOEntity;
var puzzleReset = ioEntity != null ? FindConnectedPuzzleReset(ioEntity) : null;
if (puzzleReset == null) {
ReplyToPlayer(player, LangEntry.PuzzleNotConnected, _uniqueNameRegistry.GetUniqueShortName(adapter.Entity.PrefabName));
return;
}
puzzleReset.DoReset();
puzzleReset.ResetTimer();
ReplyToPlayer(player, LangEntry.PuzzleResetSuccess);
break;
}
case "add" : case "remove" : {
var isAdd = args[0] == "add";
if (args.Length < 2) {
ReplyToPlayer(player, isAdd ? LangEntry.PuzzleAddSpawnGroupSyntax : LangEntry.PuzzleRemoveSpawnGroupSyntax, cmd);
return;
}
EntityAdapter adapter;
EntityController controller;
PuzzleReset puzzleReset;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound) || !VerifyEntityComponent(player, adapter.Entity, out puzzleReset, LangEntry.PuzzleNotPresent)) return;
SpawnGroupController spawnGroupController;
if (!VerifySpawnGroupFound(player, args[1], adapter.Monument, out spawnGroupController)) return;
var spawnGroupData = spawnGroupController.SpawnGroupData;
var spawnGroupId = spawnGroupData.Id;
var puzzleData = controller.EntityData.EnsurePuzzleData(puzzleReset);
if (!isAdd) {
puzzleData.RemoveSpawnGroupId(spawnGroupId);
}
else if (!puzzleData.HasSpawnGroupId(spawnGroupId)) {
puzzleData.AddSpawnGroupId(spawnGroupId);
}
controller.StartHandleChangesRoutine();
_profileStore.Save(controller.Profile);
ReplyToPlayer(player, isAdd ? LangEntry.PuzzleAddSpawnGroupSuccess : LangEntry.PuzzleRemoveSpawnGroupSuccess, spawnGroupData.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer, immediate : false);
break;
}
case "set" : {
if (args.Length < 3) {
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player.Id, LangEntry.ErrorSetSyntaxGeneric, cmd));
sb.AppendLine(GetMessage(player.Id, LangEntry.PuzzleSetHelpMaxPlayersBlockReset));
sb.AppendLine(GetMessage(player.Id, LangEntry.PuzzleSetHelpPlayerDetectionRadius));
sb.AppendLine(GetMessage(player.Id, LangEntry.PuzzleSetHelpSecondsBetweenResets));
player.Reply(sb.ToString());
return;
}
PuzzleOption puzzleOption;
if (!VerifyValidEnumValue(player, args[1], out puzzleOption)) return;
EntityAdapter adapter;
EntityController controller;
PuzzleReset puzzleReset;
if (!VerifyLookingAtAdapter(player, out adapter, out controller, LangEntry.ErrorNoSuitableAddonFound) || !VerifyEntityComponent(player, adapter.Entity, out puzzleReset, LangEntry.PuzzleNotPresent)) return;
var puzzleData = controller.EntityData.EnsurePuzzleData(puzzleReset);
object setValue = args[2];
var showImmediate = true;
switch (puzzleOption) {
case PuzzleOption.PlayersBlockReset : {
bool playerBlockReset;
if (!VerifyValidBool(player, args[2], out playerBlockReset, LangEntry.ErrorSetSyntax, cmd, PuzzleOption.PlayersBlockReset)) return;
puzzleData.PlayersBlockReset = playerBlockReset;
setValue = playerBlockReset;
showImmediate = false;
break;
}
case PuzzleOption.PlayerDetectionRadius : {
float playerDetectionRadius;
if (!VerifyValidFloat(player, args[2], out playerDetectionRadius, LangEntry.ErrorSetSyntax, cmd, PuzzleOption.PlayerDetectionRadius)) return;
puzzleData.PlayersBlockReset = true;
puzzleData.PlayerDetectionRadius = playerDetectionRadius;
setValue = playerDetectionRadius;
break;
}
case PuzzleOption.SecondsBetweenResets : {
float secondsBetweenResets;
if (!VerifyValidFloat(player, args[2], out secondsBetweenResets, LangEntry.ErrorSetSyntax, cmd, PuzzleOption.SecondsBetweenResets)) return;
puzzleData.SecondsBetweenResets = secondsBetweenResets;
setValue = secondsBetweenResets;
break;
}
}
controller.StartHandleChangesRoutine();
_profileStore.Save(controller.Profile);
ReplyToPlayer(player, LangEntry.PuzzleSetSuccess, puzzleOption, setValue);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer, immediate : showImmediate);
break;
}
default : {
SubCommandPuzzleHelp(player, cmd);
break;
}
}
}
private void SubCommandPuzzleHelp(IPlayer player, string cmd) {
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player.Id, LangEntry.PuzzleHelpHeader, cmd));
sb.AppendLine(GetMessage(player.Id, LangEntry.PuzzleHelpReset, cmd));
sb.AppendLine(GetMessage(player.Id, LangEntry.PuzzleHelpSet, cmd));
sb.AppendLine(GetMessage(player.Id, LangEntry.PuzzleHelpAdd, cmd));
sb.AppendLine(GetMessage(player.Id, LangEntry.PuzzleHelpRemove, cmd));
player.Reply(sb.ToString());
}
[Command("maprofile")]
private void CommandProfile(IPlayer player, string cmd, string[] args) {
if (!_serverInitialized || !VerifyHasPermission(player) || !VerifyMonumentFinderLoaded(player)) return;
if (args.Length == 0) {
SubCommandProfileHelp(player);
return;
}
var basePlayer = player.Object as BasePlayer;
switch (args[0].ToLower()) {
case "list" : {
var profileList = ProfileInfo.GetList(_data, _profileManager);
if (profileList.Count == 0) {
ReplyToPlayer(player, LangEntry.ProfileListEmpty);
return;
}
var playerProfileName = player.IsServer ? null : _data.GetSelectedProfileName(player.Id);
profileList = profileList.OrderByDescending(profile => profile.Enabled && profile.Name == playerProfileName).ThenByDescending(profile => profile.Enabled).ThenBy(profile => profile.Name).ToList();
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player.Id, LangEntry.ProfileListHeader));
foreach (var profile in profileList) {
var messageName = profile.Enabled && profile.Name == playerProfileName ? LangEntry.ProfileListItemSelected : profile.Enabled ? LangEntry.ProfileListItemEnabled : LangEntry.ProfileListItemDisabled;
sb.AppendLine(GetMessage(player.Id, messageName, profile.Name, GetAuthorSuffix(player, profile.Profile?.Author)));
}
player.Reply(sb.ToString());
break;
}
case "describe" : {
ProfileController controller;
if (!VerifyProfile(player, args, out controller, LangEntry.ProfileDescribeSyntax)) return;
if (controller.Profile.IsEmpty()) {
ReplyToPlayer(player, LangEntry.ProfileEmpty, controller.Profile.Name);
return;
}
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player.Id, LangEntry.ProfileDescribeHeader, controller.Profile.Name));
AddProfileDescription(sb, player, controller);
player.Reply(sb.ToString());
if (!player.IsServer) {
_adapterDisplayManager.SetPlayerProfile(basePlayer, controller);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
break;
}
case "select" : {
if (player.IsServer) return;
if (args.Length <= 1) {
ReplyToPlayer(player, LangEntry.ProfileSelectSyntax);
return;
}
ProfileController cont
// ... [Code preview truncated due to length. Full source in attached file] ...