From d234f6a88430b5989c353875f9c9f87317385194 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 14:56:35 +0200 Subject: [PATCH] feat: introduce xorshift and rollBelow --- core/src/Loot.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 core/src/Loot.cpp diff --git a/core/src/Loot.cpp b/core/src/Loot.cpp new file mode 100644 index 0000000..f5a17ba --- /dev/null +++ b/core/src/Loot.cpp @@ -0,0 +1,158 @@ +#include "vf/Loot.h" + +#include + +namespace vf { + +namespace { + +uint32_t xorshift(uint32_t& state) { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + return state; +} + +int rollBelow(uint32_t& rng, int bound) { + if (bound <= 1) return 0; + return static_cast(xorshift(rng) % static_cast(bound)); +} + +int rollBetween(uint32_t& rng, int low, int high) { + if (high <= low) return low; + return low + rollBelow(rng, high - low + 1); +} + +float unitFloat(uint32_t& rng) { return static_cast(xorshift(rng)) / 4294967295.0f; } + +bool conditionsPass(const LootEntry& entry, const LootContext& context) { + if ((entry.conditions & LootEntry::PlayerKill) && !context.playerKill) return false; + if ((entry.conditions & LootEntry::Fiery) && !context.fiery) return false; + if ((entry.conditions & LootEntry::NotFiery) && context.fiery) return false; + if ((entry.conditions & LootEntry::SilkTouched) && !context.silkTouch()) return false; + if ((entry.conditions & LootEntry::NotSilkTouched) && context.silkTouch()) return false; + if ((entry.conditions & LootEntry::NeedsLooting) && context.looting() <= 0) return false; + if ((entry.conditions & LootEntry::NeedsFortune) && context.fortune() <= 0) return false; + if ((entry.conditions & LootEntry::Explosive) && context.killedBy != DamageType::Explosion) { + return false; + } + if ((entry.conditions & LootEntry::Ranged) && !isProjectileDamage(context.killedBy)) { + return false; + } + return true; +} + +void addToRoll(LootRoll& roll, ItemId item, int count) { + if (item == ItemId::None || count <= 0) return; + if (isTool(item)) { + for (int i = 0; i < count; ++i) roll.items.push_back(makeToolStack(item)); + return; + } + for (ItemSlot& stack : roll.items) { + if (stack.item != item) continue; + stack.count += count; + return; + } + roll.items.push_back({item, count, 0}); +} + +void emitEntry(LootRoll& roll, const LootEntry& entry, const LootContext& context, uint32_t& rng) { + float chance = entry.chance; + if (entry.functions & LootEntry::ChanceLooting) { + chance += entry.chancePerLevel * static_cast(context.looting()); + } + if (entry.functions & LootEntry::ChanceFortune) { + chance += entry.chancePerLevel * static_cast(context.fortune()); + } + if (chance < 1.0f && unitFloat(rng) > chance) return; + + int count = entry.minCount; + if (entry.functions & LootEntry::CountRange) { + count = rollBetween(rng, entry.minCount, entry.maxCount); + } + if (entry.functions & LootEntry::OreFortune) { + int fortune = context.fortune(); + if (fortune > 0) count *= std::max(1, rollBelow(rng, fortune + 2)); + } + if (entry.functions & LootEntry::LootingBonus) { + count += rollBelow(rng, context.looting() + 1); + } + ItemId item = entry.item; + if ((entry.functions & LootEntry::SmeltIfFiery) && context.fiery) item = smeltedForm(item); + addToRoll(roll, item, count); +} + +LootEntry fixedDrop(ItemId item, int count = 1) { + LootEntry entry; + entry.item = item; + entry.minCount = count; + entry.maxCount = count; + return entry; +} + +LootEntry rangedDrop(ItemId item, int minCount, int maxCount) { + LootEntry entry; + entry.item = item; + entry.minCount = minCount; + entry.maxCount = maxCount; + entry.functions = LootEntry::CountRange; + return entry; +} + +LootEntry weightedDrop(ItemId item, int weight, int minCount, int maxCount) { + LootEntry entry = rangedDrop(item, minCount, maxCount); + entry.weight = weight; + return entry; +} + +LootEntry chanceDrop(ItemId item, float chance, float perLevel, uint32_t chanceFunction) { + LootEntry entry = fixedDrop(item); + entry.chance = chance; + entry.chancePerLevel = perLevel; + entry.functions = chanceFunction; + return entry; +} + +void addPool(LootTable& table, const LootEntry& entry) { + LootPool pool; + pool.entries.push_back(entry); + table.pools.push_back(pool); +} + +LootTable oreTable(Block block, ItemId rawDrop, int minCount, int maxCount) { + LootEntry silk = fixedDrop(itemForBlock(block)); + silk.conditions = LootEntry::SilkTouched; + + LootEntry raw = rangedDrop(rawDrop, minCount, maxCount); + raw.conditions = LootEntry::NotSilkTouched; + raw.functions = LootEntry::CountRange | LootEntry::OreFortune; + + LootTable table; + addPool(table, silk); + addPool(table, raw); + return table; +} + +LootTable selfDropTable(Block block) { + LootTable table; + ItemId self = itemForBlock(block); + if (self != ItemId::None) addPool(table, fixedDrop(self)); + return table; +} + +LootTable stoneTable() { + LootEntry silk = fixedDrop(ItemId::Stone); + silk.conditions = LootEntry::SilkTouched; + LootEntry cobble = fixedDrop(ItemId::Cobblestone); + cobble.conditions = LootEntry::NotSilkTouched; + + LootTable table; + addPool(table, silk); + addPool(table, cobble); + return table; +} + +LootTable leavesTable() { + LootEntry silk = fixedDrop(ItemId::Leaves); + silk.conditions = LootEntry::SilkTouched; +