Merge pull request 'rare_monsters' (#60) from rare_monsters into master
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #60
This commit is contained in:
commit
612c089833
@ -229,7 +229,7 @@ lck = 20
|
|||||||
esp = 15
|
esp = 15
|
||||||
exp = 40
|
exp = 40
|
||||||
|
|
||||||
[EasterRappy]
|
[AlRappy]
|
||||||
atp = 527
|
atp = 527
|
||||||
mst = 0
|
mst = 0
|
||||||
evp = 90
|
evp = 90
|
||||||
|
@ -229,7 +229,7 @@ lck = 10
|
|||||||
esp = 0
|
esp = 0
|
||||||
exp = 4
|
exp = 4
|
||||||
|
|
||||||
[EasterRappy]
|
[AlRappy]
|
||||||
atp = 184
|
atp = 184
|
||||||
mst = 0
|
mst = 0
|
||||||
evp = 45
|
evp = 45
|
||||||
|
@ -229,7 +229,7 @@ lck = 30
|
|||||||
esp = 35
|
esp = 35
|
||||||
exp = 256
|
exp = 256
|
||||||
|
|
||||||
[EasterRappy]
|
[AlRappy]
|
||||||
atp = 2100
|
atp = 2100
|
||||||
mst = 0
|
mst = 0
|
||||||
evp = 419
|
evp = 419
|
||||||
|
@ -229,7 +229,7 @@ lck = 35
|
|||||||
esp = 30
|
esp = 30
|
||||||
exp = 88
|
exp = 88
|
||||||
|
|
||||||
[EasterRappy]
|
[AlRappy]
|
||||||
atp = 913
|
atp = 913
|
||||||
mst = 0
|
mst = 0
|
||||||
evp = 194
|
evp = 194
|
||||||
|
9
data/battle_param/ep1_rare_monster.toml
Normal file
9
data/battle_param/ep1_rare_monster.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 1/10 = 0.1
|
||||||
|
# 1/100 = 0.01
|
||||||
|
# 1/256 = 0.00390625
|
||||||
|
# 1/512 = 0.001953125
|
||||||
|
|
||||||
|
Hildebear = 0.001953125
|
||||||
|
RagRappy = 0.001953125
|
||||||
|
PoisonLily = 0.001953125
|
||||||
|
PofuillySlime = 0.001953125
|
@ -229,7 +229,7 @@ lck = 20
|
|||||||
esp = 15
|
esp = 15
|
||||||
exp = 40
|
exp = 40
|
||||||
|
|
||||||
[EasterRappy]
|
[AlRappy]
|
||||||
atp = 443
|
atp = 443
|
||||||
mst = 0
|
mst = 0
|
||||||
evp = 28
|
evp = 28
|
||||||
|
@ -229,7 +229,7 @@ lck = 10
|
|||||||
esp = 0
|
esp = 0
|
||||||
exp = 4
|
exp = 4
|
||||||
|
|
||||||
[EasterRappy]
|
[AlRappy]
|
||||||
atp = 150
|
atp = 150
|
||||||
mst = 0
|
mst = 0
|
||||||
evp = 5
|
evp = 5
|
||||||
|
@ -229,7 +229,7 @@ lck = 30
|
|||||||
esp = 30
|
esp = 30
|
||||||
exp = 256
|
exp = 256
|
||||||
|
|
||||||
[EasterRappy]
|
[AlRappy]
|
||||||
atp = 1800
|
atp = 1800
|
||||||
mst = 0
|
mst = 0
|
||||||
evp = 276
|
evp = 276
|
||||||
|
@ -229,7 +229,7 @@ lck = 35
|
|||||||
esp = 30
|
esp = 30
|
||||||
exp = 88
|
exp = 88
|
||||||
|
|
||||||
[EasterRappy]
|
[AlRappy]
|
||||||
atp = 707
|
atp = 707
|
||||||
mst = 0
|
mst = 0
|
||||||
evp = 55
|
evp = 55
|
||||||
|
9
data/battle_param/ep2_rare_monster.toml
Normal file
9
data/battle_param/ep2_rare_monster.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 1/10 = 0.1
|
||||||
|
# 1/100 = 0.01
|
||||||
|
# 1/256 = 0.00390625
|
||||||
|
# 1/512 = 0.001953125
|
||||||
|
|
||||||
|
Hildebear = 0.001953125
|
||||||
|
RagRappy = 0.001953125
|
||||||
|
PoisonLily = 0.001953125
|
||||||
|
|
13
data/battle_param/ep4_rare_monster.toml
Normal file
13
data/battle_param/ep4_rare_monster.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# 1/10 = 0.1
|
||||||
|
# 1/100 = 0.01
|
||||||
|
# 1/256 = 0.00390625
|
||||||
|
# 1/512 = 0.001953125
|
||||||
|
|
||||||
|
SandRappyCrater = 0.001953125
|
||||||
|
ZuCrater = 0.001953125
|
||||||
|
Dorphon = 0.001953125
|
||||||
|
SandRappyDesert = 0.001953125
|
||||||
|
ZuDesert = 0.001953125
|
||||||
|
MerissaA = 0.001953125
|
||||||
|
Shambertin = 0.1
|
||||||
|
SaintMillion = 0.1
|
11
data/battle_param/global_rare_monster.toml
Normal file
11
data/battle_param/global_rare_monster.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# 1/100 = 0.01
|
||||||
|
# 1/256 = 0.00390625
|
||||||
|
# 1/512 = 0.001953125
|
||||||
|
|
||||||
|
# Everything that isn't Kondrieu
|
||||||
|
[[Grunt]]
|
||||||
|
rate = 0.01
|
||||||
|
|
||||||
|
# Kondrieu
|
||||||
|
[[Boss]]
|
||||||
|
rate = 0.1
|
@ -33,3 +33,7 @@ dat = "q233-ext-bb.dat"
|
|||||||
bin = "q236-ext-bb.bin"
|
bin = "q236-ext-bb.bin"
|
||||||
dat = "q236-ext-bb.dat"
|
dat = "q236-ext-bb.dat"
|
||||||
#drop_table = "q102-drops"
|
#drop_table = "q102-drops"
|
||||||
|
|
||||||
|
[[Retrieval.quests]]
|
||||||
|
bin = "q118-vr-bb.bin"
|
||||||
|
dat = "q118-vr-bb.dat"
|
||||||
|
@ -55,6 +55,17 @@ pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficul
|
|||||||
toml::from_str::<T>(s.as_str()).unwrap()
|
toml::from_str::<T>(s.as_str()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is just copypaste
|
||||||
|
pub fn load_rare_monster_file<T: serde::de::DeserializeOwned>(episode: Episode) -> T {
|
||||||
|
// TODO: where does the rare monster toml file actually live
|
||||||
|
let mut path = PathBuf::from("data/battle_param/");
|
||||||
|
path.push(episode.to_string().to_lowercase() + "_rare_monster.toml");
|
||||||
|
|
||||||
|
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, Copy, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||||
pub enum MonsterDropType {
|
pub enum MonsterDropType {
|
||||||
|
@ -208,6 +208,54 @@ impl MapArea {
|
|||||||
// MapArea::TestMapEp4 => 10,
|
// MapArea::TestMapEp4 => 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_episode(self) -> Episode {
|
||||||
|
match self {
|
||||||
|
MapArea::Pioneer2Ep1 => Episode::One,
|
||||||
|
MapArea::Forest1 => Episode::One,
|
||||||
|
MapArea::Forest2 => Episode::One,
|
||||||
|
MapArea::Caves1 => Episode::One,
|
||||||
|
MapArea::Caves2 => Episode::One,
|
||||||
|
MapArea::Caves3 => Episode::One,
|
||||||
|
MapArea::Mines1 => Episode::One,
|
||||||
|
MapArea::Mines2 => Episode::One,
|
||||||
|
MapArea::Ruins1 => Episode::One,
|
||||||
|
MapArea::Ruins2 => Episode::One,
|
||||||
|
MapArea::Ruins3 => Episode::One,
|
||||||
|
MapArea::Dragon => Episode::One,
|
||||||
|
MapArea::DeRolLe => Episode::One,
|
||||||
|
MapArea::VolOpt => Episode::One,
|
||||||
|
MapArea::DarkFalz => Episode::One,
|
||||||
|
MapArea::Pioneer2Ep2 => Episode::Two,
|
||||||
|
MapArea::VrTempleAlpha => Episode::Two,
|
||||||
|
MapArea::VrTempleBeta => Episode::Two,
|
||||||
|
MapArea::VrSpaceshipAlpha => Episode::Two,
|
||||||
|
MapArea::VrSpaceshipBeta => Episode::Two,
|
||||||
|
MapArea::Cca => Episode::Two,
|
||||||
|
MapArea::JungleAreaNorth => Episode::Two,
|
||||||
|
MapArea::JungleAreaEast => Episode::Two,
|
||||||
|
MapArea::Mountain => Episode::Two,
|
||||||
|
MapArea::Seaside => Episode::Two,
|
||||||
|
MapArea::SeabedUpper => Episode::Two,
|
||||||
|
MapArea::SeabedLower => Episode::Two,
|
||||||
|
MapArea::GalGryphon => Episode::Two,
|
||||||
|
MapArea::OlgaFlow => Episode::Two,
|
||||||
|
MapArea::BarbaRay => Episode::Two,
|
||||||
|
MapArea::GolDragon => Episode::Two,
|
||||||
|
MapArea::SeasideNight => Episode::Two,
|
||||||
|
MapArea::Tower => Episode::Two,
|
||||||
|
MapArea::Pioneer2Ep4 => Episode::Four,
|
||||||
|
MapArea::CraterEast => Episode::Four,
|
||||||
|
MapArea::CraterWest => Episode::Four,
|
||||||
|
MapArea::CraterSouth => Episode::Four,
|
||||||
|
MapArea::CraterNorth => Episode::Four,
|
||||||
|
MapArea::CraterInterior => Episode::Four,
|
||||||
|
MapArea::SubDesert1 => Episode::Four,
|
||||||
|
MapArea::SubDesert2 => Episode::Four,
|
||||||
|
MapArea::SubDesert3 => Episode::Four,
|
||||||
|
MapArea::SaintMillion => Episode::Four,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// TOOD: `pub(super) for most of these?`
|
// TOOD: `pub(super) for most of these?`
|
||||||
use std::io::{Read};
|
use std::io::{Read};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -9,6 +10,10 @@ use crate::ship::room::Episode;
|
|||||||
|
|
||||||
use crate::ship::map::*;
|
use crate::ship::map::*;
|
||||||
|
|
||||||
|
use rand::{Rng, SeedableRng};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::ship::drops::{load_rare_monster_file};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct RawMapEnemy {
|
pub struct RawMapEnemy {
|
||||||
id: u32,
|
id: u32,
|
||||||
@ -37,7 +42,7 @@ pub struct RawMapEnemy {
|
|||||||
impl RawMapEnemy {
|
impl RawMapEnemy {
|
||||||
pub fn from_byte_stream<R: Read>(cursor: &mut R) -> Result<RawMapEnemy, std::io::Error> {
|
pub fn from_byte_stream<R: Read>(cursor: &mut R) -> Result<RawMapEnemy, std::io::Error> {
|
||||||
Ok(RawMapEnemy {
|
Ok(RawMapEnemy {
|
||||||
id: cursor.read_u32::<LittleEndian>()?,
|
id: cursor.read_u32::<LittleEndian>()?, // TODO: is this really u32? shiny monsters are referred to by u16 in the client
|
||||||
_unknown1: cursor.read_u16::<LittleEndian>()?,
|
_unknown1: cursor.read_u16::<LittleEndian>()?,
|
||||||
children: cursor.read_u16::<LittleEndian>()?,
|
children: cursor.read_u16::<LittleEndian>()?,
|
||||||
_map_area: cursor.read_u16::<LittleEndian>()?,
|
_map_area: cursor.read_u16::<LittleEndian>()?,
|
||||||
@ -70,6 +75,38 @@ pub enum MapEnemyError {
|
|||||||
MapAreaError(#[from] MapAreaError),
|
MapAreaError(#[from] MapAreaError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// making this `pub type` doesn't allow `impl`s to be defined?
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct RareMonsterAppearTable {
|
||||||
|
pub appear_rate: HashMap<MonsterType, f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RareMonsterAppearTable {
|
||||||
|
pub fn new(episode: Episode) -> RareMonsterAppearTable {
|
||||||
|
let cfg: HashMap<String, f32> = load_rare_monster_file(episode);
|
||||||
|
|
||||||
|
let appear_rates: HashMap<MonsterType, f32> = cfg
|
||||||
|
.into_iter()
|
||||||
|
.map(|(monster, rate)| {
|
||||||
|
let monster: MonsterType = monster.parse().unwrap(); // TODO: don't unwrap!
|
||||||
|
let appear_rate = rate;
|
||||||
|
(monster, appear_rate)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
RareMonsterAppearTable {
|
||||||
|
appear_rate: appear_rates,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn roll_appearance(&self, monster: &MonsterType) -> bool {
|
||||||
|
if rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct MapEnemy {
|
pub struct MapEnemy {
|
||||||
@ -80,21 +117,23 @@ pub struct MapEnemy {
|
|||||||
pub player_hit: [bool; 4],
|
pub player_hit: [bool; 4],
|
||||||
pub dropped_item: bool,
|
pub dropped_item: bool,
|
||||||
pub gave_exp: bool,
|
pub gave_exp: bool,
|
||||||
|
pub shiny: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapEnemy {
|
impl MapEnemy {
|
||||||
pub fn from_raw(enemy: RawMapEnemy, episode: &Episode, map_area: &MapArea /*, battleparam */) -> Result<MapEnemy, MapEnemyError> {
|
pub fn from_raw(enemy: RawMapEnemy, episode: &Episode, map_area: &MapArea /*, battleparam */) -> Result<MapEnemy, MapEnemyError> {
|
||||||
// TODO: rare enemies ep1-4, tower lilys, event rappies, ult variants?
|
// TODO: rare enemies ep1-4, tower lilys, event rappies, ult variants?
|
||||||
|
// TODO: check what "skin" actually does. some unexpected enemies have many (panarms, slimes, lilys)
|
||||||
let monster = match map_area {
|
let monster = match map_area {
|
||||||
MapArea::Forest1 | MapArea::Forest2 | MapArea::Dragon |
|
MapArea::Forest1 | MapArea::Forest2 | MapArea::Dragon |
|
||||||
MapArea::Caves1 | MapArea::Caves2 | MapArea::Caves3 | MapArea::DeRolLe |
|
MapArea::Caves1 | MapArea::Caves2 | MapArea::Caves3 | MapArea::DeRolLe |
|
||||||
MapArea::Mines1 | MapArea::Mines2 | MapArea::VolOpt |
|
MapArea::Mines1 | MapArea::Mines2 | MapArea::VolOpt |
|
||||||
MapArea::Ruins1 | MapArea::Ruins2 | MapArea::Ruins3 | MapArea::DarkFalz => {
|
MapArea::Ruins1 | MapArea::Ruins2 | MapArea::Ruins3 | MapArea::DarkFalz => {
|
||||||
match (enemy, episode) {
|
match (enemy, episode) {
|
||||||
(RawMapEnemy {id: 64, ..}, _) => MonsterType::Hildebear,
|
(RawMapEnemy {id: 64, skin: 0, ..}, _) => MonsterType::Hildebear,
|
||||||
// (RawMapEnemy {id: 64, ..}, _) => MonsterType::Hildeblue,
|
(RawMapEnemy {id: 64, skin: 1, ..}, _) => MonsterType::Hildeblue,
|
||||||
(RawMapEnemy {id: 65, ..}, _) => MonsterType::RagRappy,
|
(RawMapEnemy {id: 65, skin: 0, ..}, _) => MonsterType::RagRappy,
|
||||||
// (RawMapEnemy {id: 65, ..}, _) => MonsterType::AlRappy,
|
(RawMapEnemy {id: 65, skin: 1, ..}, _) => MonsterType::AlRappy,
|
||||||
(RawMapEnemy {id: 66, ..}, _) => MonsterType::Monest,
|
(RawMapEnemy {id: 66, ..}, _) => MonsterType::Monest,
|
||||||
(RawMapEnemy {id: 67, field2: 0, ..}, _) => MonsterType::SavageWolf,
|
(RawMapEnemy {id: 67, field2: 0, ..}, _) => MonsterType::SavageWolf,
|
||||||
(RawMapEnemy {id: 67, ..}, _) => MonsterType::BarbarousWolf,
|
(RawMapEnemy {id: 67, ..}, _) => MonsterType::BarbarousWolf,
|
||||||
@ -103,13 +142,16 @@ impl MapEnemy {
|
|||||||
(RawMapEnemy {id: 68, skin: 2, ..}, _) => MonsterType::Gigobooma,
|
(RawMapEnemy {id: 68, skin: 2, ..}, _) => MonsterType::Gigobooma,
|
||||||
(RawMapEnemy {id: 96, ..}, _) => MonsterType::GrassAssassin,
|
(RawMapEnemy {id: 96, ..}, _) => MonsterType::GrassAssassin,
|
||||||
(RawMapEnemy {id: 97, ..}, _) => MonsterType::PoisonLily,
|
(RawMapEnemy {id: 97, ..}, _) => MonsterType::PoisonLily,
|
||||||
// (RawMapEnemy {id: 97, ..}, _) => MonsterType::NarLily,
|
// (RawMapEnemy {id: 97, skin: 0, ..}, _) => MonsterType::PoisonLily,
|
||||||
|
// (RawMapEnemy {id: 97, skin: 1, ..}, _) => MonsterType::NarLily,
|
||||||
(RawMapEnemy {id: 98, ..}, _) => MonsterType::NanoDragon,
|
(RawMapEnemy {id: 98, ..}, _) => MonsterType::NanoDragon,
|
||||||
(RawMapEnemy {id: 99, skin: 0, ..}, _) => MonsterType::EvilShark,
|
(RawMapEnemy {id: 99, skin: 0, ..}, _) => MonsterType::EvilShark,
|
||||||
(RawMapEnemy {id: 99, skin: 1, ..}, _) => MonsterType::PalShark,
|
(RawMapEnemy {id: 99, skin: 1, ..}, _) => MonsterType::PalShark,
|
||||||
(RawMapEnemy {id: 99, skin: 2, ..}, _) => MonsterType::GuilShark,
|
(RawMapEnemy {id: 99, skin: 2, ..}, _) => MonsterType::GuilShark,
|
||||||
(RawMapEnemy {id: 100, ..}, _) => MonsterType::PofuillySlime,
|
(RawMapEnemy {id: 100, ..}, _) => MonsterType::PofuillySlime,
|
||||||
// (RawMapEnemy {id: 100, ..}, _) => MonsterType::PouillySlime,
|
// (RawMapEnemy {id: 100, skin: 0, ..}, _) => MonsterType::PofuillySlime,
|
||||||
|
// (RawMapEnemy {id: 100, skin: 1, ..}, _) => MonsterType::PouillySlime,
|
||||||
|
// (RawMapEnemy {id: 100, skin: 2, ..}, _) => MonsterType::PofuillySlime,
|
||||||
(RawMapEnemy {id: 101, ..}, _) => MonsterType::PanArms,
|
(RawMapEnemy {id: 101, ..}, _) => MonsterType::PanArms,
|
||||||
(RawMapEnemy {id: 128, skin: 0, ..}, _) => MonsterType::Dubchic,
|
(RawMapEnemy {id: 128, skin: 0, ..}, _) => MonsterType::Dubchic,
|
||||||
(RawMapEnemy {id: 128, skin: 1, ..}, _) => MonsterType::Gillchic,
|
(RawMapEnemy {id: 128, skin: 1, ..}, _) => MonsterType::Gillchic,
|
||||||
@ -122,6 +164,7 @@ impl MapEnemy {
|
|||||||
(RawMapEnemy {id: 160, ..}, _) => MonsterType::Delsaber,
|
(RawMapEnemy {id: 160, ..}, _) => MonsterType::Delsaber,
|
||||||
(RawMapEnemy {id: 161, ..}, _) => MonsterType::ChaosSorcerer,
|
(RawMapEnemy {id: 161, ..}, _) => MonsterType::ChaosSorcerer,
|
||||||
(RawMapEnemy {id: 162, ..}, _) => MonsterType::DarkGunner,
|
(RawMapEnemy {id: 162, ..}, _) => MonsterType::DarkGunner,
|
||||||
|
(RawMapEnemy {id: 163, ..}, _) => MonsterType::DeathGunner,
|
||||||
(RawMapEnemy {id: 164, ..}, _) => MonsterType::ChaosBringer,
|
(RawMapEnemy {id: 164, ..}, _) => MonsterType::ChaosBringer,
|
||||||
(RawMapEnemy {id: 165, ..}, _) => MonsterType::DarkBelra,
|
(RawMapEnemy {id: 165, ..}, _) => MonsterType::DarkBelra,
|
||||||
(RawMapEnemy {id: 166, skin: 0, ..}, _) => MonsterType::Dimenian,
|
(RawMapEnemy {id: 166, skin: 0, ..}, _) => MonsterType::Dimenian,
|
||||||
@ -143,17 +186,16 @@ impl MapEnemy {
|
|||||||
MapArea::JungleAreaNorth | MapArea::JungleAreaEast | MapArea::Mountain | MapArea::Seaside | MapArea::SeasideNight | MapArea::Cca | MapArea::GalGryphon |
|
MapArea::JungleAreaNorth | MapArea::JungleAreaEast | MapArea::Mountain | MapArea::Seaside | MapArea::SeasideNight | MapArea::Cca | MapArea::GalGryphon |
|
||||||
MapArea::SeabedUpper | MapArea::SeabedLower | MapArea::OlgaFlow => {
|
MapArea::SeabedUpper | MapArea::SeabedLower | MapArea::OlgaFlow => {
|
||||||
match (enemy, episode) {
|
match (enemy, episode) {
|
||||||
(RawMapEnemy {id: 64, ..}, _) => MonsterType::Hildebear,
|
(RawMapEnemy {id: 64, skin: 0, ..}, _) => MonsterType::Hildebear,
|
||||||
// (RawMapEnemy {id: 64, ..}, _) => MonsterType::Hildeblue,
|
(RawMapEnemy {id: 64, skin: 1, ..}, _) => MonsterType::Hildeblue,
|
||||||
(RawMapEnemy {id: 65, ..}, _) => MonsterType::RagRappy,
|
(RawMapEnemy {id: 65, skin: 0, ..}, _) => MonsterType::RagRappy,
|
||||||
// (RawMapEnemy {id: 65, ..}, _) => MonsterType::EventRappy,
|
(RawMapEnemy {id: 65, skin: 1, ..}, _) => MonsterType::EventRappy,
|
||||||
(RawMapEnemy {id: 66, ..}, _) => MonsterType::Monest,
|
(RawMapEnemy {id: 66, ..}, _) => MonsterType::Monest,
|
||||||
(RawMapEnemy {id: 67, field2: 0, ..}, _) => MonsterType::SavageWolf,
|
(RawMapEnemy {id: 67, field2: 0, ..}, _) => MonsterType::SavageWolf,
|
||||||
(RawMapEnemy {id: 67, ..}, _) => MonsterType::BarbarousWolf,
|
(RawMapEnemy {id: 67, ..}, _) => MonsterType::BarbarousWolf,
|
||||||
(RawMapEnemy {id: 96, ..}, _) => MonsterType::GrassAssassin,
|
(RawMapEnemy {id: 96, ..}, _) => MonsterType::GrassAssassin,
|
||||||
(RawMapEnemy {id: 97, ..}, _) => MonsterType::PoisonLily,
|
(RawMapEnemy {id: 97, skin: 0, ..}, _) => MonsterType::PoisonLily,
|
||||||
// (RawMapEnemy {id: 97, ..}, _) => MonsterType::NarLily,
|
(RawMapEnemy {id: 97, skin: 1, ..}, _) => MonsterType::NarLily,
|
||||||
// (RawMapEnemy {id: 97, ..}, _) => MonsterType::DelLily,
|
|
||||||
(RawMapEnemy {id: 101, ..}, _) => MonsterType::PanArms,
|
(RawMapEnemy {id: 101, ..}, _) => MonsterType::PanArms,
|
||||||
(RawMapEnemy {id: 128, skin: 0, ..}, _) => MonsterType::Dubchic,
|
(RawMapEnemy {id: 128, skin: 0, ..}, _) => MonsterType::Dubchic,
|
||||||
(RawMapEnemy {id: 128, skin: 1, ..}, _) => MonsterType::Gillchic,
|
(RawMapEnemy {id: 128, skin: 1, ..}, _) => MonsterType::Gillchic,
|
||||||
@ -213,38 +255,38 @@ impl MapEnemy {
|
|||||||
|
|
||||||
MapArea::CraterEast | MapArea::CraterWest | MapArea::CraterSouth | MapArea::CraterNorth | MapArea::CraterInterior => {
|
MapArea::CraterEast | MapArea::CraterWest | MapArea::CraterSouth | MapArea::CraterNorth | MapArea::CraterInterior => {
|
||||||
match (enemy, episode) {
|
match (enemy, episode) {
|
||||||
(RawMapEnemy {id: 65, ..}, Episode::Four) => MonsterType::SandRappyCrater,
|
(RawMapEnemy {id: 65, skin: 0, ..}, Episode::Four) => MonsterType::SandRappyCrater,
|
||||||
// (RawMapEnemy {id: 65, ..}, Episode::Four) => MonsterType::DelRappyCrater,
|
(RawMapEnemy {id: 65, skin: 1, ..}, Episode::Four) => MonsterType::DelRappyCrater,
|
||||||
(RawMapEnemy {id: 272, ..}, _) => MonsterType::Astark,
|
(RawMapEnemy {id: 272, ..}, _) => MonsterType::Astark,
|
||||||
(RawMapEnemy {id: 273, field2: 0, ..}, _) => MonsterType::SatelliteLizardCrater,
|
(RawMapEnemy {id: 273, field2: 0, ..}, _) => MonsterType::SatelliteLizardCrater,
|
||||||
(RawMapEnemy {id: 273, ..}, _) => MonsterType::YowieCrater,
|
(RawMapEnemy {id: 273, ..}, _) => MonsterType::YowieCrater,
|
||||||
(RawMapEnemy {id: 276, ..}, _) => MonsterType::ZuCrater,
|
(RawMapEnemy {id: 276, skin: 0, ..}, _) => MonsterType::ZuCrater,
|
||||||
// (RawMapEnemy {id: 276, ..}, _) => MonsterType::PazuzuCrater,
|
(RawMapEnemy {id: 276, skin: 1, ..}, _) => MonsterType::PazuzuCrater,
|
||||||
(RawMapEnemy {id: 277, skin: 0, ..}, _) => MonsterType::Boota,
|
(RawMapEnemy {id: 277, skin: 0, ..}, _) => MonsterType::Boota,
|
||||||
(RawMapEnemy {id: 277, skin: 1, ..}, _) => MonsterType::ZeBoota,
|
(RawMapEnemy {id: 277, skin: 1, ..}, _) => MonsterType::ZeBoota,
|
||||||
(RawMapEnemy {id: 277, skin: 2, ..}, _) => MonsterType::BaBoota,
|
(RawMapEnemy {id: 277, skin: 2, ..}, _) => MonsterType::BaBoota,
|
||||||
(RawMapEnemy {id: 278, ..}, _) => MonsterType::Dorphon,
|
(RawMapEnemy {id: 278, skin: 0, ..}, _) => MonsterType::Dorphon,
|
||||||
// (RawMapEnemy {id: 278, ..}, _) => MonsterType::DorphonEclair,
|
(RawMapEnemy {id: 278, skin: 1, ..}, _) => MonsterType::DorphonEclair,
|
||||||
_ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
|
_ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MapArea::SubDesert1 | MapArea::SubDesert2 | MapArea::SubDesert3 | MapArea::SaintMillion => {
|
MapArea::SubDesert1 | MapArea::SubDesert2 | MapArea::SubDesert3 | MapArea::SaintMillion => {
|
||||||
match (enemy, episode) {
|
match (enemy, episode) {
|
||||||
(RawMapEnemy {id: 65, ..}, Episode::Four) => MonsterType::SandRappyDesert,
|
(RawMapEnemy {id: 65, skin: 0, ..}, Episode::Four) => MonsterType::SandRappyDesert,
|
||||||
// (RawMapEnemy {id: 65, ..}, Episode::Four) => MonsterType::DelRappyDesert,
|
(RawMapEnemy {id: 65, skin: 1, ..}, Episode::Four) => MonsterType::DelRappyDesert,
|
||||||
(RawMapEnemy {id: 273, field2: 0, ..}, _) => MonsterType::SatelliteLizardDesert,
|
(RawMapEnemy {id: 273, field2: 0, ..}, _) => MonsterType::SatelliteLizardDesert,
|
||||||
(RawMapEnemy {id: 273, ..}, _) => MonsterType::YowieDesert,
|
(RawMapEnemy {id: 273, ..}, _) => MonsterType::YowieDesert,
|
||||||
(RawMapEnemy {id: 274, ..}, _) => MonsterType::MerissaA,
|
(RawMapEnemy {id: 274, skin: 0, ..}, _) => MonsterType::MerissaA,
|
||||||
// (RawMapEnemy {id: 274, ..}, _) => MonsterType::MerissaAA,
|
(RawMapEnemy {id: 274, skin: 1, ..}, _) => MonsterType::MerissaAA,
|
||||||
(RawMapEnemy {id: 275, ..}, _) => MonsterType::Girtablulu,
|
(RawMapEnemy {id: 275, ..}, _) => MonsterType::Girtablulu,
|
||||||
(RawMapEnemy {id: 276, ..}, _) => MonsterType::ZuDesert,
|
(RawMapEnemy {id: 276, skin: 0, ..}, _) => MonsterType::ZuDesert,
|
||||||
// (RawMapEnemy {id: 276, ..}, _) => MonsterType::PazuzuDesert,
|
(RawMapEnemy {id: 276, skin: 1, ..}, _) => MonsterType::PazuzuDesert,
|
||||||
(RawMapEnemy {id: 279, skin: 0, ..}, _) => MonsterType::Goran,
|
(RawMapEnemy {id: 279, skin: 0, ..}, _) => MonsterType::Goran,
|
||||||
(RawMapEnemy {id: 279, skin: 1, ..}, _) => MonsterType::PyroGoran,
|
(RawMapEnemy {id: 279, skin: 1, ..}, _) => MonsterType::PyroGoran,
|
||||||
(RawMapEnemy {id: 279, skin: 2, ..}, _) => MonsterType::GoranDetonator,
|
(RawMapEnemy {id: 279, skin: 2, ..}, _) => MonsterType::GoranDetonator,
|
||||||
(RawMapEnemy {id: 281, skin: 0, ..}, _) => MonsterType::SaintMillion,
|
(RawMapEnemy {id: 281, skin: 0, ..}, _) => MonsterType::SaintMillion,
|
||||||
(RawMapEnemy {id: 281, skin: 1, ..}, _) => MonsterType::Shambertin,
|
(RawMapEnemy {id: 281, skin: 1, ..}, _) => MonsterType::Shambertin, // TODO: don't guess the skin
|
||||||
// (RawMapEnemy {id: 281, skin: 1, ..}, _) => MonsterType::Kondrieu,
|
(RawMapEnemy {id: 281, skin: 2, ..}, _) => MonsterType::Kondrieu, // TODO: don't guess the skin
|
||||||
_ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
|
_ => return Err(MapEnemyError::UnknownEnemyId(enemy.id))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -258,6 +300,7 @@ impl MapEnemy {
|
|||||||
dropped_item: false,
|
dropped_item: false,
|
||||||
gave_exp: false,
|
gave_exp: false,
|
||||||
player_hit: [false; 4],
|
player_hit: [false; 4],
|
||||||
|
shiny: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +312,56 @@ impl MapEnemy {
|
|||||||
dropped_item: false,
|
dropped_item: false,
|
||||||
gave_exp: false,
|
gave_exp: false,
|
||||||
player_hit: [false; 4],
|
player_hit: [false; 4],
|
||||||
|
shiny: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_shiny(self) -> MapEnemy {
|
||||||
|
MapEnemy {
|
||||||
|
shiny: true,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_rare_appearance(self) -> bool {
|
||||||
|
matches!(self.monster,
|
||||||
|
MonsterType::RagRappy | MonsterType::Hildebear |
|
||||||
|
MonsterType::PoisonLily | MonsterType::PofuillySlime |
|
||||||
|
MonsterType::SandRappyCrater | MonsterType::ZuCrater | MonsterType::Dorphon |
|
||||||
|
MonsterType::SandRappyDesert | MonsterType::ZuDesert | MonsterType::MerissaA |
|
||||||
|
MonsterType::SaintMillion | MonsterType::Shambertin
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: distinguish between a `random` rare monster and a `set/guaranteed` rare monster? (does any acceptable quest even have this?)
|
||||||
|
guaranteed rare monsters don't count towards the limit
|
||||||
|
*/
|
||||||
|
pub fn set_rare_appearance(self) -> MapEnemy {
|
||||||
|
match (self.monster, self.map_area.to_episode()) {
|
||||||
|
(MonsterType::RagRappy, Episode::One) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
|
||||||
|
(MonsterType::RagRappy, Episode::Two) => {MapEnemy {monster: MonsterType::EventRappy, shiny:true, ..self}},
|
||||||
|
(MonsterType::Hildebear, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}},
|
||||||
|
(MonsterType::PoisonLily, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}},
|
||||||
|
(MonsterType::PofuillySlime, _) => {MapEnemy {monster: MonsterType::PouillySlime, shiny:true, ..self}},
|
||||||
|
(MonsterType::SandRappyCrater, _) => {MapEnemy {monster: MonsterType::DelRappyCrater, shiny:true, ..self}},
|
||||||
|
(MonsterType::ZuCrater, _) => {MapEnemy {monster: MonsterType::PazuzuCrater, shiny:true, ..self}},
|
||||||
|
(MonsterType::Dorphon, _) => {MapEnemy {monster: MonsterType::DorphonEclair, shiny:true, ..self}},
|
||||||
|
(MonsterType::SandRappyDesert, _) => {MapEnemy {monster: MonsterType::DelRappyDesert, shiny:true, ..self}},
|
||||||
|
(MonsterType::ZuDesert, _) => {MapEnemy {monster: MonsterType::PazuzuDesert, shiny:true, ..self}},
|
||||||
|
(MonsterType::MerissaA, _) => {MapEnemy {monster: MonsterType::MerissaAA, shiny:true, ..self}},
|
||||||
|
(MonsterType::SaintMillion, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
|
||||||
|
(MonsterType::Shambertin, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
|
||||||
|
_ => {self},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in theory this should only be called on monsters we know can have rare types
|
||||||
|
pub fn roll_appearance_for_mission(self, rare_monster_table: &RareMonsterAppearTable) -> MapEnemy {
|
||||||
|
if rare_monster_table.roll_appearance(&self.monster) {
|
||||||
|
return self.set_rare_appearance()
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ fn objects_from_map_data(path: PathBuf, episode: &Episode, map_area: &MapArea) -
|
|||||||
|
|
||||||
fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) -> Vec<Option<MapEnemy>> {
|
fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) -> Vec<Option<MapEnemy>> {
|
||||||
let enemy = MapEnemy::from_raw(raw_enemy, episode, map_area);
|
let enemy = MapEnemy::from_raw(raw_enemy, episode, map_area);
|
||||||
|
|
||||||
enemy
|
enemy
|
||||||
.map_or(vec![None], |monster| {
|
.map_or(vec![None], |monster| {
|
||||||
let mut monsters = vec![Some(monster)];
|
let mut monsters = vec![Some(monster)];
|
||||||
@ -40,15 +41,20 @@ fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) ->
|
|||||||
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area)));
|
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MonsterType::PofuillySlime => {
|
|
||||||
for _ in 0..4 {
|
|
||||||
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MonsterType::PanArms => {
|
MonsterType::PanArms => {
|
||||||
monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area)));
|
monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area)));
|
||||||
monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area)));
|
monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area)));
|
||||||
},
|
},
|
||||||
|
MonsterType::PofuillySlime => {
|
||||||
|
for _ in 0..5 {
|
||||||
|
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MonsterType::PouillySlime => {
|
||||||
|
for _ in 0..5 {
|
||||||
|
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
|
||||||
|
}
|
||||||
|
},
|
||||||
MonsterType::SinowBeat => {
|
MonsterType::SinowBeat => {
|
||||||
for _ in 0..4 {
|
for _ in 0..4 {
|
||||||
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area)));
|
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area)));
|
||||||
@ -180,7 +186,7 @@ pub struct Maps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Maps {
|
impl Maps {
|
||||||
pub fn new(room_mode: RoomMode) -> Maps {
|
pub fn new(room_mode: RoomMode, rare_monster_table: &enemy::RareMonsterAppearTable) -> Maps {
|
||||||
let map_variants = match (room_mode.episode(), room_mode.single_player()) {
|
let map_variants = match (room_mode.episode(), room_mode.single_player()) {
|
||||||
(Episode::One, 0) => {
|
(Episode::One, 0) => {
|
||||||
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
|
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
|
||||||
@ -272,16 +278,19 @@ impl Maps {
|
|||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let maps = Maps {
|
let mut maps = Maps {
|
||||||
enemy_data: map_variants.iter().fold(Vec::new(), |mut enemy_data, map_variant| {
|
enemy_data: map_variants.iter()
|
||||||
enemy_data.append(&mut enemy_data_from_map_data(map_variant, &room_mode.episode()));
|
.fold(Vec::new(), |mut enemy_data, map_variant| {
|
||||||
enemy_data
|
enemy_data.append(&mut enemy_data_from_map_data(map_variant, &room_mode.episode()));
|
||||||
}),
|
enemy_data
|
||||||
object_data: map_variants.iter().map(|map_variant| {
|
}),
|
||||||
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
|
object_data: map_variants.iter()
|
||||||
}).flatten().collect(),
|
.map(|map_variant| {
|
||||||
map_variants,
|
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
|
||||||
|
}).flatten().collect(),
|
||||||
|
map_variants,
|
||||||
};
|
};
|
||||||
|
maps.roll_monster_appearance(rare_monster_table);
|
||||||
maps
|
maps
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,9 +314,49 @@ impl Maps {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_quest_data(&mut self, enemies: Vec<Option<MapEnemy>>, objects: Vec<Option<MapObject>>) {
|
pub fn set_quest_data(&mut self, enemies: Vec<Option<MapEnemy>>, objects: Vec<Option<MapObject>>, rare_monster_appear_table: &RareMonsterAppearTable) {
|
||||||
self.enemy_data = enemies;
|
self.enemy_data = enemies;
|
||||||
|
self.roll_monster_appearance(rare_monster_appear_table);
|
||||||
self.object_data = objects;
|
self.object_data = objects;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn get_rare_monster_list(&self) -> Vec<u16> {
|
||||||
|
let mut rare_monsters = vec![0xFFFF; 16];
|
||||||
|
let shiny: Vec<(usize, &Option<MapEnemy>)> = self.enemy_data.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_,m)| {
|
||||||
|
if m.is_some() {
|
||||||
|
m.unwrap().shiny
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
for monster in &shiny {
|
||||||
|
if let Some(j) = rare_monsters.iter().position(|&x| x == 0xFFFF) {
|
||||||
|
rare_monsters[j] = monster.0 as u16;
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rare_monsters
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn roll_monster_appearance(&mut self, rare_monster_table: &RareMonsterAppearTable) {
|
||||||
|
self.enemy_data = self.enemy_data
|
||||||
|
.iter()
|
||||||
|
// .map(|&x| if x.is_some() && x.unwrap().has_rare_appearance() {
|
||||||
|
.map(|&x|
|
||||||
|
if let Some(monster) = x {
|
||||||
|
if monster.has_rare_appearance() {
|
||||||
|
Some(monster.roll_appearance_for_mission(rare_monster_table))
|
||||||
|
} else {
|
||||||
|
Some(monster)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
pub mod area;
|
pub mod area;
|
||||||
mod enemy;
|
pub mod enemy;
|
||||||
mod object;
|
mod object;
|
||||||
mod variant;
|
mod variant;
|
||||||
mod maps;
|
mod maps;
|
||||||
|
@ -6,6 +6,7 @@ use crate::ship::location::{ClientLocation, RoomId, AreaClient, ClientLocationEr
|
|||||||
use crate::ship::room::RoomState;
|
use crate::ship::room::RoomState;
|
||||||
use crate::ship::items::ItemManager;
|
use crate::ship::items::ItemManager;
|
||||||
use crate::ship::packet::builder::{player_header, player_info};
|
use crate::ship::packet::builder::{player_header, player_info};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
pub fn join_room(id: ClientId,
|
pub fn join_room(id: ClientId,
|
||||||
clients: &Clients,
|
clients: &Clients,
|
||||||
@ -71,3 +72,8 @@ pub fn add_to_room(_id: ClientId,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_rare_monster_list(rare_monster_vec: Vec<u16>) -> RareMonsterList {
|
||||||
|
RareMonsterList {
|
||||||
|
ids: rare_monster_vec.try_into().unwrap_or([0xFFFFu16; 16]),
|
||||||
|
}
|
||||||
|
}
|
@ -68,7 +68,7 @@ pub fn quest_detail(id: ClientId, questdetailrequest: &QuestDetailRequest, quest
|
|||||||
Ok(Box::new(vec![(id, SendShipPacket::QuestDetail(qd))].into_iter()))
|
Ok(Box::new(vec![(id, SendShipPacket::QuestDetail(qd))].into_iter()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quests: &QuestList, clients: &mut Clients, client_location: &ClientLocation, rooms: &mut Rooms)
|
pub fn player_chose_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quests: &QuestList, clients: &mut Clients, client_location: &ClientLocation, rooms: &mut Rooms)
|
||||||
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
|
||||||
let (_, category_quests) = quests.iter()
|
let (_, category_quests) = quests.iter()
|
||||||
.nth(questmenuselect.category as usize)
|
.nth(questmenuselect.category as usize)
|
||||||
@ -83,7 +83,7 @@ pub fn load_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quests: &Ques
|
|||||||
let room = rooms.get_mut(room_id.0)
|
let room = rooms.get_mut(room_id.0)
|
||||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
|
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
|
||||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||||
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone());
|
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &room.rare_monster_table);
|
||||||
room.map_areas = quest.map_areas.clone();
|
room.map_areas = quest.map_areas.clone();
|
||||||
|
|
||||||
let bin = quest::quest_header(questmenuselect, &quest.bin_blob, "bin");
|
let bin = quest::quest_header(questmenuselect, &quest.bin_blob, "bin");
|
||||||
|
@ -149,13 +149,17 @@ pub fn done_bursting(id: ClientId,
|
|||||||
rooms: &mut Rooms)
|
rooms: &mut Rooms)
|
||||||
-> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
|
-> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
|
||||||
let area = client_location.get_area(id).unwrap();
|
let area = client_location.get_area(id).unwrap();
|
||||||
|
let mut rare_monster_list: Option<Vec<u16>> = None;
|
||||||
if let RoomLobby::Room(room_id) = area {
|
if let RoomLobby::Room(room_id) = area {
|
||||||
if let Some(room) = rooms.get_mut(room_id.0).unwrap().as_mut() {
|
if let Some(room) = rooms.get_mut(room_id.0).unwrap().as_mut() {
|
||||||
room.bursting = false;
|
room.bursting = false;
|
||||||
}
|
rare_monster_list = Some(room.maps.get_rare_monster_list());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
let area_client = client_location.get_local_client(id).unwrap(); // TODO: unwrap
|
let area_client = client_location.get_local_client(id).unwrap(); // TODO: unwrap
|
||||||
Box::new(client_location.get_client_neighbors(id).unwrap().into_iter() // TODO: unwrap
|
|
||||||
|
let mut result: Box<dyn Iterator<Item=(ClientId, SendShipPacket)> + Send> = Box::new(
|
||||||
|
client_location.get_client_neighbors(id).unwrap().into_iter() // TODO: unwrap
|
||||||
.map(move |client| {
|
.map(move |client| {
|
||||||
vec![
|
vec![
|
||||||
(client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone {
|
(client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone {
|
||||||
@ -163,7 +167,17 @@ pub fn done_bursting(id: ClientId,
|
|||||||
target: 0
|
target: 0
|
||||||
})))),
|
})))),
|
||||||
]
|
]
|
||||||
}).flatten())
|
})
|
||||||
|
.flatten()
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: check how often `done_bursting` is called. ie: make sure it's only used when joining a room and not each time a player warps in a pipe
|
||||||
|
if let Some(rare_list) = rare_monster_list {
|
||||||
|
let rare_monster_packet = SendShipPacket::RareMonsterList(builder::room::build_rare_monster_list(rare_list));
|
||||||
|
result = Box::new(result.chain(vec![(id, rare_monster_packet)])); // TODO: make sure we arent clobbering `result` here
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_room_list(id: ClientId,
|
pub fn request_room_list(id: ClientId,
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::{From, Into, TryFrom, TryInto};
|
use std::convert::{From, Into, TryFrom, TryInto};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use crate::ship::map::Maps;
|
use crate::ship::map::Maps;
|
||||||
use crate::ship::drops::DropTable;
|
use crate::ship::drops::DropTable;
|
||||||
use crate::entity::character::SectionID;
|
use crate::entity::character::SectionID;
|
||||||
use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
|
use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
|
||||||
use crate::ship::map::area::MapAreaLookup;
|
use crate::ship::map::area::MapAreaLookup;
|
||||||
use thiserror::Error;
|
use crate::ship::map::enemy::RareMonsterAppearTable;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[error("")]
|
#[error("")]
|
||||||
@ -168,6 +170,7 @@ pub struct RoomState {
|
|||||||
pub bursting: bool,
|
pub bursting: bool,
|
||||||
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
|
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
|
||||||
pub map_areas: MapAreaLookup,
|
pub map_areas: MapAreaLookup,
|
||||||
|
pub rare_monster_table: Box<RareMonsterAppearTable>,
|
||||||
// items on ground
|
// items on ground
|
||||||
// enemy info
|
// enemy info
|
||||||
}
|
}
|
||||||
@ -232,13 +235,16 @@ impl RoomState {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode());
|
||||||
|
|
||||||
Ok(RoomState {
|
Ok(RoomState {
|
||||||
monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?),
|
monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?),
|
||||||
mode: room_mode,
|
mode: room_mode,
|
||||||
random_seed: rand::thread_rng().gen(),
|
random_seed: rand::thread_rng().gen(),
|
||||||
|
rare_monster_table: Box::new(rare_monster_table.clone()),
|
||||||
name: String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).into(),
|
name: String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).into(),
|
||||||
password: create_room.password,
|
password: create_room.password,
|
||||||
maps: Maps::new(room_mode),
|
maps: Maps::new(room_mode, &rare_monster_table), // TODO: rare_monster_table here feels janky. is there some way to call the the RoomState.rare_monster_table we already created?
|
||||||
section_id,
|
section_id,
|
||||||
drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)),
|
drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)),
|
||||||
bursting: false,
|
bursting: false,
|
||||||
|
@ -195,6 +195,7 @@ pub enum SendShipPacket {
|
|||||||
DoneLoadingQuest(DoneLoadingQuest),
|
DoneLoadingQuest(DoneLoadingQuest),
|
||||||
BankItemList(BankItemList),
|
BankItemList(BankItemList),
|
||||||
RedirectClient(RedirectClient),
|
RedirectClient(RedirectClient),
|
||||||
|
RareMonsterList(RareMonsterList),
|
||||||
AcknowledgeTrade(AcknowledgeTrade),
|
AcknowledgeTrade(AcknowledgeTrade),
|
||||||
CancelTrade(CancelTrade),
|
CancelTrade(CancelTrade),
|
||||||
TradeSuccessful(TradeSuccessful),
|
TradeSuccessful(TradeSuccessful),
|
||||||
@ -235,6 +236,7 @@ impl SendServerPacket for SendShipPacket {
|
|||||||
SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(),
|
SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(),
|
||||||
SendShipPacket::BankItemList(pkt) => pkt.as_bytes(),
|
SendShipPacket::BankItemList(pkt) => pkt.as_bytes(),
|
||||||
SendShipPacket::RedirectClient(pkt) => pkt.as_bytes(),
|
SendShipPacket::RedirectClient(pkt) => pkt.as_bytes(),
|
||||||
|
SendShipPacket::RareMonsterList(pkt) => pkt.as_bytes(),
|
||||||
SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(),
|
SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(),
|
||||||
SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(),
|
SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(),
|
||||||
SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(),
|
SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(),
|
||||||
@ -397,7 +399,6 @@ impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
|
|||||||
ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)),
|
ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)),
|
||||||
port: self.port.unwrap_or(SHIP_PORT),
|
port: self.port.unwrap_or(SHIP_PORT),
|
||||||
shops: Box::new(ItemShops::default()),
|
shops: Box::new(ItemShops::default()),
|
||||||
|
|
||||||
blocks: Blocks(blocks),
|
blocks: Blocks(blocks),
|
||||||
|
|
||||||
auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())),
|
auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())),
|
||||||
@ -408,7 +409,6 @@ impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
client_location: Box<ClientLocation>,
|
client_location: Box<ClientLocation>,
|
||||||
pub rooms: Box<Rooms>,
|
pub rooms: Box<Rooms>,
|
||||||
@ -623,7 +623,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
|
|||||||
},
|
},
|
||||||
RecvShipPacket::QuestMenuSelect(questmenuselect) => {
|
RecvShipPacket::QuestMenuSelect(questmenuselect) => {
|
||||||
let block = self.blocks.with_client(id, &self.clients)?;
|
let block = self.blocks.with_client(id, &self.clients)?;
|
||||||
handler::quest::load_quest(id, questmenuselect, &self.quests, &mut self.clients, &block.client_location, &mut block.rooms)?
|
handler::quest::player_chose_quest(id, questmenuselect, &self.quests, &mut self.clients, &block.client_location, &mut block.rooms)?
|
||||||
},
|
},
|
||||||
RecvShipPacket::MenuDetail(_menudetail) => {
|
RecvShipPacket::MenuDetail(_menudetail) => {
|
||||||
//unreachable!();
|
//unreachable!();
|
||||||
|
@ -95,3 +95,23 @@ async fn test_item_ids_reset_when_rejoining_rooms() {
|
|||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_load_rare_monster_default_appear_rates() {
|
||||||
|
let mut entity_gateway = InMemoryGateway::default();
|
||||||
|
let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||||
|
let mut ship = Box::new(ShipServerState::builder()
|
||||||
|
.gateway(entity_gateway.clone())
|
||||||
|
.build());
|
||||||
|
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
|
||||||
|
join_lobby(&mut ship, ClientId(1)).await;
|
||||||
|
create_room(&mut ship, ClientId(1), "room", "").await;
|
||||||
|
|
||||||
|
// assume episode 1
|
||||||
|
let room = ship.blocks.0[0].rooms[0].as_ref().unwrap();
|
||||||
|
println!("rare monster table: {:?}", room.rare_monster_table);
|
||||||
|
let rates = &*room.rare_monster_table;
|
||||||
|
for (_monster, rate) in rates.clone().appear_rate {
|
||||||
|
assert_eq!(rate, 0.001953125f32); // 1/512 = 0.001953125
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user