fix: Adds command help, webpage, and fixes issues with other commands (#1669)

### Summary

- Fixes `[AdvancedSearch` being accessible by players 😱 
- Adds `[GenCommands` to generate the same commands html page on https://muo.gg/commands.
- Fixes `[helpinfo` so all commands properly show up!

> [!WARNING]  
> ### Developer Warning:
> Commands must now be registered in the `Configure` bootup phase.
> If a command is not registered early enough, it may not be available to systems like [helpinfo
> that cache their information.

> [!NOTE]  
> ### Developer Note:
> Various commands related to generating content have been changed to _Developer_ and above access level.

### Screenshots
<img width="673" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/b105b5c9-5eb4-4ace-93ff-1bfb31e7132f">

<img width="547" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/e97487e8-47a5-4aa7-89cc-9fe3deda584d">
This commit is contained in:
Kamron Batman 2024-02-10 00:19:19 -08:00 committed by GitHub
parent 3244704ea2
commit fffda53263
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
88 changed files with 1163 additions and 557 deletions

View file

@ -10,8 +10,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force
- name: Extract commands web file
run: |
7z x ./docs/commands/commands.7z -o./docs/commands
rm ./docs/commands/commands.7z
- name: Deploy commands file
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: ./docs/commands
clean: false
force: false

1
.gitignore vendored
View file

@ -22,6 +22,7 @@
/Distribution/runtimes
/Distribution/nohup.out
/Distribution/ref
/Distribution/web
/Projects/*/obj
/Projects/*/bin

View file

@ -0,0 +1,531 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="In-game commands for the Ultima Online Server Emulator ModernUO">
<meta name="author" content="Kamron Batman">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="https://modernuo.com/branding/favicon.png">
<title>ModernUO Commands</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com/3.4.1?plugins=typography@0.5.10"></script>
<script>
tailwind.config = {
theme: {
colors: {
inherit: "inherit",
current: "currentColor",
transparent: "transparent",
black: "#000",
white: "#fff",
counselor: "rgb(80, 80, 200)",
gamemaster: "rgb(200, 128, 112)",
seer: "rgb(128, 168, 128)",
administrator: "rgb(0, 152, 136)",
developer: "rgb(112, 88, 160)",
owner: "rgb(208, 144, 0)",
"modern-black": "#212121",
"modern-black-hover": "#2F2F2F",
"modern-dark-black": "#1A1A1A",
gold: {
50: "#fbf3ee",
100: "#f8e9dd",
200: "#f0d9bd",
300: "#e8cea1",
400: "#dfc688",
500: "#d5bf74",
600: "#cab664",
700: "#bdab58",
800: "#b09f4f",
900: "#a29147",
},
slate: {
50: "#f8fafc",
100: "#f1f5f9",
200: "#e2e8f0",
300: "#cbd5e1",
400: "#94a3b8",
500: "#64748b",
600: "#475569",
700: "#334155",
800: "#1e293b",
900: "#0f172a",
950: "#020617"
},
gray: {
50: "#f9fafb",
100: "#f3f4f6",
200: "#e5e7eb",
300: "#d1d5db",
400: "#9ca3af",
500: "#6b7280",
600: "#4b5563",
700: "#374151",
800: "#1f2937",
900: "#111827",
950: "#030712"
},
zinc: {
50: "#fafafa",
100: "#f4f4f5",
200: "#e4e4e7",
300: "#d4d4d8",
400: "#a1a1aa",
500: "#71717a",
600: "#52525b",
700: "#3f3f46",
800: "#27272a",
900: "#18181b",
950: "#09090b"
},
neutral: {
50: "#fafafa",
100: "#f5f5f5",
200: "#e5e5e5",
300: "#d4d4d4",
400: "#a3a3a3",
500: "#737373",
600: "#525252",
700: "#404040",
800: "#262626",
900: "#171717",
950: "#0a0a0a"
},
stone: {
50: "#fafaf9",
100: "#f5f5f4",
200: "#e7e5e4",
300: "#d6d3d1",
400: "#a8a29e",
500: "#78716c",
600: "#57534e",
700: "#44403c",
800: "#292524",
900: "#1c1917",
950: "#0c0a09"
},
red: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
950: "#450a0a"
},
orange: {
50: "#fff7ed",
100: "#ffedd5",
200: "#fed7aa",
300: "#fdba74",
400: "#fb923c",
500: "#f97316",
600: "#ea580c",
700: "#c2410c",
800: "#9a3412",
900: "#7c2d12",
950: "#431407"
},
amber: {
50: "#fffbeb",
100: "#fef3c7",
200: "#fde68a",
300: "#fcd34d",
400: "#fbbf24",
500: "#f59e0b",
600: "#d97706",
700: "#b45309",
800: "#92400e",
900: "#78350f",
950: "#451a03"
},
yellow: {
50: "#fefce8",
100: "#fef9c3",
200: "#fef08a",
300: "#fde047",
400: "#facc15",
500: "#eab308",
600: "#ca8a04",
700: "#a16207",
800: "#854d0e",
900: "#713f12",
950: "#422006"
},
lime: {
50: "#f7fee7",
100: "#ecfccb",
200: "#d9f99d",
300: "#bef264",
400: "#a3e635",
500: "#84cc16",
600: "#65a30d",
700: "#4d7c0f",
800: "#3f6212",
900: "#365314",
950: "#1a2e05"
},
green: {
50: "#f0fdf4",
100: "#dcfce7",
200: "#bbf7d0",
300: "#86efac",
400: "#4ade80",
500: "#22c55e",
600: "#16a34a",
700: "#15803d",
800: "#166534",
900: "#14532d",
950: "#052e16"
},
emerald: {
50: "#ecfdf5",
100: "#d1fae5",
200: "#a7f3d0",
300: "#6ee7b7",
400: "#34d399",
500: "#10b981",
600: "#059669",
700: "#047857",
800: "#065f46",
900: "#064e3b",
950: "#022c22"
},
teal: {
50: "#f0fdfa",
100: "#ccfbf1",
200: "#99f6e4",
300: "#5eead4",
400: "#2dd4bf",
500: "#14b8a6",
600: "#0d9488",
700: "#0f766e",
800: "#115e59",
900: "#134e4a",
950: "#042f2e"
},
cyan: {
50: "#ecfeff",
100: "#cffafe",
200: "#a5f3fc",
300: "#67e8f9",
400: "#22d3ee",
500: "#06b6d4",
600: "#0891b2",
700: "#0e7490",
800: "#155e75",
900: "#164e63",
950: "#083344"
},
sky: {
50: "#f0f9ff",
100: "#e0f2fe",
200: "#bae6fd",
300: "#7dd3fc",
400: "#38bdf8",
500: "#0ea5e9",
600: "#0284c7",
700: "#0369a1",
800: "#075985",
900: "#0c4a6e",
950: "#082f49"
},
blue: {
50: "#eff6ff",
100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a",
950: "#172554"
},
indigo: {
50: "#eef2ff",
100: "#e0e7ff",
200: "#c7d2fe",
300: "#a5b4fc",
400: "#818cf8",
500: "#6366f1",
600: "#4f46e5",
700: "#4338ca",
800: "#3730a3",
900: "#312e81",
950: "#1e1b4b"
},
violet: {
50: "#f5f3ff",
100: "#ede9fe",
200: "#ddd6fe",
300: "#c4b5fd",
400: "#a78bfa",
500: "#8b5cf6",
600: "#7c3aed",
700: "#6d28d9",
800: "#5b21b6",
900: "#4c1d95",
950: "#2e1065"
},
purple: {
50: "#faf5ff",
100: "#f3e8ff",
200: "#e9d5ff",
300: "#d8b4fe",
400: "#c084fc",
500: "#a855f7",
600: "#9333ea",
700: "#7e22ce",
800: "#6b21a8",
900: "#581c87",
950: "#3b0764"
},
fuchsia: {
50: "#fdf4ff",
100: "#fae8ff",
200: "#f5d0fe",
300: "#f0abfc",
400: "#e879f9",
500: "#d946ef",
600: "#c026d3",
700: "#a21caf",
800: "#86198f",
900: "#701a75",
950: "#4a044e"
},
pink: {
50: "#fdf2f8",
100: "#fce7f3",
200: "#fbcfe8",
300: "#f9a8d4",
400: "#f472b6",
500: "#ec4899",
600: "#db2777",
700: "#be185d",
800: "#9d174d",
900: "#831843",
950: "#500724"
},
rose: {
50: "#fff1f2",
100: "#ffe4e6",
200: "#fecdd3",
300: "#fda4af",
400: "#fb7185",
500: "#f43f5e",
600: "#e11d48",
700: "#be123c",
800: "#9f1239",
900: "#881337",
950: "#4c0519"
}
},
},
}
</script>
</head>
<body class="bg-modern-dark-black text-gold-300 max-w-4xl">
<div id="app" class="min-h-screen p-2">
<div class="max-w-4xl mx-auto">
<div class="bg-modern-black p-6 rounded-lg shadow-lg">
<!-- Logo -->
<div class="flex justify-center">
<img src="https://modernuo.com/branding/logo.svg" class="h-24 w-24">
</div>
<!-- Title -->
<h1 class="text-center text-white text-3xl opacity-70 p-2 pb-12 font-light">ModernUO Commands</h1>
<!-- Tabs -->
<ul id="tabs" class="flex mb-0 list-none flex-wrap pt-3 pb-4 flex-row place-content-center">
<!-- JavaScript will populate tabs here -->
</ul>
<!-- Tab Content -->
<div id="tabContent" class="p-4 border-t border-gold-500">
<!-- JavaScript will populate tab content here -->
</div>
</div>
</div>
</div>
<!-- Toolip -->
<div id="tooltip" class="absolute p-2 bg-modern-black text-white rounded-md shadow-lg max-w-lg hidden"></div>
<span id="commands" style="display:none; visibility: hidden">
<!-- COMMANDS -->
</span>
<span id="modifiers" style="display:none; visibility: hidden">
<!-- MODIFIERS -->
</span>
<script>
/*
Modifiers Structure:
{
"modifier": {
name: "Modifier",
usage: "[modifier <command> [condition]",
description: "long description. Optional condition arguments can further restrict the set of objects."
}
}
*/
let allModifiers = {};
try {
allModifiers = JSON.parse(document.querySelectorAll('#modifiers')[0].innerText.trim() || {});
} catch {}
/*
Commands Structure:
{
"AccessLevel": [
{
command: "CommandName",
aliases: ["cn"],
usage: "CommandName <options>",
modifiers: ["Global", "Online", "Region", "Multi", "Area", "Self"],
description: "Long Description"
}
]
}
*/
let commands = {};
try {
commands = JSON.parse(document.querySelectorAll('#commands')[0].innerText.trim() || {});
} catch {}
function createTab(name, selected) {
const tab = document.createElement("li");
tab.className = "mr-2 last:mr-0 flex-auto text-center";
const tabLink = document.createElement("a");
tabLink.href = "#";
tabLink.className = "text-xs font-bold uppercase px-5 py-3 rounded block leading-normal text-white bg-modern-black hover:bg-modern-black-hover hover:bg-opacity-70 transition-background-color ease-in-out duration-700";
tabLink.innerText = name;
tabLink.onclick = function (e) {
e.preventDefault();
document.querySelectorAll("#tabs a").forEach(tab => {
tab.classList.remove("bg-gold-500", "text-modern-black");
tab.classList.add("bg-modern-black", "text-white", "hover:bg-modern-black-hover", "hover:bg-opacity-70");
});
tabLink.classList.remove("bg-gold-900", "text-white", "hover:bg-modern-black-hover", "hover:bg-opacity-70");
tabLink.classList.add("bg-gold-500", "text-modern-black");
loadTabContent(name);
};
if (selected) {
tabLink.classList.remove("bg-gold-900", "text-white", "hover:bg-modern-black-hover", "hover:bg-opacity-70");
tabLink.classList.add("bg-gold-500", "text-modern-black");
}
tab.appendChild(tabLink);
return tab;
}
function createTable(tabData) {
let table = `<table class="min-w-full border-collapse table-fixed"><tbody>`;
tabData.forEach(item => {
table += `
<tr class="border-b">
<td class="px-4 py-4 text-sm font-medium text-gold-500 w-1/4 "><p>${item.name}</p></td>
<td class="text-sm text-gold-900 px-6 py-4">
`;
const usageParts = (item.usage || item.name).split(" ");
const usage = `${usageParts[0]} <span class='text-gold-700 font-light'>${usageParts.slice(1).join(" ")}</span>`;
table += `<div class="text-white">Usage: <span class="font-bold text-gold-500">${usage}</span></div>`;
if (item.aliases && item.aliases.length > 0) {
table += `<div class="text-white">Aliases: <span class="text-gold-500">${item.aliases.join(", ")}</span></div>`;
}
if (item.modifiers && item.modifiers.length > 0) {
table += "<div>";
item.modifiers.forEach((mod, index) => {
table += `<span class="modifier cursor-pointer italic text-gold-300"
data-modifier="${mod}"
data-command="${item.name}">${mod}</span>${index !== item.modifiers.length - 1 ? ", " : ""}`;
});
table += "</div>";
}
if (item.description) {
table += `<div class="text-white pt-2">${item.description}</div>`;
}
table += "</td></tr>";
});
table += "</tbody></table>";
return table;
}
function loadTabContent(tabName) {
const tabContent = document.getElementById("tabContent");
tabContent.innerHTML = createTable(commands[tabName]);
// Add event listeners to modifiers
const modifiers = document.querySelectorAll(".modifier");
modifiers.forEach(modNode => {
modNode.addEventListener("mouseenter", (event) => {
const mod = allModifiers[modNode.dataset.modifier];
const command = modNode.dataset.command.toLowerCase()
let aliases = "";
if (mod.accessors.length > 1) {
aliases = `<p class="text-white">Aliases: <span class="text-gold-500">${mod.accessors.slice(1).join(", ")}</span></p>`;
}
const usageParts = (mod.usage || mod.accessors[0]).split(" ");
const usage = `${usageParts[0]} <span class='text-gold-700 font-light'>${usageParts.slice(1).join(" ")}</span>`
.replace("<command>", command);
const content = `
<p class="font-bold text-gold-500">${mod.accessors[0]}</p>
<p class="text-white">Access Level: <span class="text-${mod.accessLevel.toLowerCase()}">${mod.accessLevel}</span></p>
${aliases}
<p class="text-white">Usage: <span class="font-bold text-gold-500">${usage}</span></p>
<p class="italic text-white pt-2">${mod.description}</p>
`;
showTooltip(event, content);
});
modNode.addEventListener("mouseleave", hideTooltip);
});
}
function initTabs() {
const tabsContainer = document.getElementById("tabs");
Object.keys(commands).forEach((tabName, index) => {
const tab = createTab(tabName, index === 0);
tabsContainer.appendChild(tab);
if (index === 0) { // Load first tab content by default
loadTabContent(tabName);
}
});
}
// Function to show the tooltip
function showTooltip(event, content) {
const tooltip = document.getElementById("tooltip");
tooltip.innerHTML = content;
tooltip.style.left = `${event.pageX + 15}px`;
tooltip.style.top = `${event.pageY + 15}px`;
tooltip.classList.remove("hidden");
}
// Function to hide the tooltip
function hideTooltip() {
const tooltip = document.getElementById("tooltip");
tooltip.classList.add("hidden");
}
const escapeHtml = (unsafe) => {
return unsafe.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#039;");
}
initTabs();
</script>
</body>
</html>

View file

@ -1,5 +1,4 @@
using System;
using System.Buffers.Binary;
using System.Net;
using Xunit;

View file

@ -1,3 +1,18 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Commands.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Generic;
@ -91,7 +106,7 @@ public class CommandEntry : IComparable<CommandEntry>
}
}
public record CommandInfo(AccessLevel AccessLevel, string Name, string[] Aliases, string Usage, string Description);
public record CommandInfo(AccessLevel AccessLevel, string Name, string[] Aliases, string Usage, string[] Modifiers, string Description);
public class CommandInfoSorter : IComparer<CommandInfo>
{

View file

@ -19,7 +19,7 @@ namespace Server.Network;
public static class DumpNetStates
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("DumpNetStates", AccessLevel.Administrator, DumpNetStatesCommand);
}

View file

@ -14,6 +14,7 @@
*************************************************************************/
using System.Runtime.CompilerServices;
using System.Text;
namespace Server.Utilities;
@ -45,4 +46,14 @@ public static class Html
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Center(this string text, string color, int size) => $"<BASEFONT COLOR={color} SIZE={size}><CENTER>{text}</CENTER></BASEFONT>";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string EscapeHtml(this string input) =>
new StringBuilder(input.Length).Append(input)
.Replace("<", "&lt;")
.Replace(">", "&gt;")
.Replace("&", "&amp;")
.Replace("\"", "&quot;")
.Replace("'", "&#39;")
.ToString();
}

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Server.Accounting;
using Server.Engines.Help;
@ -56,6 +55,11 @@ public static class AccountHandler
"accountHandler.enablePlayerPasswordCommand",
false
);
if (PasswordCommandEnabled)
{
CommandSystem.Register("Password", AccessLevel.Player, Password_OnCommand);
}
}
public static void Initialize()
@ -63,11 +67,6 @@ public static class AccountHandler
EventSink.DeleteRequest += EventSink_DeleteRequest;
EventSink.AccountLogin += EventSink_AccountLogin;
EventSink.GameLogin += EventSink_GameLogin;
if (PasswordCommandEnabled)
{
CommandSystem.Register("Password", AccessLevel.Player, Password_OnCommand);
}
}
[Usage("Password <newPassword> <repeatPassword>")]

View file

@ -48,7 +48,7 @@ namespace Server.Commands
if (command == null)
{
e.Mobile.SendMessage(
$"That is either an invalid command name or one that does not support this modifier: {commandString}."
$"That is either an invalid command name or one that does not support this modifier: {commandString}."
);
return;
}
@ -171,7 +171,7 @@ namespace Server.Commands
return true;
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Batch", AccessLevel.Counselor, Batch_OnCommand);
}

View file

@ -2,7 +2,7 @@ namespace Server.Commands
{
public static class DragEffects
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("DragEffects", AccessLevel.Developer, DragEffects_OnCommand);
}

View file

@ -7,7 +7,7 @@ namespace Server.Commands
{
public static class Dupe
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Dupe", AccessLevel.GameMaster, Dupe_OnCommand);
CommandSystem.Register("DupeInBag", AccessLevel.GameMaster, DupeInBag_OnCommand);

View file

@ -8,7 +8,7 @@ namespace Server.Commands
{
private const string ExportFile = @"C:\Uo\WorldForge\items.wsc";
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("ExportWSC", AccessLevel.Administrator, Export_OnCommand);
}

View file

@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using Server.Commands.Generic;
using Server.Json;
using Server.Utilities;
namespace Server.Commands;
public static class GenCommands
{
public static void Configure()
{
CommandSystem.Register("GenCommands", AccessLevel.Developer, GenCommandsWebpage_OnCommand);
}
[Usage("GenCommands [<command>]")]
[Aliases("GenCmdWeb")]
[Description("Generates a webpage to the web folder with a list of all commands.")]
private static void GenCommandsWebpage_OnCommand(CommandEventArgs e)
{
var allCommands = HelpInfo.SortedHelpInfo;
var allModifiers = BaseCommandImplementor.Implementors;
Dictionary<string, BaseCommandImplementor> modifiersByAccessor = [];
foreach (var modifier in allModifiers)
{
modifiersByAccessor[modifier.Accessors[0]] = modifier;
}
Dictionary<AccessLevel, List<CommandInfo>> commandsByAccessLevel = [];
foreach (var command in allCommands)
{
if (!commandsByAccessLevel.TryGetValue(command.AccessLevel, out var list))
{
commandsByAccessLevel[command.AccessLevel] = list = [];
}
list.Add(command);
}
// Serialization options with camel case naming policy
var options = JsonConfig.GetOptions();
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
// Write modifiers as JSON. All keys must be lowercase.
var modifiersJson = JsonSerializer.Serialize(
modifiersByAccessor,
options
);
// Write commands as JSON. All keys must be lowercase.
var commandsJson = JsonSerializer.Serialize(
commandsByAccessLevel,
options
);
var template = File.ReadAllText(Path.Combine(Core.BaseDirectory, "Data", "commands.html"))
.Replace("<!-- COMMANDS -->", commandsJson.EscapeHtml())
.Replace("<!-- MODIFIERS -->", modifiersJson.EscapeHtml());
// Write the new file to the web folder
var webFolder = Path.Combine(Core.BaseDirectory, "web");
PathUtility.EnsureDirectory(webFolder);
var webPath = Path.Combine(webFolder, "commands.html");
File.WriteAllText(webPath, template, Encoding.UTF8);
e.Mobile.SendMessage($"Commands webpage generated at {webPath}.");
}
}

View file

@ -16,7 +16,7 @@ namespace Server.Commands.Generic
{
public static List<BaseCommand> AllCommands { get; } = new();
public static void Initialize()
public static void Configure()
{
Register(new KillCommand(true));
Register(new KillCommand(false));
@ -408,13 +408,13 @@ namespace Server.Commands.Generic
if (inGump)
{
Commands = new[] { "Message", "Msg" };
Usage = "Message \"text\"";
Usage = "Message <text>";
Description = "Sends a message to a targeted player.";
}
else
{
Commands = new[] { "Tell" };
Usage = "Tell \"text\"";
Usage = "Tell <text>";
Description = "Sends a system message to a targeted player.";
}
}

View file

@ -26,7 +26,7 @@ namespace Server.Commands.Generic
Description = "Inserts multiple targeted items into a customizable house's design.";
}
public static void Initialize()
public static void Configure()
{
TargetCommands.Register(new DesignInsertCommand());
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Server.Text;
namespace Server.Commands.Generic
@ -28,7 +29,7 @@ namespace Server.Commands.Generic
public abstract class BaseCommandImplementor
{
private static List<BaseCommandImplementor> m_Implementors;
private static List<BaseCommandImplementor> _implementors;
public BaseCommandImplementor() => Commands = new Dictionary<string, BaseCommand>(StringComparer.OrdinalIgnoreCase);
@ -44,19 +45,20 @@ namespace Server.Commands.Generic
public CommandSupport SupportRequirement { get; set; }
[JsonIgnore]
public Dictionary<string, BaseCommand> Commands { get; }
public static List<BaseCommandImplementor> Implementors
{
get
{
if (m_Implementors == null)
if (_implementors == null)
{
m_Implementors = new List<BaseCommandImplementor>();
_implementors = [];
RegisterImplementors();
}
return m_Implementors;
return _implementors;
}
}
@ -91,7 +93,7 @@ namespace Server.Commands.Generic
}
}
public bool CheckObjectTypes(Mobile from, BaseCommand command, Extensions ext, out bool items, out bool mobiles)
public static bool CheckObjectTypes(Mobile from, BaseCommand command, Extensions ext, out bool items, out bool mobiles)
{
items = mobiles = false;
@ -314,7 +316,7 @@ namespace Server.Commands.Generic
public static void Register(BaseCommandImplementor impl)
{
m_Implementors.Add(impl);
_implementors.Add(impl);
impl.Register();
}
}

View file

@ -18,7 +18,7 @@ namespace Server.Commands
{
public static class CommandHandlers
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Prefix = ServerConfiguration.GetOrUpdateSetting("commandsystem.prefix", "[");
@ -28,7 +28,7 @@ namespace Server.Commands
Register("GetFollowers", AccessLevel.GameMaster, GetFollowers_OnCommand);
Register("ClearFacet", AccessLevel.Administrator, ClearFacet_OnCommand);
Register("ClearFacet", AccessLevel.Developer, ClearFacet_OnCommand);
Register("Where", AccessLevel.Counselor, Where_OnCommand);

View file

@ -1,449 +1,427 @@
using System.Collections.Generic;
using System.Text;
using Server.Commands.Generic;
using Server.Gumps;
using Server.Network;
using Server.Text;
namespace Server.Commands
namespace Server.Commands;
public static class HelpInfo
{
public static class HelpInfo
private static List<CommandInfo> _sortedHelpInfo;
private static Dictionary<string, CommandInfo> _helpInfos;
public static Dictionary<string, CommandInfo> HelpInfos
{
public static Dictionary<string, CommandInfo> HelpInfos { get; } = new();
public static List<CommandInfo> SortedHelpInfo { get; private set; } = new();
public static void Initialize()
get
{
CommandSystem.Register("HelpInfo", AccessLevel.Player, HelpInfo_OnCommand);
if (_helpInfos == null)
{
FillTable();
}
FillTable();
return _helpInfos;
}
}
public static List<CommandInfo> SortedHelpInfo
{
get
{
if (_sortedHelpInfo == null)
{
FillTable();
}
return _sortedHelpInfo;
}
}
public static void Configure()
{
CommandSystem.Register("HelpInfo", AccessLevel.Player, HelpInfo_OnCommand);
}
public static void Initialize()
{
FillTable();
}
[Usage("HelpInfo [<command>]")]
[Description("Gives information on a specified command, or when no argument specified, displays a gump containing all commands")]
private static void HelpInfo_OnCommand(CommandEventArgs e)
{
if (e.Length > 0)
{
var arg = e.GetString(0).ToLower();
if (HelpInfos.TryGetValue(arg, out var c))
{
var m = e.Mobile;
if (m.AccessLevel >= c.AccessLevel)
{
m.SendGump(new CommandInfoGump(c));
}
else
{
m.SendMessage("You don't have access to that command.");
}
return;
}
e.Mobile.SendMessage($"Command '{arg}' not found!");
}
[Usage("HelpInfo [<command>]")]
[Description("Gives information on a specified command, or when no argument specified, displays a gump containing all commands")]
private static void HelpInfo_OnCommand(CommandEventArgs e)
{
if (e.Length > 0)
{
var arg = e.GetString(0).ToLower();
if (HelpInfos.TryGetValue(arg, out var c))
{
var m = e.Mobile;
e.Mobile.SendGump(new CommandListGump(0, e.Mobile, null));
}
if (m.AccessLevel >= c.AccessLevel)
public static void FillTable()
{
var list = new List<CommandInfo>();
var commands = CommandEntry.GetList();
for (var i = 0; i < commands.Count; ++i)
{
var e = commands[i];
var mi = e.Handler.Method;
var attrs = mi.GetCustomAttributes(typeof(UsageAttribute), false);
if (attrs.Length == 0)
{
continue;
}
var usage = attrs[0] as UsageAttribute;
attrs = mi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs.Length == 0)
{
continue;
}
if (usage == null || attrs[0] is not DescriptionAttribute desc)
{
continue;
}
attrs = mi.GetCustomAttributes(typeof(AliasesAttribute), false);
var aliases = attrs.Length == 0 ? null : attrs[0] as AliasesAttribute;
var descString = desc.Description
.ReplaceOrdinal("<", "(")
.ReplaceOrdinal(">", ")");
list.Add(new CommandInfo(e.AccessLevel, e.Command, aliases?.Aliases, usage.Usage, null, descString));
}
for (var i = 0; i < TargetCommands.AllCommands.Count; ++i)
{
var command = TargetCommands.AllCommands[i];
var usage = command.Usage;
var desc = command.Description;
if (usage == null || desc == null)
{
continue;
}
var cmds = command.Commands;
var cmd = cmds[0];
var aliases = new string[cmds.Length - 1];
for (var j = 0; j < aliases.Length; ++j)
{
aliases[j] = cmds[j + 1];
}
desc = desc
.ReplaceOrdinal("<", "(")
.ReplaceOrdinal(">", ")");
List<string> modsList = [];
foreach (var baseImpl in BaseCommandImplementor.Implementors)
{
if ((baseImpl.SupportRequirement & command.Supports) != 0)
{
modsList.Add(baseImpl.Accessors[0]);
}
}
var modifiers = modsList.ToArray();
list.Add(new CommandInfo(command.AccessLevel, cmd, aliases, usage, modifiers, desc));
}
var commandImpls = BaseCommandImplementor.Implementors;
for (var i = 0; i < commandImpls.Count; ++i)
{
var command = commandImpls[i];
var usage = command.Usage;
var desc = command.Description;
if (usage == null || desc == null)
{
continue;
}
var cmds = command.Accessors;
var cmd = cmds[0];
var aliases = new string[cmds.Length - 1];
for (var j = 0; j < aliases.Length; ++j)
{
aliases[j] = cmds[j + 1];
}
desc = desc
.ReplaceOrdinal("<", ")")
.ReplaceOrdinal(">", ")");
list.Add(new CommandInfo(command.AccessLevel, cmd, aliases, usage, null, desc));
}
list.Sort(new CommandInfoSorter());
_sortedHelpInfo = list;
_helpInfos = [];
foreach (var c in _sortedHelpInfo)
{
_helpInfos.TryAdd(c.Name.ToLower(), c);
}
}
public class CommandListGump : BaseGridGump
{
private const int EntriesPerPage = 15;
private readonly List<CommandInfo> _list;
private readonly int _page;
public CommandListGump(int page, Mobile from, List<CommandInfo> list) : base(30, 30)
{
_page = page;
if (list == null)
{
_list = [];
foreach (var c in SortedHelpInfo)
{
if (from.AccessLevel >= c.AccessLevel)
{
m.SendGump(new CommandInfoGump(c));
_list.Add(c);
}
}
}
else
{
_list = list;
}
AddNewPage();
if (_page > 0)
{
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
}
else
{
AddEntryHeader(20);
}
AddEntryHtml(
320,
Center(
$"Page {_page + 1} of {(_list.Count + EntriesPerPage - 1) / EntriesPerPage}"
)
);
if ((_page + 1) * EntriesPerPage < _list.Count)
{
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
}
else
{
AddEntryHeader(20);
}
var last = (int)AccessLevel.Player - 1;
for (int i = _page * EntriesPerPage, line = 0; line < EntriesPerPage && i < _list.Count; ++i, ++line)
{
var c = _list[i];
if (from.AccessLevel >= c.AccessLevel)
{
if ((int)c.AccessLevel != last)
{
AddNewLine();
AddEntryHtml(20 + OffsetSize + 320, Color(c.AccessLevel.ToString(), 0xFF0000));
AddEntryHeader(20);
line++;
}
last = (int)c.AccessLevel;
AddNewLine();
string name;
if (c.Aliases?.Length > 0)
{
using var sb = ValueStringBuilder.Create();
sb.Append($"{c.Name} <i>(");
for (var j = 0; j < c.Aliases.Length; ++j)
{
if (j != 0)
{
sb.Append(", ");
}
sb.Append(c.Aliases[j]);
}
sb.Append(")</i>");
name = sb.ToString();
}
else
{
m.SendMessage("You don't have access to that command.");
name = c.Name;
}
return;
AddEntryHtml(20 + OffsetSize + 320, name);
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 3 + i, ArrowRightWidth, ArrowRightHeight);
}
e.Mobile.SendMessage($"Command '{arg}' not found!");
}
e.Mobile.SendGump(new CommandListGump(0, e.Mobile, null));
FinishPage();
}
public static void FillTable()
public override void OnResponse(NetState sender, RelayInfo info)
{
var list = new List<CommandInfo>();
var commands = CommandEntry.GetList();
for (var i = 0; i < commands.Count; ++i)
var m = sender.Mobile;
switch (info.ButtonID)
{
var e = commands[i];
var mi = e.Handler.Method;
var attrs = mi.GetCustomAttributes(typeof(UsageAttribute), false);
if (attrs.Length == 0)
{
continue;
}
var usage = attrs[0] as UsageAttribute;
attrs = mi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs.Length == 0)
{
continue;
}
if (usage == null || attrs[0] is not DescriptionAttribute desc)
{
continue;
}
attrs = mi.GetCustomAttributes(typeof(AliasesAttribute), false);
var aliases = attrs.Length == 0 ? null : attrs[0] as AliasesAttribute;
var descString = desc.Description
.ReplaceOrdinal("<", "(")
.ReplaceOrdinal(">", ")");
if (aliases == null)
{
list.Add(new CommandInfo(e.AccessLevel, e.Command, null, usage.Usage, descString));
}
else
{
list.Add(new CommandInfo(e.AccessLevel, e.Command, aliases.Aliases, usage.Usage, descString));
for (var j = 0; j < aliases.Aliases.Length; j++)
case 0:
{
var newAliases = new string[aliases.Aliases.Length];
aliases.Aliases.CopyTo(newAliases, 0);
newAliases[j] = e.Command;
list.Add(new CommandInfo(e.AccessLevel, aliases.Aliases[j], newAliases, usage.Usage, descString));
m.CloseGump<CommandInfoGump>();
break;
}
}
}
for (var i = 0; i < TargetCommands.AllCommands.Count; ++i)
{
var command = TargetCommands.AllCommands[i];
var usage = command.Usage;
var desc = command.Description;
if (usage == null || desc == null)
{
continue;
}
var cmds = command.Commands;
var cmd = cmds[0];
var aliases = new string[cmds.Length - 1];
for (var j = 0; j < aliases.Length; ++j)
{
aliases[j] = cmds[j + 1];
}
desc = desc
.ReplaceOrdinal("<", "(")
.ReplaceOrdinal(">", ")");
if (command.Supports != CommandSupport.Single)
{
var sb = new StringBuilder(50 + desc.Length);
sb.Append("Modifiers: ");
if ((command.Supports & CommandSupport.Global) != 0)
case 1:
{
sb.Append("<i>Global</i>, ");
}
if ((command.Supports & CommandSupport.Online) != 0)
{
sb.Append("<i>Online</i>, ");
}
if ((command.Supports & CommandSupport.Region) != 0)
{
sb.Append("<i>Region</i>, ");
}
if ((command.Supports & CommandSupport.Contained) != 0)
{
sb.Append("<i>Contained</i>, ");
}
if ((command.Supports & CommandSupport.Multi) != 0)
{
sb.Append("<i>Multi</i>, ");
}
if ((command.Supports & CommandSupport.Area) != 0)
{
sb.Append("<i>Area</i>, ");
}
if ((command.Supports & CommandSupport.Self) != 0)
{
sb.Append("<i>Self</i>, ");
}
sb.Remove(sb.Length - 2, 2);
sb.Append("<br>");
sb.Append(desc);
desc = sb.ToString();
}
list.Add(new CommandInfo(command.AccessLevel, cmd, aliases, usage, desc));
for (var j = 0; j < aliases.Length; j++)
{
var newAliases = new string[aliases.Length];
aliases.CopyTo(newAliases, 0);
newAliases[j] = cmd;
list.Add(new CommandInfo(command.AccessLevel, aliases[j], newAliases, usage, desc));
}
}
var commandImpls = BaseCommandImplementor.Implementors;
for (var i = 0; i < commandImpls.Count; ++i)
{
var command = commandImpls[i];
var usage = command.Usage;
var desc = command.Description;
if (usage == null || desc == null)
{
continue;
}
var cmds = command.Accessors;
var cmd = cmds[0];
var aliases = new string[cmds.Length - 1];
for (var j = 0; j < aliases.Length; ++j)
{
aliases[j] = cmds[j + 1];
}
desc = desc
.ReplaceOrdinal("<", ")")
.ReplaceOrdinal(">", ")");
list.Add(new CommandInfo(command.AccessLevel, cmd, aliases, usage, desc));
for (var j = 0; j < aliases.Length; j++)
{
var newAliases = new string[aliases.Length];
aliases.CopyTo(newAliases, 0);
newAliases[j] = cmd;
list.Add(new CommandInfo(command.AccessLevel, aliases[j], newAliases, usage, desc));
}
}
list.Sort(new CommandInfoSorter());
SortedHelpInfo = list;
foreach (var c in SortedHelpInfo)
{
HelpInfos.TryAdd(c.Name.ToLower(), c);
}
}
public class CommandListGump : BaseGridGump
{
private const int EntriesPerPage = 15;
private readonly List<CommandInfo> m_List;
private readonly int m_Page;
public CommandListGump(int page, Mobile from, List<CommandInfo> list)
: base(30, 30)
{
m_Page = page;
if (list == null)
{
m_List = new List<CommandInfo>();
foreach (var c in SortedHelpInfo)
{
if (from.AccessLevel >= c.AccessLevel)
if (_page > 0)
{
m_List.Add(c);
}
}
}
else
{
m_List = list;
}
AddNewPage();
if (m_Page > 0)
{
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
}
else
{
AddEntryHeader(20);
}
AddEntryHtml(
160,
Center(
$"Page {m_Page + 1} of {(m_List.Count + EntriesPerPage - 1) / EntriesPerPage}"
)
);
if ((m_Page + 1) * EntriesPerPage < m_List.Count)
{
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
}
else
{
AddEntryHeader(20);
}
var last = (int)AccessLevel.Player - 1;
for (int i = m_Page * EntriesPerPage, line = 0; line < EntriesPerPage && i < m_List.Count; ++i, ++line)
{
var c = m_List[i];
if (from.AccessLevel >= c.AccessLevel)
{
if ((int)c.AccessLevel != last)
{
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, Color(c.AccessLevel.ToString(), 0xFF0000));
AddEntryHeader(20);
line++;
m.SendGump(new CommandListGump(_page - 1, m, _list));
}
last = (int)c.AccessLevel;
AddNewLine();
AddEntryHtml(20 + OffsetSize + 160, c.Name);
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 3 + i, ArrowRightWidth, ArrowRightHeight);
break;
}
}
FinishPage();
}
public override void OnResponse(NetState sender, RelayInfo info)
{
var m = sender.Mobile;
switch (info.ButtonID)
{
case 0:
case 2:
{
if ((_page + 1) * EntriesPerPage < SortedHelpInfo.Count)
{
m.CloseGump<CommandInfoGump>();
break;
m.SendGump(new CommandListGump(_page + 1, m, _list));
}
case 1:
break;
}
default:
{
var v = info.ButtonID - 3;
if (v >= 0 && v < _list.Count)
{
if (m_Page > 0)
var c = _list[v];
if (m.AccessLevel >= c.AccessLevel)
{
m.SendGump(new CommandListGump(m_Page - 1, m, m_List));
m.SendGump(new CommandInfoGump(c));
m.SendGump(new CommandListGump(_page, m, _list));
}
break;
}
case 2:
{
if ((m_Page + 1) * EntriesPerPage < SortedHelpInfo.Count)
else
{
m.SendGump(new CommandListGump(m_Page + 1, m, m_List));
m.SendMessage("You no longer have access to that command.");
m.SendGump(new CommandListGump(_page, m, null));
}
break;
}
default:
{
var v = info.ButtonID - 3;
if (v >= 0 && v < m_List.Count)
{
var c = m_List[v];
if (m.AccessLevel >= c.AccessLevel)
{
m.SendGump(new CommandInfoGump(c));
m.SendGump(new CommandListGump(m_Page, m, m_List));
}
else
{
m.SendMessage("You no longer have access to that command.");
m.SendGump(new CommandListGump(m_Page, m, null));
}
}
break;
}
}
}
}
public class CommandInfoGump : Gump
{
public CommandInfoGump(CommandInfo info, int width = 320, int height = 200)
: base(300, 50)
{
AddPage(0);
AddBackground(0, 0, width, height, 5054);
// AddImageTiled( 10, 10, width - 20, 20, 2624 );
// AddAlphaRegion( 10, 10, width - 20, 20 );
// AddHtmlLocalized( 10, 10, width - 20, 20, header, headerColor, false, false );
AddHtml(10, 10, width - 20, 20, Color(Center(info.Name), 0xFF0000));
// AddImageTiled( 10, 40, width - 20, height - 80, 2624 );
// AddAlphaRegion( 10, 40, width - 20, height - 80 );
var sb = new StringBuilder();
sb.Append("Usage: ");
var usage = info.Usage
.ReplaceOrdinal("<", "(")
.ReplaceOrdinal(">", ")");
sb.Append(usage);
sb.Append("<BR>");
var aliases = info.Aliases;
if (aliases?.Length > 0)
{
sb.Append($"Alias{(aliases.Length == 1 ? "" : "es")}: ");
for (var i = 0; i < aliases.Length; ++i)
{
if (i != 0)
{
sb.Append(", ");
}
sb.Append(aliases[i]);
break;
}
sb.Append("<BR>");
}
sb.Append("AccessLevel: ");
sb.Append(info.AccessLevel.ToString());
sb.Append("<BR>");
sb.Append("<BR>");
sb.Append(info.Description);
AddHtml(10, 40, width - 20, height - 80, sb.ToString(), false, true);
// AddImageTiled( 10, height - 30, width - 20, 20, 2624 );
// AddAlphaRegion( 10, height - 30, width - 20, 20 );
}
public string Color(string text, int color) => $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
public string Center(string text) => $"<CENTER>{text}</CENTER>";
}
}
public class CommandInfoGump : Gump
{
public CommandInfoGump(CommandInfo info, int width = 320, int height = 200) : base(300, 50)
{
AddPage(0);
AddBackground(0, 0, width, height, 5054);
AddHtml(10, 10, width - 20, 20, Color(Center(info.Name), 0xFF0000));
using var sb = ValueStringBuilder.Create();
sb.Append("Usage: ");
var usage = info.Usage
.ReplaceOrdinal("<", "(")
.ReplaceOrdinal(">", ")");
sb.Append(usage);
sb.Append("<BR>");
var aliases = info.Aliases;
if (aliases?.Length > 0)
{
sb.Append($"Alias{(aliases.Length == 1 ? "" : "es")}: ");
for (var i = 0; i < aliases.Length; ++i)
{
if (i != 0)
{
sb.Append(", ");
}
sb.Append(aliases[i]);
}
sb.Append("<BR>");
}
sb.Append("AccessLevel: ");
sb.Append(info.AccessLevel.ToString());
sb.Append("<BR>");
if (info.Modifiers?.Length > 0)
{
sb.Append("Modifiers: ");
for (var i = 0; i < info.Modifiers.Length; ++i)
{
if (i != 0)
{
sb.Append(", ");
}
sb.Append(info.Modifiers[i]);
}
sb.Append("<BR>");
sb.Append("<BR>");
}
sb.Append(info.Description);
AddHtml(10, 40, width - 20, height - 80, sb.ToString(), false, true);
}
public static string Color(string text, int color) => $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
public static string Center(string text) => $"<CENTER>{text}</CENTER>";
}
}

View file

@ -4,8 +4,6 @@ using System.Diagnostics;
using System.Reflection;
using System.Text;
using Server.Items;
using CPA = Server.CommandPropertyAttribute;
using static Server.Attributes;
using static Server.Types;
@ -13,7 +11,7 @@ namespace Server.Commands
{
public static class Add
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Tile", AccessLevel.GameMaster, Tile_OnCommand);
CommandSystem.Register("TileRXYZ", AccessLevel.GameMaster, TileRXYZ_OnCommand);

View file

@ -87,7 +87,7 @@ namespace Server.Gumps
);
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("AddMenu", AccessLevel.GameMaster, AddMenu_OnCommand);
}

View file

@ -18,9 +18,9 @@ namespace Server.Commands
private static Mobile m_Mobile;
private static int m_Count;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Decorate", AccessLevel.Administrator, Decorate_OnCommand);
CommandSystem.Register("Decorate", AccessLevel.Developer, Decorate_OnCommand);
}
[Usage("Decorate")]

View file

@ -18,9 +18,9 @@ namespace Server.Commands
private static Mobile m_Mobile;
private static int m_Count;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("DecorateMag", AccessLevel.Administrator, DecorateMag_OnCommand);
CommandSystem.Register("DecorateMag", AccessLevel.Developer, DecorateMag_OnCommand);
}
[Usage("DecorateMag")]

View file

@ -28,10 +28,10 @@ namespace Server.Commands
private const int SuccessHue = 72, WarningHue = 53, ErrorHue = 33;
private static readonly string TeleporterJsonDataPath = Path.Combine(Core.BaseDirectory, "Data/teleporters.json");
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("TelGen", AccessLevel.Administrator, GenTeleporter_OnCommand);
CommandSystem.Register("TelGenDelete", AccessLevel.Administrator, TelGenDelete_OnCommand);
CommandSystem.Register("TelGen", AccessLevel.Developer, GenTeleporter_OnCommand);
CommandSystem.Register("TelGenDelete", AccessLevel.Developer, TelGenDelete_OnCommand);
}
[Usage("TelGenDelete")]

View file

@ -8,7 +8,7 @@ namespace Server.Commands
{
public static class Profiling
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("DumpTimers", AccessLevel.Administrator, DumpTimers_OnCommand);
CommandSystem.Register("CountObjects", AccessLevel.Administrator, CountObjects_OnCommand);

View file

@ -5,8 +5,6 @@ using Server.Commands.Generic;
using Server.Gumps;
using Server.Targeting;
using Server.Text;
using CPA = Server.CommandPropertyAttribute;
using static Server.Attributes;
using static Server.Types;
@ -23,7 +21,7 @@ namespace Server.Commands
public static class Properties
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Props", AccessLevel.Counselor, Props_OnCommand);
}

View file

@ -4,7 +4,7 @@ namespace Server.Commands;
public class ResetRng
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("ResetRng", AccessLevel.Administrator, ResetRng_OnCommand);
}

View file

@ -6,7 +6,7 @@ namespace Server.Commands;
public static class StaffDress
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("ResetStaffDress", AccessLevel.Counselor, StaffDress_OnCommand);
}

View file

@ -4,7 +4,7 @@ namespace Server.Commands
{
public static class ShardTime
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Time", AccessLevel.Player, Time_OnCommand);
}

View file

@ -9,9 +9,9 @@ namespace Server.Commands
{
public static class SignParser
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("SignGen", AccessLevel.Administrator, SignGen_OnCommand);
CommandSystem.Register("SignGen", AccessLevel.Developer, SignGen_OnCommand);
}
[Usage("SignGen")]

View file

@ -5,7 +5,7 @@ namespace Server.Commands
{
public static class SkillsCommand
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("SetSkill", AccessLevel.GameMaster, SetSkill_OnCommand);
CommandSystem.Register("GetSkill", AccessLevel.GameMaster, GetSkill_OnCommand);

View file

@ -5,12 +5,7 @@ namespace Server.Commands
{
public static class SkillsMenu
{
public static void Initialize()
{
Register();
}
public static void Register()
public static void Configure()
{
CommandSystem.Register("Skills", AccessLevel.Counselor, Skills_OnCommand);
}

View file

@ -13,7 +13,7 @@ public static class StaffAccess
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string AccountTag(Serial serial) => $"OriginalStaffAccess:{serial}";
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("StaffAccess", AccessLevel.Player, StaffAccess_OnCommand);
@ -47,7 +47,7 @@ public static class StaffAccess
}
[Usage("StaffAccess <access level>")]
[Description("Overrides your access level.")]
[Description("Changes your access level. Only access levels at or below your current access level can be set. Use the same access level to reset.")]
public static void StaffAccess_OnCommand(CommandEventArgs e)
{
var m = e.Mobile;

View file

@ -8,7 +8,7 @@ namespace Server.Commands
{
public static class VisibilityList
{
public static void Initialize()
public static void Configure()
{
EventSink.Login += OnLogin;

View file

@ -16,7 +16,7 @@ namespace Server.Commands
All = Items | Mobiles | Multis
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Wipe", AccessLevel.GameMaster, WipeAll_OnCommand);
CommandSystem.Register("WipeItems", AccessLevel.GameMaster, WipeItems_OnCommand);

View file

@ -2,16 +2,14 @@ namespace Server.Engines.AdvancedSearch;
public static class AdvancedSearchCommand
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("XmlFind", AccessLevel.Player, OnCommand); // For those old school peeps!
CommandSystem.Register("AdvancedSearch", AccessLevel.Player, OnCommand);
CommandSystem.Register("AdvSrch", AccessLevel.Player, OnCommand);
CommandSystem.Register("AS", AccessLevel.Player, OnCommand);
CommandSystem.Register("AdvancedSearch", AccessLevel.Administrator, OnCommand); // For those old school peeps!
}
[Usage("AdvancedSearch")]
[Description("Opens the advanced search gump.")]
[Aliases("AdvSrch", "AS", "XmlFind")]
private static void OnCommand(CommandEventArgs e)
{
var from = e.Mobile;

View file

@ -19,7 +19,7 @@ namespace Server.Engines.CannedEvil
{
public static class ChampionCommands
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("ClearChampByTarget", AccessLevel.Administrator, KillByTarget_OnCommand);
CommandSystem.Register("ClearChampByRegion", AccessLevel.Administrator, KillByRegion_OnCommand);

View file

@ -23,9 +23,9 @@ namespace Server.Engines.CannedEvil
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionGenerator));
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("GenChamps", AccessLevel.Owner, ChampGen_OnCommand);
CommandSystem.Register("GenChamps", AccessLevel.Developer, ChampGen_OnCommand);
}
private static readonly ChampionEntry[] LLLocations = {

View file

@ -1231,7 +1231,7 @@ namespace Server.Engines.ConPVP
Timer.StartTimer(TimeSpan.FromSeconds(10.0), Unregister);
}
public static void Initialize()
public static void Configure()
{
EventSink.Speech += EventSink_Speech;
EventSink.Login += EventSink_Login;

View file

@ -35,7 +35,7 @@ public class Recipe
public TextDefinition TextDefinition => _td ??= TextDefinition.Of(CraftItem.NameNumber, CraftItem.NameString);
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("LearnAllRecipes", AccessLevel.GameMaster, LearnAllRecipes_OnCommand);
CommandSystem.Register("ForgetAllRecipes", AccessLevel.GameMaster, ForgetAllRecipes_OnCommand);

View file

@ -6,10 +6,10 @@ namespace Server.Engines.Doom
{
public static class GenGauntlet
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("GenGauntlet", AccessLevel.Administrator, GenGauntlet_OnCommand);
CommandSystem.Register("RemGauntlet", AccessLevel.Administrator, RemoveGauntlet);
CommandSystem.Register("GenGauntlet", AccessLevel.Developer, GenGauntlet_OnCommand);
CommandSystem.Register("RemGauntlet", AccessLevel.Developer, RemoveGauntlet);
}
public static void RemoveGauntlet(CommandEventArgs e)

View file

@ -190,7 +190,7 @@ public partial class LeverPuzzleController : Item
}
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("GenLeverPuzzle", AccessLevel.Administrator, GenLampPuzzle_OnCommand);
}

View file

@ -639,14 +639,11 @@ namespace Server.Factions
return true;
}
public static void Initialize()
public static void Configure()
{
EventSink.Login += EventSink_Login;
EventSink.Logout += EventSink_Logout;
Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(10.0), HandleAtrophy);
Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), ProcessTick);
CommandSystem.Register("FactionElection", AccessLevel.GameMaster, FactionElection_OnCommand);
CommandSystem.Register("FactionCommander", AccessLevel.Administrator, FactionCommander_OnCommand);
CommandSystem.Register("FactionItemReset", AccessLevel.Administrator, FactionItemReset_OnCommand);
@ -654,6 +651,12 @@ namespace Server.Factions
CommandSystem.Register("FactionTownReset", AccessLevel.Administrator, FactionTownReset_OnCommand);
}
public static void Initialize()
{
Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(10.0), HandleAtrophy);
Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), ProcessTick);
}
public static void FactionTownReset_OnCommand(CommandEventArgs e)
{
var monoliths = BaseMonolith.Monoliths;

View file

@ -4,9 +4,9 @@ namespace Server.Factions
{
public static class Generator
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("GenerateFactions", AccessLevel.Administrator, GenerateFactions_OnCommand);
CommandSystem.Register("GenerateFactions", AccessLevel.Developer, GenerateFactions_OnCommand);
}
public static void GenerateFactions_OnCommand(CommandEventArgs e)

View file

@ -420,6 +420,11 @@ namespace Server.Factions
return true;
}
public static void Configure()
{
CommandSystem.Register("GrantTownSilver", AccessLevel.Administrator, GrantTownSilver_OnCommand);
}
public static void Initialize()
{
var towns = Towns;
@ -429,8 +434,6 @@ namespace Server.Factions
towns[i].Sheriff = towns[i].Sheriff;
towns[i].Finance = towns[i].Finance;
}
CommandSystem.Register("GrantTownSilver", AccessLevel.Administrator, GrantTownSilver_OnCommand);
}
public bool IsSheriff(Mobile mob) =>

View file

@ -121,7 +121,7 @@ namespace Server.Engines.Help
public static List<PageEntry> List { get; } = new();
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Pages", AccessLevel.Counselor, Pages_OnCommand);
}

View file

@ -29,7 +29,7 @@ namespace Server.Engines.Help
IEnumerator IEnumerable.GetEnumerator() => m_Queue.GetEnumerator();
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("SpeechLog", AccessLevel.Counselor, SpeechLog_OnCommand);
}

View file

@ -7,9 +7,9 @@ namespace Server.Commands
{
private static int m_Count;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("GenKhaldun", AccessLevel.Administrator, GenKhaldun_OnCommand);
CommandSystem.Register("GenKhaldun", AccessLevel.Developer, GenKhaldun_OnCommand);
}
public static bool FindMorphItem(int x, int y, int z, int inactiveItemID, int activeItemID)

View file

@ -144,6 +144,16 @@ namespace Server.Engines.MLQuests
public static void Configure()
{
Enabled = ServerConfiguration.GetOrUpdateSetting("questSystem.enableMLQuests", Core.ML);
CommandSystem.Register("MLQuestsInfo", AccessLevel.Administrator, MLQuestsInfo_OnCommand);
CommandSystem.Register("SaveQuest", AccessLevel.Administrator, SaveQuest_OnCommand);
CommandSystem.Register("SaveAllQuests", AccessLevel.Administrator, SaveAllQuests_OnCommand);
CommandSystem.Register("InvalidQuestItems", AccessLevel.Administrator, InvalidQuestItems_OnCommand);
TargetCommands.Register(new ViewQuestsCommand());
TargetCommands.Register(new ViewContextCommand());
EventSink.QuestGumpRequest += EventSink_QuestGumpRequest;
}
public static void Initialize()
@ -165,16 +175,6 @@ namespace Server.Engines.MLQuests
}
MLQuestPersistence.EnsureExistence();
CommandSystem.Register("MLQuestsInfo", AccessLevel.Administrator, MLQuestsInfo_OnCommand);
CommandSystem.Register("SaveQuest", AccessLevel.Administrator, SaveQuest_OnCommand);
CommandSystem.Register("SaveAllQuests", AccessLevel.Administrator, SaveAllQuests_OnCommand);
CommandSystem.Register("InvalidQuestItems", AccessLevel.Administrator, InvalidQuestItems_OnCommand);
TargetCommands.Register(new ViewQuestsCommand());
TargetCommands.Register(new ViewContextCommand());
EventSink.QuestGumpRequest += EventSink_QuestGumpRequest;
}
[Usage("MLQuestsInfo"),

View file

@ -96,7 +96,7 @@ namespace Server.Engines.PartySystem
}
}
public static void Initialize()
public static void Configure()
{
EventSink.Logout += EventSink_Logout;
EventSink.Login += EventSink_Login;

View file

@ -56,7 +56,7 @@ namespace Server
public static PathAlgorithm OverrideAlgorithm { get; set; }
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Path", AccessLevel.GameMaster, Path_OnCommand);
}

View file

@ -8,8 +8,6 @@ using Server.Items;
using Server.Json;
using Server.Mobiles;
using Server.Utilities;
using CPA = Server.CommandPropertyAttribute;
using static Server.Attributes;
namespace Server.Engines.Spawners;

View file

@ -29,7 +29,7 @@ namespace Server.Engines.Spawners
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ConvertPremiumSpawners));
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("ConvertPremiumSpawners", AccessLevel.Developer, ConvertPremiumSpawners_OnCommand);
}

View file

@ -23,7 +23,7 @@ namespace Server.Engines.Spawners
public class EditSpawnCommand : BaseCommand
{
public static void Initialize()
public static void Configure()
{
TargetCommands.Register(new EditSpawnCommand());
}

View file

@ -24,7 +24,7 @@ namespace Server.Engines.Spawners;
public class ExportSpawnersCommand : BaseCommand
{
public static void Initialize()
public static void Configure()
{
TargetCommands.Register(new ExportSpawnersCommand());
}

View file

@ -32,7 +32,7 @@ namespace Server.Engines.Spawners
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(GenerateSpawnersCommand));
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("GenerateSpawners", AccessLevel.Developer, GenerateSpawners_OnCommand);
}

View file

@ -21,7 +21,7 @@ namespace Server.Engines.Spawners
{
public class RespawnCommand : BaseCommand
{
public static void Initialize()
public static void Configure()
{
TargetCommands.Register(new RespawnCommand());
}

View file

@ -24,7 +24,7 @@ namespace Server.Engines.Spawners
{
public class SpawnPropsCommand : BaseCommand
{
public static void Initialize()
public static void Configure()
{
TargetCommands.Register(new SpawnPropsCommand());
}

View file

@ -19,7 +19,6 @@ using Server.Engines.Spawners;
using Server.Mobiles;
using Server.Network;
using Server.Text;
using CPA = Server.CommandPropertyAttribute;
namespace Server.Gumps
{

View file

@ -32,7 +32,7 @@ public class SpawnerControllerGump : GumpGrid
public static int GetButtonID(int type, int index) => 1 + type + index * TypeCount;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Spawn", AccessLevel.GameMaster, OpenSpawnGump_OnCommand);
}

View file

@ -21,6 +21,9 @@ public class StealableArtifacts : GenericPersistence
public static void Configure()
{
_stealableArtifactsPersistence = new StealableArtifacts();
CommandSystem.Register("GenStealArties", AccessLevel.Developer, GenStealArties_OnCommand);
CommandSystem.Register("RemoveStealArties", AccessLevel.Developer, RemoveStealArties_OnCommand);
}
public StealableArtifacts() : base("StealableArtifacts", 10)
@ -192,12 +195,6 @@ public class StealableArtifacts : GenericPersistence
private static int GetLampPostHue() =>
Utility.RandomDouble() < 0.9 ? 0 : Utility.RandomList(0x455, 0x47E, 0x482, 0x486, 0x48F, 0x4F2, 0x58C, 0x66C);
public static void Initialize()
{
CommandSystem.Register("GenStealArties", AccessLevel.Administrator, GenStealArties_OnCommand);
CommandSystem.Register("RemoveStealArties", AccessLevel.Administrator, RemoveStealArties_OnCommand);
}
[Usage("GenStealArties"), Description("Generates the stealable artifacts spawner.")]
private static void GenStealArties_OnCommand(CommandEventArgs args)
{

View file

@ -97,7 +97,7 @@ namespace Server.Gumps
}
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("AddDoor", AccessLevel.GameMaster, AddDoor_OnCommand);
}

View file

@ -1421,7 +1421,7 @@ namespace Server.Gumps
};
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Admin", AccessLevel.Administrator, Admin_OnCommand);
}

View file

@ -57,7 +57,7 @@ namespace Server.Gumps
}
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Comments", AccessLevel.Counselor, Comments_OnCommand);
}

View file

@ -4,8 +4,6 @@ using System.Linq;
using System.Reflection;
using Server.Commands.Generic;
using Server.Network;
using CPA = Server.CommandPropertyAttribute;
using static Server.Types;
using static Server.Attributes;
using static Server.Gumps.PropsConfig;

View file

@ -115,7 +115,7 @@ namespace Server.Gumps
}
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("ToTAdmin", AccessLevel.Administrator, ToTAdmin_OnCommand);
}

View file

@ -155,7 +155,7 @@ namespace Server.Gumps
}
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("ViewHouses", AccessLevel.GameMaster, ViewHouses_OnCommand);
}

View file

@ -41,7 +41,7 @@ namespace Server.Gumps
Initialize(page);
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Who", AccessLevel.Counselor, WhoList_OnCommand);
CommandSystem.Register("WhoList", AccessLevel.Counselor, WhoList_OnCommand);

View file

@ -163,9 +163,9 @@ namespace Server.Items
public override bool ShareHue => false;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("SHTelGen", AccessLevel.Administrator, SHTelGen_OnCommand);
CommandSystem.Register("SHTelGen", AccessLevel.Developer, SHTelGen_OnCommand);
}
[Usage("SHTelGen"), Description("Generates solen hives teleporters.")]

View file

@ -123,7 +123,7 @@ public abstract partial class BaseDoor : Item, ILockable, ITelekinesisable
Use(from);
}
public static void Initialize()
public static void Configure()
{
EventSink.OpenDoorMacroUsed += EventSink_OpenDoorMacroUsed;

View file

@ -106,9 +106,9 @@ public partial class MarkContainer : LockableContainer
[CommandProperty(AccessLevel.GameMaster)]
public DateTime NextRelock => _relockTimer.Next;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("SecretLocGen", AccessLevel.Administrator, SecretLocGen_OnCommand);
CommandSystem.Register("SecretLocGen", AccessLevel.Developer, SecretLocGen_OnCommand);
}
[Usage("SecretLocGen")]

View file

@ -5,7 +5,7 @@ namespace Server.Items;
public static class FlipCommandHandlers
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Flip", AccessLevel.GameMaster, Flip_OnCommand);
}

View file

@ -95,9 +95,9 @@ public partial class PublicMoongate : Item
return true;
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("MoonGen", AccessLevel.Administrator, MoonGen_OnCommand);
CommandSystem.Register("MoonGen", AccessLevel.Developer, MoonGen_OnCommand);
}
[Usage("MoonGen"), Description("Generates public moongates. Removes all old moongates.")]

View file

@ -26,7 +26,7 @@ public partial class WayPoint : Item
public override string DefaultName => "AI Way Point";
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("WayPointSeq", AccessLevel.GameMaster, WayPointSeq_OnCommand);
}

View file

@ -213,7 +213,7 @@ public partial class Spellbook : Item, ICraftable, ISlayer, IAosItem
return quality;
}
public static void Initialize()
public static void Configure()
{
EventSink.OpenSpellbookRequest += EventSink_OpenSpellbookRequest;
EventSink.CastSpellRequest += EventSink_CastSpellRequest;

View file

@ -14,7 +14,7 @@ public partial class StaffRobe : BaseSuit
AccessLevel.Seer => 0x1D3,
AccessLevel.Administrator => 0x488,
AccessLevel.Developer => 11,
AccessLevel.Owner => 0x496,
AccessLevel.Owner => 0x54F,
};
}

View file

@ -301,7 +301,7 @@ public partial class BaseTalisman : Item, IAosItem
[SerializableFieldSaveFlag(10)]
public bool ShouldSerializeCharges() => _charges != 0;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("RandomTalisman", AccessLevel.GameMaster, RandomTalisman_OnCommand);
}

View file

@ -54,10 +54,6 @@ namespace Server.Misc
public static void Configure()
{
Enabled = ServerConfiguration.GetOrUpdateSetting("world.enableAutoRestart", false);
}
public static void Initialize()
{
CommandSystem.Register("Restart", AccessLevel.Administrator, Restart_OnCommand);
}

View file

@ -332,9 +332,9 @@ namespace Server
private static Map m_Map;
private static int m_Count;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("DoorGen", AccessLevel.Administrator, DoorGen_OnCommand);
CommandSystem.Register("DoorGen", AccessLevel.Developer, DoorGen_OnCommand);
}
[Usage("DoorGen"), Description("Generates doors by analyzing the map. Slow.")]

View file

@ -180,7 +180,7 @@ namespace Server.Misc
return $"Unknown code {code}";
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("LanguageStatistics", AccessLevel.Administrator, LanguageStatistics_OnCommand);
}

View file

@ -30,12 +30,15 @@ namespace Server
}
}
public static void Configure()
{
EventSink.Login += OnLogin;
CommandSystem.Register("GlobalLight", AccessLevel.GameMaster, Light_OnCommand);
}
public static void Initialize()
{
new LightCycleTimer().Start();
EventSink.Login += OnLogin;
CommandSystem.Register("GlobalLight", AccessLevel.GameMaster, Light_OnCommand);
}
[Usage("GlobalLight <value>"), Description("Sets the current global light level.")]

View file

@ -100,7 +100,7 @@ namespace Server.Misc
"origin"
};
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("ValidateName", AccessLevel.Administrator, ValidateName_OnCommand);
}

View file

@ -12,13 +12,15 @@ namespace Server.Network
private static readonly int[] Delays = new int[0x100];
private const string ThrottlesConfiguration = "Configuration/throttles.json";
public static unsafe void Initialize()
public static void Configure()
{
CommandSystem.Register("GetThrottle", AccessLevel.Administrator, GetThrottle);
CommandSystem.Register("SetThrottle", AccessLevel.Administrator, SetThrottle);
}
var configPath = ThrottlesConfiguration;
var path = Path.Join(Core.BaseDirectory, configPath);
public static unsafe void Initialize()
{
var path = Path.Join(Core.BaseDirectory, ThrottlesConfiguration);
if (File.Exists(path))
{
@ -28,7 +30,7 @@ namespace Server.Network
if (!Utility.ToInt32(k, out var packetId))
{
Utility.PushColor(ConsoleColor.DarkYellow);
Console.WriteLine("Packet Throttles: Error deserializing {0} from {1}", k, configPath);
Console.WriteLine("Packet Throttles: Error deserializing {0} from {1}", k, ThrottlesConfiguration);
Utility.PopColor();
continue;
}
@ -38,8 +40,8 @@ namespace Server.Network
}
else
{
Delays[0x03] = 25; // Speech
Delays[0xAD] = 25; // Speech
Delays[0x03] = 25; // Speech
Delays[0xAD] = 25; // Speech
Delays[0x75] = 500; // Rename request
}

View file

@ -29,7 +29,7 @@ namespace Server.Items
[CommandProperty(AccessLevel.GameMaster)]
public bool PlayersCanToggle { get; set; }
public static void Initialize()
public static void Configure()
{
TargetCommands.Register(new ToggleCommand());
}

View file

@ -80,7 +80,7 @@ public class GlobalTownCrierEntryList : ITownCrierEntryList
}
}
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("TownCriers", AccessLevel.GameMaster, TownCriers_OnCommand);
}

View file

@ -6,7 +6,7 @@ namespace Server.Network
{
public class PacketLoggingCommand : BaseCommand
{
public static void Initialize()
public static void Configure()
{
TargetCommands.Register(new PacketLoggingCommand());
}

View file

@ -49,7 +49,7 @@ public class GuardedRegion : BaseRegion
public virtual bool IsDisabled() => GuardsDisabled;
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("CheckGuarded", AccessLevel.GameMaster, CheckGuarded_OnCommand);
CommandSystem.Register("SetGuarded", AccessLevel.Administrator, SetGuarded_OnCommand);

View file

@ -4,7 +4,7 @@ namespace Server.Commands
{
public static class SaveCommands
{
public static void Initialize()
public static void Configure()
{
CommandSystem.Register("Save", AccessLevel.Administrator, Save_OnCommand);
CommandSystem.Register("SetSaves", AccessLevel.Administrator, SetAutoSaves_OnCommand);

BIN
docs/commands/commands.7z Normal file

Binary file not shown.