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