Implement difficulty per-world (#878)

* Moved Server::getDifficultyFromString() to Level
* Added ability to set difficulty in worlds section of pocketmine.yml for generation
This commit is contained in:
Dylan K. Taylor 2017-09-26 11:16:51 +01:00 committed by GitHub
parent e64076ec81
commit 38fad4b963
9 changed files with 132 additions and 50 deletions

View File

@ -803,6 +803,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->usedChunks = []; $this->usedChunks = [];
$this->level->sendTime($this); $this->level->sendTime($this);
$this->level->sendDifficulty($this);
return true; return true;
} }
@ -1924,7 +1925,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$pk->seed = -1; $pk->seed = -1;
$pk->dimension = DimensionIds::OVERWORLD; //TODO: implement this properly $pk->dimension = DimensionIds::OVERWORLD; //TODO: implement this properly
$pk->worldGamemode = Player::getClientFriendlyGamemode($this->server->getGamemode()); $pk->worldGamemode = Player::getClientFriendlyGamemode($this->server->getGamemode());
$pk->difficulty = $this->server->getDifficulty(); $pk->difficulty = $this->level->getDifficulty();
$pk->spawnX = $spawnPosition->getFloorX(); $pk->spawnX = $spawnPosition->getFloorX();
$pk->spawnY = $spawnPosition->getFloorY(); $pk->spawnY = $spawnPosition->getFloorY();
$pk->spawnZ = $spawnPosition->getFloorZ(); $pk->spawnZ = $spawnPosition->getFloorZ();
@ -2427,7 +2428,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}elseif($target instanceof Player){ }elseif($target instanceof Player){
if(($target->getGamemode() & 0x01) > 0){ if(($target->getGamemode() & 0x01) > 0){
return true; 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; $cancelled = true;
} }

View File

@ -515,36 +515,17 @@ class Server{
} }
/** /**
* @param string $str * @deprecated Moved to {@link Level#getDifficultyFromString}
* *
* @param string $str
* @return int * @return int
*/ */
public static function getDifficultyFromString(string $str) : int{ public static function getDifficultyFromString(string $str) : int{
switch(strtolower(trim($str))){ return Level::getDifficultyFromString($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;
} }
/** /**
* Returns Server global difficulty. Note that this may be overridden in individual Levels.
* @return int * @return int
*/ */
public function getDifficulty() : int{ public function getDifficulty() : int{
@ -1589,8 +1570,8 @@ class Server{
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.authProperty", ["enable", "true"])); $this->logger->warning($this->getLanguage()->translateString("pocketmine.server.authProperty", ["enable", "true"]));
} }
if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){ if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < Level::DIFFICULTY_HARD){
$this->setConfigInt("difficulty", 3); $this->setConfigInt("difficulty", Level::DIFFICULTY_HARD);
} }
if(\pocketmine\DEBUG >= 0){ if(\pocketmine\DEBUG >= 0){
@ -1671,21 +1652,23 @@ class Server{
foreach((array) $this->getProperty("worlds", []) as $name => $worldSetting){ foreach((array) $this->getProperty("worlds", []) as $name => $worldSetting){
if($this->loadLevel($name) === false){ 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)){ if(is_string($seed) and !is_numeric($seed)){
$seed = Utils::javaStringHash($seed); $seed = Utils::javaStringHash($seed);
}elseif(!is_int($seed)){ }elseif(!is_int($seed)){
$seed = (int) $seed; $seed = (int) $seed;
} }
$options = explode(":", $this->getProperty("worlds.$name.generator", Generator::getGenerator("default"))); if(isset($options["generator"])){
$generator = Generator::getGenerator(array_shift($options)); $generatorOptions = explode(":", $options["generator"]);
if(count($options) > 0){ $generator = Generator::getGenerator(array_shift($generatorOptions));
$options = [ if(count($options) > 0){
"preset" => implode(":", $options) $options["preset"] = implode(":", $generatorOptions);
]; }
}else{ }else{
$options = []; $generator = Generator::getGenerator("default");
} }
$this->generateLevel($name, $seed, $generator, $options); $this->generateLevel($name, $seed, $generator, $options);
@ -2006,8 +1989,8 @@ class Server{
$this->properties->reload(); $this->properties->reload();
$this->maxPlayers = $this->getConfigInt("max-players", 20); $this->maxPlayers = $this->getConfigInt("max-players", 20);
if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){ if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < Level::DIFFICULTY_HARD){
$this->setConfigInt("difficulty", 3); $this->setConfigInt("difficulty", Level::DIFFICULTY_HARD);
} }
$this->banByIP->load(); $this->banByIP->load();

View File

@ -27,8 +27,7 @@ use pocketmine\command\Command;
use pocketmine\command\CommandSender; use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\event\TranslationContainer; use pocketmine\event\TranslationContainer;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket; use pocketmine\level\Level;
use pocketmine\Server;
class DifficultyCommand extends VanillaCommand{ class DifficultyCommand extends VanillaCommand{
@ -50,18 +49,19 @@ class DifficultyCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException(); throw new InvalidCommandSyntaxException();
} }
$difficulty = Server::getDifficultyFromString($args[0]); $difficulty = Level::getDifficultyFromString($args[0]);
if($sender->getServer()->isHardcore()){ if($sender->getServer()->isHardcore()){
$difficulty = 3; $difficulty = Level::DIFFICULTY_HARD;
} }
if($difficulty !== -1){ if($difficulty !== -1){
$sender->getServer()->setConfigInt("difficulty", $difficulty); $sender->getServer()->setConfigInt("difficulty", $difficulty);
$pk = new SetDifficultyPacket(); //TODO: add per-world support
$pk->difficulty = $sender->getServer()->getDifficulty(); foreach($sender->getServer()->getLevels() as $level){
$sender->getServer()->broadcastPacket($sender->getServer()->getOnlinePlayers(), $pk); $level->setDifficulty($difficulty);
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.difficulty.success", [$difficulty])); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.difficulty.success", [$difficulty]));
}else{ }else{

View File

@ -391,14 +391,14 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
if($this->isAlive()){ if($this->isAlive()){
$food = $this->getFood(); $food = $this->getFood();
$health = $this->getHealth(); $health = $this->getHealth();
$difficulty = $this->server->getDifficulty(); $difficulty = $this->level->getDifficulty();
$this->foodTickTimer += $tickDiff; $this->foodTickTimer += $tickDiff;
if($this->foodTickTimer >= 80){ if($this->foodTickTimer >= 80){
$this->foodTickTimer = 0; $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){ if($food < 20){
$this->addFood(1.0); $this->addFood(1.0);
} }

View File

@ -362,7 +362,7 @@ abstract class Living extends Entity implements Damageable{
if($e !== null){ if($e !== null){
if($e->isOnFire() > 0){ if($e->isOnFire() > 0){
$this->setOnFire(2 * $this->server->getDifficulty()); $this->setOnFire(2 * $this->level->getDifficulty());
} }
$deltaX = $this->x - $e->x; $deltaX = $this->x - $e->x;

View File

@ -77,6 +77,7 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\DataPacket; use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket; use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket; use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket; use pocketmine\network\mcpe\protocol\SetTimePacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket; use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\Player; use pocketmine\Player;
@ -112,6 +113,11 @@ class Level implements ChunkManager, Metadatable{
const TIME_FULL = 24000; const TIME_FULL = 24000;
const DIFFICULTY_PEACEFUL = 0;
const DIFFICULTY_EASY = 1;
const DIFFICULTY_NORMAL = 2;
const DIFFICULTY_HARD = 3;
/** @var Tile[] */ /** @var Tile[] */
private $tiles = []; private $tiles = [];
@ -224,6 +230,8 @@ class Level implements ChunkManager, Metadatable{
private $closed = false; private $closed = false;
public static function chunkHash(int $x, int $z){ public static function chunkHash(int $x, int $z){
return (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF); 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 * Init the default level data
* *
@ -2742,6 +2780,37 @@ class Level implements ChunkManager, Metadatable{
return $this->provider->getWorldHeight(); 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{ 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)){ if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)]) or (count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force)){

View File

@ -206,6 +206,18 @@ interface LevelProvider{
*/ */
public function setSpawn(Vector3 $pos); 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[] * @return Chunk[]
*/ */

View File

@ -168,7 +168,7 @@ class LevelDB extends BaseLevelProvider{
$levelData = new CompoundTag("", [ $levelData = new CompoundTag("", [
//Vanilla fields //Vanilla fields
new IntTag("DayCycleStopTime", -1), new IntTag("DayCycleStopTime", -1),
new IntTag("Difficulty", 2), new IntTag("Difficulty", Level::getDifficultyFromString((string) ($options["difficulty"] ?? "normal"))),
new ByteTag("ForceGameType", 0), new ByteTag("ForceGameType", 0),
new IntTag("GameType", 0), new IntTag("GameType", 0),
new IntTag("Generator", $generatorType), new IntTag("Generator", $generatorType),
@ -197,7 +197,7 @@ class LevelDB extends BaseLevelProvider{
//Additional PocketMine-MP fields //Additional PocketMine-MP fields
new CompoundTag("GameRules", []), new CompoundTag("GameRules", []),
new ByteTag("hardcore", 0), new ByteTag("hardcore", ($options["hardcore"] ?? false) === true ? 1 : 0),
new StringTag("generatorName", Generator::getGeneratorName($generator)), new StringTag("generatorName", Generator::getGeneratorName($generator)),
new StringTag("generatorOptions", $options["preset"] ?? "") new StringTag("generatorOptions", $options["preset"] ?? "")
]); ]);
@ -253,6 +253,14 @@ class LevelDB extends BaseLevelProvider{
return ["preset" => $this->levelData["generatorOptions"]]; 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{ public function getLoadedChunks() : array{
return $this->chunks; return $this->chunks;
} }

View File

@ -248,7 +248,8 @@ class McRegion extends BaseLevelProvider{
} }
//TODO, add extra details //TODO, add extra details
$levelData = new CompoundTag("Data", [ $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 ByteTag("initialized", 1),
new IntTag("GameType", 0), new IntTag("GameType", 0),
new IntTag("generatorVersion", 1), //2 in MCPE new IntTag("generatorVersion", 1), //2 in MCPE
@ -282,6 +283,14 @@ class McRegion extends BaseLevelProvider{
return ["preset" => $this->levelData["generatorOptions"]]; 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){ public function getChunk(int $chunkX, int $chunkZ, bool $create = false){
$index = Level::chunkHash($chunkX, $chunkZ); $index = Level::chunkHash($chunkX, $chunkZ);
if(isset($this->chunks[$index])){ if(isset($this->chunks[$index])){