drop table initial code + weapon drops
This commit is contained in:
parent
c0479f5476
commit
513d46c213
@ -21,7 +21,8 @@ path = "src/main.rs"
|
||||
libpso = { git = "http://git.sharnoth.com/jake/libpso" }
|
||||
async-std = { version = "1.4.0", features = ["unstable"] }
|
||||
futures = "0.3.1"
|
||||
rand = "0.6.5"
|
||||
rand = "0.7.3"
|
||||
rand_chacha = "0.2.2"
|
||||
mio = "0.6"
|
||||
mio-extras = "2.0.5"
|
||||
crc = "^1.0.0"
|
||||
@ -31,7 +32,10 @@ chrono = "*"
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
ron = "*"
|
||||
toml = "*"
|
||||
log = "*"
|
||||
fern = { version = "0.5", features = ["colored"] }
|
||||
byteorder = "1"
|
||||
enum-utils = "0.1.2"
|
||||
derive_more = { version = "0.99.3", features = ["display"]}
|
||||
|
||||
|
@ -62,7 +62,7 @@ impl Into<u8> for CharacterClass {
|
||||
|
||||
|
||||
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
||||
pub enum SectionID {
|
||||
Viridia,
|
||||
Greenill,
|
||||
|
@ -1,10 +1,11 @@
|
||||
use std::convert::{TryFrom, Into};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
pub enum ArmorTypeError {
|
||||
UnknownArmor(String)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ArmorType {
|
||||
Frame,
|
||||
Armor,
|
||||
|
@ -43,6 +43,7 @@ pub enum ItemLocation {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Weapon {
|
||||
pub equipped: bool,
|
||||
pub tekked: bool,
|
||||
pub weapon: weapon::Weapon,
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use std::convert::{TryFrom, Into};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub enum Attribute {
|
||||
Native,
|
||||
ABeast,
|
||||
@ -12,8 +13,8 @@ pub enum Attribute {
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct WeaponAttribute {
|
||||
attr: Attribute,
|
||||
value: u8,
|
||||
pub attr: Attribute,
|
||||
pub value: i8,
|
||||
}
|
||||
|
||||
|
||||
@ -65,7 +66,7 @@ pub enum WeaponTypeError {
|
||||
UnknownWeapon(String)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum WeaponType {
|
||||
Saber,
|
||||
Brand,
|
||||
|
@ -204,6 +204,7 @@ fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAccount,
|
||||
ItemDetail::Weapon(
|
||||
Weapon {
|
||||
equipped: true,
|
||||
tekked: true,
|
||||
weapon: item::weapon::Weapon {
|
||||
weapon: new_weapon,
|
||||
grind: 0,
|
||||
|
@ -86,6 +86,7 @@ fn main() {
|
||||
item::ItemDetail::Weapon(
|
||||
item::Weapon {
|
||||
equipped: true,
|
||||
tekked: true,
|
||||
weapon: item::weapon::Weapon {
|
||||
weapon: item::weapon::WeaponType::Handgun,
|
||||
grind: 5,
|
||||
|
0
src/ship/drops/drop_table.rs
Normal file
0
src/ship/drops/drop_table.rs
Normal file
0
src/ship/drops/generic_armor.rs
Normal file
0
src/ship/drops/generic_armor.rs
Normal file
0
src/ship/drops/generic_shield.rs
Normal file
0
src/ship/drops/generic_shield.rs
Normal file
0
src/ship/drops/generic_unit.rs
Normal file
0
src/ship/drops/generic_unit.rs
Normal file
579
src/ship/drops/generic_weapon.rs
Normal file
579
src/ship/drops/generic_weapon.rs
Normal file
@ -0,0 +1,579 @@
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
use crate::entity::item::{ItemDetail, Weapon as WeaponDetail};
|
||||
use crate::entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
|
||||
use crate::ship::monster::MonsterType;
|
||||
use crate::ship::room::{Difficulty, Episode};
|
||||
use crate::ship::map::MapVariantType;
|
||||
use crate::entity::character::SectionID;
|
||||
|
||||
|
||||
fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
|
||||
let mut path = PathBuf::from("data/drops/");
|
||||
path.push(episode.to_string());
|
||||
path.push(difficulty.to_string().to_lowercase());
|
||||
path.push(section_id.to_string().to_lowercase());
|
||||
path.push(filename);
|
||||
path
|
||||
}
|
||||
|
||||
fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T {
|
||||
let path = data_file_path(episode, difficulty, section_id, filename);
|
||||
let mut f = File::open(path).unwrap();
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s);
|
||||
|
||||
toml::from_str::<T>(s.as_str()).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Copy, Clone, Ord, PartialOrd)]
|
||||
pub enum WeaponDropType {
|
||||
Saber,
|
||||
Sword,
|
||||
Dagger,
|
||||
Partisan,
|
||||
Slicer,
|
||||
Handgun,
|
||||
Rifle,
|
||||
Mechgun,
|
||||
Shot,
|
||||
Cane,
|
||||
Rod,
|
||||
Wand
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Copy, Clone)]
|
||||
pub struct WeaponRatio {
|
||||
rate: u32,
|
||||
rank: i32,
|
||||
inc: u32,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct WeaponRatios {
|
||||
saber: WeaponRatio,
|
||||
sword: WeaponRatio,
|
||||
dagger: WeaponRatio,
|
||||
partisan: WeaponRatio,
|
||||
slicer: WeaponRatio,
|
||||
handgun: WeaponRatio,
|
||||
rifle: WeaponRatio,
|
||||
mechgun: WeaponRatio,
|
||||
shot: WeaponRatio,
|
||||
cane: WeaponRatio,
|
||||
rod: WeaponRatio,
|
||||
wand: WeaponRatio,
|
||||
}
|
||||
|
||||
impl WeaponRatios {
|
||||
fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> WeaponRatios {
|
||||
load_data_file(episode, difficulty, section_id, "weapon_rate.toml")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct GrindRates {
|
||||
grind_rate: [[u32; 9]; 4]
|
||||
}
|
||||
|
||||
impl GrindRates {
|
||||
fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GrindRates {
|
||||
load_data_file(episode, difficulty, section_id, "grind_rate.toml")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
struct AttributeRate {
|
||||
none: u32,
|
||||
native: u32,
|
||||
abeast: u32,
|
||||
machine: u32,
|
||||
dark: u32,
|
||||
hit: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct AttributeRates {
|
||||
area1: AttributeRate,
|
||||
area2: AttributeRate,
|
||||
area3: AttributeRate,
|
||||
area4: AttributeRate,
|
||||
area5: AttributeRate,
|
||||
area6: AttributeRate,
|
||||
area7: AttributeRate,
|
||||
area8: AttributeRate,
|
||||
area9: AttributeRate,
|
||||
area10: AttributeRate,
|
||||
}
|
||||
|
||||
impl AttributeRates {
|
||||
fn get_by_area(&self, map_area: &MapVariantType) -> AttributeRate {
|
||||
match map_area.area_value().unwrap() {
|
||||
0 => self.area1,
|
||||
1 => self.area2,
|
||||
2 => self.area3,
|
||||
3 => self.area4,
|
||||
4 => self.area5,
|
||||
5 => self.area6,
|
||||
6 => self.area7,
|
||||
7 => self.area8,
|
||||
8 => self.area9,
|
||||
_ => self.area10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
struct PercentRate {
|
||||
p5: u32,
|
||||
p10: u32,
|
||||
p15: u32,
|
||||
p20: u32,
|
||||
p25: u32,
|
||||
p30: u32,
|
||||
p35: u32,
|
||||
p40: u32,
|
||||
p45: u32,
|
||||
p50: u32,
|
||||
p55: u32,
|
||||
p60: u32,
|
||||
p65: u32,
|
||||
p70: u32,
|
||||
p75: u32,
|
||||
p80: u32,
|
||||
p85: u32,
|
||||
p90: u32,
|
||||
}
|
||||
|
||||
impl PercentRate {
|
||||
fn as_array(&self) -> [u32; 18] {
|
||||
[self.p5, self.p10, self.p15, self.p20, self.p25, self.p30, self.p35, self.p40, self.p45,
|
||||
self.p50, self.p55, self.p60, self.p65, self.p70, self.p75, self.p80, self.p85, self.p90]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
enum PercentPatternType {
|
||||
#[serde(rename = "pattern1")]
|
||||
Pattern1,
|
||||
#[serde(rename = "pattern2")]
|
||||
Pattern2,
|
||||
#[serde(rename = "pattern3")]
|
||||
Pattern3,
|
||||
#[serde(rename = "pattern4")]
|
||||
Pattern4,
|
||||
#[serde(rename = "pattern5")]
|
||||
Pattern5,
|
||||
#[serde(rename = "pattern6")]
|
||||
Pattern6,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
struct AttributePercentPattern {
|
||||
attribute1: Option<PercentPatternType>,
|
||||
attribute2: Option<PercentPatternType>,
|
||||
attribute3: Option<PercentPatternType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
struct AreaPercentPatterns {
|
||||
area1: AttributePercentPattern,
|
||||
area2: AttributePercentPattern,
|
||||
area3: AttributePercentPattern,
|
||||
area4: AttributePercentPattern,
|
||||
area5: AttributePercentPattern,
|
||||
area6: AttributePercentPattern,
|
||||
area7: AttributePercentPattern,
|
||||
area8: AttributePercentPattern,
|
||||
area9: AttributePercentPattern,
|
||||
area10: AttributePercentPattern,
|
||||
}
|
||||
|
||||
impl AreaPercentPatterns {
|
||||
fn get_by_area(&self, map_area: &MapVariantType) -> AttributePercentPattern {
|
||||
match map_area.area_value().unwrap() {
|
||||
0 => self.area1,
|
||||
1 => self.area2,
|
||||
2 => self.area3,
|
||||
3 => self.area4,
|
||||
4 => self.area5,
|
||||
5 => self.area6,
|
||||
6 => self.area7,
|
||||
7 => self.area8,
|
||||
8 => self.area9,
|
||||
_ => self.area10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
struct PercentRatePatterns {
|
||||
pattern1: PercentRate,
|
||||
pattern2: PercentRate,
|
||||
pattern3: PercentRate,
|
||||
pattern4: PercentRate,
|
||||
pattern5: PercentRate,
|
||||
pattern6: PercentRate,
|
||||
}
|
||||
|
||||
impl PercentRatePatterns {
|
||||
fn get_by_pattern(&self, pattern: &PercentPatternType) -> PercentRate {
|
||||
match pattern {
|
||||
PercentPatternType::Pattern1 => self.pattern1,
|
||||
PercentPatternType::Pattern2 => self.pattern2,
|
||||
PercentPatternType::Pattern3 => self.pattern3,
|
||||
PercentPatternType::Pattern4 => self.pattern4,
|
||||
PercentPatternType::Pattern5 => self.pattern5,
|
||||
PercentPatternType::Pattern6 => self.pattern6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AttributeTable {
|
||||
attribute_rates: AttributeRates,
|
||||
percent_rates: PercentRatePatterns,
|
||||
area_percent_patterns: AreaPercentPatterns,
|
||||
}
|
||||
|
||||
|
||||
impl AttributeTable {
|
||||
fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> AttributeTable {
|
||||
// TODO: new these
|
||||
let attribute_rates: AttributeRates = load_data_file(episode, difficulty, section_id, "attribute_rate.toml");
|
||||
let percent_rates: PercentRatePatterns = load_data_file(episode, difficulty, section_id, "percent_rate.toml");
|
||||
let area_percent_patterns: AreaPercentPatterns = load_data_file(episode, difficulty, section_id, "area_percent_pattern.toml");
|
||||
|
||||
AttributeTable {
|
||||
attribute_rates: attribute_rates,
|
||||
percent_rates: percent_rates,
|
||||
area_percent_patterns: area_percent_patterns,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn generate_attribute<R: Rng>(&self, pattern: &PercentPatternType, rates: &AttributeRate, rng: &mut R) -> Option<WeaponAttribute> {
|
||||
let attribute_weights = WeightedIndex::new(&[rates.none, rates.native, rates.abeast, rates.machine, rates.dark, rates.hit]).unwrap();
|
||||
let attr = match attribute_weights.sample(rng) {
|
||||
0 => return None,
|
||||
1 => Attribute::Native,
|
||||
2 => Attribute::ABeast,
|
||||
3 => Attribute::Machine,
|
||||
4 => Attribute::Dark,
|
||||
5 => Attribute::Hit,
|
||||
_ => panic!()
|
||||
};
|
||||
|
||||
let percents = self.percent_rates.get_by_pattern(pattern);
|
||||
|
||||
let value_weights = WeightedIndex::new(&percents.as_array()).unwrap();
|
||||
let value = value_weights.sample(rng);
|
||||
let percent = ((value + 1) * 5) as i8;
|
||||
|
||||
Some(WeaponAttribute {
|
||||
attr: attr,
|
||||
value: percent
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_attributes<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> [Option<WeaponAttribute>; 3] {
|
||||
let percent_pattern = self.area_percent_patterns.get_by_area(map_area);
|
||||
let attribute_rate = self.attribute_rates.get_by_area(map_area);
|
||||
|
||||
let mut percents = vec![
|
||||
percent_pattern.attribute1.and_then(|pattern_type| {
|
||||
self.generate_attribute(&pattern_type, &attribute_rate, rng)
|
||||
}),
|
||||
percent_pattern.attribute2.and_then(|pattern_type| {
|
||||
self.generate_attribute(&pattern_type, &attribute_rate, rng)
|
||||
}),
|
||||
percent_pattern.attribute3.and_then(|pattern_type| {
|
||||
self.generate_attribute(&pattern_type, &attribute_rate, rng)
|
||||
}),
|
||||
];
|
||||
percents.sort_by_key(|p| {
|
||||
match p {
|
||||
Some(a) => Some(a.attr),
|
||||
None => None,
|
||||
}
|
||||
});
|
||||
percents.dedup_by_key(|p| {
|
||||
match p {
|
||||
Some(a) => Some(a.attr),
|
||||
None => None,
|
||||
}
|
||||
});
|
||||
percents.iter()
|
||||
.fold(([None; 3], 0), |(mut acc, index), p| { // one day I'll be able to collece into an array
|
||||
if let Some(_) = p {
|
||||
acc[index] = *p;
|
||||
(acc, index + 1)
|
||||
}
|
||||
else {
|
||||
(acc, index)
|
||||
}
|
||||
}).0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
struct SpecialRate {
|
||||
rank: u32,
|
||||
rate: u32,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
struct SpecialRates {
|
||||
area1: SpecialRate,
|
||||
area2: SpecialRate,
|
||||
area3: SpecialRate,
|
||||
area4: SpecialRate,
|
||||
area5: SpecialRate,
|
||||
area6: SpecialRate,
|
||||
area7: SpecialRate,
|
||||
area8: SpecialRate,
|
||||
area9: SpecialRate,
|
||||
area10: SpecialRate,
|
||||
}
|
||||
|
||||
impl SpecialRates {
|
||||
fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> SpecialRates {
|
||||
load_data_file(episode, difficulty, section_id, "weapon_special_rate.toml")
|
||||
}
|
||||
|
||||
fn rate_by_area(&self, map_area: &MapVariantType) -> SpecialRate {
|
||||
match map_area.area_value().unwrap() {
|
||||
0 => self.area1,
|
||||
1 => self.area2,
|
||||
2 => self.area3,
|
||||
3 => self.area4,
|
||||
4 => self.area5,
|
||||
5 => self.area6,
|
||||
6 => self.area7,
|
||||
7=> self.area8,
|
||||
8 => self.area9,
|
||||
_ => self.area10,
|
||||
}
|
||||
}
|
||||
|
||||
fn random_special_by_rank<R: Rng>(&self, rank: u32, rng: &mut R) -> WeaponSpecial {
|
||||
let specials = match rank {
|
||||
1 => vec![WeaponSpecial::Draw, WeaponSpecial::Heart, WeaponSpecial::Ice, WeaponSpecial::Bind, WeaponSpecial::Heat, WeaponSpecial::Shock,
|
||||
WeaponSpecial::Dim, WeaponSpecial::Panic],
|
||||
2 => vec![WeaponSpecial::Drain, WeaponSpecial::Mind, WeaponSpecial::Frost, WeaponSpecial::Hold, WeaponSpecial::Fire, WeaponSpecial::Thunder,
|
||||
WeaponSpecial::Shadow, WeaponSpecial::Riot, WeaponSpecial::Masters, WeaponSpecial::Charge],
|
||||
3 => vec![WeaponSpecial::Fill, WeaponSpecial::Soul, WeaponSpecial::Freeze, WeaponSpecial::Seize, WeaponSpecial::Flame, WeaponSpecial::Storm,
|
||||
WeaponSpecial::Dark, WeaponSpecial::Havoc, WeaponSpecial::Lords, WeaponSpecial::Charge, WeaponSpecial::Spirit, WeaponSpecial::Devils],
|
||||
4 => vec![WeaponSpecial::Gush, WeaponSpecial::Geist, WeaponSpecial::Blizzard, WeaponSpecial::Arrest, WeaponSpecial::Burning, WeaponSpecial::Tempest,
|
||||
WeaponSpecial::Hell, WeaponSpecial::Chaos, WeaponSpecial::Kings, WeaponSpecial::Charge, WeaponSpecial::Berserk, WeaponSpecial::Demons],
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
*specials.choose(rng).unwrap()
|
||||
}
|
||||
|
||||
fn get_special<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> Option<WeaponSpecial> {
|
||||
let rate = self.rate_by_area(map_area);
|
||||
if rng.gen_range(0, 100) < rate.rate {
|
||||
Some(self.random_special_by_rank(rate.rank, rng))
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct GenericWeaponTable {
|
||||
rank_table: HashMap<WeaponDropType, Vec<WeaponType>>,
|
||||
weapon_ratio: WeaponRatios,
|
||||
grind_rates: GrindRates,
|
||||
attribute_table: AttributeTable,
|
||||
special_table: SpecialRates,
|
||||
}
|
||||
|
||||
|
||||
impl GenericWeaponTable {
|
||||
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GenericWeaponTable {
|
||||
let mut rank_table = HashMap::new();
|
||||
rank_table.insert(WeaponDropType::Saber, vec![WeaponType::Saber, WeaponType::Brand, WeaponType::Buster, WeaponType::Pallasch, WeaponType::Gladius]);
|
||||
rank_table.insert(WeaponDropType::Sword, vec![WeaponType::Sword, WeaponType::Gigush, WeaponType::Breaker, WeaponType::Claymore, WeaponType::Calibur]);
|
||||
rank_table.insert(WeaponDropType::Dagger, vec![WeaponType::Dagger, WeaponType::Knife, WeaponType::Blade, WeaponType::Edge, WeaponType::Ripper]);
|
||||
rank_table.insert(WeaponDropType::Partisan, vec![WeaponType::Partisan, WeaponType::Halbert, WeaponType::Glaive, WeaponType::Berdys, WeaponType::Gungnir]);
|
||||
rank_table.insert(WeaponDropType::Slicer, vec![WeaponType::Slicer, WeaponType::Spinner, WeaponType::Cutter, WeaponType::Sawcer, WeaponType::Diska]);
|
||||
rank_table.insert(WeaponDropType::Handgun, vec![WeaponType::Handgun, WeaponType::Autogun, WeaponType::Lockgun, WeaponType::Railgun, WeaponType::Raygun]);
|
||||
rank_table.insert(WeaponDropType::Rifle, vec![WeaponType::Rifle, WeaponType::Sniper, WeaponType::Blaster, WeaponType::Beam, WeaponType::Laser]);
|
||||
rank_table.insert(WeaponDropType::Mechgun, vec![WeaponType::Mechgun, WeaponType::Assault, WeaponType::Repeater, WeaponType::Gatling, WeaponType::Vulcan]);
|
||||
rank_table.insert(WeaponDropType::Shot, vec![WeaponType::Shot, WeaponType::Spread, WeaponType::Cannon, WeaponType::Launcher, WeaponType::Arms]);
|
||||
rank_table.insert(WeaponDropType::Cane, vec![WeaponType::Cane, WeaponType::Stick, WeaponType::Mace, WeaponType::Club]);
|
||||
rank_table.insert(WeaponDropType::Rod, vec![WeaponType::Rod, WeaponType::Pole, WeaponType::Pillar, WeaponType::Striker]);
|
||||
rank_table.insert(WeaponDropType::Wand, vec![WeaponType::Wand, WeaponType::Staff, WeaponType::Baton, WeaponType::Scepter]);
|
||||
|
||||
GenericWeaponTable {
|
||||
rank_table: rank_table,
|
||||
weapon_ratio: WeaponRatios::new(episode, difficulty, section_id),
|
||||
grind_rates: GrindRates::new(episode, difficulty, section_id),
|
||||
attribute_table: AttributeTable::new(episode, difficulty, section_id),
|
||||
special_table: SpecialRates::new(episode, difficulty, section_id),
|
||||
}
|
||||
}
|
||||
|
||||
fn area_rank(&self, ratio: &WeaponRatio, map_area: &MapVariantType) -> (u32, u32) {
|
||||
if ratio.rank >= 0 {
|
||||
(map_area.area_value().unwrap_or(0), ratio.rank as u32)
|
||||
}
|
||||
else {
|
||||
((map_area.area_value().unwrap_or(0) as i32 + ratio.rank) as u32, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_possible_weapon_types(&self, map_area: &MapVariantType) -> BTreeMap<WeaponDropType, WeaponRatio> {
|
||||
let mut valid_weapons = BTreeMap::new();
|
||||
|
||||
let item_rate_pairs = vec![
|
||||
(WeaponDropType::Saber, self.weapon_ratio.saber),
|
||||
(WeaponDropType::Sword, self.weapon_ratio.sword),
|
||||
(WeaponDropType::Dagger, self.weapon_ratio.dagger),
|
||||
(WeaponDropType::Partisan, self.weapon_ratio.partisan),
|
||||
(WeaponDropType::Slicer, self.weapon_ratio.slicer),
|
||||
(WeaponDropType::Handgun, self.weapon_ratio.handgun),
|
||||
(WeaponDropType::Rifle, self.weapon_ratio.rifle),
|
||||
(WeaponDropType::Mechgun, self.weapon_ratio.mechgun),
|
||||
(WeaponDropType::Shot, self.weapon_ratio.shot),
|
||||
(WeaponDropType::Cane, self.weapon_ratio.cane),
|
||||
(WeaponDropType::Rod, self.weapon_ratio.rod),
|
||||
(WeaponDropType::Wand, self.weapon_ratio.wand),
|
||||
];
|
||||
|
||||
for (item_type, ratio) in item_rate_pairs.into_iter() {
|
||||
if ratio.rate > 0 && map_area.area_value().map(|k| k as i32 + ratio.rank).unwrap_or(-1) >= 0 {
|
||||
valid_weapons.insert(item_type, ratio);
|
||||
}
|
||||
}
|
||||
|
||||
valid_weapons
|
||||
}
|
||||
|
||||
fn weapon_type<R: Rng>(&self, possible_weapon_types: &BTreeMap<WeaponDropType, WeaponRatio>, map_area: &MapVariantType, rng: &mut R) -> WeaponDropType {
|
||||
let mut weapon_rates = possible_weapon_types.iter()
|
||||
.map(|(weapon, stat)| {
|
||||
(weapon, stat.rate)
|
||||
});
|
||||
let weapon_choice = WeightedIndex::new(weapon_rates.clone().map(|wr| wr.1)).unwrap();
|
||||
*weapon_rates.nth(weapon_choice.sample(rng)).unwrap().0
|
||||
}
|
||||
|
||||
|
||||
fn weapon_rank(&self, ratio: &WeaponRatio, map_area: &MapVariantType) -> u32 {
|
||||
let (area, rank) = self.area_rank(ratio, map_area);
|
||||
let weapon_rank = (rank + area / ratio.inc);
|
||||
|
||||
std::cmp::max(weapon_rank, 0)
|
||||
}
|
||||
|
||||
fn actual_weapon(&self, weapon_type: &WeaponDropType, weapon_rank: u32) -> WeaponType {
|
||||
let weapons = self.rank_table.get(weapon_type).unwrap();
|
||||
weapons[std::cmp::min(weapons.len() - 1, weapon_rank as usize)]
|
||||
}
|
||||
|
||||
fn get_grind<R: Rng>(&self, ratio: &WeaponRatio, map_area: &MapVariantType, rng: &mut R) -> usize {
|
||||
let (area, _) = self.area_rank(ratio, map_area);
|
||||
let pattern = std::cmp::min(area % ratio.inc, 3);
|
||||
|
||||
let weights = self.grind_rates.grind_rate[pattern as usize];
|
||||
let grind_choice = WeightedIndex::new(&weights).unwrap();
|
||||
grind_choice.sample(rng)
|
||||
}
|
||||
|
||||
pub fn get_drop<R: Rng>(&self, map_area: &MapVariantType, rng: &mut R) -> Option<ItemDetail> {
|
||||
let possible_weapon_types = self.get_possible_weapon_types(map_area);
|
||||
let weapon_type = self.weapon_type(&possible_weapon_types, map_area, rng);
|
||||
let ratio = possible_weapon_types.get(&weapon_type).unwrap();
|
||||
let weapon_rank = self.weapon_rank(ratio, map_area);
|
||||
let weapon_grind = self.get_grind(ratio, map_area, rng);
|
||||
let weapon_attributes = self.attribute_table.generate_attributes(map_area, rng);
|
||||
let weapon_special = self.special_table.get_special(map_area, rng);
|
||||
let actual_weapon = self.actual_weapon(&weapon_type, weapon_rank);
|
||||
|
||||
Some(ItemDetail::Weapon(WeaponDetail {
|
||||
equipped: false,
|
||||
tekked: weapon_special.is_none(),
|
||||
weapon: Weapon {
|
||||
weapon: actual_weapon,
|
||||
special: weapon_special,
|
||||
grind: weapon_grind as u8,
|
||||
attrs: weapon_attributes,
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_weapon_generation() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]);
|
||||
|
||||
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Normal, SectionID::Skyly);
|
||||
assert!(gwt.get_drop(&MapVariantType::Forest1, &mut rng) == Some(ItemDetail::Weapon(WeaponDetail {
|
||||
equipped: false,
|
||||
tekked: true,
|
||||
weapon: Weapon {
|
||||
weapon: WeaponType::Cane,
|
||||
special: None,
|
||||
grind: 0,
|
||||
attrs: [None, None, None]
|
||||
}
|
||||
})));
|
||||
|
||||
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly);
|
||||
assert!(gwt.get_drop(&MapVariantType::Caves2, &mut rng) == Some(ItemDetail::Weapon(WeaponDetail {
|
||||
equipped: false,
|
||||
tekked: true,
|
||||
weapon: Weapon {
|
||||
weapon: WeaponType::Sniper,
|
||||
special: None,
|
||||
grind: 2,
|
||||
attrs: [None, None, None]
|
||||
}
|
||||
})));
|
||||
|
||||
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::VeryHard, SectionID::Skyly);
|
||||
assert!(gwt.get_drop(&MapVariantType::Mines1, &mut rng) == Some(ItemDetail::Weapon(WeaponDetail {
|
||||
equipped: false,
|
||||
tekked: false,
|
||||
weapon: Weapon {
|
||||
weapon: WeaponType::Club,
|
||||
special: Some(WeaponSpecial::Berserk),
|
||||
grind: 0,
|
||||
attrs: [None, None, None]
|
||||
}
|
||||
})));
|
||||
|
||||
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly);
|
||||
assert!(gwt.get_drop(&MapVariantType::DarkFalz, &mut rng) == Some(ItemDetail::Weapon(WeaponDetail {
|
||||
equipped: false,
|
||||
tekked: true,
|
||||
weapon: Weapon {
|
||||
weapon: WeaponType::Vulcan,
|
||||
special: None,
|
||||
grind: 0,
|
||||
attrs: [Some(WeaponAttribute {attr: Attribute::ABeast, value: 30}), Some(WeaponAttribute {attr: Attribute::Dark, value: 30}), None]
|
||||
}
|
||||
})));
|
||||
}
|
||||
}
|
190
src/ship/drops/mod.rs
Normal file
190
src/ship/drops/mod.rs
Normal file
@ -0,0 +1,190 @@
|
||||
mod drop_table;
|
||||
mod rare_drop_table;
|
||||
mod generic_weapon;
|
||||
mod generic_armor;
|
||||
mod generic_shield;
|
||||
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::io::Read;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
|
||||
use crate::ship::monster::MonsterType;
|
||||
use crate::ship::room::{Difficulty, Episode};
|
||||
use crate::entity::item::ItemDetail;
|
||||
use crate::entity::item::weapon::{WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
|
||||
use crate::entity::item::armor::ArmorType;
|
||||
use crate::entity::item::shield::ShieldType;
|
||||
use crate::entity::item::unit::UnitType;
|
||||
use crate::entity::item::tool::ToolType;
|
||||
use crate::ship::map::MapVariantType;
|
||||
use crate::entity::character::SectionID;
|
||||
pub use crate::ship::drops::generic_weapon::*;
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum MonsterDropType {
|
||||
#[serde(rename = "weapon")]
|
||||
Weapon,
|
||||
#[serde(rename = "armor")]
|
||||
Armor,
|
||||
#[serde(rename = "shield")]
|
||||
Shield,
|
||||
#[serde(rename = "unit")]
|
||||
Unit,
|
||||
#[serde(rename = "none")]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct MonsterDropStats {
|
||||
pub dar: u32,
|
||||
pub drop_type: MonsterDropType,
|
||||
pub min_meseta: u32,
|
||||
pub max_meseta: u32,
|
||||
}
|
||||
|
||||
enum RareDropItem {
|
||||
Weapon(WeaponType),
|
||||
Armor(ArmorType),
|
||||
Shield(ShieldType),
|
||||
Unit(UnitType),
|
||||
Tool(ToolType),
|
||||
}
|
||||
|
||||
|
||||
struct RareDrop {
|
||||
rate: f32,
|
||||
item: RareDropItem
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RareDropConfigEntity {
|
||||
pub rate: f32,
|
||||
pub item: String,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*#[derive(Serialize, Deserialize)]
|
||||
pub struct MonsterDar(pub HashMap<MonsterType, MonsterDropStats>);
|
||||
|
||||
/*impl MonsterDar {
|
||||
fn from_f
|
||||
|
||||
|
||||
}*/*/
|
||||
|
||||
|
||||
struct RareDropTable {
|
||||
|
||||
}
|
||||
|
||||
impl RareDropTable {
|
||||
fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
|
||||
RareDropTable {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn get_drop(&self, monster: &MonsterType) -> Option<ItemDetail> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct DropTable<R: Rng + SeedableRng> {
|
||||
rare_table: RareDropTable,
|
||||
monster_stats: HashMap<MonsterType, MonsterDropStats>,
|
||||
weapon_table: GenericWeaponTable,
|
||||
rng: R,
|
||||
}
|
||||
|
||||
|
||||
impl<R: Rng + SeedableRng> DropTable<R> {
|
||||
fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> {
|
||||
let mut path = PathBuf::from("data/drops/");
|
||||
path.push(episode.to_string());
|
||||
path.push(difficulty.to_string().to_lowercase());
|
||||
path.push(section_id.to_string().to_lowercase());
|
||||
path.push("grind_rate.toml");
|
||||
let mut f = File::open(path).unwrap();
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s);
|
||||
let monster_stats = toml::from_str(&s).unwrap();
|
||||
|
||||
DropTable {
|
||||
rare_table: RareDropTable::new(episode, difficulty, section_id),
|
||||
monster_stats: monster_stats,
|
||||
weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
|
||||
rng: R::from_entropy(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_meseta(&self, monster: &MonsterDropStats) -> Option<ItemDetail> {
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_tool(&self, map_area: &MapVariantType, monster: &MonsterDropStats) -> Option<ItemDetail> {
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_armor(&self) -> Option<ItemDetail> {
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_shield(&self) -> Option<ItemDetail> {
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_unit(&self) -> Option<ItemDetail> {
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_typed_drop(&mut self, map_area: &MapVariantType, monster: &MonsterDropStats) -> Option<ItemDetail> {
|
||||
match monster.drop_type {
|
||||
MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::Armor => self.generate_armor(),
|
||||
MonsterDropType::Shield => self.generate_shield(),
|
||||
MonsterDropType::Unit => self.generate_unit(),
|
||||
MonsterDropType::None => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn get_drop(&mut self, map_area: &MapVariantType, monster: &MonsterType) -> Option<ItemDetail> {
|
||||
//let mut rng = rand::thread_rng();
|
||||
let monster_stat = *self.monster_stats.get(monster)?;
|
||||
|
||||
let drop_anything = self.rng.gen_range(0, 100);
|
||||
if drop_anything > monster_stat.dar {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(item) = self.rare_table.get_drop(&monster) {
|
||||
return Some(item);
|
||||
}
|
||||
|
||||
let drop_type = self.rng.gen_range(0, 3);
|
||||
|
||||
match drop_type {
|
||||
0 => {
|
||||
self.generate_meseta(&monster_stat)
|
||||
},
|
||||
1 => {
|
||||
self.generate_tool(map_area, &monster_stat)
|
||||
},
|
||||
2 => {
|
||||
self.generate_typed_drop(map_area, &monster_stat)
|
||||
},
|
||||
_ => panic!()
|
||||
}
|
||||
}
|
||||
}
|
0
src/ship/drops/rare_drop_table.rs
Normal file
0
src/ship/drops/rare_drop_table.rs
Normal file
@ -239,6 +239,7 @@ mod test {
|
||||
},
|
||||
item: ItemDetail::Weapon(Weapon {
|
||||
equipped: false,
|
||||
tekked: true,
|
||||
weapon: item::weapon::Weapon {
|
||||
weapon: item::weapon::WeaponType::Saber,
|
||||
grind: 0,
|
||||
@ -265,6 +266,7 @@ mod test {
|
||||
},
|
||||
item: ItemDetail::Weapon(Weapon {
|
||||
equipped: false,
|
||||
tekked: true,
|
||||
weapon: item::weapon::Weapon {
|
||||
weapon: item::weapon::WeaponType::Handgun,
|
||||
grind: 12,
|
||||
@ -301,6 +303,7 @@ mod test {
|
||||
},
|
||||
item: ItemDetail::Weapon(Weapon {
|
||||
equipped: false,
|
||||
tekked: true,
|
||||
weapon: item::weapon::Weapon {
|
||||
weapon: item::weapon::WeaponType::Handgun,
|
||||
grind: 12,
|
||||
|
@ -190,8 +190,9 @@ enum MapVariantMode {
|
||||
Offline,
|
||||
}
|
||||
|
||||
// TODO: rename this since apparently I'm going to be using it a lot
|
||||
#[derive(Debug)]
|
||||
enum MapVariantType {
|
||||
pub enum MapVariantType {
|
||||
Pioneer2Ep1,
|
||||
Forest1,
|
||||
Forest2,
|
||||
@ -209,6 +210,29 @@ enum MapVariantType {
|
||||
DarkFalz,
|
||||
}
|
||||
|
||||
impl MapVariantType {
|
||||
pub fn area_value(&self) -> Option<u32> {
|
||||
match self {
|
||||
MapVariantType::Forest1 => Some(0),
|
||||
MapVariantType::Forest2 => Some(1),
|
||||
MapVariantType::Caves1 => Some(2),
|
||||
MapVariantType::Caves2 => Some(3),
|
||||
MapVariantType::Caves3 => Some(4),
|
||||
MapVariantType::Mines1 => Some(5),
|
||||
MapVariantType::Mines2 => Some(6),
|
||||
MapVariantType::Ruins1 => Some(7),
|
||||
MapVariantType::Ruins2 => Some(8),
|
||||
MapVariantType::Ruins3 => Some(9),
|
||||
MapVariantType::Dragon => Some(2),
|
||||
MapVariantType::DeRolLe => Some(5),
|
||||
MapVariantType::VolOpt => Some(7),
|
||||
MapVariantType::DarkFalz => Some(9),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MapVariant {
|
||||
map: MapVariantType,
|
||||
|
@ -5,3 +5,5 @@ pub mod room;
|
||||
pub mod items;
|
||||
pub mod map;
|
||||
pub mod monster;
|
||||
|
||||
pub mod drops;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::convert::TryFrom;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -7,12 +8,13 @@ pub enum MonsterParseError {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq, enum_utils::FromStr, derive_more::Display)]
|
||||
pub enum MonsterType {
|
||||
Hildebear,
|
||||
SandRappy,
|
||||
RagRappy,
|
||||
Monest,
|
||||
Mothmant,
|
||||
SavageWolf,
|
||||
BarbarousWolf,
|
||||
Booma,
|
||||
@ -108,25 +110,4 @@ pub enum MonsterType {
|
||||
GoranDetonator,
|
||||
SaintMillion,
|
||||
Shambertin,
|
||||
|
||||
Mothmant,
|
||||
}
|
||||
|
||||
|
||||
impl TryFrom<&str> for MonsterType {
|
||||
type Error = MonsterParseError;
|
||||
|
||||
fn try_from(monster: &str) -> Result<MonsterType, MonsterParseError> {
|
||||
match monster {
|
||||
"Booma" | "Bartle" => Ok(MonsterType::Booma),
|
||||
"Gobooma" | "Barble" => Ok(MonsterType::Gobooma),
|
||||
"Gigobooma" | "Tollaw" => Ok(MonsterType::Gigobooma),
|
||||
// etc...
|
||||
_ => Err(MonsterParseError::UnknownMonster(monster.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,10 +10,13 @@ pub enum RoomCreationError {
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, derive_more::Display)]
|
||||
pub enum Episode {
|
||||
#[display(fmt="ep1")]
|
||||
One,
|
||||
#[display(fmt="ep2")]
|
||||
Two,
|
||||
#[display(fmt="ep4")]
|
||||
Four,
|
||||
}
|
||||
|
||||
@ -40,7 +43,7 @@ impl Into<u8> for Episode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, derive_more::Display)]
|
||||
pub enum Difficulty {
|
||||
Normal,
|
||||
Hard,
|
||||
|
Loading…
x
Reference in New Issue
Block a user