You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
819 lines
37 KiB
819 lines
37 KiB
#![allow(dead_code, unused_must_use)]
|
|
use std::net::Ipv4Addr;
|
|
use std::collections::HashMap;
|
|
|
|
use rand::Rng;
|
|
use thiserror::Error;
|
|
|
|
use libpso::packet::ship::*;
|
|
use libpso::packet::login::{RedirectClient, Login, LoginResponse, Session, ShipList};
|
|
use libpso::packet::messages::*;
|
|
use libpso::{PacketParseError, PSOPacket};
|
|
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;
|
|
use crate::common::interserver::{AuthToken, Ship, ServerId, InterserverActor, LoginMessage, ShipMessage};
|
|
|
|
use crate::login::character::SHIP_MENU_ID;
|
|
|
|
use crate::entity::gateway::{EntityGateway, GatewayError};
|
|
use crate::entity::account::{UserAccountEntity, UserSettingsEntity};
|
|
use crate::entity::character::{CharacterEntity, SectionID};
|
|
use crate::entity::item;
|
|
|
|
use crate::ship::location::{ClientLocation, RoomLobby, MAX_ROOMS, ClientLocationError, GetNeighborError, GetClientsError, GetAreaError};
|
|
|
|
use crate::ship::items;
|
|
use crate::ship::room;
|
|
use crate::ship::quests;
|
|
use crate::ship::map::{MapsError, MapAreaError, MapArea};
|
|
use crate::ship::packet::handler;
|
|
use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop, WeaponShopItem, ToolShopItem, ArmorShopItem};
|
|
use crate::ship::trade::TradeState;
|
|
|
|
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>;
|
|
|
|
#[derive(Error, Debug)]
|
|
#[error("shiperror {0:?}")]
|
|
pub enum ShipError {
|
|
ClientNotFound(ClientId),
|
|
NoCharacterInSlot(ClientId, u32),
|
|
InvalidSlot(ClientId, u32),
|
|
#[error("")]
|
|
TooManyClients,
|
|
ClientLocationError(#[from] ClientLocationError),
|
|
GetNeighborError(#[from] GetNeighborError),
|
|
GetClientsError(#[from] GetClientsError),
|
|
GetAreaError(#[from] GetAreaError),
|
|
MapsError(#[from] MapsError),
|
|
MapAreaError(#[from] MapAreaError),
|
|
InvalidRoom(u32),
|
|
MonsterAlreadyDroppedItem(ClientId, u16),
|
|
SliceError(#[from] std::array::TryFromSliceError),
|
|
#[error("")]
|
|
ItemError, // TODO: refine this
|
|
PickUpInvalidItemId(u32),
|
|
DropInvalidItemId(u32),
|
|
ItemManagerError(#[from] items::ItemManagerError),
|
|
#[error("")]
|
|
ItemDropLocationNotSet,
|
|
BoxAlreadyDroppedItem(ClientId, u16),
|
|
InvalidQuestCategory(u32),
|
|
InvalidQuest(u32),
|
|
InvalidQuestFilename(String),
|
|
IoError(#[from] std::io::Error),
|
|
NotEnoughMeseta(ClientId, u32),
|
|
#[error("")]
|
|
ShopError,
|
|
GatewayError(#[from] GatewayError),
|
|
UnknownMonster(crate::ship::monster::MonsterType),
|
|
InvalidShip(usize),
|
|
InvalidBlock(usize),
|
|
InvalidItem(items::ClientItemId),
|
|
#[error("tradeerror {0}")]
|
|
TradeError(#[from] crate::ship::packet::handler::trade::TradeError),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum RecvShipPacket {
|
|
Login(Login),
|
|
MenuSelect(MenuSelect),
|
|
RoomPasswordReq(RoomPasswordReq),
|
|
CharData(CharData),
|
|
Message(Message),
|
|
DirectMessage(DirectMessage),
|
|
PlayerChat(PlayerChat),
|
|
CreateRoom(CreateRoom),
|
|
RoomNameRequest(RoomNameRequest),
|
|
UpdateConfig(UpdateConfig),
|
|
ViewInfoboardRequest(ViewInfoboardRequest),
|
|
WriteInfoboard(WriteInfoboard),
|
|
RoomListRequest(RoomListRequest),
|
|
Like62ButCooler(Like62ButCooler),
|
|
ClientCharacterData(ClientCharacterData),
|
|
DoneBursting(DoneBursting),
|
|
DoneBursting2(DoneBursting2),
|
|
LobbySelect(LobbySelect),
|
|
RequestQuestList(RequestQuestList),
|
|
MenuDetail(MenuDetail),
|
|
QuestDetailRequest(QuestDetailRequest),
|
|
QuestMenuSelect(QuestMenuSelect),
|
|
QuestFileRequest(QuestFileRequest),
|
|
QuestChunkAck(QuestChunkAck),
|
|
DoneLoadingQuest(DoneLoadingQuest),
|
|
FullCharacterData(Box<FullCharacterData>),
|
|
SaveOptions(SaveOptions),
|
|
RequestShipList(RequestShipList),
|
|
RequestShipBlockList(RequestShipBlockList),
|
|
ItemsToTrade(ItemsToTrade),
|
|
TradeConfirmed(TradeConfirmed),
|
|
}
|
|
|
|
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)?)),
|
|
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)?)),
|
|
0x06 => Ok(RecvShipPacket::PlayerChat(PlayerChat::from_bytes(data)?)),
|
|
0xC1 => Ok(RecvShipPacket::CreateRoom(CreateRoom::from_bytes(data)?)),
|
|
0x8A => Ok(RecvShipPacket::RoomNameRequest(RoomNameRequest::from_bytes(data)?)),
|
|
0x7ED => Ok(RecvShipPacket::UpdateConfig(UpdateConfig::from_bytes(data)?)),
|
|
0xD8 => Ok(RecvShipPacket::ViewInfoboardRequest(ViewInfoboardRequest::from_bytes(data)?)),
|
|
0xD9 => Ok(RecvShipPacket::WriteInfoboard(WriteInfoboard::from_bytes(data)?)),
|
|
0x08 => Ok(RecvShipPacket::RoomListRequest(RoomListRequest::from_bytes(data)?)),
|
|
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)?)),
|
|
0xA0 => Ok(RecvShipPacket::RequestShipList(RequestShipList::from_bytes(data)?)),
|
|
0xA1 => Ok(RecvShipPacket::RequestShipBlockList(RequestShipBlockList::from_bytes(data)?)),
|
|
0xA2 => Ok(RecvShipPacket::RequestQuestList(RequestQuestList::from_bytes(data)?)),
|
|
0xAC => Ok(RecvShipPacket::DoneLoadingQuest(DoneLoadingQuest::from_bytes(data)?)),
|
|
0xD0 => Ok(RecvShipPacket::ItemsToTrade(ItemsToTrade::from_bytes(data)?)),
|
|
0xD2 => Ok(RecvShipPacket::TradeConfirmed(TradeConfirmed::from_bytes(data)?)),
|
|
0xE7 => Ok(RecvShipPacket::FullCharacterData(Box::new(FullCharacterData::from_bytes(data)?))),
|
|
0x1ED => Ok(RecvShipPacket::SaveOptions(SaveOptions::from_bytes(data)?)),
|
|
_ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum SendShipPacket {
|
|
ShipWelcome(ShipWelcome),
|
|
LoginResponse(LoginResponse),
|
|
ShipList(ShipList),
|
|
ShipBlockList(ShipBlockList),
|
|
FullCharacter(Box<FullCharacter>),
|
|
CharDataRequest(CharDataRequest),
|
|
JoinLobby(JoinLobby),
|
|
AddToLobby(AddToLobby),
|
|
Message(Message),
|
|
DirectMessage(DirectMessage),
|
|
PlayerChat(PlayerChat),
|
|
SmallDialog(SmallDialog),
|
|
JoinRoom(JoinRoom),
|
|
AddToRoom(AddToRoom),
|
|
LeaveLobby(LeaveLobby),
|
|
LeaveRoom(LeaveRoom),
|
|
RoomNameResponse(RoomNameResponse),
|
|
ViewInfoboardResponse(ViewInfoboardResponse),
|
|
RoomListResponse(RoomListResponse),
|
|
Like62ButCooler(Like62ButCooler),
|
|
BurstDone72(BurstDone72),
|
|
DoneBursting(DoneBursting),
|
|
DoneBursting2(DoneBursting2),
|
|
LobbyList(LobbyList),
|
|
QuestCategoryList(QuestCategoryList),
|
|
QuestOptionList(QuestOptionList),
|
|
QuestDetail(QuestDetail),
|
|
QuestHeader(QuestHeader),
|
|
QuestChunk(QuestChunk),
|
|
DoneLoadingQuest(DoneLoadingQuest),
|
|
BankItemList(BankItemList),
|
|
RedirectClient(RedirectClient),
|
|
AcknowledgeTrade(AcknowledgeTrade),
|
|
CancelTrade(CancelTrade),
|
|
TradeSuccessful(TradeSuccessful),
|
|
}
|
|
|
|
impl SendServerPacket for SendShipPacket {
|
|
fn as_bytes(&self) -> Vec<u8> {
|
|
match self {
|
|
SendShipPacket::ShipWelcome(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::LoginResponse(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::ShipList(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::ShipBlockList(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::FullCharacter(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::CharDataRequest(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::JoinLobby(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::AddToLobby(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::Message(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::DirectMessage(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::PlayerChat(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::SmallDialog(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::JoinRoom(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::AddToRoom(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::LeaveLobby(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::LeaveRoom(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::RoomNameResponse(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::ViewInfoboardResponse(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::RoomListResponse(pkt) => pkt.as_bytes(),
|
|
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(),
|
|
SendShipPacket::BankItemList(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::RedirectClient(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(),
|
|
SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct ItemDropLocation {
|
|
pub map_area: MapArea,
|
|
pub x: f32,
|
|
pub z: f32,
|
|
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,
|
|
pub character: CharacterEntity,
|
|
session: Session,
|
|
//guildcard: GuildCard,
|
|
pub block: usize,
|
|
pub item_drop_location: Option<ItemDropLocation>,
|
|
pub done_loading_quest: bool,
|
|
//pub loading_quest: Option<LoadingQuest>,
|
|
pub area: Option<MapArea>,
|
|
pub x: f32,
|
|
pub y: f32,
|
|
pub z: f32,
|
|
pub weapon_shop: Vec<WeaponShopItem>,
|
|
pub tool_shop: Vec<ToolShopItem>,
|
|
pub armor_shop: Vec<ArmorShopItem>,
|
|
pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>,
|
|
}
|
|
|
|
impl ClientState {
|
|
pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, session: Session) -> ClientState {
|
|
ClientState {
|
|
user,
|
|
settings,
|
|
character,
|
|
session,
|
|
block: 0,
|
|
item_drop_location: None,
|
|
done_loading_quest: false,
|
|
area: None,
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0,
|
|
weapon_shop: Vec::new(),
|
|
tool_shop: Vec::new(),
|
|
armor_shop: Vec::new(),
|
|
tek: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct ItemShops {
|
|
pub weapon_shop: HashMap<(room::Difficulty, SectionID), WeaponShop<rand_chacha::ChaCha20Rng>>,
|
|
pub tool_shop: ToolShop<rand_chacha::ChaCha20Rng>,
|
|
pub armor_shop: ArmorShop<rand_chacha::ChaCha20Rng>,
|
|
}
|
|
|
|
impl Default for ItemShops {
|
|
fn default() -> ItemShops {
|
|
let difficulty = [room::Difficulty::Normal, room::Difficulty::Hard, room::Difficulty::VeryHard, room::Difficulty::Ultimate];
|
|
let section_id = [SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
|
|
SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill];
|
|
|
|
let mut weapon_shop = HashMap::new();
|
|
for d in difficulty.iter() {
|
|
for id in section_id.iter() {
|
|
weapon_shop.insert((*d, *id), WeaponShop::new(*d, *id));
|
|
}
|
|
}
|
|
|
|
ItemShops {
|
|
weapon_shop,
|
|
tool_shop: ToolShop::default(),
|
|
armor_shop: ArmorShop::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pub struct ShipServerStateBuilder<EG: EntityGateway> {
|
|
entity_gateway: Option<EG>,
|
|
name: Option<String>,
|
|
ip: Option<Ipv4Addr>,
|
|
port: Option<u16>,
|
|
auth_token: Option<AuthToken>,
|
|
num_blocks: usize,
|
|
}
|
|
|
|
impl<EG: EntityGateway> Default for ShipServerStateBuilder<EG> {
|
|
fn default() -> ShipServerStateBuilder<EG> {
|
|
ShipServerStateBuilder {
|
|
entity_gateway: None,
|
|
name: None,
|
|
ip: None,
|
|
port: None,
|
|
auth_token: None,
|
|
num_blocks: 2,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
|
|
pub fn gateway(mut self, entity_gateway: EG) -> ShipServerStateBuilder<EG> {
|
|
self.entity_gateway = Some(entity_gateway);
|
|
self
|
|
}
|
|
|
|
pub fn name(mut self, name: String) -> ShipServerStateBuilder<EG> {
|
|
self.name = Some(name);
|
|
self
|
|
}
|
|
|
|
pub fn ip(mut self, ip: Ipv4Addr) -> ShipServerStateBuilder<EG> {
|
|
self.ip = Some(ip);
|
|
self
|
|
}
|
|
|
|
pub fn port(mut self, port: u16) -> ShipServerStateBuilder<EG> {
|
|
self.port = Some(port);
|
|
self
|
|
}
|
|
|
|
pub fn auth_token(mut self, auth_token: AuthToken) -> ShipServerStateBuilder<EG> {
|
|
self.auth_token = Some(auth_token);
|
|
self
|
|
}
|
|
|
|
pub fn blocks(mut self, num_blocks: usize) -> ShipServerStateBuilder<EG> {
|
|
self.num_blocks = num_blocks;
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> ShipServerState<EG> {
|
|
let blocks = std::iter::repeat_with(Block::default).take(self.num_blocks).collect(); // Block doesn't have a Clone impl which limits the easy ways to init this
|
|
ShipServerState {
|
|
entity_gateway: self.entity_gateway.unwrap(),
|
|
clients: HashMap::new(),
|
|
level_table: CharacterLevelTable::default(),
|
|
name: self.name.unwrap_or_else(|| "NAMENOTSET".into()),
|
|
item_manager: items::ItemManager::default(),
|
|
quests: quests::load_quests("data/quests.toml".into()).unwrap(),
|
|
ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)),
|
|
port: self.port.unwrap_or(SHIP_PORT),
|
|
shops: Box::new(ItemShops::default()),
|
|
|
|
blocks: Blocks(blocks),
|
|
|
|
auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())),
|
|
ship_list: Vec::new(),
|
|
shipgate_sender: None,
|
|
trades: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pub struct Block {
|
|
client_location: Box<ClientLocation>,
|
|
pub rooms: Box<Rooms>,
|
|
}
|
|
|
|
impl Default for Block {
|
|
fn default() -> Block {
|
|
const SNONE: Option<room::RoomState> = None;
|
|
const NONE: Rooms = [SNONE; MAX_ROOMS];
|
|
Block {
|
|
client_location: Box::new(ClientLocation::default()),
|
|
rooms: Box::new(NONE),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Blocks(pub Vec<Block>);
|
|
|
|
impl Blocks {
|
|
fn with_client(&mut self, id: ClientId, clients: &Clients) -> Result<&mut Block, ShipError> {
|
|
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
|
|
self.0.get_mut(client.block).ok_or(ShipError::InvalidBlock(client.block))
|
|
}
|
|
}
|
|
|
|
pub struct ShipServerState<EG: EntityGateway> {
|
|
entity_gateway: EG,
|
|
pub clients: Clients,
|
|
level_table: CharacterLevelTable,
|
|
name: String,
|
|
item_manager: items::ItemManager,
|
|
quests: quests::QuestList,
|
|
shops: Box<ItemShops>,
|
|
pub blocks: Blocks,
|
|
|
|
ip: Ipv4Addr,
|
|
port: u16,
|
|
|
|
auth_token: AuthToken,
|
|
ship_list: Vec<Ship>,
|
|
shipgate_sender: Option<Box<dyn Fn(ShipMessage) + Send + Sync>>,
|
|
trades: TradeState,
|
|
}
|
|
|
|
impl<EG: EntityGateway> ShipServerState<EG> {
|
|
pub fn builder() -> ShipServerStateBuilder<EG> {
|
|
ShipServerStateBuilder::default()
|
|
}
|
|
|
|
pub fn set_sender(&mut self, sender: Box<dyn Fn(ShipMessage) + Send + Sync>) {
|
|
self.shipgate_sender = Some(sender);
|
|
}
|
|
|
|
async fn message(&mut self, id: ClientId, msg: &Message) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
|
|
Ok(match &msg.msg {
|
|
GameMessage::RequestExp(request_exp) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::message::request_exp(id, request_exp, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &self.level_table).await?
|
|
},
|
|
GameMessage::PlayerDropItem(player_drop_item) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::message::player_drop_item(id, player_drop_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::DropCoordinates(drop_coordinates) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::message::drop_coordinates(id, drop_coordinates, &block.client_location, &mut self.clients, &block.rooms)?
|
|
},
|
|
GameMessage::PlayerNoLongerHasItem(no_longer_has_item) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::message::no_longer_has_item(id, no_longer_has_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::PlayerChangedMap(_) | GameMessage::PlayerChangedMap2(_) | GameMessage::TellOtherPlayerMyLocation(_) |
|
|
GameMessage::PlayerWarpingToFloor(_) | GameMessage::PlayerTeleported(_) | GameMessage::PlayerStopped(_) |
|
|
GameMessage::PlayerLoadedIn(_) | GameMessage::PlayerWalking(_) | GameMessage::PlayerRunning(_) |
|
|
GameMessage::PlayerWarped(_) | GameMessage::PlayerChangedFloor(_) | GameMessage::InitializeSpeechNpc(_) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::message::update_player_position(id, msg, &mut self.clients, &block.client_location, &block.rooms)?
|
|
},
|
|
GameMessage::ChargeAttack(charge_attack) => {
|
|
handler::message::charge_attack(id, charge_attack, &mut self.clients, &mut self.entity_gateway, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::PlayerUseItem(player_use_item) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::message::use_item(id, player_use_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::PlayerUsedMedicalCenter(player_used_medical_center) => {
|
|
handler::message::player_used_medical_center(id, player_used_medical_center, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::PlayerFeedMag(player_feed_mag) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::message::player_feed_mag(id, player_feed_mag, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::PlayerEquipItem(player_equip_item) => {
|
|
handler::message::player_equips_item(id, player_equip_item, &mut self.entity_gateway, &self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::PlayerUnequipItem(player_unequip_item) => {
|
|
handler::message::player_unequips_item(id, player_unequip_item, &mut self.entity_gateway, &self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::SortItems(sort_items) => {
|
|
handler::message::player_sorts_items(id, sort_items, &mut self.entity_gateway, &self.clients, &mut self.item_manager).await?
|
|
},
|
|
_ => {
|
|
let cmsg = msg.clone();
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
Box::new(block.client_location.get_client_neighbors(id).unwrap().into_iter()
|
|
.map(move |client| {
|
|
(client.client, SendShipPacket::Message(cmsg.clone()))
|
|
}))
|
|
},
|
|
})
|
|
}
|
|
|
|
async fn direct_message(&mut self, id: ClientId, msg: &DirectMessage) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
|
|
let target = msg.flag;
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
Ok(match &msg.msg {
|
|
GameMessage::GuildcardSend(guildcard_send) => {
|
|
handler::direct_message::guildcard_send(id, guildcard_send, target, &block.client_location, &self.clients)
|
|
},
|
|
GameMessage::RequestItem(request_item) => {
|
|
handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::PickupItem(pickup_item) => {
|
|
handler::direct_message::pickup_item(id, pickup_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::BoxDropRequest(box_drop_request) => {
|
|
handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::BankRequest(_bank_request) => {
|
|
handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::BankInteraction(bank_interaction) => {
|
|
handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::ShopRequest(shop_request) => {
|
|
handler::direct_message::shop_request(id, shop_request, &block.client_location, &mut self.clients, &block.rooms, &self.level_table, &mut self.shops).await?
|
|
},
|
|
GameMessage::BuyItem(buy_item) => {
|
|
handler::direct_message::buy_item(id, buy_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::TekRequest(tek_request) => {
|
|
handler::direct_message::request_tek_item(id, tek_request, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::TekAccept(tek_accept) => {
|
|
handler::direct_message::accept_tek_item(id, tek_accept, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await?
|
|
},
|
|
GameMessage::TradeRequest(trade_request) => {
|
|
handler::trade::trade_request(id, trade_request, target, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await?
|
|
},
|
|
_ => {
|
|
let cmsg = msg.clone();
|
|
Box::new(block.client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
.map(move |client| {
|
|
(client.client, SendShipPacket::DirectMessage(cmsg.clone()))
|
|
}))
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
|
|
type SendPacket = SendShipPacket;
|
|
type RecvPacket = RecvShipPacket;
|
|
type PacketError = anyhow::Error;
|
|
|
|
async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket>>, anyhow::Error> {
|
|
let mut rng = rand::thread_rng();
|
|
|
|
let mut server_key = [0u8; 48];
|
|
let mut client_key = [0u8; 48];
|
|
rng.fill(&mut server_key[..]);
|
|
rng.fill(&mut client_key[..]);
|
|
|
|
Ok(vec![OnConnect::Packet(SendShipPacket::ShipWelcome(ShipWelcome::new(server_key, client_key))),
|
|
OnConnect::Cipher((Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key)),
|
|
Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))))
|
|
])
|
|
}
|
|
|
|
async fn handle(&mut self, id: ClientId, pkt: &RecvShipPacket)
|
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
|
|
Ok(match pkt {
|
|
RecvShipPacket::Login(login) => {
|
|
Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager, &self.shipgate_sender, &self.name, self.blocks.0.len())
|
|
.await?.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) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
match menuselect.menu {
|
|
SHIP_MENU_ID => {
|
|
let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).into_iter().into_iter().flatten();
|
|
let select_ship = handler::ship::selected_ship(id, menuselect, &self.ship_list)?;
|
|
Box::new(leave_lobby.chain(select_ship))
|
|
}
|
|
BLOCK_MENU_ID => {
|
|
let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).into_iter().into_iter().flatten();
|
|
let select_block = handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_manager, &self.level_table)?.into_iter();
|
|
Box::new(leave_lobby.chain(select_block))
|
|
}
|
|
ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?,
|
|
QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &self.quests)?,
|
|
_ => unreachable!(),
|
|
}
|
|
},
|
|
RecvShipPacket::QuestMenuSelect(questmenuselect) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::quest::load_quest(id, questmenuselect, &self.quests, &mut self.clients, &block.client_location, &mut block.rooms)?
|
|
},
|
|
RecvShipPacket::MenuDetail(_menudetail) => {
|
|
//unreachable!();
|
|
Box::new(Vec::new().into_iter())
|
|
},
|
|
RecvShipPacket::RoomPasswordReq(room_password_req) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
if room_password_req.password == block.rooms[room_password_req.item as usize].as_ref()
|
|
.ok_or(ShipError::InvalidRoom(room_password_req.item))?
|
|
.password {
|
|
let menuselect = MenuSelect {
|
|
menu: room_password_req.menu,
|
|
item: room_password_req.item,
|
|
};
|
|
handler::room::join_room(id, &menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?
|
|
}
|
|
else {
|
|
Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))].into_iter())
|
|
}
|
|
},
|
|
RecvShipPacket::CharData(chardata) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_manager, &self.level_table)?.into_iter())
|
|
},
|
|
RecvShipPacket::Message(msg) => {
|
|
self.message(id, msg).await?
|
|
},
|
|
RecvShipPacket::DirectMessage(msg) => {
|
|
self.direct_message(id, msg).await?
|
|
},
|
|
RecvShipPacket::PlayerChat(msg) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
Box::new(handler::communication::player_chat(id, msg, &block.client_location, &self.clients)?)
|
|
},
|
|
RecvShipPacket::CreateRoom(create_room) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::room::create_room(id, create_room, &mut block.client_location, &mut self.clients, &mut self.item_manager, &mut block.rooms)?
|
|
},
|
|
RecvShipPacket::RoomNameRequest(_req) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::room::room_name_request(id, &block.client_location, &block.rooms)
|
|
},
|
|
RecvShipPacket::UpdateConfig(pkt) => {
|
|
handler::settings::update_config(id, pkt, &mut self.clients, &mut self.entity_gateway).await
|
|
},
|
|
RecvShipPacket::ViewInfoboardRequest(_pkt) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::communication::request_infoboard(id, &block.client_location, &self.clients)
|
|
},
|
|
RecvShipPacket::WriteInfoboard(pkt) => {
|
|
handler::communication::write_infoboard(id, pkt, &mut self.clients, &mut self.entity_gateway).await
|
|
},
|
|
RecvShipPacket::RoomListRequest(_req) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::room::request_room_list(id, &block.client_location, &block.rooms)
|
|
},
|
|
RecvShipPacket::Like62ButCooler(cool62) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::room::cool_62(id, cool62, &block.client_location)
|
|
},
|
|
RecvShipPacket::ClientCharacterData(_) => {
|
|
// TOOD: validate this in some way?
|
|
Box::new(None.into_iter())
|
|
},
|
|
RecvShipPacket::DoneBursting(_) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::room::done_bursting(id, &block.client_location, &mut block.rooms)
|
|
},
|
|
RecvShipPacket::DoneBursting2(_) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::room::done_bursting(id, &block.client_location, &mut block.rooms)
|
|
},
|
|
RecvShipPacket::LobbySelect(pkt) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.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(_) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::quest::done_loading_quest(id, &mut self.clients, &block.client_location)?
|
|
},
|
|
RecvShipPacket::FullCharacterData(_full_character_data) => {
|
|
Box::new(None.into_iter())
|
|
},
|
|
RecvShipPacket::SaveOptions(save_options) => {
|
|
handler::settings::save_options(id, save_options, &mut self.clients, &mut self.entity_gateway).await
|
|
},
|
|
RecvShipPacket::RequestShipList(_) => {
|
|
handler::ship::ship_list(id, &self.ship_list)
|
|
},
|
|
RecvShipPacket::RequestShipBlockList(_) => {
|
|
handler::ship::block_list(id, &self.name, self.blocks.0.len())
|
|
},
|
|
RecvShipPacket::ItemsToTrade(items_to_trade) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::trade::items_to_trade(id, items_to_trade, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await?
|
|
},
|
|
RecvShipPacket::TradeConfirmed(_) => {
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await?
|
|
},
|
|
})
|
|
}
|
|
|
|
async fn on_disconnect(&mut self, id: ClientId) -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
|
|
let client = self.clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
|
|
let block = self.blocks.with_client(id, &self.clients)?;
|
|
let area_client = block.client_location.get_local_client(id)?;
|
|
let neighbors = block.client_location.get_client_neighbors(id)?;
|
|
|
|
let pkt = match block.client_location.get_area(id)? {
|
|
RoomLobby::Room(room) => {
|
|
if neighbors.is_empty() {
|
|
block.rooms[room.0] = None;
|
|
}
|
|
let leader = block.client_location.get_room_leader(room)?;
|
|
SendShipPacket::LeaveRoom(LeaveRoom::new(area_client.local_client.id(), leader.local_client.id()))
|
|
},
|
|
RoomLobby::Lobby(lobby) => {
|
|
let leader = block.client_location.get_lobby_leader(lobby)?;
|
|
SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id()))
|
|
}
|
|
};
|
|
|
|
if let Some(shipgate_sender) = self.shipgate_sender.as_ref() {
|
|
shipgate_sender(ShipMessage::RemoveUser(client.user.id));
|
|
}
|
|
|
|
block.client_location.remove_client_from_area(id);
|
|
self.item_manager.remove_character_from_room(&client.character);
|
|
|
|
if let Some(mut client) = self.clients.remove(&id) {
|
|
client.user.at_ship = false;
|
|
self.entity_gateway.save_user(&client.user).await;
|
|
}
|
|
|
|
Ok(neighbors.into_iter().map(|n| {
|
|
(n.client, pkt.clone())
|
|
}).collect())
|
|
}
|
|
}
|
|
|
|
|
|
#[async_trait::async_trait]
|
|
impl<EG: EntityGateway> InterserverActor for ShipServerState<EG> {
|
|
type SendMessage = ShipMessage;
|
|
type RecvMessage = LoginMessage;
|
|
type Error = ();
|
|
|
|
async fn on_connect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)> {
|
|
vec![
|
|
(id, ShipMessage::Authenticate(self.auth_token.clone())),
|
|
(id, ShipMessage::NewShip(Ship {
|
|
name: self.name.clone(),
|
|
ip: self.ip,
|
|
port: self.port,
|
|
block_count: 2,
|
|
})),
|
|
(id, ShipMessage::RequestShipList)
|
|
]
|
|
}
|
|
|
|
async fn action(&mut self, id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error> {
|
|
match msg {
|
|
LoginMessage::SendMail{..} => {
|
|
Ok(Vec::new())
|
|
},
|
|
LoginMessage::ShipList{ships} => {
|
|
self.ship_list = ships;
|
|
Ok(Vec::new())
|
|
},
|
|
LoginMessage::RequestUsers => {
|
|
Ok(self.clients.iter()
|
|
.map(|(_, client)| {
|
|
(id, ShipMessage::AddUser(client.user.id))
|
|
})
|
|
.collect())
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn on_disconnect(&mut self, _id: ServerId) -> Vec<(ServerId, Self::SendMessage)> {
|
|
Vec::new()
|
|
}
|
|
}
|