22declare(strict_types=1);
24namespace pocketmine\world\format\io;
31use Symfony\Component\Filesystem\Path;
34use
function file_exists;
36use
function microtime;
38use
function random_bytes;
42use
const DIRECTORY_SEPARATOR;
45 private string $backupPath;
46 private \Logger $logger;
48 public function __construct(
53 private int $chunksPerProgressUpdate = 256
55 $this->logger = new \PrefixedLogger($logger,
"World Converter: " . $this->oldProvider->getWorldData()->getName());
57 if(!file_exists($backupPath)){
58 @mkdir($backupPath, 0777,
true);
62 $this->backupPath = Path::join($backupPath, basename($this->oldProvider->getPath()) . $nextSuffix);
63 $nextSuffix =
"_" . crc32(random_bytes(4));
64 }
while(file_exists($this->backupPath));
67 public function getBackupPath() :
string{
68 return $this->backupPath;
71 public function execute() :
void{
72 $new = $this->generateNew();
74 $this->populateLevelData($new->getWorldData());
75 $this->convertTerrain($new);
77 $path = $this->oldProvider->getPath();
78 $this->oldProvider->close();
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.");
85 Filesystem::recursiveUnlink($path);
87 if(!@rename($new->getPath(), $path)){
89 $this->logger->debug(
"Relocation of new world files to location failed, attempting copy and delete instead");
91 Filesystem::recursiveUnlink($new->getPath());
94 $this->logger->info(
"Conversion completed");
98 $this->logger->info(
"Generating new world");
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);
106 $this->newProvider->generate($convertedOutput, $data->getName(), WorldCreationOptions::create()
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())
116 return $this->newProvider->fromPath($convertedOutput, $this->logger);
119 private function populateLevelData(
WorldData $data) :
void{
120 $this->logger->info(
"Converting world manifest");
121 $oldData = $this->oldProvider->getWorldData();
127 $data->setSpawn($oldData->getSpawn());
128 $data->setTime($oldData->getTime());
131 $this->logger->info(
"Finished converting manifest");
136 $this->logger->info(
"Calculating chunk count");
137 $count = $this->oldProvider->calculateChunkCount();
138 $this->logger->info(
"Discovered $count chunks");
142 $start = microtime(
true);
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);
148 if(($counter % $this->chunksPerProgressUpdate) === 0){
149 $time = microtime(
true);
150 $diff = $time - $thisRound;
152 $this->logger->info(
"Converted $counter / $count chunks (" . floor($this->chunksPerProgressUpdate / $diff) .
" chunks/sec)");
155 $total = microtime(
true) - $start;
156 $this->logger->info(
"Converted $counter / $counter chunks in " . round($total, 3) .
" seconds (" . floor($counter / $total) .
" chunks/sec)");
static recursiveCopy(string $origin, string $destination)