Browse Source

Merge pull request 'item drops' (#112) from itemdropsforreal into master

pbs
jake 5 years ago
parent
commit
c28adcd7e5
  1. 25
      src/entity/character.rs
  2. 10
      src/entity/item/mod.rs
  3. 34
      src/entity/item/tech.rs
  4. 2
      src/entity/item/tool.rs
  5. 26
      src/ship/drops/mod.rs
  6. 10
      src/ship/drops/tech_table.rs
  7. 66
      src/ship/items.rs
  8. 20
      src/ship/location.rs
  9. 107
      src/ship/map.rs
  10. 33
      src/ship/packet/builder/message.rs
  11. 1
      src/ship/packet/builder/mod.rs
  12. 62
      src/ship/packet/handler/direct_message.rs
  13. 2
      src/ship/packet/handler/message.rs
  14. 10
      src/ship/ship.rs

25
src/entity/character.rs

@ -153,29 +153,8 @@ impl CharacterTechniques {
pub fn as_bytes(&self) -> [u8; 20] { pub fn as_bytes(&self) -> [u8; 20] {
self.techs.iter() self.techs.iter()
.fold([0xFF; 20], |mut techlist, (tech, level)| { .fold([0xFF; 20], |mut techlist, (tech, level)| {
let index = match tech {
Technique::Foie => 0,
Technique::Gifoie => 1,
Technique::Rafoie => 2,
Technique::Barta => 3,
Technique::Gibarta => 4,
Technique::Rabarta => 5,
Technique::Zonde => 6,
Technique::Gizonde => 7,
Technique::Razonde => 8,
Technique::Grants => 9,
Technique::Deband => 10,
Technique::Jellen => 11,
Technique::Zalure => 12,
Technique::Shifta => 13,
Technique::Ryuker => 14,
Technique::Resta => 15,
Technique::Anti => 16,
Technique::Reverser => 17,
Technique::Megid => 18,
};
techlist[index] = level.0 - 1;
let index = tech.as_value();
techlist[index as usize] = level.0 - 1;
techlist techlist
}) })
} }

10
src/entity/item/mod.rs

@ -7,6 +7,7 @@ pub mod unit;
pub mod mag; pub mod mag;
use crate::entity::character::CharacterEntityId; use crate::entity::character::CharacterEntityId;
use crate::ship::map::MapArea;
#[derive(PartialEq, Copy, Clone, Debug, Hash, Eq)] #[derive(PartialEq, Copy, Clone, Debug, Hash, Eq)]
pub struct ItemEntityId(pub u32); pub struct ItemEntityId(pub u32);
@ -27,8 +28,10 @@ pub enum ItemLocation {
slot: BankName, slot: BankName,
}, },
Floor { Floor {
// floor: eventually
// x y z: ?????
map_area: MapArea,
x: f32,
y: f32,
z: f32,
}, },
/*Destroyed { /*Destroyed {
// marks an item that has been consumed in some way // marks an item that has been consumed in some way
@ -46,6 +49,7 @@ pub struct Meseta(pub u32);
impl Meseta { impl Meseta {
pub fn as_bytes(&self) -> [u8; 16] { pub fn as_bytes(&self) -> [u8; 16] {
let mut result = [0; 16]; let mut result = [0; 16];
result[0] = 4;
result[12..16].copy_from_slice(&u32::to_le_bytes(self.0)); result[12..16].copy_from_slice(&u32::to_le_bytes(self.0));
result result
} }
@ -96,7 +100,7 @@ impl ItemDetail {
} }
} }
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct NewItemEntity { pub struct NewItemEntity {
pub location: ItemLocation, pub location: ItemLocation,
pub item: ItemDetail, pub item: ItemDetail,

34
src/entity/item/tech.rs

@ -24,8 +24,33 @@ pub enum Technique {
Megid, Megid,
} }
impl Technique {
pub fn as_value(&self) -> u8 {
match self {
Technique::Foie => 0,
Technique::Gifoie => 1,
Technique::Rafoie => 2,
Technique::Barta => 3,
Technique::Gibarta => 4,
Technique::Rabarta => 5,
Technique::Zonde => 6,
Technique::Gizonde => 7,
Technique::Razonde => 8,
Technique::Grants => 9,
Technique::Deband => 10,
Technique::Jellen => 11,
Technique::Zalure => 12,
Technique::Shifta => 13,
Technique::Ryuker => 14,
Technique::Resta => 15,
Technique::Anti => 16,
Technique::Reverser => 17,
Technique::Megid => 18,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TechniqueDisk { pub struct TechniqueDisk {
pub tech: Technique, pub tech: Technique,
pub level: u32, pub level: u32,
@ -33,6 +58,11 @@ pub struct TechniqueDisk {
impl TechniqueDisk { impl TechniqueDisk {
pub fn as_bytes(&self) -> [u8; 16] { pub fn as_bytes(&self) -> [u8; 16] {
[0; 16]
let mut result = [0; 16];
result[0] = 3;
result[1] = 2;
result[2] = self.level as u8 - 1;
result[4] = self.tech.as_value();
result
} }
} }

2
src/entity/item/tool.rs

@ -394,7 +394,7 @@ impl ToolType {
} }
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Tool { pub struct Tool {
pub tool: ToolType, pub tool: ToolType,
} }

26
src/ship/drops/mod.rs

@ -1,3 +1,9 @@
// TODO: there is some structure duplication that occurs here:
// the rare and box tables instantiate their own copies of the
// generic drop tables as they need them to apply their modifiers
// to their drops
mod drop_table; mod drop_table;
mod rare_drop_table; mod rare_drop_table;
mod generic_weapon; mod generic_weapon;
@ -78,25 +84,20 @@ pub enum ItemDropType {
Shield(shield::Shield), Shield(shield::Shield),
Unit(unit::Unit), Unit(unit::Unit),
Tool(tool::Tool), Tool(tool::Tool),
//Tools(Vec<tool::Tool>),
TechniqueDisk(tech::TechniqueDisk), TechniqueDisk(tech::TechniqueDisk),
Mag(mag::Mag), Mag(mag::Mag),
Meseta(u32), Meseta(u32),
} }
struct ItemDrop {
x: f32,
y: f32,
z: f32,
item: ItemDropType,
pub struct ItemDrop {
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
pub item: ItemDropType,
} }
impl ItemDrop {
pub fn as_client_bytes(&self) -> u8 {
0
}
}
pub struct DropTable<R: Rng + SeedableRng> { pub struct DropTable<R: Rng + SeedableRng> {
monster_stats: HashMap<MonsterType, MonsterDropStats>, monster_stats: HashMap<MonsterType, MonsterDropStats>,
@ -110,7 +111,6 @@ pub struct DropTable<R: Rng + SeedableRng> {
rng: R, rng: R,
} }
impl<R: Rng + SeedableRng> DropTable<R> { impl<R: Rng + SeedableRng> DropTable<R> {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> { pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> {
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml"); let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");

10
src/ship/drops/tech_table.rs

@ -100,7 +100,7 @@ impl TechniqueTable {
let tech_weights = WeightedIndex::new(tech_rates.clone().map(|(_, stat)| stat.rate)).unwrap(); let tech_weights = WeightedIndex::new(tech_rates.clone().map(|(_, stat)| stat.rate)).unwrap();
let (tech, stat) = tech_rates.nth(tech_weights.sample(rng)).unwrap(); let (tech, stat) = tech_rates.nth(tech_weights.sample(rng)).unwrap();
let level = rng.gen_range(stat.min, stat.max+1);
let level = rng.gen_range(stat.min, stat.max+1) + 1;
Some(ItemDropType::TechniqueDisk(TechniqueDisk { Some(ItemDropType::TechniqueDisk(TechniqueDisk {
tech: *tech, tech: *tech,
@ -118,10 +118,10 @@ mod test {
let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]);
let tt = TechniqueTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); let tt = TechniqueTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly);
let tech_tests = vec![(MapArea::Forest1, Technique::Resta, 13),
(MapArea::Caves3, Technique::Foie, 24),
(MapArea::Mines2, Technique::Gibarta, 20),
(MapArea::DarkFalz, Technique::Razonde, 22)];
let tech_tests = vec![(MapArea::Forest1, Technique::Resta, 14),
(MapArea::Caves3, Technique::Foie, 25),
(MapArea::Mines2, Technique::Gibarta, 21),
(MapArea::DarkFalz, Technique::Razonde, 23)];
for (area, tech, level) in tech_tests { for (area, tech, level) in tech_tests {
assert!(tt.get_drop(&area, &mut rng) == Some(ItemDropType::TechniqueDisk( assert!(tt.get_drop(&area, &mut rng) == Some(ItemDropType::TechniqueDisk(

66
src/ship/items.rs

@ -11,23 +11,26 @@ use crate::entity::item::shield::Shield;
use crate::entity::item::unit::Unit; use crate::entity::item::unit::Unit;
use crate::entity::item::tool::Tool; use crate::entity::item::tool::Tool;
use crate::entity::item::mag::Mag; use crate::entity::item::mag::Mag;
use crate::entity::item::Meseta;
use crate::entity::item::{Meseta, NewItemEntity};
use crate::ship::map::MapArea;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::ship::ShipError;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ItemInstance {
enum ItemInstance {
Individual(ItemEntity), Individual(ItemEntity),
Stacked(Vec<ItemEntity>), Stacked(Vec<ItemEntity>),
Meseta(Meseta), Meseta(Meseta),
} }
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ActiveItemId(u32);
pub struct ActiveItemId(pub u32);
#[derive(Debug)] #[derive(Debug)]
pub struct ActiveItem { pub struct ActiveItem {
id: ActiveItemId,
pub id: ActiveItemId,
item: ItemInstance, item: ItemInstance,
} }
@ -108,6 +111,14 @@ fn inventory_item_index(item: &ItemInstance) -> usize {
} }
} }
pub struct ActiveItemOnFloor {
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
pub item: ActiveItem,
}
fn stack_items(items: Vec<ItemEntity>) -> Vec<ItemInstance> { fn stack_items(items: Vec<ItemEntity>) -> Vec<ItemInstance> {
let mut stacks = HashMap::new(); let mut stacks = HashMap::new();
@ -138,8 +149,6 @@ pub struct ActiveItemDatabase {
id: u32, id: u32,
} }
impl ActiveItemDatabase { impl ActiveItemDatabase {
pub fn new() -> ActiveItemDatabase { pub fn new() -> ActiveItemDatabase {
ActiveItemDatabase { ActiveItemDatabase {
@ -155,7 +164,7 @@ impl ActiveItemDatabase {
} }
} }
// deactivate item
// TODO: deactivate item
pub fn get_character_inventory<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> ActiveInventory { pub fn get_character_inventory<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> ActiveInventory {
let items = entity_gateway.get_items_by_character(&character); let items = entity_gateway.get_items_by_character(&character);
@ -173,6 +182,49 @@ impl ActiveItemDatabase {
let activated = stacked.into_iter().map(|i| self.activate_item(i)); let activated = stacked.into_iter().map(|i| self.activate_item(i));
ActiveInventory(activated.take(30).collect()) ActiveInventory(activated.take(30).collect())
} }
pub fn activate_item_drop<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, item_drop: ItemDrop) -> Result<ActiveItemOnFloor, ShipError> {
let item_detail = match item_drop.item {
ItemDropType::Weapon(w) => Some(ItemDetail::Weapon(w)),
ItemDropType::Armor(w) => Some(ItemDetail::Armor(w)),
ItemDropType::Shield(w) => Some(ItemDetail::Shield(w)),
ItemDropType::Unit(w) => Some(ItemDetail::Unit(w)),
ItemDropType::Tool(w) => Some(ItemDetail::Tool(w)),
ItemDropType::TechniqueDisk(w) => Some(ItemDetail::TechniqueDisk(w)),
ItemDropType::Mag(w) => Some(ItemDetail::Mag(w)),
ItemDropType::Meseta(_) => None
};
let item_instance = match item_detail {
Some(item) => {
let item_entity = entity_gateway.create_item(NewItemEntity {
item: item,
location: ItemLocation::Floor {
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}
}).unwrap();
stack_items(vec![item_entity]).pop().ok_or(ShipError::ItemError)?
},
None => {
let meseta = match item_drop.item {
ItemDropType::Meseta(m) => m,
_ => panic!(),
};
ItemInstance::Meseta(Meseta(meseta))
}
};
let active_item = self.activate_item(item_instance);
Ok(ActiveItemOnFloor {
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
item: active_item,
})
}
} }
#[cfg(test)] #[cfg(test)]

20
src/ship/location.rs

@ -52,6 +52,8 @@ pub enum JoinLobbyError {
#[derive(Error, Debug, PartialEq)] #[derive(Error, Debug, PartialEq)]
#[error("")] #[error("")]
pub enum GetAreaError { pub enum GetAreaError {
NotInRoom,
NotInLobby,
InvalidClient, InvalidClient,
} }
@ -331,6 +333,24 @@ impl ClientLocation {
.map(Clone::clone) .map(Clone::clone)
} }
pub fn get_room(&self, id: ClientId) -> Result<RoomId, GetAreaError> {
if let RoomLobby::Room(room) = self.client_location.get(&id).ok_or(GetAreaError::InvalidClient)? {
Ok(*room)
}
else {
Err(GetAreaError::NotInRoom)
}
}
pub fn get_lobby(&self, id: ClientId) -> Result<LobbyId, GetAreaError> {
if let RoomLobby::Lobby(lobby) = self.client_location.get(&id).ok_or(GetAreaError::InvalidClient)? {
Ok(*lobby)
}
else {
Err(GetAreaError::NotInLobby)
}
}
pub fn remove_client_from_area(&mut self, id: ClientId) -> Result<(), ClientRemovalError> { pub fn remove_client_from_area(&mut self, id: ClientId) -> Result<(), ClientRemovalError> {
let area = self.client_location.get_mut(&id).ok_or(ClientRemovalError::ClientNotInArea)?; let area = self.client_location.get_mut(&id).ok_or(ClientRemovalError::ClientNotInArea)?;
let client_list = match area { let client_list = match area {

107
src/ship/map.rs

@ -7,6 +7,7 @@ use std::fs::File;
use byteorder::{LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
use rand::Rng; use rand::Rng;
use thiserror::Error;
use crate::ship::monster::MonsterType; use crate::ship::monster::MonsterType;
use crate::ship::room::Episode; use crate::ship::room::Episode;
@ -16,7 +17,7 @@ struct RawMapEnemy {
id: u32, id: u32,
_unknown1: u16, _unknown1: u16,
children: u16, children: u16,
_unknown3: u16,
map_area: u16,
_unknown4: u16, _unknown4: u16,
section: u16, section: u16,
wave_idd: u16, wave_idd: u16,
@ -42,7 +43,7 @@ impl RawMapEnemy {
id: cursor.read_u32::<LittleEndian>()?, id: cursor.read_u32::<LittleEndian>()?,
_unknown1: cursor.read_u16::<LittleEndian>()?, _unknown1: cursor.read_u16::<LittleEndian>()?,
children: cursor.read_u16::<LittleEndian>()?, children: cursor.read_u16::<LittleEndian>()?,
_unknown3: cursor.read_u16::<LittleEndian>()?,
map_area: cursor.read_u16::<LittleEndian>()?,
_unknown4: cursor.read_u16::<LittleEndian>()?, _unknown4: cursor.read_u16::<LittleEndian>()?,
section: cursor.read_u16::<LittleEndian>()?, section: cursor.read_u16::<LittleEndian>()?,
wave_idd: cursor.read_u16::<LittleEndian>()?, wave_idd: cursor.read_u16::<LittleEndian>()?,
@ -65,18 +66,22 @@ impl RawMapEnemy {
} }
#[derive(Debug)]
#[derive(Error, Debug)]
#[error("")]
enum MapEnemyError { enum MapEnemyError {
UnknownEnemyId(u32), UnknownEnemyId(u32),
MapAreaError(#[from] MapAreaError),
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct MapEnemy { pub struct MapEnemy {
pub monster: MonsterType, pub monster: MonsterType,
pub map_area: MapArea,
hp: u32, hp: u32,
// other stats from bp.n
dead: bool,
// TODO: other stats from battleparam
pub dropped_item: bool,
pub gave_exp: bool,
} }
impl MapEnemy { impl MapEnemy {
@ -169,16 +174,20 @@ impl MapEnemy {
Ok(MapEnemy { Ok(MapEnemy {
monster: monster, monster: monster,
map_area: MapArea::from_value(&episode, enemy.map_area as u32)?,
hp: 0, hp: 0,
dead: false,
dropped_item: false,
gave_exp: false,
}) })
} }
fn new(monster: MonsterType) -> MapEnemy {
fn new(monster: MonsterType, map_area: MapArea) -> MapEnemy {
MapEnemy { MapEnemy {
monster: monster, monster: monster,
map_area: map_area,
hp: 0, hp: 0,
dead: false,
dropped_item: false,
gave_exp: false,
} }
} }
} }
@ -340,7 +349,7 @@ enum MapVariantMode {
Offline, Offline,
} }
#[derive(Debug)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum MapArea { pub enum MapArea {
Pioneer2Ep1, Pioneer2Ep1,
Forest1, Forest1,
@ -359,12 +368,14 @@ pub enum MapArea {
DarkFalz, DarkFalz,
} }
#[derive(Error, Debug)]
#[error("")]
pub enum MapAreaError { pub enum MapAreaError {
UnknownMapArea(u32), UnknownMapArea(u32),
} }
impl MapArea { impl MapArea {
pub fn from_value(episode: Episode, area: u32) -> Result<MapArea, MapAreaError> {
pub fn from_value(episode: &Episode, area: u32) -> Result<MapArea, MapAreaError> {
match (episode, area) { match (episode, area) {
(Episode::One, 0) => Ok(MapArea::Pioneer2Ep1), (Episode::One, 0) => Ok(MapArea::Pioneer2Ep1),
(Episode::One, 1) => Ok(MapArea::Forest1), (Episode::One, 1) => Ok(MapArea::Forest1),
@ -404,6 +415,26 @@ impl MapArea {
_ => None _ => None
} }
} }
pub fn area_value(&self) -> u8 {
match self {
MapArea::Pioneer2Ep1 => 0,
MapArea::Forest1 => 1,
MapArea::Forest2 => 2,
MapArea::Caves1 => 3,
MapArea::Caves2 => 4,
MapArea::Caves3 => 5,
MapArea::Mines1 => 6,
MapArea::Mines2 => 7,
MapArea::Ruins1 => 8,
MapArea::Ruins2 => 9,
MapArea::Ruins3 => 10,
MapArea::Dragon => 11,
MapArea::DeRolLe => 12,
MapArea::VolOpt => 13,
MapArea::DarkFalz => 14,
}
}
} }
@ -516,76 +547,76 @@ fn enemy_data_from_map_data(path: PathBuf, episode: &Episode) -> Vec<Option<MapE
match monster.monster { match monster.monster {
MonsterType::Monest => { MonsterType::Monest => {
for _ in 0..30 { for _ in 0..30 {
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant)));
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area)));
} }
}, },
MonsterType::PofuillySlime => { MonsterType::PofuillySlime => {
for _ in 0..4 { for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime)));
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
} }
}, },
MonsterType::PanArms => { MonsterType::PanArms => {
monsters.push(Some(MapEnemy::new(MonsterType::Hidoom)));
monsters.push(Some(MapEnemy::new(MonsterType::Migium)));
monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area)));
}, },
MonsterType::SinowBeat => { MonsterType::SinowBeat => {
for _ in 0..4 { for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat)));
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area)));
} }
}, },
MonsterType::SinowGold => { MonsterType::SinowGold => {
for _ in 0..4 { for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::SinowGold)));
monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area)));
} }
}, },
MonsterType::Canane => { MonsterType::Canane => {
for _ in 0..8 { for _ in 0..8 {
monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine)));
monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area)));
} }
}, },
MonsterType::ChaosSorcerer => { MonsterType::ChaosSorcerer => {
monsters.push(Some(MapEnemy::new(MonsterType::BeeR)));
monsters.push(Some(MapEnemy::new(MonsterType::BeeL)));
monsters.push(Some(MapEnemy::new(MonsterType::BeeR, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::BeeL, monster.map_area)));
}, },
MonsterType::Bulclaw => { MonsterType::Bulclaw => {
for _ in 0..4 { for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::Claw)));
monsters.push(Some(MapEnemy::new(MonsterType::Claw, monster.map_area)));
} }
}, },
MonsterType::DeRolLe => { MonsterType::DeRolLe => {
for _ in 0..10 { for _ in 0..10 {
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody)));
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area)));
} }
for _ in 0..9 { for _ in 0..9 {
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine)));
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area)));
} }
}, },
MonsterType::VolOptPartA => { MonsterType::VolOptPartA => {
for _ in 0..6 { for _ in 0..6 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area)));
} }
for _ in 0..24 { for _ in 0..24 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area)));
} }
for _ in 0..2 { for _ in 0..2 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
} }
monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
}, },
// TOOD: this cares about difficulty (theres an ult-specific darvant?) // TOOD: this cares about difficulty (theres an ult-specific darvant?)
MonsterType::DarkFalz => { MonsterType::DarkFalz => {
for _ in 0..509 { for _ in 0..509 {
monsters.push(Some(MapEnemy::new(MonsterType::Darvant)));
monsters.push(Some(MapEnemy::new(MonsterType::Darvant, monster.map_area)));
} }
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1, monster.map_area)));
}, },
_ => { _ => {
for _ in 0..enemy.children { for _ in 0..enemy.children {
monsters.push(Some(MapEnemy::new(monster.monster)));
monsters.push(Some(MapEnemy::new(monster.monster, monster.map_area)));
} }
} }
} }
@ -596,6 +627,12 @@ fn enemy_data_from_map_data(path: PathBuf, episode: &Episode) -> Vec<Option<MapE
} }
#[derive(Error, Debug)]
#[error("")]
pub enum MapsError {
InvalidMonsterId(usize),
}
#[derive(Debug)] #[derive(Debug)]
pub struct Maps { pub struct Maps {
map_variants: [MapVariant; 15], map_variants: [MapVariant; 15],
@ -641,8 +678,8 @@ impl Maps {
maps maps
} }
pub fn enemy_by_id(&self, id: usize) -> MapEnemy {
self.enemy_data[id].unwrap()
pub fn enemy_by_id(&self, id: usize) -> Result<MapEnemy, MapsError> {
self.enemy_data[id].ok_or(MapsError::InvalidMonsterId(id))
} }
pub fn map_headers(&self) -> [u32; 0x20] { pub fn map_headers(&self) -> [u32; 0x20] {

33
src/ship/packet/builder/message.rs

@ -0,0 +1,33 @@
use std::collections::HashMap;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
use crate::common::serverstate::ClientId;
use crate::common::leveltable::CharacterLevelTable;
use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients};
use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder};
use crate::ship::location::{ClientLocation, LobbyId, AreaClient, ClientLocationError};
use crate::entity::character::CharacterEntity;
use crate::ship::items::{ActiveInventory, ActiveItemOnFloor};
use crate::ship::packet::builder::{player_header, player_info};
use std::convert::TryInto;
use libpso::character::character::{Inventory, InventoryItem};
use libpso::utf8_to_utf16_array;
pub fn item_drop(client: u8, target: u8, item_drop: &ActiveItemOnFloor) -> Result<ItemDrop, ShipError> {
let item_bytes = item_drop.item.as_client_bytes();
Ok(ItemDrop {
client: client,
target: target,
area: item_drop.map_area.area_value(),
variety: 0,
unknown: 0,
x: item_drop.x,
z: item_drop.z,
y: item_drop.y,
item_bytes: item_bytes[0..12].try_into()?,
item_id: item_drop.item.id.0,
item_bytes2: item_bytes[12..16].try_into()?,
unknown2: 0,
})
}

1
src/ship/packet/builder/mod.rs

@ -1,4 +1,5 @@
pub mod lobby; pub mod lobby;
pub mod message;
pub mod room; pub mod room;
use libpso::character::character::Inventory; use libpso::character::character::Inventory;

62
src/ship/packet/handler/direct_message.rs

@ -6,10 +6,14 @@ use crate::common::serverstate::ClientId;
use crate::common::leveltable::CharacterLevelTable; use crate::common::leveltable::CharacterLevelTable;
use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients, Rooms}; use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients, Rooms};
use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder}; use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder};
use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOMS};
use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOMS, ClientLocationError};
use crate::ship::room::RoomState;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::items::ActiveItemDatabase;
use libpso::character::character; use libpso::character::character;
use crate::entity::gateway::EntityGateway; use crate::entity::gateway::EntityGateway;
use libpso::{utf8_to_array, utf8_to_utf16_array}; use libpso::{utf8_to_array, utf8_to_utf16_array};
use crate::ship::packet::builder;
fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation) fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation)
-> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> { -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
@ -20,8 +24,6 @@ fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location:
})) }))
} }
pub fn guildcard_send(id: ClientId, pub fn guildcard_send(id: ClientId,
guildcard_send: &GuildcardSend, guildcard_send: &GuildcardSend,
target: u32, target: u32,
@ -46,3 +48,57 @@ pub fn guildcard_send(id: ClientId,
}; };
send_to_client(id, target as u8, msg, &client_location) send_to_client(id, target as u8, msg, &client_location)
} }
pub fn request_item<EG>(id: ClientId,
request_item: &RequestItem,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
rooms: &mut Rooms,
active_items: &mut ActiveItemDatabase)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError>
where EG: EntityGateway {
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let mut 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))?;
let monster = room.maps.enemy_by_id(request_item.enemy_id as usize)?;
if monster.dropped_item {
return Err(ShipError::MonsterAlreadyDroppedItem(id, request_item.enemy_id))
}
let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
let item_drop_packets = clients_in_area.into_iter()
.filter_map(|area_client| {
room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| {
warn!("drop is? {:?}", item_drop_type);
(area_client, item_drop_type)
})
})
.map(|(area_client, item_drop_type)| -> Result<_, ShipError> {
let item_drop = ItemDrop {
map_area: monster.map_area,
x: request_item.x,
y: request_item.y,
z: request_item.z,
item: item_drop_type,
};
let activated_item = active_items.activate_item_drop(entity_gateway, item_drop)?;
let mut client = clients.get_mut(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client))?;
let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, &activated_item)?;
client.floor_items.push(activated_item);
Ok((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg)))))
})
.filter_map(|item_drop_pkt| {
// TODO: log errors here
item_drop_pkt.ok()
})
.collect::<Vec<_>>(); // TODO: can EntityGateway be Sync?
Ok(Box::new(item_drop_packets.into_iter()))
}

2
src/ship/packet/handler/message.rs

@ -19,7 +19,7 @@ pub fn request_exp(id: ClientId,
match client_location.get_area(id).unwrap() { match client_location.get_area(id).unwrap() {
RoomLobby::Room(room) => { RoomLobby::Room(room) => {
let r = rooms[room.0].as_ref().unwrap(); let r = rooms[room.0].as_ref().unwrap();
warn!("killed a {:?}", r.maps.enemy_by_id(request_exp.enemy_id as usize).monster);
warn!("killed a {:?}", r.maps.enemy_by_id(request_exp.enemy_id as usize).unwrap().monster);
}, },
_ => {} _ => {}
}; };

10
src/ship/ship.rs

@ -25,6 +25,7 @@ use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOM
use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder}; use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder};
use crate::ship::items; use crate::ship::items;
use crate::ship::room; use crate::ship::room;
use crate::ship::map::MapsError;
use crate::ship::packet::handler; use crate::ship::packet::handler;
pub const SHIP_PORT: u16 = 23423; pub const SHIP_PORT: u16 = 23423;
@ -39,7 +40,11 @@ pub enum ShipError {
InvalidSlot(ClientId, u32), InvalidSlot(ClientId, u32),
TooManyClients, TooManyClients,
ClientLocationError(#[from] ClientLocationError), ClientLocationError(#[from] ClientLocationError),
MapsError(#[from] MapsError),
InvalidRoom(u32), InvalidRoom(u32),
MonsterAlreadyDroppedItem(ClientId, u16),
SliceError(#[from] std::array::TryFromSliceError),
ItemError, // TODO: refine this
} }
#[derive(Debug)] #[derive(Debug)]
@ -145,6 +150,7 @@ pub struct ClientState {
//guildcard: GuildCard, //guildcard: GuildCard,
pub inventory: items::ActiveInventory, pub inventory: items::ActiveInventory,
//bank: Bank, //bank: Bank,
pub floor_items: Vec<items::ActiveItemOnFloor>,
pub block: u32, pub block: u32,
} }
@ -157,6 +163,7 @@ impl ClientState {
session: session, session: session,
inventory: inventory, inventory: inventory,
//bank: bank, //bank: bank,
floor_items: Vec::new(),
block: 1, block: 1,
} }
} }
@ -207,6 +214,9 @@ impl<EG: EntityGateway> ShipServerState<EG> {
GameMessage::GuildcardSend(guildcard_send) => { GameMessage::GuildcardSend(guildcard_send) => {
handler::direct_message::guildcard_send(id, guildcard_send, target, &self.client_location, &self.clients) handler::direct_message::guildcard_send(id, guildcard_send, target, &self.client_location, &self.clients)
}, },
GameMessage::RequestItem(request_item) => {
handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.rooms, &mut self.item_database).unwrap()
},
_ => { _ => {
let cmsg = msg.clone(); let cmsg = msg.clone();
Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter() Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter()

Loading…
Cancel
Save