Browse Source

rare monsters get

pull/60/head
andy 4 years ago
parent
commit
e2d45f1599
  1. 71
      src/ship/map/enemy.rs
  2. 107
      src/ship/map/maps.rs
  3. 6
      src/ship/packet/builder/room.rs
  4. 19
      src/ship/packet/handler/room.rs
  5. 3
      src/ship/ship.rs

71
src/ship/map/enemy.rs

@ -37,7 +37,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>()?,
@ -80,21 +80,24 @@ 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> {
println!("enemy.rs::from_raw - {:?}", enemy);
// 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 +106,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 +128,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 +150,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 +219,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 +264,7 @@ impl MapEnemy {
dropped_item: false,
gave_exp: false,
player_hit: [false; 4],
shiny: false,
})
}
@ -269,6 +276,14 @@ impl MapEnemy {
dropped_item: false,
gave_exp: false,
player_hit: [false; 4],
shiny: false,
}
}
pub fn set_shiny(self) -> MapEnemy {
MapEnemy {
shiny: true,
..self
}
}
}

107
src/ship/map/maps.rs

@ -12,6 +12,8 @@ use crate::ship::room::{Episode, RoomMode};
// TODO: don't use *
use crate::ship::map::*;
use rand::{Rng};
pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> {
let mut object_data = Vec::new();
@ -30,19 +32,89 @@ 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>> {
let enemy = MapEnemy::from_raw(raw_enemy, episode, map_area);
/*
TODO: load rare monster rates config
*/
enemy
.map_or(vec![None], |monster| {
let mut monsters = vec![Some(monster)];
let mut monsters = Vec::new();
monsters.push(Some(monster));
match monster.monster {
MonsterType::Monest => {
for _ in 0..30 {
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area)));
// TODO: make real spawn rates
// TODO: specific ep 2 event rappies
MonsterType::RagRappy => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
match episode {
Episode::One => {monsters.push(Some(MapEnemy::new(MonsterType::AlRappy, monster.map_area).set_shiny()))},
Episode::Two => {monsters.push(Some(MapEnemy::new(MonsterType::EventRappy, monster.map_area).set_shiny()))},
_ => {unreachable!()},
}
},
}},
MonsterType::Hildebear => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::Hildeblue, monster.map_area).set_shiny()))
}},
MonsterType::PoisonLily => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::NarLily, monster.map_area).set_shiny()))
}},
// TODO: client increments by 5 for slimes instead of 4 segawhyyyyy?????
MonsterType::PofuillySlime => {
monsters.pop();
for _ in 0..5 {
if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.push(Some(MapEnemy::new(MonsterType::PouillySlime, monster.map_area).set_shiny()))
} else {
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)))
}
}
},
MonsterType::PouillySlime => {
// guaranteed rare slime already pushed
// roll for the other 3 copies
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.push(Some(MapEnemy::new(MonsterType::PouillySlime, monster.map_area).set_shiny()))
} else {
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)))
}
}
},
MonsterType::SandRappyCrater => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::DelRappyCrater, monster.map_area).set_shiny()))
}},
MonsterType::ZuCrater => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::PazuzuCrater, monster.map_area).set_shiny()))
}},
MonsterType::Dorphon => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::DorphonEclair, monster.map_area).set_shiny()))
}},
MonsterType::SandRappyDesert => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::DelRappyDesert, monster.map_area).set_shiny()))
}},
MonsterType::ZuDesert => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::PazuzuDesert, monster.map_area).set_shiny()))
}},
MonsterType::MerissaA => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::MerissaAA, monster.map_area).set_shiny()))
}},
MonsterType::SaintMillion | MonsterType::Shambertin => {if rand::thread_rng().gen_range(0, 100) < 11 {
monsters.pop();
monsters.push(Some(MapEnemy::new(MonsterType::Kondrieu, monster.map_area).set_shiny()))
}},
MonsterType::Monest => {
for _ in 0..30 {
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area)));
}
},
MonsterType::PanArms => {
@ -309,5 +381,26 @@ impl Maps {
self.enemy_data = enemies;
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 i in 0..shiny.len() {
if let Some(j) = rare_monsters.iter().position(|&x| x == 0xFFFF) {
rare_monsters[j] = shiny[i].0 as u16;
} else {
break
}
}
rare_monsters
}
}

6
src/ship/packet/builder/room.rs

@ -6,6 +6,7 @@ use crate::ship::location::{ClientLocation, RoomId, AreaClient, ClientLocationEr
use crate::ship::room::RoomState;
use crate::ship::items::ItemManager;
use crate::ship::packet::builder::{player_header, player_info};
use std::convert::TryInto;
pub fn join_room(id: ClientId,
clients: &Clients,
@ -71,3 +72,8 @@ pub fn add_to_room(_id: ClientId,
})
}
pub fn build_rare_monster_list(rare_monster_vec: Vec<u16>) -> Result<RareMonsterList, ShipError> {
Ok(RareMonsterList {
ids: rare_monster_vec.try_into().unwrap_or([0xFFFFu16; 16]),
})
}

19
src/ship/packet/handler/room.rs

@ -108,13 +108,17 @@ pub fn done_bursting(id: ClientId,
rooms: &mut Rooms)
-> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
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 Some(room) = rooms.get_mut(room_id.0).unwrap().as_mut() {
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
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| {
vec![
(client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone {
@ -122,7 +126,16 @@ pub fn done_bursting(id: ClientId,
target: 0
})))),
]
}).flatten())
})
.flatten()
);
if rare_monster_list.is_some() {
let rare_monster_packet = SendShipPacket::RareMonsterList(builder::room::build_rare_monster_list(rare_monster_list.unwrap()).unwrap()); // TODO: don't double unwrap
result = Box::new(result.chain(vec![(id, rare_monster_packet)]));
}
result
}
pub fn request_room_list(id: ClientId,

3
src/ship/ship.rs

@ -184,6 +184,7 @@ pub enum SendShipPacket {
DoneLoadingQuest(DoneLoadingQuest),
BankItemList(BankItemList),
RedirectClient(RedirectClient),
RareMonsterList(RareMonsterList),
}
impl SendServerPacket for SendShipPacket {
@ -221,6 +222,7 @@ impl SendServerPacket for SendShipPacket {
SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(),
SendShipPacket::BankItemList(pkt) => pkt.as_bytes(),
SendShipPacket::RedirectClient(pkt) => pkt.as_bytes(),
SendShipPacket::RareMonsterList(pkt) => pkt.as_bytes(),
}
}
}
@ -384,7 +386,6 @@ impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
}
}
pub struct Block {
client_location: Box<ClientLocation>,
pub rooms: Box<Rooms>,

Loading…
Cancel
Save