C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// Reference: System.Drawing
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Plugins.SignArtistClasses;
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using UnityEngine;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
namespace Oxide.Plugins
{
[Info("Sign Artist", "RFC1920", "1.2.0", ResourceId = 992)]
[Description("Allows players with the appropriate permission to import images from the internet on paintable objects")]
/*********************************************************************************
* This plugin was originally created by Bombardir and then maintained by Nogrod.* It was rewritten from scratch by Mughisi on January 12th, 2018.*********************************************************************************/
internal class SignArtist : RustPlugin
{
private Dictionary<ulong, float> cooldowns = new Dictionary<ulong, float>();
private GameObject imageDownloaderGameObject;
private ImageDownloader imageDownloader;
public SignArtistConfig Settings { get; private set; }
public Dictionary<string, ImageSize> ImageSizePerAsset { get; private set; }
/// <summary>
/// Plugin configuration
/// </summary>
public class SignArtistConfig
{
[JsonProperty(PropertyName = "Time in seconds between download requests (0 to disable)")]
public int Cooldown { get; set; }
[JsonProperty(PropertyName = "Maximum concurrent downloads")]
public int MaxActiveDownloads { get; set; }
[JsonProperty(PropertyName = "Maximum distance from the sign")]
public int MaxDistance { get; set; }
[JsonProperty(PropertyName = "Maximum filesize in MB")]
public float MaxSize { get; set; }
[JsonProperty(PropertyName = "Enforce JPG file format")]
public bool EnforceJpeg { get; set; }
[JsonProperty(PropertyName = "JPG image quality (0-100)")]
public int Quality
{
get
{
return quality;
}
set
{
// Validate the value, it can't be less than 0 and not more than 100.if (value >= 0 && value <= 100)
{
quality = value;
}
else
{
// Set the quality to a default value of 85% when an invalid value was specified.quality = value > 100 ? 100 : 85;
}
}
}
[JsonProperty("Enable logging file")]
public bool FileLogging { get; set; }
[JsonProperty("Enable logging console")]
public bool ConsoleLogging { get; set; }
[JsonIgnore]
public float MaxFileSizeInBytes
{
get
{
//return MaxSize * 1024 * 1024;
return MaxSize * 1024 * 1000;
}
}
private int quality = 85;
/// <summary>
/// Creates a default configuration file
/// </summary>
/// <returns>Default config</returns>
public static SignArtistConfig DefaultConfig()
{
return new SignArtistConfig
{
Cooldown = 0,
MaxSize = 1,
MaxDistance = 3,
MaxActiveDownloads = 5,
EnforceJpeg = false,
Quality = 85,
FileLogging = false,
ConsoleLogging = false
};
}
}
/// <summary>
/// A type used to request new images to download./// </summary>
private class DownloadRequest
{
public BasePlayer Sender { get; }
public Signage Sign { get; }
public string Url { get; }
public bool Raw { get; }
public bool Hor { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DownloadRequest" /> class./// </summary>
/// <param name="url">The URL to download the image from.</param>
/// <param name="player">The player that requested the download.</param>
/// <param name="sign">The sign to add the image to.</param>
/// <param name="raw">Should the image be stored with or without conversion to jpeg.</param>
public DownloadRequest(string url, BasePlayer player, Signage sign, bool raw, bool hor)
{
Url = url;
Sender = player;
Sign = sign;
Raw = raw;
Hor = hor;
}
}
/// <summary>
/// A type used to request new images to be restored./// </summary>
private class RestoreRequest
{
public BasePlayer Sender { get; }
public Signage Sign { get; }
public bool Raw { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RestoreRequest" /> class./// </summary>
/// <param name="player">The player that requested the restore.</param>
/// <param name="sign">The sign to restore the image from.</param>
/// <param name="raw">Should the image be stored with or without conversion to jpeg.</param>
public RestoreRequest(BasePlayer player, Signage sign, bool raw)
{
Sender = player;
Sign = sign;
Raw = raw;
}
}
/// <summary>
/// A type used to determine the size of the image for a sign
/// </summary>
public class ImageSize
{
public int Width { get; }
public int Height { get; }
public int ImageWidth { get; }
public int ImageHeight { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ImageSize" /> class./// </summary>
/// <param name="width">The width of the canvas and the image.</param>
/// <param name="height">The height of the canvas and the image.</param>
public ImageSize(int width, int height) : this(width, height, width, height)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageSize" /> class./// </summary>
/// <param name="width">The width of the canvas.</param>
/// <param name="height">The height of the canvas.</param>
/// <param name="imageWidth">The width of the image.</param>
/// <param name="imageHeight">The height of the image.</param>
public ImageSize(int width, int height, int imageWidth, int imageHeight)
{
Width = width;
Height = height;
ImageWidth = imageWidth;
ImageHeight = imageHeight;
}
}
/// <summary>
/// UnityEngine script to be attached to a GameObject to download images and apply them to signs./// </summary>
private class ImageDownloader : MonoBehaviour
{
private byte activeDownloads;
private byte activeRestores;
private readonly SignArtist signArtist = (SignArtist)Interface.Oxide.RootPluginManager.GetPlugin(nameof(SignArtist));
private readonly Queue<DownloadRequest> downloadQueue = new Queue<DownloadRequest>();
private readonly Queue<RestoreRequest> restoreQueue = new Queue<RestoreRequest>();
/// <summary>
/// Queue a new image to download and add to a sign
/// </summary>
/// <param name="url">The URL to download the image from.</param>
/// <param name="player">The player that requested the download.</param>
/// <param name="sign">The sign to add the image to.</param>
/// <param name="raw">Should the image be stored with or without conversion to jpeg.</param>
public void QueueDownload(string url, BasePlayer player, Signage sign, bool raw, bool hor = false)
{
// Check if there is already a request for this sign and show an error if there is.bool existingRequest = downloadQueue.Any(request => request.Sign == sign) || restoreQueue.Any(request => request.Sign == sign);
if (existingRequest)
{
signArtist.SendMessage(player, "ActionQueuedAlready");
return;
}
// Instantiate a new DownloadRequest and add it to the queue.downloadQueue.Enqueue(new DownloadRequest(url, player, sign, raw, hor));
// Attempt to start the next download.StartNextDownload();
}
/// <summary>
/// Attempts to restore a sign./// </summary>
/// <param name="player">The player that requested the restore.</param>
/// <param name="sign">The sign to restore the image from.</param>
/// <param name="raw">Should the image be stored with or without conversion to jpeg.</param>
public void QueueRestore(BasePlayer player, Signage sign, bool raw)
{
// Check if there is already a request for this sign and show an error if there is.bool existingRequest = downloadQueue.Any(request => request.Sign == sign) || restoreQueue.Any(request => request.Sign == sign);
if (existingRequest)
{
signArtist.SendMessage(player, "ActionQueuedAlready");
return;
}
// Instantiate a new RestoreRequest and add it to the queue.restoreQueue.Enqueue(new RestoreRequest(player, sign, raw));
// Attempt to start the next restore.StartNextRestore();
}
/// <summary>
/// Starts the next download if available./// </summary>
/// <param name="reduceCount"></param>
private void StartNextDownload(bool reduceCount = false)
{
// Check if we need to reduce the active downloads counter after a successful or failed download.if (reduceCount)
{
activeDownloads--;
}
// Check if there is still an image in the queue.if (downloadQueue.Count <= 0)
{
activeDownloads = 0;
StopCoroutine(DownloadImage(null));
return;
}
// Check if we don't have the maximum configured amount of downloads running already.if (activeDownloads >= signArtist.Settings.MaxActiveDownloads)
{
activeDownloads = 0;
downloadQueue.Clear();
StopCoroutine(DownloadImage(null));
return;
}
// Increment the active downloads by 1 and start the download process.activeDownloads++;
StartCoroutine(DownloadImage(downloadQueue.Dequeue()));
}
/// <summary>
/// Starts the next restore if available./// </summary>
/// <param name="reduceCount"></param>
private void StartNextRestore(bool reduceCount = false)
{
// Check if we need to reduce the active restores counter after a successful or failed restore.if (reduceCount)
{
activeRestores--;
}
// Check if we don't have the maximum configured amount of restores running already.if (activeRestores >= signArtist.Settings.MaxActiveDownloads)
{
return;
}
// Check if there is still an image in the queue.if (restoreQueue.Count <= 0)
{
return;
}
// Increment the active restores by 1 and start the restore process.activeRestores++;
StartCoroutine(RestoreImage(restoreQueue.Dequeue()));
}
/// <summary>
/// Downloads the image and adds it to the sign./// </summary>
/// <param name="request">The requested <see cref="DownloadRequest"/> instance.</param>
private IEnumerator DownloadImage(DownloadRequest request)
{
if(request == null) yield break;
using (WWW www = new WWW(request.Url))
{
// Wait for the webrequest to complete
yield return www;
// Verify that there is a valid reference to the plugin from this class.if (signArtist == null)
{
throw new NullReferenceException("signArtist");
}
// Verify that the webrequest was successful.if (www.error != null)
{
// The webrequest wasn't successful.Show a message to the player and attempt to start the next download.signArtist.SendMessage(request.Sender, "WebErrorOccurred", www.error);
StartNextDownload(true);
yield break;
}
// Verify that the file doesn't exceed the maximum configured filesize.if (www.bytesDownloaded > signArtist.Settings.MaxFileSizeInBytes)
{
// The file is too large, show a message to the player and attempt to start the next download.signArtist.SendMessage(request.Sender, "FileTooLarge", signArtist.Settings.MaxSize);
StartNextDownload(true);
yield break;
}
// Get the bytes array for the image from the webrequest and lookup the target image size for the targeted sign.byte[] imageBytes;
if (request.Raw)
{
imageBytes = www.bytes;
}
else
{
imageBytes = GetImageBytes(www);
}
ImageSize size = GetImageSizeFor(request.Sign);
// Verify that we have image size data for the targeted sign.if (size == null)
{
// No data was found, show a message to the player and print a detailed message to the server console and attempt to start the next download.signArtist.SendMessage(request.Sender, "ErrorOccurred");
signArtist.PrintWarning($"Couldn't find the required image size for {request.Sign.PrefabName}, please report this in the plugin's thread.");
StartNextDownload(true);
yield break;
}
// Get the bytes array for the resized image for the targeted sign.byte[] resizedImageBytes = imageBytes.ResizeImage(size.Width, size.Height, size.ImageWidth, size.ImageHeight, signArtist.Settings.EnforceJpeg && !request.Raw);
// Flip the image horizontally if needed (sign.hanging only)
if(request.Hor)
{
var ms = new MemoryStream(resizedImageBytes);
var mo = new MemoryStream();
var img = Image.FromStream(ms);
img.RotateFlip(RotateFlipType.RotateNoneFlipX);
img.Save(mo,img.RawFormat);
resizedImageBytes = mo.ToArray();
mo = null;
ms = null;
img = null;
}
// Verify that the resized file doesn't exceed the maximum configured filesize.if (resizedImageBytes.Length > signArtist.Settings.MaxFileSizeInBytes)
{
// The file is too large, show a message to the player and attempt to start the next download.signArtist.SendMessage(request.Sender, "FileTooLarge", signArtist.Settings.MaxSize);
StartNextDownload(true);
yield break;
}
// 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.net.ID);
}
// Create the image on the filestorage and send out a network update for the sign.request.Sign.textureID = FileStorage.server.Store(resizedImageBytes, FileStorage.Type.png, request.Sign.net.ID);
request.Sign.SendNetworkUpdate();
// Notify the player that the image was loaded.signArtist.SendMessage(request.Sender, "ImageLoaded");
// Call the Oxide hook 'OnSignUpdated' to notify other plugins of the update event.Interface.Oxide.CallHook("OnSignUpdated", request.Sign, request.Sender);
// Check if logging to console is enabled.if (signArtist.Settings.ConsoleLogging)
{
// Console logging is enabled, show a message in the server console.signArtist.Puts(signArtist.GetTranslation("LogEntry"), request.Sender.displayName,
request.Sender.userID, request.Sign.textureID, request.Sign.ShortPrefabName, request.Url);
}
// Check if logging to file is enabled.if (signArtist.Settings.FileLogging)
{
// File logging is enabled, add an entry to the logfile.signArtist.LogToFile("log",
string.Format(signArtist.GetTranslation("LogEntry"), request.Sender.displayName,
request.Sender.userID, request.Sign.textureID, request.Sign.ShortPrefabName,
request.Url), signArtist);
}
// Attempt to start the next download.StartNextDownload(true);
}
}
/// <summary>
/// Restores the image and adds it to the sign again./// </summary>
/// <param name="request">The requested <see cref="RestoreRequest"/> instance.</param>
/// <returns></returns>
private IEnumerator RestoreImage(RestoreRequest request)
{
// Verify that there is a valid reference to the plugin from this class.if (signArtist == null)
{
throw new NullReferenceException("signArtist");
}
byte[] imageBytes;
// Check if the sign already has a texture assigned to it.if (request.Sign.textureID == 0)
{
// No texture was previously assigned, show a message to the player.signArtist.SendMessage(request.Sender, "RestoreErrorOccurred");
StartNextRestore(true);
yield break;
}
// Cache the byte array of the currently stored file.imageBytes = FileStorage.server.Get(request.Sign.textureID, FileStorage.Type.png, request.Sign.net.ID);
ImageSize size = GetImageSizeFor(request.Sign);
// Verify that we have image size data for the targeted sign.if (size == null)
{
// No data was found, show a message to the player and print a detailed message to the server console and attempt to start the next download.signArtist.SendMessage(request.Sender, "ErrorOccurred");
signArtist.PrintWarning($"Couldn't find the required image size for {request.Sign.PrefabName}, please report this in the plugin's thread.");
StartNextRestore(true);
yield break;
}
// Remove the texture from the FileStorage.FileStorage.server.Remove(request.Sign.textureID, FileStorage.Type.png, request.Sign.net.ID);
// Get the bytes array for the resized image for the targeted sign.byte[] resizedImageBytes = imageBytes.ResizeImage(size.Width, size.Height, size.ImageWidth, size.ImageHeight, signArtist.Settings.EnforceJpeg && !request.Raw);
// Create the image on the filestorage and send out a network update for the sign.request.Sign.textureID = FileStorage.server.Store(resizedImageBytes, FileStorage.Type.png, request.Sign.net.ID);
request.Sign.SendNetworkUpdate();
// Notify the player that the image was loaded.signArtist.SendMessage(request.Sender, "ImageRestored");
// 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.StartNextRestore(true);
}
/// <summary>
/// Gets the target image size for a <see cref="Signage"/>./// </summary>
/// <param name="signage"></param>
private ImageSize GetImageSizeFor(Signage signage)
{
if (signArtist.ImageSizePerAsset.ContainsKey(signage.PrefabName))
{
return signArtist.ImageSizePerAsset[signage.PrefabName];
}
return null;
}
/// <summary>
/// Converts the <see cref="Texture2D"/> from the webrequest to a <see cref="byte"/> array./// </summary>
/// <param name="www">The completed webrequest.</param>
private byte[] GetImageBytes(WWW www)
{
Texture2D texture = www.texture;
byte[] image;
if (texture.format == TextureFormat.ARGB32 && !signArtist.Settings.EnforceJpeg)
{
image = texture.EncodeToPNG();
}
else
{
image = texture.EncodeToJPG(signArtist.Settings.Quality);
}
DestroyImmediate(texture);
return image;
}
}
/// <summary>
/// Oxide hook that is triggered when the plugin is loaded./// </summary>
private void Init()
{
// Register all the permissions used by the plugin
permission.RegisterPermission("signartist.file", this);
permission.RegisterPermission("signartist.ignorecd", this);
permission.RegisterPermission("signartist.ignoreowner", this);
permission.RegisterPermission("signartist.raw", this);
permission.RegisterPermission("signartist.restore", this);
permission.RegisterPermission("signartist.restoreall", this);
permission.RegisterPermission("signartist.text", this);
permission.RegisterPermission("signartist.url", this);
AddCovalenceCommand("sil", "SilCommand");
AddCovalenceCommand("silt", "SiltCommand");
AddCovalenceCommand("silrestore", "RestoreCommand");
// Initialize the dictionary with all paintable object assets and their target sizes
ImageSizePerAsset = new Dictionary<string, ImageSize>()
{
// Picture Frames
["assets/prefabs/deployable/signs/sign.pictureframe.landscape.prefab"] = new ImageSize(256, 128), // Landscape Picture Frame
["assets/prefabs/deployable/signs/sign.pictureframe.portrait.prefab"] = new ImageSize(128, 256), // Portrait Picture Frame
["assets/prefabs/deployable/signs/sign.pictureframe.tall.prefab"] = new ImageSize(128, 512), // Tall Picture Frame
["assets/prefabs/deployable/signs/sign.pictureframe.xl.prefab"] = new ImageSize(512, 512), // XL Picture Frame
["assets/prefabs/deployable/signs/sign.pictureframe.xxl.prefab"] = new ImageSize(1024, 512), // XXL Picture Frame
// Wooden Signs
["assets/prefabs/deployable/signs/sign.small.wood.prefab"] = new ImageSize(128, 64), // Small Wooden Sign
["assets/prefabs/deployable/signs/sign.medium.wood.prefab"] = new ImageSize(256, 128), // Wooden Sign
["assets/prefabs/deployable/signs/sign.large.wood.prefab"] = new ImageSize(256, 128), // Large Wooden Sign
["assets/prefabs/deployable/signs/sign.huge.wood.prefab"] = new ImageSize(512, 128), // Huge Wooden Sign
// Banners
["assets/prefabs/deployable/signs/sign.hanging.banner.large.prefab"] = new ImageSize(64, 256), // Large Banner Hanging
["assets/prefabs/deployable/signs/sign.pole.banner.large.prefab"] = new ImageSize(64, 256), // Large Banner on Pole
// Hanging Signs
["assets/prefabs/deployable/signs/sign.hanging.prefab"] = new ImageSize(128, 256), // Two Sided Hanging Sign
["assets/prefabs/deployable/signs/sign.hanging.ornate.prefab"] = new ImageSize(256, 128), // Two Sided Ornate Hanging Sign
// Town Signs
["assets/prefabs/deployable/signs/sign.post.single.prefab"] = new ImageSize(128, 64), // Single Sign Post
["assets/prefabs/deployable/signs/sign.post.double.prefab"] = new ImageSize(256, 256), // Double Sign Post
["assets/prefabs/deployable/signs/sign.post.town.prefab"] = new ImageSize(256, 128), // One Sided Town Sign Post
["assets/prefabs/deployable/signs/sign.post.town.roof.prefab"] = new ImageSize(256, 128), // Two Sided Town Sign Post
// Other paintable assets
["assets/prefabs/deployable/spinner_wheel/spinner.wheel.deployed.prefab"] = new ImageSize(512, 512, 285, 285), // Spinning Wheel
};
}
/// <summary>
/// Oxide hook that is triggered automatically after it has been loaded to initialize the messages for the Lang API./// </summary>
protected override void LoadDefaultMessages()
{
// Register all messages used by the plugin in the Lang API.lang.RegisterMessages(new Dictionary<string, string>
{
// Messages used throughout the plugin.["WebErrorOccurred"] = "Failed to download the image! Error {0}.",
["FileTooLarge"] = "The file exceeds the maximum file size of {0}Mb.",
["ErrorOccurred"] = "An unknown error has occured, if this error keeps occuring please notify the server admin.",
["RestoreErrorOccurred"] = "Can't restore the sign because no texture is assigned to it.",
["DownloadQueued"] = "Your image was added to the download queue!",
["RestoreQueued"] = "Your sign was added to the restore queue!",
["RestoreBatchQueued"] = "You added all {0} signs to the restore queue!",
["ImageLoaded"] = "The image was successfully loaded to the sign!",
["ImageRestored"] = "The image was successfully restored for the sign!",
["LogEntry"] = "Player `{0}` (SteamId: {1}) loaded {2} into {3} from {4}",
["NoSignFound"] = "Unable to find a sign! Make sure you are looking at one and that you are not too far away from it.",
["Cooldown"] = "You can't use the command yet! Remaining cooldown: {0}.",
["SignNotOwned"] = "You can't change this sign as it is protected by a tool cupboard.",
["ActionQueuedAlready"] = "An action has already been queued for this sign, please wait for this action to complete.",
["SyntaxSilCommand"] = "Syntax error!\nSyntax: /sil <url> [raw]",
["SyntaxSiltCommand"] = "Syntax error!\nSyntax: /silt <message> [<fontsize:number>] [<color:hex value>] [<bgcolor:hex value>] [raw]",
["NoPermission"] = "You don't have permission to use this command.",
["NoPermissionFile"] = "You don't have permission to use images from the server's filesystem.",
["NoPermissionRaw"] = "You don't have permission to use raw images, loading normally instead.",
["NoPermissionRestoreAll"] = "You don't have permission to use restore all signs at once.",
// Cooldown formatting 'translations'.["day"] = "day",
["days"] = "days",
["hour"] = "hour",
["hours"] = "hours",
["minute"] = "minute",
["minutes"] = "minutes",
["second"] = "second",
["seconds"] = "seconds",
["and"] = "and"
}, this);
}
/// <summary>
/// Oxide hook that is triggered to automatically load the configuration file./// </summary>
protected override void LoadConfig()
{
base.LoadConfig();
Settings = Config.ReadObject<SignArtistConfig>();
SaveConfig();
}
/// <summary>
/// Oxide hook that is triggered to automatically load the default configuration file when no file exists./// </summary>
protected override void LoadDefaultConfig()
{
Settings = SignArtistConfig.DefaultConfig();
}
/// <summary>
/// Oxide hook that is triggered to save the configuration file./// </summary>
protected override void SaveConfig()
{
Config.WriteObject(Settings);
}
/// <summary>
/// Oxide hook that is triggered when the server has fully initialized./// </summary>
private void OnServerInitialized()
{
// Create a new GameObject and attach the UnityEngine script to it for handling the image downloads.imageDownloaderGameObject = new GameObject("ImageDownloader");
imageDownloader = imageDownloaderGameObject.AddComponent<ImageDownloader>();
}
/// <summary>
/// Oxide hook that is triggered when the plugin is unloaded./// </summary>
private void Unload()
{
// Destroy the created GameObject and cleanup.UnityEngine.Object.Destroy(imageDownloaderGameObject);
imageDownloader = null;
cooldowns = null;
}
/// <summary>
/// Handles the /sil command./// </summary>
/// <param name="iplayer">The player that has executed the command.</param>
/// <param name="command">The name of the command that was executed.</param>
/// <param name="args">All arguments that were passed with the command.</param>
[Command("sil"), Permission("signartist.url")]
private void SilCommand(IPlayer iplayer, string command, string[] args)
{
var player = iplayer.Object as BasePlayer;
// Verify if the correct syntax is used.if (args.Length < 1)
{
// Invalid syntax was used, show an error message to the player.SendMessage(player, "SyntaxSilCommand");
return;
}
// Verify if the player has permission to use this command.if (!HasPermission(player, "signartist.url"))
{
// The player doesn't have permission to use this command, show an error message.SendMessage(player, "NoPermission");
return;
}
// Verify that the command isn't on cooldown for the user.if (HasCooldown(player))
{
// The command is still on cooldown for the player, show an error message.SendMessage(player, "Cooldown", FormatCooldown(GetCooldown(player)));
return;
}
// Check if the player is looking at a sign.Signage sign;
if (!IsLookingAtSign(player, out sign))
{
// The player isn't looking at a sign or is too far away from it, show an error message.SendMessage(player, "NoSignFound");
return;
}
// Check if the player is able to update the sign.if (!CanChangeSign(player, sign))
{
// The player isn't able to update the sign, show an error message.SendMessage(player, "SignNotOwned");
return;
}
// Check if the player wants to add the image from the server's filesystem and has the permission to do so.if (args[0].StartsWith("file://") && !HasPermission(player, "signartist.file"))
{
// The player doesn't have permission for this, show an error message.SendMessage(player, "NoPermissionFile");
return;
}
// Check if the player wants to add the image as a raw image and has the permission to do so.bool raw = args.Length > 1 && args[1].Equals("raw", StringComparison.OrdinalIgnoreCase);
if (raw && !HasPermission(player, "signartist.raw"))
{
// The player doesn't have permission for this, show a message and disable raw.SendMessage(player, "NoPermissionRaw");
raw = false;
}
// Some signs paste in reverse, so we'll check and set a var to flip it
bool hor = IsSignHorizontal(sign);
// Notify the player that it is added to the queue.SendMessage(player, "DownloadQueued");
// Queue the download of the specified image.imageDownloader.QueueDownload(args[0], player, sign, raw, hor);
// Call external hook
Interface.Oxide.CallHook("OnImagePost", player, args[0]);
// Set the cooldown on the command for the player if the cooldown setting is enabled.SetCooldown(player);
}
/// <summary>
/// Handles the /silt command
/// </summary>
/// <param name="iplayer">The player that has executed the command.</param>
/// <param name="command">The name of the command that was executed.</param>
/// <param name="args">All arguments that were passed with the command.</param>
[Command("silt"), Permission("signartist.text")]
private void SiltCommand(IPlayer iplayer, string command, string[] args)
{
var player = iplayer.Object as BasePlayer;
// Verify if the correct syntax is used.if (args.Length < 1)
{
// Invalid syntax was used, show an error message to the player.SendMessage(player, "SyntaxSiltCommand");
return;
}
// Verify if the player has permission to use this command.if (!HasPermission(player, "signartist.text"))
{
// The player doesn't have permission to use this command, show an error message.SendMessage(player, "NoPermission");
return;
}
// Verify that the command isn't on cooldown for the user.if (HasCooldown(player))
{
// The command is still on cooldown for the player, show an error message.SendMessage(player, "Cooldown", FormatCooldown(GetCooldown(player)));
return;
}
// Check if the player is looking at a sign.Signage sign;
if (!IsLookingAtSign(player, out sign))
{
// The player isn't looking at a sign or is too far away from it, show an error message.SendMessage(player, "NoSignFound");
return;
}
// Check if the player is able to update the sign.if (!CanChangeSign(player, sign))
{
// The player isn't able to update the sign, show an error message.SendMessage(player, "SignNotOwned");
return;
}
// Build the URL for the /silt command
string message = args[0].EscapeForUrl();
int fontsize = 80;
string color = "000";
string bgcolor = "0FFF";
string format = "png32";
// Replace the default fontsize if the player specified one.if (args.Length > 1)
{
int.TryParse(args[1], out fontsize);
}
// Replace the default color if the player specified one.if (args.Length > 2)
{
color = args[2].Trim(' ', '#');
}
// Replace the default color if the player specified one.if (args.Length > 3)
{
bgcolor = args[3].Trim(' ', '#');
}
// Check if the player wants to add the image as a raw image and has the permission to do so.bool raw = args.Length > 4 && args[4].Equals("raw", StringComparison.OrdinalIgnoreCase);
if (raw && !HasPermission(player, "signartist.raw"))
{
// The player doesn't have permission for this, show a message and disable raw.SendMessage(player, "NoPermissionRaw");
raw = false;
}
// Correct the format if required
if (Settings.EnforceJpeg)
{
format = "jpg";
}
// Get the size for the image
ImageSize size = null;
if (ImageSizePerAsset.ContainsKey(sign.PrefabName))
{
size = ImageSizePerAsset[sign.PrefabName];
}
// Verify that we have image size data for the targeted sign.if (size == null)
{
// No data was found, show a message to the player and print a detailed message to the server console and attempt to start the next download.SendMessage(player, "ErrorOccurred");
PrintWarning($"Couldn't find the required image size for {sign.PrefabName}, please report this in the plugin's thread.");
return;
}
// Combine all the values into the url;
string url = $"http://assets.imgix.net/~text?fm={format}&txtalign=middle,center&txtsize={fontsize}&txt={message}&w={size.ImageWidth}&h={size.ImageHeight}&txtclr={color}&bg={bgcolor}";
// Notify the player that it is added to the queue.SendMessage(player, "DownloadQueued");
// Some signs paste in reverse, so we'll check and set a var to flip it
bool hor = IsSignHorizontal(sign);
// Queue the download of the specified image.imageDownloader.QueueDownload(url, player, sign, raw, hor);
// Call external hook
Interface.Oxide.CallHook("OnImagePost", player, url);
// Set the cooldown on the command for the player if the cooldown setting is enabled.SetCooldown(player);
}
/// <summary>
/// Handles the /silrestore command
/// </summary>
/// <param name="iplayer">The player that has executed the command.</param>
/// <param name="command">The name of the command that was executed.</param>
/// <param name="args">All arguments that were passed with the command.</param>
[Command("silrestore"), Permission("signartist.raw")]
private void RestoreCommand(IPlayer iplayer, string command, string[] args)
{
var player = iplayer.Object as BasePlayer;
// Verify if the player has permission to use this command.if (!HasPermission(player, "signartist.restore"))
{
// The player doesn't have permission to use this command, show an error message.SendMessage(player, "NoPermission");
return;
}
// Check if the user wants to restore the sign or signs as raw images and has the permission to do so
bool raw = string.IsNullOrEmpty(args.FirstOrDefault(s => s.Equals("raw", StringComparison.OrdinalIgnoreCase)));
if (raw && !HasPermission(player, "signartist.raw"))
{
// The player doesn't have permission for this, show a message and disable raw.SendMessage(player, "NoPermissionRaw");
raw = false;
}
// Check if the user wants to restore all signs and has the permission to do so.bool all = args.Any(s => s.Equals("all", StringComparison.OrdinalIgnoreCase));
if (all && !HasPermission(player, "signartist.restoreall"))
{
// The player doesn't have permission for this, show a message and disable raw.SendMessage(player, "NoPermissionRestoreAll");
return;
}
// Check if the player is looking at a sign if not all signs should be restored.if (!all)
{
Signage sign;
if (!IsLookingAtSign(player, out sign))
{
// The player isn't looking at a sign or is too far away from it, show an error message.SendMessage(player, "NoSignFound");
return;
}
// Notify the player that it is added to the queue.SendMessage(player, "RestoreQueued");
// Queue the restore of the image on the specified sign.imageDownloader.QueueRestore(player, sign, raw);
return;
}
// The player wants to restore all signs.Signage[] allSigns = UnityEngine.Object.FindObjectsOfType<Signage>();
// Notify the player that they were added to the queue
SendMessage(player, "RestoreBatchQueued", allSigns.Length);
// Queue every sign to be restored.foreach (Signage sign in allSigns)
{
imageDownloader.QueueRestore(player, sign, raw);
}
}
/// <summary>
/// Check if the given <see cref="BasePlayer"/> is able to use the command./// </summary>
/// <param name="player">The player to check.</param>
private bool HasCooldown(BasePlayer player)
{
// Check if cooldown is enabled.if (Settings.Cooldown <= 0)
{
return false;
}
// Check if cooldown is ignored for the player.if (HasPermission(player, "signartist.ignorecd"))
{
return false;
}
// Make sure there is an entry for the player in the dictionary.if (!cooldowns.ContainsKey(player.userID))
{
cooldowns.Add(player.userID, 0);
}
// Check if the command is on cooldown or not.return Time.realtimeSinceStartup - cooldowns[player.userID] < Settings.Cooldown;
}
/// <summary>
/// Returns the cooldown in seconds for the given <see cref="BasePlayer"/>./// </summary>
/// <param name="player">The player to obtain the cooldown of.</param>
private float GetCooldown(BasePlayer player)
{
return Settings.Cooldown - (Time.realtimeSinceStartup - cooldowns[player.userID]);
}
/// <summary>
/// Sets the last use for the cooldown handling of the command for the given <see cref="BasePlayer"/>./// </summary>
/// <param name="player">The player to put the command on cooldown for.</param>
private void SetCooldown(BasePlayer player)
{
// Check if cooldown is enabled.if (Settings.Cooldown <= 0)
{
return;
}
// Check if cooldown is ignored for the player.if (HasPermission(player, "signartist.ignorecd"))
{
return;
}
// Make sure there is an entry for the player in the dictionary.if (!cooldowns.ContainsKey(player.userID))
{
cooldowns.Add(player.userID, 0);
}
// Set the last use
cooldowns[player.userID] = Time.realtimeSinceStartup;
}
/// <summary>
/// Returns a formatted string for the given cooldown./// </summary>
/// <param name="seconds">The cooldown in seconds.</param>
private string FormatCooldown(float seconds)
{
// Create a new TimeSpan from the remaining cooldown.TimeSpan t = TimeSpan.FromSeconds(seconds);
List<string> output = new List<string>();
// Check if it is more than a single day and add it to the result.if (t.Days >= 1)
{
output.Add($"{t.Days} {(t.Days > 1 ? "days" : "day")}");
}
// Check if it is more than an hour and add it to the result.if (t.Hours >= 1)
{
output.Add($"{t.Hours} {(t.Hours > 1 ? "hours" : "hour")}");
}
// Check if it is more than a minute and add it to the result.if (t.Minutes >= 1)
{
output.Add($"{t.Minutes} {(t.Minutes > 1 ? "minutes" : "minute")}");
}
// Check if there is more than a second and add it to the result.if (t.Seconds >= 1)
{
output.Add($"{t.Seconds} {(t.Seconds > 1 ? "seconds" : "second")}");
}
// Format the result and return it.return output.Count >= 3 ? output.ToSentence().Replace(" and", ", and") : output.ToSentence();
}
/// <summary>
/// Checks if the <see cref="BasePlayer"/> is looking at a valid <see cref="Signage"/> object./// </summary>
/// <param name="player">The player to check.</param>
/// <param name="sign">When this method returns, contains the <see cref="Signage"/> the player contained in <paramref name="player" /> is looking at, or null if the player isn't looking at a sign.</param>
private bool IsLookingAtSign(BasePlayer player, out Signage sign)
{
RaycastHit hit;
sign = null;
// Get the object that is in front of the player within the maximum distance set in the config.//if (Physics.Raycast(player.eyes.HeadRay(), out hit))//, Settings.MaxDistance))
if (Physics.Raycast(player.eyes.HeadRay(), out hit, Settings.MaxDistance))
{
// Attempt to grab the Signage entity, if there is none this will set the sign to null,
// otherwise this will set it to the sign the player is looking at.sign = hit.GetEntity() as Signage;
}
// Return true or false depending on if we found a sign.return sign != null;
}
/// <summary>
/// Checks if the <see cref="BasePlayer"/> is allowed to change the drawing on the <see cref="Signage"/> object./// </summary>
/// <param name="player">The player to check.</param>
/// <param name="sign">The sign to check.</param>
/// <returns></returns>
private bool CanChangeSign(BasePlayer player, Signage sign)
{
return sign.CanUpdateSign(player) || HasPermission(player, "signartist.ignoreowner");
}
/// <summary>
/// Checks if the given <see cref="BasePlayer"/> has the specified permission./// </summary>
/// <param name="player">The player to check a permission on.</param>
/// <param name="perm">The permission to check for.</param>
private bool HasPermission(BasePlayer player, string perm)
{
return permission.UserHasPermission(player.UserIDString, perm);
}
/// <summary>
/// Send a formatted message to a single player./// </summary>
/// <param name="player">The player to send the message to.</param>
/// <param name="key">The key of the message from the Lang API to get the message
// ... [Code preview truncated due to length. Full source in attached file] ...