C#:
//If debug is defined it will add a stopwatch to the paste and copydata which can be used to profile copying and pasting.
// #define DEBUG
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Libraries.Covalence;
using ProtoBuf;
using UnityEngine;
using Graphics = System.Drawing.Graphics;
using WrapMode = System.Drawing.Drawing2D.WrapMode;
#if DEBUG
using System.Diagnostics;
#endif
// ReSharper disable SpecifyACultureInStringConversionExplicitly
/*
* CREDITS
*
* Orange - Saving ContainerIOEntity
* UIP88 - Turrets fix
* bsdinis - Wire fix
* nivex - Ownership option, sign fix
* bmgjet - Wallpapers, pattern firework, industrial
* DezLife - CCTV fix
* Wulf - Skipping 4.1.24 :D
* MediocratyItself - added sizing/scaling saving feature
*
*/
namespace Oxide.Plugins {
[Info("Copy Paste", "misticos", "4.3.0")]
[Description("Copy and paste buildings to save them or move them")]
public class CopyPaste : CovalencePlugin {
// ReSharper disable once Unity.IncorrectMonoBehaviourInstantiation
private readonly Item _emptyItem = new Item {
info = new ItemDefinition()
}
;
private readonly IPlayer _consolePlayer = new RustConsolePlayer();
private int _copyLayer = LayerMask.GetMask("Construction", "Prevent Building", "Construction Trigger", "Trigger", "Deployed", "Default", "Ragdoll", "Vehicle Large"), _groundLayer = LayerMask.GetMask("Terrain", "Default"), _rayCopy = LayerMask.GetMask("Construction", "Deployed", "Tree", "Resource", "Prevent Building", "Vehicle Large"), _rayPaste = LayerMask.GetMask("Construction", "Deployed", "Tree", "Terrain", "World", "Water", "Prevent Building");
private string _copyPermission = "copypaste.copy", _listPermission = "copypaste.list", _pastePermission = "copypaste.paste", _pastebackPermission = "copypaste.pasteback", _undoPermission = "copypaste.undo", _subDirectory = "copypaste/";
private readonly HashSet<string> _dlcPrefabs = new();
private readonly HashSet<int> _dlcItemIds = new();
private readonly HashSet<ulong> _paidSkinIds = new();
private readonly Dictionary<string, ItemDefinition> _prefabToItemDef = new();
private readonly Dictionary<ItemDefinition, string> _itemDefToPrefab = new();
private readonly uint _floorFramePrefabId = StringPool.Get("assets/prefabs/building core/floor.frame/floor.frame.prefab");
private readonly uint _floorTriangleFramePrefabId = StringPool.Get("assets/prefabs/building core/floor.triangle.frame/floor.triangle.frame.prefab");
private const float LegacyElevatorLiftMaxHorizontalDistanceSqr = 1f;
private const float LegacyElevatorLiftVerticalTolerance = 0.5f;
private const float LegacyElevatorShaftMatchMaxHorizontalDistanceSqr = 1f;
private const float LegacyElevatorShaftMatchForwardDotMin = 0.98f;
private bool _pasteReady;
private readonly List<PasteData> _pendingPastes = new();
public class LastPaste {
public string Filename;
public List<BaseEntity> Entities = new();
}
private Dictionary<string, Stack<LastPaste>> _lastPastes = new();
private Dictionary<string, SignSize> _signSizes = new Dictionary<string, SignSize> {
{
"photoframe.landscape", new SignSize(320, 240)
}
, {
"photoframe.large", new SignSize(320, 240)
}
, {
"photoframe.portrait", new SignSize(320, 240)
}
, {
"sign.pictureframe.landscape", new SignSize(256, 192)
}
, {
"sign.pictureframe.tall", new SignSize(128, 512)
}
, {
"sign.pictureframe.portrait", new SignSize(205, 256)
}
, {
"sign.pictureframe.xxl", new SignSize(1024, 512)
}
, {
"sign.pictureframe.xl", new SignSize(512, 512)
}
, {
"sign.small.wood", new SignSize(256, 128)
}
, {
"sign.medium.wood", new SignSize(512, 256)
}
, {
"sign.large.wood", new SignSize(512, 256)
}
, {
"sign.huge.wood", new SignSize(1024, 256)
}
, {
"sign.hanging.banner.large", new SignSize(256, 1024)
}
, {
"sign.pole.banner.large", new SignSize(256, 1024)
}
, {
"sign.post.single", new SignSize(256, 128)
}
, {
"sign.post.double", new SignSize(512, 512)
}
, {
"sign.post.town", new SignSize(512, 256)
}
, {
"sign.post.town.roof", new SignSize(512, 256)
}
, {
"sign.hanging", new SignSize(256, 512)
}
, {
"sign.hanging.ornate", new SignSize(512, 256)
}
, {
"sign.neon.xl.animated", new SignSize(256, 256)
}
, {
"sign.neon.xl", new SignSize(256, 256)
}
, {
"sign.neon.125x215.animated", new SignSize(256, 128)
}
, {
"sign.neon.125x215", new SignSize(256, 128)
}
, {
"sign.neon.125x125", new SignSize(128, 128)
}
,
}
;
private HashSet<Type> ItemModAssociatedEntities = new() {
typeof(PaintedItemStorageEntity), typeof(PhotoEntity), typeof(SignContent), typeof(HeadEntity), typeof(PagerEntity), typeof(MobileInventoryEntity), typeof(Cassette)
}
;
private List<BaseEntity.Slot> _checkSlots = new List<BaseEntity.Slot> {
BaseEntity.Slot.Lock, BaseEntity.Slot.UpperModifier, BaseEntity.Slot.MiddleModifier, BaseEntity.Slot.LowerModifier
}
;
public enum CopyMechanics {
Building, Proximity
}
private class SignSize {
public int Width;
public int Height;
public SignSize(int width, int height) {
Width = width;
Height = height;
}
}
public enum SkinsMode {
NoSkins = 0, AllSkins = 1, NoPaidSkins = 2, AllowSpecifiedOnly = 3, BlockSpecifiedOnly = 4
}
public enum LoopMode {
TimedDelay = 1, PerFrame = 2, Instant = 3
}
public enum PasteErrorMode {
Stop = 1, UndoPasted = 2, Continue = 3
}
//Config
private ConfigData _config;
private class ConfigData {
[JsonProperty(PropertyName = "Copy Options", Order = 7)]
public CopyOptions Copy {
get;
set;
}
[JsonProperty(PropertyName = "Paste Options", Order = 8)]
public PasteOptions Paste {
get;
set;
}
[JsonProperty(PropertyName = "Amount of entities to paste per batch. Use to tweak performance impact of pasting", Order = 1)]
[DefaultValue(5)]
public int PasteBatchSize = 5;
[JsonProperty(PropertyName = "Amount of entities to copy per batch. Use to tweak performance impact of copying", Order = 2)]
[DefaultValue(100)]
public int CopyBatchSize = 100;
[JsonProperty(PropertyName = "Amount of entities to undo per batch. Use to tweak performance impact of undoing", Order = 3)]
[DefaultValue(5)]
public int UndoBatchSize = 5;
[JsonProperty(PropertyName = "Loop Execution", Order = 4)]
public LoopExecutionConfig LoopExecution {
get;
set;
}
= new();
[JsonProperty(PropertyName = "Prevent These Prefabs From Spawning", ObjectCreationHandling = ObjectCreationHandling.Replace, Order = 5), DefaultValue(typeof(List<string>), "")]
public List<string> BlockedPrefabs = new();
[JsonProperty(PropertyName = "Enable data saving feature", Order = 6)]
[DefaultValue(true)]
public bool DataSaving = true;
public class LoopExecutionConfig {
[JsonProperty(PropertyName = "Mode: (1=timed delay [safest], 2=per-frame [balanced], 3=instant [lag])", Order = 1)]
[DefaultValue((int)LoopMode.TimedDelay)]
public int Mode {
get;
set;
}
= (int)LoopMode.TimedDelay;
[JsonProperty(PropertyName = "Timed delay between paste batches, in seconds", Order = 2)]
[DefaultValue(0.075)]
public float PasteBatchDelay = 0.075f;
[JsonProperty(PropertyName = "Timed delay between copy batches, in seconds", Order = 3)]
[DefaultValue(0.075)]
public float CopyBatchDelay = 0.075f;
[JsonProperty(PropertyName = "Timed delay between undo batches, in seconds", Order = 4)]
[DefaultValue(0.075)]
public float UndoBatchDelay = 0.075f;
}
public class CopyOptions {
[JsonProperty(PropertyName = "Check radius from each entity (true/false)")]
[DefaultValue(true)]
public bool EachToEach {
get;
set;
}
= true;
[JsonProperty(PropertyName = "Share (true/false)")]
[DefaultValue(false)]
public bool Share {
get;
set;
}
= false;
[JsonProperty(PropertyName = "Tree (true/false)")]
[DefaultValue(false)]
public bool Tree {
get;
set;
}
= false;
[JsonProperty(PropertyName = "Default radius to look for entities from block")]
[DefaultValue(3.0f)]
public float Radius {
get;
set;
}
= 3.0f;
}
public class PasteOptions {
[JsonProperty(PropertyName = "Auth (true/false)")]
[DefaultValue(false)]
public bool Auth {
get;
set;
}
= false;
[JsonProperty(PropertyName = "Deployables (true/false)")]
[DefaultValue(true)]
public bool Deployables {
get;
set;
}
= true;
[JsonProperty(PropertyName = "Inventories (true/false)")]
[DefaultValue(true)]
public bool Inventories {
get;
set;
}
= true;
[JsonProperty(PropertyName = "Vending Machines (true/false)")]
[DefaultValue(true)]
public bool VendingMachines {
get;
set;
}
= true;
[JsonProperty(PropertyName = "Stability (true/false)")]
[DefaultValue(true)]
public bool Stability {
get;
set;
}
= true;
[JsonProperty(PropertyName = "EntityOwner (true/false)")]
[DefaultValue(true)]
public bool EntityOwner {
get;
set;
}
= true;
[JsonProperty(PropertyName = "DLC items and deployables (true/false)")]
[DefaultValue(true)]
public bool Dlc {
get;
set;
}
= true;
[JsonProperty(PropertyName = "Skins (0=no skins, 1=all, 2=no paid skins, 3=allow specified only, 4=block specified only)")]
[DefaultValue((int)CopyPaste.SkinsMode.AllSkins)]
public int SkinsMode {
get;
set;
}
= (int)CopyPaste.SkinsMode.AllSkins;
[JsonProperty(PropertyName = "When an entity fails to paste: (1=stop, 2=undo pasted, 3=continue)")]
[DefaultValue((int)CopyPaste.PasteErrorMode.Stop)]
public int PasteErrorMode {
get;
set;
}
= (int)CopyPaste.PasteErrorMode.Stop;
[JsonProperty(PropertyName = "Specified Skins (skin id, like 2601577757, or item shortname for redirected skins, like hazmatsuit.spacesuit)")]
public List<object> SpecifiedSkins {
get;
set;
}
= new();
[JsonIgnore]
public List<ulong> SpecifiedSkinIds = new();
[JsonIgnore]
public List<string> SpecifiedSkinRedirects = new();
}
}
private void LoadVariables() {
Config.Settings.DefaultValueHandling = DefaultValueHandling.Populate;
_config = Config.ReadObject<ConfigData>();
_config.Copy ??= new();
_config.Paste ??= new();
_config.LoopExecution ??= new();
_config.BlockedPrefabs ??= new();
_config.Paste.SpecifiedSkins ??= new();
if (!IsValidSkinsMode(_config.Paste.SkinsMode)) {
PrintWarning("Invalid config value specified for 'Skins', resetting to default of 1 (all skins)");
_config.Paste.SkinsMode = (int)SkinsMode.AllSkins;
}
if (!IsValidLoopMode(_config.LoopExecution.Mode)) {
PrintWarning("Invalid config value specified for 'Loop Execution Mode', resetting to default of 1 (timed delay)");
_config.LoopExecution.Mode = (int)LoopMode.TimedDelay;
}
if (_config.LoopExecution.Mode == (int)LoopMode.Instant) PrintWarning("Loop execution mode is set to instant. Large operations can lag the server and may kick players.");
if (!IsValidPasteErrorMode(_config.Paste.PasteErrorMode)) {
PrintWarning("Invalid config value specified for 'When an entity fails to paste', resetting to default of 1 (stop)");
_config.Paste.PasteErrorMode = (int)PasteErrorMode.Stop;
}
for (var i = 0; i < _config.Paste.SpecifiedSkins.Count; i++) {
var str = _config.Paste.SpecifiedSkins[i].ToString();
if (string.IsNullOrEmpty(str)) continue;
if (UInt64.TryParse(str, out var skinid)) _config.Paste.SpecifiedSkinIds.Add(skinid);
else if (ItemManager.FindItemDefinition(str) != null) _config.Paste.SpecifiedSkinRedirects.Add(str);
else PrintWarning($"Ignoring invalid item shortname in 'Specified Skins': {str}");
}
Config.WriteObject(_config, true);
}
protected override void LoadDefaultConfig() {
var configData = new ConfigData {
Copy = new ConfigData.CopyOptions(), Paste = new ConfigData.PasteOptions(), LoopExecution = new ConfigData.LoopExecutionConfig()
}
;
Config.WriteObject(configData, true);
}
private bool IsValidSkinsMode(int mode) => Enum.IsDefined(typeof(SkinsMode), mode);
private bool IsValidLoopMode(int opt) => Enum.IsDefined(typeof(LoopMode), opt);
private bool IsValidPasteErrorMode(int mode) => Enum.IsDefined(typeof(PasteErrorMode), mode);
//Hooks
private void Init() {
permission.RegisterPermission(_copyPermission, this);
permission.RegisterPermission(_listPermission, this);
permission.RegisterPermission(_pastePermission, this);
permission.RegisterPermission(_pastebackPermission, this);
permission.RegisterPermission(_undoPermission, this);
var compiledLangs = new Dictionary<string, Dictionary<string, string>>();
foreach (var line in _messages) {
foreach (var translate in line.Value) {
if (!compiledLangs.ContainsKey(translate.Key)) compiledLangs[translate.Key] = new Dictionary<string, string>();
compiledLangs[translate.Key][line.Key] = translate.Value;
}
}
foreach (var cLangs in compiledLangs) {
lang.RegisterMessages(cLangs.Value, this, cLangs.Key);
}
LoadVariables();
Vis.colBuffer = new Collider[8192 * 16];
JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}
;
ProcessItemDefinitions();
}
// Heavily influenced by k1lly0u's Player DLC API plugin
private void ProcessItemDefinitions() {
if ((Steamworks.SteamInventory.Definitions?.Length ?? 0) == 0) {
timer.In(3f, ProcessItemDefinitions);
return;
}
const string WORKSHOP_ID = "workshopid";
foreach (Steamworks.InventoryDef inventoryDef in Steamworks.SteamInventory.Definitions) {
if (ulong.TryParse(inventoryDef.GetProperty(WORKSHOP_ID), out ulong skinId)) _paidSkinIds.Add(skinId);
}
for (int i = 0; i < ItemSkinDirectory.Instance.skins.Length; i++) {
ItemSkinDirectory.Skin skin = ItemSkinDirectory.Instance.skins[i];
_paidSkinIds.Add((ulong)skin.id);
}
foreach (ItemDefinition itemDef in ItemManager.itemList) {
string prefabPath = GetPrefabPathFromItemDef(itemDef);
if (!string.IsNullOrEmpty(prefabPath)) {
_prefabToItemDef[prefabPath] = itemDef;
_itemDefToPrefab[itemDef] = prefabPath;
}
if (IsDlcItem(itemDef)) {
_dlcItemIds.Add(itemDef.itemid);
if (!string.IsNullOrEmpty(prefabPath)) _dlcPrefabs.Add(prefabPath);
}
}
Puts($"Skin detection initialized: {_paidSkinIds.Count} official skins, {_dlcItemIds.Count} DLC items. Processing {_pendingPastes.Count} queued paste(s).");
_pasteReady = true;
for (int i = 0; i < _pendingPastes.Count; i++) {
var pasteData = _pendingPastes[i];
timer.Once(i * 0.1f, () => {
pasteData.StartLoop(PasteLoop(pasteData));
}
);
}
_pendingPastes.Clear();
}
private string GetPrefabPathFromItemDef(ItemDefinition def) {
if (def.TryGetComponent<ItemModDeployable>(out var deployable)) return deployable.entityPrefab.resourcePath;
if (def.TryGetComponent<ItemModEntity>(out var entity)) return entity.entityPrefab.resourcePath;
if (def.TryGetComponent<ItemModEntityReference>(out var entityRef)) return entityRef.entityPrefab.resourcePath;
return null;
}
public static bool IsDlcItem(ItemDefinition definition) {
var parent = definition.Parent;
return (definition.steamItem != null && definition.steamItem.id != 0) || (definition.steamDlc != null && definition.steamDlc.dlcAppID != 0) || (definition.Blueprint != null && definition.steamDlc != null) || (parent != null && parent.Blueprint != null && parent.steamDlc != null) || definition.isRedirectOf != null;
}
public ulong FilterSkinId(PasteData pasteData, ulong skinId) {
if (skinId == 0) return skinId;
return pasteData.SkinsMode switch {
SkinsMode.NoSkins => 0, SkinsMode.NoPaidSkins when _paidSkinIds.Contains(skinId) => 0, SkinsMode.AllowSpecifiedOnly when !_config.Paste.SpecifiedSkinIds.Contains(skinId) => 0, SkinsMode.BlockSpecifiedOnly when _config.Paste.SpecifiedSkinIds.Contains(skinId) => 0, _ => skinId
}
;
}
public bool GetItemDefinitionForPrefab(string prefabPath, out ItemDefinition def, bool useRedirect = true) {
def = null;
if (!_prefabToItemDef.TryGetValue(prefabPath, out def)) return false;
if (useRedirect && def?.isRedirectOf != null) def = def.isRedirectOf;
return def != null;
}
private bool ShouldRedirectForSkinsMode(PasteData pasteData, ItemDefinition itemDef) {
var useRedirect = pasteData.SkinsMode == SkinsMode.NoSkins || pasteData.SkinsMode == SkinsMode.NoPaidSkins;
if (pasteData.SkinsMode == SkinsMode.AllowSpecifiedOnly || pasteData.SkinsMode == SkinsMode.BlockSpecifiedOnly) {
bool isInSpecifiedList = _config.Paste.SpecifiedSkinRedirects.Contains(itemDef.shortname);
if (pasteData.SkinsMode == SkinsMode.AllowSpecifiedOnly && !isInSpecifiedList) useRedirect = true;
else if (pasteData.SkinsMode == SkinsMode.BlockSpecifiedOnly && isInSpecifiedList) useRedirect = true;
}
return useRedirect;
}
#region API
private bool IsPasteReady() => _pasteReady;
private object TryCopyFromSteamId(ulong userId, string filename, string[] args, Action callback = null) {
var player = players.FindPlayerById(userId.ToString())?.Object as BasePlayer;
if (player == null) return Lang("NOT_FOUND_PLAYER");
RaycastHit hit;
if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 1000f, _rayCopy)) return Lang("NO_ENTITY_RAY", player.UserIDString);
var entity = hit.GetEntity();
if (!entity.IsValid()) return Lang("NO_ENTITY_RAY", player.UserIDString);
return TryCopy(hit.point, entity.GetNetworkRotation().eulerAngles, filename, DegreeToRadian(player.GetNetworkRotation().eulerAngles.y), args, player.IPlayer, callback);
}
private object TryPasteFromSteamId(ulong userId, string filename, string[] args, Action callback = null, Action<BaseEntity> callbackSpawned = null) {
var player = players.FindPlayerById(userId.ToString())?.Object as BasePlayer;
if (player == null) return Lang("NOT_FOUND_PLAYER");
RaycastHit hit;
if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 1000f, _rayPaste)) return Lang("NO_ENTITY_RAY", player.UserIDString);
return TryPaste(hit.point, filename, player.IPlayer, DegreeToRadian(player.GetNetworkRotation().eulerAngles.y), args, callback : callback, callbackSpawned : callbackSpawned).Item1;
}
private object TryPasteFromVector3(Vector3 pos, float rotationCorrection, string filename, string[] args, Action callback = null, Action<BaseEntity> callbackSpawned = null, int loopMode = 0) {
return TryPaste(pos, filename, _consolePlayer, rotationCorrection, args, callback : callback, callbackSpawned : callbackSpawned, loopMode : loopMode).Item1;
}
private ValueTuple<object, Action> TryPasteFromVector3Cancellable(Vector3 pos, float rotationCorrection, string filename, string[] args, Action callback = null, Action<BaseEntity> callbackSpawned = null, int loopMode = 0) {
var result = TryPaste(pos, filename, _consolePlayer, rotationCorrection, args, callback : callback, callbackSpawned : callbackSpawned, loopMode : loopMode);
var pasteData = result.Item2;
return new ValueTuple<object, Action>(result.Item1, () => pasteData.Cancelled = true);
}
#endregion
//Other methods
private object CheckCollision(HashSet<Dictionary<string, object>> entities, Vector3 startPos, float radius) {
foreach (var entityobj in entities) {
if (Physics.CheckSphere((Vector3)entityobj["position"], radius, _copyLayer)) return Lang("BLOCKING_PASTE");
}
return true;
}
private bool CheckPlaced(string prefabname, Vector3 pos, Quaternion rot) {
const float maxDiff = 0.01f;
var ents = Pool.Get<List<BaseEntity>>();
try {
Vis.Entities(pos, maxDiff, ents);
foreach (var ent in ents) {
if (ent.IsDestroyed || ent.PrefabName != prefabname) continue;
if (Vector3.Distance(ent.transform.position, pos) > maxDiff) {
continue;
}
if (Vector3.Distance(ent.transform.rotation.eulerAngles, rot.eulerAngles) > maxDiff) {
continue;
}
return true;
}
return false;
}
finally {
Pool.FreeUnmanaged(ref ents);
}
}
private void RemoveEntity(BaseEntity entity) {
if (!entity.IsValid() || entity.IsDestroyed) return;
// Cleanup the hotspot beloning to the node.
var ore = entity as OreResourceEntity;
if (ore != null) {
ore.CleanupBonus();
}
var io = entity as IOEntity;
if (io != null) {
try {
io.ClearConnections();
}
catch {
}
// this can throw
}
var autoTurret = entity as AutoTurret;
if (autoTurret != null) {
AutoTurret.interferenceUpdateList.Remove(autoTurret);
}
entity.Kill();
}
private IEnumerator UndoLoop(UndoData undoData) {
var entities = undoData.EntitiesToUndo;
var player = undoData.Player;
for (var i = entities.Count - 1; i >= 0; i--) {
var baseEntity = entities.ElementAt(i);
if (baseEntity is IItemContainerEntity) {
entities.Remove(baseEntity);
// Don't .Kill() the drone storage as it'll cause Drone.Update_Server() to NRE
if (baseEntity is DroneStorage droneStorage && droneStorage.inventory != null) {
droneStorage.inventory.Clear();
continue;
}
RemoveEntity(baseEntity);
}
}
int entityIndex = 0;
// Take an amount of entities from the entity list (defined in config) and kill them. Will be repeated for every tick until there are no entities left.
foreach (var p in entities) {
RemoveEntity(p);
if (++entityIndex % _config.UndoBatchSize == 0 && undoData.TryGetBatchYield(_config.LoopExecution.UndoBatchDelay, out var batchYield)) yield return batchYield;
}
if (player != null) {
player.Reply(Lang("UNDO_SUCCESS", player.Id) + ": " + undoData.Filename);
if (_lastPastes.TryGetValue(player.Id, out var checkFrom) && checkFrom.Count == 0) _lastPastes.Remove(player.Id);
}
}
private void Copy(Vector3 sourcePos, Vector3 sourceRot, string filename, float rotationCorrection, CopyMechanics copyMechanics, float range, bool saveTree, bool saveShare, bool eachToEach, IPlayer player, Action callback) {
var currentLayer = _copyLayer;
if (saveTree) currentLayer |= LayerMask.GetMask("Tree");
var copyData = new CopyData {
Filename = filename, CurrentLayer = currentLayer, RotCor = rotationCorrection, Range = range, SaveShare = saveShare, SaveTree = saveTree, CopyMechanics = copyMechanics, EachToEach = eachToEach, SourcePos = sourcePos, SourceRot = sourceRot, Player = player, BasePlayer = player.Object as BasePlayer, Callback = callback, ExecutionMode = (LoopMode)_config.LoopExecution.Mode
}
;
copyData.CheckFrom.Push(sourcePos);
copyData.StartLoop(CopyLoop(copyData));
}
// Main loop for copy, will fetch all the data needed. Is called every tick untill copy is done (can't find any entities)
private IEnumerator CopyLoop(CopyData copyData) {
var checkFrom = copyData.CheckFrom;
var houseList = copyData.HouseList;
var buildingId = copyData.BuildingId;
var copyMechanics = copyData.CopyMechanics;
var range = copyData.Range;
while (checkFrom.Count > 0) {
var batchSize = checkFrom.Count < _config.CopyBatchSize ? checkFrom.Count : _config.CopyBatchSize;
for (var i = 0; i < batchSize; i++) {
if (checkFrom.Count == 0) break;
var list = Pool.Get<List<BaseEntity>>();
try {
Vis.Entities(checkFrom.Pop(), range, list, copyData.CurrentLayer);
foreach (var entity in list) {
// Skip entities that are already in the list
if (!entity.IsValid() || entity.HasParent()) continue;
// Skip metal detector flags
if (entity.GetComponent<MetalDetectorSource>() != null) continue;
if (!houseList.Add(entity)) continue;
var buildingBlock = entity as BuildingBlock;
if (copyMechanics == CopyMechanics.Building) {
buildingBlock ??= entity.GetComponentInParent<BuildingBlock>();
if (buildingBlock != null) {
if (buildingId == 0) buildingId = buildingBlock.buildingID;
if (buildingId != buildingBlock.buildingID) continue;
}
}
if (buildingBlock != null) QueueConnectedBlocks(copyData, buildingBlock);
var transform = entity.transform;
if (copyData.EachToEach && copyData.ScannedPositions.Add(transform.position)) checkFrom.Push(transform.position);
if (entity.GetComponent<BaseLock>() != null) continue;
copyData.RawData.Add(EntityData(entity, transform.position, transform.rotation.eulerAngles / Mathf.Rad2Deg, copyData));
}
}
finally {
Pool.FreeUnmanaged(ref list);
}
copyData.BuildingId = buildingId;
}
if (checkFrom.Count > 0 && copyData.TryGetBatchYield(_config.LoopExecution.CopyBatchDelay, out var batchYield)) yield return batchYield;
}
var path = _subDirectory + copyData.Filename;
var datafile = Interface.Oxide.DataFileSystem.GetFile(path);
datafile.Clear();
var sourcePos = copyData.SourcePos;
datafile["default"] = new Dictionary<string, object> {
{
"position", new Dictionary<string, object> {
{
"x", sourcePos.x.ToString()
}
, {
"y", sourcePos.y.ToString()
}
, {
"z", sourcePos.z.ToString()
}
}
}
, {
"rotationy", copyData.SourceRot.y.ToString()
}
, {
"rotationdiff", copyData.RotCor.ToString()
}
}
;
datafile["entities"] = copyData.RawData;
datafile["protocol"] = new Dictionary<string, object> {
{
"items", 2
}
, {
"version", Version
}
}
;
Interface.Oxide.DataFileSystem.SaveDatafile(path);
copyData.Player.Reply(Lang("COPY_SUCCESS", copyData.Player.Id, copyData.Filename));
copyData.Callback?.Invoke();
Interface.CallHook("OnCopyFinished", copyData.RawData, copyData.Filename, copyData.Player, copyData.SourcePos);
}
private void QueueConnectedBlocks(CopyData copyData, BuildingBlock block) {
var checkFrom = copyData.CheckFrom;
var scannedPositions = copyData.ScannedPositions;
var houseList = copyData.HouseList;
var links = block.GetEntityLinks();
if (links == null || links.Count == 0) return;
for (var i = 0; i < links.Count; i++) {
var link = links[i];
if (link == null) continue;
var connections = link.connections;
if (connections == null) continue;
for (var j = 0; j < connections.Count; j++) {
var neighbor = connections[j].owner as BuildingBlock;
if (neighbor != null && neighbor.IsValid() && !houseList.Contains(neighbor) && scannedPositions.Add(neighbor.transform.position)) checkFrom.Push(neighbor.transform.position);
}
}
}
private float DegreeToRadian(float angle) {
return (float)(Math.PI * angle / 180.0f);
}
private Dictionary<string, object> EntityData(BaseEntity entity, Vector3 entPos, Vector3 entRot, CopyData copyData) {
var normalizedPos = NormalizePosition(copyData.SourcePos, entPos, copyData.RotCor);
var isChild = entity.HasParent();
entRot.y -= copyData.RotCor;
var data = new Dictionary<string, object> {
{
"prefabname", entity.PrefabName
}
, {
"skinid", entity.skinID
}
, {
"flags", TryCopyFlags(entity)
}
, {
"pos", new Dictionary<string, object> {
{
"x", isChild ? entity.transform.localPosition.x.ToString() : normalizedPos.x.ToString()
}
, {
"y", isChild ? entity.transform.localPosition.y.ToString() : normalizedPos.y.ToString()
}
, {
"z", isChild ? entity.transform.localPosition.z.ToString() : normalizedPos.z.ToString()
}
}
}
, {
"rot", new Dictionary<string, object> {
{
"x", isChild ? entity.transform.localRotation.eulerAngles.x.ToString() : entRot.x.ToString()
}
, {
"y", isChild ? entity.transform.localRotation.eulerAngles.y.ToString() : entRot.y.ToString()
}
, {
"z", isChild ? entity.transform.localRotation.eulerAngles.z.ToString() : entRot.z.ToString()
}
}
}
, {
"ownerid", entity.OwnerID
}
}
;
if (entity.networkEntityScale) {
var scale = entity.transform.localScale;
data.Add("scale", new Dictionary<string, object> {
{
"x", scale.x.ToString()
}
, {
"y", scale.y.ToString()
}
, {
"z", scale.z.ToString()
}
}
);
}
if (entity.HasParent()) {
if (entity.parentBone != 0) {
data.Add("parentbone", StringPool.Get(entity.parentBone));
}
if (GetSlot(entity.GetParentEntity(), entity, out BaseEntity.Slot? theslot) && theslot != null) {
data.Add("slot", (int)theslot);
}
}
if (entity.children != null && entity.children.Count > 0) {
var children = new List<object>();
foreach (var child in entity.children) {
if (!child.IsValid() || child is BasePlayer) continue;
children.Add(EntityData(child, child.transform.position, child.transform.rotation.eulerAngles, copyData));
}
if(children.Count > 0 ) data.Add("children", children);
}
var growableEntity = entity as GrowableEntity;
if (growableEntity != null) {
var genes = GrowableGeneEncoding.EncodeGenesToInt(growableEntity.Genes);
if (genes > 0) data.Add("genes", genes);
var previousGenes = GrowableGeneEncoding.EncodePreviousGenesToInt(growableEntity.Genes);
if (previousGenes > 0) data.Add("previousGenes", previousGenes);
data.Add("state", growableEntity.State);
data.Add("totalAge", growableEntity.Age);
data.Add("stageAge", growableEntity.stageAge);
data.Add("yieldFraction", growableEntity.Yield);
data.Add("yieldPool", growableEntity.yieldPool);
data.Add("fertilized", growableEntity.Fertilized);
var perent = growableEntity.GetParentEntity();
if (perent != null) {
data.Add("hasParent", true);
}
}
// TryCopySlots(entity, data, copyData.SaveShare);
var codeLock = entity.GetComponent<CodeLock>();
if (codeLock != null) {
data.Add("code", codeLock.code);
if (copyData.SaveShare) data.Add("whitelistPlayers", codeLock.whitelistPlayers);
if (codeLock.guestCode != null && codeLock.guestCode.Length == 4) {
data.Add("guestCode", codeLock.guestCode);
if (copyData.SaveShare) data.Add("guestPlayers", codeLock.guestPlayers);
}
}
var keyLock = entity.GetComponent<KeyLock>();
if (keyLock != null) {
data.Add("code", keyLock.keyCode.ToString());
data.Add("firstKeyCreated", keyLock.firstKeyCreated);
}
var buildingblock = entity as BuildingBlock;
if (buildingblock != null) {
data.Add("grade", buildingblock.grade);
if (buildingblock.customColour != 0) {
data.Add("customColour", buildingblock.customColour);
}
if (buildingblock.HasWallpaper(0)) {
data.Add("wallpaperID", buildingblock.wallpaperID);
data.Add("wallpaperHealth", buildingblock.wallpaperHealth);
data.Add("wallpaperRotation", buildingblock.wallpaperRotation);
}
if (buildingblock.HasWallpaper(1)) {
data.Add("wallpaperID2", buildingblock.wallpaperID2);
data.Add("wallpaperHealth2", buildingblock.wallpaperHealth2);
data.Add("wallpaperRotation2", buildingblock.wallpaperRotation2);
}
}
var container = entity as IItemContainerEntity;
if (container != null && container.inventory != null) {
ExtractInventory(data, container.inventory, copyData);
}
var iSignage = entity as ISignage;
if (iSignage != null) {
ExtractTextures(data, iSignage.GetTextureCRCs(), entity, iSignage.FileType);
}
var photoEntity = entity as PhotoEntity;
if (photoEntity != null) {
ExtractTextures(data, photoEntity.GetContentCRCs, entity, FileStorage.Type.jpg);
}
var baseSculpture = entity as BaseSculpture;
if (baseSculpture != null && baseSculpture.crc != 0) {
var sculptureData = FileStorage.server.Get(baseSculpture.crc, FileStorage.Type.sculpt, entity.net.ID);
if (sculptureData != null && sculptureData.Length != 0) data.Add("sculpture", Convert.ToBase64String(sculptureData));
}
var photoFrame = entity as PhotoFrame;
if (photoFrame != null && photoFrame._photoEntity.uid.IsValid) {
data.Add("photoEntity", photoFrame._photoEntity.uid.Value);
}
var signContent = entity as SignContent;
if (signContent != null) {
ExtractTextures(data, signContent.GetContentCRCs, entity, signContent.FileType);
}
var paintedItemStorageEntity = entity as PaintedItemStorageEntity;
if (paintedItemStorageEntity != null) {
ExtractTextures(data, paintedItemStorageEntity.GetContentCRCs, entity, FileStorage.Type.png);
}
var shutterFrame = entity as ShutterFrame;
if (shutterFrame != null) data.Add("isShutterOpen", shutterFrame.IsShutterOpen);
var ornateFrame = entity as OrnateFrame;
if (ornateFrame != null) {
data.Add("frameText", ornateFrame.FrameText);
data.Add("textColour", SerializeUnityEngineColor(ornateFrame.TextColour));
}
var lights = entity as ChristmasLights;
if (lights != null) {
data.Add("animationStyle", lights.animationStyle);
}
var stringLights = entity as StringLights;
if (stringLights != null) {
var lightsPointsList = new List<Dictionary<string, object>>();
foreach (var pointEntry in stringLights.points) {
lightsPointsList.Add(new Dictionary<string, object> {
{
"normal", pointEntry.normal
}
, {
"point", NormalizePosition(copyData.SourcePos, pointEntry.point, copyData.RotCor)
}
, {
"slack", pointEntry.slack
}
,
}
);
}
data.Add("points", lightsPointsList);
}
var chandelier = entity as Chandelier;
if (chandelier != null) {
data.Add("chandelierLength", chandelier.ChandelierLength);
}
var orientableLight = entity as OrientableLight;
if (orientableLight != null) {
data.Add("pitchAmount", orientableLight.pitchAmount);
data.Add("yawAmount", orientableLight.yawAmount);
}
var mannequin = entity as Mannequin;
if (mannequin != null) {
data.Add("poseIndex", mannequin.PoseIndex);
}
var partyBalloon = entity as PartyBalloon;
if (partyBalloon != null) {
data.Add("balloonText", partyBalloon.BalloonText);
data.Add("balloonColour", SerializeUnityEngineColor(partyBalloon.BalloonColour));
data.Add("textColour", SerializeUnityEngineColor(partyBalloon.TextColour));
}
var playerBoat = entity as PlayerBoat;
if (playerBoat != null) {
data.Add("lastEditLocalPos", new Dictionary<string, object> {
{
"x", playerBoat.lastEditLocalPos.x.ToString()
}
, {
"y", playerBoat.lastEditLocalPos.y.ToString()
}
, {
"z", playerBoat.lastEditLocalPos.z.ToString()
}
}
);
data.Add("lastEditLocalRot", new Dictionary<string, object> {
{
"x", playerBoat.lastEditLocalRot.x.ToString()
}
, {
"y", playerBoat.lastEditLocalRot.y.ToString()
}
, {
"z", playerBoat.lastEditLocalRot.z.ToString()
}
}
);
}
if (copyData.SaveShare) {
var sleepingBag = entity as SleepingBag;
if (sleepingBag != null) {
data.Add("sleepingbag", new Dictionary<string, object> {
{
"niceName", sleepingBag.niceName
}
, {
"deployerUserID", sleepingBag.deployerUserID
}
, {
"isPublic", sleepingBag.IsPublic()
}
}
);
}
var cupboard = entity as BuildingPrivlidge;
if (cupboard != null) {
data.Add("cupboard", new Dictionary<string, object> {
{
"authorizedPlayers", cupboard.authorizedPlayers.ToList()
}
}
);
}
var autoTurret = entity as AutoTurret;
if (autoTurret != null) {
data.Add("autoturret", new Dictionary<string, object> {
{
"authorizedPlayers", autoTurret.authorizedPlayers.ToList()
}
}
);
}
var steeringWheel = entity as SteeringWheel;
if (steeringWheel != null && steeringWheel.Privilege != null) {
var dict = new Dictionary<string, object> {
{
// ... [Code preview truncated due to length. Full source in attached file] ...