From 3ff6d42109819f34070ec43650df55bda44ed395 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 5 Oct 2024 17:11:11 +0100 Subject: [PATCH] feat: add CommandSenderWrapper --- .../endstone/command/command_sender_wrapper.h | 118 ++++++++++++++++++ include/endstone/detail/signal_handler.h | 2 +- .../endstone/_internal/endstone_python.pyi | 10 +- python/src/endstone/command.py | 10 +- src/endstone_python/command.cpp | 9 ++ 5 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 include/endstone/command/command_sender_wrapper.h diff --git a/include/endstone/command/command_sender_wrapper.h b/include/endstone/command/command_sender_wrapper.h new file mode 100644 index 000000000..bc1f08cb4 --- /dev/null +++ b/include/endstone/command/command_sender_wrapper.h @@ -0,0 +1,118 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "endstone/command/command_sender.h" + +namespace endstone { + +/** + * @brief Represents a wrapper that forwards commands to the wrapped CommandSender and captures its output + */ +class CommandSenderWrapper final : public CommandSender { +public: + using Callback = std::function; + explicit CommandSenderWrapper(CommandSender &sender, Callback on_message = {}, Callback on_error = {}) + : sender_(sender), on_message_(std::move(on_message)), on_error_(std::move(on_error)){}; + + void sendMessage(const Message &message) const override + { + if (on_message_) { + on_message_(message); + } + } + void sendErrorMessage(const Message &message) const override + { + if (on_error_) { + on_error_(message); + } + } + + [[nodiscard]] bool isOp() const override + { + return sender_.isOp(); + } + void setOp(bool value) override + { + sender_.setOp(value); + } + [[nodiscard]] bool isPermissionSet(std::string name) const override + { + return sender_.isPermissionSet(name); + } + [[nodiscard]] bool isPermissionSet(const Permission &perm) const override + { + return sender_.isPermissionSet(perm); + } + [[nodiscard]] bool hasPermission(std::string name) const override + { + return sender_.hasPermission(name); + } + [[nodiscard]] bool hasPermission(const Permission &perm) const override + { + return sender_.hasPermission(perm); + } + Result addAttachment(Plugin &plugin, const std::string &name, bool value) override + { + return sender_.addAttachment(plugin, name, value); + } + Result addAttachment(Plugin &plugin) override + { + return sender_.addAttachment(plugin); + } + Result removeAttachment(PermissionAttachment &attachment) override + { + return sender_.removeAttachment(attachment); + } + void recalculatePermissions() override + { + sender_.recalculatePermissions(); + } + [[nodiscard]] std::unordered_set getEffectivePermissions() const override + { + return sender_.getEffectivePermissions(); + } + [[nodiscard]] CommandSender *asCommandSender() const override + { + return sender_.asCommandSender(); + } + [[nodiscard]] ConsoleCommandSender *asConsole() const override + { + return sender_.asConsole(); + } + [[nodiscard]] Actor *asActor() const override + { + return sender_.asActor(); + } + [[nodiscard]] Player *asPlayer() const override + { + return sender_.asPlayer(); + } + [[nodiscard]] Server &getServer() const override + { + return sender_.getServer(); + } + [[nodiscard]] std::string getName() const override + { + return sender_.getName(); + } + +private: + CommandSender &sender_; + Callback on_message_; + Callback on_error_; +}; + +} // namespace endstone diff --git a/include/endstone/detail/signal_handler.h b/include/endstone/detail/signal_handler.h index aba4a942e..9e5c52499 100644 --- a/include/endstone/detail/signal_handler.h +++ b/include/endstone/detail/signal_handler.h @@ -31,7 +31,7 @@ inline void print_frame(std::ostream &stream, bool color, unsigned frame_number_ const auto *green = color ? "\033[32m" : ""; const auto *yellow = color ? "\033[33m" : ""; const auto *blue = color ? "\033[34m" : ""; - std::string line = fmt::format("#{:<{}} ", counter, frame_number_width); + std::string line = fmt::format("[{:<{}}] ", counter, frame_number_width); if (frame.is_inline) { line += fmt::format("{:<{}}", "(inlined)", 2 * sizeof(cpptrace::frame_ptr) + 2); } diff --git a/python/src/endstone/_internal/endstone_python.pyi b/python/src/endstone/_internal/endstone_python.pyi index 2c67754df..103a37674 100644 --- a/python/src/endstone/_internal/endstone_python.pyi +++ b/python/src/endstone/_internal/endstone_python.pyi @@ -4,7 +4,7 @@ import numpy import os import typing import uuid -__all__ = ['ActionForm', 'Actor', 'ActorDeathEvent', 'ActorEvent', 'ActorKnockbackEvent', 'ActorRemoveEvent', 'ActorSpawnEvent', 'ActorTeleportEvent', 'BarColor', 'BarFlag', 'BarStyle', 'Block', 'BlockBreakEvent', 'BlockData', 'BlockEvent', 'BlockFace', 'BlockPlaceEvent', 'BlockState', 'BossBar', 'BroadcastMessageEvent', 'ColorFormat', 'Command', 'CommandExecutor', 'CommandSender', 'ConsoleCommandSender', 'Criteria', 'Dimension', 'DisplaySlot', 'Dropdown', 'Event', 'EventPriority', 'GameMode', 'Inventory', 'ItemStack', 'Label', 'Level', 'Location', 'Logger', 'MessageForm', 'Mob', 'ModalForm', 'Objective', 'ObjectiveSortOrder', 'Packet', 'PacketType', 'Permissible', 'Permission', 'PermissionAttachment', 'PermissionAttachmentInfo', 'PermissionDefault', 'Player', 'PlayerChatEvent', 'PlayerCommandEvent', 'PlayerDeathEvent', 'PlayerEvent', 'PlayerInteractActorEvent', 'PlayerInteractEvent', 'PlayerInventory', 'PlayerJoinEvent', 'PlayerKickEvent', 'PlayerLoginEvent', 'PlayerQuitEvent', 'PlayerTeleportEvent', 'Plugin', 'PluginCommand', 'PluginDescription', 'PluginDisableEvent', 'PluginEnableEvent', 'PluginLoadOrder', 'PluginLoader', 'PluginManager', 'Position', 'RenderType', 'Scheduler', 'Score', 'Scoreboard', 'Server', 'ServerCommandEvent', 'ServerListPingEvent', 'ServerLoadEvent', 'Skin', 'Slider', 'SocketAddress', 'SpawnParticleEffectPacket', 'StepSlider', 'Task', 'TextInput', 'ThunderChangeEvent', 'Toggle', 'Translatable', 'Vector', 'WeatherChangeEvent'] +__all__ = ['ActionForm', 'Actor', 'ActorDeathEvent', 'ActorEvent', 'ActorKnockbackEvent', 'ActorRemoveEvent', 'ActorSpawnEvent', 'ActorTeleportEvent', 'BarColor', 'BarFlag', 'BarStyle', 'Block', 'BlockBreakEvent', 'BlockData', 'BlockEvent', 'BlockFace', 'BlockPlaceEvent', 'BlockState', 'BossBar', 'BroadcastMessageEvent', 'ColorFormat', 'Command', 'CommandExecutor', 'CommandSender', 'CommandSenderWrapper', 'ConsoleCommandSender', 'Criteria', 'Dimension', 'DisplaySlot', 'Dropdown', 'Event', 'EventPriority', 'GameMode', 'Inventory', 'ItemStack', 'Label', 'Level', 'Location', 'Logger', 'MessageForm', 'Mob', 'ModalForm', 'Objective', 'ObjectiveSortOrder', 'Packet', 'PacketType', 'Permissible', 'Permission', 'PermissionAttachment', 'PermissionAttachmentInfo', 'PermissionDefault', 'Player', 'PlayerChatEvent', 'PlayerCommandEvent', 'PlayerDeathEvent', 'PlayerEvent', 'PlayerInteractActorEvent', 'PlayerInteractEvent', 'PlayerInventory', 'PlayerJoinEvent', 'PlayerKickEvent', 'PlayerLoginEvent', 'PlayerQuitEvent', 'PlayerTeleportEvent', 'Plugin', 'PluginCommand', 'PluginDescription', 'PluginDisableEvent', 'PluginEnableEvent', 'PluginLoadOrder', 'PluginLoader', 'PluginManager', 'Position', 'RenderType', 'Scheduler', 'Score', 'Scoreboard', 'Server', 'ServerCommandEvent', 'ServerListPingEvent', 'ServerLoadEvent', 'Skin', 'Slider', 'SocketAddress', 'SpawnParticleEffectPacket', 'StepSlider', 'Task', 'TextInput', 'ThunderChangeEvent', 'Toggle', 'Translatable', 'Vector', 'WeatherChangeEvent'] class ActionForm: """ Represents a form with buttons that let the player take action. @@ -751,6 +751,12 @@ class CommandSender(Permissible): """ Returns the server instance that this command is running on """ +class CommandSenderWrapper(CommandSender): + """ + Represents a wrapper class that forwards commands to a CommandSender and captures its output + """ + def __init__(self, sender: CommandSender, on_message: typing.Callable[[str | Translatable], None] = None, on_error: typing.Callable[[str | Translatable], None] = None) -> None: + ... class ConsoleCommandSender(CommandSender): """ Represents a console command sender. @@ -2585,7 +2591,7 @@ class Server: """ Creates a new Scoreboard to be tracked by the server. """ - def dispatch_command(self, sender: CommandSender, command: str) -> bool: + def dispatch_command(self, sender: CommandSender, command_line: str) -> bool: """ Dispatches a command on this server, and executes it if found. """ diff --git a/python/src/endstone/command.py b/python/src/endstone/command.py index 1dc42832a..ee233993b 100644 --- a/python/src/endstone/command.py +++ b/python/src/endstone/command.py @@ -1,3 +1,9 @@ -from endstone._internal.endstone_python import Command, CommandExecutor, CommandSender, ConsoleCommandSender +from endstone._internal.endstone_python import ( + Command, + CommandExecutor, + CommandSender, + CommandSenderWrapper, + ConsoleCommandSender, +) -__all__ = ["Command", "CommandExecutor", "CommandSender", "ConsoleCommandSender"] +__all__ = ["Command", "CommandExecutor", "CommandSender", "CommandSenderWrapper", "ConsoleCommandSender"] diff --git a/src/endstone_python/command.cpp b/src/endstone_python/command.cpp index e217c1173..61c584756 100644 --- a/src/endstone_python/command.cpp +++ b/src/endstone_python/command.cpp @@ -16,11 +16,13 @@ #include +#include #include #include #include "endstone/command/command_executor.h" #include "endstone/command/command_sender.h" +#include "endstone/command/command_sender_wrapper.h" #include "endstone/command/console_command_sender.h" #include "endstone/logger.h" #include "endstone/plugin/plugin.h" @@ -67,6 +69,13 @@ void init_command(py::module &m, py::class_ &command "Returns the server instance that this command is running on") .def_property_readonly("name", &CommandSender::getName, "Gets the name of this command sender"); + py::class_( + m, "CommandSenderWrapper", + "Represents a wrapper that forwards commands to the wrapped CommandSender and captures its output") + .def(py::init(), + py::arg("sender"), py::arg("on_message") = CommandSenderWrapper::Callback{}, + py::arg("on_error") = CommandSenderWrapper::Callback{}); + py::class_(m, "ConsoleCommandSender", "Represents a console command sender."); py::class_>(m, "Command",