From 73b9b2491f144528f9bc34eb71b9a6e5d784ba03 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Mon, 16 Jun 2014 19:34:47 +0200 Subject: [PATCH] Added custom settings, update notification --- .gitignore | 1 + src/pocketmine/Player.php | 14 +- src/pocketmine/Server.php | 136 ++++++++++--------- src/pocketmine/command/SimpleCommandMap.php | 2 +- src/pocketmine/resources/pocketmine.yml | 50 +++++++ src/pocketmine/updater/AutoUpdater.php | 140 ++++++++++++++++++++ src/pocketmine/utils/VersionString.php | 21 ++- 7 files changed, 292 insertions(+), 72 deletions(-) create mode 100644 src/pocketmine/resources/pocketmine.yml create mode 100644 src/pocketmine/updater/AutoUpdater.php diff --git a/.gitignore b/.gitignore index 055e94b5c..b394e4e51 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ bin/* *.txt *.phar server.properties +pocketmine.yml # Common IDEs .idea/* diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 43d9f4edf..ad95cb58d 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -592,6 +592,15 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ $this->teleport($ev->getRespawnPosition()); $this->spawnToAll(); + + $this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this, $this->getName() . " joined the game")); + if(strlen(trim($ev->getJoinMessage())) > 0){ + $this->server->broadcastMessage($ev->getJoinMessage()); + } + + if($this->server->getUpdater()->hasUpdate() and $this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){ + $this->server->getUpdater()->showPlayerUpdate($this); + } } $this->lastChunk = array($X, $Z, $cnt, microtime(true)); @@ -1185,11 +1194,6 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ $this->server->getLogger()->info(TextFormat::AQUA . $this->username . TextFormat::WHITE . "[/" . $this->ip . ":" . $this->port . "] logged in with entity id " . $this->id . " at (" . $this->getLevel()->getName() . ", " . round($this->x, 4) . ", " . round($this->y, 4) . ", " . round($this->z, 4) . ")"); - $this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this, $this->getName() . " joined the game")); - if(strlen(trim($ev->getJoinMessage())) > 0){ - $this->server->broadcastMessage($ev->getJoinMessage()); - } - $this->orderChunks(); $this->tasks[] = $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "orderChunks")), 30); diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 2b6dc37ff..4c10c30a4 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -74,6 +74,7 @@ use pocketmine\scheduler\SendUsageTask; use pocketmine\scheduler\ServerScheduler; use pocketmine\scheduler\TickScheduler; use pocketmine\tile\Tile; +use pocketmine\updater\AutoUpdater; use pocketmine\utils\Binary; use pocketmine\utils\Config; use pocketmine\utils\MainLogger; @@ -109,6 +110,9 @@ class Server{ /** @var PluginManager */ private $pluginManager = null; + /** @var AutoUpdater */ + private $updater = null; + /** @var ServerScheduler */ private $scheduler = null; @@ -174,6 +178,9 @@ class Server{ /** @var Config */ private $properties; + /** @var Config */ + private $config; + /** @var Player[] */ private $players = []; @@ -471,6 +478,13 @@ class Server{ return $this->levelMetadata; } + /** + * @return AutoUpdater + */ + public function getUpdater(){ + return $this->updater; + } + /** * @return PluginManager */ @@ -1049,6 +1063,33 @@ class Server{ return $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue; } + /** + * @param string $variable + * @param mixed $defaultValue + * + * @return mixed + */ + public function getProperty($variable, $defaultValue = null){ + $vars = explode(".", $variable); + $base = array_shift($vars); + if($this->config->exists($base)){ + $base = $this->config->get($base); + }else{ + return $defaultValue; + } + + while(count($vars) > 0){ + $baseKey = array_shift($vars); + if(is_array($base) and isset($base[$baseKey])){ + $base = $base[$baseKey]; + }else{ + return $defaultValue; + } + } + + return $base; + } + /** * @param string $variable * @param string $value @@ -1269,10 +1310,14 @@ class Server{ $this->logger->info("Starting Minecraft: PE server version " . TextFormat::AQUA . $this->getVersion()); $this->logger->info("Loading properties..."); + if(!file_exists($this->dataPath . "pocketmine.yml")){ + @file_put_contents($this->dataPath . "pocketmine.yml", file_get_contents($this->filePath . "src/pocketmine/resources/pocketmine.yml")); + } + $this->config = new Config($this->dataPath . "pocketmine.yml", Config::YAML, []); $this->properties = new Config($this->dataPath . "server.properties", Config::PROPERTIES, array( "motd" => "Minecraft: PE Server", "server-port" => 19132, - "memory-limit" => "128M", + "memory-limit" => "256M", "white-list" => false, "announce-player-achievements" => true, "spawn-protection" => 16, @@ -1316,12 +1361,12 @@ class Server{ $this->setConfigInt("difficulty", 3); } - define("pocketmine\\DEBUG", $this->getConfigInt("debug.level", 1)); + define("pocketmine\\DEBUG", $this->getProperty("debug.level", 1)); if($this->logger instanceof MainLogger){ $this->logger->setLogDebug(\pocketmine\DEBUG > 1); } - define("ADVANCED_CACHE", $this->getConfigBoolean("enable-advanced-cache", false)); - define("MAX_CHUNK_RATE", 20 / $this->getConfigInt("max-chunks-per-second", 20)); + define("ADVANCED_CACHE", $this->getProperty("settings.advanced-cache", false)); + define("MAX_CHUNK_RATE", 20 / $this->getProperty("chunk-sending.per-second", 20)); if(ADVANCED_CACHE == true){ $this->logger->info("Advanced cache enabled"); } @@ -1336,7 +1381,7 @@ class Server{ $this->interfaces[] = new RakLibInterface($this); - $this->logger->info("This server is running PocketMine-MP version " . ($version->isDev() ? TextFormat::YELLOW : "") . $this->getPocketMineVersion() . TextFormat::RESET . " \"" . $this->getCodename() . "\" (API " . $this->getApiVersion() . ")", true, true, 0); + $this->logger->info("This server is running PocketMine-MP version " . ($version->isDev() ? TextFormat::YELLOW : "") . $version->get(false) . TextFormat::RESET . " \"" . $this->getCodename() . "\" (API " . $this->getApiVersion() . ")", true, true, 0); $this->logger->info("PocketMine-MP is distributed under the LGPL License", true, true, 0); $this->consoleSender = new ConsoleCommandSender(); @@ -1352,7 +1397,7 @@ class Server{ $this->pluginManager->registerInterface("pocketmine\\plugin\\PharPluginLoader"); $this->pluginManager->loadPlugins($this->pluginPath); - //TODO: update checking (async) + $this->updater = new AutoUpdater($this, $this->getProperty("auto-updater.host", "www.pocketmine.net")); $this->enablePlugins(PluginLoadOrder::STARTUP); @@ -1383,57 +1428,16 @@ class Server{ $this->properties->save(); - $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask("pocketmine\\utils\\Cache::cleanup"), 20 * 45, 20 * 45); - if($this->getConfigBoolean("auto-save", true) === true){ - $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask(array($this, "doAutoSave")), 18000, 18000); + $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask("pocketmine\\utils\\Cache::cleanup"), $this->getProperty("ticks-per.cache-cleanup", 900), $this->getProperty("ticks-per.cache-cleanup", 900)); + if($this->getConfigBoolean("auto-save", true) === true and $this->getProperty("ticks-per.autosave", 6000) > 0){ + $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask(array($this, "doAutoSave")), $this->getProperty("ticks-per.autosave", 6000), $this->getProperty("ticks-per.autosave", 6000)); } + $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask(array($this, "doLevelGC")), $this->getProperty("chunk-gc.period-in-ticks", 600), $this->getProperty("chunk-gc.period-in-ticks", 600)); + $this->enablePlugins(PluginLoadOrder::POSTWORLD); - /* - //TODO - if($this->getProperty("last-update") === false or ($this->getProperty("last-update") + 3600) < time()){ - $this->logger->info("Checking for new server version"); - $this->logger->info("Last check: " . TextFormat::AQUA . date("Y-m-d H:i:s", $this->getProperty("last-update"))); - if($this->server->version->isDev()){ - $info = json_decode(Utils::getURL("https://api.github.com/repos/PocketMine/PocketMine-MP/commits"), true); - if($info === false or !isset($info[0])){ - console("[ERROR] Github API error"); - }else{ - $last = new \DateTime($info[0]["commit"]["committer"]["date"]); - $last = $last->getTimestamp(); - if($last >= $this->getProperty("last-update") and $this->getProperty("last-update") !== false and \pocketmine\GIT_COMMIT != $info[0]["sha"]){ - $this->logger->notice("" . TextFormat::YELLOW . "A new DEVELOPMENT version of PocketMine-MP has been released!"); - $this->logger->notice("" . TextFormat::YELLOW . "Commit \"" . $info[0]["commit"]["message"] . "\" [" . substr($info[0]["sha"], 0, 10) . "] by " . $info[0]["commit"]["committer"]["name"]); - $this->logger->notice("" . TextFormat::YELLOW . "Get it at PocketMine.net or at https://github.com/PocketMine/PocketMine-MP/archive/" . $info[0]["sha"] . ".zip"); - $this->logger->notice("This message will disappear after issuing the command \"/update-done\""); - }else{ - $this->setProperty("last-update", time()); - $this->logger->info("" . TextFormat::AQUA . "This is the latest DEVELOPMENT version"); - } - } - }else{ - $info = json_decode(Utils::getURL("https://api.github.com/repos/PocketMine/PocketMine-MP/tags"), true); - if($info === false or !isset($info[0])){ - console("[ERROR] Github API error"); - }else{ - $newest = new VersionString(VERSION); - $newestN = $newest->getNumber(); - $update = new VersionString($info[0]["name"]); - $updateN = $update->getNumber(); - if($updateN > $newestN){ - $this->logger->notice("" . TextFormat::GREEN . "A new STABLE version of PocketMine-MP has been released!"); - $this->logger->notice("" . TextFormat::GREEN . "Version \"" . $info[0]["name"] . "\" #" . $updateN); - $this->logger->notice("Get it at PocketMine.net or at " . $info[0]["zipball_url"]); - $this->logger->notice("This message will disappear as soon as you update"); - }else{ - $this->setProperty("last-update", time()); - $this->logger->info("" . TextFormat::AQUA . "This is the latest STABLE version"); - } - } - } - } - */ + } /** @@ -1551,11 +1555,11 @@ class Server{ $this->properties->reload(); $this->maxPlayers = $this->getConfigInt("max-players", 20); - if(($memory = str_replace("B", "", strtoupper($this->getConfigString("memory-limit", "128M")))) !== false){ + if(($memory = str_replace("B", "", strtoupper($this->getConfigString("memory-limit", "256M")))) !== false){ $value = array("M" => 1, "G" => 1024); $real = ((int) substr($memory, 0, -1)) * $value[substr($memory, -1)]; - if($real < 128){ - $this->logger->warning("PocketMine-MP may not work right with less than 128MB of RAM", true, true, 0); + if($real < 256){ + $this->logger->warning("PocketMine-MP may not work right with less than 256MB of RAM", true, true, 0); } @ini_set("memory_limit", $memory); }else{ @@ -1591,7 +1595,7 @@ class Server{ $this->rcon->stop(); } - if($this->getConfigBoolean("upnp-forwarding", false) === true){ + if($this->getProperty("settings.upnp-forwarding", false) === true){ $this->logger->info("[UPnP] Removing port forward..."); UPnP::RemovePortForward($this->getPort()); } @@ -1599,7 +1603,7 @@ class Server{ $this->pluginManager->disablePlugins(); foreach($this->players as $player){ - $player->kick("server stop"); + $player->kick($this->getProperty("settings.shutdown-message", "Server closed")); } foreach($this->getLevels() as $level){ @@ -1631,13 +1635,13 @@ class Server{ } - if($this->getConfigBoolean("send-usage", true) !== false){ + if($this->getProperty("settings.send-usage", true) !== false){ $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask(array($this, "sendUsage")), 6000, 6000); $this->sendUsage(); } - if($this->getConfigBoolean("upnp-forwarding", false) == true){ + if($this->getProperty("settings.upnp-forwarding", false) == true){ $this->logger->info("[UPnP] Trying to port forward..."); UPnP::PortForward($this->getPort()); } @@ -1744,7 +1748,7 @@ class Server{ } $dump .= "\r\n\r\n"; $version = new VersionString(); - $dump .= "PocketMine-MP version: " . $version . " #" . $version->getNumber() . " [Protocol " . Info::CURRENT_PROTOCOL . "; API " . API_VERSION . "]\r\n"; + $dump .= "PocketMine-MP version: " . $version->get(false). " #" . $version->getNumber() . " [Protocol " . Info::CURRENT_PROTOCOL . "; API " . API_VERSION . "]\r\n"; $dump .= "Git commit: " . GIT_COMMIT . "\r\n"; $dump .= "uname -a: " . php_uname("a") . "\r\n"; $dump .= "PHP Version: " . phpversion() . "\r\n"; @@ -1853,6 +1857,12 @@ class Server{ } } + public function doLevelGC(){ + foreach($this->getLevels() as $level){ + $level->doChunkGarbageCollection(); + } + } + public function sendUsage(){ if($this->lastSendUsage instanceof SendUsageTask){ if(!$this->lastSendUsage->isFinished()){ //do not call multiple times @@ -1865,6 +1875,7 @@ class Server{ $plist .= str_replace(array(";", ":"), "", $d->getName()) . ":" . str_replace(array(";", ":"), "", $d->getVersion()) . ";"; } + $version = new VersionString(); $this->lastSendUsage = new SendUsageTask("http://stats.pocketmine.net/usage.php", array( "serverid" => Binary::readLong(substr(Utils::getUniqueID(true, $this->getIp() . ":" . $this->getPort()), 0, 8)), "port" => $this->getPort(), @@ -1872,7 +1883,8 @@ class Server{ "memory_total" => $this->getConfigString("memory-limit"), "memory_usage" => memory_get_usage(), "php_version" => \pocketmine\PHP_VERSION, - "version" => \pocketmine\VERSION, + "version" => $version->get(false), + "build" => $version->getBuild(), "mc_version" => \pocketmine\MINECRAFT_VERSION, "protocol" => network\protocol\Info::CURRENT_PROTOCOL, "online" => count($this->players), diff --git a/src/pocketmine/command/SimpleCommandMap.php b/src/pocketmine/command/SimpleCommandMap.php index c516b156d..798b9056c 100644 --- a/src/pocketmine/command/SimpleCommandMap.php +++ b/src/pocketmine/command/SimpleCommandMap.php @@ -103,7 +103,7 @@ class SimpleCommandMap implements CommandMap{ $this->register("pocketmine", new TeleportCommand("tp")); $this->register("pocketmine", new ReloadCommand("reload")); - if($this->server->getConfigBoolean("debug.commands", false) === true){ + if($this->server->getProperty("debug.commands", false) === true){ $this->register("pocketmine", new StatusCommand("status")); } } diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml new file mode 100644 index 000000000..05470cae7 --- /dev/null +++ b/src/pocketmine/resources/pocketmine.yml @@ -0,0 +1,50 @@ +# Main configuration file for PocketMine-MP +# These settings are the ones that cannot be included in server.properties +# Some of these settings are safe, others can break your server if modified incorrectly + +settings: + shutdown-message: "Server closed" + advanced-cache: false + upnp-forwarding: false + send-usage: true + +debug: + #If > 1, it will show debug messages in the console + level: 1 + #Enables /status + commands: false + +chunk-sending: + per-second: 20 + compression-level: 7 + +chunk-gc: + period-in-ticks: 600 + +ticks-per: + animal-spawns: 400 + monster-spawns: 1 + autosave: 6000 + cache-cleanup: 900 + +spawn-limits: + monsters: 70 + animals: 15 + water-animals: 5 + ambient: 15 + +auto-updater: + enabled: true + on-update: + warn-console: true + warn-ops: true + #Can be development, beta or stable. + preferred-channel: stable + #If using a development version, it will suggest changing the channel + suggest-channels: true + host: www.pocketmine.net + +aliases: + #Examples + #showtheversion: version + #savestop: [save-all, stop] \ No newline at end of file diff --git a/src/pocketmine/updater/AutoUpdater.php b/src/pocketmine/updater/AutoUpdater.php new file mode 100644 index 000000000..e14de58fa --- /dev/null +++ b/src/pocketmine/updater/AutoUpdater.php @@ -0,0 +1,140 @@ +server = $server; + $this->endpoint = "http://$endpoint/api/"; + + if($server->getProperty("auto-updater.enabled", true)){ + $this->check(); + if($this->hasUpdate()){ + if($this->server->getProperty("auto-updater.on-update.warn-console", true)){ + $this->showConsoleUpdate(); + } + }elseif($this->server->getProperty("auto-updater.preferred-channel", true)){ + $version = new VersionString(); + if(!$version->isDev() and $this->getChannel() !== "stable"){ + $this->showChannelSuggestionStable(); + }elseif($this->getChannel() === "stable"){ + $this->showChannelSuggestionBeta(); + } + } + } + } + + protected function check(){ + $response = Utils::getURL($this->endpoint . "?channel=". $this->getChannel(), 4); + $response = @json_decode($response, true); + if(!is_array($response)){ + return; + } + + $this->updateInfo = [ + "version" => $response["version"], + "api_version" => $response["api_version"], + "build" => $response["build"], + "date" => $response["date"], + "details_url" => isset($response["details_url"]) ? $response["details_url"] : null, + "download_url" => $response["download_url"] + ]; + + $this->checkUpdate(); + } + + /** + * @return bool + */ + public function hasUpdate(){ + return $this->hasUpdate; + } + + public function showConsoleUpdate(){ + $logger = $this->server->getLogger(); + $newVersion = new VersionString($this->updateInfo["version"]); + $logger->warning("----- PocketMine-MP Auto Updater -----"); + $logger->warning("Your version of PocketMine-MP is out of date. Version ".$newVersion->get(false)." (build #".$newVersion->getBuild().") was released on ".date("D M j h:i:s Y", $this->updateInfo["date"])); + if($this->updateInfo["details_url"] !== null){ + $logger->warning("Details: ".$this->updateInfo["details_url"]); + } + $logger->warning("Download: ".$this->updateInfo["download_url"]); + $logger->warning("----- -------------------------- -----"); + } + + public function showPlayerUpdate(Player $player){ + $player->sendMessage(TextFormat::DARK_PURPLE . "The version of PocketMine-MP that this server is running is out of date. Please consider updating to the latest version."); + $player->sendMessage(TextFormat::DARK_PURPLE . "Check the console for more details."); + } + + protected function showChannelSuggestionStable(){ + $logger = $this->server->getLogger(); + $logger->info("----- PocketMine-MP Auto Updater -----"); + $logger->info("It appears you're running a Stable build, when you've specified that you prefer to run ".ucfirst($this->getChannel())." builds."); + $logger->info("If you would like to be kept informed about new Stable builds only, it is recommended that you change 'preferred-channel in your pocketmine.yml to 'stable'."); + $logger->info("----- -------------------------- -----"); + } + + protected function showChannelSuggestionBeta(){ + $logger = $this->server->getLogger(); + $logger->info("----- PocketMine-MP Auto Updater -----"); + $logger->info("It appears you're running a Beta build, when you've specified that you prefer to run Stable builds."); + $logger->info("If you would like to be kept informed about new Beta or Development builds, it is recommended that you change 'preferred-channel in your pocketmine.yml to 'beta' or 'development'."); + $logger->info("----- -------------------------- -----"); + } + + protected function checkUpdate(){ + if($this->updateInfo === null){ + return; + } + $currentVersion = new VersionString($this->server->getPocketMineVersion()); + $newVersion = new VersionString($this->updateInfo["version"]); + + if($currentVersion->compare($newVersion) > 0){ + $this->hasUpdate = true; + } + + $this->hasUpdate = false; + } + + public function getChannel(){ + $channel = strtolower($this->server->getProperty("auto-updater.preferred-channel", "stable")); + if($channel !== "stable" and $channel !== "beta" and $channel !== "development"){ + $channel = "stable"; + } + + return $channel; + } +} \ No newline at end of file diff --git a/src/pocketmine/utils/VersionString.php b/src/pocketmine/utils/VersionString.php index 33a18451d..6e60bb92c 100644 --- a/src/pocketmine/utils/VersionString.php +++ b/src/pocketmine/utils/VersionString.php @@ -36,7 +36,7 @@ class VersionString{ ); private $stage; private $major; - private $release; + private $build; private $minor; private $development = false; private $generation; @@ -48,12 +48,17 @@ class VersionString{ $this->generation = ($version >> 9) & 0x0F; $this->stage = array_search(($version >> 13) & 0x0F, VersionString::$stageOrder, true); }else{ - $version = preg_split("/([A-Za-z]*)[ _\-]([0-9]*)\.([0-9]*)\.{0,1}([0-9]*)(dev|)/", $version, -1, PREG_SPLIT_DELIM_CAPTURE); + $version = preg_split("/([A-Za-z]*)[ _\\-]([0-9]*)\\.([0-9]*)\\.{0,1}([0-9]*)(dev|)(-[\\0-9]{1,}|)/", $version, -1, PREG_SPLIT_DELIM_CAPTURE); $this->stage = strtolower($version[1]); //0-15 $this->generation = (int) $version[2]; //0-15 $this->major = (int) $version[3]; //0-15 $this->minor = (int) $version[4]; //0-31 $this->development = $version[5] === "dev" ? true : false; + if($version[6] !== ""){ + $this->build = intval(substr($version[6], 1)); + }else{ + $this->build = 0; + } } } @@ -81,12 +86,16 @@ class VersionString{ return $this->generation . "." . $this->major . "." . $this->minor; } + public function getBuild(){ + return $this->build; + } + public function isDev(){ return $this->development === true; } - public function get(){ - return ucfirst($this->stage) . "_" . $this->getRelease() . ($this->development === true ? "dev" : ""); + public function get($build = false){ + return ucfirst($this->stage) . "_" . $this->getRelease() . ($this->development === true ? "dev" : "") . (($this->build > 0 and $build === true) ? "-" . $this->build : ""); } public function __toString(){ @@ -106,6 +115,10 @@ class VersionString{ return -1; //Target is older }elseif($number < $tNumber){ return 1; //Target is newer + }elseif($target->getBuild() > $this->getBuild()){ + return 1; + }elseif($target->getBuild() > $this->getBuild()){ + return -1; }else{ return 0; //Same version }