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.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;
#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
*
*/
namespace Oxide.Plugins {
[Info("Copy Paste", "misticos", "4.2.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"), _groundLayer = LayerMask.GetMask("Terrain", "Default"), _rayCopy = LayerMask.GetMask("Construction", "Deployed", "Tree", "Resource", "Prevent Building"), _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 Dictionary<string, Stack<List<BaseEntity>>> _lastPastes = new Dictionary<string, Stack<List<BaseEntity>>>();
private Dictionary<string, SignSize> _signSizes = new Dictionary<string, SignSize> {
//{"spinner.wheel.deployed", new SignSize(512, 512)},
{
"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;
}
}
//Config
private ConfigData _config;
private class ConfigData {
[JsonProperty(PropertyName = "Copy Options")]
public CopyOptions Copy {
get;
set;
}
[JsonProperty(PropertyName = "Paste Options")]
public PasteOptions Paste {
get;
set;
}
[JsonProperty(PropertyName = "Amount of entities to paste per batch.Use to tweak performance impact of pasting")]
[DefaultValue(15)]
public int PasteBatchSize = 15;
[JsonProperty(PropertyName = "Amount of entities to copy per batch.Use to tweak performance impact of copying")]
[DefaultValue(100)]
public int CopyBatchSize = 100;
[JsonProperty(PropertyName = "Amount of entities to undo per batch.Use to tweak performance impact of undoing")]
[DefaultValue(15)]
public int UndoBatchSize = 15;
[JsonProperty(PropertyName = "Enable data saving feature")]
[DefaultValue(true)]
public bool DataSaving = true;
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;
}
}
private void LoadVariables() {
Config.Settings.DefaultValueHandling = DefaultValueHandling.Populate;
_config = Config.ReadObject<ConfigData>();
Config.WriteObject(_config, true);
}
protected override void LoadDefaultConfig() {
var configData = new ConfigData {
Copy = new ConfigData.CopyOptions(), Paste = new ConfigData.PasteOptions()
}
;
Config.WriteObject(configData, true);
}
//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);
}
}
private void OnServerInitialized() {
LoadVariables();
Vis.colBuffer = new Collider[8192 * 16];
JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}
;
}
#region API
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) {
return TryPaste(pos, filename, _consolePlayer, rotationCorrection, args, callback : callback, callbackSpawned : callbackSpawned).Item1;
}
private ValueTuple<object, Action> TryPasteFromVector3Cancellable(Vector3 pos, float rotationCorrection, string filename, string[] args, Action callback = null, Action<BaseEntity> callbackSpawned = null) {
var result = TryPaste(pos, filename, _consolePlayer, rotationCorrection, args, callback : callback, callbackSpawned : callbackSpawned);
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 UndoLoop(HashSet<BaseEntity> entities, IPlayer player, int count = 0) {
foreach (var storageContainer in entities.OfType<StorageContainer>().Where(x => !x.IsDestroyed)) {
storageContainer.Kill();
}
// 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.entities.Take(_config.UndoBatchSize).ToList().ForEach(p =>
{
entities.Remove(p);
// Cleanup the hotspot beloning to the node.var ore = p as OreResourceEntity;
if (ore != null) {
ore.CleanupBonus();
}
var io = p as IOEntity;
if (io != null) {
io.ClearConnections();
}
var autoTurret = p as AutoTurret;
if (autoTurret != null) {
AutoTurret.interferenceUpdateList.Remove(autoTurret);
}
if (p != null && !p.IsDestroyed) p.Kill();
}
);
// If it gets stuck in infinite loop break the loop.if (count != 0 && entities.Count != 0 && entities.Count == count)
{
player?.Reply("Undo cancelled because of infinite loop.");
return;
}
if (entities.Count > 0) NextTick(() => UndoLoop(entities, player, entities.Count));
else if (player != null) {
player.Reply(Lang("UNDO_SUCCESS", player.Id));
if (_lastPastes.ContainsKey(player.Id) && _lastPastes[player.Id].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
}
;
copyData.CheckFrom.Push(sourcePos);
NextTick(() => 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 void CopyLoop(CopyData copyData) {
var checkFrom = copyData.CheckFrom;
var houseList = copyData.HouseList;
var buildingId = copyData.BuildingId;
var copyMechanics = copyData.CopyMechanics;
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(), copyData.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;
if (copyMechanics == CopyMechanics.Building) {
var buildingBlock = entity.GetComponentInParent<BuildingBlock>();
if (buildingBlock != null) {
if (buildingId == 0) buildingId = buildingBlock.buildingID;
if (buildingId != buildingBlock.buildingID) continue;
}
}
var transform = entity.transform;
if (copyData.EachToEach) 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) {
NextTick(() => CopyLoop(copyData));
}
else {
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 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.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()) 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 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()) {
data.Add("wallpaperID", buildingblock.wallpaperID);
data.Add("wallpaperHealth", buildingblock.wallpaperHealth);
}
}
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 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 lights = entity as AdvancedChristmasLights;
if (lights != null) {
var lightsPointsList = new List<Dictionary<string, object>>();
foreach (var lightsPoint in lights.points) {
lightsPointsList.Add(new Dictionary<string, object> {
{
"normal", lightsPoint.normal
}
, {
"point", NormalizePosition(copyData.SourcePos, lightsPoint.point, copyData.RotCor)
}
, {
"slack", lightsPoint.slack
}
,
}
);
}
data.Add("points", lightsPointsList);
data.Add("animationStyle", lights.animationStyle);
}
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.Select(y => y.userid).ToList()
}
}
);
}
var autoTurret = entity as AutoTurret;
if (autoTurret != null) {
data.Add("autoturret", new Dictionary<string, object> {
{
"authorizedPlayers", autoTurret.authorizedPlayers.Select(p => p.userid).ToList()
}
}
);
}
}
var firework = entity as PatternFirework;
if (firework != null && firework?.Design != null && firework?.Design?.stars != null) {
data.Add("patternfirework", new Dictionary<string, object> {
{
"editedBy", firework.Design.editedBy
}
, {
"stars", SerializeStarPattern(firework.Design.stars)
}
,
}
);
}
var tinCanAlarm = entity as TinCanAlarm;
if (tinCanAlarm != null) {
if (tinCanAlarm.endPoint != Vector3.zero) {
data.Add("endPoint", NormalizePosition(copyData.SourcePos, tinCanAlarm.endPoint, copyData.RotCor));
}
}
var cctvRc = entity as CCTV_RC;
if (cctvRc != null) {
data.Add("cctv", new Dictionary<string, object> {
{
"yaw", cctvRc.yawAmount
}
, {
"pitch", cctvRc.pitchAmount
}
, {
"rcIdentifier", cctvRc.rcIdentifier
}
}
);
}
var headEntity = entity as HeadEntity;
if (headEntity != null) {
ExtractHeadData(data, headEntity.CurrentTrophyData);
}
var huntingTrophy = entity as HuntingTrophy;
if (huntingTrophy != null) {
ExtractHeadData(data, huntingTrophy.CurrentTrophyData);
}
// Save RF frequencies for non-IOEntity RF objects (IOEntity frequencies are handled separately)
var rfObject = entity as IRFObject;
if (rfObject != null && entity is not IOEntity) {
var frequency = rfObject.GetFrequency();
if (frequency > 0) data.Add("frequency", frequency);
}
var cassette = entity as Cassette;
if (cassette != null) {
ExtractCassette(data, cassette);
}
var elevator = entity as Elevator;
if (elevator != null) {
data.Add("Floor", elevator.Floor);
}
var weaponRack = entity as WeaponRack;
if (weaponRack != null) {
var gridSlots = new List<object>();
foreach (var weaponRackGridSlot in weaponRack.gridSlots) {
if (weaponRackGridSlot != null && weaponRackGridSlot.Used) {
gridSlots.Add(new Dictionary<string, object> {
{
"GridSlotIndex", weaponRackGridSlot.GridSlotIndex
}
, {
"InventoryIndex", weaponRackGridSlot.InventoryIndex
}
, {
"Rotation", weaponRackGridSlot.Rotation
}
,
}
);
}
}
data.Add("gridSlots", gridSlots);
}
var phoneController = entity.GetComponent<PhoneController>();
if (phoneController != null && phoneController.savedVoicemail != null && phoneController.savedVoicemail.Count > 0) {
var savedVoicemail = new List<object>();
foreach (var voicemail in phoneController.savedVoicemail) {
var oggByte = FileStorage.server.Get(voicemail.audioId, FileStorage.Type.ogg, entity.net.ID);
if (oggByte != null) {
savedVoicemail.Add(new Dictionary<string, object> {
{
"audio", Convert.ToBase64String(oggByte)
}
, {
"userName", voicemail.userName
}
}
);
}
}
data.Add("savedVoicemail", savedVoicemail);
}
var vendingMachine = entity as VendingMachine;
if (vendingMachine != null) {
var sellOrders = new List<object>();
foreach (var vendItem in vendingMachine.sellOrders.sellOrders) {
sellOrders.Add(new Dictionary<string, object> {
{
"itemToSellID", vendItem.itemToSellID
}
, {
"itemToSellAmount", vendItem.itemToSellAmount
}
, {
"currencyID", vendItem.currencyID
}
, {
"currencyAmountPerItem", vendItem.currencyAmountPerItem
}
, {
"inStock", vendItem.inStock
}
, {
"currencyIsBP", vendItem.currencyIsBP
}
, {
"itemToSellIsBP", vendItem.itemToSellIsBP
}
}
);
}
data.Add("vendingmachine", new Dictionary<string, object> {
{
"shopName", vendingMachine.shopName
}
, {
"isBroadcasting", vendingMachine.IsBroadcasting()
}
, {
"sellOrders", sellOrders
}
}
);
}
var ioEntity = entity as IOEntity;
if (ioEntity.IsValid() && !ioEntity.IsDestroyed) {
var ioData = new Dictionary<string, object>();
var inputs = ioEntity.inputs.Select(input => new Dictionary<string, object> {
{
"connectedID", input.connectedTo.entityRef.uid.Value
}
, {
"connectedToSlot", input.connectedToSlot
}
, {
"niceName", input.niceName
}
, {
"wireColour", input.wireColour
}
, {
"type", (int)input.type
}
}
).Cast<object>().ToList();
ioData.Add("inputs", inputs);
var outputs = new List<object>();
foreach (var output in ioEntity.outputs) {
var ioConnection = new Dictionary<string, object> {
{
"connectedID", output.connectedTo.entityRef.uid.Value
}
, {
"connectedToSlot", output.connectedToSlot
}
, {
"niceName", output.niceName
}
, {
"wireColour", output.wireColour
}
, {
"type", (int)output.type
}
, {
"linePoints", output.linePoints?.ToList() ?? new List<Vector3>()
}
, {
"lineAnchors", output.lineAnchors != null ? GetLineAnchors(output.lineAnchors, ioEntity) : new List<object>()
}
, {
"slackLevels", output.slackLevels?.ToList() ?? new List<float>()
}
,
}
;
outputs.Add(ioConnection);
}
ioData.Add("outputs", outputs);
ioData.Add("oldID", ioEntity.net.ID.Value);
var electricalBranch = ioEntity as ElectricalBranch;
if (electricalBranch != null) {
ioData.Add("branchAmount", electricalBranch.branchAmount);
}
var counter = ioEntity as PowerCounter;
if (counter != null) {
ioData.Add("targetNumber", counter.GetTarget());
ioData.Add("counterNumber", counter.counterNumber);
}
var timerSwitch = ioEntity as TimerSwitch;
if (timerSwitch != null) {
ioData.Add("timerLength", timerSwitch.timerLength);
}
var rfBroadcaster = ioEntity as IRFObject;
if (rfBroadcaster != null) {
ioData.Add("frequency", rfBroadcaster.GetFrequency());
}
var seismicSensor = ioEntity as SeismicSensor;
if (seismicSensor != null) {
ioData.Add("range", seismicSensor.range);
}
var conveyor = ioEntity as IndustrialConveyor;
if (conveyor != null) {
ioData.Add("industrialconveyormode", (int)conveyor.mode);
ioData.Add("industrialconveyorfilteritems", SerializeConveyorFilter(conveyor.filterItems));
}
var digitalClock = ioEntity as DigitalClock;
if (digitalClock != null) {
var alarms = new List<object>();
foreach (var alarm in digitalClock.alarms) {
alarms.Add(new Dictionary<string, object> {
{
"time", alarm.time
}
, {
"active", alarm.active
}
,
}
);
}
ioData.Add("muted", digitalClock.muted);
ioData.Add("alarms", alarms);
}
data.Add("IOEntity", ioData);
}
if (entity is StorageContainer || entity is Door || (isChild && entity is not IOEntity)) {
data.Add("oldID", entity.net.ID.Value);
}
return data;
}
private List<object> GetLineAnchors(IOEntity.LineAnchor[] lineAnchors, IOEntity ioEntity) {
var anchors = new List<object>();
foreach (var anchor in lineAnchors) {
anchors.Add(new Dictionary<string, object> {
{
"position", new Dictionary<string, object> {
{
"x", anchor.position.x.ToString()
}
, {
"y", anchor.position.y.ToString()
}
, {
"z", anchor.position.z.ToString()
}
}
}
, {
"index", anchor.index
}
, {
"boneName", anchor.boneName
}
, {
"entityRefID", anchor.entityRef.uid.Value
}
}
);
}
return anchors;
}
private object FindBestHeight(HashSet<Dictionary<string, object>> entities, Vector3 startPos) {
var maxHeight = 0f;
foreach (var entity in entities) {
var prefab = (string)entity["prefabname"];
if (prefab.Contains("/foundation/") || prefab.Contains("/foundation.triangle/")) {
var foundHeight = GetGround((Vector3)entity["position"]);
if (foundHeight != null) {
var height = (Vector3)foundHeight;
if (height.y > maxHeight) maxHeight = height.y;
}
}
}
maxHeight += 1f;
return maxHeight;
}
private bool FindRayEntity(Vector3 sourcePos, Vector3 sourceDir, out Vector3 point, out BaseEntity entity, int rayLayer) {
RaycastHit hitinfo;
entity = null;
point = Vector3.zero;
if (!Physics.Raycast(sourcePos, sourceDir, out hitinfo, 1000f, rayLayer)) return false;
entity = hitinfo.GetEntity();
point = hitinfo.point;
return true;
}
private byte[] FixSignage(ISignage iSignage, byte[] imageBytes) {
var sign = iSignage as Signage;
if (sign == null || !_signSizes.ContainsKey(sign.ShortPrefabName)) return imageBytes;
var size = Math.Max(sign.paintableSources.Length, 1);
if (sign.textureIDs == null || sign.textureIDs.Length != size) {
Array.Resize(ref sign.textureIDs, size);
}
return ImageResize(imageBytes, _signSizes[sign.ShortPrefabName].Width, _signSizes[sign.ShortPrefabName].Height);
}
private object GetGround(Vector3 pos) {
RaycastHit hitInfo;
pos += new Vector3(0, 100, 0);
if (Physics.Raycast(pos, Vector3.down, out hitInfo, 200, _groundLayer)) return hitInfo.point;
return null;
}
private int GetItemId(int itemId) {
if (ReplaceItemId.ContainsKey(itemId)) return ReplaceItemId[itemId];
return itemId;
}
private bool HasAccess(IPlayer player, string permName) {
return player.IsAdmin || player.HasPermission(permName);
}
private byte[] ImageResize(byte[] imageBytes, int width, int height) {
Bitmap resizedImage = new Bitmap(width, height), sourceImage = new Bitmap(new MemoryStream(imageBytes));
Graphics.FromImage(resizedImage).DrawImage(sourceImage, new Rectangle(0, 0, width, height), new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), GraphicsUnit.Pixel);
var ms = new MemoryStream();
resizedImage.Save(ms, ImageFormat.Png);
return ms.ToArray();
}
private string Lang(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);
private Vector3 NormalizePosition(Vector3 initialPos, Vector3 currentPos, float diffRot) {
var transformedPos = currentPos - initialPos;
var newX = transformedPos.x * (float)Math.Cos(-diffRot) + transformedPos.z * (float)Math.Sin(-diffRot);
var newZ = transformedPos.z * (float)Math.Cos(-diffRot) - transformedPos.x * (float)Math.Sin(-diffRot);
transformedPos.x = newX;
transformedPos.z = newZ;
return transformedPos;
}
private PasteData Paste(ICollection<Dictionary<string, object>> entities, Dictionary<string, object> protocol, bool ownership, Vector3 startPos, IPlayer player, bool stability, float rotationCorrection, float heightAdj, bool auth, Action callback, Action<BaseEntity> callbackSpawned, string filename, bool checkPlaced, bool enableSaving = true) {
//Settings
var isItemReplace = !protocol.ContainsKey("items");
var eulerRotation = new Vector3(0f, rotationCorrection * Mathf.Rad2Deg, 0f);
var quaternionRotation = Quaternion.Euler(eulerRotation);
// Parse VersionNumber
var version = protocol.ContainsKey("version") ? protocol["version"] as Dictionary<string, object> : null;
VersionNumber vNumber = default;
if (version != null) vNumber = new VersionNumber((int)version["Major"], (int)version["Minor"], (int)version["Patch"]);
var pasteData = new PasteData {
HeightAdj = heightAdj, IsItemReplace = isItemReplace, Entities = entities, Player = player, BasePlayer = player.Object as BasePlayer, QuaternionRotation = quaternionRotation, StartPos = startPos, Stability = stability, Auth = auth, Ownership = ownership, CallbackFinished = callback, CallbackSpawned = callbackSpawned, Filename = filename, CheckPlaced = checkPlaced, Version = vNumber, EnableSaving = enableSaving
}
;
NextTick(() => PasteLoop(pasteData));
return pasteData;
}
private void PasteLoop(PasteData pasteData) {
if (pasteData.Cancelled) {
UndoLoop(new HashSet<BaseEntity>(pasteData.PastedEntities), pasteData.Player, pasteData.PastedEntities.Count);
return;
}
var entities = pasteData.Entities;
var todo = entities.Take(_config.PasteBatchSize).ToArray();
foreach (var data in todo) {
entities.Remove(data);
PasteEntity(data, pasteData);
}
if (entities.Count > 0) NextTick(() => PasteLoop(pasteData));
else {
foreach (var ioData in pasteData.EntityLookup.Values.ToArray()) {
ProgressIOEntity(ioData, pasteData);
}
foreach (var
// ... [Code preview truncated due to length. Full source in attached file] ...