C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
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.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.Text;
using UnityEngine;
using static IOEntity;
using static WireTool;
using Component = UnityEngine.Component;
using HumanNPCGlobal = global : : HumanNPC;
using SkullTrophyGlobal = global : : SkullTrophy;
using CustomInitializeCallback = System.Func<BasePlayer, string[], object>;
using CustomInitializeCallbackV2 = System.Func<BasePlayer, string[], System.ValueTuple<bool, object>>;
using CustomEditCallback = System.Func<BasePlayer, string[], UnityEngine.Component, Newtonsoft.Json.Linq.JObject, System.ValueTuple<bool, object>>;
using CustomSpawnCallback = System.Func<UnityEngine.Vector3, UnityEngine.Quaternion, Newtonsoft.Json.Linq.JObject, UnityEngine.Component>;
using CustomSpawnCallbackV2 = System.Func<System.Guid, UnityEngine.Component, UnityEngine.Vector3, UnityEngine.Quaternion, Newtonsoft.Json.Linq.JObject, UnityEngine.Component>;
using CustomCheckSpaceCallback = System.Func<UnityEngine.Vector3, UnityEngine.Quaternion, Newtonsoft.Json.Linq.JObject, bool>;
using CustomKillCallback = System.Action<UnityEngine.Component>;
using CustomUnloadCallback = System.Action<UnityEngine.Component>;
using CustomUpdateCallback = System.Action<UnityEngine.Component, Newtonsoft.Json.Linq.JObject>;
using CustomUpdateCallbackV2 = System.Func<UnityEngine.Component, Newtonsoft.Json.Linq.JObject, UnityEngine.Component>;
using CustomDisplayCallback = System.Action<UnityEngine.Component, Newtonsoft.Json.Linq.JObject, System.Text.StringBuilder>;
using CustomDisplayCallbackV2 = System.Action<UnityEngine.Component, Newtonsoft.Json.Linq.JObject, BasePlayer, System.Text.StringBuilder, float>;
using CustomSetDataCallback = System.Action<UnityEngine.Component, object>;
using Tuple1 = System.ValueTuple<object>;
using Tuple2 = System.ValueTuple<object, object>;
using Tuple3 = System.ValueTuple<object, object, object>;
using Tuple4 = System.ValueTuple<object, object, object, object>;
namespace Oxide.Plugins {
[Info("Monument Addons", "WhiteThunder", "0.18.3")]
[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 CargoShipPrefab = "assets/content/vehicles/boats/cargoship/cargoshiptest.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 | Rust.Layers.Mask.Vehicle_World;
private static readonly Dictionary<string, string> JsonRequestHeaders = new Dictionary<string, string> {
{
"Content-Type", "application/json"
}
}
;
private static readonly Dictionary<string, string> OfficialProfileNames = new string[] {
"BarnAirwolf", "CargoShipCCTV", "FishingVillageAirwolf", "MonumentCooking", "MonumentLifts", "MonumentsRecycler", "OilRigSharks", "OutpostAirwolf", "OutpostExtended", "SafeZoneRecyclers", "TrainStationCCTV",
}
.ToDictionary(name => name, name => name, StringComparer.OrdinalIgnoreCase);
private readonly HookCollection _dynamicMonumentHooks;
private readonly ProfileStore _profileStore = new ProfileStore();
private readonly OriginalProfileStore _originalProfileStore = new OriginalProfileStore();
private readonly ProfileManager _profileManager;
private readonly CoroutineManager _coroutineManager = new CoroutineManager();
private readonly AddonComponentTracker _componentTracker = new AddonComponentTracker();
private readonly AdapterListenerManager _adapterListenerManager;
private readonly ControllerFactory _controllerFactory;
private readonly CustomAddonManager _customAddonManager;
private readonly CustomMonumentManager _customMonumentManager;
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 ValueRotator<Color> _colorRotator = new(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 True = true;
private readonly object False = false;
private ItemDefinition _waterDefinition;
private ProtectionProperties _immortalProtection;
private ActionDebounced _saveProfileStateDebounced;
private StringBuilder _sb = new StringBuilder();
private HashSet<string> _deployablePrefabs = new();
private BaseEntity[] _entityBuffer = new BaseEntity[32];
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);
_customMonumentManager = new CustomMonumentManager(this);
_controllerFactory = new ControllerFactory(this);
_monumentHelper = new MonumentHelper(this);
_wireToolManager = new WireToolManager(this, _profileStore);
_saveProfileStateDebounced = new ActionDebounced(timer, 1, () => {
if (!_isLoaded) return;
_profileStateData.Save();
}
);
_dynamicMonumentHooks = new HookCollection(this, new[] {
nameof(OnEntitySpawned)
}
, () => _profileManager.HasAnyEnabledDynamicMonuments );
}
#endregion
#region Hooks
private void Init() {
_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);
_dynamicMonumentHooks.Unsubscribe();
_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();
}
_deployablePrefabs = DetermineAllDeployablePrefabs();
_profileManager.ProfileStatusChanged += (_, _, _) => _dynamicMonumentHooks.Refresh();
_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);
_customMonumentManager.UnregisterAllForPlugin(plugin);
}
private void OnEntitySpawned(BaseEntity entity) {
if (!IsDynamicMonument(entity)) return;
var entityForClosure = entity;
NextTick(() => {
if (ExposedHooks.OnDynamicMonument(entityForClosure) is false) return;
var dynamicMonument = new DynamicMonument(entityForClosure);
_coroutineManager.StartCoroutine(_profileManager.PartialLoadForLateMonumentRoutine(dynamicMonument));
}
);
}
// This hook is exposed by plugin: Remover Tool (RemoverTool).private object canRemove(BasePlayer player, BaseEntity entity)
{
if (_componentTracker.IsAddonComponent(entity)) return False;
return null;
}
private object CanChangeGrade(BasePlayer player, BuildingBlock block, BuildingGrade.Enum grade) {
if (_componentTracker.IsAddonComponent(block) && !HasAdminPermission(player)) return False;
return null;
}
private object CanUpdateSign(BasePlayer player, ISignage signage) {
if (_componentTracker.IsAddonComponent(signage as BaseEntity) && !HasAdminPermission(player)) {
ChatMessage(player, LangEntry.ErrorNoPermission);
return False;
}
return null;
}
private void OnSignUpdated(ISignage signage, BasePlayer player) {
if (!_componentTracker.IsAddonComponent(signage as BaseEntity, out SignController controller)) 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)
{
if (!_componentTracker.IsAddonComponent(signage as BaseEntity, out SignController 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 (_componentTracker.IsAddonComponent(spray)) return False;
return null;
}
private void OnEntityScaled(BaseEntity entity, float scale) {
if (!_componentTracker.IsAddonComponent(entity, out EntityController controller) || controller.EntityData.Scale == scale) return;
controller.EntityData.Scale = scale;
controller.StartUpdateRoutine();
_profileStore.Save(controller.Profile);
}
// This hook is exposed by plugin: Telekinesis.private Component OnTelekinesisFindFailed(BasePlayer player)
{
if (!HasAdminPermission(player)) return null;
return FindAdapter<TransformAdapter>(player).Adapter?.Component;
}
// This hook is exposed by plugin: Telekinesis.private Tuple<Component, Component> OnTelekinesisStart(BasePlayer player, BaseEntity entity)
{
if (!_componentTracker.IsAddonComponent(entity, out PasteAdapter adapter, out PasteController _)) return null;
return new Tuple<Component, Component>(adapter.Component, adapter.Component);
}
// This hook is exposed by plugin: Telekinesis.private object CanStartTelekinesis(BasePlayer player, Component moveComponent, Component rotateComponent)
{
if (IsTransformAddon(moveComponent, out _) && !HasAdminPermission(player)) return False;
return null;
}
// This hook is exposed by plugin: Telekinesis.private void OnTelekinesisStarted(BasePlayer player, Component moveComponent, Component rotateComponent)
{
if (!IsTransformAddon(moveComponent, out var adapter, out var controller) || controller is not IUpdateableController) return;
if (adapter.Component == moveComponent || adapter.Component == rotateComponent) {
_adapterDisplayManager.SetPlayerMovingAdapter(player, adapter);
}
_adapterDisplayManager.ShowAllRepeatedly(player);
if (moveComponent is BaseEntity moveEntity && IsSpawnPointEntity(moveEntity)) {
_adapterDisplayManager.ShowAllRepeatedly(player);
var spawnedVehicleComponent = moveComponent.GetComponent<SpawnedVehicleComponent>();
if (spawnedVehicleComponent != null) {
spawnedVehicleComponent.CancelInvoke(spawnedVehicleComponent.CheckPositionTracked);
}
}
}
// This hook is exposed by plugin: Telekinesis.private void OnTelekinesisStopped(BasePlayer player, Component moveComponent, Component rotateComponent)
{
if (!IsTransformAddon(moveComponent, out TransformAdapter transformAdapter, out BaseController controller) || controller is not IUpdateableController updateableController) return;
if (player != null) {
_adapterDisplayManager.SetPlayerMovingAdapter(player, null);
}
if (!transformAdapter.TryRecordUpdates(moveComponent.transform, rotateComponent.transform)) return;
if (moveComponent is CustomSpawnPoint spawnPoint) {
spawnPoint.MoveSpawnedInstances();
}
updateableController.StartUpdateRoutine();
_profileStore.Save(controller.Profile);
if (player != null) {
_adapterDisplayManager.ShowAllRepeatedly(player);
ChatMessage(player, LangEntry.SaveSuccess, controller.Adapters.Count, controller.Profile.Name);
}
}
// This hook is exposed by plugin: Custom Vending Setup (CustomVendingSetup).private Dictionary<string, object> OnCustomVendingSetupDataProvider(NPCVendingMachine vendingMachine)
{
if (!_componentTracker.IsAddonComponent(vendingMachine, out EntityController 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) {
return EntityScaleManager?.Call("API_ScaleEntity", entity, scale) is true;
}
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 args = CopyPasteArgs;
if (pasteData.Args is {
Length : > 0
}
) {
args = args.Concat(pasteData.Args).ToArray();
}
var result = copyPaste.Call("TryPasteFromVector3Cancellable", position, yRotation, pasteData.Filename, args, 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(_componentTracker.IsAddonComponent(entity));
}
[HookMethod(nameof(API_GetMonumentEntityGuid))]
public object API_GetMonumentEntityGuid(BaseEntity entity) {
if (!_componentTracker.IsAddonComponent(entity)) return null;
var adapter = AddonComponent.GetForComponent(entity).Adapter;
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, Component monument, Guid guid) {
Interface.CallHook("OnMonumentEntitySpawned", entity, monument, ObjectCache.Get(guid));
}
public static void OnMonumentPrefabCreated(GameObject gameObject, Component monument, Guid guid) {
Interface.CallHook("OnMonumentPrefabCreated", gameObject, monument, ObjectCache.Get(guid));
}
public static object OnDynamicMonument(BaseEntity entity) {
return Interface.CallHook("OnDynamicMonument", entity);
}
}
#endregion
#region Commands
private enum PuzzleOption {
PlayersBlockReset, PlayerDetectionRadius, SecondsBetweenResets,
}
private enum SpawnGroupOption {
Name, Color, 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) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyMonumentFinderLoaded(player) || !VerifyProfileSelected(player, out var profileController) || !VerifyValidEntityPrefabOrDeployable(player, args, out var prefabName, out var addonDefinition, out var skinId) || !VerifyLookingAtMonumentPosition(player, out var position, out var monument)) return;
DetermineLocalTransformData(position, basePlayer, monument, out var localPosition, out var localRotationAngles, out var 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 = _config.AddonDefaults.Puzzles.ApplyTo(new PuzzleData());
}
addonData = entityData;
}
else {
// Found a custom addon definition.if (!addonDefinition.TryInitialize(basePlayer, args.Skip(1).ToArray(), out var pluginData))
return;
addonData = new CustomAddonData {
Id = Guid.NewGuid(), AddonName = addonDefinition.AddonName, Position = localPosition, RotationAngles = localRotationAngles, SnapToTerrain = isOnTerrain,
}
.SetData(pluginData);
}
var matchingMonuments = GetMonumentsByIdentifier(monument.UniqueName);
profileController.Profile.AddData(monument.UniqueName, addonData);
_profileStore.Save(profileController.Profile);
profileController.SpawnNewData(addonData, matchingMonuments);
ReplyToPlayer(player, LangEntry.SpawnSuccess, matchingMonuments.Count, profileController.Profile.Name, monument.UniqueDisplayName);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
if (addonData is not CustomAddonData && ShouldRecommendSpawnPoints(prefabName)) {
ReplyToPlayer(player, LangEntry.WarningRecommendSpawnPoint);
}
}
[Command("maedit")]
private void CommandEdit(IPlayer player, string cmd, string[] args) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out CustomAddonAdapter adapter, out CustomAddonController controller, LangEntry.ErrorNoCustomAddonFound)) return;
if (args.Length == 0) {
ReplyToPlayer(player, LangEntry.EditSynax);
return;
}
var addonDefinition = adapter.AddonDefinition;
var addonName = addonDefinition.AddonName;
if (addonName != args[0]) {
ReplyToPlayer(player, LangEntry.EditErrorNoMatch, args[0]);
return;
}
if (!addonDefinition.SupportsEditing) {
ReplyToPlayer(player, LangEntry.EditErrorNotEditable, addonName);
return;
}
if (!addonDefinition.TryEdit(basePlayer, args.Skip(1).ToArray(), adapter.Component, adapter.CustomAddonData.GetSerializedData(), out var data)) return;
addonDefinition.SetData(_profileStore, controller, data);
ReplyToPlayer(player, LangEntry.EditSuccess, addonName);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("maprefab")]
private void CommandPrefab(IPlayer player, string cmd, string[] args) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyMonumentFinderLoaded(player) || !VerifyProfileSelected(player, out var profileController) || !VerifyValidModderPrefab(player, args, out var prefabName) || !VerifyLookingAtMonumentPosition(player, out var position, out var monument)) return;
if (FindPrefabBaseEntity(prefabName) != null) {
ReplyToPlayer(player, LangEntry.PrefabErrorIsEntity, prefabName);
return;
}
DetermineLocalTransformData(position, basePlayer, monument, out var localPosition, out var localRotationAngles, out var isOnTerrain);
var prefabData = new PrefabData {
Id = Guid.NewGuid(), PrefabName = prefabName, Position = localPosition, RotationAngles = localRotationAngles, SnapToTerrain = isOnTerrain,
}
;
var matchingMonuments = GetMonumentsByIdentifier(monument.UniqueName);
profileController.Profile.AddData(monument.UniqueName, prefabData);
_profileStore.Save(profileController.Profile);
profileController.SpawnNewData(prefabData, matchingMonuments);
ReplyToPlayer(player, LangEntry.PrefabSuccess, matchingMonuments.Count, profileController.Profile.Name, monument.UniqueDisplayName);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("masave")]
private void CommandSave(IPlayer player, string cmd, string[] args) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController controller, LangEntry.ErrorNoSuitableAddonFound)) return;
if (!adapter.TryRecordUpdates()) {
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) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out TransformAdapter adapter, out BaseController controller, LangEntry.ErrorNoSuitableAddonFound)) return;
// Capture adapter count before killing the controller.var numAdapters = controller.Adapters.Count;
controller.Profile.RemoveData(adapter.Data, out var monumentIdentifier);
_dynamicMonumentHooks.Refresh();
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);
}
if (controller.Data is SpawnGroupData spawnGroupData && adapter.Data is SpawnPointData spawnPointData) {
_undoManager.AddUndo(basePlayer, new UndoKillSpawnPoint(this, profileController, monumentIdentifier, spawnGroupData, spawnPointData));
}
else {
_undoManager.AddUndo(basePlayer, new UndoKill(this, profileController, monumentIdentifier, 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) {
if (!VerifyPlayer(player, out var 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) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player)) return;
if (args.Length < 1 || !ComputerStation.IsValidIdentifier(args[0])) {
ReplyToPlayer(player, LangEntry.CCTVSetIdSyntax, cmd);
return;
}
if (!VerifyLookingAtAdapter(player, out CCTVController controller, LangEntry.ErrorNoSuitableAddonFound)) return;
controller.EntityData.CCTV ??= new CCTVInfo();
var hadIdentifier = !string.IsNullOrEmpty(controller.EntityData.CCTV.RCIdentifier);
controller.EntityData.CCTV.RCIdentifier = args[0];
_profileStore.Save(controller.Profile);
controller.StartUpdateRoutine();
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) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out CCTVEntityAdapter adapter, out CCTVController 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);
controller.EntityData.CCTV ??= new CCTVInfo();
controller.EntityData.CCTV.Pitch = lookAngles.x;
controller.EntityData.CCTV.Yaw = lookAngles.y;
_profileStore.Save(controller.Profile);
controller.StartUpdateRoutine();
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 (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController controller, LangEntry.ErrorNoSuitableAddonFound)) return;
var skullTrophy = adapter.Entity as SkullTrophyGlobal;
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.StartUpdateRoutine();
ReplyToPlayer(player, LangEntry.SkullNameSetSuccess, skullName, controller.Adapters.Count, controller.Profile.Name);
_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 (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController controller, LangEntry.ErrorNoSuitableAddonFound)) return;
var huntingTrophy = adapter.Entity as HuntingTrophy;
if (huntingTrophy == null) {
ReplyToPlayer(player, LangEntry.ErrorNoSuitableAddonFound);
return;
}
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.StartUpdateRoutine();
ReplyToPlayer(player, LangEntry.SetHeadSuccess, controller.Adapters.Count, controller.Profile.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("maskin")]
private void CommandSkin(IPlayer player, string cmd, string[] args) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController controller, LangEntry.ErrorNoSuitableAddonFound)) return;
if (args.Length == 0) {
ReplyToPlayer(player, LangEntry.SkinGet, adapter.Entity.skinID, cmd);
return;
}
if (!ulong.TryParse(args[0], out var skinId)) {
ReplyToPlayer(player, LangEntry.SkinSetSyntax, cmd);
return;
}
if (IsRedirectSkin(skinId, out var 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.StartUpdateRoutine();
ReplyToPlayer(player, LangEntry.SkinSetSuccess, skinId, controller.Adapters.Count, controller.Profile.Name);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer, immediate : !updatedExistingSkin);
}
[Command("maflag")]
private void CommandFlag(IPlayer player, string cmd, string[] args) {
if (!VerifyPlayer(player, out _) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController controller, LangEntry.ErrorNoSuitableAddonFound)) return;
if (args.Length == 0) {
var notAplicableMessage = GetMessage(player.Id, LangEntry.NotApplicable);
var currentFlags = adapter.Entity.flags == 0 ? notAplicableMessage : adapter.Entity.flags.ToString();
var enabledFlags = adapter.EntityData.EnabledFlags == 0 ? notAplicableMessage : adapter.EntityData.EnabledFlags.ToString();
var disabledFlags = adapter.EntityData.DisabledFlags == 0 ? notAplicableMessage : adapter.EntityData.DisabledFlags.ToString();
ReplyToPlayer(player, LangEntry.FlagsGet, currentFlags, enabledFlags, disabledFlags);
return;
}
if (!Enum.TryParse<BaseEntity.Flags>(args[0], ignoreCase : true, result : out var flag)) {
ReplyToPlayer(player, LangEntry.FlagsSetSyntax, cmd);
return;
}
var hasFlag = adapter.Entity.HasFlag(flag) ? true : adapter.EntityData.HasFlag(flag);
hasFlag = hasFlag switch {
true => false, false => null, null => true
}
;
adapter.EntityData.SetFlag(flag, hasFlag);
_profileStore.Save(controller.Profile);
controller.StartUpdateRoutine();
ReplyToPlayer(player, hasFlag switch {
true => LangEntry.FlagsEnableSuccess, false => LangEntry.FlagsDisableSuccess, null => LangEntry.FlagsUnsetSuccess
}
, flag);
}
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) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player) || !VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController controller, LangEntry.ErrorNoSuitableAddonFound)) return;
var cardReader = adapter.Entity as CardReader;
if ((object)cardReader == null) {
ReplyToPlayer(player, LangEntry.ErrorNoSuitableAddonFound);
return;
}
if (args.Length < 1 || !int.TryParse(args[0], result : out var 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.StartUpdateRoutine();
}
ReplyToPlayer(player, LangEntry.CardReaderSetLevelSuccess, adapter.EntityData.CardReaderLevel);
_adapterDisplayManager.ShowAllRepeatedly(basePlayer);
}
[Command("mapuzzle")]
private void CommandPuzzle(IPlayer player, string cmd, string[] args) {
if (!VerifyPlayer(player, out var basePlayer) || !VerifyHasPermission(player)) return;
if (args.Length == 0) {
SubCommandPuzzleHelp(player, cmd);
return;
}
var subCommandLower = args[0].ToLower();
switch (subCommandLower) {
case "reset" : {
if (!VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController _, 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 = subCommandLower == "add";
if (args.Length < 2) {
ReplyToPlayer(player, isAdd ? LangEntry.PuzzleAddSpawnGroupSyntax : LangEntry.PuzzleRemoveSpawnGroupSyntax, cmd);
return;
}
if (!VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController controller, LangEntry.ErrorNoSuitableAddonFound) || !VerifyEntityComponent(player, adapter.Entity, out PuzzleReset puzzleReset, LangEntry.PuzzleNotPresent)) return;
if (!VerifySpawnGroupFound(player, args[1], adapter.Monument, out var 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.StartUpdateRoutine();
_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) {
_sb.Clear();
_sb.AppendLine(GetMessage(player.Id, LangEntry.ErrorSetSyntaxGeneric, cmd, subCommandLower));
_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;
}
if (!VerifyValidEnumValue(player, args[1], out PuzzleOption puzzleOption)) return;
if (!VerifyLookingAtAdapter(player, out EntityAdapter adapter, out EntityController controller, LangEntry.ErrorNoSuitableAddonFound) || !VerifyEntityComponent(player, adapter.Entity, out PuzzleReset puzzleReset, LangEntry.PuzzleNotPresent)) return;
var puzzleData = controller.EntityData.EnsurePuzzleData(puzzleReset);
object setValue = args[2];
var showImmediate = true;
switch (puzzleOption) {
case PuzzleOption.PlayersBlockReset : {
if (!VerifyValidBool(player, args[2], out var playerBlockReset, LangEntry.ErrorSetSyntax.Bind(cmd, PuzzleOption.PlayersBlockReset))) return;
puzzleData.PlayersBlockReset = playerBlockReset;
setValue = playerBlockReset;
showImmediate = false;
break;
}
case PuzzleOption.PlayerDetectionRadius : {
if (!VerifyValidFloat(player, args[2], out var playerDetectionRadius, LangEntry.ErrorSetSyntax.Bind(cmd, PuzzleOption.PlayerDetectionRadius))) return;
puzzleData.PlayersBlockReset = true;
puzzleData.PlayerDetectionRadius = playerDetectionRadius;
setValue = playerDetectionRadius;
break;
}
case PuzzleOption.SecondsBetweenResets : {
if (!VerifyValidFloat(player, args[2], out var secondsBetweenResets, LangEntry.ErrorSetSyntax.Bind(cmd, PuzzleOption.SecondsBetweenResets))) return;
puzzleData.SecondsBetweenResets = secondsBetweenResets;
setValue = secondsBetweenResets;
break;
}
}
controller.StartUpdateRoutine();
_profileStore.Save(controller.Profile);
ReplyToPlayer(player, LangEntry.PuzzleSetSuccess, puzzleOption, se
// ... [Code preview truncated due to length. Full source in attached file] ...