PocketMine-MP 5.15.1 git-5ef247620a7c6301a849b54e5ef1009217729fc8
BedrockWorldData.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\world\format\io\data;
25
43use Symfony\Component\Filesystem\Path;
44use function array_map;
45use function file_put_contents;
46use function sprintf;
47use function strlen;
48use function substr;
49use function time;
50
52
53 public const CURRENT_STORAGE_VERSION = 10;
54 public const CURRENT_STORAGE_NETWORK_VERSION = 671;
55 public const CURRENT_CLIENT_VERSION_TARGET = [
56 1, //major
57 20, //minor
58 80, //patch
59 5, //revision
60 0 //is beta
61 ];
62
63 public const GENERATOR_LIMITED = 0;
64 public const GENERATOR_INFINITE = 1;
65 public const GENERATOR_FLAT = 2;
66
67 private const TAG_DAY_CYCLE_STOP_TIME = "DayCycleStopTime";
68 private const TAG_DIFFICULTY = "Difficulty";
69 private const TAG_FORCE_GAME_TYPE = "ForceGameType";
70 private const TAG_GAME_TYPE = "GameType";
71 private const TAG_GENERATOR = "Generator";
72 private const TAG_LAST_PLAYED = "LastPlayed";
73 private const TAG_NETWORK_VERSION = "NetworkVersion";
74 private const TAG_STORAGE_VERSION = "StorageVersion";
75 private const TAG_IS_EDU = "eduLevel";
76 private const TAG_FALL_DAMAGE_ENABLED = "falldamage";
77 private const TAG_FIRE_DAMAGE_ENABLED = "firedamage";
78 private const TAG_ACHIEVEMENTS_DISABLED = "hasBeenLoadedInCreative";
79 private const TAG_IMMUTABLE_WORLD = "immutableWorld";
80 private const TAG_LIGHTNING_LEVEL = "lightningLevel";
81 private const TAG_LIGHTNING_TIME = "lightningTime";
82 private const TAG_PVP_ENABLED = "pvp";
83 private const TAG_RAIN_LEVEL = "rainLevel";
84 private const TAG_RAIN_TIME = "rainTime";
85 private const TAG_SPAWN_MOBS = "spawnMobs";
86 private const TAG_TEXTURE_PACKS_REQUIRED = "texturePacksRequired";
87 private const TAG_LAST_OPENED_WITH_VERSION = "lastOpenedWithVersion";
88 private const TAG_COMMANDS_ENABLED = "commandsEnabled";
89
90 public static function generate(string $path, string $name, WorldCreationOptions $options) : void{
91 switch($options->getGeneratorClass()){
92 case Flat::class:
93 $generatorType = self::GENERATOR_FLAT;
94 break;
95 default:
96 $generatorType = self::GENERATOR_INFINITE;
97 //TODO: add support for limited worlds
98 }
99
100 $worldData = CompoundTag::create()
101 //Vanilla fields
102 ->setInt(self::TAG_DAY_CYCLE_STOP_TIME, -1)
103 ->setInt(self::TAG_DIFFICULTY, $options->getDifficulty())
104 ->setByte(self::TAG_FORCE_GAME_TYPE, 0)
105 ->setInt(self::TAG_GAME_TYPE, 0)
106 ->setInt(self::TAG_GENERATOR, $generatorType)
107 ->setLong(self::TAG_LAST_PLAYED, time())
108 ->setString(self::TAG_LEVEL_NAME, $name)
109 ->setInt(self::TAG_NETWORK_VERSION, self::CURRENT_STORAGE_NETWORK_VERSION)
110 //->setInt("Platform", 2) //TODO: find out what the possible values are for
111 ->setLong(self::TAG_RANDOM_SEED, $options->getSeed())
112 ->setInt(self::TAG_SPAWN_X, $options->getSpawnPosition()->getFloorX())
113 ->setInt(self::TAG_SPAWN_Y, $options->getSpawnPosition()->getFloorY())
114 ->setInt(self::TAG_SPAWN_Z, $options->getSpawnPosition()->getFloorZ())
115 ->setInt(self::TAG_STORAGE_VERSION, self::CURRENT_STORAGE_VERSION)
116 ->setLong(self::TAG_TIME, 0)
117 ->setByte(self::TAG_IS_EDU, 0)
118 ->setByte(self::TAG_FALL_DAMAGE_ENABLED, 1)
119 ->setByte(self::TAG_FIRE_DAMAGE_ENABLED, 1)
120 ->setByte(self::TAG_ACHIEVEMENTS_DISABLED, 1) //badly named, this actually determines whether achievements can be earned in this world...
121 ->setByte(self::TAG_IMMUTABLE_WORLD, 0)
122 ->setFloat(self::TAG_LIGHTNING_LEVEL, 0.0)
123 ->setInt(self::TAG_LIGHTNING_TIME, 0)
124 ->setByte(self::TAG_PVP_ENABLED, 1)
125 ->setFloat(self::TAG_RAIN_LEVEL, 0.0)
126 ->setInt(self::TAG_RAIN_TIME, 0)
127 ->setByte(self::TAG_SPAWN_MOBS, 1)
128 ->setByte(self::TAG_TEXTURE_PACKS_REQUIRED, 0) //TODO
129 ->setByte(self::TAG_COMMANDS_ENABLED, 1)
130 ->setTag(self::TAG_LAST_OPENED_WITH_VERSION, new ListTag(array_map(fn(int $v) => new IntTag($v), self::CURRENT_CLIENT_VERSION_TARGET)))
131
132 //Additional PocketMine-MP fields
133 ->setString(self::TAG_GENERATOR_NAME, GeneratorManager::getInstance()->getGeneratorName($options->getGeneratorClass()))
134 ->setString(self::TAG_GENERATOR_OPTIONS, $options->getGeneratorOptions());
135
136 $nbt = new LittleEndianNbtSerializer();
137 $buffer = $nbt->write(new TreeRoot($worldData));
138 file_put_contents(Path::join($path, "level.dat"), Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer);
139 }
140
141 protected function load() : CompoundTag{
142 try{
143 $rawLevelData = Filesystem::fileGetContents($this->dataPath);
144 }catch(\RuntimeException $e){
145 throw new CorruptedWorldException($e->getMessage(), 0, $e);
146 }
147 if(strlen($rawLevelData) <= 8){
148 throw new CorruptedWorldException("Truncated level.dat");
149 }
150 $nbt = new LittleEndianNbtSerializer();
151 try{
152 $worldData = $nbt->read(substr($rawLevelData, 8))->mustGetCompoundTag();
153 }catch(NbtDataException $e){
154 throw new CorruptedWorldException($e->getMessage(), 0, $e);
155 }
156
157 $version = $worldData->getInt(self::TAG_STORAGE_VERSION, Limits::INT32_MAX);
158 if($version === Limits::INT32_MAX){
159 throw new CorruptedWorldException(sprintf("Missing '%s' tag in level.dat", self::TAG_STORAGE_VERSION));
160 }
161 if($version > self::CURRENT_STORAGE_VERSION){
162 throw new UnsupportedWorldFormatException("LevelDB world format version $version is currently unsupported");
163 }
164 //StorageVersion is rarely updated - instead, the game relies on the NetworkVersion tag, which is synced with
165 //the network protocol version for that version.
166 $protocolVersion = $worldData->getInt(self::TAG_NETWORK_VERSION, Limits::INT32_MAX);
167 if($protocolVersion === Limits::INT32_MAX){
168 throw new CorruptedWorldException(sprintf("Missing '%s' tag in level.dat", self::TAG_NETWORK_VERSION));
169 }
170 if($protocolVersion > self::CURRENT_STORAGE_NETWORK_VERSION){
171 throw new UnsupportedWorldFormatException("LevelDB world protocol version $protocolVersion is currently unsupported");
172 }
173
174 return $worldData;
175 }
176
177 protected function fix() : void{
178 $generatorNameTag = $this->compoundTag->getTag(self::TAG_GENERATOR_NAME);
179 if(!($generatorNameTag instanceof StringTag)){
180 if(($mcpeGeneratorTypeTag = $this->compoundTag->getTag(self::TAG_GENERATOR)) instanceof IntTag){
181 switch($mcpeGeneratorTypeTag->getValue()){ //Detect correct generator from MCPE data
182 case self::GENERATOR_FLAT:
183 $this->compoundTag->setString(self::TAG_GENERATOR_NAME, "flat");
184 $this->compoundTag->setString(self::TAG_GENERATOR_OPTIONS, "2;7,3,3,2;1");
185 break;
186 case self::GENERATOR_INFINITE:
187 //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)
188 $this->compoundTag->setString(self::TAG_GENERATOR_NAME, "default");
189 $this->compoundTag->setString(self::TAG_GENERATOR_OPTIONS, "");
190 break;
191 case self::GENERATOR_LIMITED:
192 throw new UnsupportedWorldFormatException("Limited worlds are not currently supported");
193 default:
194 throw new UnsupportedWorldFormatException("Unknown LevelDB generator type");
195 }
196 }else{
197 $this->compoundTag->setString(self::TAG_GENERATOR_NAME, "default");
198 }
199 }elseif(($generatorName = self::hackyFixForGeneratorClasspathInLevelDat($generatorNameTag->getValue())) !== null){
200 $this->compoundTag->setString(self::TAG_GENERATOR_NAME, $generatorName);
201 }
202
203 if(!($this->compoundTag->getTag(self::TAG_GENERATOR_OPTIONS)) instanceof StringTag){
204 $this->compoundTag->setString(self::TAG_GENERATOR_OPTIONS, "");
205 }
206 }
207
208 public function save() : void{
209 $this->compoundTag->setInt(self::TAG_NETWORK_VERSION, self::CURRENT_STORAGE_NETWORK_VERSION);
210 $this->compoundTag->setInt(self::TAG_STORAGE_VERSION, self::CURRENT_STORAGE_VERSION);
211 $this->compoundTag->setTag(self::TAG_LAST_OPENED_WITH_VERSION, new ListTag(array_map(fn(int $v) => new IntTag($v), self::CURRENT_CLIENT_VERSION_TARGET)));
212 $this->compoundTag->setLong(VersionInfo::TAG_WORLD_DATA_VERSION, VersionInfo::WORLD_DATA_VERSION);
213
214 $nbt = new LittleEndianNbtSerializer();
215 $buffer = $nbt->write(new TreeRoot($this->compoundTag));
216 Filesystem::safeFilePutContents($this->dataPath, Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer);
217 }
218
219 public function getDifficulty() : int{
220 return $this->compoundTag->getInt(self::TAG_DIFFICULTY, World::DIFFICULTY_NORMAL);
221 }
222
223 public function setDifficulty(int $difficulty) : void{
224 $this->compoundTag->setInt(self::TAG_DIFFICULTY, $difficulty); //yes, this is intended! (in PE: int, PC: byte)
225 }
226
227 public function getRainTime() : int{
228 return $this->compoundTag->getInt(self::TAG_RAIN_TIME, 0);
229 }
230
231 public function setRainTime(int $ticks) : void{
232 $this->compoundTag->setInt(self::TAG_RAIN_TIME, $ticks);
233 }
234
235 public function getRainLevel() : float{
236 return $this->compoundTag->getFloat(self::TAG_RAIN_LEVEL, 0.0);
237 }
238
239 public function setRainLevel(float $level) : void{
240 $this->compoundTag->setFloat(self::TAG_RAIN_LEVEL, $level);
241 }
242
243 public function getLightningTime() : int{
244 return $this->compoundTag->getInt(self::TAG_LIGHTNING_TIME, 0);
245 }
246
247 public function setLightningTime(int $ticks) : void{
248 $this->compoundTag->setInt(self::TAG_LIGHTNING_TIME, $ticks);
249 }
250
251 public function getLightningLevel() : float{
252 return $this->compoundTag->getFloat(self::TAG_LIGHTNING_LEVEL, 0.0);
253 }
254
255 public function setLightningLevel(float $level) : void{
256 $this->compoundTag->setFloat(self::TAG_LIGHTNING_LEVEL, $level);
257 }
258}
static writeLInt(int $value)
Definition: Binary.php:258