Skip to content

Commit 6c8ca33

Browse files
committed
Merge branch 'development' of github.com:ImperaZim/EasyLibrary into development
2 parents aacd634 + 1718d70 commit 6c8ca33

35 files changed

Lines changed: 2229 additions & 59 deletions

src/imperazim/command/Command.php

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use imperazim\command\constraint\PermissionConstraint;
1717
use imperazim\command\result\CommandResult;
1818
use imperazim\command\result\CommandFailure;
19+
use imperazim\command\HelpGenerator;
1920

2021
/**
2122
* Abstract base class for custom commands.
@@ -30,6 +31,15 @@ abstract class Command extends PMMPCommand {
3031
/** @var Plugin The plugin that owns this command */
3132
private readonly Plugin $plugin;
3233

34+
/** @var array Custom messages for command failures */
35+
private array $messages = [];
36+
37+
/** @var float|null Command cooldown in seconds */
38+
private ?float $cooldown = null;
39+
40+
/** @var array Last usage timestamps per sender */
41+
private static array $cooldowns = [];
42+
3343
/**
3444
* Constructor.
3545
*
@@ -44,6 +54,12 @@ public function __construct(Plugin $plugin) {
4454
$aliases = $config["aliases"] ?? [];
4555
$permission = $config["permission"] ?? DefaultPermissions::ROOT_USER;
4656

57+
// Store custom messages
58+
$this->messages = $config["messages"] ?? [];
59+
60+
// Store cooldown if provided
61+
$this->cooldown = $config["cooldown"] ?? null;
62+
4763
parent::__construct(
4864
$name,
4965
$description,
@@ -81,6 +97,96 @@ public function getPlugin(): Plugin {
8197
return $this->plugin;
8298
}
8399

100+
/**
101+
* Gets a custom message by key.
102+
*
103+
* @param string $key The message key
104+
* @param string $default Default message if key not found
105+
* @return string The message
106+
*/
107+
public function getMessage(string $key, string $default = ""): string {
108+
return $this->messages[$key] ?? $default;
109+
}
110+
111+
/**
112+
* Sets a custom message.
113+
*
114+
* @param string $key The message key
115+
* @param string $message The message text
116+
*/
117+
public function setMessage(string $key, string $message): void {
118+
$this->messages[$key] = $message;
119+
}
120+
121+
/**
122+
* Generates help text for this command.
123+
*
124+
* @param string $format The format style (detailed, compact, usage)
125+
* @return string The help text
126+
*/
127+
public function getHelp(string $format = "detailed"): string {
128+
return HelpGenerator::generate($this, $format);
129+
}
130+
131+
/**
132+
* Checks if sender is on cooldown.
133+
*
134+
* @param CommandSender $sender The sender to check
135+
* @return bool True if on cooldown
136+
*/
137+
private function isOnCooldown(CommandSender $sender): bool {
138+
if ($this->cooldown === null) {
139+
return false;
140+
}
141+
142+
$name = $sender->getName();
143+
$key = $this->getName() . ":" . $name;
144+
145+
if (!isset(self::$cooldowns[$key])) {
146+
return false;
147+
}
148+
149+
$elapsed = microtime(true) - self::$cooldowns[$key];
150+
return $elapsed < $this->cooldown;
151+
}
152+
153+
/**
154+
* Gets remaining cooldown time.
155+
*
156+
* @param CommandSender $sender The sender to check
157+
* @return float Remaining seconds
158+
*/
159+
private function getRemainingCooldown(CommandSender $sender): float {
160+
if ($this->cooldown === null) {
161+
return 0.0;
162+
}
163+
164+
$name = $sender->getName();
165+
$key = $this->getName() . ":" . $name;
166+
167+
if (!isset(self::$cooldowns[$key])) {
168+
return 0.0;
169+
}
170+
171+
$elapsed = microtime(true) - self::$cooldowns[$key];
172+
return max(0.0, $this->cooldown - $elapsed);
173+
}
174+
175+
/**
176+
* Updates sender's cooldown.
177+
*
178+
* @param CommandSender $sender The sender
179+
*/
180+
private function updateCooldown(CommandSender $sender): void {
181+
if ($this->cooldown === null) {
182+
return;
183+
}
184+
185+
$name = $sender->getName();
186+
$key = $this->getName() . ":" . $name;
187+
self::$cooldowns[$key] = microtime(true);
188+
}
189+
84190
/**
85191
* Executes the command.
86192
*
@@ -93,8 +199,18 @@ public function execute(
93199
string $label,
94200
array $rawArgs
95201
): void {
202+
// Check cooldown
203+
if ($this->isOnCooldown($sender)) {
204+
$remaining = $this->getRemainingCooldown($sender);
205+
$this->onFailure(
206+
new CommandFailure($sender, CommandFailure::COOLDOWN, [
207+
"remaining" => $remaining,
208+
])
209+
);
210+
return;
211+
}
96212
// Handle subcommands
97-
if (!empty($rawArgs)) {
213+
if (!empty($rawArgs) && !empty($this->getSubCommands())) {
98214
$key = strtolower(array_shift($rawArgs));
99215
foreach ($this->getSubCommands() as $sub) {
100216
$name = $sub->getName();
@@ -104,6 +220,7 @@ public function execute(
104220
return;
105221
}
106222
}
223+
array_unshift($rawArgs, $key);
107224
}
108225

109226
// Check constraints
@@ -133,6 +250,9 @@ public function execute(
133250
$parsedArgs = $this->parseArguments($sender, $rawArgs);
134251

135252
try {
253+
// Update cooldown before execution
254+
$this->updateCooldown($sender);
255+
136256
// Execute command logic
137257
$this->onExecute(new CommandResult($sender, $parsedArgs, $label));
138258
} catch (\Throwable $e) {

src/imperazim/command/LibCommand.php

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/imperazim/command/LibCommandInterceptor.php

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
use pocketmine\Server;
88
use pocketmine\player\Player;
9-
use pocketmine\event\EventPriority;
109
use pocketmine\command\CommandSender;
1110
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
1211
use pocketmine\network\mcpe\protocol\serializer\AvailableCommandsPacketAssembler;
@@ -23,6 +22,9 @@
2322
*/
2423
final class LibCommandInterceptor implements PacketHandlerInterface {
2524

25+
/** @var array<int, bool> Track processed packets to avoid recursion */
26+
private static array $processedPackets = [];
27+
2628
/**
2729
* Gets the packet IDs this handler manages.
2830
*
@@ -45,14 +47,32 @@ public function handle($packet, $session): bool {
4547
return true;
4648
}
4749

50+
$packetId = spl_object_id($packet);
51+
52+
// Skip if already processed to avoid recursion
53+
if (isset(self::$processedPackets[$packetId])) {
54+
return true;
55+
}
56+
4857
$player = $session->getPlayer();
4958
if (!($player instanceof Player)) {
5059
return true;
5160
}
5261

62+
// Mark as processing
63+
self::$processedPackets[$packetId] = true;
64+
5365
$server = Server::getInstance();
54-
$disassembled = AvailableCommandsPacketDisassembler::disassemble($packet);
66+
$logger = $server->getLogger();
67+
68+
try {
69+
$disassembled = AvailableCommandsPacketDisassembler::disassemble($packet);
70+
} catch (\Throwable $e) {
71+
$logger->warning("[LibCommand] Failed to disassemble packet: " . $e->getMessage());
72+
return true;
73+
}
5574
$commandDataList = $disassembled->commandData;
75+
5676
foreach($commandDataList as $index => $commandData) {
5777
$cmd = $server->getCommandMap()->getCommand($commandData->getName());
5878
if (!($cmd instanceof Command)) {
@@ -68,12 +88,26 @@ public function handle($packet, $session): bool {
6888
}
6989

7090
// Rebuild command UI
71-
$commandData->overloads = $this->getOverloads($player, $cmd);
91+
$overloads = $this->getOverloads($player, $cmd);
92+
$logger->debug("[LibCommand] Command '{$commandData->getName()}' overloads: " . count($overloads) . ", args: " . count($cmd->getArguments()));
93+
$commandData->overloads = $overloads;
7294
}
7395

74-
// Update dynamic enums
75-
//$session->sendDataPacket(AvailableCommandsPacketAssembler::assemble($commandDataList, [], CommandEnumManager::getEnums()));
76-
return true;
96+
// Send modified packet
97+
try {
98+
$modifiedPacket = AvailableCommandsPacketAssembler::assemble(array_values($commandDataList), [], CommandEnumManager::getEnums());
99+
} catch (\Throwable $e) {
100+
$logger->warning("[LibCommand] Failed to assemble packet: " . $e->getMessage());
101+
return true;
102+
}
103+
self::$processedPackets[spl_object_id($modifiedPacket)] = true;
104+
105+
$session->sendDataPacket($modifiedPacket);
106+
107+
// Clean up old packet ID from tracking
108+
unset(self::$processedPackets[$packetId]);
109+
110+
return false; // Cancel original packet
77111
}
78112

79113
/**

src/imperazim/command/SubCommand.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use imperazim\command\traits\ArgumentableTrait;
1212
use imperazim\command\traits\ConstraintableTrait;
1313
use imperazim\command\constraint\PermissionConstraint;
14+
use pocketmine\plugin\Plugin;
1415

1516
/**
1617
* Represents a subcommand within a main command.

src/imperazim/command/argument/Argument.php

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,31 @@ abstract class Argument {
2121
*
2222
* @param string $name Argument name (must be unique per command)
2323
* @param bool $optional Whether the argument is optional
24+
* @param mixed $default Default value when argument is not provided (only for optional arguments)
25+
* @param string $description Human-readable description of the argument
26+
* @param array $aliases Alternative names for this argument
27+
* @param callable|null $validator Custom validation function (receives value, returns bool)
2428
* @param CommandParameter|null $parameterData Pre-configured network parameter data
2529
*
26-
* @throws ArgumentException If argument name contains spaces
30+
* @throws ArgumentException If argument name contains spaces or default provided for required argument
2731
*/
2832
public function __construct(
2933
private string $name,
3034
private bool $optional = false,
35+
private mixed $default = null,
36+
private string $description = '',
37+
private array $aliases = [],
38+
private $validator = null,
3139
private ?CommandParameter $parameterData = null
3240
) {
3341
if (str_contains($name, ' ')) {
3442
throw new ArgumentException("Argument name cannot contain spaces: '{$name}'");
3543
}
3644

45+
if (!$optional && $default !== null) {
46+
throw new ArgumentException("Cannot set default value for required argument: '{$name}'");
47+
}
48+
3749
if ($this->parameterData === null) {
3850
$this->parameterData = CommandParameter::standard($name, $this->getNetworkType(), 0, $this->isOptional());
3951
}
@@ -57,6 +69,56 @@ public function isOptional(): bool {
5769
return $this->optional;
5870
}
5971

72+
/**
73+
* Gets the default value for optional arguments
74+
*
75+
* @return mixed Default value or null
76+
*/
77+
public function getDefault(): mixed {
78+
return $this->default;
79+
}
80+
81+
/**
82+
* Gets the argument description
83+
*
84+
* @return string Description text
85+
*/
86+
public function getDescription(): string {
87+
return $this->description;
88+
}
89+
90+
/**
91+
* Gets the argument aliases
92+
*
93+
* @return array Array of alternative names
94+
*/
95+
public function getAliases(): array {
96+
return $this->aliases;
97+
}
98+
99+
/**
100+
* Checks if a name matches this argument (including aliases)
101+
*
102+
* @param string $name Name to check
103+
* @return bool True if matches
104+
*/
105+
public function matchesName(string $name): bool {
106+
return $name === $this->name || in_array($name, $this->aliases, true);
107+
}
108+
109+
/**
110+
* Validates value using custom validator if provided
111+
*
112+
* @param mixed $value Value to validate
113+
* @return bool True if valid
114+
*/
115+
public function validate(mixed $value): bool {
116+
if ($this->validator !== null) {
117+
return ($this->validator)($value);
118+
}
119+
return true;
120+
}
121+
60122
/**
61123
* Gets the network-ready parameter data
62124
*

0 commit comments

Comments
 (0)