using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Oxide.Core; using Oxide.Core.Libraries.Covalence; using Oxide.Core.Libraries; using Oxide.Core.Plugins; using System; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text.RegularExpressions; using UnityEngine; using UnityEngine.Networking; using Graphics = System.Drawing.Graphics; using Color = System.Drawing.Color; // ____ _ _ _ // / ___|(_) __ _(_) | ___ // \___ \| |/ _` | | |/ _ \ // ___) | | (_| | | | (_) | // |____/|_|\__, |_|_|\___/ // |___/ // ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾ // ✦ RUST PLUGINS ✦ // ✦ Sigilo.dev ✦ // ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾ namespace Oxide.Plugins { [Info("Rust Diffusion", "Sigilo", "1.0.0")] [Description("Generates images on signs using difussion models")] public class RustDiffusion : RustPlugin { #region Configuration private PluginConfig config; private class PluginConfig { [JsonProperty("API Provider (Google, StableDiffusion, OpenAI)")] public string Provider { get; set; } = "OpenAI"; [JsonProperty("API Key (Google/OpenAI)")] public string ApiKey { get; set; } = "YOUR_API_KEY_HERE"; [JsonProperty("API Base URL")] public string ApiBaseUrl { get; set; } = "https://api.openai.com/v1/images/generations"; [JsonProperty("Model ID")] public string ModelId { get; set; } = "gpt-image-1-mini"; [JsonProperty("Command Cooldown (seconds)")] public float Cooldown { get; set; } = 600f; [JsonProperty("Daily Request Limit")] public int DailyLimit { get; set; } = 10; [JsonProperty("Admins Unlimited")] public bool AdminsUnlimited { get; set; } = true; [JsonProperty("Max Interaction Distance")] public float MaxDistance { get; set; } = 3f; [JsonProperty("Blocked Words")] public List BlockedWords { get; set; } [JsonProperty("Debug Mode")] public bool Debug { get; set; } = false; [JsonProperty("Discord Webhook URL")] public string DiscordWebhookUrl { get; set; } = ""; [JsonProperty("Loading Image URL")] public string LoadingImageUrl { get; set; } = "https://sigilo.dev/assets/images/site/loading.png"; } private class StoredData { public Dictionary Players = new Dictionary(); } private class PlayerData { public int DailyCount; public string LastDayUsed; } private StoredData storedData; private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject("RustDiffusion", storedData); protected override void LoadDefaultConfig() { config = new PluginConfig { BlockedWords = new List { "nsfw", "nude", "porn" } }; } protected override void LoadConfig() { base.LoadConfig(); config = Config.ReadObject(); if (config.BlockedWords != null) config.BlockedWords = config.BlockedWords.Distinct().ToList(); SaveConfig(); } protected override void SaveConfig() { Config.WriteObject(config); } #endregion #region Data Structures private Dictionary DiffusionSizePerAsset; private Dictionary cooldowns = new Dictionary(); private GameObject behaviorGameObject; private DiffusionBehavior behavior; private class DiffusionSize { public int Width { get; } public int Height { get; } public int ImageWidth { get; } public int ImageHeight { get; } public DiffusionSize(int width, int height) : this(width, height, width, height) { } public DiffusionSize(int width, int height, int imageWidth, int imageHeight) { Width = width; Height = height; ImageWidth = imageWidth; ImageHeight = imageHeight; } } #endregion #region Initialization private void Init() { try { storedData = Interface.Oxide.DataFileSystem.ReadObject("RustDiffusion"); } catch { } if (storedData == null) { storedData = new StoredData(); SaveData(); } InitializeDiffusionSizes(); permission.RegisterPermission("rustdiffusion.use", this); permission.RegisterPermission("rustdiffusion.noblock", this); AddCovalenceCommand("draw", "CmdDraw"); AddCovalenceCommand("listmodels", "CmdListModels"); } private void OnServerInitialized() { behaviorGameObject = new GameObject("RustDiffusionBehavior"); behavior = behaviorGameObject.AddComponent(); behavior.plugin = this; } private void Unload() { if (behaviorGameObject != null) UnityEngine.Object.Destroy(behaviorGameObject); } private void InitializeDiffusionSizes() { DiffusionSizePerAsset = new Dictionary { ["sign.pictureframe.landscape"] = new DiffusionSize(256, 192), ["sign.pictureframe.portrait"] = new DiffusionSize(205, 256), ["sign.pictureframe.tall"] = new DiffusionSize(128, 512), ["sign.pictureframe.xl"] = new DiffusionSize(512, 512), ["sign.pictureframe.xxl"] = new DiffusionSize(1024, 512), ["sign.small.wood"] = new DiffusionSize(256, 128), ["sign.medium.wood"] = new DiffusionSize(512, 256), ["sign.large.wood"] = new DiffusionSize(512, 256), ["sign.huge.wood"] = new DiffusionSize(1024, 256), ["sign.hanging.banner.large"] = new DiffusionSize(256, 1024), ["sign.pole.banner.large"] = new DiffusionSize(256, 1024), ["sign.hanging"] = new DiffusionSize(256, 512), ["sign.hanging.ornate"] = new DiffusionSize(512, 256), ["sign.post.single"] = new DiffusionSize(256, 128), ["sign.post.double"] = new DiffusionSize(512, 512), ["sign.post.town"] = new DiffusionSize(512, 256), ["sign.post.town.roof"] = new DiffusionSize(512, 256), ["photoframe.large"] = new DiffusionSize(320, 240), ["photoframe.portrait"] = new DiffusionSize(320, 384), ["photoframe.landscape"] = new DiffusionSize(320, 240), ["sign.neon.xl.animated"] = new DiffusionSize(256, 256), ["sign.neon.xl"] = new DiffusionSize(256, 256), ["sign.neon.125x215.animated"] = new DiffusionSize(128, 256), ["sign.neon.125x215"] = new DiffusionSize(128, 256), ["sign.neon.125x125"] = new DiffusionSize(128, 128), ["spinner.wheel.deployed"] = new DiffusionSize(512, 512, 285, 285), ["carvable.pumpkin"] = new DiffusionSize(256, 256), }; } #endregion #region Commands [Command("draw")] private void CmdDraw(IPlayer iPlayer, string command, string[] args) { var player = iPlayer.Object as BasePlayer; if (player == null) return; if (!permission.UserHasPermission(player.UserIDString, "rustdiffusion.use")) { player.ChatMessage("You do not have permission to use this command."); return; } if (args.Length == 0) { player.ChatMessage("Usage: /draw "); return; } if (!((config.AdminsUnlimited && player.IsAdmin) || permission.UserHasPermission(player.UserIDString, "rustdiffusion.noblock")) && config.Cooldown > 0) { if (cooldowns.TryGetValue(player.userID, out float lastUse)) { float remaining = config.Cooldown - (UnityEngine.Time.realtimeSinceStartup - lastUse); if (remaining > 0) { player.ChatMessage($"Please wait {Math.Ceiling(remaining)}s before generating another image."); return; } } } if (player.IsBuildingBlocked()) { player.ChatMessage("You cannot use this command while building blocked."); return; } if (!GetTargetSign(player, out var paintable, out var signEntity, out string distanceError)) { player.ChatMessage(distanceError ?? "You are not looking at a valid sign."); return; } if (!paintable.CanUpdate(player) && !permission.UserHasPermission(player.UserIDString, "rustdiffusion.noblock")) { player.ChatMessage("You are not authorized to edit this sign."); return; } string prompt = string.Join(" ", args); string sanitizedPrompt = SanitizePrompt(prompt); if (string.IsNullOrWhiteSpace(sanitizedPrompt)) { player.ChatMessage("Invalid prompt."); return; } if (!((config.AdminsUnlimited && player.IsAdmin) || permission.UserHasPermission(player.UserIDString, "rustdiffusion.noblock"))) { foreach (var badWord in config.BlockedWords) { if (sanitizedPrompt.ToLower().Contains(badWord.ToLower())) { player.ChatMessage("Your prompt contains forbidden words."); return; } } if (!storedData.Players.TryGetValue(player.userID, out var pData)) { pData = new PlayerData { DailyCount = 0, LastDayUsed = DateTime.Now.ToString("yyyy-MM-dd") }; storedData.Players[player.userID] = pData; } string today = DateTime.Now.ToString("yyyy-MM-dd"); if (pData.LastDayUsed != today) { pData.DailyCount = 0; pData.LastDayUsed = today; SaveData(); } if (pData.DailyCount >= config.DailyLimit) { player.ChatMessage($"You have reached your daily limit of {config.DailyLimit} images. Please try again tomorrow."); return; } } player.ChatMessage($"Generating image for '{sanitizedPrompt}'... please wait."); bool isHanging = signEntity.ShortPrefabName == "sign.hanging"; uint textureIndex = 0; cooldowns[player.userID] = UnityEngine.Time.realtimeSinceStartup; if (!((config.AdminsUnlimited && player.IsAdmin) || permission.UserHasPermission(player.UserIDString, "rustdiffusion.noblock"))) { if (storedData.Players.TryGetValue(player.userID, out var data)) { data.DailyCount++; SaveData(); } } behavior.QueueGeneration(player, paintable, signEntity, sanitizedPrompt, textureIndex, isHanging); } [Command("listmodels")] private void CmdListModels(IPlayer iPlayer, string command, string[] args) { var player = iPlayer.Object as BasePlayer; if (player != null && !player.IsAdmin) { player.ChatMessage("You do not have permission to list models."); return; } behavior.ListAvailableModels(player); } #endregion #region Logic private string SanitizePrompt(string input) { return Regex.Replace(input, @"[^a-zA-Z0-9 ,.\-!?'""]", ""); } private bool GetTargetSign(BasePlayer player, out IDiffusionPaintable paintable, out BaseEntity entity, out string error) { paintable = null; entity = null; error = null; RaycastHit hit; if (Physics.Raycast(player.eyes.HeadRay(), out hit, 20f, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore)) { entity = hit.GetEntity(); if (entity == null) { error = "You are not looking at a sign."; return false; } if (entity is Signage signage) paintable = new DiffusionSign(signage); else if (entity is PhotoFrame frame) paintable = new DiffusionFrame(frame); else if (entity is CarvablePumpkin pumpkin) paintable = new DiffusionPumpkin(pumpkin); if (paintable != null) { if (hit.distance > config.MaxDistance) { error = "You are too far away from the sign."; return false; } return true; } } error = "You are not looking at a valid sign."; return false; } private DiffusionSize GetDiffusionSize(string shortPrefabName) { return DiffusionSizePerAsset.TryGetValue(shortPrefabName, out var size) ? size : null; } #endregion #region MonoBehaviour & Request Handling private class DiffusionBehavior : MonoBehaviour { public RustDiffusion plugin; public void QueueGeneration(BasePlayer player, IDiffusionPaintable paintable, BaseEntity entity, string prompt, uint textureIndex, bool isHanging) { if (!string.IsNullOrEmpty(plugin.config.LoadingImageUrl)) { StartCoroutine(ApplyLoadingImage(player, paintable, entity, textureIndex, isHanging)); } if (plugin.config.Provider.Equals("StableDiffusion", StringComparison.OrdinalIgnoreCase)) { StartCoroutine(GenerateStableDiffusionImage(player, paintable, entity, prompt, textureIndex, isHanging)); } else if (plugin.config.Provider.Equals("OpenAI", StringComparison.OrdinalIgnoreCase)) { StartCoroutine(GenerateOpenAIImage(player, paintable, entity, prompt, textureIndex, isHanging)); } else if (plugin.config.Provider.Equals("Google", StringComparison.OrdinalIgnoreCase)) { StartCoroutine(GenerateImageRoutine(player, paintable, entity, prompt, textureIndex, isHanging)); } else { if (player != null) player.ChatMessage($"Invalid API Provider configured: {plugin.config.Provider}"); plugin.Puts($"Invalid API Provider: {plugin.config.Provider}"); } } public void ListAvailableModels(BasePlayer player) { StartCoroutine(ListModelsRoutine(player)); } private IEnumerator ListModelsRoutine(BasePlayer player) { string url = $"{plugin.config.ApiBaseUrl}?key={plugin.config.ApiKey}"; plugin.Puts($"Listing models from: {url}"); using (UnityWebRequest www = UnityWebRequest.Get(url)) { yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { plugin.Puts($"ListModels Error: {www.error} - {www.downloadHandler.text}"); if (player != null) player.ChatMessage($"Error listing models: {www.error}"); yield break; } var json = JObject.Parse(www.downloadHandler.text); var models = json["models"]; if (models != null) { plugin.Puts("Available Models:"); foreach (var model in models) { string name = model["name"]?.ToString(); string supported = model["supportedGenerationMethods"]?.ToString(); plugin.Puts($"- Name: {name}, Methods: {supported}"); } if (player != null) player.ChatMessage("Check server console for model list."); } } } private IEnumerator ApplyLoadingImage(BasePlayer player, IDiffusionPaintable paintable, BaseEntity entity, uint textureIndex, bool isHanging) { string url = plugin.config.LoadingImageUrl; if (string.IsNullOrEmpty(url)) yield break; using (UnityWebRequest www = UnityWebRequest.Get(url)) { yield return www.SendWebRequest(); if (www.result == UnityWebRequest.Result.Success) { byte[] imageBytes = www.downloadHandler.data; ApplyImageToSign(player, paintable, entity, imageBytes, textureIndex, isHanging, true); } } } private IEnumerator GenerateStableDiffusionImage(BasePlayer player, IDiffusionPaintable paintable, BaseEntity entity, string prompt, uint textureIndex, bool isHanging) { string baseUrl = plugin.config.ApiBaseUrl; if (baseUrl.EndsWith("/")) baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); string url = $"{baseUrl}/sdapi/v1/txt2img"; DiffusionSize size = plugin.GetDiffusionSize(entity.ShortPrefabName); int width = size != null ? size.ImageWidth : 512; int height = size != null ? size.ImageHeight : 512; var payload = new { prompt = prompt, steps = 20, width = width, height = height, sampler_name = "Euler a", cfg_scale = 7 }; string jsonBody = JsonConvert.SerializeObject(payload); if (plugin.config.Debug) plugin.Puts($"Sending request to Stable Diffusion: {url}"); using (UnityWebRequest www = new UnityWebRequest(url, "POST")) { byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody); www.uploadHandler = new UploadHandlerRaw(bodyRaw); www.downloadHandler = new DownloadHandlerBuffer(); www.SetRequestHeader("Content-Type", "application/json"); yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { plugin.Puts($"StableDiffusion Error: {www.error} - {www.downloadHandler.text}"); if (player != null && player.IsConnected) player.ChatMessage($"Error generating image: Check console."); yield break; } string responseText = www.downloadHandler.text; string b64Image = null; try { JObject json = JObject.Parse(responseText); JArray images = (JArray)json["images"]; if (images != null && images.Count > 0) { b64Image = (string)images[0]; } } catch (Exception ex) { plugin.Puts($"JSON Parsing Error: {ex.Message}"); } if (string.IsNullOrEmpty(b64Image)) { if (player != null && player.IsConnected) player.ChatMessage("Failed to parse image from SD response."); yield break; } byte[] imageBytes = Convert.FromBase64String(b64Image); StartCoroutine(SendDiscordLog(player, prompt, null, entity.transform.position, imageBytes)); ApplyImageToSign(player, paintable, entity, imageBytes, textureIndex, isHanging); } } private IEnumerator GenerateOpenAIImage(BasePlayer player, IDiffusionPaintable paintable, BaseEntity entity, string prompt, uint textureIndex, bool isHanging) { string url = plugin.config.ApiBaseUrl; if (string.IsNullOrEmpty(url)) url = "https://api.openai.com/v1/images/generations"; string model = !string.IsNullOrEmpty(plugin.config.ModelId) ? plugin.config.ModelId : "gpt-image-1-mini"; string sizeParam = "1024x1024"; string qualityParam = "low"; if (model.Contains("dall-e-2")) { DiffusionSize size = plugin.GetDiffusionSize(entity.ShortPrefabName); if (size != null && size.ImageWidth <= 256 && size.ImageHeight <= 256) sizeParam = "256x256"; else if (size != null && size.ImageWidth > 512) sizeParam = "1024x1024"; else sizeParam = "512x512"; qualityParam = null; } else { sizeParam = "1024x1024"; } var payloadDict = new Dictionary { { "model", model }, { "prompt", prompt }, { "n", 1 }, { "size", sizeParam } }; if (!string.IsNullOrEmpty(qualityParam)) { payloadDict["quality"] = qualityParam; } string jsonBody = JsonConvert.SerializeObject(payloadDict); if (plugin.config.Debug) plugin.Puts($"Sending request to OpenAI ({model}, {sizeParam}, {qualityParam}): {url}"); using (UnityWebRequest www = new UnityWebRequest(url, "POST")) { byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody); www.uploadHandler = new UploadHandlerRaw(bodyRaw); www.downloadHandler = new DownloadHandlerBuffer(); www.SetRequestHeader("Content-Type", "application/json"); string apiKey = string.IsNullOrEmpty(plugin.config.ApiKey) || plugin.config.ApiKey == "YOUR_API_KEY_HERE" ? "" : plugin.config.ApiKey; if (string.IsNullOrEmpty(apiKey)) { player.ChatMessage("OpenAI API Key is missing in config."); yield break; } www.SetRequestHeader("Authorization", $"Bearer {apiKey}"); yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { string errorResponse = www.downloadHandler.text; plugin.Puts($"OpenAI Error: {www.error} - {errorResponse}"); if (errorResponse.Contains("moderation_blocked") || errorResponse.Contains("safety_violations")) { if (player != null && player.IsConnected) player.ChatMessage("Your request was rejected by the safety system (NSFW/Policy Violation)."); } else { if (player != null && player.IsConnected) player.ChatMessage($"Error generating image: {www.error}"); } yield break; } string responseText = www.downloadHandler.text; string imageUrl = null; string b64Image = null; try { JObject json = JObject.Parse(responseText); imageUrl = (string)json.SelectToken("data[0].url"); if (string.IsNullOrEmpty(imageUrl)) { b64Image = (string)json.SelectToken("data[0].b64_json"); if (string.IsNullOrEmpty(b64Image)) b64Image = (string)json.SelectToken("data[0].image"); if (string.IsNullOrEmpty(b64Image)) b64Image = (string)json.SelectToken("data[0].base64"); } } catch (Exception ex) { plugin.Puts($"JSON Parsing Error: {ex.Message}"); } if (!string.IsNullOrEmpty(imageUrl)) { StartCoroutine(SendDiscordLog(player, prompt, imageUrl, entity.transform.position, null)); using (UnityWebRequest imgRequest = UnityWebRequest.Get(imageUrl)) { yield return imgRequest.SendWebRequest(); if (imgRequest.result != UnityWebRequest.Result.Success) { plugin.Puts($"Image Download Error: {imgRequest.error}"); if (player != null && player.IsConnected) player.ChatMessage($"Failed to download image."); yield break; } byte[] imageBytes = imgRequest.downloadHandler.data; ApplyImageToSign(player, paintable, entity, imageBytes, textureIndex, isHanging); } } else if (!string.IsNullOrEmpty(b64Image)) { byte[] imageBytes = Convert.FromBase64String(b64Image); StartCoroutine(SendDiscordLog(player, prompt, null, entity.transform.position, imageBytes)); ApplyImageToSign(player, paintable, entity, imageBytes, textureIndex, isHanging); } else { plugin.Puts($"OpenAI Response Parsing Failed. Response did not contain 'url' or 'b64_json'. Full Response: {responseText}"); if (player != null && player.IsConnected) player.ChatMessage("Failed to parse image from OpenAI response."); yield break; } } } private IEnumerator GenerateImageRoutine(BasePlayer player, IDiffusionPaintable paintable, BaseEntity entity, string prompt, uint textureIndex, bool isHanging) { string url = $"{plugin.config.ApiBaseUrl}/{plugin.config.ModelId}:predict?key={plugin.config.ApiKey}"; var payload = new { instances = new[] { new { prompt = prompt } }, parameters = new { sampleCount = 1 } }; string jsonBody = JsonConvert.SerializeObject(payload); using (UnityWebRequest www = new UnityWebRequest(url, "POST")) { byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody); www.uploadHandler = new UploadHandlerRaw(bodyRaw); www.downloadHandler = new DownloadHandlerBuffer(); www.SetRequestHeader("Content-Type", "application/json"); if (plugin.config.Debug) plugin.Puts($"Sending request to {url} with prompt: {prompt}"); yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { plugin.Puts($"API Error: {www.error} - {www.downloadHandler.text}"); if (player != null && player.IsConnected) { player.ChatMessage($"Error generating image. Check console for details."); } if (www.responseCode == 404) { plugin.Puts("404 Error: The Model ID or Endpoint is incorrect. Please check configuration."); } yield break; } string responseText = www.downloadHandler.text; if (plugin.config.Debug) plugin.Puts($"API Response: {responseText}"); string b64Image = null; try { JObject json = JObject.Parse(responseText); b64Image = (string)json.SelectToken("predictions[0].bytesBase64Encoded"); if (string.IsNullOrEmpty(b64Image)) { b64Image = (string)json.SelectToken("predictions[0]"); } } catch (Exception ex) { plugin.Puts($"JSON Parsing Error: {ex.Message}"); } if (string.IsNullOrEmpty(b64Image)) { if (player != null && player.IsConnected) player.ChatMessage("Failed to parse image from API response."); yield break; } byte[] imageBytes = Convert.FromBase64String(b64Image); StartCoroutine(SendDiscordLog(player, prompt, null, entity.transform.position, imageBytes)); ApplyImageToSign(player, paintable, entity, imageBytes, textureIndex, isHanging); } } private byte[] ReEncodeImage(byte[] bytes) { if (bytes == null || bytes.Length == 0) return bytes; Texture2D texture = new Texture2D(2, 2); if (texture.LoadImage(bytes)) { byte[] image = texture.EncodeToPNG(); UnityEngine.Object.DestroyImmediate(texture); return image; } UnityEngine.Object.DestroyImmediate(texture); return bytes; } private IEnumerator SendDiscordLog(BasePlayer player, string prompt, string imageUrl, Vector3 pos, byte[] imageBytes) { if (string.IsNullOrEmpty(plugin.config.DiscordWebhookUrl)) yield break; string steamUrl = $"https://steamcommunity.com/profiles/{player.UserIDString}"; var fields = new List { new { name = "Player", value = $"[{player.displayName}]({steamUrl})", inline = true }, new { name = "Prompt", value = prompt, inline = false }, new { name = "Position", value = $"teleportpos {pos.x:F2} {pos.y:F2} {pos.z:F2}", inline = false }, new { name = "Server", value = ConVar.Server.hostname, inline = true } }; string embedImageUrl = imageUrl; if (string.IsNullOrEmpty(imageUrl) && imageBytes != null) { embedImageUrl = "attachment://image.png"; } var embed = new { title = "Rust Diffusion Image Generated", color = 3447003, fields = fields, image = !string.IsNullOrEmpty(embedImageUrl) ? new { url = embedImageUrl } : null, footer = new { text = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}" } }; var payload = new { embeds = new[] { embed } }; string json = JsonConvert.SerializeObject(payload); UnityWebRequest www; if (string.IsNullOrEmpty(imageUrl) && imageBytes != null) { List formData = new List(); formData.Add(new MultipartFormDataSection("payload_json", json)); formData.Add(new MultipartFormFileSection("file", imageBytes, "image.png", "image/png")); www = UnityWebRequest.Post(plugin.config.DiscordWebhookUrl, formData); } else { www = new UnityWebRequest(plugin.config.DiscordWebhookUrl, "POST"); byte[] jsonToSend = new System.Text.UTF8Encoding().GetBytes(json); www.uploadHandler = (UploadHandler)new UploadHandlerRaw(jsonToSend); www.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); www.SetRequestHeader("Content-Type", "application/json"); } yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { plugin.Puts($"Discord Log Error: {www.error}"); } www.Dispose(); } private void ApplyImageToSign(BasePlayer player, IDiffusionPaintable paintable, BaseEntity entity, byte[] imageBytes, uint textureIndex, bool isHanging, bool quiet = false) { if (entity == null || entity.IsDestroyed) return; imageBytes = ReEncodeImage(imageBytes); DiffusionSize size = plugin.GetDiffusionSize(entity.ShortPrefabName); if (size == null) { plugin.Puts($"Unknown sign size for {entity.ShortPrefabName}"); size = new DiffusionSize(128, 64, 128, 64); } RotateFlipType rotation = RotateFlipType.RotateNoneFlipNone; if (isHanging) rotation = RotateFlipType.RotateNoneFlipX; object rotateObj = Interface.Call("GetImageRotation", entity); if (rotateObj is RotateFlipType) rotation = (RotateFlipType)rotateObj; byte[] resizedBytes = ResizeImage(imageBytes, size.Width, size.Height, size.ImageWidth, size.ImageHeight, false, rotation); if (resizedBytes == null) { if (player != null) player.ChatMessage("Failed to process/resize image."); return; } if (paintable.TextureIds == null) return; uint existingId = paintable.TextureIds[textureIndex]; if (existingId > 0) FileStorage.server.Remove(existingId, FileStorage.Type.png, paintable.NetId); uint newId = FileStorage.server.Store(resizedBytes, FileStorage.Type.png, paintable.NetId, textureIndex); paintable.SetImage(textureIndex, newId); paintable.SendNetworkUpdate(); if (!quiet && player != null && player.IsConnected) player.ChatMessage("Image applied to sign successfully!"); Interface.Oxide.CallHook("OnSignUpdated", entity, player); } private byte[] ResizeImage(byte[] bytes, int width, int height, int targetWidth, int targetHeight, bool enforceJpeg, RotateFlipType rotation) { using (MemoryStream originalMs = new MemoryStream(bytes)) using (MemoryStream resizedMs = new MemoryStream()) { using (Bitmap original = new Bitmap(originalMs)) { if (rotation != RotateFlipType.RotateNoneFlipNone) { original.RotateFlip(rotation); } if (original.Width != targetWidth || original.Height != targetHeight) { using (Bitmap resized = new Bitmap(width, height)) using (Graphics g = Graphics.FromImage(resized)) { g.CompositingQuality = CompositingQuality.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.DrawImage(original, new Rectangle(0, 0, targetWidth, targetHeight)); Color pixel = Color.FromArgb(UnityEngine.Random.Range(0, 255), UnityEngine.Random.Range(0, 255), UnityEngine.Random.Range(0, 255)); resized.SetPixel(width - 1, height - 1, pixel); resized.Save(resizedMs, enforceJpeg ? ImageFormat.Jpeg : ImageFormat.Png); } } else { Color pixel = Color.FromArgb(UnityEngine.Random.Range(0, 255), UnityEngine.Random.Range(0, 255), UnityEngine.Random.Range(0, 255)); original.SetPixel(original.Width - 1, original.Height - 1, pixel); original.Save(resizedMs, enforceJpeg ? ImageFormat.Jpeg : ImageFormat.Png); } } return resizedMs.ToArray(); } } } #endregion #region Wrappers public interface IDiffusionPaintable { uint[] TextureIds { get; } NetworkableId NetId { get; } void SetImage(uint textureIndex, uint id); void SendNetworkUpdate(); bool CanUpdate(BasePlayer player); void EnsureInitialized(); } public class DiffusionSign : IDiffusionPaintable { private Signage _sign; public DiffusionSign(Signage sign) { _sign = sign; } public uint[] TextureIds => _sign.GetTextureCRCs(); public NetworkableId NetId => _sign.net.ID; public void SetImage(uint textureIndex, uint id) { _sign.textureIDs[textureIndex] = id; } public void SendNetworkUpdate() { _sign.SendNetworkUpdate(); } public bool CanUpdate(BasePlayer player) { return _sign.CanUpdateSign(player); } public void EnsureInitialized() { _sign.EnsureInitialized(); } } public class DiffusionFrame : IDiffusionPaintable { private PhotoFrame _frame; public DiffusionFrame(PhotoFrame frame) { _frame = frame; } public uint[] TextureIds => _frame.GetTextureCRCs(); public NetworkableId NetId => _frame.net.ID; public void SetImage(uint textureIndex, uint id) { _frame._overlayTextureCrc = id; } public void SendNetworkUpdate() { _frame.SendNetworkUpdate(); } public bool CanUpdate(BasePlayer player) { return _frame.CanUpdateSign(player); } public void EnsureInitialized() { } } public class DiffusionPumpkin : IDiffusionPaintable { private CarvablePumpkin _pumpkin; public DiffusionPumpkin(CarvablePumpkin pumpkin) { _pumpkin = pumpkin; } public uint[] TextureIds => _pumpkin.textureIDs; public NetworkableId NetId => _pumpkin.net.ID; public void SetImage(uint textureIndex, uint id) { _pumpkin.textureIDs[textureIndex] = id; } public void SendNetworkUpdate() { _pumpkin.SendNetworkUpdate(); } public bool CanUpdate(BasePlayer player) { return _pumpkin.CanUpdateSign(player); } public void EnsureInitialized() { int size = Mathf.Max(_pumpkin.paintableSources.Length, 1); if (_pumpkin.textureIDs == null || _pumpkin.textureIDs.Length != size) Array.Resize(ref _pumpkin.textureIDs, size); } } #endregion } }