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.

948 lines
44 KiB

#![allow(dead_code, unused_must_use)]
use std::net::Ipv4Addr;
use std::collections::HashMap;
use async_std::channel;
use async_std::sync::{Arc, Mutex, RwLock};
use rand::Rng;
use thiserror::Error;
use libpso::packet::ship::*;
use libpso::packet::login::{RedirectClient, Login, LoginResponse, 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::interserver::{AuthToken, Ship, ServerId, InterserverActor, LoginMessage, ShipMessage};
use crate::login::character::SHIP_MENU_ID;
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::SectionID;
use crate::entity::room::RoomNote;
use crate::ship::location::{ClientLocation, RoomLobby, ClientLocationError, RoomId};
use crate::ship::drops::DropTable;
use crate::ship::items;
use crate::ship::room;
use crate::ship::map::{Maps, MapsError, MapAreaError, generate_free_roam_maps};
use crate::ship::packet::handler;
use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop};
use crate::ship::trade::TradeState;
use crate::ship::chatcommand;
// TODO: remove once stuff settles down
pub use crate::ship::client::*;
pub const SHIP_PORT: u16 = 23423;
pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2;
pub const QUEST_SELECT_MENU_ID: u32 = 0xA3;
#[derive(Clone, Copy)]
pub enum ShipEvent {
None,
Christmas,
Valentines,
Easter,
Halloween,
Sonic,
NewYear,
Summer,
White,
Wedding,
Fall,
Spring,
Summer2,
Spring2,
}
impl From<ShipEvent> for u32 {
fn from(other: ShipEvent) -> u32 {
u16::from(other) as u32
}
}
impl From<ShipEvent> for u16 {
fn from(other: ShipEvent) -> u16 {
u8::from(other) as u16
}
}
impl From<ShipEvent> for u8 {
fn from(other: ShipEvent) -> u8 {
match other {
ShipEvent::None => 0,
ShipEvent::Christmas => 1,
ShipEvent::Valentines => 3,
ShipEvent::Easter => 4,
ShipEvent::Halloween => 5,
ShipEvent::Sonic => 6,
ShipEvent::NewYear => 7,
ShipEvent::Summer => 8,
ShipEvent::White => 9,
ShipEvent::Wedding => 10,
ShipEvent::Fall => 11,
ShipEvent::Spring => 12,
ShipEvent::Summer2 => 13,
ShipEvent::Spring2 => 14,
}
}
}
#[derive(Error, Debug)]
pub enum ShipError {
#[error("client not found {0}")]
ClientNotFound(ClientId),
#[error("no character in slot {0} {1}")]
NoCharacterInSlot(ClientId, u32),
#[error("invalid slot {0} {1}")]
InvalidSlot(ClientId, u32),
#[error("too many clients")]
TooManyClients,
#[error("client location {0}")]
ClientLocationError(ClientLocationError),
#[error("maps error {0}")]
MapsError(#[from] MapsError),
#[error("map area error {0}")]
MapAreaError(#[from] MapAreaError),
#[error("invalid room {0}")]
InvalidRoom(u32),
#[error("monster already droppped item {0} {1}")]
MonsterAlreadyDroppedItem(ClientId, u16),
#[error("slice error {0}")]
SliceError(#[from] std::array::TryFromSliceError),
#[error("item error")]
ItemError, // TODO: refine this
#[error("pick up invalid item id {0}")]
PickUpInvalidItemId(u32),
#[error("drop invalid item id {0}")]
DropInvalidItemId(u32),
#[error("item state error {0}")]
ItemStateError(#[from] items::state::ItemStateError),
#[error("item drop location not set")]
ItemDropLocationNotSet,
#[error("box already dropped item {0} {1}")]
BoxAlreadyDroppedItem(ClientId, u16),
#[error("invalid quest category {0}")]
InvalidQuestCategory(u16),
#[error("invalid quest {0}")]
InvalidQuest(u16),
#[error("invalid quest filename {0}")]
InvalidQuestFilename(String),
#[error("io error {0}")]
IoError(#[from] std::io::Error),
#[error("not enough meseta {0} {1}")]
NotEnoughMeseta(ClientId, u32),
#[error("shop error")]
ShopError,
#[error("gateway error {0}")]
GatewayError(#[from] GatewayError),
#[error("unknown monster {0}")]
UnknownMonster(crate::ship::monster::MonsterType),
#[error("invalid ship {0}")]
InvalidShip(usize),
#[error("invalid block {0}")]
InvalidBlock(usize),
#[error("invalid item {0}")]
InvalidItem(items::ClientItemId),
#[error("trade error {0}")]
TradeError(#[from] crate::ship::packet::handler::trade::TradeError),
#[error("trade state error {0}")]
TradeStateError(#[from] crate::ship::trade::TradeStateError),
#[error("message error {0}")]
MessageError(#[from] crate::ship::packet::handler::direct_message::MessageError),
#[error("room creation error {0}")]
RoomCreationError(#[from] room::RoomCreationError),
#[error("channel send error {0}")]
SendError(#[from] async_std::channel::SendError<ShipMessage>),
}
/*
impl<I: Into<ClientLocationError>> From<I> for ShipError {
fn from(other: I) -> ShipError {
ShipError::ClientLocationError(other.into())
}
}
*/
#[derive(Debug)]
pub enum RecvShipPacket {
Login(Login),
MenuSelect(MenuSelect),
RoomPasswordReq(RoomPasswordReq),
CharData(CharData),
Message(Message),
DirectMessage(DirectMessage),
PlayerChat(PlayerChat),
CreateRoom(CreateRoom),
RoomNameRequest(RoomNameRequest),
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),
KeyboardConfig(KeyboardConfig),
GamepadConfig(GamepadConfig),
UpdateConfig(UpdateConfig),
UpdateTechMenu(UpdateTechMenu),
}
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)?)),
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)?)),
0x4ED => Ok(RecvShipPacket::KeyboardConfig(KeyboardConfig::from_bytes(data)?)),
0x5ED => Ok(RecvShipPacket::GamepadConfig(GamepadConfig::from_bytes(data)?)),
0x6ED => Ok(RecvShipPacket::UpdateTechMenu(UpdateTechMenu::from_bytes(data)?)),
0x7ED => Ok(RecvShipPacket::UpdateConfig(UpdateConfig::from_bytes(data)?)),
_ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
}
}
}
#[derive(Debug, Clone, PartialEq)]
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),
SmallLeftDialog(SmallLeftDialog),
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),
RareMonsterList(RareMonsterList),
AcknowledgeTrade(AcknowledgeTrade),
CancelTrade(CancelTrade),
TradeSuccessful(TradeSuccessful),
LobbyEvent(LobbyEvent),
LargeDialog(LargeDialog),
}
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::SmallLeftDialog(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::RareMonsterList(pkt) => pkt.as_bytes(),
SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(),
SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(),
SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(),
SendShipPacket::LobbyEvent(pkt) => pkt.as_bytes(),
SendShipPacket::LargeDialog(pkt) => pkt.as_bytes(),
}
}
}
#[derive(Clone)]
pub struct ItemShops {
pub weapon_shop: HashMap<(room::Difficulty, SectionID), Arc<Mutex<WeaponShop<rand_chacha::ChaCha20Rng>>>>,
pub tool_shop: Arc<Mutex<ToolShop<rand_chacha::ChaCha20Rng>>>,
pub armor_shop: Arc<Mutex<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), Arc::new(Mutex::new(WeaponShop::new(*d, *id))));
}
}
ItemShops {
weapon_shop,
tool_shop: Arc::new(Mutex::new(ToolShop::default())),
armor_shop: Arc::new(Mutex::new(ArmorShop::default())),
}
}
}
pub struct ShipServerStateBuilder<EG: EntityGateway + Clone + 'static> {
entity_gateway: Option<EG>,
name: Option<String>,
ip: Option<Ipv4Addr>,
port: Option<u16>,
auth_token: Option<AuthToken>,
event: Option<ShipEvent>,
map_builder: Option<Box<dyn Fn(room::RoomMode, ShipEvent) -> Maps + Send + Sync>>,
drop_table_builder: Option<Box<dyn Fn(room::Episode, room::Difficulty, SectionID) -> DropTable + Send + Sync>>,
num_blocks: usize,
}
impl<EG: EntityGateway + Clone + 'static> Default for ShipServerStateBuilder<EG> {
fn default() -> ShipServerStateBuilder<EG> {
ShipServerStateBuilder {
entity_gateway: None,
name: None,
ip: None,
port: None,
auth_token: None,
event: None,
map_builder: None,
drop_table_builder: None,
num_blocks: 2,
}
}
}
impl<EG: EntityGateway + Clone + 'static> ShipServerStateBuilder<EG> {
#[must_use]
pub fn gateway(mut self, entity_gateway: EG) -> ShipServerStateBuilder<EG> {
self.entity_gateway = Some(entity_gateway);
self
}
#[must_use]
pub fn name(mut self, name: String) -> ShipServerStateBuilder<EG> {
self.name = Some(name);
self
}
#[must_use]
pub fn ip(mut self, ip: Ipv4Addr) -> ShipServerStateBuilder<EG> {
self.ip = Some(ip);
self
}
#[must_use]
pub fn port(mut self, port: u16) -> ShipServerStateBuilder<EG> {
self.port = Some(port);
self
}
#[must_use]
pub fn auth_token(mut self, auth_token: AuthToken) -> ShipServerStateBuilder<EG> {
self.auth_token = Some(auth_token);
self
}
#[must_use]
pub fn event(mut self, event: ShipEvent) -> ShipServerStateBuilder<EG> {
self.event = Some(event);
self
}
#[must_use]
pub fn map_builder(mut self, map_builder: Box<dyn Fn(room::RoomMode, ShipEvent) -> Maps + Send + Sync>) -> ShipServerStateBuilder<EG> {
self.map_builder = Some(map_builder);
self
}
#[must_use]
pub fn drop_table_builder(mut self, drop_table_builder: Box<dyn Fn(room::Episode, room::Difficulty, SectionID) -> DropTable + Send + Sync>) -> ShipServerStateBuilder<EG> {
self.drop_table_builder = Some(drop_table_builder);
self
}
#[must_use]
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: Clients::default(),
name: self.name.unwrap_or_else(|| "NAMENOTSET".into()),
item_state: items::state::ItemState::default(),
ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)),
port: self.port.unwrap_or(SHIP_PORT),
shops: ItemShops::default(),
blocks: Blocks(blocks),
event: self.event.unwrap_or(ShipEvent::None),
map_builder: Arc::new(self.map_builder.unwrap_or(Box::new(generate_free_roam_maps))),
drop_table_builder: Arc::new(self.drop_table_builder.unwrap_or(Box::new(DropTable::new))),
auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())),
ship_list: Arc::new(RwLock::new(Vec::new())),
shipgate_sender: None,
trades: Default::default(),
}
}
}
#[derive(Clone, Default)]
pub struct Block {
client_location: ClientLocation,
pub rooms: room::Rooms,
}
#[derive(Clone)]
pub struct Blocks(pub Vec<Block>);
impl Blocks {
async fn get_from_client(&mut self, id: ClientId, clients: &Clients) -> Result<&mut Block, anyhow::Error> {
let block = clients.with(id, |client| Box::pin(async move {
client.block
})).await?;
self.0
.get_mut(block)
.ok_or_else(|| ShipError::InvalidBlock(block).into())
}
}
#[derive(Clone)]
pub struct ShipServerState<EG: EntityGateway + Clone + 'static> {
pub(crate) entity_gateway: EG,
pub clients: Clients,
name: String,
pub(crate) item_state: items::state::ItemState,
shops: ItemShops,
pub blocks: Blocks,
event: ShipEvent,
ip: Ipv4Addr,
port: u16,
auth_token: AuthToken,
ship_list: Arc<RwLock<Vec<Ship>>>,
shipgate_sender: Option<channel::Sender<ShipMessage>>,
trades: TradeState,
map_builder: Arc<Box<dyn Fn(room::RoomMode, ShipEvent) -> Maps + Send + Sync>>,
drop_table_builder: Arc<Box<dyn Fn(room::Episode, room::Difficulty, SectionID) -> DropTable + Send + Sync>>,
}
impl<EG: EntityGateway + Clone + 'static> ShipServerState<EG> {
pub fn builder() -> ShipServerStateBuilder<EG> {
ShipServerStateBuilder::default()
}
async fn message(&mut self, id: ClientId, msg: Message) -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
Ok(match msg.msg {
GameMessage::RequestExp(request_exp) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::request_exp(id, request_exp, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms).await?
},
GameMessage::PlayerDropItem(player_drop_item) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::player_drop_item(id, player_drop_item, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms, &mut self.item_state).await?
},
GameMessage::DropCoordinates(drop_coordinates) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::drop_coordinates(id, drop_coordinates, &block.client_location, &self.clients, &block.rooms).await?
},
GameMessage::PlayerNoLongerHasItem(no_longer_has_item) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::no_longer_has_item(id, no_longer_has_item, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).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.get_from_client(id, &self.clients).await?;
handler::message::update_player_position(id, msg, &self.clients, &block.client_location, &block.rooms).await?
},
GameMessage::ChargeAttack(charge_attack) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::charge_attack(id, charge_attack, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await?
},
GameMessage::PlayerUseItem(player_use_item) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::player_uses_item(id, player_use_item, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await?
},
GameMessage::PlayerUsedMedicalCenter(player_used_medical_center) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::player_used_medical_center(id, player_used_medical_center, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await?
},
GameMessage::PlayerFeedMag(player_feed_mag) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::player_feed_mag(id, player_feed_mag, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await?
},
GameMessage::PlayerEquipItem(player_equip_item) => {
handler::message::player_equips_item(id, player_equip_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await?
},
GameMessage::PlayerUnequipItem(player_unequip_item) => {
handler::message::player_unequips_item(id, player_unequip_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await?
},
GameMessage::SortItems(sort_items) => {
handler::message::player_sorts_items(id, sort_items, &mut self.entity_gateway, &self.clients, &mut self.item_state).await?
},
GameMessage::PlayerSoldItem(player_sold_item) => {
handler::message::player_sells_item(id, player_sold_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await?
},
GameMessage::FloorItemLimitItemDeletion(floor_item_limit_delete) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::message::floor_item_limit_deletion(id, floor_item_limit_delete, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms, &mut self.item_state).await?
},
_ => {
let cmsg = msg.clone();
let block = self.blocks.get_from_client(id, &self.clients).await?;
block.client_location.get_client_neighbors(id).await.unwrap().into_iter()
.map(move |client| {
(client.client, SendShipPacket::Message(cmsg.clone()))
})
.collect()
},
})
}
async fn direct_message(&mut self, id: ClientId, msg: DirectMessage) -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let target = msg.flag;
let block = self.blocks.get_from_client(id, &self.clients).await?;
Ok(match msg.msg {
GameMessage::GuildcardSend(guildcard_send) => {
handler::direct_message::guildcard_send(id, guildcard_send, target, &block.client_location, &self.clients).await?
},
GameMessage::RequestItem(request_item) => {
handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms, &mut self.item_state).await?
},
GameMessage::PickupItem(pickup_item) => {
handler::direct_message::pickup_item(id, pickup_item, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await?
},
GameMessage::BoxDropRequest(box_drop_request) => {
handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &block.client_location, &self.clients, &block.rooms, &mut self.item_state).await?
},
GameMessage::BankRequest(_bank_request) => {
handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_state).await?
},
GameMessage::BankInteraction(bank_interaction) => {
handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await?
},
GameMessage::ShopRequest(shop_request) => {
handler::direct_message::shop_request(id, shop_request, &block.client_location, &self.clients, &block.rooms, &self.shops).await?
},
GameMessage::BuyItem(buy_item) => {
handler::direct_message::buy_item(id, buy_item, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await?
},
GameMessage::TekRequest(tek_request) => {
handler::direct_message::request_tek_item(id, tek_request, &mut self.entity_gateway, &self.clients, &mut self.item_state).await?
},
GameMessage::TekAccept(tek_accept) => {
handler::direct_message::accept_tek_item(id, tek_accept, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await?
},
GameMessage::TradeRequest(trade_request) => {
handler::trade::trade_request(id, trade_request, target, &block.client_location, &self.clients, &mut self.item_state, &mut self.trades).await?
},
_ => {
let cmsg = msg.clone();
block.client_location.get_all_clients_by_client(id).await.unwrap().into_iter()
.filter(move |client| client.local_client.id() == target as u8)
.map(move |client| {
(client.client, SendShipPacket::DirectMessage(cmsg.clone()))
})
.collect()
},
})
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
type SendPacket = SendShipPacket;
type RecvPacket = RecvShipPacket;
type Cipher = PSOBBCipher;
type PacketError = anyhow::Error;
async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, 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(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key),
PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))
])
}
async fn handle(&mut self, id: ClientId, pkt: RecvShipPacket) -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
if let Ok((char_id, char_playtime)) = self.clients.with_mut(id, |client| Box::pin(async move {
client.update_playtime();
(client.character.id, client.character.playtime)
})).await {
self.entity_gateway.set_character_playtime(&char_id, char_playtime).await?;
}
Ok(match pkt {
RecvShipPacket::Login(login) => {
handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_state, &self.shipgate_sender, &self.name, self.blocks.0.len())
.await?
.into_iter()
.map(move |pkt| (id, pkt))
.collect()
},
RecvShipPacket::QuestDetailRequest(questdetailrequest) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
match questdetailrequest.menu {
QUEST_SELECT_MENU_ID => handler::quest::quest_detail(id, questdetailrequest, &block.client_location, &block.rooms).await?,
_ => unreachable!(),
}
},
RecvShipPacket::MenuSelect(menuselect) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
match menuselect.menu {
SHIP_MENU_ID => {
let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).await.into_iter().flatten();
let select_ship = handler::ship::selected_ship(id, menuselect, &self.ship_list).await?;
leave_lobby.chain(select_ship).collect()
}
BLOCK_MENU_ID => {
let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).await.into_iter().flatten();
let select_block = handler::lobby::block_selected(id, menuselect, &self.clients, &self.item_state).await?.into_iter();
leave_lobby.chain(select_block).collect()
}
ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?,
QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &block.rooms).await?,
_ => unreachable!(),
}
},
RecvShipPacket::QuestMenuSelect(questmenuselect) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::quest::player_chose_quest(id, questmenuselect, &self.clients, &block.client_location, &block.rooms, self.event).await?
},
RecvShipPacket::MenuDetail(menudetail) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::lobby::get_room_tab_info(id, menudetail, &mut block.client_location, &self.clients).await?
},
RecvShipPacket::RoomPasswordReq(room_password_req) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
let room_password = block.rooms.with(RoomId(room_password_req.item as usize), |room| Box::pin(async move {
room.password
})).await?;
if room_password_req.password == room_password {
let menuselect = MenuSelect {
menu: room_password_req.menu,
item: room_password_req.item,
};
handler::room::join_room(id, menuselect, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?
}
else {
vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))]
}
},
RecvShipPacket::CharData(chardata) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_state, self.event).await?
},
RecvShipPacket::Message(msg) => {
self.message(id, msg).await?
},
RecvShipPacket::DirectMessage(msg) => {
self.direct_message(id, msg).await?
},
RecvShipPacket::PlayerChat(msg) => {
match chatcommand::handle_chat_command(id, msg.clone(), self).await {
Some(ccmd) => {
match ccmd {
Ok(pkts) => pkts,
Err(msg) => vec![(id, SendShipPacket::LargeDialog(LargeDialog::new(msg)))]
}
},
None => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::communication::player_chat(id, msg, &block.client_location, &self.clients).await?
}
}
},
RecvShipPacket::CreateRoom(create_room) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::room::create_room(id, create_room, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state,
&block.rooms, self.map_builder.clone(), self.drop_table_builder.clone(), self.event).await?
},
RecvShipPacket::RoomNameRequest(_req) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::room::room_name_request(id, &block.client_location, &block.rooms).await?
},
RecvShipPacket::UpdateTechMenu(pkt) => {
handler::settings::update_tech_menu(id, pkt, &self.clients, &mut self.entity_gateway).await?
},
RecvShipPacket::UpdateConfig(pkt) => {
handler::settings::update_config(id, pkt, &self.clients, &mut self.entity_gateway).await?
},
RecvShipPacket::ViewInfoboardRequest(_pkt) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::communication::request_infoboard(id, &block.client_location, &self.clients).await?
},
RecvShipPacket::WriteInfoboard(pkt) => {
handler::communication::write_infoboard(id, pkt, &self.clients, &mut self.entity_gateway).await?
},
RecvShipPacket::RoomListRequest(_req) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::room::request_room_list(id, &block.client_location, &block.rooms).await
},
RecvShipPacket::Like62ButCooler(cool62) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::room::cool_62(id, cool62, &block.client_location).await?
},
RecvShipPacket::ClientCharacterData(_) => {
// TOOD: validate this in some way?
Vec::new()
},
RecvShipPacket::DoneBursting(_) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::room::done_bursting(id, &block.client_location, &block.rooms).await?
},
RecvShipPacket::DoneBursting2(_) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::room::done_bursting(id, &block.client_location, &block.rooms).await?
},
RecvShipPacket::LobbySelect(pkt) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, &mut self.entity_gateway, self.event).await?
},
RecvShipPacket::RequestQuestList(rql) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::quest::send_quest_category_list(id, rql, &block.client_location, &block.rooms).await?
},
RecvShipPacket::QuestFileRequest(quest_file_request) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::quest::quest_file_request(id, quest_file_request, &block.client_location, &mut block.rooms).await?
},
RecvShipPacket::QuestChunkAck(quest_chunk_ack) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::quest::quest_chunk_ack(id, quest_chunk_ack, &block.client_location, &block.rooms).await?
},
RecvShipPacket::DoneLoadingQuest(_) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::quest::done_loading_quest(id, &self.clients, &block.client_location).await?
},
RecvShipPacket::FullCharacterData(_full_character_data) => {
Vec::new()
},
RecvShipPacket::SaveOptions(save_options) => {
handler::settings::save_options(id, save_options, &self.clients, &mut self.entity_gateway).await?
},
RecvShipPacket::RequestShipList(_) => {
handler::ship::ship_list(id, &self.ship_list).await
},
RecvShipPacket::RequestShipBlockList(_) => {
handler::ship::block_list(id, &self.name, self.blocks.0.len())
},
RecvShipPacket::ItemsToTrade(items_to_trade) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::trade::items_to_trade(id, items_to_trade, &block.client_location, &self.clients, &mut self.item_state, &mut self.trades).await?
},
RecvShipPacket::TradeConfirmed(_) => {
let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state, &mut self.trades).await?
},
RecvShipPacket::KeyboardConfig(keyboard_config) => {
handler::settings::keyboard_config(id, keyboard_config, &self.clients, &mut self.entity_gateway).await?
},
RecvShipPacket::GamepadConfig(gamepad_config) => {
handler::settings::gamepad_config(id, gamepad_config, &self.clients, &mut self.entity_gateway).await?
},
})
}
async fn on_disconnect(&mut self, id: ClientId) -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let block = self.blocks.get_from_client(id, &self.clients).await?;
let area_client = block.client_location.get_local_client(id).await?;
let neighbors = block.client_location.get_client_neighbors(id).await?;
let pkt = match block.client_location.get_area(id).await? {
RoomLobby::Room(room) => {
let character_id = self.clients.with(id, |client| Box::pin(async {
client.character.id
})).await?;
block.rooms.with(room, |room| {
let mut entity_gateway = self.entity_gateway.clone();
Box::pin(async move {
entity_gateway.add_room_note(room.room_id, RoomNote::PlayerJoin {
character_id,
}).await
})}).await;
if neighbors.is_empty() {
block.rooms.remove(room).await;
}
let leader = block.client_location.get_room_leader(room).await?;
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).await?;
SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id()))
}
};
if let Some(mut client) = self.clients.remove(&id).await {
client.user.at_ship = false;
self.entity_gateway.save_user(&client.user).await;
if let Some(shipgate_sender) = self.shipgate_sender.as_ref() {
shipgate_sender.send(ShipMessage::RemoveUser(client.user.id)).await;
}
self.item_state.remove_character_from_room(&client.character).await
}
block.client_location.remove_client_from_area(id).await?;
Ok(neighbors.into_iter().map(|n| {
(n.client, pkt.clone())
}).collect())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway + Clone> 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 on_action(&mut self, _id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error> {
match msg {
LoginMessage::SendMail{..} => {
Ok(Vec::new())
},
LoginMessage::ShipList{mut ships} => {
let mut ship_list = self.ship_list
.write()
.await;
ship_list.clear();
ship_list.append(&mut ships);
Ok(Vec::new())
},
LoginMessage::RequestUsers => {
/*
Ok(self.clients.iter()
.map(|(_, client)| {
(id, ShipMessage::AddUser(client.user.id))
})
.collect())
*/
// TODO
Ok(Vec::new())
}
}
}
async fn on_disconnect(&mut self, _id: ServerId) -> Vec<(ServerId, Self::SendMessage)> {
Vec::new()
}
async fn set_sender(&mut self, _server_id: ServerId, sender: channel::Sender<Self::SendMessage>) {
self.shipgate_sender = Some(sender);
}
}