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.

571 lines
18 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. use std::collections::HashMap;
  2. use std::fs::File;
  3. use std::io::Read;
  4. use std::path::PathBuf;
  5. use std::convert::TryInto;
  6. use std::cmp::Ordering;
  7. use serde::Deserialize;
  8. use rand::{Rng, SeedableRng};
  9. use rand::distributions::{WeightedIndex, Distribution};
  10. use rand::seq::{SliceRandom, IteratorRandom};
  11. use entity::character::SectionID;
  12. use maps::room::Difficulty;
  13. use entity::item::ItemDetail;
  14. use entity::item::weapon::{Weapon, WeaponType, WeaponSpecial, Attribute, WeaponAttribute};
  15. use crate::ShopItem;
  16. use stats::items::WEAPON_STATS;
  17. const TIER1_SPECIAL: [WeaponSpecial; 8] = [WeaponSpecial::Draw, WeaponSpecial::Heart, WeaponSpecial::Ice, WeaponSpecial::Bind,
  18. WeaponSpecial::Heat, WeaponSpecial::Shock, WeaponSpecial::Dim, WeaponSpecial::Panic];
  19. const TIER2_SPECIAL: [WeaponSpecial; 10] = [WeaponSpecial::Drain, WeaponSpecial::Mind, WeaponSpecial::Masters, WeaponSpecial::Charge, WeaponSpecial::Frost,
  20. WeaponSpecial::Hold, WeaponSpecial::Fire, WeaponSpecial::Thunder, WeaponSpecial::Shadow, WeaponSpecial::Riot];
  21. #[derive(Debug)]
  22. pub struct WeaponShopItem {
  23. weapon: WeaponType,
  24. special: Option<WeaponSpecial>,
  25. grind: usize,
  26. attributes: [Option<WeaponAttribute>; 3],
  27. }
  28. impl PartialEq for WeaponShopItem {
  29. fn eq(&self, other: &Self) -> bool {
  30. self.weapon == other.weapon &&
  31. self.special == other.special &&
  32. self.grind == other.grind &&
  33. self.attributes == other.attributes
  34. }
  35. }
  36. impl Eq for WeaponShopItem {}
  37. impl Ord for WeaponShopItem {
  38. fn cmp(&self, other: &Self) -> Ordering {
  39. self.weapon.value().cmp(&other.weapon.value())
  40. }
  41. }
  42. impl PartialOrd for WeaponShopItem {
  43. fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
  44. Some(self.cmp(other))
  45. }
  46. }
  47. impl From<&Weapon> for WeaponShopItem {
  48. fn from(weapon: &Weapon) -> WeaponShopItem {
  49. WeaponShopItem {
  50. weapon: weapon.weapon,
  51. special: weapon.special,
  52. grind: weapon.grind as usize,
  53. attributes: weapon.attrs,
  54. }
  55. }
  56. }
  57. fn special_stars(special: &WeaponSpecial) -> usize {
  58. match special {
  59. WeaponSpecial::Draw => 1,
  60. WeaponSpecial::Drain => 2,
  61. WeaponSpecial::Fill => 3,
  62. WeaponSpecial::Gush => 4,
  63. WeaponSpecial::Heart => 1,
  64. WeaponSpecial::Mind => 2,
  65. WeaponSpecial::Soul => 3,
  66. WeaponSpecial::Geist => 4,
  67. WeaponSpecial::Masters => 2,
  68. WeaponSpecial::Lords => 3,
  69. WeaponSpecial::Kings => 4,
  70. WeaponSpecial::Charge => 2,
  71. WeaponSpecial::Spirit => 3,
  72. WeaponSpecial::Berserk => 4,
  73. WeaponSpecial::Ice => 1,
  74. WeaponSpecial::Frost => 2,
  75. WeaponSpecial::Freeze => 3,
  76. WeaponSpecial::Blizzard => 4,
  77. WeaponSpecial::Bind => 1,
  78. WeaponSpecial::Hold => 2,
  79. WeaponSpecial::Seize => 3,
  80. WeaponSpecial::Arrest => 4,
  81. WeaponSpecial::Heat => 1,
  82. WeaponSpecial::Fire => 2,
  83. WeaponSpecial::Flame => 3,
  84. WeaponSpecial::Burning => 4,
  85. WeaponSpecial::Shock => 1,
  86. WeaponSpecial::Thunder => 2,
  87. WeaponSpecial::Storm => 3,
  88. WeaponSpecial::Tempest => 4,
  89. WeaponSpecial::Dim => 1,
  90. WeaponSpecial::Shadow => 2,
  91. WeaponSpecial::Dark => 3,
  92. WeaponSpecial::Hell => 4,
  93. WeaponSpecial::Panic => 1,
  94. WeaponSpecial::Riot => 2,
  95. WeaponSpecial::Havoc => 3,
  96. WeaponSpecial::Chaos => 4,
  97. WeaponSpecial::Devils => 3,
  98. WeaponSpecial::Demons => 4,
  99. }
  100. }
  101. impl ShopItem for WeaponShopItem {
  102. fn price(&self) -> usize {
  103. WEAPON_STATS.get(&self.weapon)
  104. .map(|weapon_stat| {
  105. let mut price = weapon_stat.atp_max as f32;
  106. price += self.grind as f32;
  107. price = (price * (price * 3.0)) / weapon_stat.shop_multiplier;
  108. let percent = self.attributes.iter()
  109. .fold(0.0, |acc, attr| {
  110. acc + attr.map(|a| a.value).unwrap_or(0) as f32
  111. });
  112. price = price + ((price / 300.0) * percent);
  113. let special = self.special.map(|special| {
  114. special_stars(&special) as f32
  115. }).unwrap_or(0.0);
  116. price += special * special * 1000.0;
  117. price as usize
  118. })
  119. .unwrap_or(0xFFFF)
  120. }
  121. fn as_bytes(&self) -> [u8; 12] {
  122. self.as_item().as_client_bytes()[0..12].try_into().unwrap()
  123. }
  124. fn as_item(&self) -> ItemDetail {
  125. ItemDetail::Weapon(
  126. Weapon {
  127. weapon: self.weapon,
  128. special: self.special,
  129. grind: self.grind as u8,
  130. attrs: [self.attributes[0], self.attributes[1], None],
  131. tekked: true,
  132. }
  133. )
  134. }
  135. }
  136. impl WeaponShopItem {
  137. pub fn weapon_from_item(w: &Weapon) -> WeaponShopItem {
  138. WeaponShopItem {
  139. weapon: w.weapon,
  140. special: w.special,
  141. grind: w.grind as usize,
  142. attributes: w.attrs,
  143. }
  144. }
  145. }
  146. #[derive(Debug, Deserialize)]
  147. struct WeaponTableTierEntry {
  148. weapon: WeaponType,
  149. probability: usize,
  150. }
  151. #[derive(Debug, Deserialize)]
  152. struct WeaponTableTier {
  153. level: usize,
  154. weapons: Vec<WeaponTableTierEntry>,
  155. }
  156. #[derive(Debug)]
  157. struct WeaponTable(Vec<WeaponTableTier>);
  158. #[derive(Debug, Deserialize)]
  159. struct GrindTier {
  160. level: usize,
  161. min: usize,
  162. max: usize,
  163. }
  164. #[derive(Debug)]
  165. struct GrindTable(Vec<GrindTier>);
  166. #[derive(Debug, Deserialize)]
  167. struct AttributeTier {
  168. level: usize,
  169. percent_min: isize,
  170. percent_max: isize,
  171. none: usize,
  172. native: usize,
  173. abeast: usize,
  174. machine: usize,
  175. dark: usize,
  176. hit: usize,
  177. }
  178. #[derive(Debug)]
  179. struct AttributeTable(Vec<AttributeTier>);
  180. #[derive(Debug, Deserialize)]
  181. struct SpecialTierEntry {
  182. tier: usize,
  183. probability: usize,
  184. }
  185. #[derive(Debug, Deserialize)]
  186. struct SpecialTier {
  187. level: usize,
  188. special: Vec<SpecialTierEntry>,
  189. }
  190. #[derive(Debug)]
  191. struct SpecialTable(Vec<SpecialTier>);
  192. /*
  193. trait WeaponTableLoader {
  194. fn load(difficulty: Difficulty, section_id: SectionID) -> WeaponTable where Self::Sized;
  195. }
  196. struct WeaponTableLoaderImpl;
  197. impl WeaponTableLoader for WeaponTableLoaderImpl {
  198. fn load(difficulty: Difficulty, section_id: SectionID) -> WeaponTable {
  199. let mut path = PathBuf::from("data/shops/");
  200. path.push(difficulty.to_string().to_lowercase());
  201. path.push(section_id.to_string().to_lowercase());
  202. path.push("weapon.toml");
  203. let mut f = File::open(path).unwrap();
  204. let mut s = String::new();
  205. f.read_to_string(&mut s);
  206. let table: Vec<WeaponTableTier> = toml::from_str(s.as_str()).unwrap();
  207. println!("table {:?}", table);
  208. WeaponTable {
  209. }
  210. }
  211. }
  212. */
  213. fn load_weapon_table(difficulty: Difficulty, section_id: SectionID) -> WeaponTable {
  214. let mut path = PathBuf::from("data/shops/");
  215. path.push(difficulty.to_string().to_lowercase());
  216. path.push(section_id.to_string().to_lowercase());
  217. path.push("weapon.toml");
  218. let mut f = File::open(path).unwrap();
  219. let mut s = String::new();
  220. f.read_to_string(&mut s).unwrap();
  221. let mut table: HashMap<String, Vec<WeaponTableTier>> = toml::from_str(s.as_str()).unwrap();
  222. WeaponTable(table.remove("weapon_tier").unwrap())
  223. }
  224. fn load_special_table() -> SpecialTable {
  225. let path = PathBuf::from("data/shops/special.toml");
  226. let mut f = File::open(path).unwrap();
  227. let mut s = String::new();
  228. f.read_to_string(&mut s).unwrap();
  229. let mut table: HashMap<String, Vec<SpecialTier>> = toml::from_str(s.as_str()).unwrap();
  230. SpecialTable(table.remove("specials").unwrap())
  231. }
  232. fn load_grind_table() -> GrindTable {
  233. let path = PathBuf::from("data/shops/grind.toml");
  234. let mut f = File::open(path).unwrap();
  235. let mut s = String::new();
  236. f.read_to_string(&mut s).unwrap();
  237. let mut table: HashMap<String, Vec<GrindTier>> = toml::from_str(s.as_str()).unwrap();
  238. GrindTable(table.remove("grind").unwrap())
  239. }
  240. fn load_alt_grind_table() -> GrindTable {
  241. let path = PathBuf::from("data/shops/alt_grind.toml");
  242. let mut f = File::open(path).unwrap();
  243. let mut s = String::new();
  244. f.read_to_string(&mut s).unwrap();
  245. let mut table: HashMap<String, Vec<GrindTier>> = toml::from_str(s.as_str()).unwrap();
  246. GrindTable(table.remove("grind").unwrap())
  247. }
  248. fn load_attribute1_table() -> AttributeTable {
  249. let path = PathBuf::from("data/shops/attribute1.toml");
  250. let mut f = File::open(path).unwrap();
  251. let mut s = String::new();
  252. f.read_to_string(&mut s).unwrap();
  253. let mut table: HashMap<String, Vec<AttributeTier>> = toml::from_str(s.as_str()).unwrap();
  254. AttributeTable(table.remove("attributes").unwrap())
  255. }
  256. fn load_attribute2_table() -> AttributeTable {
  257. let path = PathBuf::from("data/shops/attribute2.toml");
  258. let mut f = File::open(path).unwrap();
  259. let mut s = String::new();
  260. f.read_to_string(&mut s).unwrap();
  261. let mut table: HashMap<String, Vec<AttributeTier>> = toml::from_str(s.as_str()).unwrap();
  262. AttributeTable(table.remove("attributes").unwrap())
  263. }
  264. fn number_of_weapons_to_generate(character_level: usize) -> usize {
  265. if character_level <= 10 {
  266. 10
  267. }
  268. else if character_level <= 42 {
  269. 12
  270. }
  271. else {
  272. 16
  273. }
  274. }
  275. #[derive(Debug)]
  276. pub struct WeaponShop<R: Rng + SeedableRng> {
  277. _difficulty: Difficulty,
  278. section_id: SectionID,
  279. weapon: WeaponTable,
  280. special: SpecialTable,
  281. grind: GrindTable,
  282. alt_grind: GrindTable,
  283. attr1: AttributeTable,
  284. attr2: AttributeTable,
  285. rng: R,
  286. }
  287. impl<R: Rng + SeedableRng> WeaponShop<R> {
  288. pub fn new(difficulty: Difficulty, section_id: SectionID) -> WeaponShop<R> {
  289. WeaponShop {
  290. _difficulty: difficulty,
  291. section_id,
  292. weapon: load_weapon_table(difficulty, section_id),
  293. special: load_special_table(),
  294. grind: load_grind_table(),
  295. alt_grind: load_alt_grind_table(),
  296. attr1: load_attribute1_table(),
  297. attr2: load_attribute2_table(),
  298. rng: R::from_entropy(),
  299. }
  300. }
  301. fn generate_type(&mut self, level: usize) -> WeaponType {
  302. let tier = self.weapon.0.iter()
  303. .filter(|t| t.level <= level)
  304. .last()
  305. .unwrap();
  306. let weapon_choice = WeightedIndex::new(tier.weapons.iter().map(|t| t.probability)).unwrap();
  307. tier.weapons.get(weapon_choice.sample(&mut self.rng)).unwrap().weapon
  308. }
  309. fn generate_special(&mut self, level: usize) -> Option<WeaponSpecial> {
  310. let tier = self.special.0.iter()
  311. .filter(|t| t.level <= level)
  312. .last()
  313. .unwrap();
  314. let special_tier_choice = WeightedIndex::new(tier.special.iter().map(|t| t.probability)).unwrap();
  315. let special_tier_index = special_tier_choice.sample(&mut self.rng);
  316. let special_tier = tier.special.get(special_tier_index)?;
  317. match special_tier.tier {
  318. 1 => TIER1_SPECIAL.choose(&mut self.rng).cloned(),
  319. 2 => TIER2_SPECIAL.choose(&mut self.rng).cloned(),
  320. _ => None
  321. }
  322. }
  323. fn generate_grind(&mut self, level: usize) -> usize {
  324. let tier = self.grind.0.iter()
  325. .filter(|t| t.level <= level)
  326. .last()
  327. .unwrap();
  328. self.rng.gen_range(tier.min, tier.max+1)
  329. }
  330. fn generate_alt_grind(&mut self, level: usize) -> usize {
  331. let tier = self.alt_grind.0.iter()
  332. .find(|t| t.level <= level)
  333. .unwrap();
  334. self.rng.gen_range(tier.min, tier.max+1)
  335. }
  336. fn generate_attribute1(&mut self, level: usize) -> Option<WeaponAttribute> {
  337. let tier = self.attr1.0.iter()
  338. .filter(|t| t.level <= level)
  339. .last()
  340. .unwrap();
  341. let attr_choice = WeightedIndex::new([tier.none, tier.native, tier.abeast, tier.machine, tier.dark, tier.hit]).unwrap();
  342. let attr = match attr_choice.sample(&mut self.rng) {
  343. 0 => return None,
  344. 1 => Attribute::Native,
  345. 2 => Attribute::ABeast,
  346. 3 => Attribute::Machine,
  347. 4 => Attribute::Dark,
  348. 5 => Attribute::Hit,
  349. _ => panic!()
  350. };
  351. let percent = (tier.percent_min..tier.percent_max+1)
  352. .filter(|p| p % 5 == 0)
  353. .choose(&mut self.rng)?;
  354. Some(WeaponAttribute {
  355. attr,
  356. value: percent as i8,
  357. })
  358. }
  359. fn generate_attribute2(&mut self, level: usize) -> Option<WeaponAttribute> {
  360. let tier = self.attr2.0.iter()
  361. .filter(|t| t.level <= level)
  362. .last()
  363. .unwrap();
  364. let attr_choice = WeightedIndex::new([tier.none, tier.native, tier.abeast, tier.machine, tier.dark, tier.hit]).unwrap();
  365. let attr = match attr_choice.sample(&mut self.rng) {
  366. 0 => return None,
  367. 1 => Attribute::Native,
  368. 2 => Attribute::ABeast,
  369. 3 => Attribute::Machine,
  370. 4 => Attribute::Dark,
  371. 5 => Attribute::Hit,
  372. _ => panic!()
  373. };
  374. let percent = (tier.percent_min..tier.percent_max+1)
  375. .filter(|p| p % 5 == 0)
  376. .choose(&mut self.rng)?;
  377. Some(WeaponAttribute {
  378. attr,
  379. value: percent as i8,
  380. })
  381. }
  382. fn is_alt_grind(&self, weapon: &WeaponType) -> bool {
  383. matches!((self.section_id, weapon),
  384. (SectionID::Viridia, WeaponType::Shot) |
  385. (SectionID::Viridia, WeaponType::Spread) |
  386. (SectionID::Viridia, WeaponType::Cannon) |
  387. (SectionID::Viridia, WeaponType::Launcher) |
  388. (SectionID::Viridia, WeaponType::Arms) |
  389. (SectionID::Greenill, WeaponType::Rifle) |
  390. (SectionID::Greenill, WeaponType::Sniper) |
  391. (SectionID::Greenill, WeaponType::Blaster) |
  392. (SectionID::Greenill, WeaponType::Beam) |
  393. (SectionID::Greenill, WeaponType::Laser) |
  394. (SectionID::Skyly, WeaponType::Sword) |
  395. (SectionID::Skyly, WeaponType::Gigush) |
  396. (SectionID::Skyly, WeaponType::Breaker) |
  397. (SectionID::Skyly, WeaponType::Claymore) |
  398. (SectionID::Skyly, WeaponType::Calibur) |
  399. (SectionID::Bluefull, WeaponType::Partisan) |
  400. (SectionID::Bluefull, WeaponType::Halbert) |
  401. (SectionID::Bluefull, WeaponType::Glaive) |
  402. (SectionID::Bluefull, WeaponType::Berdys) |
  403. (SectionID::Bluefull, WeaponType::Gungnir) |
  404. (SectionID::Purplenum, WeaponType::Mechgun) |
  405. (SectionID::Purplenum, WeaponType::Assault) |
  406. (SectionID::Purplenum, WeaponType::Repeater) |
  407. (SectionID::Purplenum, WeaponType::Gatling) |
  408. (SectionID::Purplenum, WeaponType::Vulcan) |
  409. (SectionID::Pinkal, WeaponType::Cane) |
  410. (SectionID::Pinkal, WeaponType::Stick) |
  411. (SectionID::Pinkal, WeaponType::Mace) |
  412. (SectionID::Pinkal, WeaponType::Club) |
  413. (SectionID::Oran, WeaponType::Dagger) |
  414. (SectionID::Oran, WeaponType::Knife) |
  415. (SectionID::Oran, WeaponType::Blade) |
  416. (SectionID::Oran, WeaponType::Edge) |
  417. (SectionID::Oran, WeaponType::Ripper) |
  418. (SectionID::Whitill, WeaponType::Slicer) |
  419. (SectionID::Whitill, WeaponType::Spinner) |
  420. (SectionID::Whitill, WeaponType::Cutter) |
  421. (SectionID::Whitill, WeaponType::Sawcer) |
  422. (SectionID::Whitill, WeaponType::Diska))
  423. }
  424. fn generate_weapon(&mut self, level: usize) -> WeaponShopItem {
  425. let weapon = self.generate_type(level);
  426. let grind = if self.is_alt_grind(&weapon) {
  427. self.generate_alt_grind(level)
  428. } else {
  429. self.generate_grind(level)
  430. };
  431. let special = self.generate_special(level);
  432. let (attr1, attr2) = {
  433. match self.generate_attribute1(level) {
  434. Some(a1) => {
  435. let a2 = loop {
  436. let attr = self.generate_attribute2(level);
  437. match attr {
  438. Some(a2) => {
  439. if a2.attr != a1.attr {
  440. break Some(a2);
  441. }
  442. },
  443. None => break None,
  444. }
  445. };
  446. (Some(a1), a2)
  447. },
  448. None => {
  449. let a2 = self.generate_attribute2(level);
  450. (a2, None)
  451. }
  452. }
  453. };
  454. WeaponShopItem {
  455. weapon,
  456. grind,
  457. special,
  458. attributes: [attr1, attr2, None],
  459. }
  460. }
  461. pub fn generate_weapon_list(&mut self, level: usize) -> Vec<WeaponShopItem> {
  462. let mut x = (0..number_of_weapons_to_generate(level))
  463. .map(|_| {
  464. self.generate_weapon(level)
  465. })
  466. .collect::<Vec<WeaponShopItem>>();
  467. x.sort();
  468. x
  469. }
  470. }
  471. #[cfg(test)]
  472. mod test {
  473. use super::*;
  474. #[test]
  475. fn test_loading_weapon_shop() {
  476. WeaponShop::<rand_chacha::ChaCha20Rng>::new(Difficulty::Ultimate, SectionID::Pinkal);
  477. }
  478. #[test]
  479. fn test_generating_some_weapons() {
  480. let mut ws = WeaponShop::<rand_chacha::ChaCha20Rng>::new(Difficulty::Ultimate, SectionID::Pinkal);
  481. for i in 0..200 {
  482. ws.generate_weapon_list(i);
  483. }
  484. }
  485. }