Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of VariantWeaver v1.4.1
plugins/VariantWeaver/VariantWeaver.Core.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("VariantMods")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.4.1.0")] [assembly: AssemblyInformationalVersion("1.4.1")] [assembly: AssemblyProduct("WeaverEditor")] [assembly: AssemblyTitle("WeaverEditor Core")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.4.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace VariantWeaver.Core { [Serializable] public sealed class ActionMessageCatalog { public int schema = 1; public List<ActionMessageReplacement> messages = new List<ActionMessageReplacement>(); } [Serializable] public sealed class ActionMessageReplacement { public string original = ""; public string replacement = ""; } public sealed class ActionMessageOverrides { private sealed class Template { internal string original; internal string replacement; internal string[] literals; internal string[] names; internal int literalLength; } public const int MaximumEntries = 2048; public const int MaximumText = 4096; public const int MaximumBytes = 131072; private readonly Dictionary<string, string> exact = new Dictionary<string, string>(StringComparer.Ordinal); private readonly List<Template> templates = new List<Template>(); public ActionMessageOverrides(IEnumerable<KeyValuePair<string, string>> values) { int num = 0; foreach (KeyValuePair<string, string> item in (values ?? Enumerable.Empty<KeyValuePair<string, string>>()).Take(2048)) { if (!ValidText(item.Key) || !ValidText(item.Value)) { continue; } Template template = Parse(item.Key, item.Value); if (!item.Key.Contains("{value") || template != null) { num += Encoding.UTF8.GetByteCount(item.Key + item.Value); if (num > 131072) { break; } if (template == null) { exact[item.Key] = item.Value; } else if (template.literalLength >= 8 && template.names.Length <= 8) { templates.Add(template); } } } templates.Sort((Template left, Template right) => right.literalLength.CompareTo(left.literalLength)); } public static Dictionary<string, string> Read(ActionMessageCatalog catalog) { if (catalog == null || catalog.schema != 1 || catalog.messages == null || catalog.messages.Count > 2048) { throw new InvalidOperationException("The message file has an unsupported format or too many entries."); } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); int num = 0; foreach (ActionMessageReplacement message in catalog.messages) { if (message == null || !ValidText(message.original)) { throw new InvalidOperationException("Each message needs an original text up to 4096 characters."); } if (!string.IsNullOrWhiteSpace(message.replacement) && !(message.replacement == message.original)) { if (!ValidText(message.replacement)) { throw new InvalidOperationException("A replacement message is empty, too long or has unsupported control characters."); } Template template = Parse(message.original, message.replacement); if (message.original.Contains("{value") && template == null) { throw new InvalidOperationException("A message template has an invalid or repeated value placeholder."); } if (template != null && (template.literalLength < 8 || template.names.Length > 8)) { throw new InvalidOperationException("A message template needs at least eight fixed visible characters and no more than eight values."); } if (!ValidReplacement(message.replacement, template?.names ?? Array.Empty<string>())) { throw new InvalidOperationException("Use only the original message's value placeholders, {reason} or {action}."); } if (dictionary.ContainsKey(message.original)) { throw new InvalidOperationException("The message file has duplicate replacements for the same original."); } num += Encoding.UTF8.GetByteCount(message.original + message.replacement); if (num > 131072) { throw new InvalidOperationException("Changed message text exceeds 128 KiB."); } dictionary.Add(message.original, message.replacement); } } return dictionary; } public string Resolve(string message, string action = "") { if (string.IsNullOrEmpty(message) || message.Length > 4096) { return message; } if (exact.TryGetValue(message, out var value)) { return Expand(value, message, action, Array.Empty<string>(), Array.Empty<string>()); } foreach (Template template in templates) { if (!message.StartsWith(template.literals[0], StringComparison.Ordinal) || !message.EndsWith(template.literals[template.literals.Length - 1], StringComparison.Ordinal)) { continue; } string[] array = new string[template.names.Length]; int num = template.literals[0].Length; bool flag = true; for (int i = 0; i < array.Length; i++) { string text = template.literals[i + 1]; int num2 = ((text.Length == 0) ? message.Length : message.IndexOf(text, num, StringComparison.Ordinal)); if (num2 < num) { flag = false; break; } array[i] = message.Substring(num, num2 - num); num = num2 + text.Length; } if (flag && num == message.Length) { return Expand(template.replacement, message, action, template.names, array); } } return message; } private static Template Parse(string original, string replacement) { List<string> list = new List<string>(); List<string> list2 = new List<string>(); int num = 0; for (int i = 0; i < original.Length; i++) { if (!original.AsSpan(i).StartsWith("{value".AsSpan(), StringComparison.Ordinal)) { continue; } int num2 = original.IndexOf('}', i + 6); if (num2 >= 0 && int.TryParse(original.Substring(i + 6, num2 - i - 6), out var result) && result >= 1 && result <= 8) { string item = original.Substring(i + 1, num2 - i - 1); if (list.Contains(item) || (list.Count > 0 && i == num)) { return null; } list2.Add(original.Substring(num, i - num)); list.Add(item); i = num2; num = num2 + 1; } } if (list.Count == 0) { return null; } list2.Add(original.Substring(num)); return new Template { original = original, replacement = replacement, names = list.ToArray(), literals = list2.ToArray(), literalLength = list2.Sum((string value) => value.Count((char character) => !char.IsWhiteSpace(character))) }; } private static bool ValidReplacement(string value, string[] names) { for (int i = 0; i < value.Length; i++) { if (value[i] == '{') { int num = value.IndexOf('}', i + 1); if (num < 0) { return false; } string text = value.Substring(i + 1, num - i - 1); if (text != "reason" && text != "action" && !names.Contains(text)) { return false; } i = num; } } return true; } private static string Expand(string template, string reason, string action, string[] names, string[] values) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < template.Length; i++) { if (template[i] == '{') { int num = template.IndexOf('}', i + 1); if (num >= 0) { string text = template.Substring(i + 1, num - i - 1); int num2 = Array.IndexOf(names, text); string text2 = ((text == "reason") ? reason : ((text == "action") ? action : ((num2 >= 0) ? values[num2] : null))); if (text2 != null) { stringBuilder.Append(text2); i = num; } else { stringBuilder.Append(template[i]); } } else { stringBuilder.Append(template[i]); } } else { stringBuilder.Append(template[i]); } if (stringBuilder.Length > 4096) { return reason; } } return stringBuilder.ToString(); } private static bool ValidText(string text) { if (!string.IsNullOrWhiteSpace(text) && text.Length <= 4096) { return !text.Any((char value) => char.IsControl(value) && value != '\n' && value != '\r' && value != '\t'); } return false; } } public static class ActionMessagePresentation { private static readonly (string original, string replacement)[] messages = new(string, string)[174] { ("No access.", "You don't have permission for this. Ask an admin."), ("Permission denied.", "You don't have permission for this. Ask an admin."), ("Your player is not ready.", "Your character is still loading. Wait a moment and try again."), ("Player is not ready.", "Your character is still loading. Wait a moment and try again."), ("Local player is not ready.", "Your character is still loading. Wait a moment and try again."), ("Player is unavailable.", "That player isn't available. Refresh the player list and try again."), ("Player not found.", "That player isn't connected. Refresh the player list."), ("Travel is unavailable right now.", "Travel isn't ready yet. Wait a moment and try again."), ("Travel unavailable.", "Travel isn't ready yet. Wait a moment and try again."), ("Public travel is disabled by the server.", "Player travel is turned off on this server. Ask an admin."), ("You cannot use WeaverEditor travel right now.", "Travel can't start yet. Finish your current action and try again."), ("You cannot travel while dead.", "Respawn before travelling."), ("Your previous teleport is still finishing.", "Your last teleport is still loading. Wait for it to finish, then try again."), ("Your previous WeaverEditor teleport is still finishing.", "Your last teleport is still loading. Wait for it to finish, then try again."), ("Another travel handler is still finishing.", "Another teleport is already running. Wait for it to finish, then try again."), ("Your character is not ready for travel yet.", "Your character is still loading. Wait a moment before travelling."), ("This game version cannot safely start WeaverEditor travel.", "Travel is disabled for this game version. Ask an admin to check for a WeaverEditor update."), ("The destination did not load in time. Returning to your departure point.", "The destination took too long to load. You're being returned to where you started."), ("The WeaverEditor teleport could not start.", "The teleport couldn't start. Wait a moment and try again."), ("WeaverEditor travel did not finish at its destination.", "The destination didn't finish loading. Try travelling again once the area has loaded."), ("Invalid WeaverEditor destination.", "This destination isn't set up correctly. Ask an admin to check it."), ("Invalid destination.", "This destination isn't set up correctly. Ask an admin to check it."), ("Invalid WeaverEditor travel confirmation.", "That travel request is out of date. Start the teleport again."), ("Invalid WeaverEditor travel state.", "That travel request is out of date. Start the teleport again."), ("This travel request expired. Please try again.", "That travel request expired. Start the teleport again."), ("This travel request is no longer active.", "That travel request has ended. Start a new teleport if you still want to travel."), ("This portal is unavailable.", "This portal is turned off or no longer exists. Ask an admin to check it."), ("Warp missing. Type /warp to refresh the list.", "That warp no longer exists. Type /warp to refresh the list."), ("Move within five metres of the portal.", "Move closer to the middle of the portal, then press E."), ("This portal's destination is missing. Ask an admin to link it again.", "This portal has no destination. Ask an admin to link it to a warp."), ("You are carrying too much to travel.", "You're carrying too much to travel. Put some items away first."), ("No WeaverEditor return point. Use a WeaverEditor teleport first; regular portals do not count.", "There's no return point yet. Use a WeaverEditor warp first. Regular portals don't create a return point."), ("Wait a moment before using /return again.", "Your last /return request is still being handled. Wait a moment and try again."), ("Backpack API unavailable.", "Your backpack mod can't be checked right now. Remove the backpack before travelling, or ask an admin."), ("Equipped backpack API unavailable.", "Your equipped backpack can't be checked right now. Remove it before travelling, or ask an admin."), ("Backpack contents API unavailable.", "Your backpack contents can't be checked right now. Remove the backpack before travelling, or ask an admin."), ("Backpack contents unavailable.", "Your backpack contents haven't loaded. Wait a moment, or remove it before travelling."), ("Backpack contents changed during the check.", "Your backpack changed during the travel check. Close it and try again."), ("Companion inventory is not ready.", "Your companion's inventory is still loading. Wait a moment and try again."), ("Companion storage is not ready.", "Your companion's storage is still loading. Wait a moment and try again."), ("Guilds is installed; its server membership API is not ready.", "Guild membership is still loading. Wait a moment and try again."), ("Character saving is unavailable.", "Your character can't be saved right now. Wait a moment before trying again. Your transfer is held safely."), ("The character profile is unavailable.", "Your character save isn't ready. Wait a moment and try again."), ("The character save failed. Your transfer will retry safely.", "Your character couldn't be saved yet. The transfer is reserved and will retry."), ("The character save could not be confirmed. Your transfer will retry safely.", "Your character save hasn't been confirmed yet. The transfer is reserved and will retry."), ("Server Characters is finishing another save. The transfer will retry.", "Your character is already being saved. This transfer will retry when that finishes."), ("Server Characters is recovering the connection. Wait before trading.", "Your connection is being restored. Wait until you're reconnected before trading."), ("Server Characters is handling a character death. The transfer is held.", "Your death save is still finishing. This transfer is reserved until it's done."), ("Waiting for ServerCharacters to finish saving. Your trade is reserved.", "Waiting for the server to save your character. Your trade is reserved; you don't need to buy or place it again."), ("Finishing the inventory save. Your trade is reserved.", "Your inventory is still being saved. Your trade is reserved; wait a moment."), ("This ServerCharacters version cannot confirm vendor saves. No items were changed.", "Trading is paused because this ServerCharacters version can't confirm saves. Ask an admin to check the integration. No items were changed."), ("This ServerCharacters version cannot confirm vendor save ordering. No items were changed.", "Trading is paused because this ServerCharacters version can't confirm saves. Ask an admin to check the integration. No items were changed."), ("This Server Characters version cannot confirm vendor saves. Nothing was moved.", "Trading is paused because this ServerCharacters version can't confirm saves. Ask an admin to check the integration. No items were moved."), ("The saved character does not match this vendor transfer. The transfer is held for recovery.", "This transfer doesn't match your saved character. Don't repeat the purchase or placement. Ask an admin to check the pending transfer."), ("A vendor transfer ID was reused with different contents. It is held for recovery.", "This transfer conflicts with an earlier request. It has been paused so items aren't lost. Ask an admin to check it."), ("Resume this transfer in its original world with the original character.", "Rejoin the original server with the character that started this trade to finish it."), ("A vendor transfer is waiting for your original character. Rejoin with that character to finish it.", "A trade is waiting for the character that started it. Rejoin with that character to finish it."), ("This transfer belongs to another player or character.", "Only the player and character that started this transfer can finish it."), ("This transfer was not issued by the server.", "The server doesn't recognize this transfer. Refresh the vendor and try again."), ("The inventory receipt is missing.", "The transfer's saved confirmation is missing. Don't repeat it. Ask an admin to check the pending transfer."), ("The vendor receipt changed. The transfer is held for recovery.", "The transfer's saved confirmation changed. It has been paused so items aren't lost. Ask an admin to check it."), ("Vendor missing.", "That vendor no longer exists. Refresh My Vendors to check your saved shops."), ("That vendor no longer exists.", "That vendor no longer exists. Refresh My Vendors to check your saved shops."), ("Move closer to this vendor to trade.", "Move closer to the vendor before trading."), ("This vendor has a transfer still finishing.", "This vendor is finishing a trade. Wait for it to save before trying again."), ("This vendor has a transfer still finishing. Try removing it after the transfer completes.", "This vendor is finishing a trade. Wait for it to save before removing the vendor."), ("The selected item changed, is equipped, or is protected. Select it again.", "That item moved, was equipped, or is protected. Unequip or unlock it, then select it again."), ("Your inventory changed. Please try again.", "Your inventory changed before the trade finished. Select the item again."), ("Your character changed during the transfer.", "The active character changed. Rejoin with the character that started the trade."), ("The character changed during the transfer.", "The active character changed. Rejoin with the character that started the trade."), ("The vendor limit has been reached. Packed vendors also count.", "You've reached the vendor limit. Packed vendors still count. Ask an admin to remove an old vendor before placing another."), ("Only the owner or an admin can manage this vendor.", "Only this vendor's owner or a server admin can manage it."), ("Pack this vendor before moving it.", "Pack this vendor in My Vendors before placing it somewhere else."), ("A Vendor Deed is required.", "Use a Vendor Deed from your inventory to place a new vendor."), ("Invalid vendor deed.", "That Vendor Deed couldn't be read. Use it again from your inventory, or ask an admin for help."), ("Invalid vendor request.", "That vendor request is out of date. Refresh the vendor and try again."), ("Invalid vendor transfer.", "That trade confirmation couldn't be read. Refresh the vendor; the pending transfer is kept."), ("Invalid vendor transfer contents.", "That trade confirmation couldn't be read. Refresh the vendor; the pending transfer is kept."), ("Invalid saved vendor receipt.", "The saved trade confirmation couldn't be read. Ask an admin to check the pending transfer."), ("Missing transfer receipt.", "The trade confirmation hasn't arrived. Refresh the vendor to resume the pending transfer."), ("Chest is unavailable.", "This chest is turned off or no longer exists. Ask an admin to check it."), ("Chest loot kit is unavailable.", "This chest has no available reward kit. Ask an admin to check its rewards."), ("Quest is unavailable.", "This quest is no longer available. Speak to the quest NPC or ask an admin."), ("Quest NPC is unavailable.", "This quest's NPC is turned off or missing. Ask an admin to check the quest."), ("Destination NPC is unavailable.", "The next quest NPC is turned off or missing. Ask an admin to check the quest."), ("Spawner is unavailable.", "That spawner is turned off or missing. Refresh the spawner list and check its settings."), ("Spawner is missing.", "That spawner no longer exists. Select another spawner."), ("Inventory unavailable.", "That inventory isn't ready. Refresh it and try again."), ("Admin inventory details are missing.", "No inventory has been loaded for this player. Inspect their inventory first."), ("Invalid inventory inspection details.", "This inventory view is out of date. Refresh it before making changes."), ("Invalid inventory item details.", "That item changed since the inventory was loaded. Refresh it and select the item again."), ("Invalid admin inventory action.", "That inventory action couldn't be read. Refresh the inventory and try again."), ("Invalid item action or quantity.", "Choose an item action and a valid amount, then try again."), ("Invalid item amount.", "Enter a valid item amount and try again."), ("Invalid item/count.", "Choose an available item and a valid amount."), ("Invalid colour.", "Choose a valid colour and try saving again."), ("Invalid dialogue text limit.", "Enter a dialogue text limit within the range shown in settings."), ("Invalid kit items, chance or cooldown. Chance must be 0 to 100%. Server limits: {value1} rows, {value2} items per row.", "Check the kit's items and cooldown. Item chances must be 0 to 100%. This server allows {value1} rows and {value2} items per row."), ("Invalid light placement ID.", "This light placement is out of date. Select the light again before placing it."), ("Invalid light type reference.", "Choose an available light model before placing it."), ("Invalid quest item request.", "That quest item request is out of date. Speak to the quest NPC again."), ("Invalid role.", "Select an existing role, or give the new role a name before saving."), ("Invalid schedule.", "Check the event's date, time and repeat settings before saving."), ("Invalid spawn.", "Choose an available creature and a valid position before spawning it."), ("Invalid vendor payment.", "Choose a regular payment item and a positive asking price."), ("Invalid spectate target.", "That player isn't available to spectate. Refresh the player list."), ("Invalid spectate request.", "That spectate request is out of date. Select the player again."), ("Invalid grave recovery request.", "That grave view is out of date. Scan for graves again, then select the grave."), ("Invalid grave scan request.", "Choose a player or scan area, then search for graves again."), ("Invalid grave handoff.", "That grave recovery request has ended. Scan again before trying to move it."), ("Invalid recovery point.", "Choose a valid recovery position before moving the grave."), ("A build recovery is still finishing.", "The previous build recovery is still running. Wait for it to finish before starting another."), ("A required build prefab or its saved data is unavailable.", "A mod or saved build item needed for this recovery is missing. Ask an admin to check the required mods."), ("Invalid build recovery record.", "That build backup couldn't be read. Keep the backup and ask an admin to check it."), ("Invalid build map position or cell size.", "Choose a valid map position and scan size, then scan again."), ("Invalid costume reward.", "This costume reward isn't set up correctly. Ask an admin to check its costume and claim settings."), ("Invalid costume settings.", "Check the costume's model, size and movement settings before saving it."), ("Invalid event chest settings.", "Check the chest's position, size and reward settings before saving it."), ("Invalid NPC settings.", "Check the NPC's name, position, size and appearance before saving it."), ("Invalid warp.", "Check the warp's name and destination before saving it."), ("A warp has invalid placement or cooldown settings.", "Check the warp's position and cooldown before saving it."), ("Invalid zone dimensions/timing.", "Check the zone's shape, size and timing before saving it."), ("Invalid facing direction.", "Enter a valid rotation angle and try again."), ("Invalid graph identity/name.", "Give the graph a name, then save it again."), ("A graph has invalid nodes.", "This graph has nodes that need fixing. Open its readiness check to find them."), ("Invalid node layout.", "The graph layout couldn't be saved. Reopen the graph and try again."), ("Missing node layout.", "No graph layout was sent. Reopen the graph and try again."), ("Missing graph.", "That graph is no longer available. Refresh the graph list and select it again."), ("Missing node.", "That graph node is no longer available. Reopen the graph and check its links."), ("Missing parameter {value1}.", "Set the graph's {value1} parameter before running it."), ("Missing quest payment.", "The quest payment wasn't supplied. Speak to the quest NPC again with the required items."), ("Missing media file: {value1}", "The media file {value1} is missing from the server. Upload it again or choose another file."), ("Invalid reply or dialogue settings.", "Check the dialogue text, reply links and type speed before saving."), ("Invalid camera audio cues.", "Check the camera's audio clips and their start times before saving."), ("Your camera is not ready for a scripted shot.", "A camera view or teleport is already running. Finish it before starting this scene."), ("Audio track missing.", "This audio track isn't available. Ask an admin to check the uploaded file or URL."), ("Audio could not be downloaded. Use a direct file URL without a redirect.", "The audio file couldn't be downloaded. Use a direct audio-file URL, or upload it to the server library."), ("Audio decoding timed out. Preload this track and try again.", "The audio is taking too long to load. Preload the track and try again."), ("Audio must be a valid clip no longer than 600 seconds.", "Choose a supported audio file under the server's duration limit. The default limit is 10 minutes."), ("Audio scripts are disabled by the server.", "Scene audio is turned off on this server. Ask an admin to enable it."), ("Invalid media request token.", "This media request expired. Refresh the media library and try again."), ("Invalid media transfer offset.", "The file transfer lost its place. Retry the upload or download."), ("Invalid media chunk.", "Part of the file couldn't be read. Retry the upload or download."), ("Invalid media chunk size.", "Part of the file couldn't be read. Retry the upload or download."), ("Unknown WeaverEditor request.", "This request isn't supported. Check that the server and your client have the same WeaverEditor version."), ("Unknown build recovery action.", "That build action isn't available. Refresh the build tools and try again."), ("Unknown community action.", "That community action isn't available. Refresh the Community menu and try again."), ("Unknown costume action.", "That costume action isn't available. Refresh the costume menu and try again."), ("Unknown custom event action.", "That event action isn't available. Refresh the event menu and try again."), ("Unknown economy action.", "That economy action isn't available. Refresh the economy menu and try again."), ("Unknown graph destination.", "Choose an existing NPC, chest or zone for this graph, then save it again."), ("Unknown grave recovery action.", "That grave action isn't available. Scan again and select the grave."), ("Unknown inventory action.", "That inventory action isn't available. Refresh the inventory and try again."), ("Unknown media request.", "That media request isn't supported. Refresh the library and check that your WeaverEditor version matches the server."), ("Unknown player trade action.", "That trade action isn't available. Close the trade window and open it again."), ("Unknown prop hunt action.", "That prop hunt action isn't available. Open the scoreboard menu again."), ("Unknown trade receipt.", "The server doesn't recognize this trade confirmation. Reopen the trade window to check its status."), ("Unknown vendor action.", "That vendor action isn't available. Refresh the vendor window and try again."), ("Unknown vendor transfer operation.", "This trade uses an unsupported action. Ask an admin to check that the server and clients have the same WeaverEditor version."), ("Unknown world control.", "That world action isn't available. Refresh the World menu and try again."), ("Unsupported character data version for vendor receipts.", "This character save format can't be checked for trading. Ask an admin to check the game and ServerCharacters versions. The transfer is reserved."), ("Unsupported saved skills version.", "This character's skill save format can't be checked. Ask an admin to check the game and ServerCharacters versions."), ("Unsupported world data schema.", "This world data was saved in an unsupported format. Keep the file and ask an admin to check the WeaverEditor version."), ("Unsupported player action.", "This player action isn't supported. Check that your WeaverEditor version matches the server."), ("Unsupported effect: {value1}", "The graph action {value1} isn't supported. Ask an admin to check the graph and WeaverEditor versions."), ("The specified path is not a legal form (empty).", "Choose a file before importing."), ("Item unavailable: {value1}", "The item {value1} isn't installed or hasn't loaded. Check the required mod."), ("Kit item is unavailable: {value1}", "The kit item {value1} isn't available. Ask an admin to check its prefab and required mod."), ("Chest reward item is unavailable: {value1}", "The chest reward {value1} isn't available. Ask an admin to check its reward kit."), ("Please wait {value1} seconds before travelling.", "This warp is on cooldown. Try again in {value1} seconds."), ("Available in {value1} seconds.", "This is on cooldown. Try again in {value1} seconds."), ("Vendor transfer paused: {value1}", "Your vendor transfer is paused: {value1}"), ("Vendor transfer waiting for a confirmed character save: {value1}", "Waiting for your character save: {value1}"), ("Costume unavailable: {value1} Your normal appearance has been kept.", "That costume couldn't be loaded: {value1} You're still in your normal form.") }; private static readonly ActionMessageOverrides defaults = new ActionMessageOverrides(messages.Select(((string original, string replacement) message) => new KeyValuePair<string, string>(message.original, message.replacement))); public const string UnexpectedFailure = "That action hit an unexpected error. Close this menu and open it again. If it keeps happening, ask an admin to check the game and server logs."; public static IEnumerable<string> Originals => messages.Select(((string original, string replacement) message) => message.original).Concat(new string[1] { "That action hit an unexpected error. Close this menu and open it again. If it keeps happening, ask an admin to check the game and server logs." }); public static bool IsUnexpectedTechnicalError(string message) { if (string.IsNullOrEmpty(message)) { return false; } if (!message.Contains("Object reference not set to an instance of an object", StringComparison.Ordinal) && !message.Contains("Exception has been thrown by the target of an invocation", StringComparison.Ordinal) && !message.Contains("Index was outside the bounds of the array", StringComparison.Ordinal) && !message.Contains("Collection was modified; enumeration operation may not execute", StringComparison.Ordinal) && !message.Contains("Sequence contains no elements", StringComparison.Ordinal) && !message.Contains("Sequence contains no matching element", StringComparison.Ordinal) && !message.StartsWith("System.NullReferenceException", StringComparison.Ordinal) && !message.StartsWith("System.InvalidCastException", StringComparison.Ordinal)) { return message.StartsWith("System.ArgumentOutOfRangeException", StringComparison.Ordinal); } return true; } public static string Present(string message) { if (!IsUnexpectedTechnicalError(message)) { return defaults.Resolve(message); } return "That action hit an unexpected error. Close this menu and open it again. If it keeps happening, ask an admin to check the game and server logs."; } public static bool IsStandardTemplate(string key, string value) { return key switch { "Combat" => value == "You can't use WeaverEditor travel in combat. Try again in {seconds} seconds." || value == "You're still in combat. Try travelling again in {seconds} seconds.", "Encumbered" => value == "You are carrying too much to travel." || value == "You're carrying too much to travel. Put some items away first.", "Restricted items" => value == "You cannot travel with {item}. Store it first.", "Travel unavailable" => value == "Travel is unavailable right now." || value == "Travel isn't ready yet. Wait a moment and try again.", "Cooldown" => value == "Please wait before trying that again." || value == "Wait for the current action to finish, then try again.", "No access" => value == "You do not have permission to do that." || value == "You don't have permission for this. Ask an admin.", "Other refusal" => value == "That action could not be completed: {reason}." || value == "That action could not be completed." || value == "{reason}", _ => false, }; } } public static class AdminBuildRules { public static bool Allows(bool serverAdmin, bool worldEdit, bool adminPowers, bool enabled, bool editing) { return serverAdmin && worldEdit && adminPowers && enabled && editing; } } public sealed class AdminModeLease { private bool owned; private bool original; private bool applied; public bool OwnsCurrent(bool current) { if (owned) { return current == applied; } return false; } public void Record(bool before, bool after) { if (before != after) { if (!owned || before != applied) { original = before; } applied = after; owned = original != applied; } } public bool TryRestore(bool current, out bool value) { bool flag = owned && current == applied; value = (flag ? original : current); Clear(); return flag; } public void Clear() { owned = false; } } [Serializable] public class Assignment { public string identity = ""; public string role = "Moderator"; } public sealed class AudioEnvelope { private float from; private float target; private double started; private double duration; public float Gain(double now) { if (duration <= 0.0) { return target; } double num = Math.Max(0.0, Math.Min(1.0, (now - started) / duration)); return (float)((double)from + (double)(target - from) * num); } public bool Complete(double now) { if (!(duration <= 0.0)) { return now >= started + duration; } return true; } public void Reset(float gain = 0f) { from = (target = Math.Max(0f, Math.Min(1f, gain))); started = (duration = 0.0); } public void FadeTo(float gain, double seconds, double now) { from = Gain(now); target = Math.Max(0f, Math.Min(1f, gain)); started = now; duration = Math.Max(0.0, seconds); } } public sealed class AudioFocusEntry { public string owner; public string parent = ""; public AudioTrack track; public int priority; public double expires; public double pendingUntil; public bool started; internal long order; } public sealed class AudioFocus { private readonly Dictionary<string, AudioFocusEntry> entries = new Dictionary<string, AudioFocusEntry>(StringComparer.Ordinal); private long order; public int Count => entries.Count; public IEnumerable<AudioFocusEntry> Entries => entries.Values; public AudioFocusEntry Set(string owner, AudioTrack track, int priority, double now, string parent = "") { if (string.IsNullOrEmpty(owner) || owner.Length > 160 || parent == null || parent.Length > 160 || track == null || !double.IsFinite(now)) { throw new ArgumentException("Audio needs a valid source and time."); } SceneRules.ValidateAudioParameters(track); int num; if (entries.TryGetValue(owner, out var value) && value.track.id == track.id && MediaRules.AudioKey(value.track) == MediaRules.AudioKey(track)) { num = ((value.track.loop == track.loop) ? 1 : 0); if (num != 0) { goto IL_00c5; } } else { num = 0; } if (entries.Count >= 32 && !entries.ContainsKey(owner)) { throw new InvalidOperationException("Too many simultaneous music sources."); } goto IL_00c5; IL_00c5: if (num == 0) { value = new AudioFocusEntry { owner = owner, order = ++order }; } value.track = track; value.parent = parent; value.priority = Math.Max(-1000, Math.Min(1000, priority)); value.expires = now + (double)track.seconds; value.pendingUntil = now + 180.0; entries[owner] = value; return value; } public AudioFocusEntry Winner(double now) { string[] array = (from x in entries where (x.Value.started ? x.Value.expires : x.Value.pendingUntil) <= now select x.Key).ToArray(); foreach (string key in array) { entries.Remove(key); } return (from x in entries.Values orderby x.priority descending, x.order descending select x).FirstOrDefault(); } public void BeginPlayback(string owner, double begins) { if (!double.IsFinite(begins)) { throw new ArgumentOutOfRangeException("begins"); } if (entries.TryGetValue(owner, out var value) && !value.started) { value.started = true; value.expires = begins + (double)value.track.seconds; } } public bool Remove(string owner) { if (owner != null) { return entries.Remove(owner); } return false; } public void RemoveSource(string source) { if (!string.IsNullOrEmpty(source)) { string[] array = (from x in entries where x.Key == source || x.Value.parent == source select x.Key).ToArray(); foreach (string key in array) { entries.Remove(key); } } } public void RemoveZonesExcept(ISet<string> owners) { string[] array = (from x in entries where (x.Key.StartsWith("zone:", StringComparison.Ordinal) && !owners.Contains(x.Key)) || (x.Value.parent.StartsWith("zone:", StringComparison.Ordinal) && !owners.Contains(x.Value.parent)) select x.Key).ToArray(); foreach (string key in array) { entries.Remove(key); } } public void Clear() { entries.Clear(); order = 0L; } } public static class AudioGraphWaitRules { public const double LoadSeconds = 180.0; public const double CompletionGraceSeconds = 5.0; public static double ReceiptSeconds(AudioTrack track, bool waitUntilFinished) { SceneRules.ValidateAudioParameters(track); return 180.0 + (waitUntilFinished ? ((double)track.seconds + 5.0) : 0.0); } } public static class AudioPreloadPlan { public const int MaximumTracks = 3; public const float Margin = 48f; public static double Distance(Zone zone, Vec position) { if (zone == null || !position.Finite || !zone.position.Finite || !zone.size.Finite) { return double.PositiveInfinity; } double num = ((zone.shape == "Sphere") ? ((double)Math.Max(0f, zone.size.x)) : (Math.Sqrt(zone.size.DistanceSquared(default(Vec))) / 2.0)); return Math.Max(0.0, Math.Sqrt(zone.position.DistanceSquared(position)) - num); } public static AudioTrack[] Nearby(WorldData world, Vec position) { if (world == null || !position.Finite) { return Array.Empty<AudioTrack>(); } List<AudioTrack> list = new List<AudioTrack>(); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); foreach (Zone item in from z in world.zones where z.enabled && Distance(z, position) <= 48.0 orderby Distance(z, position) select z) { Queue<string> queue = new Queue<string>(new string[3] { item.enter, item.first, item.stay }); while (queue.Count != 0 && hashSet2.Count < 128) { string id = queue.Dequeue(); if (string.IsNullOrEmpty(id) || !hashSet2.Add(id)) { continue; } Graph graph = world.graphs.Find((Graph g) => g.id == id && g.published); if (graph == null) { continue; } foreach (Node node in graph.nodes) { if (node.kind == "Call" && !string.IsNullOrEmpty(node.reference)) { queue.Enqueue(node.reference); } object obj; if (!(node.kind == "PlayUrlAudio")) { obj = ((!(node.kind == "CameraCue")) ? Enumerable.Empty<string>() : ((from x in world.cameraCues.Find((CameraCue x) => x.id == node.reference)?.cues where x.kind == "Audio" orderby x.at select x.reference) ?? Enumerable.Empty<string>())); } else { IEnumerable<string> enumerable = new string[1] { node.reference }; obj = enumerable; } foreach (string trackId in (IEnumerable<string>)obj) { AudioTrack audioTrack = world.audioTracks.Find((AudioTrack t) => t.id == trackId); if (audioTrack != null && !string.IsNullOrWhiteSpace(MediaRules.AudioKey(audioTrack)) && hashSet.Add(MediaRules.AudioKey(audioTrack))) { list.Add(audioTrack); if (list.Count == 3) { return list.ToArray(); } } } } } } return list.ToArray(); } } [Serializable] public class Audit { public string time = ""; public string actor = ""; public string actorName = ""; public string subject = ""; public string subjectName = ""; public string action = ""; public string detail = ""; } [Serializable] public sealed class HistoryFilter { public string actor = ""; public string subject = ""; public List<string> subjectAliases = new List<string>(); public string action = ""; public string text = ""; public string from = ""; public string to = ""; public int page; public int pageSize = 50; } [Serializable] public sealed class HistoryPage { public List<Audit> entries = new List<Audit>(); public int total; public int page; public int pageSize; public int pageCount; } public static class AuditHistory { public static HistoryPage Query(IEnumerable<Audit> entries, HistoryFilter filter) { filter = filter ?? new HistoryFilter(); string actor = (filter.actor ?? "").Trim(); string subject = (filter.subject ?? "").Trim(); string action = (filter.action ?? "").Trim(); string text = (filter.text ?? "").Trim(); bool hasFrom = !string.IsNullOrWhiteSpace(filter.from); bool hasTo = !string.IsNullOrWhiteSpace(filter.to); DateTimeOffset from; bool flag = PlayerProfiles.TryTime(filter.from, out from); DateTimeOffset to; bool flag2 = PlayerProfiles.TryTime(filter.to, out to); if (hasTo && flag2 && (filter.to ?? "").Trim().Length == 10) { to = ((to.Year == 9999 && to.Month == 12 && to.Day == 31) ? DateTimeOffset.MaxValue : new DateTimeOffset(to.UtcDateTime.Date.AddDays(1.0).AddTicks(-1L))); } HistoryPage historyPage = new HistoryPage { pageSize = Math.Max(1, Math.Min(200, filter.pageSize)) }; if ((hasFrom && !flag) || (hasTo && !flag2) || (hasFrom && hasTo && from > to)) { return historyPage; } List<Audit> list = (from x in (entries ?? Enumerable.Empty<Audit>()).Select((Audit entry, int index) => new { entry, index }) where x.entry != null && MatchActor(x.entry, actor) && MatchSubject(x.entry, subject, filter.subjectAliases) && PlayerProfiles.Contains(x.entry.action, action) && MatchText(x.entry, text) && MatchDate(x.entry, hasFrom, @from, hasTo, to) orderby Time(x.entry.time) descending, x.index descending select x.entry).ToList(); historyPage.total = list.Count; historyPage.pageCount = (historyPage.total + historyPage.pageSize - 1) / historyPage.pageSize; historyPage.page = Math.Max(0, Math.Min(filter.page, Math.Max(0, historyPage.pageCount - 1))); historyPage.entries = list.Skip(historyPage.page * historyPage.pageSize).Take(historyPage.pageSize).ToList(); return historyPage; } private static bool MatchActor(Audit entry, string actor) { if (actor.Length != 0 && !PlayerProfiles.Contains(entry.actor, actor) && !PlayerProfiles.Contains(entry.actorName, actor)) { return PlayerProfiles.AccountContains(entry.actor, actor); } return true; } private static bool MatchSubject(Audit entry, string subject, IEnumerable<string> aliases) { if (subject.Length == 0) { return true; } if (!string.IsNullOrEmpty(entry.subject)) { if (!PlayerProfiles.Contains(entry.subject, subject) && !PlayerProfiles.Contains(entry.subjectName, subject)) { return PlayerProfiles.AccountContains(entry.subject, subject); } return true; } if (!PlayerProfiles.Contains(entry.subjectName, subject) && !PlayerProfiles.Contains(entry.detail, subject)) { return aliases?.Any((string alias) => !string.IsNullOrWhiteSpace(alias) && (PlayerProfiles.Contains(entry.subjectName, alias) || PlayerProfiles.Contains(entry.detail, alias))) ?? false; } return true; } private static bool MatchText(Audit entry, string text) { if (!PlayerProfiles.Contains(entry.time, text) && !PlayerProfiles.Contains(entry.actor, text) && !PlayerProfiles.Contains(entry.actorName, text) && !PlayerProfiles.Contains(entry.subject, text) && !PlayerProfiles.Contains(entry.subjectName, text) && !PlayerProfiles.Contains(entry.action, text)) { return PlayerProfiles.Contains(entry.detail, text); } return true; } private static bool MatchDate(Audit entry, bool hasFrom, DateTimeOffset from, bool hasTo, DateTimeOffset to) { if (!hasFrom && !hasTo) { return true; } if (PlayerProfiles.TryTime(entry.time, out var time) && (!hasFrom || time >= from)) { if (hasTo) { return time <= to; } return true; } return false; } private static DateTimeOffset Time(string value) { if (!PlayerProfiles.TryTime(value, out var time)) { return DateTimeOffset.MinValue; } return time; } } public sealed class BoundedDictionaryCursor<TKey, TValue> where TValue : class { private const BindingFlags Fields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly FieldInfo Entries = FindField(typeof(Dictionary<TKey, TValue>), "_entries", "entries"); private static readonly FieldInfo Count = FindField(typeof(Dictionary<TKey, TValue>), "_count", "count"); private readonly Dictionary<TKey, TValue> source; private readonly Queue<TKey> additions = new Queue<TKey>(); private readonly HashSet<TKey> queued = new HashSet<TKey>(); private Array entries; private FieldInfo entryKey; private FieldInfo entryValue; private int index; private int limit; public int Checked => index; public int Total => limit; public bool Complete { get { if (index >= limit) { return additions.Count == 0; } return false; } } public BoundedDictionaryCursor(Dictionary<TKey, TValue> source) { this.source = source ?? throw new ArgumentNullException("source"); BeginBatch(); } public void BeginBatch() { if (Entries == null || Count == null) { throw new InvalidOperationException("The native object table cannot be scanned safely on this runtime."); } Array array = Entries.GetValue(source) as Array; int num = (int)Count.GetValue(source); if (num < 0 || ((array == null) ? ((byte)num != 0) : (num > array.Length))) { throw new InvalidOperationException("The native object table has an unsupported layout."); } if (array != null && entries != array) { Type elementType = array.GetType().GetElementType(); entryKey = elementType.GetField("key", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); entryValue = elementType.GetField("value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (entryKey?.FieldType != typeof(TKey) || entryValue?.FieldType != typeof(TValue)) { throw new InvalidOperationException("The native object records cannot be read safely on this runtime."); } } entries = array; limit = num; } public void Added(TKey key) { if (queued.Add(key)) { additions.Enqueue(key); } } public bool TryNext(out TValue value) { value = null; if (index < limit) { object value2 = entries.GetValue(index++); if (!(entryValue.GetValue(value2) is TValue val)) { return true; } TKey key = (TKey)entryKey.GetValue(value2); if (source.TryGetValue(key, out var value3) && value3 == val) { value = value3; } return true; } if (additions.Count == 0) { return false; } TKey val2 = additions.Dequeue(); queued.Remove(val2); source.TryGetValue(val2, out value); return true; } private static FieldInfo FindField(Type type, string first, string second) { return type.GetField(first, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField(second, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } [Serializable] public sealed class BuildHeatCell { public Vec position; public int count; } [Serializable] public sealed class BuildRecoverySummary { public string id = ""; public string player = ""; public string name = ""; public string state = ""; public double created; public int pieces; public int removed; public int restored; public int skipped; } [Serializable] public sealed class BuildRecoveryView { public string token = ""; public string player = ""; public string name = ""; public string message = ""; public string phase = ""; public bool complete; public bool truncated; public bool busy; public bool allPlayers; public Vec center; public float radius; public int pieces; public int unknown; public int checkedObjects; public int totalObjects; public List<BuildHeatCell> cells = new List<BuildHeatCell>(); public List<BuildRecoverySummary> recoveries = new List<BuildRecoverySummary>(); } public static class BuildRecoveryRules { public static string CellKey(Vec position, float cellSize) { if (!CostumeRules.Finite(position.x) || !CostumeRules.Finite(position.z) || !CostumeRules.Finite(cellSize) || cellSize < 8f || cellSize > 256f) { throw new InvalidOperationException("Invalid build map position or cell size."); } return Math.Floor(position.x / cellSize) + ":" + Math.Floor(position.z / cellSize); } public static bool InArea(Vec position, Vec center, float radius) { if (!CostumeRules.Finite(radius) || radius < 0f || radius > 5000f) { return false; } if (radius == 0f) { return true; } double num = position.x - center.x; double num2 = position.z - center.z; return num * num + num2 * num2 <= (double)(radius * radius); } public static HashSet<long> Characters(IEnumerable<PlayerIdentity> records, string account) { List<PlayerIdentity> identities = records?.ToList() ?? new List<PlayerIdentity>(); string resolved = PlayerProfiles.ResolveAccount(account, identities); return (from entry in PlayerProfiles.VerifiedAccounts(identities) where entry.Value == resolved select entry.Key).ToHashSet(); } } public static class CameraDraftPlacement { public static int Store(CameraCue cue, CameraPoint replace, CameraPoint point) { if (cue?.points == null || point == null || !point.position.Finite || !point.rotation.Finite || float.IsNaN(point.fov) || float.IsInfinity(point.fov) || point.fov < 20f || point.fov > 110f) { throw new InvalidOperationException("Enter a valid camera position, rotation and FOV."); } int num = ((replace == null) ? cue.points.Count : cue.points.IndexOf(replace)); if (num < 0) { throw new InvalidOperationException("This camera is no longer in the draft."); } if (replace == null && num >= 16) { throw new InvalidOperationException("This shot has 16 cameras. Press Esc to finish or remove a point in the editor."); } double num2 = 250000.0; if ((num > 0 && cue.points[num - 1].position.DistanceSquared(point.position) > num2) || (replace != null && num + 1 < cue.points.Count && cue.points[num + 1].position.DistanceSquared(point.position) > num2)) { throw new InvalidOperationException("Keep consecutive camera points within 500 metres."); } CameraPoint cameraPoint = new CameraPoint { id = (replace?.id ?? Id.New()), label = (replace?.label ?? ""), position = point.position, rotation = point.rotation, fov = point.fov, travelSeconds = (replace?.travelSeconds ?? 3f), holdSeconds = (replace?.holdSeconds ?? 1f), transition = (replace?.transition ?? "Smooth") }; if (replace == null) { cue.points.Add(cameraPoint); } else { cue.points[num] = cameraPoint; } return num; } } [Serializable] public sealed class SceneTimelineCue { public string id = Id.New(); public string kind = "Dialogue"; public float at; public string reference = ""; public string text = ""; public float seconds = 4f; public string textColor = "#F2DDB2"; public float textSpeed; } public readonly struct CameraSample { public readonly int from; public readonly int to; public readonly float blend; public CameraSample(int from, int to, float blend) { this.from = from; this.to = to; this.blend = blend; } } public static class CameraTimeline { public const float MaximumDuration = 600f; public const int MaximumCues = 64; public static double Duration(CameraCue cue) { if (cue == null || cue.points == null || cue.points.Count == 0) { return 0.0; } if (!cue.timeline) { return cue.seconds + cue.holdSeconds; } double num = 0.0; for (int i = 0; i < cue.points.Count; i++) { num += (double)(Travel(cue, i) + cue.points[i].holdSeconds); } return num; } public static float Travel(CameraCue cue, int index) { if (index != 0 && !(cue.points[index].transition == "Cut")) { return cue.points[index].travelSeconds; } return 0f; } public static double Arrival(CameraCue cue, int index) { if (index < 0 || index >= cue.points.Count) { throw new ArgumentOutOfRangeException("index"); } if (!cue.timeline) { if (cue.points.Count > 1) { return (double)(cue.seconds * (float)index) / ((double)cue.points.Count - 1.0); } return 0.0; } double num = 0.0; for (int i = 0; i <= index; i++) { num += (double)Travel(cue, i); if (i < index) { num += (double)cue.points[i].holdSeconds; } } return num; } public static CameraSample Sample(CameraCue cue, double elapsed) { if (cue?.points == null || cue.points.Count == 0) { throw new InvalidOperationException("Add a camera first."); } if (!double.IsFinite(elapsed)) { throw new ArgumentOutOfRangeException("elapsed"); } elapsed = Math.Max(0.0, elapsed); if (!cue.timeline) { double num = Math.Min(1.0, elapsed / Math.Max(0.1, cue.seconds)) * (double)(cue.points.Count - 1); int num2 = (int)Math.Floor(num); return new CameraSample(num2, Math.Min(num2 + 1, cue.points.Count - 1), Smooth((float)(num - (double)num2))); } double num3 = 0.0; for (int i = 0; i < cue.points.Count; i++) { float num4 = Travel(cue, i); if (num4 > 0f && elapsed < num3 + (double)num4) { float num5 = (float)((elapsed - num3) / (double)num4); return new CameraSample(i - 1, i, (cue.points[i].transition == "Linear") ? num5 : Smooth(num5)); } num3 += (double)num4; if (elapsed < num3 + (double)cue.points[i].holdSeconds) { return new CameraSample(i, i, 0f); } num3 += (double)cue.points[i].holdSeconds; } int num6 = cue.points.Count - 1; return new CameraSample(num6, num6, 0f); } public static void Enable(CameraCue cue) { if (cue.timeline) { return; } for (int i = 0; i < cue.points.Count; i++) { CameraPoint cameraPoint = cue.points[i]; cameraPoint.travelSeconds = ((cue.points.Count > 1) ? (cue.seconds / (float)(cue.points.Count - 1)) : 3f); cameraPoint.holdSeconds = ((i == cue.points.Count - 1) ? cue.holdSeconds : 0f); cameraPoint.transition = "Smooth"; } if (cue.points.Count == 1) { float num = cue.seconds + cue.holdSeconds; cue.points[0].holdSeconds = Math.Min(30f, num); if (num > 30f) { CameraPoint cameraPoint2 = CopyPoint(cue.points[0], newId: true); cameraPoint2.label = "Continued hold"; cameraPoint2.transition = "Cut"; cameraPoint2.travelSeconds = 0f; cameraPoint2.holdSeconds = num - 30f; cue.points.Add(cameraPoint2); } } cue.timeline = true; } public static void Move(CameraCue cue, int from, int to) { if (from < 0 || to < 0 || from >= cue.points.Count || to >= cue.points.Count) { throw new ArgumentOutOfRangeException("Choose a camera inside this shot."); } CameraPoint item = cue.points[from]; cue.points.RemoveAt(from); cue.points.Insert(to, item); } public static void Validate(CameraCue cue) { if (cue.points.Any((CameraPoint x) => !Guid.TryParseExact(x.id, "N", out var _)) || cue.points.Select((CameraPoint x) => x.id).Distinct().Count() != cue.points.Count) { throw new InvalidOperationException("Camera point IDs must be unique within the shot."); } foreach (CameraPoint point in cue.points) { if ((point.label ?? "").Length > 80 || (point.label ?? "").Any((char c) => char.IsControl(c) || c == '<' || c == '>')) { throw new InvalidOperationException("Camera labels can contain 80 plain characters."); } if (cue.timeline && (!float.IsFinite(point.travelSeconds) || point.travelSeconds < 0f || point.travelSeconds > 20f || !float.IsFinite(point.holdSeconds) || point.holdSeconds < 0f || point.holdSeconds > 30f || !new string[3] { "Cut", "Linear", "Smooth" }.Contains(point.transition))) { throw new InvalidOperationException("Each camera supports 0–20 travel seconds and 0–30 hold seconds."); } } double num = Duration(cue); if (cue.timeline && (num < 0.1 || num > 600.0)) { throw new InvalidOperationException("The timeline must last between 0.1 and 600 seconds."); } if (cue.cues == null || cue.cues.Count > 64) { throw new InvalidOperationException("A timeline supports up to 64 music or dialogue cues."); } foreach (SceneTimelineCue cue2 in cue.cues) { if (cue2 == null || !Guid.TryParseExact(cue2.id, "N", out var result) || !new string[3] { "Audio", "Dialogue", "StopAudio" }.Contains(cue2.kind) || !float.IsFinite(cue2.at) || cue2.at < 0f || (double)cue2.at > num || !float.IsFinite(cue2.seconds) || cue2.seconds < 1f || cue2.seconds > 30f || (cue2.kind == "Audio" && !Guid.TryParseExact(cue2.reference, "N", out result))) { throw new InvalidOperationException("Keep cues inside the timeline; dialogue lasts 1–30 seconds and uses up to 500 characters."); } SceneDialogueText.Parse(cue2.text); if (!SceneDialogueText.IsColor(cue2.textColor) || !float.IsFinite(cue2.textSpeed) || cue2.textSpeed < 0f || cue2.textSpeed > 200f) { throw new InvalidOperationException("Choose a text color such as #F2DDB2 and a text speed from 0 to 200 percent."); } } if (cue.cues.Select((SceneTimelineCue x) => x.id).Distinct().Count() != cue.cues.Count) { throw new InvalidOperationException("Timeline cue IDs must be unique."); } } public static CameraPoint CopyPoint(CameraPoint point, bool newId = false) { return new CameraPoint { id = (newId ? Id.New() : point.id), label = point.label, position = point.position, rotation = point.rotation, fov = point.fov, travelSeconds = point.travelSeconds, holdSeconds = point.holdSeconds, transition = point.transition }; } private static float Smooth(float t) { return t * t * (3f - 2f * t); } } public static class ChestCooldowns { public const double Permanent = 8.988465674311579E+307; private static bool IsPermanent(double until) { return until >= 1E+100; } public static bool Update(WorldData world, EventChest previous, EventChest current, double now) { bool result = false; string prefix = "chest:" + current.id + ":"; string[] array = world.claims.Keys.Where((string k) => k.StartsWith(prefix, StringComparison.Ordinal)).ToArray(); foreach (string key in array) { double num2 = world.claims[key]; double num3 = num2; if (current.once) { if (previous != null && !previous.once) { num3 = 8.988465674311579E+307; } } else if (IsPermanent(num2)) { num3 = now + current.cooldown; } else if (previous != null && !previous.once && previous.cooldown != current.cooldown && num2 > now) { num3 = Math.Max(now, num2 - previous.cooldown + current.cooldown); } if (num3 != num2) { world.claims[key] = num3; result = true; } } return result; } public static bool Repair(WorldData world, double now) { bool flag = false; foreach (EventChest chest in world.chests) { flag |= Update(world, chest, chest, now); } return flag; } public static string Remaining(double seconds) { if (double.IsNaN(seconds) || double.IsInfinity(seconds) || seconds < 0.0 || seconds > 31536000.0) { throw new ArgumentOutOfRangeException("seconds"); } TimeSpan timeSpan = TimeSpan.FromSeconds(Math.Ceiling(seconds)); if (timeSpan.TotalDays >= 1.0) { return timeSpan.Days + "d " + timeSpan.Hours + "h"; } if (timeSpan.TotalHours >= 1.0) { return timeSpan.Hours + "h " + timeSpan.Minutes + "m"; } if (timeSpan.TotalMinutes >= 1.0) { return timeSpan.Minutes + "m " + timeSpan.Seconds + "s"; } return timeSpan.Seconds + ((timeSpan.Seconds == 1) ? " second" : " seconds"); } } [Serializable] public class ChestReward { public string key = ""; public string weaverToken = ""; public string weaverType = ""; public string weaverBinding = ""; public int keyCount = 1; public bool consume; public List<ItemAmount> items = new List<ItemAmount>(); } [Serializable] public class Choice { public string text = "Continue"; public string target = ""; public double delay; public TradeOffer trade; public string group = "Conversation"; public string lockReason = ""; public bool available = true; public bool showLocked = true; public List<DialogueRequirement> requirements = new List<DialogueRequirement>(); public QuestDialogueCard quest; } [Serializable] public sealed class ContentPackage { public int format = 1; public string name = "Content package"; public Vec origin; public List<Graph> graphs = new List<Graph>(); public List<Npc> npcs = new List<Npc>(); public List<Zone> zones = new List<Zone>(); public List<Spawner> spawners = new List<Spawner>(); public List<EventChest> chests = new List<EventChest>(); public List<Kit> kits = new List<Kit>(); public List<Warp> warps = new List<Warp>(); public List<WarpPortal> warpPortals = new List<WarpPortal>(); public List<WorldDecoration> decorations = new List<WorldDecoration>(); public List<LightPreset> lightPresets = new List<LightPreset>(); public List<CameraCue> cameraCues = new List<CameraCue>(); public List<AudioTrack> audioTracks = new List<AudioTrack>(); public List<MediaAsset> mediaAssets = new List<MediaAsset>(); public List<CostumeReward> costumeRewards = new List<CostumeReward>(); public List<PropHuntEvent> propHunts = new List<PropHuntEvent>(); public List<CustomEventDefinition> customEvents = new List<CustomEventDefinition>(); public List<WorldObjectGroup> objectGroups = new List<WorldObjectGroup>(); } public sealed class ContentRecord { public string kind; public object value; public string Id => (string)value.GetType().GetField("id").GetValue(value); public string Name => (string)value.GetType().GetField("name").GetValue(value); } public static class ContentPackages { public const int MaximumRecords = 2000; public const int MaximumMetadataBytes = 4194304; public const long MaximumArchiveBytes = 134217728L; private static readonly HashSet<string> ReferenceFields = new HashSet<string> { "id", "graph", "reference", "destination", "kit", "lootKit", "warp", "zone", "arenaZone", "patrolZone", "enter", "exit", "stay", "first", "last", "onClaim", "winnerGraph", "startGraph", "tagGraph", "caughtGraph", "winGraph", "loseGraph", "cancelGraph", "cleanupGraph", "enterGraph", "exitGraph", "optionRewardKit", "dialogueAudio", "vendorTemplate", "costumeReward", "lightPresetId", "mediaId", "audio", "pointId" }; public static IEnumerable<ContentRecord> Records(ContentPackage package) { foreach (Graph graph in package.graphs) { yield return Row("Graph", graph); } foreach (Npc npc in package.npcs) { yield return Row("Npc", npc); } foreach (Zone zone in package.zones) { yield return Row("Zone", zone); } foreach (Spawner spawner in package.spawners) { yield return Row("Spawner", spawner); } foreach (EventChest chest in package.chests) { yield return Row("Chest", chest); } foreach (Kit kit in package.kits) { yield return Row("Kit", kit); } foreach (Warp warp in package.warps) { yield return Row("Warp", warp); } foreach (WarpPortal warpPortal in package.warpPortals) { yield return Row("Portal", warpPortal); } foreach (WorldDecoration decoration in package.decorations) { yield return Row("Decoration", decoration); } foreach (LightPreset lightPreset in package.lightPresets) { yield return Row("Light", lightPreset); } foreach (CameraCue cameraCue in package.cameraCues) { yield return Row("Camera", cameraCue); } foreach (AudioTrack audioTrack in package.audioTracks) { yield return Row("Audio", audioTrack); } foreach (MediaAsset mediaAsset in package.mediaAssets) { yield return Row("Media", mediaAsset); } foreach (CostumeReward costumeReward in package.costumeRewards) { yield return Row("Costume", costumeReward); } foreach (PropHuntEvent propHunt in package.propHunts) { yield return Row("PropHunt", propHunt); } foreach (CustomEventDefinition customEvent in package.customEvents) { yield return Row("Event", customEvent); } foreach (WorldObjectGroup objectGroup in package.objectGroups) { yield return Row("Group", objectGroup); } } private static ContentRecord Row(string kind, object value) { return new ContentRecord { kind = kind, value = value }; } public static ContentPackage Capture(WorldData copy, string graphId, Vec origin, float nearbyRadius) { ContentPackage contentPackage = new ContentPackage { origin = origin, graphs = (from item in copy.drafts.Concat(copy.graphs) group item by item.id into @group select @group.First()).ToList(), npcs = copy.npcs, zones = copy.zones, spawners = copy.spawners, chests = copy.chests, kits = copy.kits, warps = copy.warps, warpPortals = copy.warpPortals, decorations = copy.decorations, lightPresets = copy.lightPresets, cameraCues = copy.cameraCues, audioTracks = copy.audioTracks, mediaAssets = copy.mediaAssets, costumeRewards = copy.costumeRewards, propHunts = copy.propHunts, customEvents = copy.customEvents, objectGroups = copy.objectGroups }; Dictionary<string, ContentRecord> rows = Records(contentPackage).ToDictionary((ContentRecord item) => item.kind + ":" + item.Id); HashSet<string> selected = new HashSet<string>(); if (string.IsNullOrEmpty(graphId)) { selected.UnionWith(rows.Keys); } else { Add("Graph", graphId); if (nearbyRadius > 0f) { foreach (ContentRecord value in rows.Values) { FieldInfo field = value.value.GetType().GetField("position"); if (field?.FieldType == typeof(Vec) && ((Vec)field.GetValue(value.value)).DistanceSquared(origin) <= (double)(nearbyRadius * nearbyRadius)) { Add(value.kind, value.Id); } if (value.value is PropHuntEvent propHuntEvent && propHuntEvent.boardPosition.DistanceSquared(origin) <= (double)(nearbyRadius * nearbyRadius)) { Add(value.kind, value.Id); } } } int count; do { count = selected.Count; string[] array = selected.ToArray(); foreach (string key in array) { foreach (var item in Dependencies(rows[key], contentPackage.graphs)) { Add(item.kind, item.id); } } foreach (ContentRecord item2 in rows.Values.Where((ContentRecord item) => item.kind == "Npc" || item.kind == "Zone" || item.kind == "PropHunt" || item.kind == "Event" || item.kind == "Group" || item.kind == "Portal")) { if (Dependencies(item2, contentPackage.graphs).Any(((string kind, string id) item) => selected.Contains(item.kind + ":" + item.id))) { Add(item2.kind, item2.Id); } } } while (selected.Count != count); } foreach (FieldInfo item3 in from fieldInfo in typeof(ContentPackage).GetFields() where typeof(IList).IsAssignableFrom(fieldInfo.FieldType) select fieldInfo) { IList list = (IList)item3.GetValue(contentPackage); int index; for (index = list.Count - 1; index >= 0; index--) { ContentRecord contentRecord = rows.Values.First((ContentRecord item) => item.value == list[index]); if (!selected.Contains(contentRecord.kind + ":" + contentRecord.Id)) { list.RemoveAt(index); } } } contentPackage.name = (string.IsNullOrEmpty(graphId) ? "World content" : contentPackage.graphs.First((Graph item) => item.id == graphId).name); Sanitize(contentPackage); Validate(contentPackage); return contentPackage; void Add(string kind, string id) { if (!string.IsNullOrEmpty(id) && !GraphParameters.IsToken(id)) { string text = kind + ":" + id; if (!rows.ContainsKey(text)) { throw new InvalidDataException("Missing " + kind + " reference: " + id); } selected.Add(text); } } } public static IEnumerable<(string kind, string id)> Dependencies(ContentRecord row, IEnumerable<Graph> knownGraphs = null) { object value = row.value; if (value is Graph graph) { foreach (var item4 in GraphParameters.PackageDependencies(graph, knownGraphs ?? Array.Empty<Graph>())) { yield return item4; } foreach (Node node in graph.nodes) { string text = GraphReferences.Kind(node); if (text != "") { yield return (kind: text, id: node.reference); } if (node.kind == "GiveQuest") { yield return (kind: "Warp", id: node.destination); } yield return (kind: "Kit", id: node.optionRewardKit); yield return (kind: "Audio", id: node.dialogueAudio); foreach (QuestObjective questObjective in node.questObjectives) { if (questObjective.kind == "Encounter" || questObjective.kind == "Npc") { yield return (kind: (questObjective.kind == "Encounter") ? "Spawner" : "Npc", id: questObjective.reference); } } } } if (row.value is Npc npc) { yield return (kind: "Graph", id: npc.graph); } if (row.value is Zone zone) { string[] array = new string[5] { zone.enter, zone.exit, zone.stay, zone.first, zone.last }; foreach (string item in array) { yield return (kind: "Graph", id: item); } } value = row.value; if (value is Spawner spawner) { yield return (kind: "Kit", id: spawner.lootKit); if (spawner.patrolMode == "Zone") { yield return (kind: "Zone", id: spawner.patrolZone); } } value = row.value; if (value is EventChest chest) { yield return (kind: "Kit", id: chest.kit); yield return (kind: "Graph", id: chest.onClaim); } if (row.value is Kit kit) { foreach (ItemAmount item2 in kit.items) { yield return (kind: "Npc", id: item2.vendorTemplate); yield return (kind: "Costume", id: item2.costumeReward); } } if (row.value is WarpPortal warpPortal) { yield return (kind: "Warp", id: warpPortal.warp); } if (row.value is AudioTrack audioTrack) { yield return (kind: "Media", id: audioTrack.mediaId); } if (row.value is CameraCue cameraCue) { foreach (SceneTimelineCue item5 in cameraCue.cues.Where((SceneTimelineCue cue) => cue.kind == "Audio")) { yield return (kind: "Audio", id: item5.reference); } } value = row.value; if (value is WorldDecoration decoration) { yield return (kind: "Media", id: decoration.mediaId); yield return (kind: "Light", id: decoration.lightPresetId); } value = row.value; if (value is PropHuntEvent hunt) { yield return (kind: "Zone", id: hunt.zone); yield return (kind: "Zone", id: hunt.arenaZone); string[] array = new string[7] { hunt.winnerGraph, hunt.startGraph, hunt.tagGraph, hunt.caughtGraph, hunt.loseGraph, hunt.cancelGraph, hunt.cleanupGraph }; foreach (string item3 in array) { yield return (kind: "Graph", id: item3); } } value = row.value; if (value is CustomEventDefinition customEvent) { yield return (kind: "Zone", id: customEvent.zone); foreach (string item6 in CustomEventRules.Graphs(customEvent)) { yield return (kind: "Graph", id: item6); } } if (!(row.value is WorldObjectGroup worldObjectGroup)) { yield break; } foreach (WorldObjectRef member in worldObjectGroup.members) { yield return (kind: (member.kind == "Camera") ? "Camera" : "Decoration", id: member.id); } } public static void Validate(ContentPackage package) { if (package == null || package.format != 1 || !package.origin.Finite || string.IsNullOrWhiteSpace(package.name) || package.name.Length > 100) { throw new InvalidDataException("Choose a supported content package with a name and valid origin."); } foreach (FieldInfo item in from field in typeof(ContentPackage).GetFields() where typeof(IList).IsAssignableFrom(field.FieldType) select field) { if (item.GetValue(package) == null) { throw new InvalidDataException("A package content list is missing."); } } ContentRecord[] array = Records(package).ToArray(); if (array.Length == 0 || array.Length > 2000 || array.Any((ContentRecord row) => row.value == null || !Guid.TryParseExact(row.Id, "N", out var _) || string.IsNullOrWhiteSpace(row.Name) || row.Name.Length > 100) || array.Select((ContentRecord row) => row.Id).Distinct().Count() != array.Length) { throw new InvalidDataException("A package needs unique content IDs and at most 2,000 records."); } HashSet<string> hashSet = new HashSet<string>(array.Select((ContentRecord row) => row.kind + ":" + row.Id)); ContentRecord[] array2 = array; foreach (ContentRecord contentRecord in array2) { foreach (var item2 in Dependencies(contentRecord, package.graphs)) { if (!string.IsNullOrEmpty(item2.id) && !GraphParameters.IsToken(item2.id) && !hashSet.Contains(item2.kind + ":" + item2.id)) { throw new InvalidDataException(contentRecord.Name + " needs a missing " + item2.kind + " reference."); } } } foreach (Graph graph in package.graphs) { if (graph.nodes == null || graph.nodes.Count > 2000 || graph.nodes.Any((Node node) => node == null)) { throw new InvalidDataException("A graph has invalid nodes."); } foreach (Node node in graph.nodes) { QuestObjectives.Validate(node.questObjectives); } } foreach (Spawner spawner in package.spawners) { SpawnerPatrolRules.Capture(spawner, package.zones); } foreach (MediaAsset mediaAsset in package.mediaAssets) { MediaRules.Validate(mediaAsset); } if (((IEnumerable<MediaAsset>)package.mediaAssets).Sum((Func<MediaAsset, long>)((MediaAsset asset) => asset.bytes)) > 134217728) { throw new InvalidDataException("Keep a content package's media under 128 MiB."); } } public static void Sanitize(ContentPackage package) { foreach (ContentRecord item in Records(package)) { Set(item.value, "zdoUser", 0L); Set(item.value, "zdoId", 0u); Set(item.value, "nextAllowed", 0.0); } foreach (ItemAmount item2 in package.kits.SelectMany((Kit kit) => kit.items)) { item2.costumeToken = ""; } } public static Dictionary<string, string> PrepareImport(ContentPackage copy, Vec destination) { Validate(copy); if (!destination.Finite) { throw new InvalidDataException("Choose a valid placement origin."); } ContentRecord[] array = Records(copy).ToArray(); Dictionary<string, string> dictionary = array.Where((ContentRecord row) => row.kind != "Media").ToDictionary((ContentRecord row) => row.Id, (ContentRecord row) => Id.New()); foreach (Graph graph2 in copy.graphs) { GraphParameters.RemapContent(graph2, dictionary, copy.graphs); } ContentRecord[] array2 = array; for (int num = 0; num < array2.Length; num++) { Rewrite(array2[num].value, dictionary); } Vec offset = new Vec(destination.x - copy.origin.x, destination.y - copy.origin.y, destination.z - copy.origin.z); array2 = array; foreach (ContentRecord contentRecord in array2) { Move(contentRecord.value, "position", offset); Move(contentRecord.value, "boardPosition", offset); Set(contentRecord.value, "enabled", false); Set(contentRecord.value, "isPublic", false); if (contentRecord.value is Npc npc) { npc.patrol = npc.patrol.Select((Vec point) => Offset(point, offset)).ToList(); } if (contentRecord.value is Spawner spawner) { spawner.patrolPoints = spawner.patrolPoints.Select((Vec point) => Offset(point, offset)).ToList(); } if (contentRecord.value is CameraCue cameraCue) { foreach (CameraPoint point in cameraCue.points) { point.position = Offset(point.position, offset); } } if (contentRecord.value is Graph graph) { graph.published = false; graph.revision = 0; } } copy.origin = destination; Sanitize(copy); return dictionary; } private static void Rewrite(object value, IReadOnlyDictionary<string, string> remap, bool rewriteNodes = true) { if (value == null || value is string || value.GetType().IsValueType) { return; } if (rewriteNodes && value is Graph graph) { Dictionary<string, string> nodes = graph.nodes.ToDictionary((Node node) => node.id, (Node node) => Id.New()); GraphParameters.RemapSections(graph, nodes); graph.entry = (nodes.TryGetValue(graph.entry, out var value2) ? value2 : graph.entry); foreach (Node node in graph.nodes) { node.id = nodes[node.id]; node.next = (nodes.TryGetValue(node.next, out var value3) ? value3 : node.next); node.otherwise = (nodes.TryGetValue(node.otherwise, out var value4) ? value4 : node.otherwise); node.nextLinks = node.nextLinks.Select((string id) => (!nodes.TryGetValue(id, out var value9)) ? id : value9).ToList(); node.falseLinks = node.falseLinks.Select((string id) => (!nodes.TryGetValue(id, out value9)) ? id : value9).ToList(); foreach (Choice choice in node.choices) { if (nodes.TryGetValue(choice.target, out var value5)) { choice.target = value5; } } } foreach (GraphReference reference in graph.references) { if (nodes.TryGetValue(reference.node, out var value6)) { reference.node = value6; } } } if (value is IList list) { { foreach (object item in list) { Rewrite(item, remap, rewriteNodes); } return; } } FieldInfo[] fields = value.GetType().GetFields(); foreach (FieldInfo fieldInfo in fields) { object value7 = fieldInfo.GetValue(value); if (fieldInfo.FieldType == typeof(string)) { if (ReferenceFields.Contains(fieldInfo.Name) && value7 is string key && remap.TryGetValue(key, out var value8)) { fieldInfo.SetValue(value, value8); } } else { Rewrite(value7, remap, rewriteNodes); } } } public static void RewriteMedia(ContentPackage package, IReadOnlyDictionary<string, string> remap) { foreach (ContentRecord item in Records(package)) { Rewrite(item.value, remap, rewriteNodes: false); } } public static void Append(ContentPackage package, WorldData world, bool validationCopy = false) { foreach (Graph graph in package.graphs) { if (validationCopy) { world.graphs.Add(graph); } else { world.drafts.Add(graph); } } world.npcs.AddRange(package.npcs); world.zones.AddRange(package.zones); world.spawners.AddRange(package.spawners); world.chests.AddRange(package.chests); world.kits.AddRange(package.kits); world.warps.AddRange(package.warps); world.warpPortals.AddRange(package.warpPortals); world.decorations.AddRange(package.decorations); world.lightPresets.AddRange(package.lightPresets); world.cameraCues.AddRange(package.cameraCues); world.audioTracks.AddRange(package.audioTracks); world.costumeRewards.AddRange(package.costumeRewards); world.propHunts.AddRange(package.propHunts); world.customEvents.AddRange(package.customEvents); world.objectGroups.AddRange(package.objectGroups); } private static void Set(object value, string field, object replacement) { value.GetType().GetField(field)?.SetValue(value, replacement); } private static void Move(object value, string field, Vec offset) { FieldInfo field2 = value.GetType().GetField(field); if (field2?.FieldType == typeof(Vec)) { field2.SetValue(value, Offset((Vec)field2.GetValue(value), offset)); } } private static Vec Offset(Vec value, Vec offset) { Vec result = new Vec(value.x + offset.x, value.y + offset.y, value.z + offset.z); if (!result.Finite) { throw new InvalidDataException("Package placement is outside supported coordinates."); } return result; } } public sealed class ContentIssue { public string kind = ""; public string id = ""; public string name = ""; public string message = ""; public bool error; } public static class ContentReadiness { public static List<ContentIssue> Review(WorldData world, Func<string, bool> item, Func<string, bool> prefab) { List<ContentIssue> issues = new List<ContentIssue>(); ContentPackage contentPackage = new ContentPackage { graphs = (from graph2 in world.drafts.Concat(world.graphs) group graph2 by graph2.id into @group select @group.First()).ToList(), npcs = world.npcs, zones = world.zones, spawners = world.spawners, chests = world.chests, kits = world.kits, warps = world.warps, warpPortals = world.warpPortals, decorations = world.decorations, lightPresets = world.lightPresets, cameraCues = world.cameraCues, audioTracks = world.audioTracks, mediaAssets = world.mediaAssets, costumeRewards = world.costumeRewards, propHunts = world.propHunts, customEvents = world.customEvents, objectGroups = world.objectGroups }; ContentRecord[] array = ContentPackages.Records(contentPackage).ToArray(); HashSet<string> hashSet = new HashSet<string>(array.Select((ContentRecord row) => row.kind + ":" + row.Id)); ContentRecord[] array2 = array; foreach (ContentRecord contentRecord in array2) { foreach (var dependency in ContentPackages.Dependencies(contentRecord, contentPackage.graphs)) { if (!string.IsNullOrEmpty(dependency.id) && !GraphParameters.IsToken(dependency.id)) { if (!hashSet.Contains(dependency.kind + ":" + dependency.id)) { Add(contentRecord, "Missing " + dependency.kind.ToLowerInvariant() + " link: " + dependency.id, error: true); } else if (dependency.kind == "Graph" && !world.graphs.Any((Graph graph2) => graph2.id == dependency.id && graph2.published)) { Add(contentRecord, "Linked graph is still a draft. Publish it before enabling this content.", error: true); } } } object value = contentRecord.value; Graph graph = value as Graph; if (graph != null) { foreach (GraphIssue item2 in from issue in GraphIssues.Review(graph, world, item, prefab) where issue.error select issue) { Add(contentRecord, item2.message, error: true); } if (world.graphs.Find((Graph graph2) => graph2.id == graph.id && graph2.published) == null) { Add(contentRecord, "Draft has not been published.", error: false); } foreach (Node node in graph.nodes.Where((Node node2) => node2.kind == "GiveQuest")) { if (!string.IsNullOrEmpty(node.key) && contentPackage.graphs.Any((Graph other) => other.id != graph.id && other.nodes.Any((Node node2) => node2.kind == "GiveQuest" && node2.key == node.key))) { Add(contentRecord, "Quest key is shared with another graph: " + node.key, error: false); } } } foreach (string item3 in (from text in Items(contentRecord) where !string.IsNullOrEmpty(text) && !GraphParameters.IsToken(text) select text).Distinct()) { if (item != null && !item(item3)) { Add(contentRecord, "Item is unavailable: " + item3, error: true); } } if (contentRecord.value is Spawner spawner && prefab != null && !prefab(spawner.prefab)) { Add(contentRecord, "Creature is unavailable: " + spawner.prefab, error: true); } if (contentRecord.value is Spawner spawner2) { try { SpawnerPatrolRules.Capture(spawner2, world.zones); } catch (InvalidOperationException ex) { Add(contentRecord, ex.Message, error: true); } } if (contentRecord.value is CostumeReward costumeReward && prefab != null && !prefab(costumeReward.costume.prefab)) { Add(contentRecord, "Costume model is unavailable: " + costumeReward.costume.prefab, error: true); } if (contentRecord.value is PropHuntEvent propHuntEvent && prefab != null) { foreach (string item4 in propHuntEvent.props.Where((string arg) => !prefab(arg))) { Add(contentRecord, "Prop is unavailable: " + item4, error: true); } } if (contentRecord.value is AudioTrack audioTrack && string.IsNullOrEmpty(audioTrack.mediaId) && string.IsNullOrEmpty(audioTrack.url)) { Add(contentRecord, "Choose a media file or audio URL.", error: true); } if (contentRecord.value is WorldDecoration { kind: "Image" } worldDecoration && string.IsNullOrEmpty(worldDecoration.mediaId) && string.IsNullOrEmpty(worldDecoration.imageUrl) && string.IsNullOrEmpty(worldDecoration.imageData) && !worldDecoration.uploaded) { Add(contentRecord, "Choose an image file or image URL.", error: true); } } return (from issue in issues group issue by issue.kind + ":" + issue.id + ":" + issue.message into @group select @group.First()).ToList(); void Add(ContentRecord row, string message, bool error) { issues.Add(new ContentIssue { kind = row.kind, id = row.Id, name = row.Name, message = message, error = error }); } } public static IEnumerable<string> Items(ContentRecord row) { if (row.value is Npc npc) { string[] array = new string[8] { npc.hair, npc.beard, npc.chest, npc.legs, npc.helmet, npc.cape, npc.left, npc.right }; for (int i = 0; i < array.Length; i++) { yield return array[i]; } } if (row.value is Kit kit) { foreach (ItemAmount item in kit.items) { yield return item.prefab; } } if (row.value is EventChest eventChest) { yield return eventChest.keyItem; } object value = row.value; if (!(value is Graph graph)) { yield break; } foreach (GraphParameter item2 in graph.parameters.Where((GraphParameter graphParameter) => graphParameter.kind == "Item")) { yield return item2.defaultValue; } foreach (Node node in graph.nodes) { if (new string[5] { "GiveItem", "TakeItem", "HasItem", "Trade", "HasRecipe" }.Contains(node.kind)) { yield return node.reference; } if (node.kind == "Trade") { yield return node.key; } yield return node.objectiveItem; foreach (QuestObjective item3 in node.questObjectives.Where((QuestObjective questObjective) => questObjective.kind == "Item")) { yield return item3.reference; } foreach (DialogueRequirement item4 in node.optionRequirements.Where((DialogueRequirement dialogueRequirement) => dialogueRequirement.kind == "Item")) { yield return item4.key; } } } } public static class ContentTransfer { public const int Megabyte = 1048576; public const int MaxWorldMegabytes = 16; public const int MaxMessageBytes = 50331648; public const int MaxWireCharacters = 67108864; private const string CompressedPrefix = "gz:"; private static readonly Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); public static string Encode(string json, int limit = 50331648) { if (json == null || Utf8.GetByteCount(json) > limit) { throw new InvalidDataException("WeaverEditor message is too large to transfer."); } if (json.Length < 16384) { return json; } byte[] bytes = Utf8.GetBytes(json); using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true)) { gZipStream.Write(bytes, 0, bytes.Length); } string text = "gz:" + Convert.ToBase64String(memoryStream.ToArray()); return (text.Length < json.Length) ? text : json; } public static string Decode(string wire, int limit = 50331648) { if (wire == null || wire.Length > 67108864) { throw new InvalidDataException("WeaverEditor transfer exceeds its size limit."); } if (!wire.StartsWith("gz:", StringComparison.Ordinal)) { if (Utf8.GetByteCount(wire) > limit) { throw new InvalidDataException("WeaverEditor message exceeds its size limit."); } return wire; } using MemoryStream stream = new MemoryStream(Convert.FromBase64String(wire.Substring("gz:".Length))); using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[8192]; int num; while ((num = gZipStream.Read(array, 0, array.Length)) > 0) { if (memoryStream.Length + num > limit) { throw new InvalidDataException("Expanded WeaverEditor message exceeds its size limit."); } memoryStream.Write(array, 0, num); } return Utf8.GetString(memoryStream.ToArray()); } public static int ByteCount(string json) { return Utf8.GetByteCount(json); } } [Serializable] public sealed class CostumeDefinition { public string prefab = ""; public float scale = 1f; public Vec offset; public float yaw; public bool animate = true; public bool controlCreature; public bool hideBar = true; public CostumeDefinition Copy() { return new CostumeDefinition { prefab = prefab, scale = scale, offset = offset, yaw = yaw, animate = animate, controlCreature = controlCreature, hideBar = hideBar }; } } [Serializable] public sealed class CostumeState { public long peer; public long characterOwner; public uint characterObject; public long bodyOwner; public uint bodyObject; public string token = ""; public bool adminOnly = true; public string source = ""; public CostumeDefinition costume = new CostumeDefinition(); public CostumeState Copy() { return new CostumeState { peer = peer, characterOwner = characterOwner, characterObject = characterObject, bodyOwner = bodyOwner, bodyObject = bodyObject, token = token, adminOnly = adminOnly, source = source, costume = costume?.Copy() }; } } [Serializable] public sealed class CostumeRoster { public string world = ""; public string session = ""; public long revision; public CostumeState[] states = Array.Empty<CostumeState>(); } public static class CostumeRules { public const int MaximumCostumes = 64; public const int MaximumTransforms = 2048; public const int MaximumRenderers = 256; public const long MaximumVertices = 2000000L; public const double LeaseSeconds = 45.0; public static string Check(CostumeDefinition costume) { if (costume == null) { return "Choose a costume first."; } string prefab = costume.prefab; if (string.IsNullOrWhiteSpace(prefab) || prefab.Length > 160 || prefab != prefab.Trim() || prefab.Any((char c) => char.IsControl(c) || c == '/' || c == '\\')) { return "Choose a registered model from the costume list."; } if (!Finite(costume.scale) || costume.scale < 0.1f || costume.scale > 5f) { return "Costume size must be between 0.1 and 5."; } if (!costume.offset.Finite || Math.Abs(costume.offset.x) > 5f || Math.Abs(costume.offset.y) > 5f || Math.Abs(costume.offset.z) > 5f) { return "Costume offsets must be between -5 and 5 metres."; } if (!Finite(costume.yaw) || costume.yaw < -360f || costume.yaw > 360f) { return "Costume facing must be between -360 and 360 degrees."; } return ""; } public static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } public static bool Token(string value) { Guid result; if (value != null && value.Length == 32) { return Guid.TryParseExact(value, "N", out result); } return false; } public static int ClipScore(string name, bool moving) { if (string.IsNullOrEmpty(name)) { return 0; } string text = name.ToLowerInvariant(); if (new string[10] { "attack", "death", "dead", "damage", "hit", "stagger", "spawn", "summon", "intro", "outro" }.Any(text.Contains)) { return 0; } string[] array = ((!moving) ? new string[3] { "idle", "stand", "rest" } : new string[4] { "walk", "run", "move", "fly" }); for (int i = 0; i < array.Length; i++) { if (text.Contains(array[i])) { return 100 - i * 10 + ((text == array[i]) ? 5 : 0); } } return 0; } } public sealed class CostumeReplica { private string session = ""; private long revision = -1L; private double expires; private CostumeState[] states = Array.Empty<CostumeState>(); public bool Receive(CostumeRoster roster, string world, double now) { if (roster == null || string.IsNullOrEmpty(world) || roster.world != world || !CostumeRules.Token(roster.session) || roster.revision < 0 || roster.states == null || roster.states.Length > 64 || double.IsNaN(now) || double.IsInfinity(now)) { return false; } if (session != "" && (session != roster.session || roster.revision < revision)) { return false; } HashSet<long> hashSet = new HashSet<long>(); HashSet<(long, uint)> hashSet2 = new HashSet<(long, uint)>(); HashSet<(long, uint)> hashSet3 = new HashSet<(long, uint)>(); CostumeState[] array = roster.states; foreach (CostumeState costumeState in array) { if (costumeState == null || costumeState.characterObject == 0 || !CostumeRules.Token(costumeState.token) || CostumeRules.Check(costumeState.costume) != "" || !hashSet.Add(costumeState.peer) || !hashSet2.Add((costumeState.characterOwner, costumeState.characterObject))) { return false; } if (costumeState.costume.controlCreature != (costumeState.bodyObject != 0)) { return false; } if (!costumeState.adminOnly && costumeState.costume.controlCreature && (costumeState.source == null || !costumeState.source.StartsWith("reward:", StringComparison.Ordinal) || !CostumeRules.Token(costumeState.source.Substring("reward:".Length)))) { return false; } if (costumeState.costume.controlCreature && (costumeState.bodyOwner == 0L || (costumeState.bodyOwner == costumeState.characterOwner && costumeState.bodyObject == costumeState.characterObject) || !hashSet3.Add((costumeState.bodyOwner, costumeState.bodyObject)))) { return false; } } states = roster.states.Select((CostumeState state) => state.Copy()).ToArray(); session = roster.session; revision = roster.revision; expires = now + 45.0; return true; } public CostumeState[] Active(double now) { if (!(now < expires)) { return Array.Empty<CostumeState>(); } return states.Select((CostumeState state) => state.Copy()).ToArray(); } public void Clear() { session = ""; revision = -1L; expires = 0.0; states = Array.Empty<CostumeState>(); } } public static class CostumeAccess { public const double DefaultHours = 12.0; public static bool ValidDuration(double hours) { if (!double.IsNaN(hours) && !double.IsInfinity(hours)) { if (hours != -1.0) { if (hours >= 0.0) { return hours <= 8760.0; } return false; } return true; } return false; } public static double Deadline(CostumeReward reward, double serverHours, double now) { if (!ValidDuration(reward.durationHours) || serverHours < 0.0 || serverHours > 8760.0 || !ValidDuration(serverHours) || double.IsNaN(now) || double.IsInfinity(now) || now <= 0.0) { throw new InvalidOperationException("Choose a valid costume access duration."); } double num = ((reward.durationHours < 0.0) ? serverHours : reward.durationHours); if (num != 0.0) { return now + num * 3600.0; } return 0.0; } public static CostumeUnlock Grant(string player, CostumeReward reward, double serverHours, double now) { return new CostumeUnlock { player = player, reward = reward.id, unlocked = now, expires = Deadline(reward, serverHours, now), expiryInitialized = true }; } public static bool Active(CostumeUnlock unlock, double now) { if (unlock != null && unlock.expiryInitialized && !double.IsNaN(unlock.expires) && !double.IsInfinity(unlock.expires) && unlock.expires >= 0.0) { if (unlock.expires != 0.0) { return now < unlock.expires; } return true; } return false; } public static bool Migrate(CostumeUnlock unlock, CostumeReward reward, double serverHours, double now) { if (unlock.expiryInitialized) { return false; } unlock.expires = Deadline(reward, serverHours, now); unlock.expiryInitialized = true; if (unlock.unlocked <= 0.0) { unlock.unlocked = now; } return true; } } [Serializable] public sealed class CostumeReward { public string id = Id.New(); public string name = "New costume"; public CostumeDefinition costume = new CostumeDefinition(); public string controls = "Creature"; public double durationHours = -1.0; public double accessExpires; public CostumeReward Copy() { return new CostumeReward { id = id, name = name, costume = costume?.Copy(), controls = controls, durationHours = durationHours, accessExpires = accessExpires }; } } [Serializable] public sealed class CostumeClaim { public string token = Id.New(); public string player = ""; public string reward = ""; public double issued; public bool used; } [Serializable] public sealed class CostumeUnlock { public string player = ""; public string reward = ""; public double unlocked; public double expires; public bool expiryInitialized; } public static class CostumeRewardRules { public const string ClaimPrefab = "VW_CostumeClaim"; public const int MaximumRewards = 256; public const int MaximumClaimsPerPlayer = 256; public static string Check(CostumeReward reward) { if (reward == null || !CostumeRules.Token(reward.id)) { return "Choose a saved costume reward."; } if (string.IsNullOrWhiteSpace(reward.name) || reward.name.Length > 100 || reward.name.Any(char.IsControl)) { return "Give the costume a s