diff --git a/src/MessageBroadcastSubscriber.php b/src/MessageBroadcastSubscriber.php new file mode 100644 index 00000000000..11f9f38a4da --- /dev/null +++ b/src/MessageBroadcastSubscriber.php @@ -0,0 +1,40 @@ +> + * @var MessageBroadcastSubscriber[][] + * @phpstan-var array> */ private array $broadcastSubscribers = []; @@ -1056,7 +1056,7 @@ public function __construct( $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET))); $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3))))); - $forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language); + $forwarder = new BroadcastLoggerForwarder($this->logger, $this->language); $this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $forwarder); $this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $forwarder); @@ -1251,14 +1251,14 @@ private function startupPrepareNetworkInterfaces() : bool{ * Subscribes to a particular message broadcast channel. * The channel ID can be any arbitrary string. */ - public function subscribeToBroadcastChannel(string $channelId, CommandSender $subscriber) : void{ + public function subscribeToBroadcastChannel(string $channelId, MessageBroadcastSubscriber $subscriber) : void{ $this->broadcastSubscribers[$channelId][spl_object_id($subscriber)] = $subscriber; } /** * Unsubscribes from a particular message broadcast channel. */ - public function unsubscribeFromBroadcastChannel(string $channelId, CommandSender $subscriber) : void{ + public function unsubscribeFromBroadcastChannel(string $channelId, MessageBroadcastSubscriber $subscriber) : void{ if(isset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)])){ if(count($this->broadcastSubscribers[$channelId]) === 1){ unset($this->broadcastSubscribers[$channelId]); @@ -1271,43 +1271,44 @@ public function unsubscribeFromBroadcastChannel(string $channelId, CommandSender /** * Unsubscribes from all broadcast channels. */ - public function unsubscribeFromAllBroadcastChannels(CommandSender $subscriber) : void{ + public function unsubscribeFromAllBroadcastChannels(MessageBroadcastSubscriber $subscriber) : void{ foreach(Utils::stringifyKeys($this->broadcastSubscribers) as $channelId => $recipients){ $this->unsubscribeFromBroadcastChannel($channelId, $subscriber); } } /** - * Returns a list of all the CommandSenders subscribed to the given broadcast channel. + * Returns a list of all the broadcast subscribers subscribed to the given broadcast channel with permission to + * receive messages from it. * - * @return CommandSender[] - * @phpstan-return array + * @return MessageBroadcastSubscriber[] + * @phpstan-return array */ public function getBroadcastChannelSubscribers(string $channelId) : array{ return $this->broadcastSubscribers[$channelId] ?? []; } /** - * @param CommandSender[]|null $recipients + * @param MessageBroadcastSubscriber[]|null $recipients */ - public function broadcastMessage(Translatable|string $message, ?array $recipients = null) : int{ - $recipients = $recipients ?? $this->getBroadcastChannelSubscribers(self::BROADCAST_CHANNEL_USERS); + public function broadcastMessage(CommandSender $source, Translatable|string $message, string $channelId = self::BROADCAST_CHANNEL_USERS, ?array $recipients = null) : int{ + $recipients = $recipients ?? $this->getBroadcastChannelSubscribers($channelId); foreach($recipients as $recipient){ - $recipient->sendMessage($message); + $recipient->onMessage($source, $message, $channelId); } return count($recipients); } /** - * @return Player[] + * @return MinecraftMessageBroadcastSubscriber[] */ - private function getPlayerBroadcastSubscribers(string $channelId) : array{ + private function getMinecraftBroadcastSubscribers(string $channelId) : array{ /** @var Player[] $players */ $players = []; - foreach($this->broadcastSubscribers[$channelId] as $subscriber){ - if($subscriber instanceof Player){ + foreach($this->broadcastSubscribers[$channelId] ?? [] as $subscriber){ + if($subscriber instanceof MinecraftMessageBroadcastSubscriber){ $players[spl_object_id($subscriber)] = $subscriber; } } @@ -1315,42 +1316,51 @@ private function getPlayerBroadcastSubscribers(string $channelId) : array{ } /** - * @param Player[]|null $recipients + * @param MinecraftMessageBroadcastSubscriber[]|null $recipients */ - public function broadcastTip(string $tip, ?array $recipients = null) : int{ - $recipients = $recipients ?? $this->getPlayerBroadcastSubscribers(self::BROADCAST_CHANNEL_USERS); + public function broadcastTip(CommandSender $source, string $tip, string $channelId = self::BROADCAST_CHANNEL_USERS, ?array $recipients = null) : int{ + $recipients = $recipients ?? $this->getMinecraftBroadcastSubscribers($channelId); foreach($recipients as $recipient){ - $recipient->sendTip($tip); + $recipient->onTip($source, $tip, $channelId); } return count($recipients); } /** - * @param Player[]|null $recipients + * @param MinecraftMessageBroadcastSubscriber[]|null $recipients */ - public function broadcastPopup(string $popup, ?array $recipients = null) : int{ - $recipients = $recipients ?? $this->getPlayerBroadcastSubscribers(self::BROADCAST_CHANNEL_USERS); + public function broadcastPopup(CommandSender $source, string $popup, string $channelId = self::BROADCAST_CHANNEL_USERS, ?array $recipients = null) : int{ + $recipients = $recipients ?? $this->getMinecraftBroadcastSubscribers($channelId); foreach($recipients as $recipient){ - $recipient->sendPopup($popup); + $recipient->onPopup($source, $popup, $channelId); } return count($recipients); } /** - * @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used. - * @param int $stay Duration in ticks to stay on screen for - * @param int $fadeOut Duration in ticks for fade-out. - * @param Player[]|null $recipients + * @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used. + * @param int $stay Duration in ticks to stay on screen for + * @param int $fadeOut Duration in ticks for fade-out. + * @param MinecraftMessageBroadcastSubscriber[]|null $recipients */ - public function broadcastTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1, ?array $recipients = null) : int{ - $recipients = $recipients ?? $this->getPlayerBroadcastSubscribers(self::BROADCAST_CHANNEL_USERS); + public function broadcastTitle( + CommandSender $source, + string $title, + string $subtitle = "", + int $fadeIn = -1, + int $stay = -1, + int $fadeOut = -1, + string $channelId = self::BROADCAST_CHANNEL_USERS, + ?array $recipients = null + ) : int{ + $recipients = $recipients ?? $this->getMinecraftBroadcastSubscribers($channelId); foreach($recipients as $recipient){ - $recipient->sendTitle($title, $subtitle, $fadeIn, $stay, $fadeOut); + $recipient->onTitle($source, $title, $subtitle, $fadeIn, $stay, $fadeOut, $channelId); } return count($recipients); diff --git a/src/command/Command.php b/src/command/Command.php index 30338ddad7f..7f13c986ee4 100644 --- a/src/command/Command.php +++ b/src/command/Command.php @@ -31,7 +31,6 @@ use pocketmine\lang\Translatable; use pocketmine\permission\PermissionManager; use pocketmine\Server; -use pocketmine\utils\BroadcastLoggerForwarder; use pocketmine\utils\TextFormat; use function explode; use function implode; @@ -223,21 +222,13 @@ public function setUsage(Translatable|string $usage) : void{ } public static function broadcastCommandMessage(CommandSender $source, Translatable|string $message, bool $sendToSource = true) : void{ - $users = $source->getServer()->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_ADMINISTRATIVE); - $result = KnownTranslationFactory::chat_type_admin($source->getName(), $message); - $colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC); + $broadcast = KnownTranslationFactory::chat_type_admin($source->getName(), $message); if($sendToSource){ $source->sendMessage($message); } - foreach($users as $user){ - if($user instanceof BroadcastLoggerForwarder){ - $user->sendMessage($result); - }elseif($user !== $source){ - $user->sendMessage($colored); - } - } + $source->getServer()->broadcastMessage($source, $broadcast, Server::BROADCAST_CHANNEL_ADMINISTRATIVE); } public function __toString() : string{ diff --git a/src/command/defaults/MeCommand.php b/src/command/defaults/MeCommand.php index a6708840c3d..de043d109da 100644 --- a/src/command/defaults/MeCommand.php +++ b/src/command/defaults/MeCommand.php @@ -48,7 +48,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args throw new InvalidCommandSyntaxException(); } - $sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::RESET . implode(" ", $args))); + $sender->getServer()->broadcastMessage($sender, KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::RESET . implode(" ", $args))); return true; } diff --git a/src/command/defaults/SayCommand.php b/src/command/defaults/SayCommand.php index 5c3203b5f07..d7b5ba1f330 100644 --- a/src/command/defaults/SayCommand.php +++ b/src/command/defaults/SayCommand.php @@ -49,7 +49,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args throw new InvalidCommandSyntaxException(); } - $sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_announcement( + $sender->getServer()->broadcastMessage($sender, KnownTranslationFactory::chat_type_announcement( $sender instanceof Player ? $sender->getDisplayName() : ($sender instanceof ConsoleCommandSender ? "Server" : $sender->getName()), implode(" ", $args) )->prefix(TextFormat::LIGHT_PURPLE)); diff --git a/src/event/player/PlayerChatEvent.php b/src/event/player/PlayerChatEvent.php index 1cdbb49fb80..6658dd25908 100644 --- a/src/event/player/PlayerChatEvent.php +++ b/src/event/player/PlayerChatEvent.php @@ -23,9 +23,9 @@ namespace pocketmine\event\player; -use pocketmine\command\CommandSender; use pocketmine\event\Cancellable; use pocketmine\event\CancellableTrait; +use pocketmine\MessageBroadcastSubscriber; use pocketmine\player\chat\ChatFormatter; use pocketmine\player\Player; use pocketmine\utils\Utils; @@ -37,7 +37,7 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{ use CancellableTrait; /** - * @param CommandSender[] $recipients + * @param MessageBroadcastSubscriber[] $recipients */ public function __construct( Player $player, @@ -72,17 +72,17 @@ public function setFormatter(ChatFormatter $formatter) : void{ } /** - * @return CommandSender[] + * @return MessageBroadcastSubscriber[] */ public function getRecipients() : array{ return $this->recipients; } /** - * @param CommandSender[] $recipients + * @param MessageBroadcastSubscriber[] $recipients */ public function setRecipients(array $recipients) : void{ - Utils::validateArrayValueType($recipients, function(CommandSender $_) : void{}); + Utils::validateArrayValueType($recipients, function(MessageBroadcastSubscriber $_) : void{}); $this->recipients = $recipients; } } diff --git a/src/player/Player.php b/src/player/Player.php index 96f53470fbe..8c3e5263402 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -104,6 +104,7 @@ use pocketmine\lang\Language; use pocketmine\lang\Translatable; use pocketmine\math\Vector3; +use pocketmine\MinecraftMessageBroadcastSubscriber; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\network\mcpe\NetworkSession; @@ -167,7 +168,7 @@ /** * Main class that handles networking, recovery, and packet sending to the server part */ -class Player extends Human implements CommandSender, ChunkListener, IPlayer{ +class Player extends Human implements MinecraftMessageBroadcastSubscriber, CommandSender, ChunkListener, IPlayer{ use PermissibleDelegateTrait; private const MOVES_PER_TICK = 2; @@ -905,7 +906,7 @@ public function doFirstSpawn() : void{ ); $ev->call(); if($ev->getJoinMessage() !== ""){ - $this->server->broadcastMessage($ev->getJoinMessage()); + $this->server->broadcastMessage($this, $ev->getJoinMessage()); } $this->noDamageTicks = 60; @@ -1530,7 +1531,11 @@ public function chat(string $message) : bool{ $ev = new PlayerChatEvent($this, $messagePart, $this->server->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_USERS), new StandardChatFormatter()); $ev->call(); if(!$ev->isCancelled()){ - $this->server->broadcastMessage($ev->getFormatter()->format($ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients()); + $this->server->broadcastMessage( + $ev->getPlayer(), + $ev->getFormatter()->format($ev->getPlayer()->getDisplayName(), $ev->getMessage()), + recipients: $ev->getRecipients() + ); } } } @@ -2130,6 +2135,34 @@ public function setTitleDuration(int $fadeIn, int $stay, int $fadeOut) : void{ } } + public function onMessage(CommandSender $source, Translatable|string $message, string $channelId) : void{ + if($channelId === Server::BROADCAST_CHANNEL_ADMINISTRATIVE){ + if($source === $this){ + return; + } + if($message instanceof Translatable){ + $message = $message->prefix(TextFormat::GRAY . TextFormat::ITALIC); + }else{ + $message = TextFormat::GRAY . TextFormat::ITALIC . $message; + } + } + $this->sendMessage($message); + } + + public function onTip(CommandSender $source, Translatable|string $message, string $channelId) : void{ + //TODO: support translations in the proper plugin methods + $this->sendTip($message instanceof Translatable ? $this->getLanguage()->translate($message) : $message); + } + + public function onPopup(CommandSender $source, Translatable|string $message, string $channelId) : void{ + //TODO: support translations in the proper plugin methods + $this->sendPopup($message instanceof Translatable ? $this->getLanguage()->translate($message) : $message); + } + + public function onTitle(CommandSender $source, string $title, string $subtitle, int $fadeIn, int $stay, int $fadeOut, string $channelId) : void{ + $this->sendTitle($title, $subtitle, $fadeIn, $stay, $fadeOut); + } + /** * Sends a direct chat message to a player */ @@ -2287,7 +2320,7 @@ public function onPostDisconnect(Translatable|string $reason, Translatable|strin $ev = new PlayerQuitEvent($this, $quitMessage ?? $this->getLeaveMessage(), $reason); $ev->call(); if(($quitMessage = $ev->getQuitMessage()) != ""){ - $this->server->broadcastMessage($quitMessage); + $this->server->broadcastMessage($this, $quitMessage); } $this->save(); @@ -2418,7 +2451,7 @@ protected function onDeath() : void{ } if($ev->getDeathMessage() != ""){ - $this->server->broadcastMessage($ev->getDeathMessage()); + $this->server->broadcastMessage($this, $ev->getDeathMessage()); } $this->startDeathAnimation(); diff --git a/src/utils/BroadcastLoggerForwarder.php b/src/utils/BroadcastLoggerForwarder.php index 1900421fc99..dfc4de696e7 100644 --- a/src/utils/BroadcastLoggerForwarder.php +++ b/src/utils/BroadcastLoggerForwarder.php @@ -26,55 +26,24 @@ use pocketmine\command\CommandSender; use pocketmine\lang\Language; use pocketmine\lang\Translatable; -use pocketmine\permission\PermissibleBase; -use pocketmine\permission\PermissibleDelegateTrait; -use pocketmine\Server; -use const PHP_INT_MAX; +use pocketmine\MessageBroadcastSubscriber; /** * Forwards any messages it receives via sendMessage() to the given logger. Used for forwarding chat messages and * command audit log messages to the server log file. - * - * Unfortunately, broadcast subscribers are currently required to implement CommandSender, so this class has to include - * a lot of useless methods. */ -final class BroadcastLoggerForwarder implements CommandSender{ - use PermissibleDelegateTrait; +final class BroadcastLoggerForwarder implements MessageBroadcastSubscriber{ public function __construct( - private Server $server, //annoying useless dependency private \Logger $logger, private Language $language - ){ - //this doesn't need any permissions - $this->perm = new PermissibleBase([]); - } - - public function getLanguage() : Language{ - return $this->language; - } + ){} - public function sendMessage(Translatable|string $message) : void{ + public function onMessage(CommandSender $source, Translatable|string $message, string $channelId) : void{ if($message instanceof Translatable){ $this->logger->info($this->language->translate($message)); }else{ $this->logger->info($message); } } - - public function getServer() : Server{ - return $this->server; - } - - public function getName() : string{ - return "Broadcast Logger Forwarder"; - } - - public function getScreenLineHeight() : int{ - return PHP_INT_MAX; - } - - public function setScreenLineHeight(?int $height) : void{ - //NOOP - } }