1 /* 2 * Copyright (c) 2017-2018 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: 2017-2018 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/selery/source/selery/commands.d, selery/commands.d) 28 */ 29 module selery.commands; 30 31 import std.algorithm : sort, clamp, min, filter; 32 import std.conv : to; 33 import std.math : ceil; 34 import std.random : uniform; 35 import std.string : join, toLower, startsWith; 36 import std.traits : hasUDA, getUDAs, Parameters; 37 import std.typetuple : TypeTuple; 38 39 import selery.about : Software; 40 import selery.command.command : Command; 41 import selery.command.util : CommandSender, WorldCommandSender, PocketType, SingleEnum, SnakeCaseEnum, Ranged, Position, Target; 42 import selery.config : Config, Gamemode, Difficulty, Dimension; 43 import selery.effect : Effects; 44 import selery.enchantment : Enchantments; 45 import selery.entity.entity : Entity; 46 import selery.lang : Translation, Translatable; 47 import selery.log : Format; 48 import selery.node.info : PlayerInfo, WorldInfo; 49 import selery.node.server : isServerRunning, NodeServer, ServerCommandSender; 50 import selery.player.bedrock : BedrockPlayer; 51 import selery.player.java : JavaPlayer; 52 import selery.player.player : Player, InputMode, PermissionLevel; 53 import selery.plugin : Description, permission, hidden, unimplemented; 54 import selery.util.messages : Messages; 55 import selery.util.util : unformat; 56 import selery.world.world : Time; 57 58 enum vanilla; 59 enum op; 60 61 struct aliases { 62 63 string[] aliases; 64 65 this(string[] aliases...) { 66 this.aliases = aliases; 67 } 68 69 } 70 71 /** 72 * Supported vanilla commands: 73 * [ ] clear 74 * [ ] clone 75 * [ ] defaultgamemode 76 * [x] deop 77 * [ ] difficulty 78 * [ ] effect 79 * [ ] enchant 80 * [ ] execute 81 * [ ] fill 82 * [x] gamemode 83 * [ ] gamerule 84 * [ ] give 85 * [x] help 86 * [x] kick 87 * [ ] kill 88 * [x] list 89 * [ ] locate 90 * [x] me 91 * [x] op 92 * [ ] playsound 93 * [ ] replaceitem 94 * [x] say 95 * [ ] setblock 96 * [x] setmaxplayers 97 * [ ] setworldspawn 98 * [ ] spawnpoint 99 * [ ] spreadplayers 100 * [x] stop 101 * [ ] stopsound 102 * [ ] summon 103 * [x] tell 104 * [ ] testfor 105 * [ ] testforblock 106 * [ ] testforblocks 107 * [ ] time 108 * [ ] title 109 * [x] toggledownfall 110 * [ ] tp (teleport) 111 * [x] transferserver 112 * [x] weather 113 * 114 * Supported multiplayer commands: 115 * [ ] ban 116 * [ ] ban-ip 117 * [ ] banlist 118 * [ ] pardon 119 * [x] stop 120 * [ ] whitelist 121 */ 122 final class Commands { 123 124 enum list = mixin({ 125 string[] commands; 126 foreach(member ; __traits(allMembers, typeof(this))) { 127 static if(member[$-1] == '0') commands ~= member[0..$-1]; 128 } 129 return "TypeTuple!(" ~ commands.to!string[1..$-1] ~ ")"; 130 }()); 131 132 static Commands register(shared NodeServer server) { 133 return new Commands(server).register(); 134 } 135 136 private shared NodeServer server; 137 138 public this(shared NodeServer server) { 139 this.server = server; 140 } 141 142 public Commands register() { 143 auto server = cast()this.server; 144 const config = this.server.config.node; 145 foreach(command ; list) { 146 if(mixin("config." ~ command ~ "Command")) this.registerImpl!(command, 0)(server); 147 } 148 return this; 149 } 150 151 private void registerImpl(string command, size_t count)(NodeServer server) { 152 mixin("alias C = " ~ command ~ to!string(count) ~ ";"); 153 static if(count == 0) { 154 static if(hasUDA!(C, vanilla)) enum description = Translatable.fromBedrock("commands." ~ command ~ ".description"); 155 else enum description = Translatable("commands." ~ command ~ ".description"); 156 static if(hasUDA!(C, aliases)) enum aliases = getUDAs!(C, aliases)[0].aliases; 157 else enum string[] aliases = []; 158 static if(hasUDA!(C, permission)) enum permissions = getUDAs!(C, permission)[0].permissions; 159 else enum string[] permissions = []; 160 server.registerCommand!C(mixin("&this." ~ command ~ count.to!string), convertedName!command, Description(description), aliases, hasUDA!(C, op), permissions, hasUDA!(C, hidden), !hasUDA!(C, unimplemented)); 161 } else { 162 server.registerCommand!C(mixin("&this." ~ command ~ count.to!string), convertedName!command, Description.init, [], 0, [], false, !hasUDA!(C, unimplemented)); 163 } 164 static if(__traits(hasMember, typeof(this), command ~ to!string(count + 1))) this.registerImpl!(command, count + 1)(server); 165 } 166 167 private void sendUnimplementedMessage(CommandSender sender) { 168 sender.sendMessage(Format.red, "Not Implemented"); 169 } 170 171 public Commands unregister() { 172 //TODO unregister overloads using delegate's pointers 173 auto server = cast()this.server; 174 foreach(command ; list) { 175 this.unregisterImpl!(command, 0)(server); 176 } 177 return this; 178 } 179 180 private void unregisterImpl(string command, size_t count)(NodeServer server) { 181 mixin("alias C = " ~ command ~ to!string(count) ~ ";"); 182 //server.unregisterCommandByOverload(mixin("&this." ~ command ~ to!string(count)), convertedName!command); 183 static if(__traits(hasMember, typeof(this), command ~ to!string(count + 1))) this.unregisterImpl!(command, count + 1)(server); 184 } 185 186 // about 187 188 void about0(CommandSender sender) { 189 sender.sendMessage(Translation(Messages.about.software, Software.name ~ " " ~ Software.fullVersion)); 190 if(this.server.plugins.length) { 191 sender.sendMessage(Translation(Messages.about.plugins, this.server.plugins.length)); 192 foreach(_plugin ; this.server.plugins) { 193 auto plugin = cast()_plugin; 194 sender.sendMessage("* ", Format.green, plugin.name, Format.reset, " ", (!plugin.vers.startsWith("~") ? "v" : ""), plugin.vers); 195 } 196 } 197 } 198 199 // clear 200 201 @vanilla @op clear0(Player sender) { 202 this.clear1(sender, [sender]); 203 } 204 205 @unimplemented @vanilla clear1(WorldCommandSender sender, Player[] target) {} 206 207 @unimplemented @vanilla clear2(WorldCommandSender sender, Player[] target, string itemName) {} 208 209 // clone 210 211 enum MaskMode { masked, replace } 212 213 enum CloneMode { force, move, normal } 214 215 @unimplemented @vanilla @op clone0(WorldCommandSender sender, Position begin, Position end, Position destination, MaskMode maskMode=MaskMode.replace, CloneMode cloneMode=CloneMode.normal) {} 216 217 @unimplemented @vanilla clone0(WorldCommandSender sender, Position begin, Position end, Position destination, SingleEnum!"filtered" maskMode, CloneMode cloneMode, string tileName) {} 218 219 // defaultgamemode 220 221 @unimplemented @vanilla @op defaultgamemode0(WorldCommandSender sender, Gamemode gamemode) {} 222 223 // deop 224 225 @vanilla @op deop0(WorldCommandSender sender, Player player) { 226 if(player.permissionLevel <= PermissionLevel.operator) { 227 if(player.operator) { 228 player.operator = false; 229 player.sendMessage(Translation(Messages.deop.message)); 230 } 231 sender.sendMessage(Translation(Messages.deop.success, player.displayName)); 232 } else { 233 sender.sendMessage(Translation(Messages.deop.failed, player.displayName)); 234 } 235 } 236 237 @vanilla deop1(ServerCommandSender sender, string player) { 238 executeOnPlayers(sender, player, (shared PlayerInfo info){ 239 if(info.permissionLevel <= PermissionLevel.operator) { 240 if(info.permissionLevel == PermissionLevel.operator) { 241 sender.server.updatePlayerPermissionLevel(info, PermissionLevel.user); 242 //TODO send message to the player 243 } 244 sender.sendMessage(Translation(Messages.deop.success, info.displayName)); 245 } else { 246 sender.sendMessage(Format.red, Translation(Messages.deop.failed, info.displayName)); 247 } 248 }); 249 } 250 251 // difficulty 252 253 @vanilla @op difficulty0(WorldCommandSender sender, Difficulty difficulty) { 254 sender.world.difficulty = difficulty; 255 sender.sendMessage(Translation(Messages.difficulty.success, difficulty)); 256 } 257 258 @vanilla difficulty1(WorldCommandSender sender, Ranged!(ubyte, 0, 3) difficulty) { 259 this.difficulty0(sender, cast(Difficulty)difficulty.value); 260 } 261 262 @vanilla difficulty2(ServerCommandSender sender, string world, Difficulty difficulty) { 263 executeOnWorlds(sender, world, (shared WorldInfo info){ 264 sender.server.updateWorldDifficulty(info, difficulty); 265 sender.sendMessage(Translation(Messages.difficulty.success, difficulty)); 266 }); 267 268 } 269 270 @vanilla difficulty3(ServerCommandSender sender, string world, Ranged!(ubyte, 0, 3) difficulty) { 271 this.difficulty2(sender, world, cast(Difficulty)difficulty.value); 272 } 273 274 // effect 275 276 @unimplemented @vanilla @op effect0(WorldCommandSender sender, SingleEnum!"clear" clear, Entity[] target) {} 277 278 @unimplemented @vanilla effect1(WorldCommandSender sender, SingleEnum!"clear" clear, Entity[] target, SnakeCaseEnum!Effects effect) {} 279 280 alias Duration = Ranged!(uint, 0, 1_000_000); 281 282 @unimplemented @vanilla effect2(WorldCommandSender sender, SingleEnum!"give" give, Entity[] target, SnakeCaseEnum!Effects effect, Duration duration=Duration(30), ubyte amplifier=0, bool hideParticles=false) {} 283 284 // enchant 285 286 alias Level = Ranged!(ubyte, 1, ubyte.max); 287 288 @unimplemented @vanilla @op enchant0(WorldCommandSender sender, Player[] target, SnakeCaseEnum!Enchantments enchantment, Level level=Level(1)) {} 289 290 @vanilla enchant1(Player sender, SnakeCaseEnum!Enchantments enchantment, Level level=Level(1)) { 291 this.enchant0(sender, [sender], enchantment, level); 292 } 293 294 // experience 295 296 enum ExperienceAction { add, set } 297 298 enum ExperienceType { points, levels } 299 300 @unimplemented @vanilla @op @aliases("xp") experience0(WorldCommandSender sender, ExperienceAction action, Player[] target, uint amount, ExperienceType type=ExperienceType.levels) {} 301 302 @vanilla experience1(Player sender, ExperienceAction action, uint amount, ExperienceType type=ExperienceType.levels) { 303 this.experience0(sender, action, [sender], amount, type); 304 } 305 306 @unimplemented @vanilla experience2(WorldCommandSender sender, SingleEnum!"query" query, Player target, ExperienceType type) {} 307 308 @vanilla experience3(Player sender, SingleEnum!"query" query, ExperienceType type) { 309 this.experience2(sender, query, sender, type); 310 } 311 312 // execute 313 314 //class ExecuteCommand : WorldCommandSender {} 315 316 @unimplemented @vanilla @op execute0(WorldCommandSender sender, Entity[] origin, Position position, string command) {} 317 318 // fill 319 320 enum OldBlockHandling { destroy, hollow, keep, outline, replace } 321 322 @unimplemented @vanilla @op fill0(WorldCommandSender sender, Position from, Position to, string block, OldBlockHandling oldBlockHandling=OldBlockHandling.replace) {} 323 324 // gamemode 325 326 @vanilla @op @aliases("gm") gamemode0(WorldCommandSender sender, Gamemode gamemode, Player[] target) { 327 foreach(player ; target) { 328 player.gamemode = gamemode; 329 sender.sendMessage(Translation(Messages.gamemode.successOther, player.displayName, gamemode)); 330 } 331 } 332 333 @vanilla gamemode1(Player sender, Gamemode gamemode) { 334 sender.gamemode = gamemode; 335 sender.sendMessage(Translation(Messages.gamemode.successSelf, gamemode)); 336 } 337 338 @vanilla gamemode2(ServerCommandSender sender, Gamemode gamemode, string target) { 339 executeOnPlayers(sender, target, (shared PlayerInfo info){ 340 sender.server.updatePlayerGamemode(info, gamemode); 341 sender.sendMessage(Translation(Messages.gamemode.successOther, info.displayName, gamemode)); 342 }); 343 } 344 345 // gamerule 346 347 enum Gamerule { depleteHunger, doDaylightCycle, doWeatherCycle, naturalRegeneration, pvp, randomTickSpeed } 348 349 @vanilla @op gamerule0(WorldCommandSender sender) { 350 sender.sendMessage(join([__traits(allMembers, Gamerule)], ", ")); 351 } 352 353 @vanilla gamerule1(WorldCommandSender sender, Gamerule rule) { 354 //TODO 355 sender.sendMessage(rule, " = ", { 356 final switch(rule) with(Gamerule) { 357 case depleteHunger: return sender.world.depleteHunger.to!string; 358 case doDaylightCycle: return sender.world.time.cycle.to!string; 359 case doWeatherCycle: return sender.world.weather.cycle.to!string; 360 case naturalRegeneration: return sender.world.naturalRegeneration.to!string; 361 case pvp: return sender.world.pvp.to!string; 362 case randomTickSpeed: return sender.world.randomTickSpeed.to!string; 363 } 364 }()); 365 } 366 367 @vanilla gamerule2(WorldCommandSender sender, Gamerule rule, bool value) { 368 //TODO 369 switch(rule) with(Gamerule) { 370 case depleteHunger: sender.world.depleteHunger = value; break; 371 case doDaylightCycle: sender.world.time.cycle = value; break; 372 case doWeatherCycle: sender.world.weather.cycle = value; break; 373 case naturalRegeneration: sender.world.naturalRegeneration = value; break; 374 case pvp: sender.world.pvp = value; break; 375 default: 376 sender.sendMessage(Format.red, Translation(Messages.gamerule.invalidType, rule)); 377 return; 378 } 379 sender.sendMessage(Translation(Messages.gamerule.success, rule, value)); 380 } 381 382 @vanilla gamerule3(WorldCommandSender sender, Gamerule rule, Ranged!(int, 0, int.max) value) { 383 //TODO 384 switch(rule) with(Gamerule) { 385 case randomTickSpeed: sender.world.randomTickSpeed = value; break; 386 default: 387 sender.sendMessage(Format.red, Translation(Messages.gamerule.invalidType, rule)); 388 return; 389 } 390 sender.sendMessage(Translation(Messages.gamerule.success, rule, value.value)); 391 } 392 393 // give 394 395 @unimplemented @vanilla @op give0(WorldCommandSender sender, Player[] target, string item, ubyte amount=1) {} 396 397 @vanilla give1(Player sender, string item, ubyte amount=1) { 398 this.give0(sender, [sender], item, amount); 399 } 400 401 // help 402 403 @vanilla @aliases("?") help0(JavaPlayer sender, int page=1) { 404 // pocket players have the help command client-side 405 Command[] commands; 406 foreach(name, command; sender.availableCommands) { 407 sender.server.logger.log(name); 408 if(command.name == name && !command.hidden) commands ~= command; 409 } 410 sort!((a, b) => a.name < b.name)(commands); 411 immutable pages = cast(size_t)ceil(commands.length.to!float / 7); // commands.length should always be at least 1 (help command) 412 page = clamp(--page, 0, pages - 1); 413 sender.sendMessage(Format.darkGreen, Translation(Messages.help.header, page+1, pages)); 414 foreach(command ; commands[page*7..min($, (page+1)*7)]) { 415 if(command.description.type == Description.EMPTY) sender.sendMessage(command.name); 416 else if(command.description.type == Description.TEXT) sender.sendMessage(command.name, " - ", command.description.text); 417 else sender.sendMessage(command.name, " - ", Translation(command.description.translatable)); 418 } 419 sender.sendMessage(Format.green, Translation(Messages.help.footer)); 420 } 421 422 @vanilla help1(ServerCommandSender sender) { 423 Command[] commands; 424 foreach(name, command; sender.availableCommands) { 425 if(!command.hidden && name == command.name) { 426 foreach(overload ; command.overloads) { 427 if(overload.callableBy(sender)) { 428 commands ~= command; 429 break; 430 } 431 } 432 } 433 } 434 sort!((a, b) => a.name < b.name)(commands); 435 foreach(cmd ; commands) { 436 if(cmd.description.type == Description.EMPTY) sender.sendMessage(Format.yellow, cmd.name, ":"); 437 else if(cmd.description.type == Description.TEXT) sender.sendMessage(Format.yellow, cmd.description.text); 438 else sender.sendMessage(Format.yellow, Translation(cmd.description.translatable)); 439 foreach(overload ; cmd.overloads) { 440 if(overload.callableBy(sender)) { 441 sender.sendMessage("- ", cmd.name, " ", formatArg(overload)); 442 } 443 } 444 } 445 } 446 447 @vanilla help2(JavaPlayer sender, string command) { 448 this.helpImpl(sender, "/", command); 449 } 450 451 @vanilla help3(ServerCommandSender sender, string command) { 452 this.helpImpl(sender, "", command); 453 } 454 455 private void helpImpl(CommandSender sender, string slash, string command) { 456 auto cmd = command in sender.availableCommands; 457 if(cmd) { 458 string[] messages; 459 foreach(overload ; cmd.overloads) { 460 if(overload.callableBy(sender)) { 461 messages ~= ("- " ~ slash ~ cmd.name ~ " " ~ formatArg(overload)); 462 } 463 } 464 if(messages.length) { 465 if(cmd.aliases.length) { 466 sender.sendMessage(Format.yellow, Translation(Messages.help.commandAliases, cmd.name, cmd.aliases.join(", "))); 467 } else { 468 sender.sendMessage(Format.yellow ~ cmd.name ~ ":"); 469 } 470 if(cmd.description.type == Description.TEXT) { 471 sender.sendMessage(Format.yellow, cmd.description.text); 472 } else if(cmd.description.type == Description.TRANSLATABLE) { 473 sender.sendMessage(Format.yellow, Translation(cmd.description.translatable)); 474 } 475 sender.sendMessage(Translation(Messages.generic.usage, "")); 476 foreach(message ; messages) { 477 sender.sendMessage(message); 478 } 479 return; 480 } 481 } 482 sender.sendMessage(Format.red, Translation(Messages.generic.invalidParameter, command)); 483 } 484 485 // kick 486 487 @vanilla @op kick0(WorldCommandSender sender, Player[] target, string message) { 488 string[] kicked; 489 foreach(player ; target) { 490 player.kick(message); 491 kicked ~= player.displayName; 492 } 493 sender.sendMessage(Translation(Messages.kick.successReason, kicked.join(", "), message)); 494 } 495 496 @vanilla kick1(WorldCommandSender sender, Player[] target) { 497 string[] kicked; 498 foreach(player ; target) { 499 player.kick(); 500 kicked ~= player.name; 501 } 502 sender.sendMessage(Translation(Messages.kick.success, kicked.join(", "))); 503 } 504 505 @vanilla kick2(ServerCommandSender sender, string player, string message) { 506 executeOnPlayers(sender, player, (shared PlayerInfo info){ 507 sender.server.kick(info.hubId, message); 508 sender.sendMessage(Translation(Messages.kick.successReason, info.displayName, message)); 509 }); 510 } 511 512 @vanilla kick3(ServerCommandSender sender, string player) { 513 executeOnPlayers(sender, player, (shared PlayerInfo info){ 514 server.kick(info.hubId, "disconnect.closed", []); 515 sender.sendMessage(Translation(Messages.kick.success, info.displayName)); 516 }); 517 } 518 519 // kill 520 521 @unimplemented @vanilla @op kill0(WorldCommandSender sender, Entity[] target) {} 522 523 @vanilla kill1(Player sender) { 524 this.kill0(sender, [sender]); 525 } 526 527 // list 528 529 @vanilla list0(CommandSender sender) { 530 // list players on the current node 531 sender.sendMessage(Translation(Messages.list.players, sender.server.online, sender.server.max)); 532 if(sender.server.online) { 533 string[] names; 534 foreach(player ; server.players) { 535 names ~= player.displayName; 536 } 537 sender.sendMessage(names.join(", ")); 538 } 539 } 540 541 // locate 542 543 enum StructureType { endcity, fortress, mansion, mineshaft, monument, stronghold, temple, village } 544 545 @unimplemented @vanilla @op locate0(WorldCommandSender sender, StructureType structureType) {} 546 547 // me 548 549 @vanilla me0(Player sender, string message) { 550 //TODO replace target selectors with names 551 sender.world.broadcast("* " ~ sender.displayName ~ Format.reset ~ " " ~ unformat(message)); 552 } 553 554 // op 555 556 @vanilla @op op0(WorldCommandSender sender, Player player) { 557 if(!player.operator) { 558 player.operator = true; 559 player.sendMessage(Translation(Messages.op.message)); 560 sender.sendMessage(Translation(Messages.op.success, player.displayName)); 561 } else { 562 sender.sendMessage(Format.red, Translation(Messages.op.failed, player.displayName)); 563 } 564 } 565 566 @vanilla op1(ServerCommandSender sender, string player) { 567 executeOnPlayers(sender, player, (shared PlayerInfo info){ 568 if(info.permissionLevel < PermissionLevel.operator) { 569 sender.server.updatePlayerPermissionLevel(info, PermissionLevel.operator); 570 //TODO send message to the player 571 sender.sendMessage(Translation(Messages.op.success, info.displayName)); 572 } else { 573 sender.sendMessage(Format.red, Translation(Messages.op.failed, info.displayName)); 574 } 575 }); 576 } 577 578 // permission 579 580 enum PermissionAction { grant, revoke } 581 582 @unimplemented @op permission0(WorldCommandSender sender, PermissionAction action, Player[] target, string permission) {} 583 584 @unimplemented void permission1(WorldCommandSender sender, SingleEnum!"list" list, Player target) {} 585 586 @unimplemented void permission2(ServerCommandSender sender, PermissionAction action, string target, string permission) {} 587 588 // say 589 590 @vanilla @op say0(WorldCommandSender sender, string message) { 591 auto player = cast(Player)sender; 592 immutable name = player is null ? "@" : player.displayName ~ Format.reset; 593 //TODO convert targets into strings 594 sender.world.broadcast("[" ~ name ~ "] " ~ message); //TODO unformat 595 } 596 597 @vanilla say1(ServerCommandSender sender, string message) { 598 sender.server.broadcast("[@] " ~ message); 599 } 600 601 // seed 602 603 @vanilla @op seed0(WorldCommandSender sender) { 604 sender.sendMessage(Translation(Messages.seed.success, sender.world.seed)); 605 } 606 607 // setmaxplayers 608 609 @vanilla @op setmaxplayers0(CommandSender sender, uint players) { 610 sender.server.max = players; 611 sender.sendMessage(Translation(Messages.setmaxplayers.success, players)); 612 } 613 614 // setworldspawn 615 616 @unimplemented @vanilla @op setworldspawn0(WorldCommandSender sender, Position position) {} 617 618 @vanilla setworldspawn1(WorldCommandSender sender) { 619 this.setworldspawn0(sender, Position(Position.Point(true, sender.position.x), Position.Point(true, sender.position.y), Position.Point(true, sender.position.z))); 620 } 621 622 // spawnpoint 623 624 @unimplemented @vanilla @op spawnpoint0(WorldCommandSender sender, Player[] target, Position position) {} 625 626 @vanilla spawnpoint1(WorldCommandSender sender, Player[] target) { 627 this.spawnpoint0(sender, target, Position(Position.Point(true, sender.position.x), Position.Point(true, sender.position.y), Position.Point(true, sender.position.z))); 628 } 629 630 @vanilla spawnpoint2(Player sender) { 631 this.spawnpoint1(sender, [sender]); 632 } 633 634 // spreadplayers 635 636 //TODO implement Rotation 637 //@unimplemented @vanilla @op spreadplayers0(WorldCommandSender sender, Rotation x, Rotation z, double spreadDistance, double maxRange, Entity[] target) {} 638 639 // stop 640 641 @vanilla @op stop0(CommandSender sender, bool gracefully=true) { 642 if(gracefully) { 643 if(isServerRunning) { 644 sender.sendMessage(Translation(Messages.stop.start)); 645 this.server.shutdown(); 646 } else { 647 sender.sendMessage(Format.red, Translation(Messages.stop.failed)); 648 } 649 } else { 650 import std.c.stdlib : exit; 651 exit(0); 652 } 653 } 654 655 // summon 656 657 @unimplemented @vanilla @op summon0(WorldCommandSender sender, string entityType, Position position) {} 658 659 @unimplemented @vanilla summon1(WorldCommandSender sender, string entityType) {} 660 661 // tell 662 663 @vanilla @aliases("msg", "w") tell0(Player sender, Player[] recipient, string message) { 664 string[] sent; 665 foreach(player ; recipient) { 666 if(player.id != sender.id) { 667 player.sendMessage(Format.italic, Translation(Messages.message.incoming, sender.displayName, message)); 668 sent ~= player.displayName; 669 } 670 } 671 if(sent.length) sender.sendMessage(Format.italic, Translation(Messages.message.outcoming, sent.join(", "), message)); 672 else sender.sendMessage(Format.red, Translation(Messages.message.sameTarget)); 673 } 674 675 @vanilla @op time0(WorldCommandSender sender, SingleEnum!"add" add, uint amount) { 676 uint time = sender.world.time.time + amount; 677 if(time >= 24000) sender.world.time.day += time / 24000; 678 sender.world.time.time = time; 679 sender.sendMessage(Translation(Messages.time.added, amount)); 680 } 681 682 // time 683 684 enum TimeQuery { day, daytime, gametime } 685 686 @vanilla @op time1(WorldCommandSender sender, SingleEnum!"query" query, TimeQuery time) { 687 final switch(time) with(TimeQuery) { 688 case day: 689 sender.sendMessage(Translation(Messages.time.queryDay, sender.world.time.day)); 690 break; 691 case daytime: 692 sender.sendMessage(Translation(Messages.time.queryDaytime, sender.world.time.time)); 693 break; 694 case gametime: 695 sender.sendMessage(Translation(Messages.time.queryGametime, sender.world.ticks)); 696 break; 697 } 698 } 699 700 @vanilla @op time2(WorldCommandSender sender, SingleEnum!"set" set, uint amount) { 701 sender.sendMessage(Translation(Messages.time.set, (sender.world.time.time = amount))); 702 } 703 704 @vanilla @op time3(WorldCommandSender sender, SingleEnum!"set" set, Time amount) { 705 this.time2(sender, set, cast(uint)amount); 706 } 707 708 // title 709 710 @vanilla @op title0(WorldCommandSender sender, Player[] target, SingleEnum!"clear" clear) { 711 foreach(player ; target) player.clearTitle(); 712 //TODO send message 713 } 714 715 @vanilla title1(WorldCommandSender sender, Player[] target, SingleEnum!"reset" reset) { 716 foreach(player ; target) player.resetTitle(); 717 //TODO send message 718 } 719 720 @unimplemented @vanilla title2(WorldCommandSender sender, Player[] target, SingleEnum!"title" title, string text) {} 721 722 @unimplemented @vanilla title3(WorldCommandSender sender, Player[] target, SingleEnum!"subtitle" subtitle, string text) {} 723 724 @unimplemented @vanilla title4(WorldCommandSender sender, Player[] target, SingleEnum!"actionbar" actionbar, string text) { 725 foreach(player ; target) player.sendTip(text); 726 //TODO send message 727 } 728 729 @unimplemented @vanilla title5(WorldCommandSender sender, Player[] target, SingleEnum!"times" times, uint fadeIn, uint stay, uint fadeOut) {} 730 731 // toggledownfall 732 733 @vanilla @op toggledownfall0(WorldCommandSender sender) { 734 if(sender.world.weather.raining) sender.world.weather.clear(); 735 else sender.world.weather.start(); 736 sender.sendMessage(Translation(Messages.toggledownfall.success)); 737 } 738 739 // tp 740 741 @vanilla @op @permission("minecraft:teleport") @aliases("teleport") tp0(Player sender, Entity destination) { 742 this.tp2(sender, [sender], destination); 743 } 744 745 @vanilla tp1(Player sender, Position destination) { 746 this.tp3(sender, [sender], destination); 747 } 748 749 @unimplemented @vanilla tp2(WorldCommandSender sender, Entity[] victim, Entity destination) {} 750 751 @unimplemented @vanilla tp3(WorldCommandSender sender, Entity[] victim, Position destination) {} 752 753 // transfer 754 755 @unimplemented @op transfer0(WorldCommandSender sender, Player[] target, string node) {} 756 757 @unimplemented @op transfer1(ServerCommandSender sender, string target, string node) {} 758 759 // transferserver 760 761 @vanilla @op transferserver0(Player sender, string ip, int port=19132) { 762 immutable _port = cast(ushort)port; 763 if(_port == port) { 764 try { 765 sender.transfer(ip, _port); 766 } catch(Exception) {} 767 } else { 768 sender.sendMessage(Format.red, Translation(Messages.transferserver.invalidPort)); 769 } 770 } 771 772 @vanilla @op transferserver1(WorldCommandSender sender, Player[] target, string ip, int port=19132) { 773 immutable _port = cast(ushort)port; 774 if(_port == port) { 775 bool success = false; 776 foreach(player ; target) { 777 try { 778 player.transfer(ip, _port); 779 success = true; 780 } catch(Exception) {} 781 } 782 if(success) sender.sendMessage(Translation(Messages.transferserver.success)); 783 } else { 784 sender.sendMessage(Format.red, Translation(Messages.transferserver.invalidPort)); 785 } 786 } 787 788 // weather 789 790 enum Weather { clear, rain, thunder } 791 792 @vanilla @op weather0(WorldCommandSender sender, Weather type, int duration=0) { 793 if(type == Weather.clear) { 794 if(duration <= 0) sender.world.weather.clear(); 795 else sender.world.weather.clear(duration); 796 sender.sendMessage(Translation(Messages.weather.clear)); 797 } else { 798 if(duration <= 0 || duration > 1_000_000) duration = uniform!"[]"(6000, 18000, sender.world.random); 799 if(type == Weather.rain) { 800 sender.world.weather.start(duration, false); 801 sender.sendMessage(Translation(Messages.weather.rain)); 802 } else { 803 sender.world.weather.start(duration, true); 804 sender.sendMessage(Translation(Messages.weather.thunder)); 805 } 806 } 807 } 808 809 // world 810 811 void world0(CommandSender sender, SingleEnum!"list" list) { 812 string[] names; 813 foreach(world ; sender.server.worlds) names ~= world.name; 814 sender.sendMessage(Translation("commands.world.list", names.length, names.join(", "))); 815 } 816 817 @op world1(CommandSender sender, SingleEnum!"add" add, string name, bool defaultWorld=false) { 818 auto world = sender.server.addWorld(name); 819 if(world) { 820 sender.sendMessage(Translation("commands.world.add.success")); 821 if(defaultWorld) sender.server.defaultWorld = world; 822 } else { 823 sender.sendMessage(Format.red, Translation("commands.world.add.failed")); 824 } 825 } 826 827 void world2(CommandSender sender, SingleEnum!"remove" remove, string name) { 828 executeOnWorlds(sender, name, (shared WorldInfo info){ 829 if(sender.server.removeWorld(info.id)) sender.sendMessage(Translation("commands.world.remove.success")); 830 }); 831 } 832 833 @unimplemented void world3(CommandSender sender, SingleEnum!"info" info, string name) {} 834 835 } 836 837 string convertName(string command, string replacement=" ") { 838 string ret; 839 foreach(c ; command) { 840 if(c >= 'A' && c <= 'Z') ret ~= replacement ~ cast(char)(c + 32); 841 else ret ~= c; 842 } 843 return ret; 844 } 845 846 private enum convertedName(string command) = convertName(command); 847 848 private string[] formatArgs(Command command, CommandSender sender) { 849 string[] ret; 850 foreach(overload ; command.overloads) { 851 if(overload.callableBy(sender)) ret ~= formatArg(overload); 852 } 853 return ret; 854 } 855 856 private string formatArg(Command.Overload overload) { 857 string[] p; 858 foreach(i, param; overload.params) { 859 immutable enum_ = overload.pocketTypeOf(i) == PocketType.stringenum; 860 if(enum_ && overload.enumMembers(i).length == 1) { 861 p ~= overload.enumMembers(i)[0]; 862 } else { 863 string full = enum_ && overload.enumMembers(i).length < 5 ? overload.enumMembers(i).join("|") : (param ~ ": " ~ overload.typeOf(i)); 864 if(i < overload.requiredArgs) { 865 p ~= "<" ~ full ~ ">"; 866 } else { 867 p ~= "[" ~ full ~ "]"; 868 } 869 } 870 } 871 return p.join(" "); 872 } 873 874 private void executeOnWorlds(CommandSender sender, string name, void delegate(shared WorldInfo) del) { 875 auto world = sender.server.getWorldByName(name); 876 if(world !is null) { 877 del(world); 878 } else { 879 sender.sendMessage(Format.red, Translation("commands.world.notFound", name)); 880 } 881 } 882 883 private void executeOnPlayers(CommandSender sender, string name, void delegate(shared PlayerInfo) del) { 884 if(name.startsWith("@")) { 885 if(name == "@a" || name == "@r") { 886 auto players = sender.server.players; 887 if(players.length) { 888 final switch(name) { 889 case "@a": 890 foreach(player ; sender.server.players) { 891 del(player); 892 } 893 break; 894 case "@r": 895 del(players[uniform(0, $)]); 896 break; 897 } 898 } else { 899 sender.sendMessage(Format.red, Translation(Messages.generic.targetNotFound)); 900 } 901 } else { 902 sender.sendMessage(Format.red, Translation(Messages.generic.invalidSyntax)); 903 } 904 } else { 905 immutable iname = name.toLower(); 906 bool executed = false; 907 foreach(player ; sender.server.players) { 908 if(player.lname == iname) { 909 executed = true; 910 del(player); 911 } 912 } 913 if(!executed) sender.sendMessage(Format.red, Translation(Messages.generic.playerNotFound, name)); 914 } 915 }