overlord_event_system/
cases.rs

1use super::BehaviorRegistry;
2use crate::game_config_helpers::GameConfigLookup;
3use crate::mechanics::balance;
4use configs::game_config;
5use essences::{currency::from_es_currencies, items};
6use event_system::script::random::GameRng;
7
8pub fn try_finalize_item(
9    item: &mut items::Item,
10    game_config: &game_config::GameConfig,
11    behaviors: &BehaviorRegistry,
12) -> Result<(), String> {
13    // A native error yields no price currencies.
14    let price = crate::behaviors::items::item_price(&crate::behaviors::items::ItemPriceCtx {
15        item,
16        config: game_config,
17        lookups: behaviors.lookups(),
18    })
19    .unwrap_or_default();
20
21    item.price = from_es_currencies(&price);
22
23    let Ok(experience) = crate::behaviors::items::item_experience_eff_item(
24        &crate::behaviors::items::ItemExperienceCtx {
25            item,
26            config: game_config,
27            lookups: behaviors.lookups(),
28        },
29    ) else {
30        return Err("Couldn't calculate items experience".to_string());
31    };
32
33    item.experience = experience;
34
35    let item_clone = item.clone();
36
37    for attribute in &mut item.attributes {
38        let attr_config = game_config.attribute(attribute.attr_id).unwrap_or_else(|| {
39            panic!(
40                "Attribute with id = {} couldnt be found in config",
41                attribute.attr_id
42            )
43        });
44        // The native attribute fn is selected by the attribute's
45        // `calculation_behavior` config ref (was passed to
46        // `run_item_attribute` as `native_name`).
47        let native_name = attr_config.calculation_behavior.as_deref();
48        let Some(native_name) = native_name else {
49            return Err(format!(
50                "Attribute with id = {} has no native calculation fn",
51                attribute.attr_id
52            ));
53        };
54        let Some(attribute_fn) = behaviors.item_attribute_fn(native_name) else {
55            return Err(format!(
56                "Native attribute fn `{native_name}` not registered for attribute id = {}",
57                attribute.attr_id
58            ));
59        };
60
61        let Ok(attribute_value) = attribute_fn(&crate::behaviors::items::ItemAttributeCtx {
62            item: &item_clone,
63            attributes_quantity: item_clone.attributes.len() as i64,
64            random: &GameRng::from_entropy(),
65            config: game_config,
66            lookups: behaviors.lookups(),
67        }) else {
68            return Err(format!(
69                "Couldn't calculate attribute with id = {} value",
70                attribute.attr_id
71            ));
72        };
73
74        attribute.value = attribute_value as i32;
75    }
76
77    // Floor: an item's effective power (attr-derived power + power_bonus) must
78    // never be ≤ 0.  At very low character levels the attribute-derived power of
79    // a newly-rolled item can be in the single digits, so a negative power_bonus
80    // drawn from the jitter range could push the net contribution below zero —
81    // which is nonsensical (a fresh item making the character weaker than having
82    // no item at all).
83    //
84    // After attributes are filled we compute the item's standalone attribute
85    // power by running `power_from_attrs` over only this item's attributes, then
86    // clamp power_bonus so that (standalone_attr_power + power_bonus) >= 1.
87    // Normal items (base power >> POWER_JITTER_HALF) are unaffected; only tiny-
88    // base items at the very start of the game have their negative tail trimmed.
89    let standalone_attr_power: i64 = {
90        let mut attrs = balance::AttrMap::new();
91        for attr in &item.attributes {
92            if let Some(a) = game_config.attribute(attr.attr_id) {
93                // Items accumulate (not overwrite) within the same attribute slot.
94                *attrs.entry(a.code.as_str().to_string()).or_insert(0.0) += attr.value as f64;
95            }
96        }
97        balance::power_from_attrs(&attrs)
98    };
99    // Minimum power_bonus such that standalone_attr_power + power_bonus >= 1.
100    let min_bonus = 1_i64 - standalone_attr_power;
101    item.power_bonus = item.power_bonus.max(min_bonus as i32);
102
103    Ok(())
104}