Создание новой команды
Чтобы создать новую команду Toolshed, нужно создать новый класс, который:- Наследуется от
ToolshedCommand - Имеет имя, оканчивающееся на “Command”
- Аннотирован атрибутом
ToolshedCommandAttribute - Содержит один или несколько (не статических) методов, аннотированных атрибутом
CommandImplementationAttribute.
foo:
FooCommand отображается в foo. Альтернативно, имя команды можно указать через атрибут класса, например [ToolshedCommand(Name = "foo")]. Если имя указано явно, имя класса не обязано заканчиваться на “Command”, но это всё равно хорошее соглашение.
Автоматически генерируемые имена команд можно настроить для использования snake_case в каждом проекте. Поэтому для поддержки преобразования имён классов в CamelCase следует избегать имён классов с аббревиатурами, содержащими последовательные заглавные буквы. Например, используйте
GetNpcCommand вместо GetNPCCommand, так как последнее будет преобразовано в “get_n_p_c”.Аргументы и возвращаемые значения
Чтобы определить команду, возвращающую значение, которое можно передать в другую команду, достаточно задать методу возвращаемый тип. Чтобы добавить команде аргументы, просто добавьте параметры в метод. Например:IInvocationContext), считается обычным аргументом команды и будет пытаться быть распарсен из строки команды в Toolshed. При желании их также можно явно аннотировать атрибутом CommandArgumentAttribute.
Toolshed также поддерживает методы с опциональными аргументами и params []. Например:
Парсеры аргументов
Toolshed может парсить любой тип аргумента, для которого есть соответствующая реализацияTypeParser<T>. Например, строковые аргументы парсятся классом StringTypeParser : TypeParser<string>. Парсер отвечает за генерацию опций автодополнения и подсказок для консольной команды. Если тип ещё не поддерживается, вы всегда можете создать собственный парсер.
Если вам нужен больший контроль над тем, как парсится аргумент, или над подсказками автодополнения, вы также можете указать в атрибуте аргумента, что он должен использовать пользовательский парсер.
Аргументы с piped вводом
Чтобы создать команду, способную принимать входные значения, передаваемые от другой команды, нужно добавить аргумент, аннотированный атрибутомPipedArgumentAttribute. Например, так создаётся простая команда сложения:
Инвертируемые команды
Чтобы создать команду, поведение которой можно инвертировать префиксом “not”, нужно добавить методу аргументbool, аннотированный атрибутом CommandInvertedAttribute. Например, это простая команда для поиска конкретного числа в последовательности:
Контексты вызова
Если вы хотите создать команду, которая выводит текст в консоль или может читать и записывать переменные Toolshed, ваш метод должен принимать аргументIInvocationContext. Этот аргумент также можно опционально аннотировать атрибутом CommandInvocationContextAttribute. Например, это простая команда, выдающая однократные приветствия:
OldShellInvocationContext, где каждый игрок имеет свой контекст, сохраняющийся между отключениями и переподключениями, но не при перезапуске сервера. Контекст также не является сетевым, поэтому команды, выполняемые на стороне клиента и сервера, используют разные контексты.
Зависимости
Команды Toolshed поддерживают обычное внедрение зависимостейEntitySystem и менеджеров. Если вашей команде нужно работать с трансформами сущностей, вы можете просто добавить обычное поле зависимости в класс, например:
ToolshedCommand уже предоставляет зависимости ToolshedManager, ILocalizationManager и IEntityManager. Он также определяет несколько полезных proxy-методов IEntityManager (например, TryComp<T>, Spawn и т.д.). Таким образом, вы можете писать код, как обычно, внутри EntitySystem.
Множественные реализации и подкоманды
До сих пор все примеры определяли команду с единственным методом реализации. Команды могут иметь более одной реализации, но каждая реализация должна принимать разный piped-тип. Например, так выглядит корректная команда, принимающая как целое число, так и число с плавающей точкой:CommandImplementationAttribute. Обратите внимание: если команда содержит любые именованные реализации, то все они должны быть именованы. Например, наша предыдущая команда может быть исправлена именованием реализаций:
tp:ent и tp:map.
По соглашению, все новые команды должны использовать snake_case при именовании команд или подкоманд.
Generics
Команды Toolshed имеют некоторую поддержку C# generics, хотя есть несколько ограничений. Наиболее частый случай использования — когда нужно определить метод, принимающий произвольный piped-тип, и использовать тип входа в качестве generic-аргумента. В этом случае достаточно аннотировать generic-метод атрибутомTakesPipedTypeAsGenericAttribute. Например, так частично определяется реальная команда сложения:
TakesPipedTypeAsGeneric также поддерживает извлечение generic-типа, даже если он не напрямую соответствует типу piped-аргумента. Например, если piped-аргумент имеет тип IEnumerable<T>, он всё равно может извлечь generic-тип T из piped-значения. Например, так работает команда append:
Foo<T>([PipedArgument] Dictionary<int, List<(T, string)>> input) скорее всего не сможет извлечь T из переданного piped-значения.
Также отсутствует поддержка автоматического определения нескольких generic-аргументов из piped-входа. Если вам нужны команды, использующие более сложные generics, обычно потребуется определить команду с явными аргументами типа.
Аргументы типа
Если вам нужно создать команду, использующую несколько generic-аргументов или имеющую generics, которые не могут быть автоматически выведены из piped-входа, необходимо использовать явные аргументы типа. При написании shell-команды аргументы типа выглядят как обычные аргументы, но всегда предшествуют любым другим аргументам и используются для определения типов для generic-реализации. Чтобы ваша команда требовала аргументы типа, нужно переопределить свойствоTypeParameterParsers команды. Оно должно возвращать массив типов, наследующих от TypeParser<Type>, которые будут использоваться для парсинга аргументов типа из строки команды. Поскольку это свойство уровня класса, это означает, что все реализации или подкоманды должны требовать одинаковое количество аргументов типа. Вы также можете комбинировать явные аргументы типа с атрибутом TakesPipedTypeAsGenericAttribute. Обратите внимание, что автоматически выведенный аргумент типа всегда должен быть последним аргументом типа этой функции.
Например, эти две команды используют явные аргументы типа для вывода синтаксиса C#-подобного вызова метода:
Автоматические преобразования типов
Как упоминалось в других разделах документации, Toolshed выполняет некоторые автоматические преобразования типов. Наиболее примечательно: любая команда, ожидающаяIEnumerable<T>, также примет одиночное значение T, так как Toolshed автоматически преобразует его в IEnumerable<T> с одним элементом.
Toolshed также автоматически приводит любой тип, реализующий интерфейс IAsType<T>. Например, Entity<T> реализует IAsType<EntityUid>. Таким образом, Toolshed позволит передать вывод Entity<T> методу, ожидающему EntityUid.
Пользовательские парсеры типов
Если вы хотите создать метод, использующий пользовательский парсер, вы можете указать его через атрибутCommandArgumentAttribute для аргумента. Это полезно, если вам нужен больший контроль над парсингом или опциями/подсказками автодополнения в консоли.
Например, следующий код определяет метод, использующий пользовательский парсер для получения целого числа из двоичной строки. Хотя в данном конкретном случае можно было бы просто сделать аргумент строкой и выполнить преобразование внутри самого метода, но тогда аргумент нужно было бы заключать в кавычки (все строковые аргументы должны быть в кавычках).
Разрешения
Все команды Toolshed должны указывать определённые разрешения для возможности выполнения, и существует интеграционный тест, проверяющий это (AdminTest.AllCommandsHavePermissions). Разрешения для команд, определённых в движке, указаны в /Resources/toolshedEngineCommandPerms.yml, тогда как команды Content могут получать разрешения путём аннотирования класса команды обычными атрибутами (AnyCommandAttribute, AdminCommandAttribute). Разрешения нельзя задавать для отдельных подкоманд; все подкоманды должны иметь одинаковые разрешения.
Автодополнение, подсказки и локализация
Каждая команда Toolshed должна иметь локализованное описание. Ключ для локализованной строки основан на имени (под)команды. Например,foo или foo:bar использует ключ “command-description-foo” или “command-description-foo-bar”. Если имя команды содержит не ASCII-символы, вместо него используется имя класса. Например, команда сложения (+) определена в классе AddCommand, поэтому используется ключ “command-description-AddCommand”.
Toolshed автоматически генерирует строки помощи для команд в виде сигнатуры метода. Автоматически сгенерированную строку помощи можно переопределить, определив локализованную строку. Например, подсказку для команды foo можно переопределить строкой с ключом “command-help-foo”.
Подсказки аргументов
Большинство парсеров аргументов Toolshed автоматически генерируют подсказки автодополнения в консоли при вводе аргументов команды. Например, при вводе аргумента для методаFoo(int myNumber) будет сгенерирована подсказка [myNumber (int)]. Чтобы переопределить автоматически сгенерированную подсказку, можно определить локализованную строку с ключом “command-arg-hint-foo-myNumber”. Если нужен больший контроль над подсказкой или предложениями автодополнения, используйте пользовательский парсер.