You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

122 lines
3.8 KiB

  1. use std::collections::BTreeMap;
  2. use serde::{Serialize, Deserialize};
  3. use rand::Rng;
  4. use rand::seq::IteratorRandom;
  5. use entity::character::SectionID;
  6. use entity::item::unit::{UnitType, Unit, UnitModifier};
  7. use maps::room::{Difficulty, Episode};
  8. use maps::area::MapArea;
  9. use crate::ship::drops::{ItemDropType, load_data_file};
  10. use stats::items::{unit_stats, UnitStats};
  11. #[derive(Debug, Serialize, Deserialize)]
  12. struct UnitLevels {
  13. area1: u32,
  14. area2: u32,
  15. area3: u32,
  16. area4: u32,
  17. area5: u32,
  18. area6: u32,
  19. area7: u32,
  20. area8: u32,
  21. area9: u32,
  22. area10: u32,
  23. }
  24. impl UnitLevels {
  25. fn level_by_area(&self, map_area: &MapArea) -> u32 {
  26. match map_area.drop_area_value().unwrap() {
  27. 0 => self.area1,
  28. 1 => self.area2,
  29. 2 => self.area3,
  30. 3 => self.area1,
  31. 4 => self.area1,
  32. 5 => self.area6,
  33. 6 => self.area7,
  34. 7 => self.area8,
  35. 8 => self.area9,
  36. 9 => self.area10,
  37. _ => panic!()
  38. }
  39. }
  40. }
  41. pub struct GenericUnitTable {
  42. unit_levels: UnitLevels,
  43. unit_stats: BTreeMap<UnitType, UnitStats>,
  44. }
  45. impl GenericUnitTable {
  46. pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GenericUnitTable {
  47. GenericUnitTable {
  48. unit_levels: load_data_file(episode, difficulty, section_id, "unit_rate.toml"),
  49. unit_stats: unit_stats(),
  50. }
  51. }
  52. fn unit_type_and_modifier<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> Option<(UnitType, Option<UnitModifier>)> {
  53. let level = self.unit_levels.level_by_area(area_map) as i32;
  54. if level == 0 {
  55. return None;
  56. }
  57. let units = self.unit_stats
  58. .iter()
  59. .filter(|(_, stats)| {
  60. stats.stars < 9
  61. })
  62. .filter_map(|(utype, stats)| {
  63. match level - stats.stars as i32 {
  64. -1 if stats.modifier != 0 => Some(vec![(*utype, Some(UnitModifier::Minus)), (*utype, Some(UnitModifier::MinusMinus))]),
  65. 0 => Some(vec![(*utype, None)]),
  66. 1 if stats.modifier != 0 => Some(vec![(*utype, Some(UnitModifier::Plus)), (*utype, Some(UnitModifier::PlusPlus))]),
  67. _ => None,
  68. }
  69. })
  70. .flatten();
  71. Some(units.choose(rng).unwrap())
  72. }
  73. pub fn get_drop<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> Option<ItemDropType> {
  74. let unit_type_modifier = self.unit_type_and_modifier(area_map, rng);
  75. unit_type_modifier.map(|(unit_type, unit_modifier)| {
  76. ItemDropType::Unit(Unit {
  77. unit: unit_type,
  78. modifier: unit_modifier,
  79. })
  80. })
  81. }
  82. }
  83. #[cfg(test)]
  84. mod test {
  85. use super::*;
  86. use rand::{SeedableRng};
  87. #[test]
  88. fn test_unit_drops() {
  89. let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]);
  90. let gut = GenericUnitTable::new(Episode::One, Difficulty::Normal, SectionID::Skyly);
  91. assert!(gut.get_drop(&MapArea::Forest1, &mut rng) == None);
  92. let gut = GenericUnitTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly);
  93. let unit_tests = vec![(MapArea::Forest1, UnitType::ResistFreeze, Some(UnitModifier::PlusPlus)),
  94. (MapArea::Caves3, UnitType::GeneralTp, None),
  95. (MapArea::Mines2, UnitType::ResistEvil, Some(UnitModifier::PlusPlus)),
  96. (MapArea::DarkFalz, UnitType::DragonHp, Some(UnitModifier::Minus))];
  97. for (area, unit, umod) in unit_tests {
  98. assert!(gut.get_drop(&area, &mut rng) == Some(ItemDropType::Unit(Unit {
  99. unit: unit,
  100. modifier: umod,
  101. })));
  102. }
  103. }
  104. }