1use rand::{thread_rng, Rng};
2use serde::{Deserialize, Serialize};
3use std::borrow::Cow;
4use uuid::Uuid;
5
6use crate::{character::CharacterClass, session::PlayerCharacter};
7
8pub const ITEM_RARITY_DROP_RATES: ItemRarityDropRates = ItemRarityDropRates {
9 common: 0.43,
10 uncommon: 0.30,
11 rare: 0.14,
12 epic: 0.08,
13 legendary: 0.05,
14};
15pub const WEAPON_BASE_VALUES: WeaponBaseValues = WeaponBaseValues {
16 min_damage: 12,
17 max_damage: 15,
18 min_crit_hit_rate: 0.12,
19 max_crit_hit_rate: 0.15,
20};
21pub const ARMOR_BASE_VALUES: ArmorBaseValues = ArmorBaseValues {
22 min_health: 15,
23 max_health: 20,
24 min_defense: 1,
25 max_defense: 2,
26};
27pub const RING_BASE_VALUES: RingBaseValues = RingBaseValues {
28 min_mana: 20,
29 max_mana: 25,
30};
31pub const ENCHANTMENT_BASE_VALUES: EnchantmentBaseValues = EnchantmentBaseValues {
32 min_damage: 3,
33 max_damage: 5,
34 min_crit_hit_rate: 0.03,
35 max_crit_hit_rate: 0.05,
36 min_health: 7,
37 max_health: 10,
38 min_defense: 1,
39 max_defense: 2,
40 min_mana: 10,
41 max_mana: 15,
42};
43
44pub const ITEM_HEALTH_POTION_NAME: &str = "Health Potion";
49pub const ITEM_HEALTH_POTION: ItemInfo = ItemInfo {
50 name: Cow::Borrowed(ITEM_HEALTH_POTION_NAME),
51 description: Cow::Borrowed("A magical potion that restores health points."),
52 category: ItemCategory::Consumable,
53};
54
55pub const ITEM_MANA_POTION_NAME: &str = "Mana Potion";
56pub const ITEM_MANA_POTION: ItemInfo = ItemInfo {
57 name: Cow::Borrowed(ITEM_MANA_POTION_NAME),
58 description: Cow::Borrowed("A magical potion that restores mana points."),
59 category: ItemCategory::Consumable,
60};
61
62pub const ITEM_SWORD: ItemInfo = ItemInfo {
67 name: Cow::Borrowed("Sword"),
68 description: Cow::Borrowed("A sword that increases offensive stats."),
69 category: ItemCategory::Weapon,
70};
71
72pub const ITEM_AXE: ItemInfo = ItemInfo {
73 name: Cow::Borrowed("Axe"),
74 description: Cow::Borrowed("An axe that increases offensive stats."),
75 category: ItemCategory::Weapon,
76};
77
78pub const ITEM_STAFF: ItemInfo = ItemInfo {
79 name: Cow::Borrowed("Staff"),
80 description: Cow::Borrowed("A staff that increases offensive stats."),
81 category: ItemCategory::Weapon,
82};
83
84pub const ITEM_DAGGER: ItemInfo = ItemInfo {
85 name: Cow::Borrowed("Dagger"),
86 description: Cow::Borrowed("A dagger that increases offensive stats."),
87 category: ItemCategory::Weapon,
88};
89
90pub const ITEM_HALBERD: ItemInfo = ItemInfo {
91 name: Cow::Borrowed("Halberd"),
92 description: Cow::Borrowed("A halberd that increases offensive stats."),
93 category: ItemCategory::Weapon,
94};
95
96pub const ITEM_ARMOR: ItemInfo = ItemInfo {
101 name: Cow::Borrowed("Armor"),
102 description: Cow::Borrowed("An armor that increases defensive stats."),
103 category: ItemCategory::Armor,
104};
105
106pub const ITEM_RING: ItemInfo = ItemInfo {
111 name: Cow::Borrowed("Ring"),
112 description: Cow::Borrowed("A ring that increases some stats."),
113 category: ItemCategory::Ring,
114};
115
116#[derive(Serialize, Deserialize, Clone)]
119pub struct ItemInfo {
120 pub name: Cow<'static, str>,
121 pub description: Cow<'static, str>,
122 pub category: ItemCategory,
123}
124
125#[derive(Serialize, Deserialize, Clone)]
126pub struct ConsumableItem {
127 pub info: ItemInfo,
128 pub effect: String,
129 pub rarity: ItemRarity,
130 pub amount_in_inventory: u32,
131}
132
133impl ConsumableItem {
134 pub fn new_health_potion(rarity: ItemRarity) -> Self {
135 Self {
136 info: ITEM_HEALTH_POTION,
137 effect: get_health_potion_effect(&rarity),
138 rarity,
139 amount_in_inventory: 0,
140 }
141 }
142
143 pub fn new_mana_potion(rarity: ItemRarity) -> Self {
144 Self {
145 info: ITEM_MANA_POTION,
146 effect: get_mana_potion_effect(&rarity),
147 rarity,
148 amount_in_inventory: 0,
149 }
150 }
151
152 pub fn use_item(&self, character: &mut PlayerCharacter) -> (String, ItemRarity, String) {
154 let display_name = get_item_display_name(CharacterItem::Consumable(&self));
155 let mut item_name = "Player used an unknown item.".to_string();
156 let mut effect = "Nothing happened".to_string();
157 let mut rarity = ItemRarity::Unknown;
158 match self.info.name.as_ref() {
159 ITEM_HEALTH_POTION_NAME => {
160 let heal_percentage = get_potion_effect_percentage(&self.rarity) as f64 / 100.0;
161 let restored_health = character
162 .restore_health((heal_percentage * character.get_total_health() as f64) as u32);
163 item_name = format!("{}", &display_name);
164 rarity = self.rarity.clone();
165 effect = format!("Player restored {} health points", restored_health);
166 }
167 ITEM_MANA_POTION_NAME => {
168 let heal_percentage = get_potion_effect_percentage(&self.rarity) as f64 / 100.0;
169 let restored_mana = character
170 .restore_mana((heal_percentage * character.get_total_mana() as f64) as u32);
171 item_name = format!("{}", &display_name);
172 rarity = self.rarity.clone();
173 effect = format!("Player restored {} mana points", restored_mana);
174 }
175 _ => {}
176 }
177 if self.amount_in_inventory > 1 {
178 character.decrease_consumable_inventory_amount(&display_name, 1);
179 } else {
180 character.delete_consumable(&display_name);
181 }
182 (item_name, rarity, effect)
183 }
184}
185
186#[derive(Serialize, Deserialize, Clone)]
187pub struct ArmorItemStats {
188 pub health: u32,
189 pub defense: u32,
190}
191
192#[derive(Serialize, Deserialize, Clone)]
193pub struct ArmorItem {
194 pub info: ItemInfo,
195 pub id: String,
196 pub level: u32,
197 pub rarity: ItemRarity,
198 pub stats: ArmorItemStats,
199 pub enchantments: Vec<Enchantment>,
200}
201
202impl ArmorItem {
203 pub fn new(
204 info: ItemInfo,
205 level: u32,
206 rarity: ItemRarity,
207 stats: ArmorItemStats,
208 enchantments: Vec<Enchantment>,
209 ) -> Self {
210 Self {
211 info,
212 id: Uuid::new_v4().to_string(),
213 level,
214 rarity,
215 stats,
216 enchantments,
217 }
218 }
219
220 pub fn is_equipped(&self, character: &PlayerCharacter) -> bool {
221 if let Some(id) = &character.equipped_items.armor {
222 if let Some(armor) = character.data.inventory.armors.get(id) {
223 if armor.id.eq(&self.id) {
224 return true;
225 }
226 }
227 }
228 false
229 }
230}
231
232#[derive(Serialize, Deserialize, Clone)]
233pub struct WeaponItemStats {
234 pub damage: u32,
235 pub crit_hit_rate: f64,
236}
237
238#[derive(Serialize, Deserialize, Clone)]
239pub struct WeaponItem {
240 pub info: ItemInfo,
241 pub id: String,
242 pub level: u32,
243 pub rarity: ItemRarity,
244 pub stats: WeaponItemStats,
245 pub enchantments: Vec<Enchantment>,
246}
247
248impl WeaponItem {
249 pub fn new(
250 info: ItemInfo,
251 level: u32,
252 rarity: ItemRarity,
253 stats: WeaponItemStats,
254 enchantments: Vec<Enchantment>,
255 ) -> Self {
256 Self {
257 info,
258 id: Uuid::new_v4().to_string(),
259 level,
260 rarity,
261 stats,
262 enchantments,
263 }
264 }
265
266 pub fn is_equipped(&self, character: &PlayerCharacter) -> bool {
267 if let Some(id) = &character.equipped_items.weapon {
268 if let Some(weapon) = character.data.inventory.weapons.get(id) {
269 if weapon.id.eq(&self.id) {
270 return true;
271 }
272 }
273 }
274 false
275 }
276}
277
278#[derive(Serialize, Deserialize, Clone)]
279pub struct RingItemStats {
280 pub mana: u32,
281}
282
283#[derive(Serialize, Deserialize, Clone)]
284pub struct RingItem {
285 pub info: ItemInfo,
286 pub id: String,
287 pub level: u32,
288 pub rarity: ItemRarity,
289 pub stats: RingItemStats,
290 pub enchantments: Vec<Enchantment>,
291}
292
293impl RingItem {
294 pub fn new(
295 info: ItemInfo,
296 level: u32,
297 rarity: ItemRarity,
298 stats: RingItemStats,
299 enchantments: Vec<Enchantment>,
300 ) -> Self {
301 Self {
302 info,
303 id: Uuid::new_v4().to_string(),
304 level,
305 rarity,
306 stats,
307 enchantments,
308 }
309 }
310
311 pub fn is_equipped(&self, character: &PlayerCharacter) -> bool {
312 if let Some(id) = &character.equipped_items.ring {
313 if let Some(ring) = character.data.inventory.rings.get(id) {
314 if ring.id.eq(&self.id) {
315 return true;
316 }
317 }
318 }
319 false
320 }
321}
322
323pub struct ItemRarityDropRates {
324 pub common: f64,
325 pub uncommon: f64,
326 pub rare: f64,
327 pub epic: f64,
328 pub legendary: f64,
329}
330
331pub struct WeaponBaseValues {
332 pub min_damage: u32,
333 pub max_damage: u32,
334 pub min_crit_hit_rate: f64,
335 pub max_crit_hit_rate: f64,
336}
337
338pub struct ArmorBaseValues {
339 pub min_health: u32,
340 pub max_health: u32,
341 pub min_defense: u32,
342 pub max_defense: u32,
343}
344
345pub struct RingBaseValues {
346 pub min_mana: u32,
347 pub max_mana: u32,
348}
349
350pub struct EnchantmentBaseValues {
351 pub min_damage: u32,
352 pub max_damage: u32,
353 pub min_crit_hit_rate: f64,
354 pub max_crit_hit_rate: f64,
355 pub min_health: u32,
356 pub max_health: u32,
357 pub min_defense: u32,
358 pub max_defense: u32,
359 pub min_mana: u32,
360 pub max_mana: u32,
361}
362
363pub enum CharacterItem<'a> {
364 Consumable(&'a ConsumableItem),
365 Weapon(&'a WeaponItem),
366 Armor(&'a ArmorItem),
367 Ring(&'a RingItem),
368 Unknown,
369}
370
371pub enum CharacterItemOwned {
372 Consumable(ConsumableItem),
373 Weapon(WeaponItem),
374 Armor(ArmorItem),
375 Ring(RingItem),
376 Unknown,
377}
378
379#[derive(Serialize, Deserialize, Clone)]
380pub enum Enchantment {
381 Damage(u32),
382 CritHitRate(f64),
383 Health(u32),
384 Defense(u32),
385 Mana(u32),
386 Unknown,
387}
388
389#[derive(Debug, Serialize, Deserialize, Clone)]
390pub enum ItemRarity {
391 Common,
392 Uncommon,
393 Rare,
394 Epic,
395 Legendary,
396 Mythical,
397 Unknown,
398}
399
400#[derive(Debug, Serialize, Deserialize, Clone)]
401pub enum ItemCategory {
402 Consumable,
403 Weapon,
404 Armor,
405 Ring,
406 Unknown,
407}
408
409pub fn get_potion_effect_percentage(rarity: &ItemRarity) -> i32 {
414 match rarity {
415 ItemRarity::Common => 20,
416 ItemRarity::Uncommon => 40,
417 ItemRarity::Rare => 60,
418 ItemRarity::Epic => 80,
419 ItemRarity::Legendary => 100,
420 _ => 0,
421 }
422}
423
424pub fn get_health_potion_effect(rarity: &ItemRarity) -> String {
425 format!(
426 "Restores {}% of your maximum health points.",
427 get_potion_effect_percentage(rarity)
428 )
429}
430
431pub fn get_mana_potion_effect(rarity: &ItemRarity) -> String {
432 format!(
433 "Restores {}% of your maximum mana points.",
434 get_potion_effect_percentage(rarity)
435 )
436}
437
438pub fn get_item_display_name<'a>(item: CharacterItem<'a>) -> String {
441 match item {
442 CharacterItem::Consumable(consumable) => {
443 format!("{:?} {}", consumable.rarity, consumable.info.name)
444 }
445 CharacterItem::Weapon(weapon) => {
446 format!("{:?} {}", weapon.rarity, weapon.info.name)
447 }
448 CharacterItem::Armor(armor) => {
449 format!("{:?} {}", armor.rarity, armor.info.name)
450 }
451 CharacterItem::Ring(ring) => {
452 format!("{:?} {}", ring.rarity, ring.info.name)
453 }
454 _ => format!("?Unknown?"),
455 }
456}
457
458pub fn get_item_level_display<'a>(level: u32) -> String {
459 format!("(Level {})", level)
460}
461
462pub fn get_item_purchase_value(rarity: &ItemRarity) -> u32 {
464 match rarity {
465 ItemRarity::Common => 200,
466 ItemRarity::Uncommon => 400,
467 ItemRarity::Rare => 600,
468 ItemRarity::Epic => 800,
469 ItemRarity::Legendary => 1000,
470 _ => 0,
471 }
472}
473
474pub fn get_item_sell_value(rarity: &ItemRarity) -> u32 {
476 match rarity {
477 ItemRarity::Common => 25,
478 ItemRarity::Uncommon => 50,
479 ItemRarity::Rare => 75,
480 ItemRarity::Epic => 100,
481 ItemRarity::Legendary => 125,
482 ItemRarity::Mythical => 150,
483 _ => 0,
484 }
485}
486
487pub fn create_starter_weapon(character_class: &CharacterClass) -> WeaponItem {
488 let item_info = match character_class {
489 CharacterClass::Mage => ITEM_STAFF,
490 CharacterClass::Cleric => ITEM_HALBERD,
491 CharacterClass::Assassin => ITEM_DAGGER,
492 CharacterClass::Warrior => ITEM_AXE,
493 CharacterClass::Knight => ITEM_SWORD,
494 };
495 WeaponItem::new(
496 item_info,
497 1,
498 ItemRarity::Common,
499 WeaponItemStats {
500 damage: WEAPON_BASE_VALUES.min_damage,
501 crit_hit_rate: WEAPON_BASE_VALUES.min_crit_hit_rate,
502 },
503 Vec::new(),
504 )
505}
506
507pub fn random_equipment_item() -> ItemCategory {
508 let mut rng = rand::thread_rng();
509 let rand_num = rng.gen_range(0..=2);
510 match rand_num {
511 0 => ItemCategory::Weapon,
512 1 => ItemCategory::Armor,
513 2 => ItemCategory::Ring,
514 _ => ItemCategory::Unknown,
515 }
516}
517
518pub fn random_item_rarity(drop_rates: &ItemRarityDropRates) -> ItemRarity {
519 let mut rng = rand::thread_rng();
520 let rand_num = rng.gen_range(0.0..1.0);
521 let mut drop_rate = 0.0;
522
523 drop_rate += drop_rates.common;
524 if rand_num < drop_rate {
525 return ItemRarity::Common;
526 }
527
528 drop_rate += drop_rates.uncommon;
529 if rand_num < drop_rate {
530 return ItemRarity::Uncommon;
531 }
532
533 drop_rate += drop_rates.rare;
534 if rand_num < drop_rate {
535 return ItemRarity::Rare;
536 }
537
538 drop_rate += drop_rates.epic;
539 if rand_num < drop_rate {
540 return ItemRarity::Epic;
541 }
542
543 drop_rate += drop_rates.legendary;
544 if rand_num < drop_rate {
545 return ItemRarity::Legendary;
546 }
547
548 ItemRarity::Unknown
549}
550
551pub fn num_enchantments(rarity: &ItemRarity) -> u8 {
552 match rarity {
553 ItemRarity::Common => 0,
554 ItemRarity::Uncommon => 1,
555 ItemRarity::Rare => 2,
556 ItemRarity::Epic => 3,
557 ItemRarity::Legendary => 4,
558 ItemRarity::Mythical => 5,
559 _ => 0,
560 }
561}
562
563pub fn generate_item_enchantments(
564 num: u8,
565 category: ItemCategory,
566 base_values: &EnchantmentBaseValues,
567 dungeon_floor: u32,
568) -> Vec<Enchantment> {
569 let mut enchantments: Vec<Enchantment> = Vec::new();
570 for _ in 0..num {
571 match category {
572 ItemCategory::Weapon => {
573 enchantments.push(random_weapon_enchantment(base_values, dungeon_floor))
574 }
575 ItemCategory::Armor => {
576 enchantments.push(random_armor_enchantment(base_values, dungeon_floor))
577 }
578 ItemCategory::Ring => {
579 enchantments.push(random_ring_enchantment(base_values, dungeon_floor))
580 }
581 _ => {}
582 }
583 }
584 enchantments
585}
586
587pub fn random_weapon_enchantment(
588 base_values: &EnchantmentBaseValues,
589 dungeon_floor: u32,
590) -> Enchantment {
591 let mut rng = thread_rng();
592 let rand_num = rng.gen_range(0..=1);
593 match rand_num {
594 0 => {
595 let damage = rng.gen_range(base_values.min_damage..=base_values.max_damage)
596 + (2 * dungeon_floor);
597 return Enchantment::Damage(damage);
598 }
599 1 => {
600 let crit_hit_rate =
601 rng.gen_range(base_values.min_crit_hit_rate..=base_values.max_crit_hit_rate);
602 return Enchantment::CritHitRate(crit_hit_rate);
603 }
604 _ => Enchantment::Unknown,
605 }
606}
607
608pub fn random_armor_enchantment(
609 base_values: &EnchantmentBaseValues,
610 dungeon_floor: u32,
611) -> Enchantment {
612 let mut rng = thread_rng();
613 let rand_num = rng.gen_range(0..=1);
614 match rand_num {
615 0 => {
616 let health = rng.gen_range(base_values.min_health..=base_values.max_health)
617 + (4 * dungeon_floor);
618 return Enchantment::Health(health);
619 }
620 1 => {
621 let defense = rng.gen_range(base_values.min_defense..=base_values.max_defense)
622 + (1 * dungeon_floor);
623 return Enchantment::Defense(defense);
624 }
625 _ => Enchantment::Unknown,
626 }
627}
628
629pub fn random_ring_enchantment(
630 base_values: &EnchantmentBaseValues,
631 dungeon_floor: u32,
632) -> Enchantment {
633 let mut rng = thread_rng();
634 let rand_num = rng.gen_range(0..=3);
635 match rand_num {
636 0 => {
637 let mana = rng.gen_range(base_values.min_mana..=base_values.max_mana);
638 return Enchantment::Mana(mana);
639 }
640 1 => {
641 let damage = rng.gen_range(base_values.min_damage..=base_values.max_damage)
642 + (2 * dungeon_floor);
643 return Enchantment::Damage(damage);
644 }
645 2 => {
646 let health = rng.gen_range(base_values.min_health..=base_values.max_health)
647 + (3 * dungeon_floor);
648 return Enchantment::Health(health);
649 }
650 3 => {
651 let crit_hit_rate =
652 rng.gen_range(base_values.min_crit_hit_rate..=base_values.max_crit_hit_rate);
653 return Enchantment::CritHitRate(crit_hit_rate);
654 }
655 _ => Enchantment::Unknown,
656 }
657}
658
659pub fn generate_random_weapon(
660 rarity: ItemRarity,
661 base_values: WeaponBaseValues,
662 dungeon_floor: u32,
663 character_class: &CharacterClass,
664) -> WeaponItem {
665 let mut rng = thread_rng();
666 let damage =
667 rng.gen_range(base_values.min_damage..=base_values.max_damage) + (3 * dungeon_floor);
668 let crit_hit_rate =
669 rng.gen_range(base_values.min_crit_hit_rate..=base_values.max_crit_hit_rate);
670 let enchantments = generate_item_enchantments(
671 num_enchantments(&rarity),
672 ItemCategory::Weapon,
673 &ENCHANTMENT_BASE_VALUES,
674 dungeon_floor,
675 );
676 let item_info = match character_class {
677 CharacterClass::Mage => ITEM_STAFF,
678 CharacterClass::Cleric => ITEM_HALBERD,
679 CharacterClass::Assassin => ITEM_DAGGER,
680 CharacterClass::Warrior => ITEM_AXE,
681 CharacterClass::Knight => ITEM_SWORD,
682 };
683
684 WeaponItem::new(
685 item_info,
686 dungeon_floor,
687 rarity,
688 WeaponItemStats {
689 damage,
690 crit_hit_rate,
691 },
692 enchantments,
693 )
694}
695
696pub fn generate_random_armor(
697 rarity: ItemRarity,
698 base_values: ArmorBaseValues,
699 dungeon_floor: u32,
700) -> ArmorItem {
701 let mut rng = thread_rng();
702 let health =
703 rng.gen_range(base_values.min_health..=base_values.max_health) + (8 * dungeon_floor);
704 let defense =
705 rng.gen_range(base_values.min_defense..=base_values.max_defense) + (2 * dungeon_floor);
706 let enchantments = generate_item_enchantments(
707 num_enchantments(&rarity),
708 ItemCategory::Armor,
709 &ENCHANTMENT_BASE_VALUES,
710 dungeon_floor,
711 );
712
713 ArmorItem::new(
714 ITEM_ARMOR,
715 dungeon_floor,
716 rarity,
717 ArmorItemStats { health, defense },
718 enchantments,
719 )
720}
721
722pub fn generate_random_ring(
723 rarity: ItemRarity,
724 base_values: RingBaseValues,
725 dungeon_floor: u32,
726) -> RingItem {
727 let mut rng = thread_rng();
728 let mana = rng.gen_range(base_values.min_mana..=base_values.max_mana);
729 let enchantments = generate_item_enchantments(
730 num_enchantments(&rarity),
731 ItemCategory::Ring,
732 &ENCHANTMENT_BASE_VALUES,
733 dungeon_floor,
734 );
735
736 RingItem::new(
737 ITEM_RING,
738 dungeon_floor,
739 rarity,
740 RingItemStats { mana },
741 enchantments,
742 )
743}
744
745pub fn generate_random_consumable() -> ConsumableItem {
746 let mut rng = thread_rng();
747 let num = rng.gen_range(0..2);
748 let rarity = random_item_rarity(&ITEM_RARITY_DROP_RATES);
749
750 match num {
751 0 => ConsumableItem::new_health_potion(rarity),
752 1 => ConsumableItem::new_mana_potion(rarity),
753 _ => ConsumableItem::new_health_potion(rarity),
754 }
755}