C#:
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<string> 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<ulong, PlayerData> Players = new Dictionary<ulong, PlayerData>();
}
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<string> { "nsfw", "nude", "porn" }
};
}
protected override void LoadConfig()
{
base.LoadConfig();
config = Config.ReadObject<PluginConfig>();
if (config.BlockedWords != null)
config.BlockedWords = config.BlockedWords.Distinct().ToList();
SaveConfig();
}
protected override void SaveConfig()
{
Config.WriteObject(config);
}
#endregion
#region Data Structures
private Dictionary<string, DiffusionSize> DiffusionSizePerAsset;
private Dictionary<ulong, float> cooldowns = new Dictionary<ulong, float>();
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<StoredData>("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<DiffusionBehavior>();
behavior.plugin = this;
}
private void Unload()
{
if (behaviorGameObject != null)
UnityEngine.Object.Destroy(behaviorGameObject);
}
private void InitializeDiffusionSizes()
{
DiffusionSizePerAsset = new Dictionary<string, DiffusionSize>
{
["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 <prompt>");
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<string, object>
{
{ "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<object>
{
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<IMultipartFormSection> formData = new List<IMultipartFormSection>();
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
}
}