From 157b6e520751732d49757fc0bbae96f8dd5107a1 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 16:14:21 +0200 Subject: [PATCH] test: cover testDataPack --- tests/testDataPack.cpp | 160 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/tests/testDataPack.cpp b/tests/testDataPack.cpp index e71e0f3..a99c873 100644 --- a/tests/testDataPack.cpp +++ b/tests/testDataPack.cpp @@ -158,3 +158,163 @@ VF_TEST(packMetadataComesOutOfTheDataItself) { CHECK(!DataPackMeta::fromJson(noName.root, broken, problem)); CHECK(problem.find("name") != std::string::npos); + JsonParseResult zeroVersion = parseJson(R"json({"name": "x", "formatVersion": 0})json"); + CHECK(!DataPackMeta::fromJson(zeroVersion.root, broken, problem)); + CHECK(problem.find("formatVersion") != std::string::npos); + + JsonParseResult badSpace = parseJson(R"json({"name": "x", "defaultSpace": "Bad Space"})json"); + CHECK(!DataPackMeta::fromJson(badSpace.root, broken, problem)); + CHECK(problem.find("defaultSpace") != std::string::npos); +} + +VF_TEST(loadedRecipesAreMatchedByTheExistingCraftingRegistry) { + DataPackLoader loader; + loader.addPack(basePack()); + CHECK(loader.load()); + CHECK_EQ(loader.recipes().size(), static_cast(3)); + + const RecipeData* torch = loader.recipe(id("voxelforge:redstone_torch")); + CHECK(torch != nullptr); + CHECK(torch->kind == RecipeData::Kind::Shaped); + CHECK_EQ(torch->width, 1); + CHECK_EQ(torch->height, 2); + // Packed by the recipe width, which is how Crafting.cpp reads a pattern. + CHECK(torch->pattern[0] == ItemId::RedstoneDust); + CHECK(torch->pattern[1] == ItemId::Stick); + CHECK(!torch->allowMirror); + CHECK(torch->resultItem == ItemId::RedstoneTorch); + + CraftingRegistry registry; + std::size_t shapedBefore = registry.shapedRecipes().size(); + std::size_t shapelessBefore = registry.shapelessRecipes().size(); + std::size_t smeltingBefore = registry.smeltingRecipes().size(); + CHECK_EQ(loader.applyTo(registry), 3); + CHECK_EQ(registry.shapedRecipes().size(), shapedBefore + 1u); + CHECK_EQ(registry.shapelessRecipes().size(), shapelessBefore + 1u); + CHECK_EQ(registry.smeltingRecipes().size(), smeltingBefore + 1u); + + CraftGrid grid; + grid.fill(ItemId::None); + grid[static_cast(1 * craftGridWidth + 2)] = ItemId::RedstoneDust; + grid[static_cast(2 * craftGridWidth + 2)] = ItemId::Stick; + CraftResult crafted = registry.craftMatch(grid); + CHECK(crafted.valid()); + CHECK(crafted.item == ItemId::RedstoneTorch); + CHECK_EQ(crafted.count, 4); + + // The built in coal torch is untouched: loading data adds, it does not + // shadow what the registry already knew. + CraftGrid coalTorch; + coalTorch.fill(ItemId::None); + coalTorch[static_cast(1 * craftGridWidth + 2)] = ItemId::CoalLump; + coalTorch[static_cast(2 * craftGridWidth + 2)] = ItemId::Stick; + CHECK(registry.craftMatch(coalTorch).item == ItemId::Torch); + + CraftGrid shapeless; + shapeless.fill(ItemId::None); + shapeless[0] = ItemId::Apple; + shapeless[4] = ItemId::Apple; + CraftResult baked = registry.craftMatch(shapeless); + CHECK(baked.valid()); + CHECK(baked.item == ItemId::Bread); + + const SmeltingRecipe* smelt = registry.smeltingFor(ItemId::Sand); + CHECK(smelt != nullptr); + CHECK(smelt->result.item == ItemId::Sandstone); + CHECK_NEAR(smelt->smeltSeconds, 12.5f, 0.001f); + CHECK_NEAR(loader.recipe(id("voxelforge:sandstone"))->experience, 0.7f, 0.001f); +} + +VF_TEST(loadedLootTablesRollThroughTheExistingLootRoller) { + DataPackLoader loader; + loader.addPack(basePack()); + CHECK(loader.load()); + + const LootTable* table = loader.lootTable(id("voxelforge:chests/starter")); + CHECK(table != nullptr); + CHECK_EQ(table->pools.size(), static_cast(2)); + CHECK_EQ(table->pools[0].entries.size(), static_cast(1)); + CHECK(table->pools[0].entries[0].item == ItemId::Bread); + CHECK_EQ(table->pools[0].entries[0].minCount, 2); + CHECK_EQ(table->pools[1].entries[0].conditions, LootEntry::PlayerKill); + CHECK_EQ(table->pools[1].entries[0].functions, LootEntry::LootingBonus); + + LootContext context; + context.seed = 1234u; + LootRoll plain = loader.roll(id("voxelforge:chests/starter"), context); + CHECK_EQ(plain.countOf(ItemId::Bread), 2); + // The second pool is gated behind a player kill, so it stays shut here. + CHECK_EQ(plain.countOf(ItemId::DiamondGem), 0); + + context.playerKill = true; + LootRoll earned = loader.roll(id("voxelforge:chests/starter"), context); + CHECK_EQ(earned.countOf(ItemId::Bread), 2); + CHECK_EQ(earned.countOf(ItemId::DiamondGem), 1); + + // Same seed, same roll: nothing here reaches for a clock. + LootRoll again = loader.roll(id("voxelforge:chests/starter"), context); + CHECK_EQ(again.totalCount(), earned.totalCount()); + CHECK(loader.roll(id("voxelforge:chests/nothing"), context).empty()); +} + +VF_TEST(aLaterPackOverwritesAResourceAndAppendsToATag) { + DataPackLoader loader; + loader.addPack(basePack()); + loader.addPack(overlayPack()); + CHECK(loader.load()); + CHECK_EQ(loader.errors().size(), static_cast(0)); + CHECK_EQ(loader.applyOrder().size(), static_cast(2)); + CHECK_EQ(loader.applyOrder()[0], std::string("base")); + CHECK_EQ(loader.applyOrder()[1], std::string("overlay")); + + // One recipe id, the higher pack's version of it. + CHECK_EQ(loader.recipes().size(), static_cast(3)); + CHECK_EQ(loader.recipe(id("voxelforge:redstone_torch"))->resultCount, 8); + + std::vector logs; + CHECK(loader.blocksInTag(id("voxelforge:logs"), logs)); + CHECK_EQ(logs.size(), static_cast(2)); + if (logs.size() == 2u) { + CHECK(logs[0] == Block::Wood); + CHECK(logs[1] == Block::Planks); + } + + CraftingRegistry registry; + loader.applyTo(registry); + CraftGrid grid; + grid.fill(ItemId::None); + grid[0] = ItemId::RedstoneDust; + grid[static_cast(craftGridWidth)] = ItemId::Stick; + CHECK(registry.craftMatch(grid).item == ItemId::RedstoneTorch); + CHECK_EQ(registry.craftMatch(grid).count, 8); +} + +VF_TEST(packPriorityDecidesWhoWinsRegardlessOfInsertionOrder) { + DataPackLoader loader; + loader.addPack(overlayPack()); + loader.addPack(basePack()); + CHECK(loader.load()); + CHECK_EQ(loader.applyOrder()[0], std::string("base")); + CHECK_EQ(loader.applyOrder()[1], std::string("overlay")); + CHECK_EQ(loader.recipe(id("voxelforge:redstone_torch"))->resultCount, 8); +} + +VF_TEST(aTagMarkedReplaceWipesWhatCameBeforeIt) { + DataPackMeta meta; + meta.name = "replacement"; + meta.priority = 20; + DataPack replacement(meta); + replacement.addFile("data/voxelforge/tags/blocks/logs.json", + R"json({"replace": true, "values": ["voxelforge:cactus"]})json"); + + DataPackLoader loader; + loader.addPack(basePack()); + loader.addPack(overlayPack()); + loader.addPack(replacement); + CHECK(loader.load()); + + std::vector logs; + CHECK(loader.blocksInTag(id("voxelforge:logs"), logs)); + CHECK_EQ(logs.size(), static_cast(1)); + if (!logs.empty()) CHECK(logs[0] == Block::Cactus); +