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->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;
}

View File

@ -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();

View File

@ -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{

View File

@ -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);
}

View File

@ -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;

View File

@ -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)){

View File

@ -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[]
*/

View File

@ -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;
}

View File

@ -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])){