PocketMine-MP 5.21.2 git-b2aa6396c3cc2cafdd815eacc360e1ad89599899
Loading...
Searching...
No Matches
FormatConverter.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;
25
31use Symfony\Component\Filesystem\Path;
32use function basename;
33use function crc32;
34use function file_exists;
35use function floor;
36use function microtime;
37use function mkdir;
38use function random_bytes;
39use function rename;
40use function round;
41use function rtrim;
42use const DIRECTORY_SEPARATOR;
43
45 private string $backupPath;
46 private \Logger $logger;
47
48 public function __construct(
49 private WorldProvider $oldProvider,
50 private WritableWorldProviderManagerEntry $newProvider,
51 string $backupPath,
52 \Logger $logger,
53 private int $chunksPerProgressUpdate = 256
54 ){
55 $this->logger = new \PrefixedLogger($logger, "World Converter: " . $this->oldProvider->getWorldData()->getName());
56
57 if(!file_exists($backupPath)){
58 @mkdir($backupPath, 0777, true);
59 }
60 $nextSuffix = "";
61 do{
62 $this->backupPath = Path::join($backupPath, basename($this->oldProvider->getPath()) . $nextSuffix);
63 $nextSuffix = "_" . crc32(random_bytes(4));
64 }while(file_exists($this->backupPath));
65 }
66
67 public function getBackupPath() : string{
68 return $this->backupPath;
69 }
70
71 public function execute() : void{
72 $new = $this->generateNew();
73
74 $this->populateLevelData($new->getWorldData());
75 $this->convertTerrain($new);
76
77 $path = $this->oldProvider->getPath();
78 $this->oldProvider->close();
79 $new->close();
80
81 $this->logger->info("Backing up pre-conversion world to " . $this->backupPath);
82 if(!@rename($path, $this->backupPath)){
83 $this->logger->warning("Moving old world files for backup failed, attempting copy instead. This might take a long time.");
84 Filesystem::recursiveCopy($path, $this->backupPath);
85 Filesystem::recursiveUnlink($path);
86 }
87 if(!@rename($new->getPath(), $path)){
88 //we don't expect this to happen because worlds/ should most likely be all on the same FS, but just in case...
89 $this->logger->debug("Relocation of new world files to location failed, attempting copy and delete instead");
90 Filesystem::recursiveCopy($new->getPath(), $path);
91 Filesystem::recursiveUnlink($new->getPath());
92 }
93
94 $this->logger->info("Conversion completed");
95 }
96
97 private function generateNew() : WritableWorldProvider{
98 $this->logger->info("Generating new world");
99 $data = $this->oldProvider->getWorldData();
100
101 $convertedOutput = rtrim($this->oldProvider->getPath(), "/" . DIRECTORY_SEPARATOR) . "_converted" . DIRECTORY_SEPARATOR;
102 if(file_exists($convertedOutput)){
103 $this->logger->info("Found previous conversion attempt, deleting...");
104 Filesystem::recursiveUnlink($convertedOutput);
105 }
106 $this->newProvider->generate($convertedOutput, $data->getName(), WorldCreationOptions::create()
107 //TODO: defaulting to NORMAL here really isn't very good behaviour, but it's consistent with what we already
108 //did previously; besides, WorldManager checks for unknown generators before this is reached anyway.
109 ->setGeneratorClass(GeneratorManager::getInstance()->getGenerator($data->getGenerator())?->getGeneratorClass() ?? Normal::class)
110 ->setGeneratorOptions($data->getGeneratorOptions())
111 ->setSeed($data->getSeed())
112 ->setSpawnPosition($data->getSpawn())
113 ->setDifficulty($data->getDifficulty())
114 );
115
116 return $this->newProvider->fromPath($convertedOutput, $this->logger);
117 }
118
119 private function populateLevelData(WorldData $data) : void{
120 $this->logger->info("Converting world manifest");
121 $oldData = $this->oldProvider->getWorldData();
122 $data->setDifficulty($oldData->getDifficulty());
123 $data->setLightningLevel($oldData->getLightningLevel());
124 $data->setLightningTime($oldData->getLightningTime());
125 $data->setRainLevel($oldData->getRainLevel());
126 $data->setRainTime($oldData->getRainTime());
127 $data->setSpawn($oldData->getSpawn());
128 $data->setTime($oldData->getTime());
129
130 $data->save();
131 $this->logger->info("Finished converting manifest");
132 //TODO: add more properties as-needed
133 }
134
135 private function convertTerrain(WritableWorldProvider $new) : void{
136 $this->logger->info("Calculating chunk count");
137 $count = $this->oldProvider->calculateChunkCount();
138 $this->logger->info("Discovered $count chunks");
139
140 $counter = 0;
141
142 $start = microtime(true);
143 $thisRound = $start;
144 foreach($this->oldProvider->getAllChunks(true, $this->logger) as $coords => $loadedChunkData){
145 [$chunkX, $chunkZ] = $coords;
146 $new->saveChunk($chunkX, $chunkZ, $loadedChunkData->getData(), Chunk::DIRTY_FLAGS_ALL);
147 $counter++;
148 if(($counter % $this->chunksPerProgressUpdate) === 0){
149 $time = microtime(true);
150 $diff = $time - $thisRound;
151 $thisRound = $time;
152 $this->logger->info("Converted $counter / $count chunks (" . floor($this->chunksPerProgressUpdate / $diff) . " chunks/sec)");
153 }
154 }
155 $total = microtime(true) - $start;
156 $this->logger->info("Converted $counter / $counter chunks in " . round($total, 3) . " seconds (" . floor($counter / $total) . " chunks/sec)");
157 }
158}
saveChunk(int $chunkX, int $chunkZ, ChunkData $chunkData, int $dirtyFlags)