wotw_seedgen 0.2.1

Seed Generator for the Ori and the Will of the Wisps Randomizer
use rand::{Rng, seq::SliceRandom};
use rustc_hash::FxHashMap;

use crate::inventory::Inventory;
use crate::item::{Item, Resource, Skill, Shard, BonusUpgrade};

#[derive(Default, Debug, Clone)]
pub struct Pool {
    pub inventory: Inventory,
    pub spirit_light: u32,
}
impl Pool {
    pub fn preset() -> Pool {
        let mut items = FxHashMap::default();

        items.insert(Item::Resource(Resource::Health), 24);
        items.insert(Item::Resource(Resource::Energy), 24);
        items.insert(Item::Resource(Resource::Ore), 40);
        items.insert(Item::Resource(Resource::Keystone), 34);
        items.insert(Item::Resource(Resource::ShardSlot), 5);
        items.insert(Item::Skill(Skill::Bash), 1);
        items.insert(Item::Skill(Skill::DoubleJump), 1);
        items.insert(Item::Skill(Skill::Launch), 1);
        items.insert(Item::Skill(Skill::Glide), 1);
        items.insert(Item::Skill(Skill::WaterBreath), 1);
        items.insert(Item::Skill(Skill::Grenade), 1);
        items.insert(Item::Skill(Skill::Grapple), 1);
        items.insert(Item::Skill(Skill::Flash), 1);
        items.insert(Item::Skill(Skill::Spear), 1);
        items.insert(Item::Skill(Skill::Regenerate), 1);
        items.insert(Item::Skill(Skill::Bow), 1);
        items.insert(Item::Skill(Skill::Hammer), 1);
        items.insert(Item::Skill(Skill::Sword), 1);
        items.insert(Item::Skill(Skill::Burrow), 1);
        items.insert(Item::Skill(Skill::Dash), 1);
        items.insert(Item::Skill(Skill::WaterDash), 1);
        items.insert(Item::Skill(Skill::Shuriken), 1);
        items.insert(Item::Skill(Skill::Blaze), 1);
        items.insert(Item::Skill(Skill::Sentry), 1);
        items.insert(Item::Skill(Skill::Flap), 1);
        items.insert(Item::Skill(Skill::AncestralLight1), 1);
        items.insert(Item::Skill(Skill::AncestralLight2), 1);
        items.insert(Item::Water, 1);
        items.insert(Item::Shard(Shard::Overcharge), 1);
        items.insert(Item::Shard(Shard::TripleJump), 1);
        items.insert(Item::Shard(Shard::Wingclip), 1);
        items.insert(Item::Shard(Shard::Bounty), 1);
        items.insert(Item::Shard(Shard::Swap), 1);
        items.insert(Item::Shard(Shard::Magnet), 1);
        items.insert(Item::Shard(Shard::Splinter), 1);
        items.insert(Item::Shard(Shard::Reckless), 1);
        items.insert(Item::Shard(Shard::Quickshot), 1);
        items.insert(Item::Shard(Shard::Resilience), 1);
        items.insert(Item::Shard(Shard::SpiritLightHarvest), 1);
        items.insert(Item::Shard(Shard::Vitality), 1);
        items.insert(Item::Shard(Shard::LifeHarvest), 1);
        items.insert(Item::Shard(Shard::EnergyHarvest), 1);
        items.insert(Item::Shard(Shard::Energy), 1);
        items.insert(Item::Shard(Shard::LifePact), 1);
        items.insert(Item::Shard(Shard::LastStand), 1);
        items.insert(Item::Shard(Shard::Sense), 1);
        items.insert(Item::Shard(Shard::UltraBash), 1);
        items.insert(Item::Shard(Shard::UltraGrapple), 1);
        items.insert(Item::Shard(Shard::Overflow), 1);
        items.insert(Item::Shard(Shard::Thorn), 1);
        items.insert(Item::Shard(Shard::Catalyst), 1);
        items.insert(Item::Shard(Shard::Turmoil), 1);
        items.insert(Item::Shard(Shard::Sticky), 1);
        items.insert(Item::Shard(Shard::Finesse), 1);
        items.insert(Item::Shard(Shard::SpiritSurge), 1);
        items.insert(Item::Shard(Shard::Lifeforce), 1);
        items.insert(Item::Shard(Shard::Deflector), 1);
        items.insert(Item::Shard(Shard::Fracture), 1);
        items.insert(Item::Shard(Shard::Arcing), 1);
        items.insert(Item::BonusUpgrade(BonusUpgrade::ExplodingSpike), 1);
        items.insert(Item::BonusUpgrade(BonusUpgrade::ShockSmash), 1);
        items.insert(Item::BonusUpgrade(BonusUpgrade::StaticStar), 1);
        items.insert(Item::BonusUpgrade(BonusUpgrade::ChargeBlaze), 1);
        items.insert(Item::BonusUpgrade(BonusUpgrade::RapidSentry), 1);

        Pool {
            inventory: Inventory {
                items,
            },
            spirit_light: 20000,
        }
    }

    pub fn grant(&mut self, item: Item, amount: u32) {
        if let Item::SpiritLight(amount) = item {
            self.spirit_light += amount;
        } else {
            self.inventory.grant(item, amount);
        }
    }
    pub fn remove(&mut self, item: &Item, amount: u32) {
        if let Item::SpiritLight(stacked_amount) = item {
            let amount = amount * stacked_amount;
            self.spirit_light = self.spirit_light.saturating_sub(amount);
        } else {
            self.inventory.remove(item, amount);
        }
    }

    pub fn contains(&self, other: &Inventory) -> bool {
        for (item, amount) in &other.items {
            if let Item::SpiritLight(1) = item {
                if &self.spirit_light < amount {
                    return false;
                }
            } else if !self.inventory.has(item, *amount) {
                return false;
            }
        }
        true
    }

    pub fn choose_random(&self, multiworld_spread: bool, rng: &mut impl Rng) -> Option<&Item> {
        let mut items = self.inventory.items.iter().collect::<Vec<_>>();
        if multiworld_spread {
            items.retain(|&(item, _)| item.is_multiworld_spread());
        }

        loop {
            let (item, _) = *items.choose_weighted(rng, |&(_, amount)| amount).ok()?;
            let cost = item.cost();

            if cost > 10000 {
                let reroll_chance = -10000.0 / f64::from(item.cost()) + 1.0;

                if rng.gen_bool(reroll_chance) {
                    log::trace!("Rerolling random placement {}", item);
                    continue;
                }
            }

            return Some(item)
        }
    }
}