LiteLoaderBDS-1.16.40/LiteLoader/Kernel/Command/DynamicCommandAPI.cpp
2022-11-10 16:54:20 +08:00

1287 lines
57 KiB
C++

//#define COMMAND_REGISTRY_EXTRA
#include <DynamicCommandAPI.h>
#include <I18nAPI.h>
#include <LLAPI.h>
#include <LoggerAPI.h>
#include <MC/AvailableCommandsPacket.hpp>
#include <MC/CommandMessage.hpp>
#include <MC/CommandOutput.hpp>
#include <MC/CommandRegistry.hpp>
#include <MC/ItemInstance.hpp>
#include <MC/MobEffect.hpp>
#include <MC/ActorDefinitionIdentifier.hpp>
#include <MC/Level.hpp>
#include <MC/Block.hpp>
#include <Utils/SRWLock.h>
#include <ScheduleAPI.h>
#include <MC/Minecraft.hpp>
#include <MC/LoopbackPacketSender.hpp>
#include <dyncall/dyncall_callback.h>
#include <MC/CommandUtils.hpp>
#include <MC/CommandSoftEnumRegistry.hpp>
extern Logger logger;
#define ForEachParameterType(func) \
func(Bool); \
func(Int); \
func(Float); \
func(String); \
func(Actor); \
func(Player); \
func(BlockPos); \
func(Vec3); \
func(RawText); \
func(Message); \
func(JsonValue); \
func(Item); \
func(Block); \
func(Effect); \
func(Enum); \
func(SoftEnum); \
func(ActorType); \
func(Command); \
func(WildcardSelector);
#define CatchDynamicCommandError(func, handle) \
catch (const seh_exception& e) { \
OutputError("Uncaught SEH Exception Detected!", e.code(), TextEncoding::toUTF8(e.what()), func, handle); \
} \
catch (const std::exception& e) { \
OutputError("Uncaught C++ Exception Detected!", errno, TextEncoding::toUTF8(e.what()), func, handle); \
} \
catch (...) { \
OutputError("Uncaught Exception Detected!", -1, "", func, handle); \
}
// global variable and function
namespace {
bool serverCommandsRegistered = false;
std::unordered_map<std::string, std::unique_ptr<DynamicCommandInstance>> dynamicCommandInstances;
std::vector<std::unique_ptr<DynamicCommandInstance>> delaySetupCommandInstances;
SRWLock delaySetupLock;
using Result = DynamicCommand::Result;
using ParameterType = DynamicCommand::ParameterType;
using ParameterPtr = DynamicCommand::ParameterPtr;
using ParameterData = DynamicCommand::ParameterData;
using ParameterIndex = DynamicCommandInstance::ParameterIndex;
namespace ParameterDataType {
typedef bool Bool;
typedef int Int;
typedef float Float;
typedef std::string String;
typedef WildcardCommandSelector<Actor> WildcardSelector;
typedef CommandSelector<Actor> Actor;
typedef CommandSelector<Player> Player;
typedef CommandPosition BlockPos;
typedef CommandPositionFloat Vec3;
typedef CommandRawText RawText;
typedef CommandMessage Message;
typedef Json::Value JsonValue;
typedef CommandItem Item;
typedef Block const* Block;
typedef MobEffect const* Effect;
// typedef CommandPosition Position;
#ifdef USE_PARSE_ENUM_STRING
typedef std::pair<std::string, int> Enum;
#else
typedef int Enum;
#endif // USE_PARSE_ENUM_STRING
typedef std::string SoftEnum;
typedef ActorDefinitionIdentifier const* ActorType;
typedef std::unique_ptr<Command> Command;
#ifdef ENABLE_PARAMETER_TYPE_POSTFIX
typedef int Postfix;
#endif // ENABLE_PARAMETER_TYPE_POSTFIX
} // namespace ParameterDataType
auto const ParameterSizeMap = std::unordered_map<ParameterType, size_t>{
{ParameterType::Bool, std::max((size_t)8, sizeof(ParameterDataType::Bool))},
{ParameterType::Int, std::max((size_t)8, sizeof(ParameterDataType::Int))},
{ParameterType::Float, std::max((size_t)8, sizeof(ParameterDataType::Float))},
{ParameterType::Actor, std::max((size_t)8, sizeof(ParameterDataType::Actor))},
{ParameterType::Player, std::max((size_t)8, sizeof(ParameterDataType::Player))},
{ParameterType::String, std::max((size_t)8, sizeof(ParameterDataType::String))},
{ParameterType::BlockPos, std::max((size_t)8, sizeof(ParameterDataType::BlockPos))},
{ParameterType::Vec3, std::max((size_t)8, sizeof(ParameterDataType::Vec3))},
{ParameterType::RawText, std::max((size_t)8, sizeof(ParameterDataType::RawText))},
{ParameterType::Message, std::max((size_t)8, sizeof(ParameterDataType::Message))},
{ParameterType::JsonValue, std::max((size_t)8, sizeof(ParameterDataType::JsonValue))},
{ParameterType::Item, std::max((size_t)8, sizeof(ParameterDataType::Item))},
{ParameterType::Block, std::max((size_t)8, sizeof(ParameterDataType::Block))},
{ParameterType::Effect, std::max((size_t)8, sizeof(ParameterDataType::Effect))},
{ParameterType::Enum, std::max((size_t)8, sizeof(ParameterDataType::Enum))},
{ParameterType::SoftEnum, std::max((size_t)8, sizeof(ParameterDataType::SoftEnum))},
{ParameterType::ActorType, std::max((size_t)8, sizeof(ParameterDataType::ActorType))},
{ParameterType::Command, std::max((size_t)8, sizeof(ParameterDataType::Command))},
#ifdef ENABLE_PARAMETER_TYPE_POSTFIX
{ParameterType::Postfix, std::max((size_t)8, sizeof(ParameterDataType::Postfix))},
#endif // ENABLE_PARAMETER_TYPE_POSTFIX
};
inline void OutputError(std::string errorMsg, int errorCode, std::string errorWhat, std::string func, HMODULE handle) {
logger.error(errorMsg);
logger.error("Error: Code [{}] {}", errorCode, errorWhat);
logger.error("In Function ({})", func);
if (auto plugin = LL::getPlugin(handle))
logger.error("In Plugin <{}>", plugin->name);
}
} // namespace
#pragma region Command init and destroy
template <typename T>
inline void destruct(void* command, size_t offset) {
dAccess<T>(command, offset).~T();
}
template <typename T>
inline void initValue(void* command, size_t offset) {
dAccess<T>(command, offset) = T();
}
template <>
inline void initValue<std::string>(void* command, size_t offset) {
dAccess<std::string>(command, offset).basic_string::basic_string();
}
template <>
inline void initValue<CommandItem>(void* command, size_t offset) {
dAccess<CommandItem>(command, offset).CommandItem::CommandItem();
}
template <>
inline void initValue<CommandMessage>(void* command, size_t offset) {
dAccess<CommandMessage>(command, offset).CommandMessage::CommandMessage();
}
template <>
inline void initValue<CommandSelector<Actor>>(void* command, size_t offset) {
dAccess<CommandSelector<Actor>>(command, offset).CommandSelector<Actor>::CommandSelector();
}
template <>
inline void initValue<CommandSelector<Player>>(void* command, size_t offset) {
dAccess<CommandSelector<Player>>(command, offset).CommandSelector<Player>::CommandSelector();
}
template <>
inline void initValue<WildcardCommandSelector<Actor>>(void* command, size_t offset) {
dAccess<WildcardCommandSelector<Actor>>(command, offset).WildcardCommandSelector<Actor>::WildcardCommandSelector();
}
#pragma endregion
#pragma region ParameterPtr
inline DynamicCommand::ParameterPtr::ParameterPtr(ParameterType type, size_t offset)
: type(type)
, offset(offset) {
}
inline bool DynamicCommand::ParameterPtr::isValueSet(DynamicCommand const* command) const {
return dAccess<bool>(command, offset + ParameterSizeMap.at(type));
}
Result ParameterPtr::getResult(DynamicCommand const* command, CommandOrigin const* origin) const {
// auto commandInstance = dynamicCommandInstances.at(command->getCommandName()).get();
return {this, command, origin};
}
#pragma endregion
#pragma region ParameterData
DynamicCommand::ParameterData::ParameterData(ParameterData const& right)
: ParameterData(right.name, right.type, right.optional, right.description, right.identifier, right.option) {
offset = right.offset;
};
inline DynamicCommand::ParameterData::ParameterData(std::string const& name, ParameterType type, bool optional, std::string const& enumOptions, std::string const& identifier, CommandParameterOption parameterOption)
: name(name)
, type(type)
, optional(optional)
, description(enumOptions)
, option(parameterOption) {
if (identifier.empty())
this->identifier = description.empty() ? name : description;
else
this->identifier = identifier;
if (type != DynamicCommand::ParameterType::Enum && type != DynamicCommand::ParameterType::SoftEnum) {
if (!description.empty())
description = "";
} else {
if (description.empty())
throw std::runtime_error("Enum or SoftEnum parameter need a description to confirm which enum to use");
}
}
inline DynamicCommand::ParameterData::ParameterData(std::string const& name, DynamicCommand::ParameterType type, std::string const& enumOptions, std::string const& identifier, CommandParameterOption parameterOption)
: ParameterData(name, type, false, enumOptions, identifier, parameterOption) {
}
inline CommandParameterData DynamicCommand::ParameterData::makeParameterData() const {
switch (type) {
case ParameterType::Bool:
return makeParameterData<ParameterType::Bool, ParameterDataType::Bool>();
case ParameterType::Int:
return makeParameterData<ParameterType::Int, ParameterDataType::Int>();
case ParameterType::Float:
return makeParameterData<ParameterType::Float, ParameterDataType::Float>();
case ParameterType::String:
return makeParameterData<ParameterType::String, ParameterDataType::String>();
case ParameterType::Actor:
return makeParameterData<ParameterType::Actor, ParameterDataType::Actor>();
case ParameterType::Player:
return makeParameterData<ParameterType::Player, ParameterDataType::Player>();
case ParameterType::BlockPos:
return makeParameterData<ParameterType::BlockPos, ParameterDataType::BlockPos>();
case ParameterType::Vec3:
return makeParameterData<ParameterType::Vec3, ParameterDataType::Vec3>();
case ParameterType::RawText:
return makeParameterData<ParameterType::RawText, ParameterDataType::RawText>();
case ParameterType::Message:
return makeParameterData<ParameterType::Message, ParameterDataType::Message>();
case ParameterType::JsonValue:
return makeParameterData<ParameterType::JsonValue, ParameterDataType::JsonValue>();
case ParameterType::Item:
return makeParameterData<ParameterType::Item, ParameterDataType::Item>();
case ParameterType::Block:
return makeParameterData<ParameterType::Block, ParameterDataType::Block>();
case ParameterType::Effect:
return makeParameterData<ParameterType::Effect, ParameterDataType::Effect>();
// case ParameterType::Position:
// return makeParameterData<ParameterType::Position, ParameterDataType::Position>();
case ParameterType::Enum:
return makeParameterData<ParameterType::Enum, ParameterDataType::Enum>();
case ParameterType::SoftEnum:
return makeParameterData<ParameterType::SoftEnum, ParameterDataType::SoftEnum>();
case ParameterType::ActorType:
return makeParameterData<ParameterType::ActorType, ParameterDataType::ActorType>();
case ParameterType::Command:
return makeParameterData<ParameterType::Command, ParameterDataType::Command>();
#ifdef ENABLE_PARAMETER_TYPE_POSTFIX
case ParameterType::Postfix:
return makeParameterData<ParameterType::Postfix, ParameterDataType::Postfix>();
#endif // ENABLE_PARAMETER_TYPE_POSTFIX
default:
return {};
}
}
#pragma endregion
#pragma region Result
inline DynamicCommand::Result::Result(ParameterPtr const* ptr, DynamicCommand const* command, CommandOrigin const* origin, DynamicCommandInstance const* instance)
: type(ptr->type)
, offset(ptr->offset)
, command(command)
, origin(origin)
, instance(instance ? instance : command->getInstance())
, isSet(ptr->isValueSet(command)) {
}
inline DynamicCommand::Result::Result()
: type((ParameterType)-1)
, offset(-1)
, command(nullptr)
, origin(nullptr)
, instance(nullptr)
, isSet(false) {
}
inline std::string const& DynamicCommand::Result::getEnumValue() const {
if (getType() == ParameterType::Enum) {
return getRaw<std::string>();
} else if (getType() == ParameterType::SoftEnum) {
return getRaw<std::string>();
}
static std::string const EMPTY_STRING = "";
return EMPTY_STRING;
}
inline ParameterType DynamicCommand::Result::getType() const {
return type;
}
inline std::string DynamicCommand::Result::getName() const {
for (auto& [name, ptr] : instance->parameterPtrs) {
if (ptr.getOffset() == offset)
return name;
}
return "";
}
std::string DynamicCommand::Result::toDebugString() const {
std::string name = getName();
ParameterType type = getType();
std::string typeName = fmt::format("{}({})", magic_enum::enum_name(type), (int)type);
switch (type) {
case ParameterType::Bool:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<bool>());
case ParameterType::Int:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<int>());
case ParameterType::Float:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<float>());
case ParameterType::Actor:
// return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<CommandSelector<Actor>>().getName());
case ParameterType::Player: {
std::vector<Actor*> actors = get<std::vector<Actor*>>();
std::ostringstream oss;
oss << "count: " << actors.size() << ", actors: [";
bool first = true;
for (auto& actor : actors) {
if (!first)
oss << ", ";
oss << CommandUtils::getActorName(*actor);
}
oss << "]";
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, oss.str());
}
case ParameterType::String:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<std::string>());
case ParameterType::BlockPos:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<CommandPosition>().serialize().toSNBT());
case ParameterType::Vec3:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<CommandPositionFloat>().serialize().toSNBT());
case ParameterType::RawText:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<CommandRawText>().getText());
case ParameterType::Message:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<CommandMessage>().getMessage(*origin));
case ParameterType::JsonValue:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<Json::Value>().toStyledString().substr(0, getRaw<Json::Value>().toStyledString().size() - 1));
case ParameterType::Item:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<CommandItem>().createInstance(1, 1, nullptr, true).value_or(ItemInstance::EMPTY_ITEM).toString());
case ParameterType::Block:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, isSet ? getRaw<Block const*>()->toDebugString() : "nullptr");
case ParameterType::Effect:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, isSet ? getRaw<MobEffect const*>()->getResourceName() : "nullptr");
case ParameterType::Enum:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, fmt::format("{}({})", getRaw<std::string>(), getRaw<int>()));
case ParameterType::SoftEnum:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, getRaw<std::string>());
case ParameterType::ActorType:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, isSet ? getRaw<ActorDefinitionIdentifier const*>()->getCanonicalName() : "Null");
case ParameterType::Command:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, isSet ? getRaw<std::unique_ptr<Command>>()->getCommandName() : "Null");
#ifdef ENABLE_PARAMETER_TYPE_POSTFIX
case ParameterType::Postfix:
return fmt::format("name: {:15s}, type: {:15s}, isSet: {:5}, value: {}", name, typeName, isSet, get<std::string>());
#endif // ENABLE_PARAMETER_TYPE_POSTFIX
default:
logger.error("Unknown Parameter Type {}, name: {}", typeName, name);
return "";
}
}
inline DynamicCommandInstance const* DynamicCommand::Result::getInstance() const {
std::string commandName = command->getCommandName();
auto iter = dynamicCommandInstances.find(commandName);
if (iter == dynamicCommandInstances.end())
return nullptr;
return iter->second.get();
}
#pragma endregion
#pragma region DynamicCommand
// std::unique_ptr<Command> DynamicCommand::commandBuilder()
//{
// std::unique_ptr<Command> command;
// commandBuilder2(&command, latestAllocateName);
// return std::move(command);
// }
inline char DynamicCommand::builderCallbackHanler(DCCallback* cb, DCArgs* args, DCValue* result, void* userdata) {
DynamicCommandInstance& command = *(DynamicCommandInstance*)userdata;
auto arg1 = (std::unique_ptr<Command>*)dcbArgPointer(args);
arg1->release();
result->p = DynamicCommand::commandBuilder(arg1, command.getCommandName());
return 'p';
}
std::unique_ptr<Command>* DynamicCommand::commandBuilder(std::unique_ptr<Command>* rtn, std::string name) {
#define CaseInitBreak(type) \
case ParameterType::type: \
initValue<ParameterDataType::type>(command, offset); \
break;
assert(dynamicCommandInstances.count(name) == 1);
if (dynamicCommandInstances.count(name) == 0) {
logger.error("Error in allocate dynamic command");
return rtn;
}
auto& commandInstance = *dynamicCommandInstances.at(name);
auto command = new char[commandInstance.commandSize]{0};
(*(DynamicCommand*)command).DynamicCommand::DynamicCommand();
for (auto& [name, param] : commandInstance.parameterPtrs) {
size_t offset = param.getOffset();
dAccess<bool>(command, offset + ParameterSizeMap.at(param.type)) = false; // XXXX_isSet;
switch (param.type) {
ForEachParameterType(CaseInitBreak);
default:
break;
}
}
rtn->reset(std::move((Command*)command));
return rtn;
}
DynamicCommandInstance* DynamicCommand::_setup(std::unique_ptr<class DynamicCommandInstance> commandInstance) {
std::string name = commandInstance->getCommandName();
#ifdef DEBUG
logger.info("Setting up command \"{}\"", name);
#endif // DEBUG
// Check if there is another command with the same name
auto signature = Global<CommandRegistry>->findCommand(name);
if (signature) {
throw std::runtime_error("There is already a command named " + signature->name);
}
auto handle = commandInstance->handle;
try {
if (!commandInstance)
throw std::runtime_error("Command instance is null");
if (!commandInstance->callback)
throw std::runtime_error("Can't setup command without callback");
if (commandInstance->overloads.empty())
throw std::runtime_error("Can't setup command without overloads");
// commandInstance->updateSoftEnum();
for (auto& param : commandInstance->parameterDatas) {
if (param.type == ParameterType::Enum) {
// clone BDS's enum
if (commandInstance->enumRanges.count(param.description) == 0) {
auto namesInBds = CommandRegistry::getEnumNames();
auto iter = std::find(namesInBds.begin(), namesInBds.end(), param.description);
if (iter == namesInBds.end())
throw std::runtime_error("Enum " + std::string(param.description) + "not found in command and BDS");
#ifndef USE_PARSE_ENUM_STRING_ // fix Enum
commandInstance->setEnum(*iter, CommandRegistry::getEnumValues(*iter));
#endif // USE_PARSE_ENUM_STRING
}
} else if (param.type == ParameterType::SoftEnum) {
// add empty Soft Enum if not found in command and BDS
if (commandInstance->softEnums.count(param.description) == 0) {
auto namesInBds = CommandRegistry::getSoftEnumNames();
auto iter = std::find(namesInBds.begin(), namesInBds.end(), param.description);
if (iter == namesInBds.end())
commandInstance->setSoftEnum(param.description, {});
}
}
}
// fix enum name with prefix '_...' if Enum name is exists in BDS
auto namesInBds = CommandRegistry::getEnumNames();
std::unordered_map<std::string_view, std::pair<size_t, size_t>> convertedEnumRanges;
for (auto& [name, range] : commandInstance->enumRanges) {
std::string fixedName = name.data();
while (std::find(namesInBds.begin(), namesInBds.end(), fixedName) != namesInBds.end()) {
fixedName.append("_");
}
std::string_view fixedView = name;
if (fixedName != name) {
for (auto& namePtr : commandInstance->enumNames) {
if (*namePtr == name) {
namePtr->swap(fixedName);
fixedView = *namePtr;
for (auto& data : commandInstance->parameterDatas) {
if (data.description == fixedName) {
data.description = *namePtr;
}
}
break;
}
}
}
std::vector<std::pair<std::string, uint64_t>> values;
size_t index = range.first;
for (auto iter = commandInstance->enumValues.begin() + range.first; iter != commandInstance->enumValues.begin() + range.first + range.second; ++iter) {
values.emplace_back(*iter, index);
++index;
}
#ifdef USE_PARSE_ENUM_STRING
Global<CommandRegistry>->addEnumValuesInternal(fixedView.data(), values, typeid_t<CommandRegistry>::count++, &CommandRegistry::parseEnumStringAndInt).val;
#else
Global<CommandRegistry>->addEnumValuesInternal(fixedView.data(), values, typeid_t<CommandRegistry>::count++, &CommandRegistry::parseEnum<int>).val;
#endif // USE_PARSE_ENUM_STRING
}
commandInstance->enumRanges.swap(convertedEnumRanges);
// add Soft Enum to BDS
for (auto& [name, values] : commandInstance->softEnums) {
Global<CommandRegistry>->addSoftEnum(name, values);
}
Global<CommandRegistry>->registerCommand(commandInstance->name, commandInstance->description->c_str(), commandInstance->permission, commandInstance->flag, commandInstance->flag);
if (!commandInstance->alias.empty())
Global<CommandRegistry>->registerAlias(commandInstance->name, commandInstance->alias);
auto builder = commandInstance->initCommandBuilder();
for (auto& overload : commandInstance->overloads) {
Global<CommandRegistry>->registerOverload(commandInstance->name, builder, commandInstance->buildOverload(overload));
}
// commandInstance->overloads.clear();
auto res = dynamicCommandInstances.emplace(commandInstance->name, std::move(commandInstance));
return res.first->second.get();
}
CatchDynamicCommandError("DynamicCommand::_setup - " + name, handle);
return nullptr;
}
bool DynamicCommand::onServerCommandsRegister(CommandRegistry& registry) {
serverCommandsRegistered = true;
SRWLockHolder locker(delaySetupLock);
for (auto& command : delaySetupCommandInstances) {
std::string name = command->getCommandName();
auto handle = command->handle;
try {
if (!LL::getPlugin(handle) && handle != GetCurrentModule())
throw std::runtime_error("Plugin that registered command \"" + name + "\" not found");
auto res = DynamicCommand::_setup(std::move(command));
if (!res)
throw std::runtime_error("Command \"" + name + "\" setup failed");
}
CatchDynamicCommandError("DynamicCommand::_setup - " + name, handle);
};
delaySetupCommandInstances.clear();
return true;
}
DynamicCommand::~DynamicCommand() {
#define CaseDestructBreak(type) \
case ParameterType::type: \
destruct<ParameterDataType::type>(this, offset); \
break;
std::string commandName = getCommandName();
auto iter = dynamicCommandInstances.find(commandName);
if (iter == dynamicCommandInstances.end()) {
logger.error("Error in DynamicCommand::~DynamicCommand(), command \"{}\" not found", commandName);
return;
}
auto& commandIns = *iter->second;
for (auto& [name, parameter] : commandIns.parameterPtrs) {
auto offset = parameter.getOffset();
switch (parameter.type) {
ForEachParameterType(CaseDestructBreak);
default:
break;
}
}
}
void DynamicCommand::execute(CommandOrigin const& origin, CommandOutput& output) const {
auto iter = dynamicCommandInstances.find(getCommandName());
if (iter == dynamicCommandInstances.end()) {
return output.error("Dynamic Command Not Found");
}
auto& commandIns = *iter->second;
if (!commandIns.callback) {
return output.error(fmt::format("Command {} has been removed.", getCommandName()));
}
try {
std::unordered_map<std::string, Result> results;
for (auto& [name, param] : commandIns.parameterPtrs) {
results.emplace(name, param.getResult(this, &origin));
}
commandIns.callback(*this, origin, output, results);
}
CatchDynamicCommandError("DynamicCommand::execute", commandIns.handle);
}
std::unique_ptr<class DynamicCommandInstance> DynamicCommand::createCommand(std::string const& name, std::string const& description, CommandPermissionLevel permission, CommandFlag flag1, CommandFlag flag2, HMODULE handle) {
return DynamicCommandInstance::create(name, description, permission, flag1 |= flag2, handle);
}
#include <LLAPI.h>
#include <EventAPI.h>
#include <Main/Config.h>
DynamicCommandInstance const* DynamicCommand::setup(std::unique_ptr<class DynamicCommandInstance> commandInstance) {
auto ptr = commandInstance.get();
if (!ptr)
throw std::runtime_error("DynamicCommand::setup - commandInstance is null");
if (!serverCommandsRegistered) {
delaySetupLock.lock();
auto& uptr = delaySetupCommandInstances.emplace_back(std::move(commandInstance));
delaySetupLock.unlock();
return uptr.get();
}
// logger.warn("Registering command \"{}\" after RegCmdEvent, note that this is unstable!", commandInstance->getCommandName());
Schedule::nextTick([instance{commandInstance.release()}]() {
if (!_setup(std::unique_ptr<class DynamicCommandInstance>(instance)))
logger.warn("Registering command \"{}\" failed", instance->getCommandName());
updateAvailableCommands();
});
return ptr;
}
std::unique_ptr<class DynamicCommandInstance> DynamicCommand::createCommand(std::string const& name, std::string const& description, std::unordered_map<std::string, std::vector<std::string>>&& enums, std::vector<ParameterData>&& params, std::vector<std::vector<std::string>>&& overloads, CallBackFn callback, CommandPermissionLevel permission, CommandFlag flag1, CommandFlag flag2, HMODULE handle) {
auto command = createCommand(name, description, permission, flag1, flag2, handle);
if (!command)
return std::unique_ptr<class DynamicCommandInstance>();
for (auto& [name, values] : enums) {
command->setEnum(name, std::move(values));
}
for (auto& param : params) {
command->newParameter(std::move(param));
}
for (auto& overload : overloads) {
command->addOverload(std::move(overload));
}
command->setCallback(std::move(callback));
return std::move(command);
}
bool DynamicCommand::unregisterCommand(std::string const& name) {
#ifdef DEBUG
Schedule::nextTick([tid = std::this_thread::get_id()]() {
// Call DynamicCommand::unregisterCommand in other thread is not allowed!
assert(tid == std::this_thread::get_id());
});
#endif // DEBUG
if (Global<CommandRegistry>->unregisterCommand(name)) {
dynamicCommandInstances.erase(name);
updateAvailableCommands();
return true;
}
return false;
}
inline bool DynamicCommand::updateAvailableCommands() {
#ifdef DEBUG
Schedule::nextTick([tid = std::this_thread::get_id()]() {
// Call DynamicCommand::updateAvailableCommands in other thread is not allowed!
assert(tid == std::this_thread::get_id());
});
#endif // DEBUG
if (!Global<CommandRegistry> || !Global<Level>)
return false;
auto packet = Global<CommandRegistry>->serializeAvailableCommands();
auto sender = (LoopbackPacketSender*)Global<Level>->getPacketSender();
if (!sender)
return false;
sender->sendBroadcast(packet);
return true;
}
inline DynamicCommandInstance const* DynamicCommand::getInstance() const {
return getInstance(getCommandName());
}
DynamicCommandInstance const* DynamicCommand::getInstance(std::string const& commandName) {
auto iter = dynamicCommandInstances.find(commandName);
if (iter == dynamicCommandInstances.end())
return nullptr;
else
return iter->second.get();
}
#pragma endregion
#pragma region DynamicCommandInstance
inline DynamicCommandInstance::DynamicCommandInstance(std::string const& name, std::string const& description, CommandPermissionLevel permission, CommandFlag flag, HMODULE handle)
: name(name)
, description(std::make_unique<std::string>(description))
, permission(permission)
, flag(flag)
, handle(handle){};
inline DynamicCommandInstance::~DynamicCommandInstance() {
if (this->builder)
dcbFreeCallback((DCCallback*)this->builder);
this->builder = nullptr;
}
inline std::unique_ptr<DynamicCommandInstance> DynamicCommandInstance::create(std::string const& name, std::string const& description, CommandPermissionLevel permission, CommandFlag flag, HMODULE handle) {
if (LL::globalConfig.serverStatus != LL::LLServerStatus::Running) {
for (auto& cmd : delaySetupCommandInstances) {
if (cmd->name == name) {
logger.error("Command \"{}\" already exists", name);
return {};
}
}
} else if (Global<CommandRegistry>->findCommand(name)) {
logger.error("Command \"{}\" already exists", name);
return {};
}
return std::unique_ptr<DynamicCommandInstance>(new DynamicCommandInstance(name, description, permission, flag, handle));
}
inline bool DynamicCommandInstance::addOverload(std::vector<DynamicCommand::ParameterData>&& params) {
std::vector<ParameterIndex> indices;
for (auto& param : params) {
indices.push_back(newParameter(std::forward<ParameterData>(param)));
}
return addOverload(std::move(indices));
}
inline std::string const& DynamicCommandInstance::setEnum(std::string const& description, std::vector<std::string> const& values) {
auto& desc = enumNames.emplace_back(std::make_unique<std::string>(description));
enumRanges.emplace(*desc, std::pair{enumValues.size(), values.size()});
enumValues.insert(enumValues.end(), values.begin(), values.end());
return *desc;
}
inline std::string const& DynamicCommandInstance::getEnumValue(int index) const {
if (index < 0 || index >= enumValues.size())
throw std::runtime_error("Enum index out of range");
return enumValues.at(index);
}
ParameterIndex DynamicCommandInstance::newParameter(DynamicCommand::ParameterData&& data) {
auto iter = parameterPtrs.find(data.name);
size_t offset = -1;
if (iter == parameterPtrs.end()) {
offset = commandSize;
parameterPtrs.emplace(data.name, DynamicCommand::ParameterPtr(data.type, offset));
commandSize += ParameterSizeMap.at(data.type) + 8;
} else {
offset = iter->second.getOffset();
if (iter->second.type != data.type)
throw std::runtime_error(fmt::format("dynamic command \"{}\" register failed, Different type parameters with the same name {} are not allowed", name, data.name));
}
std::string const& identifier = data.identifier;
if (parameterDatas.end() != std::find_if(parameterDatas.begin(), parameterDatas.end(),
[&](DynamicCommand::ParameterData const& data) { return data.identifier == identifier; }))
throw std::runtime_error("parameter identifier already exists");
data.offset = offset;
parameterDatas.emplace_back(std::move(data));
return {this, parameterDatas.size() - 1};
}
inline ParameterIndex DynamicCommandInstance::newParameter(std::string const& name, DynamicCommand::ParameterType type, bool optional, std::string const& description, std::string const& identifier, CommandParameterOption parameterOption) {
return newParameter(ParameterData(name, type, optional, description, identifier, parameterOption));
}
inline ParameterIndex DynamicCommandInstance::findParameterIndex(std::string const& indentifier) {
size_t index = 0;
for (auto& paramData : parameterDatas) {
if (paramData.identifier == indentifier || paramData.description == indentifier || paramData.name == indentifier)
break;
++index;
}
if (index == parameterDatas.size())
index = -1;
return {this, index};
}
inline ParameterIndex DynamicCommandInstance::mandatory(std::string const& name, DynamicCommand::ParameterType type, std::string const& description, std::string const& identifier, CommandParameterOption parameterOption) {
return newParameter(ParameterData(name, type, false, description, identifier, parameterOption));
}
inline ParameterIndex DynamicCommandInstance::mandatory(std::string const& name, DynamicCommand::ParameterType type, std::string const& description, CommandParameterOption parameterOption) {
return mandatory(name, type, description, "", parameterOption);
};
inline ParameterIndex DynamicCommandInstance::mandatory(std::string const& name, DynamicCommand::ParameterType type, CommandParameterOption parameterOption) {
return mandatory(name, type, "", "", parameterOption);
};
inline ParameterIndex DynamicCommandInstance::optional(std::string const& name, DynamicCommand::ParameterType type, std::string const& description, std::string const& identifier, CommandParameterOption parameterOption) {
return newParameter(ParameterData(name, type, true, description, identifier, parameterOption));
}
inline ParameterIndex DynamicCommandInstance::optional(std::string const& name, DynamicCommand::ParameterType type, std::string const& description, CommandParameterOption parameterOption) {
return optional(name, type, description, "", parameterOption);
}
inline ParameterIndex DynamicCommandInstance::optional(std::string const& name, DynamicCommand::ParameterType type, CommandParameterOption parameterOption) {
return optional(name, type, "", "", parameterOption);
}
bool DynamicCommandInstance::addOverload(std::vector<ParameterIndex>&& params) {
for (auto& index : params) {
if (index >= parameterDatas.size())
throw std::runtime_error("parameter index " + std::to_string(index) + " out of range 0 ~ " + std::to_string(parameterDatas.size()));
}
overloads.emplace_back(params);
return true;
}
inline bool DynamicCommandInstance::addOverload(std::vector<char const*>&& params) {
std::vector<ParameterIndex> paramIndices;
for (auto& param : params) {
auto index = findParameterIndex(param);
if (!index.isValid()) {
throw std::runtime_error("Parameter " + std::string(param) + "not found");
}
paramIndices.push_back(index);
}
return addOverload(std::move(paramIndices));
}
inline bool DynamicCommandInstance::addOverload(std::vector<std::string>&& params) {
std::vector<ParameterIndex> paramIndices;
for (auto& param : params) {
paramIndices.push_back(findParameterIndex(param));
}
return addOverload(std::move(paramIndices));
}
inline bool DynamicCommandInstance::setAlias(std::string const& alias) {
this->alias = alias;
return true;
}
inline std::vector<CommandParameterData> DynamicCommandInstance::buildOverload(std::vector<ParameterIndex> const& overload) {
std::vector<CommandParameterData> datas;
for (auto& index : overload) {
auto& param = parameterDatas.at(index);
datas.emplace_back(param.makeParameterData());
}
return datas;
}
// bool DynamicCommandInstance::updateSoftEnum(std::string const& name) const
//{
// if (!hasRegistered())
// {
// for (auto& [key, values] : softEnumValues)
// {
// Global<CommandRegistry>->addSoftEnum(key, values);
// }
// return true;
// }
// if (!name.empty())
// {
// auto iter = softEnumValues.find(name);
// if (iter != softEnumValues.end())
// CommandSoftEnumRegistry(Global<CommandRegistry>).updateSoftEnum(SoftEnumUpdateType::Set, iter->first, iter->second);
// else
// return false;
// return true;
// }
// else
// {
// for (auto& [key, values] : softEnumValues)
// {
// CommandSoftEnumRegistry(Global<CommandRegistry>).updateSoftEnum(SoftEnumUpdateType::Set, key, values);
// }
// }
// }
std::string DynamicCommandInstance::setSoftEnum(std::string const& name, std::vector<std::string> const& values) const {
if (!hasRegistered()) {
softEnums.emplace(name, values);
} else {
if (!Global<CommandRegistry>)
return "";
auto names = CommandRegistry::getSoftEnumNames();
if (std::find(names.begin(), names.end(), name) == names.end()) {
Global<CommandRegistry>->addSoftEnum(name, values);
return name;
}
CommandSoftEnumRegistry(Global<CommandRegistry>).updateSoftEnum(SoftEnumUpdateType::Set, name, values);
}
return name;
}
bool DynamicCommandInstance::addSoftEnumValues(std::string const& name, std::vector<std::string> const& values) const {
if (!hasRegistered()) {
auto iter = softEnums.find(name);
if (iter != softEnums.end()) {
iter->second.insert(iter->second.end(), values.begin(), values.end());
} else {
setSoftEnum(name, values);
}
} else {
if (!Global<CommandRegistry>)
return false;
auto names = CommandRegistry::getSoftEnumNames();
if (std::find(names.begin(), names.end(), name) == names.end()) {
Global<CommandRegistry>->addSoftEnum(name, values);
return true;
}
CommandSoftEnumRegistry(Global<CommandRegistry>).updateSoftEnum(SoftEnumUpdateType::Add, name, values);
}
return true;
};
bool DynamicCommandInstance::removeSoftEnumValues(std::string const& name, std::vector<std::string> const& values) const {
if (!hasRegistered()) {
auto iter = softEnums.find(name);
if (iter != softEnums.end()) {
auto tmp = std::remove_if(
iter->second.begin(), iter->second.end(),
[values](std::string const& val) {
return std::find(values.begin(), values.end(), val) != values.end();
});
iter->second.erase(tmp, iter->second.end());
return true;
}
return false;
} else {
if (Global<CommandRegistry>)
CommandSoftEnumRegistry(Global<CommandRegistry>).updateSoftEnum(SoftEnumUpdateType::Remove, name, values);
}
return true;
}
inline std::vector<std::string> DynamicCommandInstance::getSoftEnumValues(std::string const& name) {
return CommandRegistry::getSoftEnumValues(name);
}
inline std::vector<std::string> DynamicCommandInstance::getSoftEnumNames() {
return CommandRegistry::getSoftEnumNames();
}
inline void DynamicCommandInstance::setCallback(DynamicCommand::CallBackFn&& callback) const {
this->callback = callback;
}
inline void DynamicCommandInstance::removeCallback() const {
callback = nullptr;
}
inline std::string const& DynamicCommandInstance::getCommandName() const {
return name;
}
inline bool DynamicCommandInstance::setBuilder(DynamicCommand::BuilderFn builder) {
if (this->builder == nullptr)
this->builder = builder;
else
return false;
return true;
}
inline DynamicCommand::BuilderFn DynamicCommandInstance::initCommandBuilder() {
auto builder = (DynamicCommand::BuilderFn)dcbNewCallback("ifsdl)p", &DynamicCommand::builderCallbackHanler, this);
if (this->setBuilder(builder))
return builder;
dcbFreeCallback((DCCallback*)builder);
return nullptr;
}
#pragma endregion
#ifdef DEBUG
#define TEST_DYNAMIC_COMMAND
#define successf(...) success(fmt::format(__VA_ARGS__))
#define errorf(...) error(fmt::format(__VA_ARGS__))
using Param = DynamicCommand::ParameterData;
using ParamType = DynamicCommand::ParameterType;
using ParamIndex = DynamicCommandInstance::ParameterIndex;
#ifdef TEST_DYNAMIC_COMMAND
#include <MC/Actor.hpp>
#include <MC/Player.hpp>
#include <MC/Minecraft.hpp>
void setupTestParamCommand() {
using Param = DynamicCommand::ParameterData;
using ParamType = DynamicCommand::ParameterType;
Param boolParam("testBool", ParamType::Bool, true);
Param intParam("testInt", ParamType::Int, true);
Param floatParam("testFloat", ParamType::Float, true);
Param strParam("testStr", ParamType::String, true);
Param actorParam("testActor", ParamType::Actor, true);
Param playerParam("testPlayer", ParamType::Player, true);
Param BlockPosParam("testBlockPos", ParamType::BlockPos, true);
Param Vec3Param("testVec3", ParamType::Vec3, true);
Param RawTextParam("testRawText", ParamType::RawText, true);
Param MessageParam("testMessage", ParamType::Message, true);
Param JsonValueParam("testJsonValue", ParamType::JsonValue, true);
Param ItemParam("testItem", ParamType::Item, true);
Param BlockParam("testBlock", ParamType::Block, true);
Param ActorTypeParam("testActorType", ParamType::ActorType, true);
Param EffectParam("testEffect", ParamType::Effect, true);
Param CommandParam("testCommand", ParamType::Command, true);
// Param posParam(ParamType::Position, "testPos", true);
// Test Command: dynparam true 114 3.14 str @e @a 3 1 4 ~3 ~1 ~4 raw text msg {"a":4} stick concrete speed version
DynamicCommand::setup(
"param", "dynamic command",
{},
{
boolParam,
intParam,
floatParam,
strParam,
actorParam,
playerParam,
BlockPosParam,
Vec3Param,
RawTextParam,
MessageParam,
JsonValueParam,
ItemParam,
BlockParam,
ActorTypeParam,
EffectParam,
CommandParam,
},
{{
"testActorType",
"testBool",
"testInt",
"testFloat",
"testStr",
"testActor",
"testPlayer",
"testBlockPos",
"testVec3",
//"testRawText",
//"testMessage",
//"testJsonValue",
//"testItem",
//"testBlock",
//"testEffect",
//"testCommand",
}},
[](DynamicCommand const& command, CommandOrigin const& origin, CommandOutput& output, std::unordered_map<std::string, Result>& results) {
for (auto& [name, result] : results) {
output.success(result.toDebugString());
}
},
CommandPermissionLevel::Any);
}
void setupTestEnumCommand() {
using ParamType = DynamicCommand::ParameterType;
using Param = DynamicCommand::ParameterData;
DynamicCommand::setup(
"testenum", // command name
"dynamic command", // command description
{
// enums{enumName, {values...}}
{"TestEnum1", {"add", "remove"}},
{"TestEnum2", {"list"}},
},
{
// parameters(type, name, [optional], [enumOptions(also enumName)], [identifier])
// identifier: used to identify unique parameter data, if idnetifier is not set,
// it is set to be the same as enumOptions or name (identifier = enumOptions.empty() ? name:enumOptions)
Param("testEnum", ParamType::Enum, "TestEnum1", "", CommandParameterOption::EnumAutocompleteExpansion),
Param("testEnum", ParamType::Enum, "TestEnum2", "", CommandParameterOption::EnumAutocompleteExpansion),
Param("testInt", ParamType::Int, true),
},
{
// overloads{ (type == Enum ? enumOptions : name) ...}
{"TestEnum1", "testInt"}, // testenum <add|remove> [testInt]
{"TestEnum2"}, // testenum <list>
},
// dynamic command callback
[](DynamicCommand const& command, CommandOrigin const& origin, CommandOutput& output,
std::unordered_map<std::string, DynamicCommand::Result>& results) {
auto& action = results["testEnum"].getRaw<std::string>();
switch (do_hash(action.c_str())) {
case do_hash("add"):
if (results["testInt"].isSet)
output.success(fmt::format("add {}", results["testInt"].getRaw<int>()));
else
output.success("add nothing");
break;
case do_hash("remove"):
if (results["testInt"].isSet)
output.success(fmt::format("remove {}", results["testInt"].getRaw<int>()));
else
output.success("remove nothing");
break;
case do_hash("list"):
output.success("list");
break;
default:
break;
}
},
CommandPermissionLevel::GameMasters);
}
void setupExampleCommand() {
using ParamType = DynamicCommand::ParameterType;
// create a dynamic command
auto command = DynamicCommand::createCommand("testcmd", "dynamic command", CommandPermissionLevel::GameMasters);
auto& optionsAdd = command->setEnum("TestOperation1", {"add", "remove"});
auto& optionsList = command->setEnum("TestOperation2", {"list"});
command->mandatory("testEnum", ParamType::Enum, optionsAdd, CommandParameterOption::EnumAutocompleteExpansion);
command->mandatory("testEnum", ParamType::Enum, optionsList, CommandParameterOption::EnumAutocompleteExpansion);
command->mandatory("testString", ParamType::String);
command->addOverload({optionsAdd, "testString"}); // dyncmd <add|remove> <testString:string>
command->addOverload({"TestOperation2"}); // dyncmd <list>
command->setCallback([](DynamicCommand const& command, CommandOrigin const& origin, CommandOutput& output, std::unordered_map<std::string, Result>& results) {
switch (do_hash(results["testEnum"].getRaw<std::string>().c_str())) {
case do_hash("add"):
output.success(fmt::format("Add - {}", results["testString"].getRaw<std::string>()));
break;
case do_hash("remove"):
output.success(fmt::format("Remove - {}", results["testString"].getRaw<std::string>()));
break;
case do_hash("list"):
output.success("List");
break;
default:
break;
}
});
// do not forget to setup the command instance
DynamicCommand::setup(std::move(command));
}
// "remove command" command
void setupRemoveCommand() {
auto command = DynamicCommand::createCommand("unregister", "unregister command", CommandPermissionLevel::Any);
command->setAlias("remove");
auto name = command->mandatory("name", ParamType::SoftEnum,
command->setSoftEnum("CommandNames", {}));
command->addOverload(name);
command->setCallback(
[](DynamicCommand const& cmd, CommandOrigin const& origin, CommandOutput& output,
std::unordered_map<std::string, Result>& results) {
auto& name = results["name"].getRaw<std::string>();
auto fullName = Global<CommandRegistry>->getCommandFullName(name);
if (fullName == cmd.getCommandName()) {
output.success("Request unregister itself");
Schedule::delay([fullName]() {
auto res = Global<CommandRegistry>->unregisterCommand(fullName);
if (res) {
dynamicCommandInstances.erase(fullName);
logger.info("unregister command " + fullName);
((DynamicCommandInstance*)0)->setSoftEnum("CommandNames", CommandRegistry::getEnumValues("CommandName"));
} else
logger.error("error in unregister command " + fullName);
},
20);
return;
}
auto res = Global<CommandRegistry>->unregisterCommand(fullName);
if (res) {
dynamicCommandInstances.erase(fullName);
output.success("unregister command " + fullName);
cmd.getInstance()->setSoftEnum("CommandNames", CommandRegistry::getEnumValues("CommandName"));
} else
output.error("error in unregister command " + fullName);
});
command->setSoftEnum("CommandNames", CommandRegistry::getEnumValues("CommandName"));
DynamicCommand::setup(std::move(command));
}
// TInstanceHook(void, "?run@Command@@QEBAXAEBVCommandOrigin@@AEAVCommandOutput@@@Z",
// Command, class CommandOrigin const& origin, class CommandOutput& output)
//{
// logger.info("Command({})::run()", getCommandName());
// }
// force enable cheat
TClasslessInstanceHook(bool, "?hasCommandsEnabled@LevelData@@QEBA_NXZ") {
return true;
}
TInstanceHook(CommandParameterData&, "?addOptions@CommandParameterData@@QEAAAEAV1@W4CommandParameterOption@@@Z",
CommandParameterData, CommandParameterOption option) {
// logger.warn("CommandParameterData::addOptions - name: {}, type: {}, desc: {}, option: {:x}",
// name, magic_enum::enum_name(type), desc ? desc : "", (int)option);
return original(this, option);
}
#endif // TEST_DYNAMIC_COMMAND
// enum command
void onEnumExecute(DynamicCommand const& cmd, CommandOrigin const& origin, CommandOutput& output,
std::unordered_map<std::string, Result>& results) {
auto enumNames = Global<CommandRegistry>->getEnumNames();
auto softEnumNames = Global<CommandRegistry>->getSoftEnumNames();
cmd.getInstance()->setSoftEnum("EnumNameList", enumNames);
cmd.getInstance()->addSoftEnumValues("EnumNameList", softEnumNames);
if (results["name"].isSet) {
auto& enumName = results["name"].getRaw<std::string>();
bool found = false;
if (std::find(enumNames.begin(), enumNames.end(), enumName) != enumNames.end()) {
found = true;
output.successf("§eEnum §l{}§r§e Values:", enumName);
for (auto& val : CommandRegistry::getEnumValues(enumName)) {
output.success(val);
// output.addToResultList("enums", val);
}
// output.success("Enums: %1$s", {CommandOutputParameter("enums")});
}
if (std::find(softEnumNames.begin(), softEnumNames.end(), enumName) != softEnumNames.end()) {
found = true;
output.successf("§eSoft Enum §l{}§r§e Values:", enumName);
for (auto& val : CommandRegistry::getSoftEnumValues(enumName)) {
output.success(val);
}
}
if (!found)
output.errorf("Enum or Soft Enum \"{}\" not found", enumName);
} else {
output.success("§eEnum Names:");
for (auto& val : CommandRegistry::getEnumNames()) {
output.success(val);
}
output.success("§eSoft Enum Names:");
for (auto& val : CommandRegistry::getSoftEnumNames()) {
output.success(val);
}
}
}
void setupEnumCommand() {
auto command = DynamicCommand::createCommand("enum", "get command enum names or values", CommandPermissionLevel::Any);
command->setAlias("enums");
auto name = command->mandatory("name", ParamType::SoftEnum,
command->setSoftEnum("EnumNameList", {}));
command->addOverload(name);
command->addOverload();
command->setCallback(onEnumExecute);
auto cmd = DynamicCommand::setup(std::move(command));
Schedule::delay(
[cmd]() {
auto packet = Global<CommandRegistry>->serializeAvailableCommands();
cmd->setSoftEnum("EnumNameList", packet.getEnumNames());
cmd->addSoftEnumValues("EnumNameList", packet.getSoftEnumNames());
},
10);
}
// echo command
void setupEchoCommand() {
auto command = DynamicCommand::createCommand("echo", "show message", CommandPermissionLevel::Any);
command->addOverload(command->mandatory("text", ParamType::RawText));
command->setCallback(
[](DynamicCommand const& cmd, CommandOrigin const& origin, CommandOutput& output,
std::unordered_map<std::string, Result>& results) {
auto& text = results["text"].getRaw<std::string>();
output.success(text);
});
DynamicCommand::setup(std::move(command));
}
TClasslessInstanceHook2("startServerThread_RegisterDebugCommand", void, "?startServerThread@ServerInstance@@QEAAXXZ") {
#ifdef DEBUG
setupRemoveCommand();
setupTestEnumCommand();
setupTestParamCommand();
setupExampleCommand();
#if false
Global<CommandRegistry>->printAll();
Schedule::delayRepeat(
[]() {
static bool Switch = true;
if (Switch)
Global<Level>->runcmd("remove example");
else
setupExampleCommand();
Switch = !Switch;
},
200, 2);
#endif // COMMAND_REGISTRY_EXTRA
#endif // DEBUG
original(this);
setupEnumCommand();
setupEchoCommand();
}
#endif // DEBUG
TClasslessInstanceHook(void, "?compile@BaseCommandBlock@@AEAAXAEBVCommandOrigin@@AEAVLevel@@@Z",
class CommandOrigin const& origin, class Level& level) {
if (LL::globalConfig.tickThreadId != std::this_thread::get_id()) {
SRWLockSharedHolder locker(delaySetupLock);
return original(this, origin, level);
}
return original(this, origin, level);
}