1use configs::game_config::GameConfig;
32use essences::items::{Item, ItemType};
33use event_system::script::random::GameRng;
34use uuid::Uuid;
35
36use crate::behaviors::{BehaviorKind, BehaviorMeta, BehaviorRegistry};
37use crate::mechanics::balance;
38use crate::mechanics::content_lookups::ContentLookups;
39
40pub struct ItemAttributeCtx<'a> {
45 pub item: &'a Item,
46 pub attributes_quantity: i64,
47 pub random: &'a GameRng,
48 pub config: &'a GameConfig,
49 pub lookups: &'a ContentLookups,
50}
51
52pub type ItemAttributeFn = fn(&ItemAttributeCtx) -> anyhow::Result<i64>;
55
56fn eff_item(ctx: &ItemAttributeCtx) -> f64 {
58 balance::eff_item_with_config(
59 ctx.config,
60 ctx.lookups,
61 ctx.item.item_template_id,
62 ctx.item.level as f64,
63 )
64}
65
66fn attr_spread(ctx: &ItemAttributeCtx) -> f64 {
68 balance::attr_spread_for_item(
69 ctx.config,
70 ctx.lookups,
71 ctx.random,
72 ctx.item.item_template_id,
73 ctx.item.level as f64,
74 )
75}
76
77fn aux_attr_eff(ctx: &ItemAttributeCtx, base_eff: f64) -> f64 {
79 balance::aux_attr_eff_for_item(
80 ctx.config,
81 ctx.lookups,
82 base_eff,
83 ctx.random,
84 ctx.item.item_template_id,
85 ctx.item.level as f64,
86 )
87}
88
89pub fn attr_health(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
91 let aux_attrs = (ctx.attributes_quantity - balance::MAIN_ATTRS_QUANTITY) as f64;
92 let base_attrs_impact = 1.0 - aux_attrs * balance::AUX_ATTR_IMPACT;
93 let base_attr_impact = base_attrs_impact * 0.5;
94 let eff = eff_item(ctx);
95 let rand_mod = attr_spread(ctx);
96 let attr_eff = (eff * rand_mod).powf(base_attr_impact);
97 let hp_k = balance::hp_k_for_level(ctx.item.level as f64);
98 Ok((balance::BASE_HP * attr_eff * hp_k / 10.0).floor() as i64)
99}
100
101pub fn attr_armor(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
103 let dmg_increase = balance::eff_spell_by_level(ctx.item.level as f64);
104 let spread = attr_spread(ctx);
105 let dr = (1.0 - 1.0 / (dmg_increase * spread)).max(0.03) * 10000.0;
106 Ok((dr / 10.0).floor() as i64)
107}
108
109pub fn attr_damage(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
111 let aux_attrs = (ctx.attributes_quantity - balance::MAIN_ATTRS_QUANTITY) as f64;
112 let base_attrs_impact = 1.0 - aux_attrs * balance::AUX_ATTR_IMPACT;
113 let base_attr_impact = base_attrs_impact * 0.5;
114 let eff = eff_item(ctx);
115 let rand_mod = attr_spread(ctx);
116 let attr_eff = eff.powf(base_attr_impact) * rand_mod;
118 Ok((balance::BASE_ATTACK * attr_eff / 10.0).floor() as i64)
119}
120
121pub fn attr_crit_chance(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
123 let eff = eff_item(ctx);
124 let attr_eff = aux_attr_eff(ctx, eff).powi(2);
125 let crit_chance = (-1.0 + (8.0 * attr_eff - 7.0).powf(0.5)) / 4.0;
126 Ok((crit_chance * 10000.0 / 10.0).floor() as i64)
127}
128
129pub fn attr_crit_damage(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
131 let eff = eff_item(ctx);
132 let attr_eff = aux_attr_eff(ctx, eff).powi(2);
133 let crit_mod = (-1.0 + (8.0 * attr_eff - 7.0).powf(0.5)) / 2.0;
134 Ok((crit_mod * 10000.0 / 10.0).floor() as i64)
135}
136
137pub fn attr_evasion(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
139 let eff = eff_item(ctx);
140 let attr_eff = aux_attr_eff(ctx, eff);
141 let evasion_chance = 1.0 - 1.0 / attr_eff;
142 Ok((evasion_chance * 10000.0 / 10.0).floor() as i64)
143}
144
145pub fn attr_speed(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
147 let eff = eff_item(ctx);
148 let attr_eff = aux_attr_eff(ctx, eff);
149 Ok(((attr_eff - 1.0) * 10000.0 / 10.0).floor() as i64)
150}
151
152pub fn attr_hp_regen(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
154 let aux_attrs = (ctx.attributes_quantity - balance::MAIN_ATTRS_QUANTITY) as f64;
155 let eff = eff_item(ctx);
156 let base_attr_eff = eff.powf(1.0 - aux_attrs * balance::AUX_ATTR_IMPACT);
157 let hp_eff = base_attr_eff.powf(0.5);
158 let hp = hp_eff * balance::BASE_HP;
159 let attr_eff = aux_attr_eff(ctx, eff);
160 let hp_per_sec = hp * (attr_eff - 1.0) / (balance::FIGHT_DURATION * attr_eff);
161 Ok((hp_per_sec / 10.0).floor() as i64)
162}
163
164pub fn attr_multi_cast(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
166 let eff = eff_item(ctx);
167 let attr_eff = aux_attr_eff(ctx, eff);
168 Ok(((attr_eff - 1.0) * 10000.0 / 10.0).floor() as i64)
169}
170
171pub fn attr_counter_attack(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
173 let eff = eff_item(ctx);
174 let attr_eff = aux_attr_eff(ctx, eff);
175 let dmg_increase = balance::eff_spell_by_level(ctx.item.level as f64);
176 let dps = dmg_increase * balance::SPELL_QUANTITY as f64;
177 let overall_damage = dps * balance::FIGHT_DURATION;
178 let added_damage = overall_damage * (attr_eff - 1.0);
179 let attacks_per_fight = balance::FIGHT_DURATION * balance::ATTACKS_PER_SEC;
180 let p = added_damage / attacks_per_fight / balance::COUNTERATTACK_POWER;
181 Ok((p * 10000.0 / 10.0).floor() as i64)
182}
183
184pub fn attr_bravery(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
186 let eff = eff_item(ctx);
187 let attr_eff = aux_attr_eff(ctx, eff);
188 let p = balance::bravery_p_from_eff(attr_eff);
189 Ok((p * 10000.0 / 10.0).floor() as i64)
190}
191
192pub fn attr_guile(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
194 let eff = eff_item(ctx);
195 let attr_eff = aux_attr_eff(ctx, eff);
196 let p = balance::deceit_p_from_eff(attr_eff);
197 Ok((p * 10000.0 / 10.0).floor() as i64)
198}
199
200pub fn attr_block(ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
202 let eff = eff_item(ctx);
203 let attr_eff = aux_attr_eff(ctx, eff).min(2.0);
205 let block_chance = 2.0 - 2.0 / attr_eff;
206 Ok((block_chance * 10000.0 / 10.0).floor() as i64)
207}
208
209pub fn attr_zero(_ctx: &ItemAttributeCtx) -> anyhow::Result<i64> {
212 Ok(0)
213}
214
215pub fn register(registry: &mut BehaviorRegistry) {
218 let fns: &[(&str, &str, &str, ItemAttributeFn)] = &[
219 (
220 "attr_health",
221 "Атрибут: здоровье",
222 "Порт calculation_behavior атрибута Health.",
223 attr_health,
224 ),
225 (
226 "attr_armor",
227 "Атрибут: броня",
228 "Порт calculation_behavior атрибута Armor.",
229 attr_armor,
230 ),
231 (
232 "attr_damage",
233 "Атрибут: урон",
234 "Порт calculation_behavior атрибута Damage.",
235 attr_damage,
236 ),
237 (
238 "attr_crit_chance",
239 "Атрибут: шанс крита",
240 "Порт calculation_behavior атрибута Crit_Chance.",
241 attr_crit_chance,
242 ),
243 (
244 "attr_crit_damage",
245 "Атрибут: крит. урон",
246 "Порт calculation_behavior атрибута Crit_Damage.",
247 attr_crit_damage,
248 ),
249 (
250 "attr_evasion",
251 "Атрибут: уклонение",
252 "Порт calculation_behavior атрибута Evasion.",
253 attr_evasion,
254 ),
255 (
256 "attr_speed",
257 "Атрибут: скорость",
258 "Порт calculation_behavior атрибута Speed.",
259 attr_speed,
260 ),
261 (
262 "attr_hp_regen",
263 "Атрибут: реген HP",
264 "Порт calculation_behavior атрибута HP_Regen.",
265 attr_hp_regen,
266 ),
267 (
268 "attr_multi_cast",
269 "Атрибут: мультикаст",
270 "Порт calculation_behavior атрибута Multi_Cast.",
271 attr_multi_cast,
272 ),
273 (
274 "attr_counter_attack",
275 "Атрибут: контратака",
276 "Порт calculation_behavior атрибута Counter_Attack.",
277 attr_counter_attack,
278 ),
279 (
280 "attr_bravery",
281 "Атрибут: храбрость",
282 "Порт calculation_behavior атрибута Bravery.",
283 attr_bravery,
284 ),
285 (
286 "attr_guile",
287 "Атрибут: коварство",
288 "Порт calculation_behavior атрибута Guile.",
289 attr_guile,
290 ),
291 (
292 "attr_block",
293 "Атрибут: блок",
294 "Порт calculation_behavior атрибута Block.",
295 attr_block,
296 ),
297 (
298 "attr_zero",
299 "Атрибут: ноль",
300 "Возвращает 0 (порт пустых / `0` calculation_behavior: \
301 Damage_Received, Bonus_Health, Bonus_damage).",
302 attr_zero,
303 ),
304 ];
305 for (name, title, description, f) in fns {
306 registry.register_item_attribute(
307 BehaviorMeta {
308 name: name.to_string(),
309 category: BehaviorKind::ItemAttribute,
310 title: title.to_string(),
311 description: description.to_string(),
312 },
313 *f,
314 );
315 }
316}
317
318const ITEMS_FOR_SLOTS: &[(&str, &str)] = &[
321 ("Boots", "0194d64e-216d-7059-863f-26f67c64267b"),
322 ("Torso", "0194d64e-216d-7059-863f-26f7e69b8f4a"),
323 ("Head", "0194d64e-216d-7059-863f-26f85eb1e25f"),
324 ("Gloves", "0194d64e-216d-7059-863f-26f9ab123d46"),
325 ("Ring", "0194d64e-216d-7059-863f-26fa1fc8410b"),
326 ("Waist", "0194d64e-216d-7059-863f-26fb000bf4e9"),
327 ("Legs", "0194d64e-216d-7059-863f-26fc9c33e67e"),
328 ("Shoulders", "0194d64e-216d-7059-863f-26fddb278b04"),
329 ("Neck", "0194d64e-216d-7059-863f-26fec3786536"),
330 ("Weapon", "0194d64e-216d-7059-863f-26ff77d309a3"),
331];
332
333pub struct ItemPriceCtx<'a> {
336 pub item: &'a Item,
337 pub config: &'a GameConfig,
338 pub lookups: &'a ContentLookups,
339}
340
341pub fn item_price(
347 ctx: &ItemPriceCtx,
348) -> anyhow::Result<Vec<event_system::script::types::ESCurrencyUnit>> {
349 let eff = crate::mechanics::balance::eff_item_with_config(
350 ctx.config,
351 ctx.lookups,
352 ctx.item.item_template_id,
353 ctx.item.level as f64,
354 );
355 let price = crate::mechanics::balance::sell_price(eff);
356 Ok(vec![event_system::script::types::ESCurrencyUnit {
357 currency_id: Uuid::from_u128(0x0194d64e_2386_7020_8b01_d6b3d5424506),
358 amount: price,
359 }])
360}
361
362pub struct ItemExperienceCtx<'a> {
364 pub item: &'a Item,
365 pub config: &'a GameConfig,
366 pub lookups: &'a ContentLookups,
367}
368
369pub fn item_experience_eff_item(ctx: &ItemExperienceCtx) -> anyhow::Result<i64> {
371 let eff = crate::mechanics::balance::eff_item_with_config(
372 ctx.config,
373 ctx.lookups,
374 ctx.item.item_template_id,
375 ctx.item.level as f64,
376 );
377 Ok((eff * 100.0).floor() as i64)
378}
379
380pub struct ChestItemChooseCtx<'a> {
382 pub character: &'a essences::character_state::CharacterState,
383 pub config: &'a GameConfig,
384 pub lookups: &'a ContentLookups,
385}
386
387pub fn chest_item_choose(ctx: &ChestItemChooseCtx) -> anyhow::Result<Option<Uuid>> {
391 use crate::mechanics::content;
392
393 let character = &ctx.character.character;
394
395 if let Some(&code) = character.custom_values.0.get("next_mimic_item_code")
397 && code != 0
398 && let Some(item) = content::get_item_by_code(ctx.config, ctx.lookups, code)
399 {
400 return Ok(Some(item.id));
401 }
402
403 let mut levels: Vec<&content::InventoryLevel> =
406 content::get_inventory_levels(ctx.config).iter().collect();
407 levels.sort_by_key(|l| std::cmp::Reverse(l.from_chapter_level));
408 let Some(current_level) = levels
409 .into_iter()
410 .find(|l| l.from_chapter_level <= character.current_chapter_level)
411 else {
412 return Ok(None);
413 };
414
415 let mut slots: Vec<String> = current_level
418 .item_types
419 .iter()
420 .filter(|t| **t != ItemType::Artifact)
421 .map(|t| t.to_string())
422 .collect();
423 for item in &ctx.character.inventory {
424 if item.is_equipped {
425 let equipped = item.item_type.to_string();
426 slots.retain(|s| *s != equipped);
427 }
428 }
429
430 if let Some(selected_slot) = slots.first() {
431 if let Some((_, uuid_str)) = ITEMS_FOR_SLOTS.iter().find(|(s, _)| s == selected_slot) {
435 let selected = Uuid::parse_str(uuid_str)
436 .map_err(|e| anyhow::anyhow!("ITEMS_FOR_SLOTS bad uuid {uuid_str:?}: {e}"))?;
437 if ctx.config.items.iter().any(|i| i.id == selected) {
438 return Ok(Some(selected));
439 }
440 }
441 if let Some(item) = ctx
442 .config
443 .items
444 .iter()
445 .find(|i| i.item_type.to_string() == *selected_slot)
446 {
447 return Ok(Some(item.id));
448 }
449 }
450
451 Ok(None)
452}