From 4e552e8a0bf89e642fb14dfaa12e7689dcff3204 Mon Sep 17 00:00:00 2001 From: Alejandro Liu Date: Sat, 2 Jul 2016 09:19:44 +0200 Subject: [PATCH] 1.3.4 --- README.md | 140 ++++- plugin.yml | 67 +-- src/ManyWorlds/Main.php | 217 -------- src/aliuly/manyworlds/Main.php | 628 ++++++++++++++++++++++ src/aliuly/manyworlds/TeleportManager.php | 97 ++++ 5 files changed, 892 insertions(+), 257 deletions(-) delete mode 100644 src/ManyWorlds/Main.php create mode 100644 src/aliuly/manyworlds/Main.php create mode 100644 src/aliuly/manyworlds/TeleportManager.php diff --git a/README.md b/README.md index 1508763..5d9ef8e 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,27 @@ ManyWorlds * Plugin Access: Commands, Manages Worlds * WebSite: [github](https://github.com/alejandroliu/pocketmine-plugins/tree/master/ManyWorlds) +Overview +--------- + +A very basic plugin implementing MultiWorld functionality + +Features: -A very basic Plugin implementing Multiworld functionality. +* teleport +* load/unload +* create +* world info Basic Usage: * /mw tp *level* [player] * /mw create *level* [seed [flat|normal [preset]]] * /mw load *level* +* /mw unload [-f] *level* * /mw ls [level] +* /mw lvdat [level] [attr=value] +* /mw fixname [level] Documentation ------------- @@ -25,19 +37,82 @@ Documentation This plugin is a world manager that allows you to generate and load worlds as well as teleport between worlds. +The teleport itself has a number of workarounds to deal with +Client-Server glitches. Essentially, it works for me. + ### Commands: +Teleporting: + * mw tp *level* [player] Teleports `player` to `level`. If no `player` is specified, it teleports the current user. +* mw ls [level] + If `level` is not specified, it will list all available worlds. If + `level` is specified, it will provide details on that `level`. + +World management: + * mw create *level* [seed] [flat|normal] [preset] Creates a world named `level`. You can optionally specify a `seed` as number, the generator (`flat` or `normal`) and a `preset` string. * mw load *level* - Loads `level` directly. -* mw ls [level] - If `level` is not specified, it will list all available worlds. If - `level` is specified, it will provide details on that `level`. + Loads `level` directly. If you use `--all` for the `level` name, it + will load all worlds. +* mw unload *level* + Unloads `level`. + +Format hacking: + +* mw fixname *level* + Fixes `level.dat` files so that the name matches the folder name. +* mw lvdat *attr=value* *attr=value* + Change directly some `level.dat` values/attributes. Supported + attributes: + * spawn=x,y,z : Sets spawn point + * seed=randomseed : seed used for terrain generation + * name=string : Level name + * generator=flat|normal : Terrain generator + * preset=string : Presets string. + +### Examples: + +Create a new normal world: + + /mw create overworld 711 normal + +Create a new flat world: + + /mw create flatland 404 flat 2;7,59x1,3x3,2;1; + +Teleport to this newly created world: + + /mw tp flatland + +Teleport a player to another world: + + /mw tp flatland joshua + +### Configuration + +In the plugin's config.yml file you can have: + + settings: + broadcast-tp: true + +* `broadcast-tp`: Controls broadcast message that somebody teleported. + +### API + +To use the teleport provided by ManyWorlds, you can use this code: + + if (($mw = $this->getServer()->getPluginManager()->getPlugin("ManyWorlds")) != null) { + $mw->mwtp($player,$pos); + } else { + $player->teleport($pos); + } + +You need to do this in order for WorldProtect limits to work. ### Permission Nodes: @@ -45,25 +120,71 @@ worlds as well as teleport between worlds. * mw.cmd.tp.others - Allows users to make others travel to other worlds * mw.cmd.world.create - Allows users to create worlds * mw.cmd.world.load - Allows users to load worlds +* mw.cmd.lvdat - Manipulate level data FAQ --- -* Q: Creating a world using `generator` doesn't work. -* A: PocketMine-MP has a bug in the `Server->generateLevel` method - where specifying a `generator` is called incorrectly. +* Q: How do I create a `FLAT` world? +* A: You must be using PocketMine-MP v1.4.1. Set the `generator` to + `flat`. +* Q: How do I load multiple worlds on start-up? + A: That functionality is provided by PocketMine-MP core by default. + In the `pocketmine.yml` file there is a `worlds` section where you + can define which worlds to load on start-up. +Issues +------ + +* World names can not contain spaces. Changes ------- +* 1.3.4: Updates for PM1.5 + * Removed CallbackTask deprecation warnings +* 1.3.3: Updates for PM1.5 + * Minor cosmetic changes. + * Changes default canUnload to true if running on PM1.5 + * Fixed use of WorldProtect limits + TeleportMgr +* 1.3.2: API update + * Allow WorldProtect to call our commands. + * Simplified API and updated its documentation +* 1.3.1: + * Fixed a bug around not show who was teleported for 3rd party + teleport + * Fixed all bugs reported by [Crash Archive](http://crash.pocketmine.net/) +* 1.3.0: Level.dat hacking. + * Added `lvdat` command to change `level.dat` settings. + * Added `fixname` command to fix `levelName` vs. `foldername` + mismatches. + * Fixed critical error for teleport! +* 1.2.0: Clean-ups + * Added a setting to control if to broadcast when people teleport. + * Removed per-level `motd.txt`. + * Code clean-up + * Teleport functionality encapsulated in TeleportManager. + * Added workaround to remove TileEntities that linger on when teleporting. +* 1.1.0: + * Show better help messages. + * Added world unload. May cause core dumps. + * `ls` sub-command improvements: + * paginated output + * show number of players, autoloading and default status. + * Per-level `motd.txt`. Worlds can contain a small `motd.txt` text + file that will be displayed when the player enters or when they + type the `/motd` command. + * Workaround teleport glitches, with a minimal API to let other + plugins use this. + * Added `loadall` functionality. + * BugFix: given an invalid player name to teleport would crash server. * 1.0.0 : Initial release Copyright --------- ManyWorlds - Copyright (C) 2015 Alejandro Liu + Copyright (C) 2015 Alejandro Liu All Rights Reserved. This program is free software: you can redistribute it and/or modify @@ -78,4 +199,3 @@ Copyright You should have received a copy of the GNU General Public License along with this program. If not, see . - diff --git a/plugin.yml b/plugin.yml index 2f5e50d..b442ce0 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,30 +1,37 @@ -main: ManyWorlds\Main -api: 1.10.0 -load: POSTWORLD - -name: ManyWorlds -description: Manage Multiple Worlds -version: 1.0.0 -author: aliuly - -commands: - mw: - description: Manage worlds - usage: "/mw " - -permissions: - mw.cmd.tp: - default: op - description: "Allows users to travel to other worlds" - mw.cmd.tp.others: - default: op - description: "Allows users to make others travel to other worlds" - mw.cmd.world.create: - default: op - description: "Allows users to create worlds" - mw.cmd.world.load: - default: op - description: "Allows users to load worlds" - mw.cmd.world.ls: - default: op - description: "Allows users to list worlds" \ No newline at end of file +main: aliuly\manyworlds\Main +api: 1.10.0 +load: POSTWORLD + +name: ManyWorlds +description: Manage Multiple Worlds +version: 1.3.4 +author: aliuly + +commands: + mw: + description: Manage worlds + usage: "/mw [options]" + permission: mw.cmds + +permissions: + mw.cmds: + default: true + description: "Allow all the ManyWorlds functionality" + mw.cmd.tp: + default: op + description: "Allows users to travel to other worlds" + mw.cmd.tp.others: + default: op + description: "Allows users to make others travel to other worlds" + mw.cmd.ls: + default: op + description: "Allows users to list worlds" + mw.cmd.world.create: + default: op + description: "Allows users to create worlds" + mw.cmd.world.load: + default: op + description: "Allows users to load worlds" + mw.cmd.lvdat: + default: op + description: "Manipulate level.dat" diff --git a/src/ManyWorlds/Main.php b/src/ManyWorlds/Main.php deleted file mode 100644 index 18bd2a7..0000000 --- a/src/ManyWorlds/Main.php +++ /dev/null @@ -1,217 +0,0 @@ -getLogger()->info("ManyWorlds Loaded!"); - } - public function onCommand(CommandSender $sender, Command $cmd, $label, array $args) { - switch($cmd->getName()) { - case "mw": - if(isset($args[0])) { - $scmd = strtolower($args[0]); - switch ($scmd) { - case "tp": - return $this->mwTeleportCommand($sender,$args); - case "create": - return $this->mwWorldCreateCommand($sender,$args); - case "load": - case "ld": - return $this->mwWorldLoadCommand($sender,$args); - case "ls": - case "list": - return $this->mwWorldListCommand($sender,$args); - default: - $sender->sendMessage("Unknown sub command: $args[0]"); - return true; - } - } else { - $sender->sendMessage("Must specify sub command"); - return true; - } - break; - } - return false; - } - private function mwAutoLoad(CommandSender $c,$level) { - if (!$this->getServer()->isLevelLoaded($level)) { - if(!$this->checkPermission($c, "mw.cmd.world.load")) { - $c->sendMessage("[MW] $level is not loaded."); - return false; - } - if(!$this->getServer()->isLevelGenerated($level)) { - $c->sendMessage("[MW] No level with the name $level exists!"); - return false; - } - $c->sendMessage("[MW] Loading $level..."); - $this->getServer()->loadLevel($level); - } - return true; - } - - private function mwTeleportCommand(CommandSender $sender,array $args) { - if (!isset($args[1])) { - $sender->sendMessage("[MW] must specify level"); - return true; - } - - $level = $args[1]; - - if (isset($args[2])) { - // Teleport others - $player = $this->getServer()->getPlayer($args[2]); - if($this->checkPermission($sender, "universe.cmd.tp.others")) { - if ($player->isOnline()) { - if($player->getLevel() == $this->getServer()->getLevelByName($level)) { - $sender->sendMessage("[MW] " . $player . " is already in " . $level . "!"); - } else { - if ($this->mwAutoLoad($sender,$level)) { - $player->sendMessage("[MW] Teleporting you to " . $level . " at\n" . $sender->getName() . "'s request..."); - $world = $this->getServer()->getLevelByName($level); - $player->teleport($world->getSafeSpawn()); - $sender->sendMessage("[MW] " . $player . " has been teleported to " . $level . "!"); - } else { - $sender->sendMessage("[MW] Unable to teleport " . $player . " as\nlevel " . $level . " is not loaded!"); - } - } - } else { - $sender->sendMessage("[MW] ".$player." is offline!"); - } - } else { - return $this->permissionFail($sender); - } - } else { - // Teleport self - if ($sender instanceof Player) { - if ($this->checkPermission($sender,"mw.cmd.tp")) { - if(!($sender->getLevel() == $this->getServer()->getLevelByName($level))) { - if($this->mwAutoLoad($sender,$level)) { - $sender->sendMessage("[MW] Teleporting you to level " . $level . "..."); - $world = $this->getServer()->getLevelByName($level); - $sender->teleport($world->getSpawnLocation()); - $this->getServer()->broadcastMessage("[MW] ".$sender->getName()." teleported to $level"); - } else { - $sender->sendMessage("[MW] Unable to teleport you to " . $level . " as it\nis not loaded!"); - } - } else { - $sender->sendMessage("[Universe] You are already in " . $level . "!"); - } - } else { - return $this->permissionFail($sender); - } - } else { - $sender->sendMessage("[MW] This command may only be used in-game"); - } - } - return true; - } - private function mwWorldCreateCommand(CommandSender $sender, array $args) { - if(!isset($args[1])) { - $sender->sendMessage("[MW] Usage: create level seed generator options"); - return true; - } - if(!$this->checkPermission($sender, "mw.cmd.world.create")) { - return $this->permissionFail($sender); - } - $level = $args[1]; - if($this->getServer()->isLevelGenerated($level)) { - $sender->sendMessage("[MW] A world with the name " . $level . " already exists!"); - return true; - } - $seed = null; - $generator = null; - $options = []; - if(isset($args[2])) $seed = intval($args[2]); - if(isset($args[3])) { - $generator = Generator::getGenerator($args[3]); - } - if(isset($args[4])) $options = ["preset" => $args[4] ]; - $this->getServer()->broadcastMessage("[MW] Creating level " . $level . "... (Expect Lag)"); - $this->getServer()->generateLevel($level, $seed, $generator, $options); - $this->getServer()->loadLevel($level); - return true; - } - private function mwWorldLoadCommand(CommandSender $sender, array $args) { - if(!isset($args[1])) { - $sender->sendMessage("[MW] Must specify level name"); - return true; - } - $level = $args[1]; - if (!$this->mwAutoLoad($sender,$level)) { - $sender->sendMessage("[MW] Unable to load $level"); - } - return true; - } - private function mwWorldListCommand(CommandSender $sender, array $args) { - if(!$this->checkPermission($sender, "mw.cmd.world.ls")) { - return $this->permissionFail($sender); - } - if (isset($args[1])) { - $level = $args[1]; - if (!$this->mwAutoLoad($sender,$level)) { - $sender->sendMessage("[MW] Unable to load $level"); - return true; - } - $level = $this->getServer()->getLevelByName($level); - if (!$level) { - $sender->sendMessage("[MW] $level not loaded"); - return true; - } - //==== provider - $provider = $level->getProvider(); - $sender->sendMessage("Provider: ". get_class($provider)); - $sender->sendMessage("Path: ".$provider->getPath()); - $sender->sendMessage("Name: ".$provider->getName()); - $sender->sendMessage("Seed: ".$provider->getSeed()); - $sender->sendMessage("Generator: ".$provider->getGenerator()); - $sender->sendMessage("Generator Options: ".print_r($provider->getGeneratorOptions(),true));; - $spawn = $provider->getSpawn(); - $sender->sendMessage("Spawn: ".$spawn->getX().",".$spawn->getY().",".$spawn->getZ()); - return true; - } - - $dir = $this->getServer()->getDataPath(). "worlds"; - if (!is_dir($dir)) { - $sender->sendMessage("[MW] Missing path $dir"); - return true; - } - $dh = opendir($dir); - if (!$dh) return true; - while (($file = readdir($dh)) !== false) { - if ($file == '.' || $file == '..') continue; - if ($this->getServer()->isLevelLoaded($file)) { - $sender->sendMessage("- $file (loaded)"); - continue; - } - if ($this->getServer()->isLevelGenerated($file)) { - $sender->sendMessage("- $file"); - continue; - } - } - closedir($dh); - return true; - } - private function checkPermission(CommandSender $sender, $permission) { - if($sender->hasPermission($permission)) return true; - return false; - } - private function permissionFail(CommandSender $sender) { - $sender->sendMessage("You do not have permission to do that."); - return true; - } - public function onDisable() { - $this->getLogger()->info("ManyWorlds Unloaded!"); - } -} diff --git a/src/aliuly/manyworlds/Main.php b/src/aliuly/manyworlds/Main.php new file mode 100644 index 0000000..152170a --- /dev/null +++ b/src/aliuly/manyworlds/Main.php @@ -0,0 +1,628 @@ + "ls", + "load" => "ld", + ]; + + // Access and other permission related checks + private function access(CommandSender $sender, $permission) { + if($sender->hasPermission($permission)) return true; + $sender->sendMessage("You do not have permission to do that."); + return false; + } + private function inGame(CommandSender $sender,$msg = true) { + if ($sender instanceof Player) return true; + if ($msg) $sender->sendMessage("You can only use this command in-game"); + return false; + } + + // Paginate output + private function getPageNumber(array &$args) { + $pageNumber = 1; + if (count($args) && is_numeric($args[count($args)-1])) { + $pageNumber = (int)array_pop($args); + if($pageNumber <= 0) $pageNumber = 1; + } + return $pageNumber; + } + private function paginateText(CommandSender $sender,$pageNumber,array $txt) { + $hdr = array_shift($txt); + if($sender instanceof ConsoleCommandSender){ + $sender->sendMessage( TextFormat::GREEN.$hdr.TextFormat::RESET); + foreach ($txt as $ln) $sender->sendMessage($ln); + return true; + } + $pageHeight = 5; + $hdr = TextFormat::GREEN.$hdr. TextFormat::RESET; + if (($pageNumber-1) * $pageHeight >= count($txt)) { + $sender->sendMessage($hdr); + $sender->sendMessage("Only ".intval(count($txt)/$pageHeight+1)." pages available"); + return true; + } + $hdr .= TextFormat::RED." ($pageNumber of ".intval(count($txt)/$pageHeight+1).")".TextFormat::RESET; + $sender->sendMessage($hdr); + for ($ln = ($pageNumber-1)*$pageHeight;$ln < count($txt) && $pageHeight--;++$ln) { + $sender->sendMessage($txt[$ln]); + } + return true; + } + private function paginateTable(CommandSender $sender,$pageNumber,array $tab) { + $cols = []; + for($i=0;$i < count($tab[0]);$i++) $cols[$i] = strlen($tab[0][$i]); + foreach ($tab as $row) { + for($i=0;$i < count($row);$i++) { + if (($l=strlen($row[$i])) > $cols[$i]) $cols[$i] = $l; + } + } + $txt = []; + foreach ($tab as $row) { + $txt[] = sprintf("%-$cols[0]s %-$cols[1]s %-$cols[2]s %-$cols[3]s", + $row[0],$row[1],$row[2],$row[3]); + } + return $this->paginateText($sender,$pageNumber,$txt); + } + // Standard call-backs + public function onEnable(){ + // Depending on the API, we allow unload by default... + $api = explode(".",$this->getServer()->getApiVersion()); + if (intval($api[1]) < 12) { + $this->canUnload = false; + $this->is15 = false; + } else { + $this->getLogger()->info("Runniong on PocketMine-MP v1.5 or better"); + $this->getLogger()->info(TextFormat::RED. + "This version is still under development"); + $this->getLogger()->info(TextFormat::RED. + "and it may not be fully stable"); + $this->canUnload = true; + $this->is15 = true; + } + + $this->tpManager = new TeleportManager($this); + if (!is_dir($this->getDataFolder())) mkdir($this->getDataFolder()); + $defaults = [ + "settings" => [ + "broadcast-tp" => true, + ], + ]; + $this->cfg = (new Config($this->getDataFolder()."config.yml", + Config::YAML,$defaults))->getAll(); + $this->maxplayers = [$this,"maxPlayers1st"]; + } + + public function onCommand(CommandSender $sender, Command $cmd, $label, array $args) { + switch($cmd->getName()) { + case "worldprotect": // Allow WP to call us... + case "mw": + if(isset($args[0])) { + $scmd = strtolower(array_shift($args)); + if (isset(self::$aliases[$scmd])) $scmd = self::$aliases[$scmd]; + switch ($scmd) { + case "tp": + if (!$this->access($sender,"mw.cmd.tp")) return false; + return $this->mwTpCmd($sender,$args); + break; + case "ls": + if (!$this->access($sender,"mw.cmd.ls")) return false; + return $this->mwLsCmd($sender,$args); + case "create": + if (!$this->access($sender,"mw.cmd.world.create")) return false; + return $this->mwWorldCreateCmd($sender,$args); + break; + case "fixname": + if (!$this->access($sender,"mw.cmd.lvdat")) return false; + if (count($args) != 1) return $this->mwHelpCmd($sender,["fixname"]); + $sender->sendMessage("Running /mw lvdat $args[0] name=$args[0]"); + return $this->mwLevelDatCmd($sender,[$args[0], "name=".$args[0]]); + break; + case "lvdat": + if (!$this->access($sender,"mw.cmd.lvdat")) return false; + return $this->mwLevelDatCmd($sender,$args); + break; + case "ld": + if (!$this->access($sender,"mw.cmd.world.load")) return false; + return $this->mwWorldLoadCmd($sender,$args); + case "unload": + if (!$this->access($sender,"mw.cmd.world.load")) return false; + return $this->mwWorldUnloadCmd($sender,$args); + case "help": + return $this->mwHelpCmd($sender,$args); + default: + $sender->sendMessage(TextFormat::RED."Unknown sub command: ". + TextFormat::RESET.$scmd); + $sender->sendMessage("Use: ".TextFormat::GREEN." /mw help". + TextFormat::RESET); + } + return true; + } else { + $sender->sendMessage("Must specify sub command"); + $sender->sendMessage("Try: ".TextFormat::GREEN." /mw help". + TextFormat::RESET); + return false; + } + } + return false; + } + // Command entry points + private function mwTpCmd(CommandSender $sender,$args) { + if (!isset($args[0])) + return $this->mwHelpCmd($sender,["tp"]); + $level = array_shift($args); + if (isset($args[0])) { + // Teleport others... + if (!$this->access($sender,"mw.cmd.tp.others")) return false; + $player = $this->getServer()->getPlayer($args[0]); + if (!$player) { + $sender->sendMessage("[MW] Player ".$args[0]." can not be found"); + return true; + } + if (!$player->isOnLine()) { + $sender->sendMessage("[MW] ".$args[0]." is offline!"); + return true; + } + if($player->getLevel() == $this->getServer()->getLevelByName($level)) { + $sender->sendMessage("[MW] " . $player->getName() . " is already in " . $level . "!"); + return true; + } + if (!$this->mwAutoLoad($sender,$level)) { + $sender->sendMessage("[MW] Unable to teleport " . $player->getName() . " as\nlevel " . $level . " is not loaded!"); + return true; + } + $player->sendMessage("[MW] Teleporting you to " . $level . " at\n" . $sender->getName() . "'s request..."); + if ($this->teleport($player,$level)) { + if ($this->cfg["settings"]["broadcast-tp"]) { + $this->getServer()->broadcastMessage("[MW] ".$player->getName()." was teleported to $level"); + } else { + $sender->sendMessage("[MW] " . $player->getName() . " has been teleported to " . $level . "!"); + } + } else { + $sender->sendMessage("[MW] unable to teleport ".$player->getName()." to ".$level); + } + return true; + } + // Teleport self... + if (!$this->inGame($sender)) return true; + if ($sender->getLevel() == $this->getServer()->getLevelByName($level)) { + $sender->sendMessage("[MW] You are already in " . $level . "!"); + return true; + } + if(!$this->mwAutoLoad($sender,$level)) { + $sender->sendMessage("[MW] Unable to teleport"); + $sender->sendMessage("[MW] " . $level . " is not loaded!"); + return true; + } + $sender->sendMessage("[MW] Teleporting you to level " . $level . "..."); + if ($this->teleport($sender,$level)) { + if ($this->cfg["settings"]["broadcast-tp"]) { + $this->getServer()->broadcastMessage("[MW] ".$sender->getName()." teleported to $level"); + } else { + $sender->sendMessage("[MW] you were teleported to $level"); + } + } else { + $sender->sendMessage("[MW] Unable to teleport ".$sender->getName()." to $level"); + } + return true; + } + private function mwLsCmd(CommandSender $sender,$args) { + $pageNumber = $this->getPageNumber($args); + if (isset($args[0])) { + if(!$this->mwAutoLoad($sender,$args[0])) { + $sender->sendMessage("[MW] " . $args[0] . " is not loaded!"); + return true; + } + $txt = $this->mwWorldDetails($sender,$args[0]); + } else { + $txt = $this->mwWorldList($sender); + } + if ($txt == null) return true; + return $this->paginateText($sender,$pageNumber,$txt); + } + private function mwLevelDatCmd(CommandSender $sender,$args) { + if (!count($args)) return $this->mwHelpCmd($sender,["lvdat"]); + $level = array_shift($args); + if(!$this->mwAutoLoad($sender,$level)) { + $sender->sendMessage("[MW] $level is not loaded!"); + return true; + } + $world = $this->getServer()->getLevelByName($level); + if (!$world) { + $sender->sendMessage("[MW] $level not loaded"); + return null; + } + //==== provider + $provider = $world->getProvider(); + $changed = false; $unload = false; + foreach ($args as $kv) { + $kv = explode("=",$kv,2); + if (count($kv) != 2) { + $sender->sendMessage("Invalid element: $kv[0], ignored"); + continue; + } + list($k,$v) = $kv; + switch ($k) { + case "spawn": + $pos = explode(",",$v); + if (count($pos)!=3) { + $sender->sendMessage("Invalid spawn location: ".implode(",",$pos)); + continue; + } + list($x,$y,$z) = $pos; + $cpos = $provider->getSpawn(); + if (($x=intval($x)) == $cpos->getX() && + ($y=intval($y)) == $cpos->getY() && + ($z=intval($z)) == $cpos->getZ()) { + $sender->sendMessage("Spawn location is unchanged"); + continue; + } + $changed = true; + $provider->setSpawn(new Vector3($x,$y,$z)); + break; + case "seed": + if ($provider->getSeed() != intval($v)) { + $sender->sendMessage("Seed unchanged"); + continue; + } + $changed = true; $unload = true; + $provider->setSeed($v); + break; + case "name": // LevelName String + if ($provider->getName() == $v) { + $sender->sendMessage("Name unchanged"); + continue; + } + $changed = true; + $provider->getLevelData()->LevelName = new String("LevelName",$v); + break; + case "generator": // generatorName(String) + if ($provider->getLevelData()->generatorName == $v) { + $sender->sendMessage("Generator unchanged"); + continue; + } + $changed=true; $unload=true; + $provider->getLevelData()->generatorName=new String("generatorName",$v); + break; + case "preset": // String("generatorOptions"); + if ($provider->getLevelData()->generatorOptions == $v) { + $sender->sendMessage("Preset unchanged"); + continue; + } + $changed=true; $unload=true; + $provider->getLevelData()->generatorOptions = + new String("generatorOptions",$v); + break; + default: + $sender->sendMessage("Unknown key $k, ignored"); + continue; + } + } + if ($changed) { + $sender->sendMessage("Updating level.dat for $level"); + $provider->saveLevelData(); + if ($unload) { + $sender->sendMessage(TextFormat::RED. + "CHANGES WILL NOT TAKE EFFECT UNTIL UNLOAD"); + } + } else { + $sender->sendMessage("Nothing happens"); + } + return true; + } + + private function mwWorldCreateCmd(CommandSender $sender,$args) { + if (!isset($args[0])) + return $this->mwHelpCmd($sender,["create"]); + $level = array_shift($args); + if($this->getServer()->isLevelGenerated($level)) { + $sender->sendMessage("[MW] A world with the name " . $level . " already exists!"); + return true; + } + $seed = null; + $generator = null; + $options = []; + if(isset($args[0])) $seed = intval($args[0]); + if(isset($args[1])) { + $generator = Generator::getGenerator($args[1]); + $sender->sendMessage("Using ".Generator::getGeneratorName($generator)); + } + if(isset($args[2])) $options = ["preset" => $args[2] ]; + $this->getServer()->broadcastMessage("[MW] Creating level " . $level . "... (Expect Lag)"); + $this->getServer()->generateLevel($level, $seed, $generator, $options); + $this->getServer()->loadLevel($level); + return true; + } + private function mwWorldLoadCmd(CommandSender $sender,$args) { + if (!isset($args[0])) + return $this->mwHelpCmd($sender,["ld"]); + if ($args[0] == "--all") { + $sender->sendMessage("[MW] ".TextFormat::RED."Loading ALL levels".TextFormat::RESET); + $args = []; + foreach (glob($this->getServer()->getDataPath(). "worlds/*") as $f) { + $level = basename($f); + if ($this->getServer()->isLevelLoaded($level)) continue; + if (!$this->getServer()->isLevelGenerated($level)) continue; + $args[] = $level; + } + } + foreach ($args as $level) { + if (!$this->mwAutoLoad($sender,$level)) + $sender->sendMessage("[MW] Unable to load $level"); + } + return true; + } + private function mwWorldUnloadCmd(CommandSender $sender,$args) { + if (!isset($args[0])) + return $this->mwHelpCmd($sender,["unload"]); + + // Activate|Deactive unload command + if (isset($args[0]) && $args[0] == '--enable') { + $this->canUnload = true; + $sender->sendMessage("[MW] Unload sub-command enabled"); + $sender->sendMessage("[MW] To disable use: /mw unload --disable"); + return true; + } elseif (isset($args[0]) && $args[0] == '--disable') { + $this->canUnload = true; + $sender->sendMessage("[MW] Unload sub-command disabled"); + $sender->sendMessage("[MW] To enable use: /mw unload --enable"); + return true; + } + if (!$this->canUnload) { + $sender->sendMessage("[MW] Unload sub-command is disabled by default"); + $sender->sendMessage("[MW] this is because that it usually causes the"); + $sender->sendMessage("[MW] server to ".TextFormat::RED."crash.".TextFormat::RESET); + $sender->sendMessage("[MW] Use: ".TextFormat::BLUE."/mw unload --enable".TextFormat::RESET); + $sender->sendMessage("[MW] To activate"); + return true; + } + + // Actual implementation + $force = false; + if (isset($args[0]) && $args[0] == '-f') { + $force = true; + array_shift($args); + } + if (!isset($args[0])) + return $this->mwHelpCmd($sender,["unload"]); + + foreach ($args as $level) { + $level = $args[0]; + if (!$this->getServer()->isLevelLoaded($level)) { + $sender->sendMessage("[MW] Level $level is not loaded."); + continue; + } + $world = $this->getServer()->getLevelByName($level); + if ($world === null) { + $sender->sendMessage("[MW] Unable to get $level"); + continue; + } + if (!$this->getServer()->unloadLevel($world,$force)) { + $sender->sendMessage("[MW] Unable to unload $level. Try -f"); + continue; + } + $sender->sendMessage("[MW] $level unloaded."); + } + return true; + } + private function mwHelpCmd(CommandSender $sender,$args) { + $pageNumber = $this->getPageNumber($args); + $cmds = [ + "tp" => [" [player]", + "Teleport across worlds"], + "ls" => ["[level]", + "List world information"], + "create" => [" [seed [flat|normal [preset]]]", + "Create a new world"], + "lvdat" => [" [attr=value]","Manipulate level.dat"], + "fixname" => ["","Fix level.dat world names"], + "ld" => ["|--all","Load a world"], + "unload" => ["","Attempt to unload a world"], + ]; + if (count($args)) { + foreach ($args as $c) { + if (isset(self::$aliases[$c])) $c = self::$aliases[$c]; + if (isset($cmds[$c])) { + list($a,$b) = $cmds[$c]; + $sender->sendMessage(TextFormat::RED."Usage: /mw $c $a" + .TextFormat::RESET); + $sender->sendMessage($b); + } + } + return true; + } + $txt = ["ManyWorlds sub-commands"]; + foreach ($cmds as $a => $b) { + $ln = "- ".TextFormat::GREEN."/mw ".$a; + foreach (self::$aliases as $i => $j) { + if ($j == $a) $ln .= "|$i"; + } + $ln .= TextFormat::RESET." ".$b[0]; + $txt[] = $ln; + } + return $this->paginateText($sender,$pageNumber,$txt); + } + + // + // Helper functions + // + private function mwAutoLoad(CommandSender $c,$level) { + if ($this->getServer()->isLevelLoaded($level)) return true; + if(!$this->access($c, "mw.cmd.world.load")) return false; + if(!$this->getServer()->isLevelGenerated($level)) { + $c->sendMessage("[MW] No level with the name $level exists!"); + return false; + } + $this->getServer()->loadLevel($level); + return true; + } + private function mwWorldList(CommandSender $sender) { + $dir = $this->getServer()->getDataPath(). "worlds"; + if (!is_dir($dir)) { + $sender->sendMessage("[MW] Missing path $dir"); + return null; + } + $txt = ["HDR"]; + + $auto = $this->getServer()->getProperty("worlds",[]); + $default = $this->getServer()->getDefaultLevel(); + if ($default) $default = $default->getName(); + + $count = 0; + $dh = opendir($dir); + if (!$dh) return null; + while (($file = readdir($dh)) !== false) { + if ($file == '.' || $file == '..') continue; + if (!$this->getServer()->isLevelGenerated($file)) continue; + $attrs = []; + ++$count; + if (isset($auto[$file])) $attrs[] = "auto"; + if ($default == $file) $attrs[]="default"; + if ($this->getServer()->isLevelLoaded($file)) { + $attrs[] = "loaded"; + $np = count($this->getServer()->getLevelByName($file)->getPlayers()); + if ($np) $attrs[] = "players:$np"; + } + $ln = "- $file"; + if (count($attrs)) $ln .= TextFormat::AQUA." (".implode(",",$attrs).")"; + $txt[] = $ln; + } + closedir($dh); + $txt[0] = "Worlds: ".$count; + return $txt; + } + private function mwWorldDetails(CommandSender $sender,$level) { + $txt = []; + $world = $this->getServer()->getLevelByName($level); + if (!$world) { + $sender->sendMessage("[MW] $level not loaded"); + return null; + } + //==== provider + $provider = $world->getProvider(); + $txt[] = "Info for $level"; + $txt[] = TextFormat::AQUA."Provider: ".TextFormat::WHITE. $provider::getProviderName(); + $txt[] = TextFormat::AQUA."Path: ".TextFormat::WHITE.$provider->getPath(); + $txt[] = TextFormat::AQUA."Name: ".TextFormat::WHITE.$provider->getName(); + $txt[] = TextFormat::AQUA."Seed: ".TextFormat::WHITE.$provider->getSeed(); + $txt[] = TextFormat::AQUA."Generator: ".TextFormat::WHITE.$provider->getGenerator(); + $gopts = $provider->getGeneratorOptions(); + if ($gopts["preset"] != "") + $txt[] = TextFormat::AQUA."Generator Presets: ".TextFormat::WHITE. + $gopts["preset"]; + $spawn = $provider->getSpawn(); + $txt[] = TextFormat::AQUA."Spawn: ".TextFormat::WHITE.$spawn->getX().",".$spawn->getY().",".$spawn->getZ(); + $plst = $this->getServer()->getLevelByName($level)->getPlayers(); + $lst = ""; + if (count($plst)) { + foreach ($plst as $p) { + $lst .= (strlen($lst) ? ", " : "").$p->getName(); + } + } + $total = count($plst); + $max = call_user_func($this->maxplayers,$level); + if ($max) $total .= "/$max"; + $txt[] = TextFormat::AQUA."Players(".TextFormat::WHITE.$total. + TextFormat::AQUA."): ".TextFormat::WHITE.$lst; + + $fn = "getWorldInfo"; + foreach ($this->getServer()->getPluginManager()->getPlugins() as $p) { + if ($p->isDisabled()) continue; + if (is_callable([$p,$fn])) { + foreach (call_user_func([$p,$fn],$world->getName()) as $ln) { + $txt[] = $ln; + } + } + } + ////////////////////////////////////////////////////////////////////// + // Checks + //$txt[] = "levelName: ".$world->getName()."\n"; + //$txt[] = "folderName: ".$world->getFolderName()."\n"; + //$txt[] = "providerName: ".$provider->getName()."\n"; + ////////////////////////////////////////////////////////////////////// + + + // Check for warnings... + if ($provider->getName() != $level) { + $txt[] = TextFormat::RED."Folder Name and Level.Dat names do NOT match"; + $txt[] = TextFormat::RED."This can cause intermitent problems"; + if($sender->hasPermission("mw.cmd.lvdat")) { + $txt[] = TextFormat::RED."Use: "; + $txt[] = TextFormat::GREEN."> /mw fixname $level"; + $txt[] = TextFormat::RED."to fix this issue"; + } + } + return $txt; + } + public function _getPlayerLimit($level) { return 0; } + public function maxPlayers1st($level) { + $fn = "getPlayerLimit"; + foreach ($this->getServer()->getPluginManager()->getPlugins() as $p) { + if ($p->isDisabled()) continue; + if (is_callable([$p,$fn])) { + $this->maxplayers = [$p,$fn]; + $this->getLogger()->info(TextFormat::YELLOW."Using plugin ". + $p->getName()." for Player Limits"); + return call_user_func($this->maxplayers,$level); + } + } + $this->maxplayers = [$this,"_".$fn]; + return call_user_func($this->maxplayers,$level); + } + // + // Public API + // + public function mwtp($pl,$pos) { + if ($pos instanceof Position) { + // Using ManyWorlds for teleporting... + return $this->teleport($pl,$pos->getLevel()->getName(), + new Vector3($pos->getX(), + $pos->getY(), + $pos->getZ())); + } + $pl->teleport($pos); + return true; + } + public function teleport($player,$level,$spawn=null) { + if (!$this->getServer()->isLevelLoaded($level)) return false; + /* + * Check if we can enforce player limits + */ + $max = call_user_func($this->maxplayers,$level); + if ($max) { + $np = count($this->getServer()->getLevelByName($level)->getPlayers()); + if ($np >= $max) { + $player->sendMessage("Can not teleport to $level, its FULL\n"); + return false; + } + } + return $this->tpManager->teleport($player,$level,$spawn); + } +} diff --git a/src/aliuly/manyworlds/TeleportManager.php b/src/aliuly/manyworlds/TeleportManager.php new file mode 100644 index 0000000..7e9a130 --- /dev/null +++ b/src/aliuly/manyworlds/TeleportManager.php @@ -0,0 +1,97 @@ +owner = $plugin; + if (!$this->owner->is15) { + $this->owner->getServer()->getPluginManager()->registerEvents($this, $this->owner); + } + } + /** + * @priority LOWEST + */ + public function onDamage(EntityDamageEvent $event) { + // Try keep the player alive while on transit... + $victim= $event->getEntity(); + if (!($victim instanceof Player)) return; + $vname = $victim->getName(); + if (!isset($this->teleporters[$vname])) return; + if (time() - $this->teleporters[$vname] > 2) { + unset($this->teleporters[$vname()]); + return; + } + $victim->heal($event->getDamage()); + $event->setDamage(0); + $event->setCancelled(true); + } + public function teleport($player,$level,$spawn=null) { + // + // This CRAZY HACK is to remove Tile entities that seem to linger + // whenever you teleport! + // + $current = $player->getLevel(); + if ($current->getName() != $level) { + foreach ($current->getTiles() as $tile) { + $pk = new \pocketmine\network\protocol\UpdateBlockPacket(); + $pk->x = $tile->x; + $pk->y = $tile->y; + $pk->z = $tile->z; + $pk->block = 0; + $pk->meta = 0; + $player->dataPacket($pk); + } + } + + $world = $this->owner->getServer()->getLevelByName($level); + if (!$world) { + $player->sendMessage("Unable to teleport to $level"); + $player->sendMessage("Level $level was not found"); + return false; + } + // Try to find a reasonable spawn location + $location = $world->getSafeSpawn($spawn); + if (!$this->owner->is15) { + $this->teleporters[$player->getName()] = time(); + // This shouldn't be needed in PocketMine-MP v1.5... + foreach ([5,10,20] as $ticks) { + // Try to keep the player in place until the chunk finish loading + $this->after("delayedTP",[$player->getName(), + $location->getX(),$location->getY(), + $location->getZ(), + $location->getLevel()->getName()],$ticks); + } + // Make sure that any damage he may have taken is restored + $this->after("restoreHealth",[$player->getName(),$player->getHealth()],20); + // Make sure the player survives the transfer... + $player->setHealth($player->getMaxHealth()); + } + $player->teleport($location); // Start the teleport + return true; + } + public function after($method,$args,$ticks) { + $this->owner->getServer()->getScheduler()->scheduleDelayedTask(new CallbackTask([$this,$method],$args),$ticks); + } + public function restoreHealth($name,$health) { + $player = $this->owner->getServer()->getPlayer($name); + if (!$player) return; + $player->setHealth($health); + } + public function delayedTP($name,$x,$y,$z,$level) { + $player = $this->owner->getServer()->getPlayer($name); + if (!$player) return; + if($player->getLevel()->getName() != $level) return; + $player->teleport(new Vector3($x,$y,$z)); + } + + +}