Skip to main content

yog_api/
loot.rs

1//! Loot: change what loot tables give — a chest's, a block's drops, a
2//! mob's. [`crate::Registry::on_loot`] sees every table rolled; a
3//! [`LootModifier`] (a `.ydef`, or built in code) adds and scales items on
4//! some tables, finishing the stacks it adds with the mod's named loot
5//! functions ([`crate::Registry::loot_function`]).
6
7use std::cell::Cell;
8use std::collections::HashMap;
9use std::sync::{Arc, OnceLock, RwLock};
10
11pub use yog_registry::{Loot, LootAdd, LootModifier, LootScale, LootStack};
12
13use yog_core::Server;
14
15/// A loot function: finishes a stack a [`LootModifier`] adds (sets its custom data).
16pub(crate) type LootFunction = Arc<dyn Fn(&dyn Server, &mut LootStack, &Loot) + Send + Sync>;
17
18/// This mod's loot functions, by name.
19pub(crate) fn functions() -> &'static RwLock<HashMap<String, LootFunction>> {
20    static FUNCTIONS: OnceLock<RwLock<HashMap<String, LootFunction>>> = OnceLock::new();
21    FUNCTIONS.get_or_init(Default::default)
22}
23
24thread_local! {
25    static SEED: Cell<u64> = Cell::new({
26        let t = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_nanos() as u64);
27        t ^ 0x9e37_79b9_7f4a_7c15
28    });
29}
30
31/// A number in `[0, 1)` — what loot's chances and counts are drawn with.
32pub fn random() -> f64 {
33    SEED.with(|s| {
34        // xorshift64*
35        let mut x = s.get() | 1;
36        x ^= x >> 12;
37        x ^= x << 25;
38        x ^= x >> 27;
39        s.set(x);
40        (x.wrapping_mul(0x2545_f491_4f6c_dd1d) >> 11) as f64 / (1u64 << 53) as f64
41    })
42}
43
44/// Applies `modifier` to `loot`, running the mod's loot functions it names.
45pub(crate) fn apply(srv: &dyn Server, modifier: &LootModifier, loot: &mut Loot) {
46    let functions = functions().read().map(|f| f.clone()).unwrap_or_default();
47    modifier.apply(loot, &mut random, &mut |name, stack, loot| match functions.get(name) {
48        Some(f) => f(srv, stack, loot),
49        None => crate::error!("loot modifier {}: no loot function {name} (registry.loot_function)", modifier.id),
50    });
51}