quests!
This commit is contained in:
		
							parent
							
								
									1bd10f4bb8
								
							
						
					
					
						commit
						35dd503d14
					
				| @ -39,4 +39,5 @@ byteorder = "1" | ||||
| enum-utils = "0.1.2" | ||||
| derive_more = { version = "0.99.3", features = ["display"]} | ||||
| thiserror = "1.0.15" | ||||
| ages-prs = "0.1" | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										204
									
								
								src/ship/map.rs
									
									
									
									
									
								
							
							
						
						
									
										204
									
								
								src/ship/map.rs
									
									
									
									
									
								
							| @ -524,110 +524,121 @@ impl MapVariant { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| fn objects_from_map_data(path: PathBuf, episode: &Episode, map_variant: &MapVariant) -> Vec<Option<MapObject>> { | ||||
|     let mut cursor = File::open(path.clone()).unwrap(); | ||||
| pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> { | ||||
|     let mut object_data = Vec::new(); | ||||
| 
 | ||||
|     while let Ok(raw_object) = RawMapObject::from_byte_stream(&mut cursor) { | ||||
|         let object = MapObject::from_raw(raw_object.clone(), *episode, &map_variant.map); | ||||
|     while let Ok(raw_object) = RawMapObject::from_byte_stream(cursor) { | ||||
|         let object = MapObject::from_raw(raw_object.clone(), *episode, map_area); | ||||
|         object_data.push(object.ok()); | ||||
| 
 | ||||
|     } | ||||
|     object_data | ||||
| } | ||||
| 
 | ||||
| fn objects_from_map_data(path: PathBuf, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> { | ||||
|     let mut cursor = File::open(path.clone()).unwrap(); | ||||
|     objects_from_stream(&mut cursor, episode, map_area) | ||||
| } | ||||
| 
 | ||||
| fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) -> Vec<Option<MapEnemy>> { | ||||
|     let enemy = MapEnemy::from_raw(raw_enemy, episode, map_area); | ||||
|     enemy | ||||
|         .map_or(vec![None], |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))); | ||||
|                     } | ||||
|                 }, | ||||
|                 MonsterType::PofuillySlime => { | ||||
|                     for _ in 0..4 { | ||||
|                         monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area))); | ||||
|                     } | ||||
|                 }, | ||||
|                 MonsterType::PanArms => { | ||||
|                     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, monster.map_area))); | ||||
|                     } | ||||
|                 }, | ||||
|                 MonsterType::SinowGold => { | ||||
|                     for _ in 0..4 { | ||||
|                         monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area))); | ||||
|                     } | ||||
|                 }, | ||||
|                 MonsterType::Canane => { | ||||
|                     for _ in 0..8 { | ||||
|                         monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area))); | ||||
|                     } | ||||
|                 }, | ||||
|                 MonsterType::ChaosSorcerer => { | ||||
|                     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, monster.map_area))); | ||||
|                     } | ||||
|                 }, | ||||
|                 MonsterType::DeRolLe => { | ||||
|                     for _ in 0..10 { | ||||
|                         monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area))); | ||||
|                     } | ||||
|                     for _ in 0..9 { | ||||
|                         monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area))); | ||||
|                     } | ||||
|                 }, | ||||
|                 MonsterType::VolOptPartA => { | ||||
|                     for _ in 0..6 { | ||||
|                         monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area))); | ||||
|                     } | ||||
|                     for _ in 0..24 { | ||||
|                         monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area))); | ||||
|                     } | ||||
|                     for _ in 0..2 { | ||||
|                         monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area))); | ||||
|                     } | ||||
|                     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, monster.map_area))); | ||||
|                     } | ||||
|                     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..raw_enemy.children { | ||||
|                         monsters.push(Some(MapEnemy::new(monster.monster, monster.map_area))); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             monsters | ||||
|         }) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| pub fn enemy_data_from_stream(cursor: &mut impl Read, map_area: &MapArea, episode: &Episode) -> Vec<Option<MapEnemy>> { | ||||
|     let mut enemy_data = Vec::new(); | ||||
|     while let Ok(enemy) = RawMapEnemy::from_byte_stream(cursor) { | ||||
|         enemy_data.append(&mut parse_enemy(episode, map_area, enemy)); | ||||
|     } | ||||
|     enemy_data | ||||
| } | ||||
| 
 | ||||
| fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec<Option<MapEnemy>> { | ||||
|     let path = map_variant.dat_file(); | ||||
|     let mut cursor = File::open(path).unwrap(); | ||||
|     let mut enemy_data = Vec::new(); | ||||
|     while let Ok(enemy) = RawMapEnemy::from_byte_stream(&mut cursor) { | ||||
|         let new_enemy = MapEnemy::from_raw(enemy, episode, &map_variant.map); | ||||
|         enemy_data.append(&mut new_enemy | ||||
|                                .map_or(vec![None], |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))); | ||||
|                                            } | ||||
|                                        }, | ||||
|                                        MonsterType::PofuillySlime => { | ||||
|                                            for _ in 0..4 { | ||||
|                                                monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area))); | ||||
|                                            } | ||||
|                                        }, | ||||
|                                        MonsterType::PanArms => { | ||||
|                                            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, monster.map_area))); | ||||
|                                            } | ||||
|                                        }, | ||||
|                                        MonsterType::SinowGold => { | ||||
|                                            for _ in 0..4 { | ||||
|                                                monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area))); | ||||
|                                            } | ||||
|                                        }, | ||||
|                                        MonsterType::Canane => { | ||||
|                                            for _ in 0..8 { | ||||
|                                                monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area))); | ||||
|                                            } | ||||
|                                        }, | ||||
|                                        MonsterType::ChaosSorcerer => { | ||||
|                                            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, monster.map_area))); | ||||
|                                            } | ||||
|                                        }, | ||||
|                                        MonsterType::DeRolLe => { | ||||
|                                            for _ in 0..10 { | ||||
|                                                monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area))); | ||||
|                                            } | ||||
|                                            for _ in 0..9 { | ||||
|                                                monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area))); | ||||
|                                            } | ||||
|                                        }, | ||||
|                                        MonsterType::VolOptPartA => { | ||||
|                                            for _ in 0..6 { | ||||
|                                                monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area))); | ||||
|                                            } | ||||
|                                            for _ in 0..24 { | ||||
|                                                monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area))); | ||||
|                                            } | ||||
|                                            for _ in 0..2 { | ||||
|                                                monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area))); | ||||
|                                            } | ||||
|                                            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, monster.map_area))); | ||||
|                                            } | ||||
|                                            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, monster.map_area))); | ||||
|                                            } | ||||
|                                        } | ||||
|                                    } | ||||
|                                    monsters | ||||
|                                })); | ||||
|     } | ||||
|     enemy_data | ||||
|     enemy_data_from_stream(&mut cursor, &map_variant.map, episode) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -675,7 +686,7 @@ impl Maps { | ||||
|                 enemy_data | ||||
|             }), | ||||
|             object_data: map_variants.iter().map(|map_variant| { | ||||
|                 objects_from_map_data(map_variant.obj_file().into(), &episode, &map_variant) | ||||
|                 objects_from_map_data(map_variant.obj_file().into(), &episode, &map_variant.map) | ||||
|             }).flatten().collect(), | ||||
|             map_variants: map_variants, | ||||
|         }; | ||||
| @ -702,6 +713,11 @@ impl Maps { | ||||
|                 header | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_quest_data(&mut self, enemies: Vec<Option<MapEnemy>>, objects: Vec<Option<MapObject>>) { | ||||
|         self.enemy_data = enemies; | ||||
|         self.object_data = objects; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -8,3 +8,4 @@ pub mod map; | ||||
| pub mod monster; | ||||
| pub mod drops; | ||||
| pub mod packet; | ||||
| pub mod quests; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| pub mod lobby; | ||||
| pub mod message; | ||||
| pub mod room; | ||||
| pub mod quest; | ||||
| 
 | ||||
| use libpso::character::character::Inventory; | ||||
| use libpso::packet::ship::{PlayerHeader, PlayerInfo}; | ||||
|  | ||||
							
								
								
									
										47
									
								
								src/ship/packet/builder/quest.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/ship/packet/builder/quest.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| use crate::ship::quests::{Quest, QuestList}; | ||||
| use crate::ship::ship::{QUEST_CATEGORY_MENU_ID, QUEST_SELECT_MENU_ID}; | ||||
| use libpso::packet::ship::*; | ||||
| use libpso::utf8_to_utf16_array; | ||||
| 
 | ||||
| 
 | ||||
| pub fn quest_category_list(quests: &QuestList) -> QuestCategoryList { | ||||
|     let categories = quests.iter() | ||||
|         .enumerate() | ||||
|         .map(|(i, (category, _))| { | ||||
|             QuestCategory { | ||||
|                 menu_id: QUEST_CATEGORY_MENU_ID, | ||||
|                 option_id: i as u32, | ||||
|                 name: utf8_to_utf16_array!(category.name, 32), | ||||
|                 description: utf8_to_utf16_array!(category.description, 122), | ||||
|             } | ||||
|         }) | ||||
|         .collect(); | ||||
| 
 | ||||
|     QuestCategoryList { | ||||
|         quest_categories: categories, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn quest_list(category_id: u32, quests: &Vec<Quest>) -> QuestOptionList { | ||||
|     let quest_entries = quests.iter() | ||||
|         .map(|quest| { | ||||
|             QuestEntry { | ||||
|                 menu_id: QUEST_SELECT_MENU_ID, | ||||
|                 category_id: category_id as u16, | ||||
|                 quest_id: quest.id, | ||||
|                 name: utf8_to_utf16_array!(quest.name, 32), | ||||
|                 description: utf8_to_utf16_array!(quest.description, 122), | ||||
|             } | ||||
|         }) | ||||
|         .collect(); | ||||
| 
 | ||||
|     QuestOptionList { | ||||
|         quests: quest_entries, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn quest_detail(quest: &Quest) -> QuestDetail { | ||||
|     QuestDetail { | ||||
|         description: utf8_to_utf16_array!(quest.full_description, 288), | ||||
|     } | ||||
| } | ||||
| @ -5,3 +5,4 @@ pub mod lobby; | ||||
| pub mod message; | ||||
| pub mod room; | ||||
| pub mod settings; | ||||
| pub mod quest; | ||||
|  | ||||
							
								
								
									
										209
									
								
								src/ship/packet/handler/quest.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/ship/packet/handler/quest.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,209 @@ | ||||
| use std::collections::HashMap; | ||||
| use std::convert::TryInto; | ||||
| use std::io::{Cursor, Read, Seek, SeekFrom}; | ||||
| use libpso::packet::ship::*; | ||||
| use crate::common::serverstate::ClientId; | ||||
| use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms}; | ||||
| use crate::ship::quests::QuestList; | ||||
| use crate::ship::location::{ClientLocation, ClientLocationError}; | ||||
| use crate::ship::packet::builder::quest; | ||||
| use libpso::utf8_to_array; | ||||
| use libpso::util::array_to_utf8; | ||||
| 
 | ||||
| // TOOD: enum
 | ||||
| const DATATYPE_BIN: u16 = 1; | ||||
| const DATATYPE_DAT: u16 = 2; | ||||
| 
 | ||||
| 
 | ||||
| fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, u16), ShipError> { | ||||
|     let filename = array_to_utf8(*filename_bytes).map_err(|_| ShipError::InvalidQuestFilename("NOT UTF8".to_string()))?; | ||||
|     let (filename, suffix) = { | ||||
|         let mut s = filename.splitn(2, '.'); | ||||
|         (s.next().ok_or(ShipError::InvalidQuestFilename(filename.to_owned()))?, | ||||
|          s.next().ok_or(ShipError::InvalidQuestFilename(filename.to_owned()))?) | ||||
|     }; | ||||
| 
 | ||||
|     let datatype = match suffix { | ||||
|         "bin" => DATATYPE_BIN, | ||||
|         "dat" => DATATYPE_DAT, | ||||
|         _ => return Err(ShipError::InvalidQuestFilename(filename.to_owned())) | ||||
|     }; | ||||
| 
 | ||||
|     let (category, quest) = { | ||||
|         let mut s = filename.splitn(2, '-'); | ||||
|         (s.next().and_then(|k| k.parse().ok()).ok_or(ShipError::InvalidQuestFilename(filename.to_owned()))?, | ||||
|          s.next().and_then(|k| k.parse().ok()).ok_or(ShipError::InvalidQuestFilename(filename.to_owned()))?) | ||||
|     }; | ||||
| 
 | ||||
|     Ok((category, quest, datatype)) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| pub fn send_quest_category_list(id: ClientId, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> { | ||||
|     let qcl = quest::quest_category_list(quests); | ||||
|     Ok(Box::new(vec![(id, SendShipPacket::QuestCategoryList(qcl))].into_iter())) | ||||
| } | ||||
| 
 | ||||
| pub fn select_quest_category(id: ClientId, menuselect: &MenuSelect, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> { | ||||
|     let (_, category_quests) = quests.iter() | ||||
|         .nth(menuselect.item as usize) | ||||
|         .ok_or(ShipError::InvalidQuestCategory(menuselect.item))?; | ||||
| 
 | ||||
|     let ql = quest::quest_list(menuselect.item, category_quests); | ||||
|     
 | ||||
|     Ok(Box::new(vec![(id, SendShipPacket::QuestOptionList(ql))].into_iter())) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| pub fn quest_detail(id: ClientId, questdetailrequest: &QuestDetailRequest, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> { | ||||
|     let (_, category_quests) = quests.iter() | ||||
|         .nth(questdetailrequest.category as usize) | ||||
|         .ok_or(ShipError::InvalidQuestCategory(questdetailrequest.category as u32))?; | ||||
| 
 | ||||
|     let quest = category_quests.iter() | ||||
|         .find(|q| { | ||||
|             q.id == questdetailrequest.quest as u16 | ||||
|         }).ok_or(ShipError::InvalidQuest(questdetailrequest.quest as u32))?; | ||||
| 
 | ||||
|     let qd = quest::quest_detail(&quest); | ||||
|     
 | ||||
|     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) | ||||
|                   -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> { | ||||
|     let (_, category_quests) = quests.iter() | ||||
|         .nth(questmenuselect.category as usize) | ||||
|         .ok_or(ShipError::InvalidQuestCategory(questmenuselect.category as u32))?; | ||||
| 
 | ||||
|     let quest = category_quests.iter() | ||||
|         .find(|q| { | ||||
|             q.id == questmenuselect.quest as u16 | ||||
|         }).ok_or(ShipError::InvalidQuest(questmenuselect.quest as u32))?; | ||||
| 
 | ||||
|     let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?; | ||||
|     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))?; | ||||
|     room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone()); | ||||
| 
 | ||||
|     let bin_path = format!("{}-{}.bin", questmenuselect.category, questmenuselect.quest); | ||||
|     let bin = QuestHeader { | ||||
|         unknown1: [0; 0x24], | ||||
|         filename: utf8_to_array!(bin_path, 16), | ||||
|         length: quest.bin_blob.len() as u32, | ||||
|         name: utf8_to_array!("quest.bin", 16), | ||||
|         unknown2: [0; 8], | ||||
|     }; | ||||
| 
 | ||||
|     let dat_path = format!("{}-{}.dat", questmenuselect.category, questmenuselect.quest); | ||||
|     let dat = QuestHeader { | ||||
|         unknown1: [0; 0x24], | ||||
|         filename: utf8_to_array!(dat_path, 16), | ||||
|         length: quest.dat_blob.len() as u32, | ||||
|         name: utf8_to_array!("quest.dat", 16), | ||||
|         unknown2: [0; 8], | ||||
|     }; | ||||
| 
 | ||||
|     let area_clients = client_location.get_all_clients_by_client(id).map_err(|err| -> ClientLocationError { err.into() })?; | ||||
|     area_clients.iter().for_each(|c| { | ||||
|         clients.get_mut(&c.client).map(|client| { | ||||
|             client.done_loading_quest = false; | ||||
|         }); | ||||
|     }); | ||||
|     Ok(Box::new(area_clients.into_iter().map(move |c| { | ||||
|         vec![(c.client, SendShipPacket::QuestHeader(bin.clone())), (c.client, SendShipPacket::QuestHeader(dat.clone()))] | ||||
|     }).flatten())) | ||||
| 
 | ||||
|     //Ok(Box::new(vec![(id, SendShipPacket::QuestHeader(bin)), (id, SendShipPacket::QuestHeader(dat))].into_iter()))
 | ||||
| } | ||||
| 
 | ||||
| pub fn quest_file_request(id: ClientId, quest_file_request: &QuestFileRequest, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> { | ||||
|     let (category_id, quest_id, datatype) = parse_filename(&quest_file_request.filename)?; | ||||
|     let (_, category_quests) = quests.iter() | ||||
|         .nth(category_id as usize) | ||||
|         .ok_or(ShipError::InvalidQuestCategory(category_id as u32))?; | ||||
| 
 | ||||
|     let quest = category_quests.iter() | ||||
|         .find(|q| { | ||||
|             q.id == quest_id as u16 | ||||
|         }).ok_or(ShipError::InvalidQuest(quest_id as u32))?; | ||||
| 
 | ||||
|     // quest.Bin quest.Dat
 | ||||
|     let blob = match datatype { | ||||
|         DATATYPE_BIN => &quest.bin_blob, | ||||
|         DATATYPE_DAT => &quest.dat_blob, | ||||
|         _ => panic!() | ||||
|     }; | ||||
|     let mut blob_cursor = Cursor::new(blob); | ||||
| 
 | ||||
|     let mut subblob = [0u8; 0x400]; | ||||
|     let blob_length = blob_cursor.read(&mut subblob)?; | ||||
|     let qc = QuestChunk { | ||||
|         chunk_num: 0, | ||||
|         filename: quest_file_request.filename, | ||||
|         blob: subblob, | ||||
|         blob_length: blob_length as u32, | ||||
|         unknown: 0, | ||||
|     }; | ||||
|     
 | ||||
|     Ok(Box::new(vec![(id, SendShipPacket::QuestChunk(qc))].into_iter())) | ||||
| } | ||||
| 
 | ||||
| pub fn quest_chunk_ack(id: ClientId, quest_chunk_ack: &QuestChunkAck, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> { | ||||
|     let (category_id, quest_id, datatype) = parse_filename(&quest_chunk_ack.filename)?; | ||||
|     let (_, category_quests) = quests.iter() | ||||
|         .nth(category_id as usize) | ||||
|         .ok_or(ShipError::InvalidQuestCategory(category_id as u32))?; | ||||
| 
 | ||||
|     let quest = category_quests.iter() | ||||
|         .find(|q| { | ||||
|             q.id == quest_id | ||||
|         }).ok_or(ShipError::InvalidQuest(quest_id as u32))?; | ||||
| 
 | ||||
|     let blob = match datatype { | ||||
|         DATATYPE_BIN => &quest.bin_blob, | ||||
|         DATATYPE_DAT => &quest.dat_blob, | ||||
|         _ => panic!() | ||||
|     }; | ||||
| 
 | ||||
|     let mut blob_cursor = Cursor::new(blob); | ||||
|     blob_cursor.seek(SeekFrom::Start((quest_chunk_ack.chunk_num as u64 + 1) * 0x400)); | ||||
|     let mut subblob = [0u8; 0x400]; | ||||
|     let blob_length = blob_cursor.read(&mut subblob)?; | ||||
|     if blob_length == 0 { | ||||
|         return Ok(Box::new(None.into_iter())); | ||||
|     } | ||||
|     let qc = QuestChunk { | ||||
|         chunk_num: quest_chunk_ack.chunk_num + 1, | ||||
|         filename: quest_chunk_ack.filename, | ||||
|         blob: subblob, | ||||
|         blob_length: blob_length as u32, | ||||
|         unknown: 0, | ||||
|     }; | ||||
|     
 | ||||
|     Ok(Box::new(vec![(id, SendShipPacket::QuestChunk(qc))].into_iter())) | ||||
| } | ||||
| 
 | ||||
| pub fn done_loading_quest(id: ClientId, quests: &QuestList, clients: &mut Clients, client_location: &ClientLocation) | ||||
|                           -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> { | ||||
|     let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; | ||||
|     client.done_loading_quest = true; | ||||
|     let area_clients = client_location.get_all_clients_by_client(id).map_err(|err| -> ClientLocationError { err.into() })?; | ||||
|     let all_loaded = area_clients.iter().all(|c| { | ||||
|         clients.get(&c.client) | ||||
|             .map(|client| { | ||||
|                 client.done_loading_quest | ||||
|             }) | ||||
|             .unwrap_or(false) | ||||
|     }); | ||||
| 
 | ||||
|     if all_loaded { | ||||
|         Ok(Box::new(area_clients.into_iter().map(|c| { | ||||
|             (c.client, SendShipPacket::DoneLoadingQuest(DoneLoadingQuest {})) | ||||
|         }))) | ||||
|     } | ||||
|     else { | ||||
|         Ok(Box::new(None.into_iter())) | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,5 @@ | ||||
| use libpso::packet::ship::*; | ||||
| use libpso::packet::messages::*; | ||||
| use crate::common::serverstate::ClientId; | ||||
| use crate::common::leveltable::CharacterLevelTable; | ||||
| use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients}; | ||||
| @ -110,10 +111,35 @@ pub fn done_bursting(id: ClientId, | ||||
|         let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap(); | ||||
|         room.bursting = false; | ||||
|     } | ||||
|     Box::new(client_location.get_client_neighbors(id).unwrap().into_iter() | ||||
|              .map(move |client| { | ||||
|                  vec![ | ||||
|                      //(client.client, SendShipPacket::BurstDone72(BurstDone72::new())),
 | ||||
|                      (client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone { | ||||
|                          client: 0, | ||||
|                          target: 0 | ||||
|                      })))), | ||||
|                  ] | ||||
|              }).flatten()) | ||||
| } | ||||
| 
 | ||||
| pub fn done_bursting2(id: ClientId, | ||||
|                      client_location: &ClientLocation, | ||||
|                      rooms: &mut Rooms) | ||||
|                      -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> { | ||||
|     let area = client_location.get_area(id).unwrap(); | ||||
|     if let RoomLobby::Room(room_id) = area { | ||||
|         let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap(); | ||||
|         room.bursting = false; | ||||
|     } | ||||
|     Box::new(client_location.get_client_neighbors(id).unwrap().into_iter() | ||||
|              .map(move |client| { | ||||
|                  vec![ | ||||
|                      (client.client, SendShipPacket::BurstDone72(BurstDone72::new())), | ||||
|                      /*(client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone {
 | ||||
|                          client: 1, | ||||
|                          target: 0 | ||||
|                      })))),*/ | ||||
|                  ] | ||||
|              }).flatten()) | ||||
| } | ||||
|  | ||||
							
								
								
									
										224
									
								
								src/ship/quests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								src/ship/quests.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,224 @@ | ||||
| use log::warn; | ||||
| use std::collections::{HashMap, BTreeMap, BTreeSet}; | ||||
| use std::fs::File; | ||||
| use std::io::{Read, Write, Cursor, Seek, SeekFrom}; | ||||
| use std::path::PathBuf; | ||||
| use std::convert::{TryInto, TryFrom}; | ||||
| use thiserror::Error; | ||||
| use serde::{Serialize, Deserialize}; | ||||
| use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder}; | ||||
| use byteorder::{LittleEndian, ReadBytesExt}; | ||||
| use libpso::packet::ship::QuestChunk; | ||||
| use libpso::util::array_to_utf16; | ||||
| use crate::ship::map::{MapArea, MapAreaError, MapObject, MapEnemy, enemy_data_from_stream, objects_from_stream}; | ||||
| use crate::ship::room::Episode; | ||||
| 
 | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] | ||||
| pub struct QuestCategory { | ||||
|     index: usize, | ||||
|     pub name: String, | ||||
|     pub description: String, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize, Hash)] | ||||
| struct QuestListEntry { | ||||
|     bin: String, | ||||
|     dat: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize, Hash)] | ||||
| struct QuestListCategory { | ||||
|     list_order: usize, | ||||
|     description: String, | ||||
|     quests: Vec<QuestListEntry>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| struct QuestListConfig { | ||||
|     questlist: HashMap<String, Vec<QuestListEntry>>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Error, Debug)] | ||||
| #[error("")] | ||||
| enum ParseDatError { | ||||
|     IoError(#[from] std::io::Error), | ||||
|     MapError(#[from] MapAreaError), | ||||
|     UnknownDatHeader(u32), | ||||
|     CouldNotDetermineEpisode, | ||||
| } | ||||
| 
 | ||||
| const DAT_OBJECT_HEADER_ID: u32 = 1; | ||||
| const DAT_ENEMY_HEADER_ID: u32 = 2; | ||||
| const DAT_WAVE_HEADER_ID: u32 = 3; | ||||
| 
 | ||||
| enum DatBlock { | ||||
|     Object(Vec<Option<MapObject>>), | ||||
|     Enemy(Vec<Option<MapEnemy>>), | ||||
|     Wave, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode) -> Result<DatBlock, ParseDatError> { | ||||
|     let header = cursor.read_u32::<LittleEndian>()?; | ||||
|     let offset = cursor.read_u32::<LittleEndian>()?; | ||||
|     let area = cursor.read_u32::<LittleEndian>()?; | ||||
|     let length = cursor.read_u32::<LittleEndian>()?; | ||||
| 
 | ||||
|     let map_area = MapArea::from_value(episode, area)?; | ||||
| 
 | ||||
|     match header { | ||||
|         DAT_OBJECT_HEADER_ID => { | ||||
|             let mut obj_data = vec![0u8; length as usize]; | ||||
|             cursor.read(&mut obj_data); | ||||
|             let mut obj_cursor = Cursor::new(obj_data); | ||||
| 
 | ||||
|             let objects = objects_from_stream(&mut obj_cursor, episode, &map_area); | ||||
|             Ok(DatBlock::Object(objects)) | ||||
|         }, | ||||
|         DAT_ENEMY_HEADER_ID => { | ||||
|             let mut enemy_data = vec![0u8; length as usize]; | ||||
|             cursor.read(&mut enemy_data); | ||||
|             let mut enemy_cursor = Cursor::new(enemy_data); | ||||
| 
 | ||||
|             let enemies = enemy_data_from_stream(&mut enemy_cursor, &map_area, episode); | ||||
|             
 | ||||
|             Ok(DatBlock::Enemy(enemies)) | ||||
|         }, | ||||
|         DAT_WAVE_HEADER_ID => { | ||||
|             cursor.seek(SeekFrom::Current(length as i64)); | ||||
|             Ok(DatBlock::Wave) | ||||
|         }, | ||||
|         _ => Err(ParseDatError::UnknownDatHeader(header)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn quest_episode(bin: &[u8]) -> Option<Episode> { | ||||
|     for bytes in bin.windows(3) { | ||||
|         if bytes[0] == 0xF8 && bytes[1] == 0xBC { | ||||
|             warn!("ep? {:?}", bytes[2]); | ||||
|             return Some(Episode::from_quest(bytes[2]).ok()?) | ||||
|         } | ||||
|     } | ||||
|     None | ||||
| } | ||||
| 
 | ||||
| fn parse_dat(dat: &[u8], episode: &Episode) -> Result<(Vec<Option<MapEnemy>>, Vec<Option<MapObject>>), ParseDatError> { | ||||
|     let mut cursor = Cursor::new(dat); | ||||
| 
 | ||||
|     let header_iter = std::iter::from_fn(move || { | ||||
|         match read_dat_section_header(&mut cursor, episode) { | ||||
|             Ok(dat_block) => Some(dat_block), | ||||
|             Err(err) => { | ||||
|                 warn!("unknown header in dat: {:?}", err); | ||||
|                 None | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     Ok(header_iter.fold((Vec::new(), Vec::new()), |(mut enemies, mut objects), dat_block| { | ||||
|         match dat_block { | ||||
|             DatBlock::Object(mut objs) => { | ||||
|                 objects.append(&mut objs) | ||||
|             }, | ||||
|             DatBlock::Enemy(mut enemy) => { | ||||
|                 enemies.append(&mut enemy) | ||||
|             }, | ||||
|             _ => {} | ||||
|         } | ||||
|         
 | ||||
|         (enemies, objects) | ||||
|     })) | ||||
| } | ||||
| 
 | ||||
| #[derive(Error, Debug)] | ||||
| #[error("")] | ||||
| enum QuestLoadError { | ||||
|     ParseDatError(#[from] ParseDatError), | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct Quest { | ||||
|     pub name: String, | ||||
|     pub description: String, | ||||
|     pub full_description: String, | ||||
|     pub language: u16, | ||||
|     pub id: u16, | ||||
|     pub bin_blob: Vec<u8>, | ||||
|     pub dat_blob: Vec<u8>, | ||||
|     pub enemies: Vec<Option<MapEnemy>>, | ||||
|     pub objects: Vec<Option<MapObject>>, | ||||
| } | ||||
| 
 | ||||
| impl Quest { | ||||
|     fn from_bin_dat(bin: Vec<u8>, dat: Vec<u8>) -> Result<Quest, QuestLoadError> { | ||||
|         let id = u16::from_le_bytes(bin[16..18].try_into().unwrap()); | ||||
|         let language = u16::from_le_bytes(bin[18..20].try_into().unwrap()); | ||||
|         let name = array_to_utf16(&bin[24..88]); | ||||
|         let description = array_to_utf16(&bin[88..334]); | ||||
|         let full_description = array_to_utf16(&bin[334..920]); | ||||
| 
 | ||||
|         let episode = quest_episode(&bin).ok_or(ParseDatError::CouldNotDetermineEpisode)?; | ||||
|         let (enemies, objects) = parse_dat(&dat, &episode)?; | ||||
| 
 | ||||
|         let mut prs_bin = LegacyPrsEncoder::new(Vec::new()); | ||||
|         prs_bin.write(&bin); | ||||
|         let mut prs_dat = LegacyPrsEncoder::new(Vec::new()); | ||||
|         prs_dat.write(&dat); | ||||
| 
 | ||||
|         Ok(Quest { | ||||
|             name: name, | ||||
|             description: description, | ||||
|             full_description: full_description, | ||||
|             id: id, | ||||
|             language: language, | ||||
|             bin_blob: prs_bin.into_inner().unwrap(), | ||||
|             dat_blob: prs_dat.into_inner().unwrap(), | ||||
|             enemies: enemies, | ||||
|             objects: objects, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // QuestCollection
 | ||||
| pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>; | ||||
| 
 | ||||
| pub fn load_quests(quest_path: PathBuf) -> QuestList { | ||||
|     let mut f = File::open(quest_path).unwrap(); | ||||
|     let mut s = String::new(); | ||||
|     f.read_to_string(&mut s); | ||||
| 
 | ||||
|     let mut used_quest_ids = BTreeSet::new(); | ||||
|     let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).unwrap(); | ||||
| 
 | ||||
|     ql.into_iter().map(|(category, category_details)| { | ||||
|         let quests = category_details.quests | ||||
|             .into_iter() | ||||
|             .filter_map(|quest| { | ||||
|                 warn!("{:?}", quest.bin); | ||||
|                 let dat_file = File::open(format!("data/quests/{}", quest.dat)).unwrap(); | ||||
|                 let bin_file = File::open(format!("data/quests/{}", quest.bin)).unwrap(); | ||||
|                 let mut dat_prs = LegacyPrsDecoder::new(dat_file); | ||||
|                 let mut bin_prs = LegacyPrsDecoder::new(bin_file); | ||||
|                 
 | ||||
|                 let mut dat = Vec::new(); | ||||
|                 let mut bin = Vec::new(); | ||||
|                 dat_prs.read_to_end(&mut dat).unwrap(); | ||||
|                 bin_prs.read_to_end(&mut bin).unwrap(); | ||||
|                 
 | ||||
|                 let quest = Quest::from_bin_dat(bin, dat).unwrap(); | ||||
|                 if used_quest_ids.contains(&quest.id) { | ||||
|                     return None; | ||||
|                 } | ||||
|                 used_quest_ids.insert(quest.id); | ||||
|                 Some(quest) | ||||
|             }); | ||||
|         (QuestCategory{ | ||||
|             index: category_details.list_order, | ||||
|             name: category, | ||||
|             description: category_details.description, | ||||
|         }, quests.collect()) | ||||
|     }).collect() | ||||
|     
 | ||||
| } | ||||
| @ -46,6 +46,17 @@ impl Into<u8> for Episode { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Episode { | ||||
|     pub fn from_quest(value: u8) -> Result<Episode, RoomCreationError> { | ||||
|         match value { | ||||
|             0 => Ok(Episode::One), | ||||
|             1 => Ok(Episode::Two), | ||||
|             2 => Ok(Episode::Four), | ||||
|             _ => Err(RoomCreationError::InvalidEpisode(value)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Copy, Clone, derive_more::Display)] | ||||
| pub enum Difficulty { | ||||
|     Normal, | ||||
|  | ||||
| @ -12,7 +12,6 @@ use libpso::crypto::bb::PSOBBCipher; | ||||
| 
 | ||||
| use libpso::packet::ship::{BLOCK_MENU_ID, ROOM_MENU_ID}; | ||||
| 
 | ||||
| 
 | ||||
| use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; | ||||
| use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; | ||||
| use crate::common::leveltable::CharacterLevelTable; | ||||
| @ -25,10 +24,13 @@ use crate::ship::location::{ClientLocation, RoomLobby, MAX_ROOMS, ClientLocation | ||||
| 
 | ||||
| use crate::ship::items; | ||||
| use crate::ship::room; | ||||
| use crate::ship::quests; | ||||
| use crate::ship::map::{MapsError, MapAreaError, MapArea}; | ||||
| use crate::ship::packet::handler; | ||||
| 
 | ||||
| pub const SHIP_PORT: u16 = 23423; | ||||
| pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2; | ||||
| pub const QUEST_SELECT_MENU_ID: u32 = 0xA3; | ||||
| pub type Rooms = [Option<room::RoomState>; MAX_ROOMS]; | ||||
| pub type Clients = HashMap<ClientId, ClientState>; | ||||
| 
 | ||||
| @ -51,6 +53,10 @@ pub enum ShipError { | ||||
|     ItemManagerError(#[from] items::ItemManagerError), | ||||
|     ItemDropLocationNotSet, | ||||
|     BoxAlreadyDroppedItem(ClientId, u16), | ||||
|     InvalidQuestCategory(u32), | ||||
|     InvalidQuest(u32), | ||||
|     InvalidQuestFilename(String), | ||||
|     IoError(#[from] std::io::Error), | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| @ -71,18 +77,33 @@ pub enum RecvShipPacket { | ||||
|     Like62ButCooler(Like62ButCooler), | ||||
|     ClientCharacterData(ClientCharacterData), | ||||
|     DoneBursting(DoneBursting), | ||||
|     DoneBursting2(DoneBursting2), | ||||
|     LobbySelect(LobbySelect), | ||||
|     RequestQuestList(RequestQuestList), | ||||
|     MenuDetail(MenuDetail), | ||||
|     QuestDetailRequest(QuestDetailRequest), | ||||
|     QuestMenuSelect(QuestMenuSelect), | ||||
|     QuestFileRequest(QuestFileRequest), | ||||
|     QuestChunkAck(QuestChunkAck), | ||||
|     DoneLoadingQuest(DoneLoadingQuest), | ||||
| } | ||||
| 
 | ||||
| impl RecvServerPacket for RecvShipPacket { | ||||
|     fn from_bytes(data: &[u8]) -> Result<RecvShipPacket, PacketParseError> { | ||||
|         match u16::from_le_bytes([data[2], data[3]]) { | ||||
|             0x93 => Ok(RecvShipPacket::Login(Login::from_bytes(data)?)), | ||||
|             0x10 => match data[0] { | ||||
|                 16 => Ok(RecvShipPacket::MenuSelect(MenuSelect::from_bytes(data)?)), | ||||
|                 48 => Ok(RecvShipPacket::RoomPasswordReq(RoomPasswordReq::from_bytes(data)?)), | ||||
|             0x09 => match data[8] as u32 { | ||||
|                 QUEST_SELECT_MENU_ID => Ok(RecvShipPacket::QuestDetailRequest(QuestDetailRequest::from_bytes(data)?)), | ||||
|                 _ => Ok(RecvShipPacket::MenuDetail(MenuDetail::from_bytes(data)?)), | ||||
|             } | ||||
|             0x10 => match (data[0], data[8] as u32) { | ||||
|                 (16, QUEST_SELECT_MENU_ID) => Ok(RecvShipPacket::QuestMenuSelect(QuestMenuSelect::from_bytes(data)?)), | ||||
|                 (16, _) => Ok(RecvShipPacket::MenuSelect(MenuSelect::from_bytes(data)?)), | ||||
|                 (48, _) => Ok(RecvShipPacket::RoomPasswordReq(RoomPasswordReq::from_bytes(data)?)), | ||||
|                 _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())), | ||||
|             }, | ||||
|             0x13 => Ok(RecvShipPacket::QuestChunkAck(QuestChunkAck::from_bytes(data)?)), | ||||
|             0x44 => Ok(RecvShipPacket::QuestFileRequest(QuestFileRequest::from_bytes(data)?)), | ||||
|             0x61 => Ok(RecvShipPacket::CharData(CharData::from_bytes(data)?)), | ||||
|             0x60 => Ok(RecvShipPacket::Message(Message::from_bytes(data)?)), | ||||
|             0x62 => Ok(RecvShipPacket::DirectMessage(DirectMessage::from_bytes(data)?)), | ||||
| @ -96,7 +117,10 @@ impl RecvServerPacket for RecvShipPacket { | ||||
|             0x6D => Ok(RecvShipPacket::Like62ButCooler(Like62ButCooler::from_bytes(data)?)), | ||||
|             0x98 => Ok(RecvShipPacket::ClientCharacterData(ClientCharacterData::from_bytes(data)?)), | ||||
|             0x6F => Ok(RecvShipPacket::DoneBursting(DoneBursting::from_bytes(data)?)), | ||||
|             0x16F => Ok(RecvShipPacket::DoneBursting2(DoneBursting2::from_bytes(data)?)), | ||||
|             0x84 => Ok(RecvShipPacket::LobbySelect(LobbySelect::from_bytes(data)?)), | ||||
|             0xA2 => Ok(RecvShipPacket::RequestQuestList(RequestQuestList::from_bytes(data)?)), | ||||
|             0xAC => Ok(RecvShipPacket::DoneLoadingQuest(DoneLoadingQuest::from_bytes(data)?)), | ||||
|             _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())) | ||||
|         } | ||||
|     } | ||||
| @ -125,7 +149,14 @@ pub enum SendShipPacket { | ||||
|     Like62ButCooler(Like62ButCooler), | ||||
|     BurstDone72(BurstDone72), | ||||
|     DoneBursting(DoneBursting), | ||||
|     DoneBursting2(DoneBursting2), | ||||
|     LobbyList(LobbyList), | ||||
|     QuestCategoryList(QuestCategoryList), | ||||
|     QuestOptionList(QuestOptionList), | ||||
|     QuestDetail(QuestDetail), | ||||
|     QuestHeader(QuestHeader), | ||||
|     QuestChunk(QuestChunk), | ||||
|     DoneLoadingQuest(DoneLoadingQuest), | ||||
| } | ||||
| 
 | ||||
| impl SendServerPacket for SendShipPacket { | ||||
| @ -152,7 +183,14 @@ impl SendServerPacket for SendShipPacket { | ||||
|             SendShipPacket::Like62ButCooler(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::BurstDone72(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::DoneBursting(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::DoneBursting2(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::LobbyList(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::QuestCategoryList(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::QuestOptionList(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::QuestDetail(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::QuestHeader(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::QuestChunk(pkt) => pkt.as_bytes(), | ||||
|             SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -165,6 +203,12 @@ pub struct ItemDropLocation { | ||||
|     pub item_id: items::ClientItemId, | ||||
| } | ||||
| 
 | ||||
| pub struct LoadingQuest { | ||||
|     pub header_bin: Option<QuestHeader>, | ||||
|     pub header_dat: Option<QuestHeader>, | ||||
|     //pub quest_chunk_bin: Option<Box<dyn Iterator<Item = >>>,
 | ||||
| } | ||||
| 
 | ||||
| pub struct ClientState { | ||||
|     pub user: UserAccountEntity, | ||||
|     pub settings: UserSettingsEntity, | ||||
| @ -173,6 +217,8 @@ pub struct ClientState { | ||||
|     //guildcard: GuildCard,
 | ||||
|     pub block: u32, | ||||
|     pub item_drop_location: Option<ItemDropLocation>, | ||||
|     pub done_loading_quest: bool, | ||||
|     //pub loading_quest: Option<LoadingQuest>,
 | ||||
| } | ||||
| 
 | ||||
| impl ClientState { | ||||
| @ -184,6 +230,7 @@ impl ClientState { | ||||
|             session: session, | ||||
|             block: 1, | ||||
|             item_drop_location: None, | ||||
|             done_loading_quest: false, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -197,6 +244,7 @@ pub struct ShipServerState<EG: EntityGateway> { | ||||
|     name: String, | ||||
|     rooms: Rooms, | ||||
|     item_manager: items::ItemManager, | ||||
|     quests: quests::QuestList, | ||||
| } | ||||
| 
 | ||||
| impl<EG: EntityGateway> ShipServerState<EG> { | ||||
| @ -209,6 +257,7 @@ impl<EG: EntityGateway> ShipServerState<EG> { | ||||
|             name: "Sona-Nyl".into(), | ||||
|             rooms: [None; MAX_ROOMS], | ||||
|             item_manager: items::ItemManager::new(), | ||||
|             quests: quests::load_quests("data/quests.toml".into()), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -288,13 +337,26 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> { | ||||
|             RecvShipPacket::Login(login) => { | ||||
|                 Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager, &self.name)?.into_iter().map(move |pkt| (id, pkt))) | ||||
|             }, | ||||
|             RecvShipPacket::QuestDetailRequest(questdetailrequest) => { | ||||
|                 match questdetailrequest.menu { | ||||
|                     QUEST_SELECT_MENU_ID => handler::quest::quest_detail(id, questdetailrequest, &self.quests)?, | ||||
|                     _ => unreachable!(), | ||||
|                 } | ||||
|             }, | ||||
|             RecvShipPacket::MenuSelect(menuselect) => { | ||||
|                 match menuselect.menu { | ||||
|                     BLOCK_MENU_ID => Box::new(handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_manager, &self.level_table)?.into_iter().map(move |pkt| (id, pkt))), | ||||
|                     ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut self.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms)?, | ||||
|                     QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &self.quests)?, | ||||
|                     _ => unreachable!(), | ||||
|                 } | ||||
|             }, | ||||
|             RecvShipPacket::QuestMenuSelect(questmenuselect) => { | ||||
|                 handler::quest::load_quest(id, questmenuselect, &self.quests, &mut self.clients, &self.client_location, &mut self.rooms)? | ||||
|             }, | ||||
|             RecvShipPacket::MenuDetail(_menudetail) => { | ||||
|                 unreachable!(); | ||||
|             }, | ||||
|             RecvShipPacket::RoomPasswordReq(room_password_req) => { | ||||
|                 if room_password_req.password == self.rooms[room_password_req.item as usize].as_ref() | ||||
|                     .ok_or(ShipError::InvalidRoom(room_password_req.item))? | ||||
| @ -349,9 +411,25 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> { | ||||
|             RecvShipPacket::DoneBursting(_) => { | ||||
|                 handler::room::done_bursting(id, &self.client_location, &mut self.rooms) | ||||
|             }, | ||||
|             RecvShipPacket::DoneBursting2(_) => { | ||||
|                 Box::new(None.into_iter()) | ||||
|                 //handler::room::done_bursting2(id, &self.client_location, &mut self.rooms)
 | ||||
|             }, | ||||
|             RecvShipPacket::LobbySelect(pkt) => { | ||||
|                 Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut self.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms, &mut self.entity_gateway)?.into_iter()) | ||||
|             } | ||||
|             }, | ||||
|             RecvShipPacket::RequestQuestList(_) => { | ||||
|                 handler::quest::send_quest_category_list(id, &self.quests)? | ||||
|             }, | ||||
|             RecvShipPacket::QuestFileRequest(quest_file_request) => { | ||||
|                 handler::quest::quest_file_request(id, quest_file_request, &self.quests)? | ||||
|             }, | ||||
|             RecvShipPacket::QuestChunkAck(quest_chunk_ack) => { | ||||
|                 handler::quest::quest_chunk_ack(id, quest_chunk_ack, &self.quests)? | ||||
|             }, | ||||
|             RecvShipPacket::DoneLoadingQuest(_) => { | ||||
|                 handler::quest::done_loading_quest(id, &self.quests, &mut self.clients, &self.client_location)? | ||||
|             }, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user