mirror of
https://github.com/redstrate/Kawari.git
synced 2025-05-07 05:07:44 +00:00
Instead of one single slot available in your inventory, all four pages should be available now. Moving items around should be less buggy, and it's now possible to discard items. Items cannot stack still, and when given will always take up the next free slot.
753 lines
26 KiB
Rust
753 lines
26 KiB
Rust
use std::{
|
|
net::SocketAddr,
|
|
sync::{
|
|
Arc, Mutex,
|
|
atomic::{AtomicUsize, Ordering},
|
|
},
|
|
};
|
|
|
|
use tokio::{net::TcpStream, sync::mpsc::Sender};
|
|
|
|
use crate::{
|
|
common::{GameData, ObjectId, Position, timestamp_secs},
|
|
opcodes::ServerZoneIpcType,
|
|
packet::{
|
|
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, parse_packet,
|
|
send_packet,
|
|
},
|
|
};
|
|
|
|
use super::{
|
|
Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone,
|
|
inventory::Container,
|
|
ipc::{
|
|
ActorControlSelf, ActorMove, ActorSetPos, ClientZoneIpcSegment, CommonSpawn, ContainerInfo,
|
|
ContainerType, DisplayFlag, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind, PlayerSubKind,
|
|
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo,
|
|
WeatherChange,
|
|
},
|
|
};
|
|
|
|
#[derive(Debug, Default, Clone)]
|
|
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,
|
|
pub inventory: Inventory,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
|
pub struct ClientId(usize);
|
|
|
|
pub enum FromServer {
|
|
/// A chat message.
|
|
Message(String),
|
|
/// An actor has been spawned.
|
|
ActorSpawn(Actor, CommonSpawn),
|
|
/// An actor moved to a new position.
|
|
ActorMove(u32, Position, f32),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ClientHandle {
|
|
pub id: ClientId,
|
|
pub ip: SocketAddr,
|
|
pub channel: Sender<FromServer>,
|
|
// TODO: restore, i guess
|
|
//pub kill: JoinHandle<()>,
|
|
}
|
|
|
|
impl ClientHandle {
|
|
/// Send a message to this client actor. Will emit an error if sending does
|
|
/// not succeed immediately, as this means that forwarding messages to the
|
|
/// tcp connection cannot keep up.
|
|
pub fn send(&mut self, msg: FromServer) -> Result<(), std::io::Error> {
|
|
if self.channel.try_send(msg).is_err() {
|
|
Err(std::io::Error::new(
|
|
std::io::ErrorKind::BrokenPipe,
|
|
"Can't keep up or dead",
|
|
))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Kill the actor.
|
|
pub fn kill(self) {
|
|
// run the destructor
|
|
drop(self);
|
|
}
|
|
}
|
|
|
|
pub enum ToServer {
|
|
NewClient(ClientHandle),
|
|
Message(ClientId, String),
|
|
ActorSpawned(ClientId, Actor, CommonSpawn),
|
|
ActorMoved(ClientId, u32, Position, f32),
|
|
ZoneLoaded(ClientId),
|
|
FatalError(std::io::Error),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ServerHandle {
|
|
pub chan: Sender<ToServer>,
|
|
pub next_id: Arc<AtomicUsize>,
|
|
}
|
|
|
|
impl ServerHandle {
|
|
pub async fn send(&mut self, msg: ToServer) {
|
|
if self.chan.send(msg).await.is_err() {
|
|
panic!("Main loop has shut down.");
|
|
}
|
|
}
|
|
pub fn next_id(&self) -> ClientId {
|
|
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
|
|
ClientId(id)
|
|
}
|
|
}
|
|
|
|
/// 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 status_effects: StatusEffects,
|
|
|
|
pub event: Option<Event>,
|
|
pub actors: Vec<Actor>,
|
|
|
|
pub ip: SocketAddr,
|
|
pub id: ClientId,
|
|
pub handle: ServerHandle,
|
|
|
|
pub database: Arc<WorldDatabase>,
|
|
pub lua: Arc<Mutex<mlua::Lua>>,
|
|
pub gamedata: Arc<Mutex<GameData>>,
|
|
}
|
|
|
|
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 send_chat_segment(&mut self, segment: PacketSegment<ServerZoneIpcSegment>) {
|
|
send_packet(
|
|
&mut self.socket,
|
|
&mut self.state,
|
|
ConnectionType::Chat,
|
|
CompressionType::Oodle,
|
|
&[segment],
|
|
)
|
|
.await;
|
|
}
|
|
|
|
pub async fn initialize(&mut self, connection_type: &ConnectionType, actor_id: u32) {
|
|
// some still hardcoded values
|
|
self.player_data.classjob_id = 1;
|
|
self.player_data.level = 5;
|
|
self.player_data.curr_hp = 100;
|
|
self.player_data.max_hp = 100;
|
|
self.player_data.curr_mp = 10000;
|
|
self.player_data.max_mp = 10000;
|
|
|
|
match connection_type {
|
|
ConnectionType::Zone => {
|
|
tracing::info!("Client {actor_id} is initializing zone session...");
|
|
|
|
// We have send THEM a keep alive
|
|
{
|
|
self.send_segment(PacketSegment {
|
|
source_actor: 0,
|
|
target_actor: 0,
|
|
segment_type: SegmentType::KeepAlive {
|
|
id: 0xE0037603u32,
|
|
timestamp: timestamp_secs(),
|
|
},
|
|
})
|
|
.await;
|
|
}
|
|
|
|
self.send_segment(PacketSegment {
|
|
source_actor: 0,
|
|
target_actor: 0,
|
|
segment_type: SegmentType::ZoneInitialize {
|
|
player_id: self.player_data.actor_id,
|
|
timestamp: timestamp_secs(),
|
|
},
|
|
})
|
|
.await;
|
|
}
|
|
ConnectionType::Chat => {
|
|
tracing::info!("Client {actor_id} is initializing chat session...");
|
|
|
|
// We have send THEM a keep alive
|
|
{
|
|
self.send_chat_segment(PacketSegment {
|
|
source_actor: 0,
|
|
target_actor: 0,
|
|
segment_type: SegmentType::KeepAlive {
|
|
id: 0xE0037603u32,
|
|
timestamp: timestamp_secs(),
|
|
},
|
|
})
|
|
.await;
|
|
}
|
|
|
|
{
|
|
self.send_chat_segment(PacketSegment {
|
|
source_actor: 0,
|
|
target_actor: 0,
|
|
segment_type: SegmentType::ZoneInitialize {
|
|
player_id: self.player_data.actor_id,
|
|
timestamp: timestamp_secs(),
|
|
},
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// we need the actor id at this point!
|
|
assert!(self.player_data.actor_id != 0);
|
|
|
|
{
|
|
let ipc = ServerZoneIpcSegment {
|
|
op_code: ServerZoneIpcType::InitializeChat,
|
|
timestamp: timestamp_secs(),
|
|
data: ServerZoneIpcData::InitializeChat { unk: [0; 8] },
|
|
..Default::default()
|
|
};
|
|
|
|
self.send_chat_segment(PacketSegment {
|
|
source_actor: self.player_data.actor_id,
|
|
target_actor: self.player_data.actor_id,
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
})
|
|
.await;
|
|
}
|
|
}
|
|
_ => panic!("The client is trying to initialize the wrong connection?!"),
|
|
}
|
|
}
|
|
|
|
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 set_actor_position(&mut self, actor_id: u32, position: Position, rotation: f32) {
|
|
let ipc = ServerZoneIpcSegment {
|
|
op_code: ServerZoneIpcType::ActorMove,
|
|
timestamp: timestamp_secs(),
|
|
data: ServerZoneIpcData::ActorMove(ActorMove {
|
|
speed: 24,
|
|
position,
|
|
..Default::default()
|
|
}),
|
|
..Default::default()
|
|
};
|
|
|
|
self.send_segment(PacketSegment {
|
|
source_actor: actor_id,
|
|
target_actor: actor_id,
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
})
|
|
.await;
|
|
}
|
|
|
|
pub async fn spawn_actor(&mut self, actor: Actor, mut common: CommonSpawn) {
|
|
// There is no reason for us to spawn our own player again. It's probably a bug!'
|
|
assert!(actor.id.0 != self.player_data.actor_id);
|
|
|
|
common.spawn_index = self.get_free_spawn_index();
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
unk1: 20,
|
|
unk2: 0,
|
|
op_code: ServerZoneIpcType::NpcSpawn,
|
|
server_id: 0,
|
|
timestamp: timestamp_secs(),
|
|
data: ServerZoneIpcData::NpcSpawn(NpcSpawn {
|
|
common,
|
|
..Default::default()
|
|
}),
|
|
};
|
|
|
|
self.send_segment(PacketSegment {
|
|
source_actor: actor.id.0,
|
|
target_actor: self.player_data.actor_id,
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
})
|
|
.await;
|
|
}
|
|
|
|
pub async fn update_class_info(&mut self) {
|
|
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;
|
|
}
|
|
|
|
pub async fn change_zone(&mut self, new_zone_id: u16) {
|
|
{
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
|
self.zone = Some(Zone::load(&mut game_data.game_data, new_zone_id));
|
|
}
|
|
self.player_data.zone_id = new_zone_id;
|
|
|
|
// Player Class Info
|
|
self.update_class_info().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;
|
|
self.spawn_index
|
|
}
|
|
|
|
pub async fn send_inventory(&mut self, send_appearance_update: bool) {
|
|
let mut sequence = 0;
|
|
|
|
// pages
|
|
for (i, page) in self.player_data.inventory.pages.clone().iter().enumerate() {
|
|
let kind = match i {
|
|
0 => ContainerType::Inventory0,
|
|
1 => ContainerType::Inventory1,
|
|
2 => ContainerType::Inventory2,
|
|
3 => ContainerType::Inventory3,
|
|
_ => panic!("Shouldn't be anything else!"),
|
|
};
|
|
|
|
let mut send_slot = async |slot_index: u16, item: &Item| {
|
|
let ipc = ServerZoneIpcSegment {
|
|
op_code: ServerZoneIpcType::ItemInfo,
|
|
timestamp: timestamp_secs(),
|
|
data: ServerZoneIpcData::ItemInfo(ItemInfo {
|
|
sequence,
|
|
container: kind,
|
|
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;
|
|
};
|
|
|
|
for (i, slot) in page.slots.iter().enumerate() {
|
|
send_slot(i as u16, slot).await;
|
|
}
|
|
|
|
// inform the client of container state
|
|
{
|
|
let ipc = ServerZoneIpcSegment {
|
|
op_code: ServerZoneIpcType::ContainerInfo,
|
|
timestamp: timestamp_secs(),
|
|
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
|
container: kind,
|
|
num_items: page.num_items(),
|
|
sequence,
|
|
..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;
|
|
}
|
|
|
|
sequence += 1;
|
|
}
|
|
|
|
// equipped
|
|
{
|
|
let equipped = self.player_data.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 {
|
|
sequence,
|
|
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;
|
|
};
|
|
|
|
// TODO: make containers enumerable to vastly simplify this code
|
|
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.player_data.inventory.equipped.num_items(),
|
|
sequence,
|
|
..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 them an appearance update
|
|
if send_appearance_update {
|
|
let ipc;
|
|
{
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
|
let equipped = &self.player_data.inventory.equipped;
|
|
|
|
ipc = ServerZoneIpcSegment {
|
|
op_code: ServerZoneIpcType::Equip,
|
|
timestamp: timestamp_secs(),
|
|
data: ServerZoneIpcData::Equip(Equip {
|
|
main_weapon_id: 0,
|
|
sub_weapon_id: 0,
|
|
crest_enable: 0,
|
|
pattern_invalid: 0,
|
|
model_ids: [
|
|
game_data.get_primary_model_id(equipped.head.id) as u32,
|
|
game_data.get_primary_model_id(equipped.body.id) as u32,
|
|
game_data.get_primary_model_id(equipped.hands.id) as u32,
|
|
game_data.get_primary_model_id(equipped.legs.id) as u32,
|
|
game_data.get_primary_model_id(equipped.feet.id) as u32,
|
|
game_data.get_primary_model_id(equipped.ears.id) as u32,
|
|
game_data.get_primary_model_id(equipped.neck.id) as u32,
|
|
game_data.get_primary_model_id(equipped.wrists.id) as u32,
|
|
game_data.get_primary_model_id(equipped.left_ring.id) as u32,
|
|
game_data.get_primary_model_id(equipped.right_ring.id) as u32,
|
|
],
|
|
}),
|
|
..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 {
|
|
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);
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub async fn actor_control_self(&mut self, actor_control: ActorControlSelf) {
|
|
let ipc = ServerZoneIpcSegment {
|
|
op_code: ServerZoneIpcType::ActorControlSelf,
|
|
timestamp: timestamp_secs(),
|
|
data: ServerZoneIpcData::ActorControlSelf(actor_control),
|
|
..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_player_common_spawn(
|
|
&self,
|
|
exit_position: Option<Position>,
|
|
exit_rotation: Option<f32>,
|
|
) -> CommonSpawn {
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
|
|
|
let chara_details = self.database.find_chara_make(self.player_data.content_id);
|
|
|
|
let equipped = &self.player_data.inventory.equipped;
|
|
CommonSpawn {
|
|
class_job: self.player_data.classjob_id,
|
|
name: chara_details.name,
|
|
hp_curr: self.player_data.curr_hp,
|
|
hp_max: self.player_data.max_hp,
|
|
mp_curr: self.player_data.curr_mp,
|
|
mp_max: self.player_data.max_mp,
|
|
level: self.player_data.level,
|
|
object_kind: ObjectKind::Player(PlayerSubKind::Player),
|
|
look: chara_details.chara_make.customize,
|
|
display_flags: DisplayFlag::UNK,
|
|
models: [
|
|
game_data.get_primary_model_id(equipped.head.id) as u32,
|
|
game_data.get_primary_model_id(equipped.body.id) as u32,
|
|
game_data.get_primary_model_id(equipped.hands.id) as u32,
|
|
game_data.get_primary_model_id(equipped.legs.id) as u32,
|
|
game_data.get_primary_model_id(equipped.feet.id) as u32,
|
|
game_data.get_primary_model_id(equipped.ears.id) as u32,
|
|
game_data.get_primary_model_id(equipped.neck.id) as u32,
|
|
game_data.get_primary_model_id(equipped.wrists.id) as u32,
|
|
game_data.get_primary_model_id(equipped.left_ring.id) as u32,
|
|
game_data.get_primary_model_id(equipped.right_ring.id) as u32,
|
|
],
|
|
pos: exit_position.unwrap_or(Position::default()),
|
|
rotation: exit_rotation.unwrap_or(0.0),
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|