SignTool/SignTool.cs

1369 lines
55 KiB
C#

using UnityEngine;
using System.Collections.Generic;
using CompanionServer.Handlers;
using System.Drawing.Imaging;
using Graphics = System.Drawing.Graphics;
using ProtoBuf;
using System;
using System.Drawing;
using System.IO;
using System.Text;
using System.Linq;
using Oxide.Core;
using System.Collections;
using UnityEngine.Networking;
using Color = System.Drawing.Color;
using Oxide.Core.Plugins;
namespace Oxide.Plugins
{
[Info("SignTool", "bmgjet", "1.0.6")]
[Description("SignTool, Insert Images, Skins,Scale into map file directly, Then reload them on server startup.")]
//XML Data LayOut for Image Data
//<? xml version="1.0"?>
//<SerializedImageData>
// <position>
// <x>0</x>
// <y>0</y>
// <z>0</z>
// </position>
// <texture>Base64 Image Bytes<frame>X<frame>.....</texture>
//</SerializedImageData>
//XML Data LayOut for Image Data
//<? xml version="1.0"?>
//<SerializedSkinData>
// <position>
// <x>0</x>
// <y>0</y>
// <z>0</z>
// </position>
// <skin>uint</skin>
//</SerializedSkinData>
public class SignTool : RustPlugin
{
//Debug Output
bool showDebug = false;
//Temp List Of Things Scales Applied Too.
List<BaseEntity> ScaledEntitys = new List<BaseEntity>();
List<BaseEntity> Protected = new List<BaseEntity>();
//List Of Server Signs Found
Dictionary<Signage, Vector3> ServerSigns = new Dictionary<Signage, Vector3>();
//List Of Server Skinnable prefabs Found
Dictionary<BaseEntity, Vector3> ServerSkinnables = new Dictionary<BaseEntity, Vector3>();
//List of server RE Scaleable Prefabs
Dictionary<BaseEntity, Vector3> ServerScalable = new Dictionary<BaseEntity, Vector3>();
//IDs of types of signs
uint[] signids = { 1447270506, 4057957010, 120534793, 58270319, 4290170446, 3188315846, 3215377795, 1960724311, 3159642196, 3725754530, 1957158128, 637495597, 1283107100, 4006597758, 3715545584, 3479792512, 3618197174, 550204242};
//IDs of prefabs that are skinnable
uint[] skinnableids = { 1844023509, 177343599, 3994459244, 4196580066, 3110378351, 2206646561, 2931042549, 159326486, 2245774897, 1560881570, 3647679950, 170207918, 202293038, 1343928398, 43442943, 201071098, 1418678061, 2662124780, 2057881102, 2335812770, 2905007296, 34236153 };
//Deployables in RE to check scale of
uint[] ScaleableRE = { 34236153, 184980835, 4094102585, 4111973013 };
//Neon sign Ids
uint[] Neons = { 708840119, 3591916872, 3919686896, 2628005754, 3168507223 };
/*
//Paintable Signs
sign.small.wood.prefab
sign.post.town.roof.prefab
sign.post.town.prefab
sign.post.single.prefab
sign.post.double.prefab
sign.pole.banner.large.prefab
sign.pictureframe.landscape.prefab
sign.pictureframe.portrait.prefab
sign.pictureframe.tall.prefab
sign.pictureframe.xxl.prefab
sign.pictureframe.xl.prefab
sign.hanging.banner.large.prefab
sign.hanging.ornate.prefab
spinner.wheel.deployed.prefab
sign.medium.wood.prefab
sign.large.wood.prefab
sign.huge.wood.prefab
sign.hanging.prefab
//Neon Patched
sign.neon.xl.prefab
sign.neon.xl.animated.prefab
sign.neon.125x215.animated.prefab
sign.neon.125x215.prefab
sign.neon.125x125.prefab
//Skinnable Items
fridge.deployed.prefab
locker.deployed.prefab
reactivetarget_deployed.prefab
rug.deployed.prefab
rug.bear.deployed.prefab
box.wooden.large.prefab
woodbox_deployed.prefab
furnace.prefab
sleepingbag_leather_deployed.prefab
npcvendingmachine.prefab
wall.frame.garagedoor.prefab
door.hinged.toptier.prefab
door.hinged.metal.prefab
door.hinged.wood.prefab
door.double.hinged.wood.prefab
door.double.hinged.toptier.prefab
door.double.hinged.metal.prefab
table.deployed.prefab
barricade.concrete.prefab
barricade.sandbags.prefab
waterpurifier.deployed.prefab
//ScaleableRE
sliding_blast_door.prefab
door.hinged.security.blue.prefab
door.hinged.security.green.prefab
door.hinged.security.red.prefab
*/
//Admin Permission
const string PermMap = "SignTool.admin";
//Sign Data Extracted from MapData
Dictionary<Vector3, List<byte[]>> SignData = new Dictionary<Vector3, List<byte[]>>();
//Skin Data Extracted from MapData
Dictionary<Vector3, uint> SkinData = new Dictionary<Vector3, uint>();
//Sign Sizes (Thanks to SignArtists code)
private Dictionary<string, SignSize> _signSizes = new Dictionary<string, SignSize>
{
{"spinner.wheel.deployed", new SignSize(64, 64)},
{"sign.pictureframe.landscape", new SignSize(256, 128)},
{"sign.pictureframe.tall", new SignSize(128, 512)},
{"sign.pictureframe.portrait", new SignSize(128, 256)},
{"sign.pictureframe.xxl", new SignSize(1024, 512)},
{"sign.pictureframe.xl", new SignSize(512, 512)},
{"sign.small.wood", new SignSize(128, 64)},
{"sign.medium.wood", new SignSize(256, 128)},
{"sign.large.wood", new SignSize(256, 128)},
{"sign.huge.wood", new SignSize(512, 128)},
{"sign.hanging.banner.large", new SignSize(64, 256)},
{"sign.pole.banner.large", new SignSize(64, 256)},
{"sign.post.single", new SignSize(128, 64)},
{"sign.post.double", new SignSize(256, 256)},
{"sign.post.town", new SignSize(256, 128)},
{"sign.post.town.roof", new SignSize(256, 128)},
{"sign.hanging", new SignSize(128, 256)},
{"sign.hanging.ornate", new SignSize(256, 128)},
{"sign.neon.xl.animated", new SignSize(250, 250)},
{"sign.neon.xl", new SignSize(250, 250)},
{"sign.neon.125x215.animated", new SignSize(215, 125)},
{"sign.neon.125x215", new SignSize(215, 125)},
{"sign.neon.125x125", new SignSize(125, 125)},
};
public String Blanked = "iVBORw0KGgoAAAANSUhEUgAAANcAAAB9CAYAAAAx+vY9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB/SURBVHhe7cGBAAAAAMOg+VNf4QBVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8aqR4AAFsKyZjAAAAAElFTkSuQmCC";
private readonly Queue<DownloadRequest> downloadQueue = new Queue<DownloadRequest>();
public interface IBasePaintableEntity
{
BaseEntity Entity { get; }
string PrefabName { get; }
string ShortPrefabName { get; }
uint NetId { get; }
void SendNetworkUpdate();
}
public interface IPaintableEntity : IBasePaintableEntity
{
void SetImage(uint id, int frameid);
bool CanUpdate(BasePlayer player);
uint TextureId();
}
public class BasePaintableEntity : IBasePaintableEntity
{
public BaseEntity Entity { get; }
public string PrefabName { get; }
public string ShortPrefabName { get; }
public uint NetId { get; }
protected BasePaintableEntity(BaseEntity entity)
{
Entity = entity;
PrefabName = Entity.PrefabName;
ShortPrefabName = Entity.ShortPrefabName;
NetId = Entity.net.ID;
}
public void SendNetworkUpdate()
{
Entity.SendNetworkUpdate();
}
}
private class PaintableSignage : BasePaintableEntity, IPaintableEntity
{
public Signage Sign { get; set; }
public PaintableSignage(Signage sign) : base(sign)
{
Sign = sign;
}
public void SetImage(uint id, int frameid)
{
Sign.textureIDs[frameid] = id;
}
public bool CanUpdate(BasePlayer player)
{
return Sign.CanUpdateSign(player);
}
public uint TextureId()
{
return Sign.textureIDs.First();
}
}
private class PaintableFrame : BasePaintableEntity, IPaintableEntity
{
public PhotoFrame Sign { get; set; }
public PaintableFrame(PhotoFrame sign) : base(sign)
{
Sign = sign;
}
public void SetImage(uint id, int frameid)
{
Sign._overlayTextureCrc = id;
}
public bool CanUpdate(BasePlayer player)
{
return Sign.CanUpdateSign(player);
}
public uint TextureId()
{
return Sign._overlayTextureCrc;
}
}
private class SignSize
{
public int Width;
public int Height;
public int ImageWidth;
public int ImageHeight;
public SignSize(int width, int height)
{
Width = width;
Height = height;
ImageWidth = width;
ImageHeight = height;
}
}
private void Init()
{
//Setup Permission
permission.RegisterPermission(PermMap, this);
}
private void OnWorldPrefabSpawned(GameObject gameObject, string str)
{
//Fix Neons Loading by removing them and storing while server loads.
BaseEntity component = gameObject.GetComponent<BaseEntity>();
if (component != null)
{
if ((component.prefabID == 708840119 || component.prefabID == 3591916872 || component.prefabID == 3919686896 || component.prefabID == 2628005754 || component.prefabID == 3168507223) && component.OwnerID == 0)
{
//Kill all the Neons that server Created.
component.Kill();
}
}
}
bool isSign(PrefabData sign)
{
//Checks prefab has a valid sign id
return (signids.Contains(sign.id) || Neons.Contains(sign.id));
}
bool isSkinnable(PrefabData skinid)
{
//Checks prefab has a valid skinnable id
return (skinnableids.Contains(skinid.id));
}
bool isScaleable(PrefabData scale)
{
//Checks prefab has a valid skinnable id
return (ScaleableRE.Contains(scale.id));
}
public string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
object OnEntityKill(BaseNetworkable entity)
{
//Protects items from being destroyed.
if (Protected.Contains(entity)) return true;
return null;
}
[PluginReference]
Plugin EntityScaleManager;
public void Rescale()
{
//Checks if plugin is installed
if (EntityScaleManager == null)
{
Puts(@"Scaling Disabled get plugin https://umod.org/plugins/entity-scale-manager");
return;
}
int Scaled = 0;
if (ServerSigns.Count != 0)
{
//Apply Scale Data to Found Signs
foreach (KeyValuePair<Signage, Vector3> ss in ServerSigns)
{
if (showDebug) Puts("Found Scaled Prefab @ " + ss.Key.transform.position + " : " + ss.Value.z.ToString());
foreach (KeyValuePair<Vector3, List<byte[]>> sd in SignData)
{
if (Vector3.Distance(sd.Key, ss.Key.transform.position) < 0.2)
{
//Scale
RemoveSphere(ss.Key);
if (doScale(ss.Key as BaseEntity, ss.Value.z))
{
ScaledEntitys.Add(ss.Key as BaseEntity);
Scaled++;
if (showDebug) Puts("Scaled to " + ss.Value.z.ToString());
}
}
}
}
}
if (ServerSkinnables.Count != 0)
{
//Apply Scale Data to Found Skinnables
foreach (KeyValuePair<BaseEntity, Vector3> ss in ServerSkinnables)
{
if (showDebug) Puts("Found Scaled Prefab @ " + ss.Key.transform.position + " : " + ss.Value.z.ToString());
foreach (KeyValuePair<Vector3, uint> sd in SkinData)
{
if (Vector3.Distance(sd.Key, ss.Key.transform.position) < 0.2)
{
//Scale
RemoveSphere(ss.Key);
if (doScale(ss.Key, ss.Value.z))
{
ScaledEntitys.Add(ss.Key as BaseEntity);
Scaled++;
if (showDebug) Puts("Scaled to " + ss.Value.z.ToString());
}
}
}
}
}
if (ServerScalable.Count != 0)
{
//Apply Scale Data to Found Scaleable
foreach (KeyValuePair<BaseEntity, Vector3> ss in ServerScalable)
{
if (showDebug) Puts("Found Scaled Prefab @ " + ss.Key.transform.position + " : " + ss.Value.z.ToString());
//Scale
RemoveSphere(ss.Key);
if (doScale(ss.Key, ss.Value.z))
{
ScaledEntitys.Add(ss.Key as BaseEntity);
Scaled++;
if (showDebug) Puts("Scaled to " + ss.Value.z.ToString());
}
}
}
Puts("Scaled " + Scaled.ToString() + " Entitys");
}
private void Unload()
{
//Remove all scaling for clear start using map data.
foreach (BaseEntity be in ScaledEntitys)
{
RemoveSphere(be);
}
}
private void RemoveSphere(BaseEntity be)
{
var sphereEntity = be.GetParentEntity() as SphereEntity;
if (sphereEntity == null)
{
return;
}
be.transform.localScale /= sphereEntity.currentRadius;
be.SetParent(sphereEntity.GetParentEntity(), worldPositionStays: true, sendImmediate: true);
sphereEntity.Kill();
}
void OnServerInitialized()
{
Startup();
}
public void Startup()
{
foreach (Signage Neon in UnityEngine.Object.FindObjectsOfType<Signage>())
{
if (Neons.Contains(Neon.prefabID) && Neon.OwnerID == 0)
{
Neon.Kill();
}
}
//Delay slightly to give time for everything to be fully loaded on slow servers.
timer.Once(2f, () =>
{
//Extract Map Data
for (int i = World.Serialization.world.maps.Count - 1; i >= 0; i--)
{
MapData mapdata = World.Serialization.world.maps[i];
if (mapdata.name == Base64Encode("SerializedImageData"))
{
//Process ImageData
XMLDecode(System.Text.Encoding.ASCII.GetString(mapdata.data));
Puts("Processed SerializedImageData " + SignData.Count.ToString() + " Images Found");
}
else if (mapdata.name == Base64Encode("SerializedSkinData"))
{
//Process SkinData
XMLDecodeSkin(System.Text.Encoding.ASCII.GetString(mapdata.data));
Puts("Processed SerializedSkinData " + SkinData.Count.ToString() + " Skins Found");
}
}
int FixedNeons = 0;
FixedNeons = 0;
//Find All Server Signs and skinnables in the map file
for (int i = World.Serialization.world.prefabs.Count - 1; i >= 0; i--)
{
PrefabData prefabdata = World.Serialization.world.prefabs[i];
if (Neons.Contains(prefabdata.id))
{
FixedNeons += CreateNeon(prefabdata);
}
if (isSign(prefabdata))
{
foreach (Signage s in FindSign(prefabdata.position, 0.2f))
{
if (!ServerSigns.ContainsKey(s))
ServerSigns.Add(s, prefabdata.scale);
}
}
if (isSkinnable(prefabdata))
{
foreach (BaseEntity s in FindSkin(prefabdata.position, 0.55f))
{
if (!ServerSkinnables.ContainsKey(s))
ServerSkinnables.Add(s, prefabdata.scale);
}
}
if (isScaleable(prefabdata))
{
foreach (BaseEntity s in FindSkin(prefabdata.position, 0.55f))
{
if (!ServerScalable.ContainsKey(s))
ServerScalable.Add(s, prefabdata.scale);
}
}
}
Puts("Fixed " + FixedNeons.ToString() + " Neon Signs");
if (showDebug) Puts("Found " + ServerSigns.Count.ToString() + " Server Signs");
if (showDebug) Puts("Found " + ServerSkinnables.Count.ToString() + " Server Skinnable Items");
if (showDebug) Puts("Found " + ServerScalable.Count.ToString() + " Server Scaleable Items");
//Check if there is sign data
if (ServerSigns.Count != 0)
{
//Apply Sign Data to Found Signs
foreach (KeyValuePair<Signage, Vector3> ss in ServerSigns)
{
if (showDebug) Puts("Found Sign @ " + ss.Key.transform.position);
foreach (KeyValuePair<Vector3, List<byte[]>> sd in SignData)
{
if (Vector3.Distance(sd.Key, ss.Key.transform.position) < 0.2)
{
if (showDebug) Puts("Applying Image");
if (sd.Value.Count == 1)
{
ApplySignage(ss.Key, sd.Value[0], 0);
}
else
{
for(int id = 0; id < sd.Value.Count;id++)
{
try
{
ApplySignage(ss.Key, sd.Value[id], id);
}
catch { }
}
}
if (!Protected.Contains(ss.Key as BaseEntity))
{
Protected.Add(ss.Key as BaseEntity);
}
ss.Key.SetFlag(BaseEntity.Flags.Locked, true);
ss.Key.SendNetworkUpdate();
}
}
}
}
if (ServerSkinnables.Count != 0)
{
//Apply Skin Data to Found Skinnables
foreach (KeyValuePair<BaseEntity, Vector3> ss in ServerSkinnables)
{
if (showDebug) Puts("Found skinnable @ " + ss.Key.transform.position);
foreach (KeyValuePair<Vector3, uint> sd in SkinData)
{
if (Vector3.Distance(sd.Key, ss.Key.transform.position) < 0.2)
{
if (showDebug) Puts("Applying skin");
ApplySkin(ss.Key, sd.Value);
if (!Protected.Contains(ss.Key as BaseEntity))
{
Protected.Add(ss.Key as BaseEntity);
}
}
}
}
}
Rescale();
foreach(BaseEntity meshdestroy in Protected)
{
DestroyMeshCollider(meshdestroy);
}
});
}
public int CreateNeon(PrefabData pd)
{
//Create New Neon
try
{
if(FindSign(pd.position,0.2f).Count < 0)
{
Puts("Already A Neon There");
return 0;
}
NeonSign replacement = GameManager.server.CreateEntity(StringPool.Get(pd.id), pd.position, pd.rotation) as NeonSign;
if (replacement == null) return 0;
DestroyGroundComp(replacement);
DestroyMeshCollider(replacement);
Protected.Add(replacement);
replacement.Spawn();
replacement.currentFrame = 0;
replacement.animationSpeed = 1f;
replacement.transform.position = pd.position;
replacement.transform.rotation = pd.rotation;
replacement.pickup.enabled = false;
byte[] Blank = Convert.FromBase64String(Blanked);
if (replacement.prefabID == 708840119)
{
ApplySignage(replacement, Blank, 0);
ApplySignage(replacement, Blank, 1);
ApplySignage(replacement, Blank, 2);
ApplySignage(replacement, Blank, 3);
ApplySignage(replacement, Blank, 4);
}
else if (replacement.prefabID == 3591916872)
{
ApplySignage(replacement, Blank, 0);
ApplySignage(replacement, Blank, 1);
ApplySignage(replacement, Blank, 2);
}
else
{
ApplySignage(replacement, Blank, 0);
}
//Give full power
replacement.UpdateHasPower(100, 1);
replacement.SendNetworkUpdateImmediate(true);
return 1;
}
catch { }
return 0;
}
public bool doScale(BaseEntity be, float radius)
{
//Scale
if (EntityScaleManager != null)
{
//Dead zone for rounding
if (radius > 1.1f || radius < 0.9f)
{
//Sends Command to EntityScaleManager
EntityScaleManager.Call("API_ScaleEntity", be, radius);
return true;
}
}
return false;
}
List<Signage> FindSign(Vector3 pos, float radius)
{
//Casts a sphere at given position and find all signs there
var hits = Physics.SphereCastAll(pos, radius, Vector3.up);
var x = new List<Signage>();
foreach (var hit in hits)
{
var entity = hit.GetEntity()?.GetComponent<Signage>();
if (entity && !x.Contains(entity))
x.Add(entity);
}
return x;
}
List<BaseEntity> FindSkin(Vector3 pos, float radius)
{
//Casts a sphere at given position and find all Skins there
var hits = Physics.SphereCastAll(pos, radius, Vector3.up);
var x = new List<BaseEntity>();
foreach (var hit in hits)
{
var entity = hit.GetEntity()?.GetComponent<BaseEntity>();
if (entity && !x.Contains(entity))
x.Add(entity);
}
return x;
}
//(Thanks to SignArtists code)
void ApplySignage(Signage sign, byte[] imageBytes, int index)
{
if (!_signSizes.ContainsKey(sign.ShortPrefabName))
return;
var size = Math.Max(sign.paintableSources.Length, 1);
if (sign.textureIDs == null || sign.textureIDs.Length != size)
{
Array.Resize(ref sign.textureIDs, size);
}
var resizedImage = ImageResize(imageBytes, _signSizes[sign.ShortPrefabName].Width,
_signSizes[sign.ShortPrefabName].Height);
//Applys Image
sign.textureIDs[index] = FileStorage.server.Store(resizedImage, FileStorage.Type.png, sign.net.ID);
}
void ApplySkin(BaseEntity item, uint SkinID)
{
//Apply Skin to item
item.skinID = SkinID;
item.SendNetworkUpdate();
}
//(Thanks to SignArtists code)
byte[] ImageResize(byte[] imageBytes, int width, int height)
{
//Resize image to sign size.
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();
}
//Decodes XML data from MapData
void XMLDecode(string SerialData)
{
string[] DataParse = SerialData.Split(new string[] { "<position>" }, StringSplitOptions.None);
foreach (string xmldata in DataParse)
{
if (xmldata.Contains("xml version")) continue;
string x = xmldata.Split(new string[] { "</x><y>" }, StringSplitOptions.None)[0].Replace("<x>", "");
string y = xmldata.Split(new string[] { "</y><z>" }, StringSplitOptions.None)[0].Replace("<x>" + x + "</x><y>", "");
string z = xmldata.Split(new string[] { "</z></position>" }, StringSplitOptions.None)[0].Replace("<x>" + x + "</x><y>" + y + "</y><z>", "");
string texture = xmldata.Split(new string[] { "<texture>" }, StringSplitOptions.None)[1].Replace("</texture>", "").Replace("</SerializedImageData>", "");
string[] imageFrames = texture.Split(new string[] { "<frame>" }, StringSplitOptions.None);
List<byte[]> ImageData = new List<byte[]>();
foreach (string imageframe in imageFrames)
{
if (imageframe != "")
{
ImageData.Add(Convert.FromBase64String(imageframe.Replace("<frame>", "")));
}
else
{
ImageData.Add(Convert.FromBase64String(Blanked));
}
}
Vector3 pos = new Vector3(float.Parse(x), float.Parse(y), float.Parse(z));
if (!SignData.ContainsKey(pos))
{
try
{
SignData.Add(pos, ImageData);
}
catch { }
}
}
}
void XMLDecodeSkin(string SerialData)
{
string[] DataParse = SerialData.Split(new string[] { "<position>" }, StringSplitOptions.None);
foreach (string xmldata in DataParse)
{
if (xmldata.Contains("xml version")) continue;
string x = xmldata.Split(new string[] { "</x><y>" }, StringSplitOptions.None)[0].Replace("<x>", "");
string y = xmldata.Split(new string[] { "</y><z>" }, StringSplitOptions.None)[0].Replace("<x>" + x + "</x><y>", "");
string z = xmldata.Split(new string[] { "</z></position>" }, StringSplitOptions.None)[0].Replace("<x>" + x + "</x><y>" + y + "</y><z>", "");
uint skinid = uint.Parse(xmldata.Split(new string[] { "<skin>" }, StringSplitOptions.None)[1].Replace("</skin>", "").Replace("</SerializedSkinData>", ""));
Vector3 pos = new Vector3(float.Parse(x), float.Parse(y), float.Parse(z));
SkinData.Add(pos, skinid);
}
}
//Create XML Data
string XMLEncode()
{
string XMLData = @"<? xml version=""1.0""?><SerializedImageData>";
string SerialData = "";
foreach (KeyValuePair<Signage, Vector3> _sign in ServerSigns)
{
SerialData += ("<position>" +
"<x>" + _sign.Key.transform.position.x.ToString("0.0") + "</x>" +
"<y>" + _sign.Key.transform.position.y.ToString("0.0") + "</y>" +
"<z>" + _sign.Key.transform.position.z.ToString("0.0") + "</z>" +
"</position>" +
"<texture>");
List<byte[]> Images = new List<byte[]>();
for(int ids = 0; ids < _sign.Key.textureIDs.Length; ids++)
{
try
{
byte[] image = FileStorage.server.Get(_sign.Key.textureIDs[ids], FileStorage.Type.png, _sign.Key.net.ID);
Images.Add(image);
}
catch
{
Images.Add(Convert.FromBase64String(Blanked));
}
}
foreach(byte[] imagedata in Images)
{
try
{
SerialData += Convert.ToBase64String(imagedata) + "<frame>";
}
catch
{
SerialData += Blanked + "<frame>";
}
}
SerialData += "</texture>";
}
XMLData = XMLData + SerialData + "</SerializedImageData>";
return XMLData;
}
string XMLEncodeSkin()
{
string XMLData = @"<? xml version=""1.0""?><SerializedSkinData>";
string SerialData = "";
foreach (KeyValuePair<BaseEntity, Vector3> _skin in ServerSkinnables)
{
if (_skin.Key.skinID != 0)
{
SerialData += ("<position>" +
"<x>" + _skin.Key.transform.position.x.ToString("0.0") + "</x>" +
"<y>" + _skin.Key.transform.position.y.ToString("0.0") + "</y>" +
"<z>" + _skin.Key.transform.position.z.ToString("0.0") + "</z>" +
"</position>" +
"<skin>" +
_skin.Key.skinID.ToString() +
"</skin>");
}
}
XMLData = XMLData + SerialData + "</SerializedSkinData>";
return XMLData;
}
//Finds Signs
private bool IsLookingAtSign(BasePlayer player, out IPaintableEntity sign)
{
RaycastHit hit;
sign = null;
if (Physics.Raycast(player.eyes.HeadRay(), out hit, 5f))
{
BaseEntity entity = hit.GetEntity();
if (entity is Signage)
{
sign = new PaintableSignage(entity as Signage);
}
else if (entity is PhotoFrame)
{
sign = new PaintableFrame(entity as PhotoFrame);
}
}
return sign != null;
}
//Image Downloading Thanks SignArtist
private class DownloadRequest
{
public BasePlayer Sender { get; }
public IPaintableEntity Sign { get; }
public string Url { get; set; }
public bool Raw { get; }
public bool Hor { get; }
public DownloadRequest(string url, BasePlayer player, IPaintableEntity sign, bool raw, bool hor)
{
Url = url;
Sender = player;
Sign = sign;
Raw = raw;
Hor = hor;
}
}
private void StartNextDownload(bool reduceCount = false)
{
try
{
ServerMgr.Instance.StartCoroutine(DownloadImage(downloadQueue.Dequeue()));
}
catch { }
}
private SignSize GetImageSizeFor(IPaintableEntity signage)
{
if (_signSizes.ContainsKey(signage.ShortPrefabName))
{
return _signSizes[signage.ShortPrefabName];
}
return null;
}
private IEnumerator DownloadImage(DownloadRequest request)
{
int fselected = 0;
if (request.Url.StartsWith("frame:0"))
{
fselected = 0;
request.Url = request.Url.Replace("frame:0", "");
}
else if (request.Url.StartsWith("frame:1"))
{
fselected = 1;
request.Url = request.Url.Replace("frame:1", "");
}
else if (request.Url.StartsWith("frame:2"))
{
fselected = 2;
request.Url = request.Url.Replace("frame:2", "");
}
else if (request.Url.StartsWith("frame:3"))
{
fselected = 3;
request.Url = request.Url.Replace("frame:3", "");
}
else if (request.Url.StartsWith("frame:4"))
{
fselected = 4;
request.Url = request.Url.Replace("frame:4", "");
}
byte[] imageBytes;
//Path for Base64 weblinks
if (request.Url.StartsWith("data:image"))
{
imageBytes = LoadImage(request.Url);
}
else
{
UnityWebRequest www = UnityWebRequest.Get(request.Url);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
// The webrequest wasn't succesful, show a message to the player and attempt to start the next download.
request.Sender.ChatMessage("Download Error");
www.Dispose();
StartNextDownload(true);
yield break;
}
// Get the bytes array for the image from the webrequest and lookup the target image size for the targeted sign.
if (request.Raw)
{
imageBytes = www.downloadHandler.data;
}
else
{
imageBytes = GetImageBytes(www);
}
www.Dispose();
}
SignSize size = GetImageSizeFor(request.Sign);
// Verify that we have image size data for the targeted sign.
RotateFlipType rotation = RotateFlipType.RotateNoneFlipNone;
if (request.Hor)
{
rotation = RotateFlipType.RotateNoneFlipX;
}
object rotateObj = Interface.Call("GetImageRotation", request.Sign.Entity);
if (rotateObj is RotateFlipType)
{
rotation = (RotateFlipType)rotateObj;
}
// Get the bytes array for the resized image for the targeted sign.
byte[] resizedImageBytes = ResizeImage(imageBytes, size.Width, size.Height, size.ImageWidth, size.ImageHeight, false && !request.Raw, rotation);
// Check if the sign already has a texture assigned to it.
if (request.Sign.TextureId() > 0)
{
// A texture was already assigned, remove this file to make room for the new one.
FileStorage.server.Remove(request.Sign.TextureId(), FileStorage.Type.png, request.Sign.NetId);
}
// Create the image on the filestorage and send out a network update for the sign.
request.Sign.SetImage(FileStorage.server.Store(resizedImageBytes, FileStorage.Type.png, request.Sign.NetId), fselected);
request.Sign.SendNetworkUpdate();
// Notify the player that the image was loaded.
request.Sender.ChatMessage("Sign Updated");
// Call the Oxide hook 'OnSignUpdated' to notify other plugins of the update event.
Interface.Oxide.CallHook("OnSignUpdated", request.Sign, request.Sender);
// Attempt to start the next download.
StartNextDownload(true);
}
private byte[] GetImageBytes(UnityWebRequest www)
{
Texture2D texture = new Texture2D(2, 2);
texture.LoadImage(www.downloadHandler.data);
byte[] image;
if (texture.format == TextureFormat.ARGB32)
{
image = texture.EncodeToPNG();
}
else
{
image = texture.EncodeToJPG(90);
}
return image;
}
private byte[] LoadImage(string data)
{
//Convert Base64 link image to data.
data = data.Replace("data:image/gif;base64,", "");
data = data.Replace("data:image/jpeg;base64,", "");
data = data.Replace("data:image/png;base64,", "");
return Convert.FromBase64String(data);
}
public static byte[] ResizeImage(byte[] bytes, int width, int height, int targetWidth, int targetHeight, bool enforceJpeg, RotateFlipType rotation = RotateFlipType.RotateNoneFlipNone)
{
byte[] resizedImageBytes;
using (MemoryStream originalBytesStream = new MemoryStream(), resizedBytesStream = new MemoryStream())
{
// Write the downloaded image bytes array to the memorystream and create a new Bitmap from it.
originalBytesStream.Write(bytes, 0, bytes.Length);
Bitmap image = new Bitmap(originalBytesStream);
if (rotation != RotateFlipType.RotateNoneFlipNone)
{
image.RotateFlip(rotation);
}
// Check if the width and height match, if they don't we will have to resize this image.
if (image.Width != targetWidth || image.Height != targetHeight)
{
// Create a new Bitmap with the target size.
Bitmap resizedImage = new Bitmap(width, height);
// Draw the original image onto the new image and resize it accordingly.
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(resizedImage))
{
graphics.DrawImage(image, new Rectangle(0, 0, targetWidth, targetHeight));
}
TimestampImage(resizedImage);
// Save the bitmap to a MemoryStream as either Jpeg or Png.
if (enforceJpeg)
{
resizedImage.Save(resizedBytesStream, ImageFormat.Jpeg);
}
else
{
resizedImage.Save(resizedBytesStream, ImageFormat.Png);
}
// Grab the bytes array from the new image's MemoryStream and dispose of the resized image Bitmap.
resizedImageBytes = resizedBytesStream.ToArray();
resizedImage.Dispose();
}
else
{
TimestampImage(image);
// The image has the correct size so we can just return the original bytes without doing any resizing.
resizedImageBytes = bytes;
}
// Dispose of the original image Bitmap.
image.Dispose();
}
// Return the bytes array.
return resizedImageBytes;
}
private static void TimestampImage(Bitmap image)
{
//Rust images are crc and if we have the same image it is deleted from the file storage
//Here we changed the last few pixels of the image with colors based off the current milliseconds since wipe
//This will generate a unique image every time and allow us to use the same image multiple times
Color pixel = Color.FromArgb(UnityEngine.Random.Range(0, 256), UnityEngine.Random.Range(0, 256), UnityEngine.Random.Range(0, 256), UnityEngine.Random.Range(0, 256));
image.SetPixel(image.Width - 1, image.Height - 1, pixel);
}
void DestroyGroundComp(BaseEntity ent)
{
UnityEngine.Object.DestroyImmediate(ent.GetComponent<DestroyOnGroundMissing>());
UnityEngine.Object.DestroyImmediate(ent.GetComponent<GroundWatch>());
//Stops Decay
UnityEngine.Object.DestroyImmediate(ent.GetComponent<DeployableDecay>());
}
void DestroyMeshCollider(BaseEntity ent)
{
foreach (var mesh in ent.GetComponentsInChildren<MeshCollider>())
{
UnityEngine.Object.DestroyImmediate(mesh);
}
}
//Chat Commands
//
//Prints Image Onto Sign being looked at.
[ChatCommand("ssign")]
void signcommand(BasePlayer basePlayer, string command, string[] args)
{
if (!permission.UserHasPermission(basePlayer.UserIDString, PermMap))
{
//Dont have Permission to use so exit.
return;
}
if (args.Length < 1)
{
basePlayer.ChatMessage("Invalid Args");
return;
}
IPaintableEntity sign;
if (!IsLookingAtSign(basePlayer, out sign))
{
basePlayer.ChatMessage("No Signs Found");
return;
}
// This sign pastes in reverse, so we'll check and set a var to flip it
bool hor = sign.ShortPrefabName == "sign.hanging";
downloadQueue.Enqueue(new DownloadRequest(args[0], basePlayer, sign, false, hor));
// Attempt to start the next download.
StartNextDownload();
Interface.Oxide.CallHook("OnImagePost", basePlayer, args[0]);
}
//Skins Items that are being looked at.
[ChatCommand("sskin")]
private void SkinCommand(BasePlayer basePlayer, string command, string[] args)
{
if (basePlayer == null)
{
return;
}
if (!permission.UserHasPermission(basePlayer.UserIDString, PermMap))
{
//Dont have Permission to use so exit.
return;
}
ulong skin;
if (args.Length != 1 || !ulong.TryParse(args[0], out skin))
{
basePlayer.ChatMessage("Invalid Skin ID");
return;
}
RaycastHit hit;
if (!Physics.Raycast(basePlayer.eyes.HeadRay(), out hit))
{
basePlayer.ChatMessage("No Skinnable Entitys Found. Try Looking at the Hinge area if its a door.");
return;
}
var entity = hit.GetEntity();
if (entity == null)
{
basePlayer.ChatMessage("No Skinnable Entitys Found. Try Looking at the Hinge area if its a door.");
return;
}
//Sets Skin
entity.skinID = skin;
entity.SendNetworkUpdateImmediate();
basePlayer.ChatMessage("Applying Skin");
}
//Scale Items that are being looked at.
[ChatCommand("sscale")]
private void ScaleCommand(BasePlayer basePlayer, string command, string[] args)
{
if (basePlayer == null)
{
return;
}
if (!permission.UserHasPermission(basePlayer.UserIDString, PermMap))
{
//Dont have Permission to use so exit.
return;
}
if (EntityScaleManager == null)
{
Puts(@"Scaling Disabled get plugin https://umod.org/plugins/entity-scale-manager");
return;
}
float scale;
if (args.Length != 1 || !float.TryParse(args[0], out scale))
{
basePlayer.ChatMessage("Invalid Scale");
return;
}
RaycastHit hit;
if (!Physics.Raycast(basePlayer.eyes.HeadRay(), out hit))
{
basePlayer.ChatMessage("No Scalable Entitys Found. Try Looking at the Hinge area if its a door.");
return;
}
var entity = hit.GetEntity();
if (entity == null)
{
basePlayer.ChatMessage("No Scalable Entitys Found. Try Looking at the Hinge area if its a door.");
return;
}
//Send scale command to EntityScaleManager
EntityScaleManager.Call("API_ScaleEntity", entity, scale);
//Find in prefab list and update its scale
for (int i = 0; i < World.Serialization.world.prefabs.Count; i++)
{
if (entity.transform.position == World.Serialization.world.prefabs[i].position)
{
World.Serialization.world.prefabs[i].scale.z = scale;
basePlayer.ChatMessage("Updated in Map Prefab Data");
}
}
}
//Resets to settings in mapdata
[ChatCommand("sreset")]
void Reset(BasePlayer player, string command, string[] args)
{
//Remove Protection
Protected.Clear();
//Remove all scaled entitys.
foreach (BaseEntity be in ScaledEntitys)
{
try
{
be.OwnerID = 123456;
be.Kill();
}
catch { }
}
//Scan though prefab list and remove skinnable items and paintable signs placed in rustedit.
foreach (PrefabData pd in World.Serialization.world.prefabs)
{
if (signids.Contains(pd.id))
{
BaseEntity[] BaseEntity = FindSign(pd.position, 0.65f).ToArray();
foreach (BaseEntity be in BaseEntity)
{
if (be != null)
{
be.OwnerID = 123456;
be.Kill();
}
}
}
if (skinnableids.Contains(pd.id))
{
BaseEntity[] BaseEntity = FindSkin(pd.position, 0.65f).ToArray();
foreach (BaseEntity be in BaseEntity)
{
if (be != null)
{
be.OwnerID = 123456;
be.Kill();
}
}
}
}
player.ChatMessage("Removed server skinnables and signs.");
//Delay to allow everything to be destroyed.
timer.Once(5f, () =>
{
player.ChatMessage("Respawning server skinnables and signs.");
//Recreate them from Prefab List
foreach (PrefabData pd in World.Serialization.world.prefabs)
{
if (signids.Contains(pd.id))
{
Signage replacement = GameManager.server.CreateEntity(StringPool.Get(pd.id), pd.position, pd.rotation) as Signage;
if (replacement == null) return;
DestroyGroundComp(replacement);
Protected.Add(replacement);
replacement.Spawn();
replacement.transform.position = pd.position;
replacement.transform.rotation = pd.rotation;
replacement.pickup.enabled = false;
}
if (skinnableids.Contains(pd.id))
{
string isdoor = StringPool.Get(pd.id);
if (isdoor.Contains("hinged"))
{
Door replacement = GameManager.server.CreateEntity(StringPool.Get(pd.id), pd.position, pd.rotation) as Door;
if (replacement == null) return;
DestroyMeshCollider(replacement);
DestroyGroundComp(replacement);
Protected.Add(replacement);
replacement.Spawn();
replacement.transform.position = pd.position;
replacement.transform.rotation = pd.rotation;
replacement.grounded = true;
replacement.pickup.enabled = false;
}
else
{
BaseEntity replacement = GameManager.server.CreateEntity(StringPool.Get(pd.id), pd.position, pd.rotation) as BaseEntity;
if (replacement == null) return;
DestroyGroundComp(replacement);
Protected.Add(replacement);
replacement.Spawn();
replacement.transform.position = pd.position;
replacement.transform.rotation = pd.rotation;
}
}
}
//Send update for each.
foreach (BaseEntity be in Protected)
{
be.SendNetworkUpdateImmediate(true);
}
//Clear Data ready for resetup
ScaledEntitys.Clear();
ServerSigns.Clear();
ServerSkinnables.Clear();
SignData.Clear();
SkinData.Clear();
ServerScalable.Clear();
//Resetup
Startup();
player.ChatMessage("Completed");
});
}
//Chat Command to remove skinnable and paintable entitys since they are given protection.
[ChatCommand("sremove")]
void removeentity(BasePlayer player, string command, string[] args)
{
RaycastHit hit;
if (!Physics.Raycast(player.eyes.HeadRay(), out hit))
{
player.ChatMessage("No Entitys Found. Try Looking at the Hinge area if its a door.");
return;
}
var entity = hit.GetEntity();
if (entity == null)
{
player.ChatMessage("No Entitys Found. Try Looking at the Hinge area if its a door.");
return;
}
if (Protected.Contains(entity))
{
Protected.Remove(entity);
entity.OwnerID = 123456;
entity.Kill();
player.ChatMessage("Entity protection disabled and removed.");
return;
}
player.ChatMessage("Not a protected entity use normal admin kill on it.");
}
//Save Map and Data to MapData
[ChatCommand("MapSave")]
void MapSave(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, PermMap))
{
//Dont have Permission to use so exit.
return;
}
//Create XML Data
string XMLData = XMLEncode();
string XMLDataSkin = XMLEncodeSkin();
//Check if mapdata already has image data
MapData sd = World.Serialization.GetMap(Base64Encode("SerializedImageData"));
MapData ssd = World.Serialization.GetMap(Base64Encode("SerializedSkinData"));
if (sd == null)
{
player.ChatMessage("Creating Sign Data In Map");
World.Serialization.AddMap(Base64Encode("SerializedImageData"), Encoding.ASCII.GetBytes(XMLData));
}
else
{
player.ChatMessage("Updating Sign Data In Map");
sd.data = Encoding.ASCII.GetBytes(XMLData);
}
if (ssd == null)
{
player.ChatMessage("Creating Skin Data In Map");
World.Serialization.AddMap(Base64Encode("SerializedSkinData"), Encoding.ASCII.GetBytes(XMLDataSkin));
}
else
{
player.ChatMessage("Updating Skin Data In Map");
ssd.data = Encoding.ASCII.GetBytes(XMLDataSkin);
}
string mapname = World.MapFileName.ToString().Replace(".map", ".embeded.map");
//Create File
World.Serialization.Save(mapname);
player.ChatMessage("Saved edited map in root dir as " + mapname);
}
}
}