/*▄▄▄ ███▄ ▄███▓ ▄████ ▄▄▄██▀▀▀▓█████▄▄▄█████▓ ▓█████▄ ▓██▒▀█▀ ██▒ ██▒ ▀█▒ ▒██ ▓█ ▀▓ ██▒ ▓▒ ▒██▒ ▄██▓██ ▓██░▒██░▄▄▄░ ░██ ▒███ ▒ ▓██░ ▒░ ▒██░█▀ ▒██ ▒██ ░▓█ ██▓▓██▄██▓ ▒▓█ ▄░ ▓██▓ ░ ░▓█ ▀█▓▒██▒ ░██▒░▒▓███▀▒ ▓███▒ ░▒████▒ ▒██▒ ░ ░▒▓███▀▒░ ▒░ ░ ░ ░▒ ▒ ▒▓▒▒░ ░░ ▒░ ░ ▒ ░░ ▒░▒ ░ ░ ░ ░ ░ ░ ▒ ░▒░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░*/ using Newtonsoft.Json; using System; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Linq; using UnityEngine; using UnityEngine.Networking; namespace Oxide.Plugins { [Info("MyPattern", "bmgjet", "1.0.2")] [Description("Converts png image into boomer pattern")] class MyPattern : RustPlugin { //Permission public const string UsePerm = "MyPattern.use"; //Boomer 8 Colour Palette private readonly ArrayList colors = new ArrayList() { System.Drawing.Color.FromArgb(255, 255, 255), System.Drawing.Color.FromArgb(255, 229, 70), System.Drawing.Color.FromArgb(255, 118, 44), System.Drawing.Color.FromArgb(255, 39, 25), System.Drawing.Color.FromArgb(148, 255, 57), System.Drawing.Color.FromArgb(119, 235, 255), System.Drawing.Color.FromArgb(71, 86, 255), System.Drawing.Color.FromArgb(255, 78, 218), }; #region Configuration private Configuration config; private class Configuration { [JsonProperty("Limit Max Amount Of Dots")] public int MaxDots = 576; [JsonProperty("Flip Image")] public bool FlipImage = false; [JsonProperty("Resolution Of Boomer Pattern (Larger = More Dots = More Lag)")] public int BoomerRes = 24; [JsonProperty("Default Dot Scale (Larger = More Gap Between Dots)")] public int Scale = 2; [JsonProperty("Resizer Interpolation Mode (0 Default, 1 Low, 2 High, 3 Bilinear, 4 Bicubic, 5 NearestNeighbor, 6 HighQualityBilinear, 7 HighQualityBicubic)")] public int Resizer = 3; [JsonProperty("Limit Color Palette To Vanilla (Limits To 8 Colors)")] public bool LimitPalette = false; [JsonProperty("Crop Black Edges (No Black Boomer So Saves Space)")] public bool BlackCrop = false; [JsonProperty("Max File Size (Bytes)")] public int MaxSize = 3000000; public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonConvert.DeserializeObject>(ToJson()); } protected override void LoadDefaultConfig() { config = new Configuration(); } protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) { throw new JsonException(); } if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys)) { PrintWarning("Configuration appears to be outdated; updating and saving"); SaveConfig(); } } catch { PrintWarning($"Configuration file {Name}.json is invalid; using defaults"); LoadDefaultConfig(); } } protected override void SaveConfig() { PrintWarning($"Configuration changes saved to {Name}.json"); Config.WriteObject(config, true); } #endregion Configuration #region Oxide Hooks private void Init() { permission.RegisterPermission(UsePerm, this); } private object CanDesignFirework(BasePlayer player, PatternFirework pattern) { if (pattern != null && player != null && pattern?.MaxStars == config.MaxDots) { player.ChatMessage("Can't Open Custom Patterns Too Many Dots For The Client!"); return false; } return null; } [ChatCommand("mypattern")] private void LoadMyPattern(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, UsePerm)) { player.ChatMessage("Missing Permission"); return; } //Check Permission if (args.Length != 1 && args.Length != 2) { player.ChatMessage("Invalid Args /mypattern url scale"); return; } //Validate has arg if (!args[0].StartsWith("http") || !args[0].Contains(".png")) //Validate URL { player.ChatMessage("Not A Valid PNG URL!"); return; } int scale = config.Scale; if (args.Length == 2) { if (int.TryParse(args[1], out scale)) { ServerMgr.Instance.StartCoroutine(_ImageToPattern(player, args[0], scale)); } } ServerMgr.Instance.StartCoroutine(_ImageToPattern(player, args[0], scale)); } #endregion #region Methods //System Colour Uses 0-255 Unity Colour Uses 0-1.0f public UnityEngine.Color c2uc(System.Drawing.Color color) { return new UnityEngine.Color(color.R / 255f, color.G / 255f, color.B / 255f); } //Check can convert to image from memory stream public bool IsValidImage(byte[] bytes) { try { using (var ms = new System.IO.MemoryStream(bytes)) { System.Drawing.Image.FromStream(ms); } } catch { return false; } return true; } //Resize image so has less dots public Image resizeImage(Image imgToResize, Size size) { var b = new Bitmap(size.Width, size.Height); var g = System.Drawing.Graphics.FromImage(b); g.InterpolationMode = (System.Drawing.Drawing2D.InterpolationMode)config.Resizer; g.DrawImage(imgToResize, 0, 0, size.Width, size.Height); g.Dispose(); return b; } private PatternFirework FindBaseEntity(Ray ray) { //Ray cast to find BaseEntity RaycastHit hit; var raycast = Physics.Raycast(ray, out hit, 4, -1); BaseEntity entity = raycast ? hit.GetEntity() : null; if (entity != null && entity is PatternFirework) { return entity as PatternFirework; } return null; } //Crop black edges since there is no black firework public Bitmap CropImg(Bitmap bmp) { if (!config.BlackCrop) { return bmp; } int topmost = 0, bottommost = 0, leftmost = 0, rightmost = 0; //Check X pixel Line values Func X = r => { for (int i = 0; i < bmp.Width; ++i) { var C = bmp.GetPixel(i, r); if (C.R != 0 && C.G != 0 && C.B != 0) { return false; } //Black if (C.A != 0) { return false; } //Alpha } return true; }; //Check Y pixel Line values Func Y = c => { for (int i = 0; i < bmp.Height; ++i) { var C = bmp.GetPixel(i, c); if (C.R != 0 && C.G != 0 && C.B != 0) { return false; } //Black if (C.A != 0) { return false; } //Alpha } return true; }; //Find Edges for (int row = 0; row < bmp.Height; ++row) { if (!X(row)) { break; } topmost = row; } for (int row = bmp.Height - 1; row >= 0; --row) { if (!X(row)) { break; } bottommost = row; } for (int col = 0; col < bmp.Width; ++col) { if (!Y(col)) { break; } leftmost = col; } for (int col = bmp.Width - 1; col >= 0; --col) { if (!Y(col)) { break; } rightmost = col; } if (rightmost == 0) rightmost = bmp.Width; if (bottommost == 0) bottommost = bmp.Height; int croppedWidth = rightmost - leftmost; int croppedHeight = bottommost - topmost; //No Edges Found byte Crop = 0; if (croppedWidth == 0) { leftmost = 0; croppedWidth = bmp.Width; Crop++; } if (croppedHeight == 0) { topmost = 0; croppedHeight = bmp.Height; Crop++; } if (Crop == 2) { return bmp; } //No Change So Return Orignal Image //Give Small Boarder int OffsetW = (int)(bmp.Width * 0.95); int OffsetH = (int)(bmp.Height * 0.95); if (croppedWidth < OffsetW) { croppedWidth += (bmp.Width - OffsetW); } if (croppedHeight < OffsetH) { croppedHeight += (bmp.Height - OffsetH); } if (leftmost > (bmp.Width * 0.05)) { leftmost -= (bmp.Width - OffsetW); } if (topmost > (bmp.Height * 0.05)) { topmost -= (bmp.Height - OffsetH); } try { //Crop var target = new Bitmap(croppedWidth, croppedHeight); using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(target)) { g.DrawImage(bmp, new RectangleF(0, 0, croppedWidth, croppedHeight), new RectangleF(leftmost, topmost, croppedWidth, croppedHeight), GraphicsUnit.Pixel); } return target; //Return Modded Image } catch { Puts("Failed To Crop Image"); } return bmp; //Something Went Wrong Return Input Image } //Get nearest colour to ones in boomer palette public UnityEngine.Color Nearest(System.Drawing.Color C) { if (!config.LimitPalette) { return c2uc(C); } double temp; double distance = double.MaxValue; System.Drawing.Color nearest_color = System.Drawing.Color.Empty; foreach (object o in colors) { //Basically do vector3 distance temp = Math.Sqrt(Math.Pow(Convert.ToDouble(((System.Drawing.Color)o).B) - Convert.ToDouble(C.B), 2.0) + Math.Pow(Convert.ToDouble(((System.Drawing.Color)o).G) - Convert.ToDouble(C.G), 2.0) + Math.Pow(Convert.ToDouble(((System.Drawing.Color)o).R) - Convert.ToDouble(C.R), 2.0)); if (temp == 0.0) { nearest_color = (System.Drawing.Color)o; //Exact Match break; } else if (temp < distance) { distance = temp; nearest_color = (System.Drawing.Color)o; //Closest Match } } return c2uc(nearest_color); //Convert to Unity Colour } private IEnumerator _ImageToPattern(BasePlayer player, string url, int scale) { if (scale > 10) { scale = 10; } else if (scale < 1) { scale = 1; } PatternFirework patternFirework = FindBaseEntity(player.eyes.HeadRay()); //Find boomer looked at if (patternFirework == null) { player.ChatMessage("No Pattern Boomer Found!"); yield break; } UnityWebRequest www = UnityWebRequest.Get(Uri.EscapeUriString(url)); yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { if (player != null) { player.ChatMessage("Failed To Download Image!"); } Puts(player.ToString() + " Failed To Download Image! " + url); www.Dispose(); yield break; } var response = www.downloadHandler.data; www.Dispose(); if (response.Length > config.MaxSize) { player.ChatMessage("Image To Large [" + (int)(response.Length / 1024) + "/" + (int)(config.MaxSize / 1024) + " KB]"); Puts(player.ToString() + "Image To Large[" + response.Length + "/" + config.MaxSize + "]" + url); yield break; } if (!IsValidImage(response)) //Invalid Image { player.ChatMessage("Invalid Image!"); yield break; } //Adjust boomer patternFirework.MaxStars = config.MaxDots; Vector2 vector2 = new Vector2(0, 0); if (patternFirework?.Design == null) { patternFirework.Design = new ProtoBuf.PatternFirework.Design(); patternFirework.Design.stars = new List(); } patternFirework.Design.stars.Clear(); //Convert Image float steps = 1f / config.BoomerRes; Bitmap newImage = new Bitmap(resizeImage(CropImg(new Bitmap(new System.IO.MemoryStream(response))), new Size(config.BoomerRes, config.BoomerRes))); //Resize if (!config.FlipImage) { newImage.RotateFlip(RotateFlipType.Rotate180FlipX); } //Create Dots for (var Ycount = 0; Ycount < config.BoomerRes; Ycount++) { for (var Xcount = 0; Xcount < config.BoomerRes; Xcount++) { var C = newImage.GetPixel(Xcount, Ycount); if (C.A > 6 && (C.R > 20 || C.G > 20 || C.B > 20)) // Ignore Black And Alpha { var newstar = new ProtoBuf.PatternFirework.Star(); newstar.position = vector2; newstar.color = Nearest(C); //Get nearest Colour patternFirework.Design.stars.Add(newstar); } if (vector2.x >= scale) { vector2.x = 0; vector2.y += (steps * scale); } vector2.x += (steps * scale); } } player.ChatMessage("Loaded " + patternFirework.Design.stars.Count + " Dots / " + scale + " X Scale."); yield break; } #endregion } }