46 private string $backupPath;
47 private \Logger $logger;
49 public function __construct(
54 private int $chunksPerProgressUpdate = 256
56 $this->logger = new \PrefixedLogger($logger,
"World Converter: " . $this->oldProvider->getWorldData()->getName());
58 if(!file_exists($backupPath)){
59 @mkdir($backupPath, 0777,
true);
63 $this->backupPath = Path::join($backupPath, basename($this->oldProvider->getPath()) . $nextSuffix);
64 $nextSuffix =
"_" . crc32(random_bytes(4));
65 }
while(file_exists($this->backupPath));
68 public function getBackupPath() :
string{
69 return $this->backupPath;
72 public function execute() :
void{
73 $new = $this->generateNew();
75 $this->populateLevelData($new->getWorldData());
76 $this->convertTerrain($new);
78 $path = $this->oldProvider->getPath();
79 $this->oldProvider->close();
82 $this->logger->info(
"Backing up pre-conversion world to " . $this->backupPath);
83 if(!@rename($path, $this->backupPath)){
84 $this->logger->warning(
"Moving old world files for backup failed, attempting copy instead. This might take a long time.");
85 Filesystem::recursiveCopy($path, $this->backupPath);
86 Filesystem::recursiveUnlink($path);
88 if(!@rename($new->getPath(), $path)){
90 $this->logger->debug(
"Relocation of new world files to location failed, attempting copy and delete instead");
91 Filesystem::recursiveCopy($new->getPath(), $path);
92 Filesystem::recursiveUnlink($new->getPath());
95 $this->logger->info(
"Conversion completed");
99 $this->logger->info(
"Generating new world");
100 $data = $this->oldProvider->getWorldData();
102 $convertedOutput = rtrim($this->oldProvider->getPath(),
"/" . DIRECTORY_SEPARATOR) .
"_converted" . DIRECTORY_SEPARATOR;
103 if(file_exists($convertedOutput)){
104 $this->logger->info(
"Found previous conversion attempt, deleting...");
105 Filesystem::recursiveUnlink($convertedOutput);
107 $this->newProvider->generate($convertedOutput, $data->getName(), WorldCreationOptions::create()
110 ->setGeneratorClass(GeneratorManager::getInstance()->getGenerator($data->getGenerator())?->getGeneratorClass() ?? Normal::class)
111 ->setGeneratorOptions($data->getGeneratorOptions())
112 ->setSeed($data->getSeed())
113 ->setSpawnPosition($data->getSpawn())
114 ->setDifficulty($data->getDifficulty())
117 return $this->newProvider->fromPath($convertedOutput, $this->logger);
120 private function populateLevelData(
WorldData $data) :
void{
121 $this->logger->info(
"Converting world manifest");
122 $oldData = $this->oldProvider->getWorldData();
128 $data->setSpawn($oldData->getSpawn());
129 $data->setTime($oldData->getTime());
132 $this->logger->info(
"Finished converting manifest");
137 $this->logger->info(
"Calculating chunk count");
138 $count = $this->oldProvider->calculateChunkCount();
139 $this->logger->info(
"Discovered $count chunks");
143 $start = microtime(
true);
145 foreach($this->oldProvider->getAllChunks(
true, $this->logger) as $coords => $loadedChunkData){
146 [$chunkX, $chunkZ] = $coords;
147 $new->
saveChunk($chunkX, $chunkZ, $loadedChunkData->getData(), Chunk::DIRTY_FLAGS_ALL);
149 if(($counter % $this->chunksPerProgressUpdate) === 0){
150 $time = microtime(
true);
151 $diff = $time - $thisRound;
153 $this->logger->info(
"Converted $counter / $count chunks (" . floor($this->chunksPerProgressUpdate / $diff) .
" chunks/sec)");
156 if(($counter % (2 ** 16)) === 0){
160 $total = microtime(
true) - $start;
161 $this->logger->info(
"Converted $counter / $counter chunks in " . round($total, 3) .
" seconds (" . floor($counter / $total) .
" chunks/sec)");