@ -1,5 +1,6 @@
// TOOD: `pub(super) for most of these?`
use std ::io ::{ Read } ;
use std ::collections ::HashMap ;
use byteorder ::{ LittleEndian , ReadBytesExt } ;
use thiserror ::Error ;
@ -9,6 +10,10 @@ use crate::ship::room::Episode;
use crate ::ship ::map ::* ;
use rand ::{ Rng , SeedableRng } ;
use serde ::{ Serialize , Deserialize } ;
use crate ::ship ::drops ::{ load_rare_monster_file } ;
#[ derive(Debug, Copy, Clone) ]
pub struct RawMapEnemy {
id : u32 ,
@ -37,7 +42,7 @@ pub struct RawMapEnemy {
impl RawMapEnemy {
pub fn from_byte_stream < R : Read > ( cursor : & mut R ) -> Result < RawMapEnemy , std ::io ::Error > {
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 > ( ) ? ,
children : cursor . read_u16 ::< LittleEndian > ( ) ? ,
_map_area : cursor . read_u16 ::< LittleEndian > ( ) ? ,
@ -70,6 +75,38 @@ pub enum MapEnemyError {
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.0 f32 ) {
return true
}
false
}
}
#[ derive(Debug, Copy, Clone) ]
pub struct MapEnemy {
@ -80,21 +117,23 @@ pub struct MapEnemy {
pub player_hit : [ bool ; 4 ] ,
pub dropped_item : bool ,
pub gave_exp : bool ,
pub shiny : bool ,
}
impl MapEnemy {
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: check what "skin" actually does. some unexpected enemies have many (panarms, slimes, lilys)
let monster = match map_area {
MapArea ::Forest1 | MapArea ::Forest2 | MapArea ::Dragon |
MapArea ::Caves1 | MapArea ::Caves2 | MapArea ::Caves3 | MapArea ::DeRolLe |
MapArea ::Mines1 | MapArea ::Mines2 | MapArea ::VolOpt |
MapArea ::Ruins1 | MapArea ::Ruins2 | MapArea ::Ruins3 | MapArea ::DarkFalz = > {
match ( enemy , episode ) {
( RawMapEnemy { id : 64 , . . } , _ ) = > MonsterType ::Hildebear ,
// (RawMapEnemy {id: 64, ..}, _) => MonsterType::Hildeblue,
( RawMapEnemy { id : 65 , . . } , _ ) = > MonsterType ::RagRappy ,
// (RawMapEnemy {id: 65, ..}, _) => MonsterType::AlRappy,
( RawMapEnemy { id : 64 , skin : 0 , . . } , _ ) = > MonsterType ::Hildebear ,
( RawMapEnemy { id : 64 , skin : 1 , . . } , _ ) = > MonsterType ::Hildeblue ,
( RawMapEnemy { id : 65 , skin : 0 , . . } , _ ) = > MonsterType ::RagRappy ,
( RawMapEnemy { id : 65 , skin : 1 , . . } , _ ) = > MonsterType ::AlRappy ,
( RawMapEnemy { id : 66 , . . } , _ ) = > MonsterType ::Monest ,
( RawMapEnemy { id : 67 , field2 : 0 , . . } , _ ) = > MonsterType ::SavageWolf ,
( RawMapEnemy { id : 67 , . . } , _ ) = > MonsterType ::BarbarousWolf ,
@ -103,13 +142,16 @@ impl MapEnemy {
( RawMapEnemy { id : 68 , skin : 2 , . . } , _ ) = > MonsterType ::Gigobooma ,
( RawMapEnemy { id : 96 , . . } , _ ) = > MonsterType ::GrassAssassin ,
( 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 : 99 , skin : 0 , . . } , _ ) = > MonsterType ::EvilShark ,
( RawMapEnemy { id : 99 , skin : 1 , . . } , _ ) = > MonsterType ::PalShark ,
( RawMapEnemy { id : 99 , skin : 2 , . . } , _ ) = > MonsterType ::GuilShark ,
( 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 : 128 , skin : 0 , . . } , _ ) = > MonsterType ::Dubchic ,
( RawMapEnemy { id : 128 , skin : 1 , . . } , _ ) = > MonsterType ::Gillchic ,
@ -122,6 +164,7 @@ impl MapEnemy {
( RawMapEnemy { id : 160 , . . } , _ ) = > MonsterType ::Delsaber ,
( RawMapEnemy { id : 161 , . . } , _ ) = > MonsterType ::ChaosSorcerer ,
( RawMapEnemy { id : 162 , . . } , _ ) = > MonsterType ::DarkGunner ,
( RawMapEnemy { id : 163 , . . } , _ ) = > MonsterType ::DeathGunner ,
( RawMapEnemy { id : 164 , . . } , _ ) = > MonsterType ::ChaosBringer ,
( RawMapEnemy { id : 165 , . . } , _ ) = > MonsterType ::DarkBelra ,
( 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 ::SeabedUpper | MapArea ::SeabedLower | MapArea ::OlgaFlow = > {
match ( enemy , episode ) {
( RawMapEnemy { id : 64 , . . } , _ ) = > MonsterType ::Hildebear ,
// (RawMapEnemy {id: 64, ..}, _) => MonsterType::Hildeblue,
( RawMapEnemy { id : 65 , . . } , _ ) = > MonsterType ::RagRappy ,
// (RawMapEnemy {id: 65, ..}, _) => MonsterType::EventRappy,
( RawMapEnemy { id : 64 , skin : 0 , . . } , _ ) = > MonsterType ::Hildebear ,
( RawMapEnemy { id : 64 , skin : 1 , . . } , _ ) = > MonsterType ::Hildeblue ,
( RawMapEnemy { id : 65 , skin : 0 , . . } , _ ) = > MonsterType ::RagRappy ,
( RawMapEnemy { id : 65 , skin : 1 , . . } , _ ) = > MonsterType ::EventRappy ,
( RawMapEnemy { id : 66 , . . } , _ ) = > MonsterType ::Monest ,
( RawMapEnemy { id : 67 , field2 : 0 , . . } , _ ) = > MonsterType ::SavageWolf ,
( RawMapEnemy { id : 67 , . . } , _ ) = > MonsterType ::BarbarousWolf ,
( RawMapEnemy { id : 96 , . . } , _ ) = > MonsterType ::GrassAssassin ,
( RawMapEnemy { id : 97 , . . } , _ ) = > MonsterType ::PoisonLily ,
// (RawMapEnemy {id: 97, ..}, _) => MonsterType::NarLily,
// (RawMapEnemy {id: 97, ..}, _) => MonsterType::DelLily,
( RawMapEnemy { id : 97 , skin : 0 , . . } , _ ) = > MonsterType ::PoisonLily ,
( RawMapEnemy { id : 97 , skin : 1 , . . } , _ ) = > MonsterType ::NarLily ,
( RawMapEnemy { id : 101 , . . } , _ ) = > MonsterType ::PanArms ,
( RawMapEnemy { id : 128 , skin : 0 , . . } , _ ) = > MonsterType ::Dubchic ,
( RawMapEnemy { id : 128 , skin : 1 , . . } , _ ) = > MonsterType ::Gillchic ,
@ -213,38 +255,38 @@ impl MapEnemy {
MapArea ::CraterEast | MapArea ::CraterWest | MapArea ::CraterSouth | MapArea ::CraterNorth | MapArea ::CraterInterior = > {
match ( enemy , episode ) {
( RawMapEnemy { id : 65 , . . } , Episode ::Four ) = > MonsterType ::SandRappyCrater ,
// (RawMapEnemy {id: 65, ..}, Episode::Four) => MonsterType::DelRappyCrater,
( RawMapEnemy { id : 65 , skin : 0 , . . } , Episode ::Four ) = > MonsterType ::SandRappyCrater ,
( RawMapEnemy { id : 65 , skin : 1 , . . } , Episode ::Four ) = > MonsterType ::DelRappyCrater ,
( RawMapEnemy { id : 272 , . . } , _ ) = > MonsterType ::Astark ,
( RawMapEnemy { id : 273 , field2 : 0 , . . } , _ ) = > MonsterType ::SatelliteLizardCrater ,
( RawMapEnemy { id : 273 , . . } , _ ) = > MonsterType ::YowieCrater ,
( RawMapEnemy { id : 276 , . . } , _ ) = > MonsterType ::ZuCrater ,
// (RawMapEnemy {id: 276, ..}, _) => MonsterType::PazuzuCrater,
( RawMapEnemy { id : 276 , skin : 0 , . . } , _ ) = > MonsterType ::ZuCrater ,
( RawMapEnemy { id : 276 , skin : 1 , . . } , _ ) = > MonsterType ::PazuzuCrater ,
( RawMapEnemy { id : 277 , skin : 0 , . . } , _ ) = > MonsterType ::Boota ,
( RawMapEnemy { id : 277 , skin : 1 , . . } , _ ) = > MonsterType ::ZeBoota ,
( RawMapEnemy { id : 277 , skin : 2 , . . } , _ ) = > MonsterType ::BaBoota ,
( RawMapEnemy { id : 278 , . . } , _ ) = > MonsterType ::Dorphon ,
// (RawMapEnemy {id: 278, ..}, _) => MonsterType::DorphonEclair,
( RawMapEnemy { id : 278 , skin : 0 , . . } , _ ) = > MonsterType ::Dorphon ,
( RawMapEnemy { id : 278 , skin : 1 , . . } , _ ) = > MonsterType ::DorphonEclair ,
_ = > return Err ( MapEnemyError ::UnknownEnemyId ( enemy . id ) )
}
} ,
MapArea ::SubDesert1 | MapArea ::SubDesert2 | MapArea ::SubDesert3 | MapArea ::SaintMillion = > {
match ( enemy , episode ) {
( RawMapEnemy { id : 65 , . . } , Episode ::Four ) = > MonsterType ::SandRappyDesert ,
// (RawMapEnemy {id: 65, ..}, Episode::Four) => MonsterType::DelRappyDesert,
( RawMapEnemy { id : 65 , skin : 0 , . . } , Episode ::Four ) = > MonsterType ::SandRappyDesert ,
( RawMapEnemy { id : 65 , skin : 1 , . . } , Episode ::Four ) = > MonsterType ::DelRappyDesert ,
( RawMapEnemy { id : 273 , field2 : 0 , . . } , _ ) = > MonsterType ::SatelliteLizardDesert ,
( RawMapEnemy { id : 273 , . . } , _ ) = > MonsterType ::YowieDesert ,
( RawMapEnemy { id : 274 , . . } , _ ) = > MonsterType ::MerissaA ,
// (RawMapEnemy {id: 274, ..}, _) => MonsterType::MerissaAA,
( RawMapEnemy { id : 274 , skin : 0 , . . } , _ ) = > MonsterType ::MerissaA ,
( RawMapEnemy { id : 274 , skin : 1 , . . } , _ ) = > MonsterType ::MerissaAA ,
( RawMapEnemy { id : 275 , . . } , _ ) = > MonsterType ::Girtablulu ,
( RawMapEnemy { id : 276 , . . } , _ ) = > MonsterType ::ZuDesert ,
// (RawMapEnemy {id: 276, ..}, _) => MonsterType::PazuzuDesert,
( RawMapEnemy { id : 276 , skin : 0 , . . } , _ ) = > MonsterType ::ZuDesert ,
( RawMapEnemy { id : 276 , skin : 1 , . . } , _ ) = > MonsterType ::PazuzuDesert ,
( RawMapEnemy { id : 279 , skin : 0 , . . } , _ ) = > MonsterType ::Goran ,
( RawMapEnemy { id : 279 , skin : 1 , . . } , _ ) = > MonsterType ::PyroGoran ,
( RawMapEnemy { id : 279 , skin : 2 , . . } , _ ) = > MonsterType ::GoranDetonator ,
( RawMapEnemy { id : 281 , skin : 0 , . . } , _ ) = > MonsterType ::SaintMillion ,
( RawMapEnemy { id : 281 , skin : 1 , . . } , _ ) = > MonsterType ::Shambertin ,
// (RawMapEnemy {id: 281, skin: 1, ..}, _) => MonsterType::Kondrieu,
( RawMapEnemy { id : 281 , skin : 1 , . . } , _ ) = > MonsterType ::Shambertin , // TODO: don't guess the skin
( RawMapEnemy { id : 281 , skin : 2 , . . } , _ ) = > MonsterType ::Kondrieu , // TODO: don't guess the skin
_ = > return Err ( MapEnemyError ::UnknownEnemyId ( enemy . id ) )
}
} ,
@ -258,6 +300,7 @@ impl MapEnemy {
dropped_item : false ,
gave_exp : false ,
player_hit : [ false ; 4 ] ,
shiny : false ,
} )
}
@ -269,7 +312,56 @@ impl MapEnemy {
dropped_item : false ,
gave_exp : false ,
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
}
}