Merge pull request 'item drops' (#112) from itemdropsforreal into master
This commit is contained in:
commit
c28adcd7e5
@ -153,29 +153,8 @@ impl CharacterTechniques {
|
||||
pub fn as_bytes(&self) -> [u8; 20] {
|
||||
self.techs.iter()
|
||||
.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
|
||||
})
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ pub mod unit;
|
||||
pub mod mag;
|
||||
|
||||
use crate::entity::character::CharacterEntityId;
|
||||
use crate::ship::map::MapArea;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug, Hash, Eq)]
|
||||
pub struct ItemEntityId(pub u32);
|
||||
@ -27,8 +28,10 @@ pub enum ItemLocation {
|
||||
slot: BankName,
|
||||
},
|
||||
Floor {
|
||||
// floor: eventually
|
||||
// x y z: ?????
|
||||
map_area: MapArea,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
},
|
||||
/*Destroyed {
|
||||
// marks an item that has been consumed in some way
|
||||
@ -46,6 +49,7 @@ pub struct Meseta(pub u32);
|
||||
impl Meseta {
|
||||
pub fn as_bytes(&self) -> [u8; 16] {
|
||||
let mut result = [0; 16];
|
||||
result[0] = 4;
|
||||
result[12..16].copy_from_slice(&u32::to_le_bytes(self.0));
|
||||
result
|
||||
}
|
||||
@ -96,7 +100,7 @@ impl ItemDetail {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NewItemEntity {
|
||||
pub location: ItemLocation,
|
||||
pub item: ItemDetail,
|
||||
|
@ -24,8 +24,33 @@ pub enum Technique {
|
||||
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 tech: Technique,
|
||||
pub level: u32,
|
||||
@ -33,6 +58,11 @@ pub struct TechniqueDisk {
|
||||
|
||||
impl TechniqueDisk {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +394,7 @@ impl ToolType {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Tool {
|
||||
pub tool: ToolType,
|
||||
}
|
||||
|
@ -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 rare_drop_table;
|
||||
mod generic_weapon;
|
||||
@ -78,25 +84,20 @@ pub enum ItemDropType {
|
||||
Shield(shield::Shield),
|
||||
Unit(unit::Unit),
|
||||
Tool(tool::Tool),
|
||||
//Tools(Vec<tool::Tool>),
|
||||
TechniqueDisk(tech::TechniqueDisk),
|
||||
Mag(mag::Mag),
|
||||
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> {
|
||||
monster_stats: HashMap<MonsterType, MonsterDropStats>,
|
||||
@ -110,7 +111,6 @@ pub struct DropTable<R: Rng + SeedableRng> {
|
||||
rng: R,
|
||||
}
|
||||
|
||||
|
||||
impl<R: Rng + SeedableRng> 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");
|
||||
|
@ -100,7 +100,7 @@ impl TechniqueTable {
|
||||
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 level = rng.gen_range(stat.min, stat.max+1);
|
||||
let level = rng.gen_range(stat.min, stat.max+1) + 1;
|
||||
|
||||
Some(ItemDropType::TechniqueDisk(TechniqueDisk {
|
||||
tech: *tech,
|
||||
@ -118,10 +118,10 @@ mod test {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]);
|
||||
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 {
|
||||
assert!(tt.get_drop(&area, &mut rng) == Some(ItemDropType::TechniqueDisk(
|
||||
|
@ -11,23 +11,26 @@ use crate::entity::item::shield::Shield;
|
||||
use crate::entity::item::unit::Unit;
|
||||
use crate::entity::item::tool::Tool;
|
||||
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)]
|
||||
pub enum ItemInstance {
|
||||
enum ItemInstance {
|
||||
Individual(ItemEntity),
|
||||
Stacked(Vec<ItemEntity>),
|
||||
Meseta(Meseta),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct ActiveItemId(u32);
|
||||
pub struct ActiveItemId(pub u32);
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveItem {
|
||||
id: ActiveItemId,
|
||||
pub id: ActiveItemId,
|
||||
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> {
|
||||
let mut stacks = HashMap::new();
|
||||
|
||||
@ -138,8 +149,6 @@ pub struct ActiveItemDatabase {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl ActiveItemDatabase {
|
||||
pub fn new() -> 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 {
|
||||
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));
|
||||
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)]
|
||||
|
@ -52,6 +52,8 @@ pub enum JoinLobbyError {
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
#[error("")]
|
||||
pub enum GetAreaError {
|
||||
NotInRoom,
|
||||
NotInLobby,
|
||||
InvalidClient,
|
||||
}
|
||||
|
||||
@ -331,6 +333,24 @@ impl ClientLocation {
|
||||
.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> {
|
||||
let area = self.client_location.get_mut(&id).ok_or(ClientRemovalError::ClientNotInArea)?;
|
||||
let client_list = match area {
|
||||
|
107
src/ship/map.rs
107
src/ship/map.rs
@ -7,6 +7,7 @@ use std::fs::File;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use rand::Rng;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ship::monster::MonsterType;
|
||||
use crate::ship::room::Episode;
|
||||
@ -16,7 +17,7 @@ struct RawMapEnemy {
|
||||
id: u32,
|
||||
_unknown1: u16,
|
||||
children: u16,
|
||||
_unknown3: u16,
|
||||
map_area: u16,
|
||||
_unknown4: u16,
|
||||
section: u16,
|
||||
wave_idd: u16,
|
||||
@ -42,7 +43,7 @@ impl RawMapEnemy {
|
||||
id: cursor.read_u32::<LittleEndian>()?,
|
||||
_unknown1: 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>()?,
|
||||
section: cursor.read_u16::<LittleEndian>()?,
|
||||
wave_idd: cursor.read_u16::<LittleEndian>()?,
|
||||
@ -65,18 +66,22 @@ impl RawMapEnemy {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Error, Debug)]
|
||||
#[error("")]
|
||||
enum MapEnemyError {
|
||||
UnknownEnemyId(u32),
|
||||
MapAreaError(#[from] MapAreaError),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MapEnemy {
|
||||
pub monster: MonsterType,
|
||||
pub map_area: MapArea,
|
||||
hp: u32,
|
||||
// other stats from bp.n
|
||||
dead: bool,
|
||||
// TODO: other stats from battleparam
|
||||
pub dropped_item: bool,
|
||||
pub gave_exp: bool,
|
||||
}
|
||||
|
||||
impl MapEnemy {
|
||||
@ -169,16 +174,20 @@ impl MapEnemy {
|
||||
|
||||
Ok(MapEnemy {
|
||||
monster: monster,
|
||||
map_area: MapArea::from_value(&episode, enemy.map_area as u32)?,
|
||||
hp: 0,
|
||||
dead: false,
|
||||
dropped_item: false,
|
||||
gave_exp: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn new(monster: MonsterType) -> MapEnemy {
|
||||
fn new(monster: MonsterType, map_area: MapArea) -> MapEnemy {
|
||||
MapEnemy {
|
||||
monster: monster,
|
||||
map_area: map_area,
|
||||
hp: 0,
|
||||
dead: false,
|
||||
dropped_item: false,
|
||||
gave_exp: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -340,7 +349,7 @@ enum MapVariantMode {
|
||||
Offline,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum MapArea {
|
||||
Pioneer2Ep1,
|
||||
Forest1,
|
||||
@ -359,12 +368,14 @@ pub enum MapArea {
|
||||
DarkFalz,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("")]
|
||||
pub enum MapAreaError {
|
||||
UnknownMapArea(u32),
|
||||
}
|
||||
|
||||
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) {
|
||||
(Episode::One, 0) => Ok(MapArea::Pioneer2Ep1),
|
||||
(Episode::One, 1) => Ok(MapArea::Forest1),
|
||||
@ -404,6 +415,26 @@ impl MapArea {
|
||||
_ => 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 {
|
||||
MonsterType::Monest => {
|
||||
for _ in 0..30 {
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant)));
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area)));
|
||||
}
|
||||
},
|
||||
MonsterType::PofuillySlime => {
|
||||
for _ in 0..4 {
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime)));
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
|
||||
}
|
||||
},
|
||||
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 => {
|
||||
for _ in 0..4 {
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat)));
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area)));
|
||||
}
|
||||
},
|
||||
MonsterType::SinowGold => {
|
||||
for _ in 0..4 {
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::SinowGold)));
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area)));
|
||||
}
|
||||
},
|
||||
MonsterType::Canane => {
|
||||
for _ in 0..8 {
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine)));
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area)));
|
||||
}
|
||||
},
|
||||
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 => {
|
||||
for _ in 0..4 {
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::Claw)));
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::Claw, monster.map_area)));
|
||||
}
|
||||
},
|
||||
MonsterType::DeRolLe => {
|
||||
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 {
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine)));
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area)));
|
||||
}
|
||||
},
|
||||
MonsterType::VolOptPartA => {
|
||||
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 {
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor)));
|
||||
monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area)));
|
||||
}
|
||||
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?)
|
||||
MonsterType::DarkFalz => {
|
||||
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 {
|
||||
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)]
|
||||
pub struct Maps {
|
||||
map_variants: [MapVariant; 15],
|
||||
@ -641,8 +678,8 @@ impl 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] {
|
||||
|
33
src/ship/packet/builder/message.rs
Normal file
33
src/ship/packet/builder/message.rs
Normal file
@ -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,4 +1,5 @@
|
||||
pub mod lobby;
|
||||
pub mod message;
|
||||
pub mod room;
|
||||
|
||||
use libpso::character::character::Inventory;
|
||||
|
@ -6,10 +6,14 @@ use crate::common::serverstate::ClientId;
|
||||
use crate::common::leveltable::CharacterLevelTable;
|
||||
use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients, Rooms};
|
||||
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 crate::entity::gateway::EntityGateway;
|
||||
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)
|
||||
-> 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,
|
||||
guildcard_send: &GuildcardSend,
|
||||
target: u32,
|
||||
@ -46,3 +48,57 @@ pub fn guildcard_send(id: ClientId,
|
||||
};
|
||||
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()))
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ pub fn request_exp(id: ClientId,
|
||||
match client_location.get_area(id).unwrap() {
|
||||
RoomLobby::Room(room) => {
|
||||
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);
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
|
@ -25,6 +25,7 @@ use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOM
|
||||
use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder};
|
||||
use crate::ship::items;
|
||||
use crate::ship::room;
|
||||
use crate::ship::map::MapsError;
|
||||
use crate::ship::packet::handler;
|
||||
|
||||
pub const SHIP_PORT: u16 = 23423;
|
||||
@ -39,7 +40,11 @@ pub enum ShipError {
|
||||
InvalidSlot(ClientId, u32),
|
||||
TooManyClients,
|
||||
ClientLocationError(#[from] ClientLocationError),
|
||||
MapsError(#[from] MapsError),
|
||||
InvalidRoom(u32),
|
||||
MonsterAlreadyDroppedItem(ClientId, u16),
|
||||
SliceError(#[from] std::array::TryFromSliceError),
|
||||
ItemError, // TODO: refine this
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -145,6 +150,7 @@ pub struct ClientState {
|
||||
//guildcard: GuildCard,
|
||||
pub inventory: items::ActiveInventory,
|
||||
//bank: Bank,
|
||||
pub floor_items: Vec<items::ActiveItemOnFloor>,
|
||||
pub block: u32,
|
||||
}
|
||||
|
||||
@ -157,6 +163,7 @@ impl ClientState {
|
||||
session: session,
|
||||
inventory: inventory,
|
||||
//bank: bank,
|
||||
floor_items: Vec::new(),
|
||||
block: 1,
|
||||
}
|
||||
}
|
||||
@ -207,6 +214,9 @@ impl<EG: EntityGateway> ShipServerState<EG> {
|
||||
GameMessage::GuildcardSend(guildcard_send) => {
|
||||
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();
|
||||
Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
||||
|
Loading…
x
Reference in New Issue
Block a user