From 35644f97719ee71a51269ca20990b1246b5a0453 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 16:10:36 +0200 Subject: [PATCH] feat: add lootContextFor and smeltedForm to Loot --- core/src/Loot.cpp | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/core/src/Loot.cpp b/core/src/Loot.cpp index 23df435..ad74ecb 100644 --- a/core/src/Loot.cpp +++ b/core/src/Loot.cpp @@ -314,3 +314,114 @@ int LootRoll::totalCount() const { return total; } +LootContext lootContextFor(const HurtSource& source, ItemId tool, const EnchantSet& toolEnchants, + uint32_t seed) { + LootContext context; + context.seed = seed != 0 ? seed : 0x9e3779b9u; + context.killedBy = source.type; + context.fiery = source.fiery || isFireDamage(source.type); + context.playerKill = source.byPlayer; + context.tool = tool; + context.toolEnchants = toolEnchants; + return context; +} + +ItemId smeltedForm(ItemId raw) { + switch (raw) { + case ItemId::RawPorkchop: return ItemId::CookedPorkchop; + case ItemId::RawBeef: return ItemId::CookedBeef; + case ItemId::RawMutton: return ItemId::CookedMutton; + case ItemId::IronOre: return ItemId::IronIngot; + case ItemId::GoldOre: return ItemId::GoldIngot; + default: return raw; + } +} + +bool toolQualifiesFor(Block block, ItemId tool) { + if (correctToolFor(block) != ToolType::Pickaxe) return true; + + const ItemInfo& info = itemInfo(tool); + if (info.toolType != ToolType::Pickaxe) return false; + switch (block) { + case Block::DiamondOre: + case Block::EmeraldOre: + case Block::GoldOre: + return info.toolTier == ToolTier::Iron || info.toolTier == ToolTier::Diamond; + case Block::IronOre: + case Block::LapisOre: + case Block::RedstoneOre: + return info.toolTier != ToolTier::Wood && info.toolTier != ToolTier::Gold; + default: + return true; + } +} + +const LootTable& blockLootTable(Block block) { + static const std::vector tables = buildBlockTables(); + if (block >= Block::Count) return emptyTable(); + return tables[static_cast(block)]; +} + +const LootTable& mobLootTable(LootTable::MobKind kind) { + static const std::vector tables = { + zombieTable(), + skeletonTable(), + creeperTable(), + meatTable(ItemId::RawMutton, 1, 2), + meatTable(ItemId::RawBeef, 1, 3), + meatTable(ItemId::RawPorkchop, 1, 3), + }; + if (kind >= LootTable::MobKind::Count) return emptyTable(); + return tables[static_cast(kind)]; +} + +const LootTable& chestLootTable(LootTable::ChestKind kind) { + static const std::vector tables = { + dungeonChestTable(), + villageChestTable(), + templeChestTable(), + }; + if (kind >= LootTable::ChestKind::Count) return emptyTable(); + return tables[static_cast(kind)]; +} + +LootRoll rollLootTable(const LootTable& table, const LootContext& context) { + LootRoll roll; + uint32_t rng = context.seed != 0 ? context.seed : 0x9e3779b9u; + + for (const LootPool& pool : table.pools) { + int rolls = rollBetween(rng, pool.minRolls, pool.maxRolls); + rolls += pool.bonusRollsPerLooting * context.looting(); + for (int r = 0; r < rolls; ++r) { + int totalWeight = 0; + for (const LootEntry& entry : pool.entries) { + if (conditionsPass(entry, context)) totalWeight += entry.weight; + } + if (totalWeight <= 0) continue; + int pick = rollBelow(rng, totalWeight); + for (const LootEntry& entry : pool.entries) { + if (!conditionsPass(entry, context)) continue; + pick -= entry.weight; + if (pick >= 0) continue; + emitEntry(roll, entry, context, rng); + break; + } + } + } + return roll; +} + +LootRoll rollBlockDrops(Block block, const LootContext& context) { + if (!toolQualifiesFor(block, context.tool)) return {}; + return rollLootTable(blockLootTable(block), context); +} + +LootRoll rollMobDrops(LootTable::MobKind kind, const LootContext& context) { + return rollLootTable(mobLootTable(kind), context); +} + +LootRoll rollChestContents(LootTable::ChestKind kind, const LootContext& context) { + return rollLootTable(chestLootTable(kind), context); +} + +} // namespace vf