diff --git a/core/src/LootTables.cpp b/core/src/LootTables.cpp index 357bd05..54c27d5 100644 --- a/core/src/LootTables.cpp +++ b/core/src/LootTables.cpp @@ -630,3 +630,148 @@ LootTableId LootTableId::ofChest(ChestFill::Structure structure) { return LootTableId{Domain::Chest, static_cast(structure)}; } +LootTableId LootTableId::ofFishing(FishingLoot::Pool pool) { + return LootTableId{Domain::Fishing, static_cast(pool)}; +} + +bool LootTableId::valid() const { + switch (domain) { + case Domain::Block: return index < static_cast(Block::Count); + case Domain::Mob: return index < static_cast(MobDropRule::Species::Count); + case Domain::Chest: return index < static_cast(ChestFill::Structure::Count); + case Domain::Fishing: return index < static_cast(FishingLoot::Pool::Count); + case Domain::Count: return false; + } + return false; +} + +LootRegistry::LootRegistry() + : blocks(buildBlockRules()), + mobs(buildMobRules()), + chests(buildChestFills()), + fishingTables(buildFishingTables()) { + for (const BlockDropRule& rule : blocks) blockTables.push_back(blockRuleTable(rule)); + for (const MobDropRule& rule : mobs) mobTables.push_back(mobRuleTable(rule)); + for (const ChestFill& fill : chests) { + LootTable table; + table.pools.push_back(fill.pool); + chestTables.push_back(table); + } +} + +const BlockDropRule& LootRegistry::blockRule(Block block) const { + return blocks[static_cast(block)]; +} + +const MobDropRule& LootRegistry::mobRule(MobDropRule::Species species) const { + return mobs[static_cast(species)]; +} + +const ChestFill& LootRegistry::chestFill(ChestFill::Structure structure) const { + return chests[static_cast(structure)]; +} + +bool LootRegistry::coversEveryBlock() const { + if (blocks.size() != static_cast(Block::Count)) return false; + for (std::size_t i = 0; i < blocks.size(); ++i) { + if (blocks[i].block != static_cast(i)) return false; + } + return true; +} + +bool LootRegistry::coversEveryMob() const { + if (mobs.size() != static_cast(MobDropRule::Species::Count)) return false; + for (std::size_t i = 0; i < mobs.size(); ++i) { + if (mobs[i].species != static_cast(i)) return false; + } + return true; +} + +bool LootRegistry::coversEveryStructure() const { + if (chests.size() != static_cast(ChestFill::Structure::Count)) return false; + for (std::size_t i = 0; i < chests.size(); ++i) { + if (chests[i].structure != static_cast(i)) return false; + } + return true; +} + +const LootTable& LootRegistry::tableFor(LootTableId id) const { + if (!id.valid()) return emptyLootTable(); + std::size_t index = static_cast(id.index); + switch (id.domain) { + case LootTableId::Domain::Block: return blockTables[index]; + case LootTableId::Domain::Mob: return mobTables[index]; + case LootTableId::Domain::Chest: return chestTables[index]; + case LootTableId::Domain::Fishing: return fishingTables[index]; + case LootTableId::Domain::Count: break; + } + return emptyLootTable(); +} + +LootRoll LootRegistry::rollBlock(Block block, const LootContext& context) const { + if (block >= Block::Count) return {}; + const BlockDropRule& rule = blockRule(block); + if (!toolAllows(rule, context.tool)) return {}; + + if (rule.shearHarvest && itemInfo(context.tool).toolType == shearToolType) { + LootRoll sheared; + ItemId harvested = rule.silkTouchDrop(); + if (harvested != ItemId::None) sheared.items.push_back({harvested, 1, 0}); + return sheared; + } + return rollLootTable(blockTables[static_cast(block)], context); +} + +LootRoll LootRegistry::rollMob(MobDropRule::Species species, const LootContext& context) const { + if (species >= MobDropRule::Species::Count) return {}; + return rollLootTable(mobTables[static_cast(species)], context); +} + +LootRoll LootRegistry::rollChest(ChestFill::Structure structure, const LootContext& context) const { + if (structure >= ChestFill::Structure::Count) return {}; + return rollLootTable(chestTables[static_cast(structure)], context); +} + +int LootRegistry::chestCountFor(ChestFill::Structure structure, uint32_t seed) const { + if (structure >= ChestFill::Structure::Count) return 0; + const ChestFill& fill = chestFill(structure); + uint32_t rng = seed != 0 ? seed : defaultLootSeed; + return rollBetween(rng, fill.chestsMin, fill.chestsMax); +} + +FishingLoot LootRegistry::rollFishing(const LootContext& context) const { + uint32_t rng = context.seed != 0 ? context.seed : defaultLootSeed; + int luck = context.fortune(); + int lure = context.toolEnchants.levelOf(EnchantKind::Efficiency); + + FishingLoot outcome; + outcome.fishWeight = fishingFishWeight; + outcome.treasureWeight = fishingTreasureWeight + fishingLuckWeightShift * luck; + outcome.junkWeight = std::max(0, fishingJunkWeight - fishingLuckWeightShift * luck); + + int pick = rollBelow(rng, outcome.fishWeight + outcome.treasureWeight + outcome.junkWeight); + if (pick < outcome.junkWeight) { + outcome.pool = FishingLoot::Pool::Junk; + } else if (pick < outcome.junkWeight + outcome.treasureWeight) { + outcome.pool = FishingLoot::Pool::Treasure; + } else { + outcome.pool = FishingLoot::Pool::Fish; + } + + int waited = rollBetween(rng, fishingMinWaitTicks, fishingBaseWaitTicks); + outcome.waitTicks = std::max(fishingMinWaitTicks / 2, + waited - lure * fishingLureTicksPerLevel); + + LootContext poolContext = context; + poolContext.seed = rng; + outcome.caught = rollLootTable(fishingTables[static_cast(outcome.pool)], + poolContext); + return outcome; +} + +const LootRegistry& lootRegistry() { + static const LootRegistry registry; + return registry; +} + +} // namespace vf