Skip to content

Commit 60c70d4

Browse files
committed
feat: incomplete Infdev 624 world writing
1 parent 2c623a6 commit 60c70d4

8 files changed

Lines changed: 312 additions & 10 deletions

File tree

projects/Converters/Minecraft/Java/include/Lodestone.Minecraft.Java/conversion/infdev/InfdevWorldIo.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
#include <Lodestone.Conversion/io/ObjectIOs.h>
1616
#include <Lodestone.Conversion/registry/RegistryRelations.h>
1717
#include "Lodestone.Minecraft.Java/Identifiers.h"
18-
#include "Lodestone.Minecraft.Java/conversion/alpha/AlphaPlayerIo.h"
19-
#include "Lodestone.Minecraft.Java/conversion/mcregion/McRegionChunkIo.h"
2018
#include "Lodestone.Minecraft.Java/conversion/infdev/InfdevZoneIo.h"
2119

2220
namespace lodestone::minecraft::java::infdev::world {

projects/Converters/Minecraft/Java/include/Lodestone.Minecraft.Java/conversion/infdev/InfdevZoneIo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
namespace lodestone::minecraft::java::infdev::zone {
2222
constexpr uint32_t EXPECTED_MAGIC = 0x13737000;
23+
constexpr uint16_t EXPECTED_VERSION = 0;
2324

2425
class InfdevZoneIo : public conversion::io::LevelIO<&identifiers::INF_624_ZONE_IO, const
2526
common::conversion::io::options::OptionPresets::CommonChunkReadOptions, const

projects/Converters/Minecraft/Java/src/infdev/chunk/InfdevChunkIo.cpp

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
#include <BinaryIO/stream/BinaryInputStream.h>
1414
#include <Lodestone.Common/Indexing.h>
1515
#include <Lodestone.Conversion/block/data/NumericBlockData.h>
16+
17+
#include <BinaryIO/stream/BinaryOutputStream.h>
1618
#include "Lodestone.Minecraft.Java/LodestoneJava.h"
1719
#include "Lodestone.Minecraft.Java/infdev/InfdevChunk.h"
1820

19-
#include "Lodestone.Minecraft.Java/Identifiers.h"
20-
2121
namespace lodestone::minecraft::java::infdev::chunk {
2222
std::unique_ptr<level::chunk::Chunk> InfdevChunkIO::read(
2323
const common::conversion::io::options::OptionPresets::CommonReadOptions &options) const {
@@ -87,6 +87,86 @@ bio::stream::BinaryInputStream bis(options.input);
8787
void InfdevChunkIO::write(level::chunk::Chunk *c,
8888
const common::conversion::io::options::OptionPresets::CommonChunkWriteOptions &options)
8989
const {
90-
throw std::runtime_error("Not implemented");
90+
bio::stream::BinaryOutputStream bos(options.output);
91+
92+
const std::unique_ptr<conversion::block::version::BlockIO>
93+
bio = LodestoneJava::getInstance()->io.getIo(options.version);
94+
95+
const level::types::Vec2i coords = c->getCoords().value();
96+
bos.writeBE<int32_t>(coords.x);
97+
bos.writeBE<int32_t>(coords.y); // Z
98+
99+
// Write padding
100+
for (int i = 0; i < 232; i++) {
101+
bos.writeBE<int8_t>(0);
102+
}
103+
104+
std::vector<int8_t> blocks(
105+
CHUNK_WIDTH * CHUNK_HEIGHT *
106+
CHUNK_DEPTH);
107+
std::vector<int8_t> data((CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH) /
108+
2);
109+
std::vector<int8_t> skyLight(
110+
(CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH) / 2);
111+
std::vector<int8_t> blockLight(
112+
(CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH) / 2);
113+
std::vector<int8_t> heightMap(CHUNK_WIDTH * CHUNK_DEPTH);
114+
// todo wiki says there's tileticks which is gonna be pain
115+
116+
int8_t *blockData = blocks.data(); // avoid the annoying bounds checks
117+
// and just write directly
118+
int8_t *dataData = data.data(); // welcome back 4J
119+
int8_t *skyLightData = skyLight.data();
120+
int8_t *blockLightData = blockLight.data();
121+
int8_t *heightMapData = heightMap.data();
122+
123+
for (int cx = 0; cx < CHUNK_WIDTH; cx++) {
124+
for (int cz = 0; cz < CHUNK_DEPTH; cz++) {
125+
for (int cy = 0; cy < CHUNK_HEIGHT; cy++) {
126+
const size_t idx =
127+
INDEX_XZY(cx, cy, cz, InfdevChunkIO::CHUNK_HEIGHT, InfdevChunkIO::CHUNK_DEPTH);
128+
const level::block::instance::BlockInstance &b =
129+
c->getBlock(cx, cy, cz);
130+
#ifdef USE_RISKY_OPTIMIZATIONS
131+
if (b.getBlock() !=
132+
level::block::BlockRegistry::s_defaultBlock) {
133+
#endif
134+
uint8_t id = 0;
135+
uint8_t dat = 0;
136+
137+
if (const conversion::block::data::NumericBlockData *bl =
138+
bio->convertBlockFromInternal(&b)->as<conversion::block::data::NumericBlockData>()) {
139+
id = bl->getId();
140+
dat = bl->getData();
141+
}
142+
143+
blockData[idx] = id;
144+
145+
SET_NIBBLE(dataData, idx, dat);
146+
#ifdef USE_RISKY_OPTIMIZATIONS
147+
}
148+
#endif
149+
150+
level::chunk::section::Section *s = c->getSection(cy >> 4);
151+
152+
SET_NIBBLE(skyLightData, idx,
153+
s ? s->getSkyLight()->getNibble(cx, cy & 15, cz)
154+
: 15);
155+
SET_NIBBLE(
156+
blockLightData, idx,
157+
s ? s->getBlockLight()->getNibble(cx, cy & 15, cz)
158+
: 15);
159+
}
160+
161+
heightMapData[INDEX_YX(cx, cz, CHUNK_WIDTH)] =
162+
c->getHeightAt(cx, cz);
163+
}
164+
165+
bos.writeBytes(reinterpret_cast<uint8_t*>(blockData), 32768);
166+
bos.writeBytes(reinterpret_cast<uint8_t*>(dataData), 16384);
167+
bos.writeBytes(reinterpret_cast<uint8_t*>(skyLightData), 16384);
168+
bos.writeBytes(reinterpret_cast<uint8_t*>(blockLightData), 16384);
169+
bos.writeBytes(reinterpret_cast<uint8_t*>(heightMapData), 256);
170+
}
91171
}
92172
}

projects/Converters/Minecraft/Java/src/infdev/world/InfdevWorldIo.cpp

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
#include <libnbt++/nbt_tags.h>
1818

1919
#include <Lodestone.Level/world/World.h>
20+
21+
#include <libnbt++/io/ozlibstream.h>
22+
23+
#include "Lodestone.Minecraft.Java/conversion/anvil/jungle/JungleAnvilChunkIo.h"
2024
#include "Lodestone.Minecraft.Java/conversion/infdev/InfdevZoneIo.h"
2125
#include "Lodestone.Minecraft.Java/infdev/InfdevWorld.h"
2226
#include "Lodestone.Minecraft.Java/infdev/InfdevZone.h"
@@ -114,6 +118,98 @@ namespace lodestone::minecraft::java::infdev::world {
114118

115119
void InfdevWorldIo::write(level::world::World *w,
116120
const common::conversion::io::options::OptionPresets::CommonFilesystemOptions &options) const {
117-
throw new std::runtime_error("Not implemented");
121+
if (!std::filesystem::exists(options.path)) {
122+
std::filesystem::create_directories(options.path);
123+
}
124+
125+
// Create level.dat
126+
{
127+
std::ofstream ofs(options.path / "level.dat", std::ifstream::binary);
128+
zlib::ozlibstream strm(ofs, Z_DEFAULT_COMPRESSION, true);
129+
130+
nbt::io::stream_writer writer(strm);
131+
nbt::tag_compound root{};
132+
nbt::tag_compound data{};
133+
134+
// Not the cleanest solution, but it definitely works.
135+
auto lastPlayed = w->getProperty("lastPlayed");
136+
data["LastPlayed"] =
137+
lastPlayed
138+
? lastPlayed
139+
->as<
140+
level::properties::TemplatedProperty<int64_t>>()
141+
->getValue()
142+
: static_cast<int64_t>(0);
143+
144+
auto seed = w->getProperty("seed");
145+
data["RandomSeed"] =
146+
seed
147+
? seed->as<
148+
level::properties::TemplatedProperty<int64_t>>()
149+
->getValue()
150+
: static_cast<int64_t>(0);
151+
152+
// TODO: Write default player tag here
153+
154+
level::types::Vec3i sp = w->getDefaultLevel()->getSpawnPos();
155+
data["SpawnX"] = sp.x;
156+
data["SpawnY"] = sp.y;
157+
data["SpawnZ"] = sp.z;
158+
159+
auto time = w->getProperty("time");
160+
data["Time"] =
161+
time
162+
? time->as<
163+
level::properties::TemplatedProperty<int64_t>>()
164+
->getValue()
165+
: static_cast<int64_t>(0);
166+
167+
data["SizeOnDisk"] = static_cast<int64_t>(0);
168+
169+
root["Data"] = std::move(data);
170+
writer.write_tag("", root);
171+
}
172+
173+
// Write zones out
174+
const zone::InfdevZoneIo *io = this->getAsByRelation<const zone::InfdevZoneIo, &conversion::identifiers::LEVEL_IO>();
175+
176+
std::filesystem::path p = options.path;
177+
178+
std::filesystem::path dataDir = p / "data";
179+
if (!std::filesystem::exists(dataDir))
180+
std::filesystem::create_directory(dataDir);
181+
182+
183+
auto lvl = w->getDefaultLevel();
184+
level::types::Bounds3i bounds = lvl->getChunkBounds();
185+
186+
for (int cX = 0; cX = bounds.min.x / chunk::InfdevChunkIO::CHUNK_WIDTH; cX++) {
187+
188+
}
189+
190+
for (int rx = bounds.min.x >> 5; rx <= bounds.max.x >> 5; rx++) {
191+
for (int rz = bounds.min.z >> 5; rz <= bounds.max.z >> 5; rz++) {
192+
auto zoneX = lodestone::common::util::Math::encodeBase36(rx);
193+
auto zoneZ = lodestone::common::util::Math::encodeBase36(rz);
194+
std::ofstream o(dataDir / ("zone_" + zoneX + "_" +
195+
zoneZ + ".dat"));
196+
197+
io->write(lvl, common::conversion::io::options::OptionPresets::CommonChunkWriteOptions {
198+
common::conversion::io::options::ChunkOptions {
199+
{rx, rz}
200+
},
201+
common::conversion::io::options::OptionPresets::CommonWriteOptions {
202+
conversion::io::options::fs::file::FileWriterOptions {
203+
o
204+
},
205+
conversion::io::options::versioned::VersionedOptions {
206+
options.version
207+
}
208+
}
209+
});
210+
211+
o.close();
212+
}
213+
}
118214
}
119215
}

projects/Converters/Minecraft/Java/src/infdev/zone/InfdevZoneIo.cpp

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <Lodestone.Conversion/registry/RegistryRelations.h>
2020

21+
#include <BinaryIO/stream/BinaryOutputStream.h>
2122
#include "Lodestone.Minecraft.Java/infdev/InfdevChunk.h"
2223

2324
namespace lodestone::minecraft::java::infdev::zone {
@@ -29,13 +30,12 @@ namespace lodestone::minecraft::java::infdev::zone {
2930
const chunk::InfdevChunkIO *chunkIo = this->getAsByRelation<const chunk::InfdevChunkIO, &
3031
conversion::identifiers::CHUNK_IO>();
3132

32-
33-
if (const uint32_t magic = bis.readBE<uint32_t>(); magic != 0x13737000) {
33+
if (const uint32_t magic = bis.readBE<uint32_t>(); magic != EXPECTED_MAGIC) {
3434
throw std::runtime_error{std::format("Invalid magic value! Expected: {}, read: {}", EXPECTED_MAGIC, magic)};
3535
}
3636

37-
if (const uint16_t version = bis.readBE<uint16_t>(); version != 0) {
38-
throw std::runtime_error{std::format("Unexpected world version! Expected: {}, read: {}", 0, version)};
37+
if (const uint16_t version = bis.readBE<uint16_t>(); version != EXPECTED_VERSION) {
38+
throw std::runtime_error{std::format("Unexpected world version! Expected: {}, read: {}", EXPECTED_VERSION, version)};
3939
}
4040

4141
// Read slot indices
@@ -74,5 +74,62 @@ namespace lodestone::minecraft::java::infdev::zone {
7474
void InfdevZoneIo::write(level::Level *c,
7575
const common::conversion::io::options::OptionPresets::CommonChunkWriteOptions &options)
7676
const {
77+
bio::stream::BinaryOutputStream bos(options.output);
78+
const level::types::Vec2i zoneCoordinates = options.coords;
79+
80+
// Write zone header
81+
bos.writeBE<uint32_t>(EXPECTED_MAGIC);
82+
83+
bos.writeBE<uint16_t>(EXPECTED_VERSION);
84+
85+
const int idx = bos.getOffset();
86+
87+
// Write chunk data
88+
auto *chunkIo = this->getAsByRelation<const chunk::InfdevChunkIO, &conversion::identifiers::CHUNK_IO>();
89+
90+
int slotCount = 0;
91+
int slots[1024];
92+
for (char x = 0; x < 32; x++) {
93+
for (char z = 0; z < 32; z++) {
94+
const int cx = zoneCoordinates.x * 32 + x;
95+
const int cz = zoneCoordinates.y * 32 + z;
96+
97+
level::chunk::Chunk *ch = c->getChunk(cx, cz);
98+
if (ch != nullptr) {
99+
slotCount++;
100+
101+
// TODO: Refactor to separate function
102+
const int v5 = cx - (zoneCoordinates.x << 5);
103+
const int v6 = cz - (zoneCoordinates.y << 5);
104+
const int slotIdx = v5 + v6 * 32;
105+
slots[slotCount] = slotIdx;
106+
107+
const uint64_t writePos = static_cast<uint64_t>(slotIdx) * (32768 * 3 + 256) + 4096;
108+
bos.seek(writePos);
109+
chunkIo->write(ch, common::conversion::io::options::OptionPresets::CommonChunkWriteOptions {
110+
common::conversion::io::options::ChunkOptions {
111+
{cx, cz}
112+
},
113+
common::conversion::io::options::OptionPresets::CommonWriteOptions {
114+
conversion::io::options::fs::file::FileWriterOptions {
115+
bos.getStream(),
116+
},
117+
conversion::io::options::versioned::VersionedOptions {
118+
options.version
119+
}
120+
}
121+
});
122+
}
123+
}
124+
}
125+
126+
// Seek back to header to write slot information
127+
bos.seek(idx);
128+
129+
// TODO: Refactor this
130+
bos.writeBE<uint16_t>(slotCount);
131+
for (int i = 0; i < slotCount; i++) {
132+
bos.writeBE<uint16_t>(slots[i]);
133+
}
77134
}
78135
}

projects/Utilities/Tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ target_compile_definitions(Lodestone.Tests PRIVATE
7171
READ_MINEV2_WORLD=false
7272
READ_ALPHA_WORLD=false
7373
WRITE_ALPHA_WORLD=false
74+
READ_INFDEV_624_WORLD=true
75+
WRITE_INFDEV_624_WORLD=true
7476
)
7577

7678
setupLinter(Lodestone.Tests)

projects/Utilities/Tests/include/Lodestone.Tests/tests/MainTests.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ namespace lodestone::tests::test {
1111
public:
1212
static void add();
1313

14+
static tfw::test::result::TestResult readInfdev624World(tfw::test::logging::loggers::ITestLogger &logger);
15+
16+
static tfw::test::result::TestResult writeInfdev624World(tfw::test::logging::loggers::ITestLogger &logger);
17+
1418
static tfw::test::result::TestResult generateWaterChunk(tfw::test::logging::loggers::ITestLogger &logger);
1519

1620
static tfw::test::result::TestResult readAnvilWorld(tfw::test::logging::loggers::ITestLogger &logger);

0 commit comments

Comments
 (0)