1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-21 15:07:45 +00:00
kawari/src/world/connection.rs

385 lines
12 KiB
Rust
Raw Normal View History

use tokio::net::TcpStream;
use crate::{
common::{ObjectId, Position, timestamp_secs},
opcodes::ServerZoneIpcType,
packet::{
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, parse_packet,
send_packet,
},
};
use super::{
Actor, Event, Inventory, Item, LuaPlayer, Zone,
ipc::{
ActorSetPos, ClientZoneIpcSegment, ContainerInfo, ContainerType, InitZone, ItemInfo,
2025-03-27 23:32:36 -04:00
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo,
WeatherChange,
},
};
#[derive(Debug, Default, Clone, Copy)]
pub struct PlayerData {
// Static data
pub actor_id: u32,
pub content_id: u64,
pub account_id: u32,
pub classjob_id: u8,
pub level: u8,
pub curr_hp: u32,
pub max_hp: u32,
pub curr_mp: u16,
pub max_mp: u16,
// Dynamic data
pub position: Position,
/// In radians.
pub rotation: f32,
pub zone_id: u16,
}
#[derive(Debug, Default, Clone)]
pub struct StatusEffects {
pub status_effects: Vec<StatusEffect>,
/// If the list is dirty and must be propagated to the client
pub dirty: bool,
}
impl StatusEffects {
pub fn add(&mut self, effect_id: u16, duration: f32) {
let status_effect = self.find_or_create_status_effect(effect_id);
status_effect.duration = duration;
self.dirty = true
}
fn find_or_create_status_effect(&mut self, effect_id: u16) -> &mut StatusEffect {
if let Some(i) = self
.status_effects
.iter()
.position(|effect| effect.effect_id == effect_id)
{
&mut self.status_effects[i]
} else {
self.status_effects.push(StatusEffect {
effect_id,
..Default::default()
});
self.status_effects.last_mut().unwrap()
}
}
}
/// Represents a single connection between an instance of the client and the world server
pub struct ZoneConnection {
pub socket: TcpStream,
pub state: PacketState,
pub player_data: PlayerData,
pub zone: Option<Zone>,
pub spawn_index: u8,
pub inventory: Inventory,
pub status_effects: StatusEffects,
pub event: Option<Event>,
pub actors: Vec<Actor>,
}
impl ZoneConnection {
pub async fn parse_packet(
&mut self,
data: &[u8],
) -> (Vec<PacketSegment<ClientZoneIpcSegment>>, ConnectionType) {
parse_packet(data, &mut self.state).await
}
pub async fn send_segment(&mut self, segment: PacketSegment<ServerZoneIpcSegment>) {
send_packet(
&mut self.socket,
&mut self.state,
ConnectionType::Zone,
CompressionType::Oodle,
&[segment],
)
.await;
}
pub async fn set_player_position(&mut self, position: Position) {
// set pos
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ActorSetPos,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ActorSetPos(ActorSetPos {
unk: 0x020fa3b8,
position,
..Default::default()
}),
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
}
pub async fn change_zone(&mut self, new_zone_id: u16) {
self.zone = Some(Zone::load(new_zone_id));
self.player_data.zone_id = new_zone_id;
// Player Class Info
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::UpdateClassInfo,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo {
class_id: self.player_data.classjob_id as u16,
unknown: 1,
synced_level: self.player_data.level as u16,
class_level: self.player_data.level as u16,
..Default::default()
}),
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
// link shell information
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::LinkShellInformation,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::LinkShellInformation { unk: [0; 456] },
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
// TODO: send unk16?
// Init Zone
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::InitZone,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::InitZone(InitZone {
server_id: 0,
zone_id: self.zone.as_ref().unwrap().id,
weather_id: 1,
..Default::default()
}),
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
}
pub async fn change_weather(&mut self, new_weather_id: u16) {
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::WeatherChange,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::WeatherChange(WeatherChange {
weather_id: new_weather_id,
transistion_time: 1.0,
}),
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
pub fn get_free_spawn_index(&mut self) -> u8 {
self.spawn_index += 1;
2025-03-16 14:09:12 -04:00
self.spawn_index
}
pub async fn send_inventory(&mut self) {
// item list
{
let equipped = self.inventory.equipped;
let mut send_slot = async |slot_index: u16, item: &Item| {
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ItemInfo,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ItemInfo(ItemInfo {
container: ContainerType::Equipped,
slot: slot_index,
quantity: item.quantity,
catalog_id: item.id,
condition: 30000,
..Default::default()
}),
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
};
send_slot(0, &equipped.main_hand).await;
send_slot(1, &equipped.off_hand).await;
send_slot(2, &equipped.head).await;
send_slot(3, &equipped.body).await;
send_slot(4, &equipped.hands).await;
send_slot(6, &equipped.legs).await;
send_slot(7, &equipped.feet).await;
send_slot(8, &equipped.ears).await;
send_slot(9, &equipped.neck).await;
send_slot(10, &equipped.wrists).await;
send_slot(11, &equipped.right_ring).await;
send_slot(12, &equipped.left_ring).await;
send_slot(13, &equipped.soul_crystal).await;
}
// inform the client they have items equipped
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ContainerInfo,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
container: ContainerType::Equipped,
num_items: self.inventory.equipped.num_items(),
..Default::default()
}),
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
}
pub async fn send_message(&mut self, message: &str) {
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ServerChatMessage,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ServerChatMessage {
message: message.to_string(),
unk: 0,
},
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
pub async fn process_lua_player(&mut self, player: &mut LuaPlayer) {
for segment in &player.queued_segments {
self.send_segment(segment.clone()).await;
}
player.queued_segments.clear();
for task in &player.queued_tasks {
self.change_zone(task.zone_id).await;
}
player.queued_tasks.clear();
}
pub async fn process_effects_list(&mut self) {
// Only update the client if absolutely nessecary (e.g. an effect is added, removed or changed duration)
if self.status_effects.dirty {
let mut list = [StatusEffect::default(); 30];
list[..self.status_effects.status_effects.len()]
.copy_from_slice(&self.status_effects.status_effects);
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::StatusEffectList,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::StatusEffectList(StatusEffectList {
statues: list,
classjob_id: self.player_data.classjob_id,
level: self.player_data.level,
curr_hp: self.player_data.curr_hp,
max_hp: self.player_data.max_hp,
curr_mp: self.player_data.curr_mp,
max_mp: self.player_data.max_mp,
..Default::default()
}),
..Default::default()
};
self.send_segment(PacketSegment {
source_actor: self.player_data.actor_id,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
self.status_effects.dirty = false;
}
}
pub async fn update_hp_mp(&mut self, actor_id: ObjectId, hp: u32, mp: u16) {
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::UpdateHpMpTp,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::UpdateHpMpTp { hp, mp, unk: 0 },
..Default::default()
};
self.send_segment(PacketSegment {
2025-03-29 14:38:40 -04:00
source_actor: actor_id.0,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
pub fn add_actor(&mut self, actor: Actor) {
self.actors.push(actor);
}
pub fn get_actor(&mut self, id: ObjectId) -> Option<&mut Actor> {
for actor in &mut self.actors {
if actor.id == id {
return Some(actor);
}
}
return None;
}
}