Constify NBT keys in world data handling

this code is one giant mess that needs to be cleaned up though...
This commit is contained in:
Dylan K. Taylor 2022-12-19 13:39:41 +00:00
parent 4f86ea9933
commit 58eec637c1
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
3 changed files with 155 additions and 105 deletions

View File

@ -32,6 +32,15 @@ use pocketmine\world\format\io\WorldData;
use function file_exists;
abstract class BaseNbtWorldData implements WorldData{
protected const TAG_LEVEL_NAME = "LevelName";
protected const TAG_GENERATOR_NAME = "generatorName";
protected const TAG_GENERATOR_OPTIONS = "generatorOptions";
protected const TAG_RANDOM_SEED = "RandomSeed";
protected const TAG_TIME = "Time";
protected const TAG_SPAWN_X = "SpawnX";
protected const TAG_SPAWN_Y = "SpawnY";
protected const TAG_SPAWN_Z = "SpawnZ";
protected CompoundTag $compoundTag;
/**
@ -104,40 +113,40 @@ abstract class BaseNbtWorldData implements WorldData{
/* The below are common between PC and PE */
public function getName() : string{
return $this->compoundTag->getString("LevelName");
return $this->compoundTag->getString(self::TAG_LEVEL_NAME);
}
public function getGenerator() : string{
return $this->compoundTag->getString("generatorName", "DEFAULT");
return $this->compoundTag->getString(self::TAG_GENERATOR_NAME, "DEFAULT");
}
public function getGeneratorOptions() : string{
return $this->compoundTag->getString("generatorOptions", "");
return $this->compoundTag->getString(self::TAG_GENERATOR_OPTIONS, "");
}
public function getSeed() : int{
return $this->compoundTag->getLong("RandomSeed");
return $this->compoundTag->getLong(self::TAG_RANDOM_SEED);
}
public function getTime() : int{
if(($timeTag = $this->compoundTag->getTag("Time")) instanceof IntTag){ //some older PM worlds had this in the wrong format
if(($timeTag = $this->compoundTag->getTag(self::TAG_TIME)) instanceof IntTag){ //some older PM worlds had this in the wrong format
return $timeTag->getValue();
}
return $this->compoundTag->getLong("Time", 0);
return $this->compoundTag->getLong(self::TAG_TIME, 0);
}
public function setTime(int $value) : void{
$this->compoundTag->setLong("Time", $value);
$this->compoundTag->setLong(self::TAG_TIME, $value);
}
public function getSpawn() : Vector3{
return new Vector3($this->compoundTag->getInt("SpawnX"), $this->compoundTag->getInt("SpawnY"), $this->compoundTag->getInt("SpawnZ"));
return new Vector3($this->compoundTag->getInt(self::TAG_SPAWN_X), $this->compoundTag->getInt(self::TAG_SPAWN_Y), $this->compoundTag->getInt(self::TAG_SPAWN_Z));
}
public function setSpawn(Vector3 $pos) : void{
$this->compoundTag->setInt("SpawnX", $pos->getFloorX());
$this->compoundTag->setInt("SpawnY", $pos->getFloorY());
$this->compoundTag->setInt("SpawnZ", $pos->getFloorZ());
$this->compoundTag->setInt(self::TAG_SPAWN_X, $pos->getFloorX());
$this->compoundTag->setInt(self::TAG_SPAWN_Y, $pos->getFloorY());
$this->compoundTag->setInt(self::TAG_SPAWN_Z, $pos->getFloorZ());
}
}

View File

@ -54,6 +54,29 @@ class BedrockWorldData extends BaseNbtWorldData{
public const GENERATOR_INFINITE = 1;
public const GENERATOR_FLAT = 2;
private const TAG_DAY_CYCLE_STOP_TIME = "DayCycleStopTime";
private const TAG_DIFFICULTY = "Difficulty";
private const TAG_FORCE_GAME_TYPE = "ForceGameType";
private const TAG_GAME_TYPE = "GameType";
private const TAG_GENERATOR = "Generator";
private const TAG_LAST_PLAYED = "LastPlayed";
private const TAG_NETWORK_VERSION = "NetworkVersion";
private const TAG_STORAGE_VERSION = "StorageVersion";
private const TAG_IS_EDU = "eduLevel";
private const TAG_FALL_DAMAGE_ENABLED = "falldamage";
private const TAG_FIRE_DAMAGE_ENABLED = "firedamage";
private const TAG_ACHIEVEMENTS_DISABLED = "hasBeenLoadedInCreative";
private const TAG_IMMUTABLE_WORLD = "immutableWorld";
private const TAG_LIGHTNING_LEVEL = "lightningLevel";
private const TAG_LIGHTNING_TIME = "lightningTime";
private const TAG_PVP_ENABLED = "pvp";
private const TAG_RAIN_LEVEL = "rainLevel";
private const TAG_RAIN_TIME = "rainTime";
private const TAG_SPAWN_MOBS = "spawnMobs";
private const TAG_TEXTURE_PACKS_REQUIRED = "texturePacksRequired";
private const TAG_HARDCORE = "hardcore";
private const TAG_GAME_RULES = "GameRules";
public static function generate(string $path, string $name, WorldCreationOptions $options) : void{
switch($options->getGeneratorClass()){
case Flat::class:
@ -66,39 +89,39 @@ class BedrockWorldData extends BaseNbtWorldData{
$worldData = CompoundTag::create()
//Vanilla fields
->setInt("DayCycleStopTime", -1)
->setInt("Difficulty", $options->getDifficulty())
->setByte("ForceGameType", 0)
->setInt("GameType", 0)
->setInt("Generator", $generatorType)
->setLong("LastPlayed", time())
->setString("LevelName", $name)
->setInt("NetworkVersion", self::CURRENT_STORAGE_NETWORK_VERSION)
->setInt(self::TAG_DAY_CYCLE_STOP_TIME, -1)
->setInt(self::TAG_DIFFICULTY, $options->getDifficulty())
->setByte(self::TAG_FORCE_GAME_TYPE, 0)
->setInt(self::TAG_GAME_TYPE, 0)
->setInt(self::TAG_GENERATOR, $generatorType)
->setLong(self::TAG_LAST_PLAYED, time())
->setString(self::TAG_LEVEL_NAME, $name)
->setInt(self::TAG_NETWORK_VERSION, self::CURRENT_STORAGE_NETWORK_VERSION)
//->setInt("Platform", 2) //TODO: find out what the possible values are for
->setLong("RandomSeed", $options->getSeed())
->setInt("SpawnX", $options->getSpawnPosition()->getFloorX())
->setInt("SpawnY", $options->getSpawnPosition()->getFloorY())
->setInt("SpawnZ", $options->getSpawnPosition()->getFloorZ())
->setInt("StorageVersion", self::CURRENT_STORAGE_VERSION)
->setLong("Time", 0)
->setByte("eduLevel", 0)
->setByte("falldamage", 1)
->setByte("firedamage", 1)
->setByte("hasBeenLoadedInCreative", 1) //badly named, this actually determines whether achievements can be earned in this world...
->setByte("immutableWorld", 0)
->setFloat("lightningLevel", 0.0)
->setInt("lightningTime", 0)
->setByte("pvp", 1)
->setFloat("rainLevel", 0.0)
->setInt("rainTime", 0)
->setByte("spawnMobs", 1)
->setByte("texturePacksRequired", 0) //TODO
->setLong(self::TAG_RANDOM_SEED, $options->getSeed())
->setInt(self::TAG_SPAWN_X, $options->getSpawnPosition()->getFloorX())
->setInt(self::TAG_SPAWN_Y, $options->getSpawnPosition()->getFloorY())
->setInt(self::TAG_SPAWN_Z, $options->getSpawnPosition()->getFloorZ())
->setInt(self::TAG_STORAGE_VERSION, self::CURRENT_STORAGE_VERSION)
->setLong(self::TAG_TIME, 0)
->setByte(self::TAG_IS_EDU, 0)
->setByte(self::TAG_FALL_DAMAGE_ENABLED, 1)
->setByte(self::TAG_FIRE_DAMAGE_ENABLED, 1)
->setByte(self::TAG_ACHIEVEMENTS_DISABLED, 1) //badly named, this actually determines whether achievements can be earned in this world...
->setByte(self::TAG_IMMUTABLE_WORLD, 0)
->setFloat(self::TAG_LIGHTNING_LEVEL, 0.0)
->setInt(self::TAG_LIGHTNING_TIME, 0)
->setByte(self::TAG_PVP_ENABLED, 1)
->setFloat(self::TAG_RAIN_LEVEL, 0.0)
->setInt(self::TAG_RAIN_TIME, 0)
->setByte(self::TAG_SPAWN_MOBS, 1)
->setByte(self::TAG_TEXTURE_PACKS_REQUIRED, 0) //TODO
//Additional PocketMine-MP fields
->setTag("GameRules", new CompoundTag())
->setByte("hardcore", 0)
->setString("generatorName", GeneratorManager::getInstance()->getGeneratorName($options->getGeneratorClass()))
->setString("generatorOptions", $options->getGeneratorOptions());
->setTag(self::TAG_GAME_RULES, new CompoundTag())
->setByte(self::TAG_HARDCORE, 0)
->setString(self::TAG_GENERATOR_NAME, GeneratorManager::getInstance()->getGeneratorName($options->getGeneratorClass()))
->setString(self::TAG_GENERATOR_OPTIONS, $options->getGeneratorOptions());
$nbt = new LittleEndianNbtSerializer();
$buffer = $nbt->write(new TreeRoot($worldData));
@ -120,7 +143,7 @@ class BedrockWorldData extends BaseNbtWorldData{
throw new CorruptedWorldException($e->getMessage(), 0, $e);
}
$version = $worldData->getInt("StorageVersion", Limits::INT32_MAX);
$version = $worldData->getInt(self::TAG_STORAGE_VERSION, Limits::INT32_MAX);
if($version > self::CURRENT_STORAGE_VERSION){
throw new UnsupportedWorldFormatException("LevelDB world format version $version is currently unsupported");
}
@ -129,18 +152,18 @@ class BedrockWorldData extends BaseNbtWorldData{
}
protected function fix() : void{
$generatorNameTag = $this->compoundTag->getTag("generatorName");
$generatorNameTag = $this->compoundTag->getTag(self::TAG_GENERATOR_NAME);
if(!($generatorNameTag instanceof StringTag)){
if(($mcpeGeneratorTypeTag = $this->compoundTag->getTag("Generator")) instanceof IntTag){
if(($mcpeGeneratorTypeTag = $this->compoundTag->getTag(self::TAG_GENERATOR)) instanceof IntTag){
switch($mcpeGeneratorTypeTag->getValue()){ //Detect correct generator from MCPE data
case self::GENERATOR_FLAT:
$this->compoundTag->setString("generatorName", "flat");
$this->compoundTag->setString("generatorOptions", "2;7,3,3,2;1");
$this->compoundTag->setString(self::TAG_GENERATOR_NAME, "flat");
$this->compoundTag->setString(self::TAG_GENERATOR_OPTIONS, "2;7,3,3,2;1");
break;
case self::GENERATOR_INFINITE:
//TODO: add a null generator which does not generate missing chunks (to allow importing back to MCPE and generating more normal terrain without PocketMine messing things up)
$this->compoundTag->setString("generatorName", "default");
$this->compoundTag->setString("generatorOptions", "");
$this->compoundTag->setString(self::TAG_GENERATOR_NAME, "default");
$this->compoundTag->setString(self::TAG_GENERATOR_OPTIONS, "");
break;
case self::GENERATOR_LIMITED:
throw new UnsupportedWorldFormatException("Limited worlds are not currently supported");
@ -148,20 +171,20 @@ class BedrockWorldData extends BaseNbtWorldData{
throw new UnsupportedWorldFormatException("Unknown LevelDB generator type");
}
}else{
$this->compoundTag->setString("generatorName", "default");
$this->compoundTag->setString(self::TAG_GENERATOR_NAME, "default");
}
}elseif(($generatorName = self::hackyFixForGeneratorClasspathInLevelDat($generatorNameTag->getValue())) !== null){
$this->compoundTag->setString("generatorName", $generatorName);
$this->compoundTag->setString(self::TAG_GENERATOR_NAME, $generatorName);
}
if(!($this->compoundTag->getTag("generatorOptions")) instanceof StringTag){
$this->compoundTag->setString("generatorOptions", "");
if(!($this->compoundTag->getTag(self::TAG_GENERATOR_OPTIONS)) instanceof StringTag){
$this->compoundTag->setString(self::TAG_GENERATOR_OPTIONS, "");
}
}
public function save() : void{
$this->compoundTag->setInt("NetworkVersion", self::CURRENT_STORAGE_NETWORK_VERSION);
$this->compoundTag->setInt("StorageVersion", self::CURRENT_STORAGE_VERSION);
$this->compoundTag->setInt(self::TAG_NETWORK_VERSION, self::CURRENT_STORAGE_NETWORK_VERSION);
$this->compoundTag->setInt(self::TAG_STORAGE_VERSION, self::CURRENT_STORAGE_VERSION);
$nbt = new LittleEndianNbtSerializer();
$buffer = $nbt->write(new TreeRoot($this->compoundTag));
@ -169,42 +192,42 @@ class BedrockWorldData extends BaseNbtWorldData{
}
public function getDifficulty() : int{
return $this->compoundTag->getInt("Difficulty", World::DIFFICULTY_NORMAL);
return $this->compoundTag->getInt(self::TAG_DIFFICULTY, World::DIFFICULTY_NORMAL);
}
public function setDifficulty(int $difficulty) : void{
$this->compoundTag->setInt("Difficulty", $difficulty); //yes, this is intended! (in PE: int, PC: byte)
$this->compoundTag->setInt(self::TAG_DIFFICULTY, $difficulty); //yes, this is intended! (in PE: int, PC: byte)
}
public function getRainTime() : int{
return $this->compoundTag->getInt("rainTime", 0);
return $this->compoundTag->getInt(self::TAG_RAIN_TIME, 0);
}
public function setRainTime(int $ticks) : void{
$this->compoundTag->setInt("rainTime", $ticks);
$this->compoundTag->setInt(self::TAG_RAIN_TIME, $ticks);
}
public function getRainLevel() : float{
return $this->compoundTag->getFloat("rainLevel", 0.0);
return $this->compoundTag->getFloat(self::TAG_RAIN_LEVEL, 0.0);
}
public function setRainLevel(float $level) : void{
$this->compoundTag->setFloat("rainLevel", $level);
$this->compoundTag->setFloat(self::TAG_RAIN_LEVEL, $level);
}
public function getLightningTime() : int{
return $this->compoundTag->getInt("lightningTime", 0);
return $this->compoundTag->getInt(self::TAG_LIGHTNING_TIME, 0);
}
public function setLightningTime(int $ticks) : void{
$this->compoundTag->setInt("lightningTime", $ticks);
$this->compoundTag->setInt(self::TAG_LIGHTNING_TIME, $ticks);
}
public function getLightningLevel() : float{
return $this->compoundTag->getFloat("lightningLevel", 0.0);
return $this->compoundTag->getFloat(self::TAG_LIGHTNING_LEVEL, 0.0);
}
public function setLightningLevel(float $level) : void{
$this->compoundTag->setFloat("lightningLevel", $level);
$this->compoundTag->setFloat(self::TAG_LIGHTNING_LEVEL, $level);
}
}

View File

@ -46,31 +46,49 @@ use const ZLIB_ENCODING_GZIP;
class JavaWorldData extends BaseNbtWorldData{
private const TAG_DAY_TIME = "DayTime";
private const TAG_DIFFICULTY = "Difficulty";
private const TAG_FORMAT_VERSION = "version";
private const TAG_GAME_RULES = "GameRules";
private const TAG_GAME_TYPE = "GameType";
private const TAG_GENERATOR_VERSION = "generatorVersion";
private const TAG_HARDCORE = "hardcore";
private const TAG_INITIALIZED = "initialized";
private const TAG_LAST_PLAYED = "LastPlayed";
private const TAG_LIGHTNING_LEVEL = "lightningLevel";
private const TAG_RAINING = "raining";
private const TAG_RAIN_LEVEL = "rainLevel";
private const TAG_RAIN_TIME = "rainTime";
private const TAG_ROOT_DATA = "Data";
private const TAG_SIZE_ON_DISK = "SizeOnDisk";
private const TAG_THUNDERING = "thundering";
private const TAG_THUNDER_TIME = "thunderTime";
public static function generate(string $path, string $name, WorldCreationOptions $options, int $version = 19133) : void{
//TODO, add extra details
$worldData = CompoundTag::create()
->setByte("hardcore", 0)
->setByte("Difficulty", $options->getDifficulty())
->setByte("initialized", 1)
->setInt("GameType", 0)
->setInt("generatorVersion", 1) //2 in MCPE
->setInt("SpawnX", $options->getSpawnPosition()->getFloorX())
->setInt("SpawnY", $options->getSpawnPosition()->getFloorY())
->setInt("SpawnZ", $options->getSpawnPosition()->getFloorZ())
->setInt("version", $version)
->setInt("DayTime", 0)
->setLong("LastPlayed", (int) (microtime(true) * 1000))
->setLong("RandomSeed", $options->getSeed())
->setLong("SizeOnDisk", 0)
->setLong("Time", 0)
->setString("generatorName", GeneratorManager::getInstance()->getGeneratorName($options->getGeneratorClass()))
->setString("generatorOptions", $options->getGeneratorOptions())
->setString("LevelName", $name)
->setTag("GameRules", new CompoundTag());
->setByte(self::TAG_HARDCORE, 0)
->setByte(self::TAG_DIFFICULTY, $options->getDifficulty())
->setByte(self::TAG_INITIALIZED, 1)
->setInt(self::TAG_GAME_TYPE, 0)
->setInt(self::TAG_GENERATOR_VERSION, 1) //2 in MCPE
->setInt(self::TAG_SPAWN_X, $options->getSpawnPosition()->getFloorX())
->setInt(self::TAG_SPAWN_Y, $options->getSpawnPosition()->getFloorY())
->setInt(self::TAG_SPAWN_Z, $options->getSpawnPosition()->getFloorZ())
->setInt(self::TAG_FORMAT_VERSION, $version)
->setInt(self::TAG_DAY_TIME, 0)
->setLong(self::TAG_LAST_PLAYED, (int) (microtime(true) * 1000))
->setLong(self::TAG_RANDOM_SEED, $options->getSeed())
->setLong(self::TAG_SIZE_ON_DISK, 0)
->setLong(self::TAG_TIME, 0)
->setString(self::TAG_GENERATOR_NAME, GeneratorManager::getInstance()->getGeneratorName($options->getGeneratorClass()))
->setString(self::TAG_GENERATOR_OPTIONS, $options->getGeneratorOptions())
->setString(self::TAG_LEVEL_NAME, $name)
->setTag(self::TAG_GAME_RULES, new CompoundTag());
$nbt = new BigEndianNbtSerializer();
$buffer = zlib_encode($nbt->write(new TreeRoot(CompoundTag::create()->setTag("Data", $worldData))), ZLIB_ENCODING_GZIP);
$buffer = zlib_encode($nbt->write(new TreeRoot(CompoundTag::create()->setTag(self::TAG_ROOT_DATA, $worldData))), ZLIB_ENCODING_GZIP);
file_put_contents(Path::join($path, "level.dat"), $buffer);
}
@ -90,79 +108,79 @@ class JavaWorldData extends BaseNbtWorldData{
throw new CorruptedWorldException($e->getMessage(), 0, $e);
}
$dataTag = $worldData->getTag("Data");
$dataTag = $worldData->getTag(self::TAG_ROOT_DATA);
if(!($dataTag instanceof CompoundTag)){
throw new CorruptedWorldException("Missing 'Data' key or wrong type");
throw new CorruptedWorldException("Missing '" . self::TAG_ROOT_DATA . "' key or wrong type");
}
return $dataTag;
}
protected function fix() : void{
$generatorNameTag = $this->compoundTag->getTag("generatorName");
$generatorNameTag = $this->compoundTag->getTag(self::TAG_GENERATOR_NAME);
if(!($generatorNameTag instanceof StringTag)){
$this->compoundTag->setString("generatorName", "default");
$this->compoundTag->setString(self::TAG_GENERATOR_NAME, "default");
}elseif(($generatorName = self::hackyFixForGeneratorClasspathInLevelDat($generatorNameTag->getValue())) !== null){
$this->compoundTag->setString("generatorName", $generatorName);
$this->compoundTag->setString(self::TAG_GENERATOR_NAME, $generatorName);
}
if(!($this->compoundTag->getTag("generatorOptions") instanceof StringTag)){
$this->compoundTag->setString("generatorOptions", "");
if(!($this->compoundTag->getTag(self::TAG_GENERATOR_OPTIONS) instanceof StringTag)){
$this->compoundTag->setString(self::TAG_GENERATOR_OPTIONS, "");
}
}
public function save() : void{
$nbt = new BigEndianNbtSerializer();
$buffer = Utils::assumeNotFalse(zlib_encode($nbt->write(new TreeRoot(CompoundTag::create()->setTag("Data", $this->compoundTag))), ZLIB_ENCODING_GZIP));
$buffer = Utils::assumeNotFalse(zlib_encode($nbt->write(new TreeRoot(CompoundTag::create()->setTag(self::TAG_ROOT_DATA, $this->compoundTag))), ZLIB_ENCODING_GZIP));
Filesystem::safeFilePutContents($this->dataPath, $buffer);
}
public function getDifficulty() : int{
return $this->compoundTag->getByte("Difficulty", World::DIFFICULTY_NORMAL);
return $this->compoundTag->getByte(self::TAG_DIFFICULTY, World::DIFFICULTY_NORMAL);
}
public function setDifficulty(int $difficulty) : void{
$this->compoundTag->setByte("Difficulty", $difficulty);
$this->compoundTag->setByte(self::TAG_DIFFICULTY, $difficulty);
}
public function getRainTime() : int{
return $this->compoundTag->getInt("rainTime", 0);
return $this->compoundTag->getInt(self::TAG_RAIN_TIME, 0);
}
public function setRainTime(int $ticks) : void{
$this->compoundTag->setInt("rainTime", $ticks);
$this->compoundTag->setInt(self::TAG_RAIN_TIME, $ticks);
}
public function getRainLevel() : float{
if(($rainLevelTag = $this->compoundTag->getTag("rainLevel")) instanceof FloatTag){ //PocketMine/MCPE
if(($rainLevelTag = $this->compoundTag->getTag(self::TAG_RAIN_LEVEL)) instanceof FloatTag){ //PocketMine/MCPE
return $rainLevelTag->getValue();
}
return (float) $this->compoundTag->getByte("raining", 0); //PC vanilla
return (float) $this->compoundTag->getByte(self::TAG_RAINING, 0); //PC vanilla
}
public function setRainLevel(float $level) : void{
$this->compoundTag->setFloat("rainLevel", $level); //PocketMine/MCPE
$this->compoundTag->setByte("raining", (int) ceil($level)); //PC vanilla
$this->compoundTag->setFloat(self::TAG_RAIN_LEVEL, $level); //PocketMine/MCPE
$this->compoundTag->setByte(self::TAG_RAINING, (int) ceil($level)); //PC vanilla
}
public function getLightningTime() : int{
return $this->compoundTag->getInt("thunderTime", 0);
return $this->compoundTag->getInt(self::TAG_THUNDER_TIME, 0);
}
public function setLightningTime(int $ticks) : void{
$this->compoundTag->setInt("thunderTime", $ticks);
$this->compoundTag->setInt(self::TAG_THUNDER_TIME, $ticks);
}
public function getLightningLevel() : float{
if(($lightningLevelTag = $this->compoundTag->getTag("lightningLevel")) instanceof FloatTag){ //PocketMine/MCPE
if(($lightningLevelTag = $this->compoundTag->getTag(self::TAG_LIGHTNING_LEVEL)) instanceof FloatTag){ //PocketMine/MCPE
return $lightningLevelTag->getValue();
}
return (float) $this->compoundTag->getByte("thundering", 0); //PC vanilla
return (float) $this->compoundTag->getByte(self::TAG_THUNDERING, 0); //PC vanilla
}
public function setLightningLevel(float $level) : void{
$this->compoundTag->setFloat("lightningLevel", $level); //PocketMine/MCPE
$this->compoundTag->setByte("thundering", (int) ceil($level)); //PC vanilla
$this->compoundTag->setFloat(self::TAG_LIGHTNING_LEVEL, $level); //PocketMine/MCPE
$this->compoundTag->setByte(self::TAG_THUNDERING, (int) ceil($level)); //PC vanilla
}
}