From 38fad4b9639c1c13d9ea358a64b8e388e14da220 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 26 Sep 2017 11:16:51 +0100 Subject: [PATCH] Implement difficulty per-world (#878) * Moved Server::getDifficultyFromString() to Level * Added ability to set difficulty in worlds section of pocketmine.yml for generation --- src/pocketmine/Player.php | 5 +- src/pocketmine/Server.php | 53 +++++--------- .../command/defaults/DifficultyCommand.php | 14 ++-- src/pocketmine/entity/Human.php | 4 +- src/pocketmine/entity/Living.php | 2 +- src/pocketmine/level/Level.php | 69 +++++++++++++++++++ .../level/format/io/LevelProvider.php | 12 ++++ .../level/format/io/leveldb/LevelDB.php | 12 +++- .../level/format/io/region/McRegion.php | 11 ++- 9 files changed, 132 insertions(+), 50 deletions(-) diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index d0d3ea45c..c540bc486 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -803,6 +803,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $this->usedChunks = []; $this->level->sendTime($this); + $this->level->sendDifficulty($this); return true; } @@ -1924,7 +1925,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $pk->seed = -1; $pk->dimension = DimensionIds::OVERWORLD; //TODO: implement this properly $pk->worldGamemode = Player::getClientFriendlyGamemode($this->server->getGamemode()); - $pk->difficulty = $this->server->getDifficulty(); + $pk->difficulty = $this->level->getDifficulty(); $pk->spawnX = $spawnPosition->getFloorX(); $pk->spawnY = $spawnPosition->getFloorY(); $pk->spawnZ = $spawnPosition->getFloorZ(); @@ -2427,7 +2428,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ }elseif($target instanceof Player){ if(($target->getGamemode() & 0x01) > 0){ return true; - }elseif($this->server->getConfigBoolean("pvp") !== true or $this->server->getDifficulty() === 0){ + }elseif($this->server->getConfigBoolean("pvp") !== true or $this->level->getDifficulty() === Level::DIFFICULTY_PEACEFUL){ $cancelled = true; } diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 1671f1074..c80bebebf 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -515,36 +515,17 @@ class Server{ } /** - * @param string $str + * @deprecated Moved to {@link Level#getDifficultyFromString} * + * @param string $str * @return int */ public static function getDifficultyFromString(string $str) : int{ - switch(strtolower(trim($str))){ - case "0": - case "peaceful": - case "p": - return 0; - - case "1": - case "easy": - case "e": - return 1; - - case "2": - case "normal": - case "n": - return 2; - - case "3": - case "hard": - case "h": - return 3; - } - return -1; + return Level::getDifficultyFromString($str); } /** + * Returns Server global difficulty. Note that this may be overridden in individual Levels. * @return int */ public function getDifficulty() : int{ @@ -1589,8 +1570,8 @@ class Server{ $this->logger->warning($this->getLanguage()->translateString("pocketmine.server.authProperty", ["enable", "true"])); } - if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){ - $this->setConfigInt("difficulty", 3); + if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < Level::DIFFICULTY_HARD){ + $this->setConfigInt("difficulty", Level::DIFFICULTY_HARD); } if(\pocketmine\DEBUG >= 0){ @@ -1671,21 +1652,23 @@ class Server{ foreach((array) $this->getProperty("worlds", []) as $name => $worldSetting){ if($this->loadLevel($name) === false){ - $seed = $this->getProperty("worlds.$name.seed", time()); + $options = $this->getProperty("worlds.$name"); + + $seed = $this->getProperty($options["seed"], time()); if(is_string($seed) and !is_numeric($seed)){ $seed = Utils::javaStringHash($seed); }elseif(!is_int($seed)){ $seed = (int) $seed; } - $options = explode(":", $this->getProperty("worlds.$name.generator", Generator::getGenerator("default"))); - $generator = Generator::getGenerator(array_shift($options)); - if(count($options) > 0){ - $options = [ - "preset" => implode(":", $options) - ]; + if(isset($options["generator"])){ + $generatorOptions = explode(":", $options["generator"]); + $generator = Generator::getGenerator(array_shift($generatorOptions)); + if(count($options) > 0){ + $options["preset"] = implode(":", $generatorOptions); + } }else{ - $options = []; + $generator = Generator::getGenerator("default"); } $this->generateLevel($name, $seed, $generator, $options); @@ -2006,8 +1989,8 @@ class Server{ $this->properties->reload(); $this->maxPlayers = $this->getConfigInt("max-players", 20); - if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){ - $this->setConfigInt("difficulty", 3); + if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < Level::DIFFICULTY_HARD){ + $this->setConfigInt("difficulty", Level::DIFFICULTY_HARD); } $this->banByIP->load(); diff --git a/src/pocketmine/command/defaults/DifficultyCommand.php b/src/pocketmine/command/defaults/DifficultyCommand.php index 5b8cae9d6..fb484ec12 100644 --- a/src/pocketmine/command/defaults/DifficultyCommand.php +++ b/src/pocketmine/command/defaults/DifficultyCommand.php @@ -27,8 +27,7 @@ use pocketmine\command\Command; use pocketmine\command\CommandSender; use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\event\TranslationContainer; -use pocketmine\network\mcpe\protocol\SetDifficultyPacket; -use pocketmine\Server; +use pocketmine\level\Level; class DifficultyCommand extends VanillaCommand{ @@ -50,18 +49,19 @@ class DifficultyCommand extends VanillaCommand{ throw new InvalidCommandSyntaxException(); } - $difficulty = Server::getDifficultyFromString($args[0]); + $difficulty = Level::getDifficultyFromString($args[0]); if($sender->getServer()->isHardcore()){ - $difficulty = 3; + $difficulty = Level::DIFFICULTY_HARD; } if($difficulty !== -1){ $sender->getServer()->setConfigInt("difficulty", $difficulty); - $pk = new SetDifficultyPacket(); - $pk->difficulty = $sender->getServer()->getDifficulty(); - $sender->getServer()->broadcastPacket($sender->getServer()->getOnlinePlayers(), $pk); + //TODO: add per-world support + foreach($sender->getServer()->getLevels() as $level){ + $level->setDifficulty($difficulty); + } Command::broadcastCommandMessage($sender, new TranslationContainer("commands.difficulty.success", [$difficulty])); }else{ diff --git a/src/pocketmine/entity/Human.php b/src/pocketmine/entity/Human.php index 00b567cb2..8dbf99255 100644 --- a/src/pocketmine/entity/Human.php +++ b/src/pocketmine/entity/Human.php @@ -391,14 +391,14 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ if($this->isAlive()){ $food = $this->getFood(); $health = $this->getHealth(); - $difficulty = $this->server->getDifficulty(); + $difficulty = $this->level->getDifficulty(); $this->foodTickTimer += $tickDiff; if($this->foodTickTimer >= 80){ $this->foodTickTimer = 0; } - if($difficulty === 0 and $this->foodTickTimer % 10 === 0){ //Peaceful + if($difficulty === Level::DIFFICULTY_PEACEFUL and $this->foodTickTimer % 10 === 0){ if($food < 20){ $this->addFood(1.0); } diff --git a/src/pocketmine/entity/Living.php b/src/pocketmine/entity/Living.php index 3cde9874e..0c968cd0f 100644 --- a/src/pocketmine/entity/Living.php +++ b/src/pocketmine/entity/Living.php @@ -362,7 +362,7 @@ abstract class Living extends Entity implements Damageable{ if($e !== null){ if($e->isOnFire() > 0){ - $this->setOnFire(2 * $this->server->getDifficulty()); + $this->setOnFire(2 * $this->level->getDifficulty()); } $deltaX = $this->x - $e->x; diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 2bdae962d..0268d83f8 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -77,6 +77,7 @@ use pocketmine\network\mcpe\protocol\BatchPacket; use pocketmine\network\mcpe\protocol\DataPacket; use pocketmine\network\mcpe\protocol\LevelEventPacket; use pocketmine\network\mcpe\protocol\LevelSoundEventPacket; +use pocketmine\network\mcpe\protocol\SetDifficultyPacket; use pocketmine\network\mcpe\protocol\SetTimePacket; use pocketmine\network\mcpe\protocol\UpdateBlockPacket; use pocketmine\Player; @@ -112,6 +113,11 @@ class Level implements ChunkManager, Metadatable{ const TIME_FULL = 24000; + const DIFFICULTY_PEACEFUL = 0; + const DIFFICULTY_EASY = 1; + const DIFFICULTY_NORMAL = 2; + const DIFFICULTY_HARD = 3; + /** @var Tile[] */ private $tiles = []; @@ -224,6 +230,8 @@ class Level implements ChunkManager, Metadatable{ private $closed = false; + + public static function chunkHash(int $x, int $z){ return (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF); } @@ -259,6 +267,36 @@ class Level implements ChunkManager, Metadatable{ } } + /** + * @param string $str + * @return int + */ + public static function getDifficultyFromString(string $str) : int{ + switch(strtolower(trim($str))){ + case "0": + case "peaceful": + case "p": + return Level::DIFFICULTY_PEACEFUL; + + case "1": + case "easy": + case "e": + return Level::DIFFICULTY_EASY; + + case "2": + case "normal": + case "n": + return Level::DIFFICULTY_NORMAL; + + case "3": + case "hard": + case "h": + return Level::DIFFICULTY_HARD; + } + + return -1; + } + /** * Init the default level data * @@ -2742,6 +2780,37 @@ class Level implements ChunkManager, Metadatable{ return $this->provider->getWorldHeight(); } + /** + * @return int + */ + public function getDifficulty() : int{ + return $this->provider->getDifficulty(); + } + + /** + * @param int $difficulty + */ + public function setDifficulty(int $difficulty){ + if($difficulty < 0 or $difficulty > 3){ + throw new \InvalidArgumentException("Invalid difficulty level $difficulty"); + } + $this->provider->setDifficulty($difficulty); + + $this->sendDifficulty(); + } + + /** + * @param Player[] ...$targets + */ + public function sendDifficulty(Player ...$targets){ + if(count($targets) === 0){ + $targets = $this->getPlayers(); + } + + $pk = new SetDifficultyPacket(); + $pk->difficulty = $this->getDifficulty(); + $this->server->broadcastPacket($targets, $pk); + } public function populateChunk(int $x, int $z, bool $force = false) : bool{ if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)]) or (count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force)){ diff --git a/src/pocketmine/level/format/io/LevelProvider.php b/src/pocketmine/level/format/io/LevelProvider.php index e3634c3b6..b48c7abc2 100644 --- a/src/pocketmine/level/format/io/LevelProvider.php +++ b/src/pocketmine/level/format/io/LevelProvider.php @@ -206,6 +206,18 @@ interface LevelProvider{ */ public function setSpawn(Vector3 $pos); + /** + * Returns the world difficulty. This will be one of the Level constants. + * @return int + */ + public function getDifficulty() : int; + + /** + * Sets the world difficulty. + * @param int $difficulty + */ + public function setDifficulty(int $difficulty); + /** * @return Chunk[] */ diff --git a/src/pocketmine/level/format/io/leveldb/LevelDB.php b/src/pocketmine/level/format/io/leveldb/LevelDB.php index 8f50df58d..d0c434036 100644 --- a/src/pocketmine/level/format/io/leveldb/LevelDB.php +++ b/src/pocketmine/level/format/io/leveldb/LevelDB.php @@ -168,7 +168,7 @@ class LevelDB extends BaseLevelProvider{ $levelData = new CompoundTag("", [ //Vanilla fields new IntTag("DayCycleStopTime", -1), - new IntTag("Difficulty", 2), + new IntTag("Difficulty", Level::getDifficultyFromString((string) ($options["difficulty"] ?? "normal"))), new ByteTag("ForceGameType", 0), new IntTag("GameType", 0), new IntTag("Generator", $generatorType), @@ -197,7 +197,7 @@ class LevelDB extends BaseLevelProvider{ //Additional PocketMine-MP fields new CompoundTag("GameRules", []), - new ByteTag("hardcore", 0), + new ByteTag("hardcore", ($options["hardcore"] ?? false) === true ? 1 : 0), new StringTag("generatorName", Generator::getGeneratorName($generator)), new StringTag("generatorOptions", $options["preset"] ?? "") ]); @@ -253,6 +253,14 @@ class LevelDB extends BaseLevelProvider{ return ["preset" => $this->levelData["generatorOptions"]]; } + public function getDifficulty() : int{ + return isset($this->levelData->Difficulty) ? $this->levelData->Difficulty->getValue() : Level::DIFFICULTY_NORMAL; + } + + public function setDifficulty(int $difficulty){ + $this->levelData->Difficulty = new IntTag("Difficulty", $difficulty); + } + public function getLoadedChunks() : array{ return $this->chunks; } diff --git a/src/pocketmine/level/format/io/region/McRegion.php b/src/pocketmine/level/format/io/region/McRegion.php index a0ee72980..e157a7489 100644 --- a/src/pocketmine/level/format/io/region/McRegion.php +++ b/src/pocketmine/level/format/io/region/McRegion.php @@ -248,7 +248,8 @@ class McRegion extends BaseLevelProvider{ } //TODO, add extra details $levelData = new CompoundTag("Data", [ - new ByteTag("hardcore", 0), + new ByteTag("hardcore", ($options["hardcore"] ?? false) === true ? 1 : 0), + new ByteTag("Difficulty", Level::getDifficultyFromString((string) ($options["difficulty"] ?? "normal"))), new ByteTag("initialized", 1), new IntTag("GameType", 0), new IntTag("generatorVersion", 1), //2 in MCPE @@ -282,6 +283,14 @@ class McRegion extends BaseLevelProvider{ return ["preset" => $this->levelData["generatorOptions"]]; } + public function getDifficulty() : int{ + return isset($this->levelData->Difficulty) ? $this->levelData->Difficulty->getValue() : Level::DIFFICULTY_NORMAL; + } + + public function setDifficulty(int $difficulty){ + $this->levelData->Difficulty = new ByteTag("Difficulty", $difficulty); + } + public function getChunk(int $chunkX, int $chunkZ, bool $create = false){ $index = Level::chunkHash($chunkX, $chunkZ); if(isset($this->chunks[$index])){