2025-03-30 16:41:06 -04:00
|
|
|
use std::{
|
2025-06-21 10:36:44 -04:00
|
|
|
collections::HashMap,
|
2025-03-30 16:41:06 -04:00
|
|
|
net::SocketAddr,
|
2025-05-09 19:49:40 -04:00
|
|
|
sync::{Arc, Mutex},
|
2025-05-12 01:17:15 -04:00
|
|
|
time::Instant,
|
2025-03-30 16:41:06 -04:00
|
|
|
};
|
|
|
|
|
2025-06-21 10:36:44 -04:00
|
|
|
use mlua::Function;
|
2025-05-09 19:49:40 -04:00
|
|
|
use tokio::net::TcpStream;
|
2025-03-15 20:36:39 -04:00
|
|
|
|
|
|
|
use crate::{
|
2025-04-30 22:37:57 -04:00
|
|
|
OBFUSCATION_ENABLED_MODE,
|
2025-06-27 23:01:00 -04:00
|
|
|
common::{
|
|
|
|
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
|
|
|
|
},
|
2025-05-02 22:41:31 -04:00
|
|
|
config::{WorldConfig, get_config},
|
2025-06-28 09:56:04 -04:00
|
|
|
inventory::{ContainerType, Inventory, Item, Storage},
|
2025-05-02 22:41:31 -04:00
|
|
|
ipc::{
|
|
|
|
chat::ServerChatIpcSegment,
|
|
|
|
zone::{
|
2025-06-21 10:36:44 -04:00
|
|
|
ActionEffect, ActionRequest, ActionResult, ActorControl, ActorControlCategory,
|
2025-06-25 21:30:14 -04:00
|
|
|
ActorControlSelf, ActorControlTarget, ClientZoneIpcSegment, CommonSpawn, Config,
|
|
|
|
ContainerInfo, CurrencyInfo, DisplayFlag, EffectKind, Equip, GameMasterRank, InitZone,
|
|
|
|
ItemInfo, Move, NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind, ServerZoneIpcData,
|
2025-06-24 19:15:25 -04:00
|
|
|
ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, Warp,
|
|
|
|
WeatherChange,
|
2025-05-02 22:41:31 -04:00
|
|
|
},
|
2025-05-02 00:47:11 -04:00
|
|
|
},
|
2025-05-02 00:11:37 -04:00
|
|
|
opcodes::ServerZoneIpcType,
|
2025-03-15 20:36:39 -04:00
|
|
|
packet::{
|
2025-05-02 00:03:36 -04:00
|
|
|
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType,
|
|
|
|
parse_packet, send_packet,
|
2025-03-15 20:36:39 -04:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2025-03-16 17:43:29 -04:00
|
|
|
use super::{
|
2025-06-21 10:36:44 -04:00
|
|
|
Actor, CharacterData, EffectsBuilder, Event, LuaPlayer, StatusEffects, ToServer, WorldDatabase,
|
|
|
|
Zone,
|
2025-05-09 19:49:40 -04:00
|
|
|
common::{ClientId, ServerHandle},
|
2025-06-25 13:25:48 -04:00
|
|
|
load_init_script,
|
2025-05-09 19:49:40 -04:00
|
|
|
lua::Task,
|
2025-03-16 17:43:29 -04:00
|
|
|
};
|
2025-03-15 20:36:39 -04:00
|
|
|
|
2025-06-21 10:36:44 -04:00
|
|
|
#[derive(Default)]
|
|
|
|
pub struct ExtraLuaState {
|
|
|
|
pub action_scripts: HashMap<u32, String>,
|
|
|
|
pub event_scripts: HashMap<u32, String>,
|
|
|
|
pub command_scripts: HashMap<String, String>,
|
2025-06-28 09:56:04 -04:00
|
|
|
pub gm_command_scripts: HashMap<u32, String>,
|
2025-06-21 10:36:44 -04:00
|
|
|
}
|
|
|
|
|
2025-05-11 10:12:02 -04:00
|
|
|
#[derive(Debug, Default, Clone)]
|
|
|
|
pub struct TeleportQuery {
|
|
|
|
pub aetheryte_id: u16,
|
|
|
|
}
|
|
|
|
|
2025-04-01 21:37:41 -04:00
|
|
|
#[derive(Debug, Default, Clone)]
|
2025-03-21 19:56:16 -04:00
|
|
|
pub struct PlayerData {
|
2025-03-29 00:15:29 -04:00
|
|
|
// Static data
|
2025-03-21 19:56:16 -04:00
|
|
|
pub actor_id: u32,
|
|
|
|
pub content_id: u64,
|
|
|
|
pub account_id: u32,
|
2025-03-29 00:15:29 -04:00
|
|
|
|
2025-03-29 14:52:27 -04:00
|
|
|
pub classjob_id: u8,
|
2025-06-20 19:08:53 -04:00
|
|
|
pub classjob_levels: [i32; 32],
|
2025-06-22 09:18:14 -04:00
|
|
|
pub classjob_exp: [u32; 32],
|
2025-03-29 14:52:27 -04:00
|
|
|
pub curr_hp: u32,
|
|
|
|
pub max_hp: u32,
|
|
|
|
pub curr_mp: u16,
|
|
|
|
pub max_mp: u16,
|
|
|
|
|
2025-03-29 00:15:29 -04:00
|
|
|
// Dynamic data
|
|
|
|
pub position: Position,
|
|
|
|
/// In radians.
|
|
|
|
pub rotation: f32,
|
|
|
|
pub zone_id: u16,
|
2025-04-01 18:49:42 -04:00
|
|
|
pub inventory: Inventory,
|
2025-05-11 10:12:02 -04:00
|
|
|
|
|
|
|
pub teleport_query: TeleportQuery,
|
2025-05-12 15:44:39 -04:00
|
|
|
pub gm_rank: GameMasterRank,
|
2025-06-19 09:53:21 -04:00
|
|
|
pub gm_invisible: bool,
|
2025-06-27 23:01:00 -04:00
|
|
|
|
|
|
|
pub unlocks: Vec<u8>,
|
2025-06-27 23:24:30 -04:00
|
|
|
pub aetherytes: Vec<u8>,
|
2025-03-21 19:56:16 -04:00
|
|
|
}
|
|
|
|
|
2025-06-20 19:08:53 -04:00
|
|
|
impl PlayerData {
|
|
|
|
pub fn current_level(&self) -> i32 {
|
|
|
|
self.classjob_levels[self.classjob_id as usize]
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_current_level(&mut self, level: i32) {
|
|
|
|
self.classjob_levels[self.classjob_id as usize] = level;
|
|
|
|
}
|
2025-06-22 09:18:14 -04:00
|
|
|
|
|
|
|
pub fn current_exp(&self) -> u32 {
|
|
|
|
self.classjob_exp[self.classjob_id as usize]
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_current_exp(&mut self, exp: u32) {
|
|
|
|
self.classjob_exp[self.classjob_id as usize] = exp;
|
|
|
|
}
|
2025-06-20 19:08:53 -04:00
|
|
|
}
|
|
|
|
|
2025-03-17 17:02:53 -04:00
|
|
|
/// Represents a single connection between an instance of the client and the world server
|
2025-03-15 20:36:39 -04:00
|
|
|
pub struct ZoneConnection {
|
2025-05-02 22:41:31 -04:00
|
|
|
pub config: WorldConfig,
|
2025-03-15 20:36:39 -04:00
|
|
|
pub socket: TcpStream,
|
|
|
|
|
2025-03-16 17:43:29 -04:00
|
|
|
pub state: PacketState,
|
2025-03-21 19:56:16 -04:00
|
|
|
pub player_data: PlayerData,
|
2025-03-15 20:49:07 -04:00
|
|
|
|
2025-03-22 18:53:53 -04:00
|
|
|
pub zone: Option<Zone>,
|
2025-03-16 14:07:56 -04:00
|
|
|
pub spawn_index: u8,
|
2025-03-23 10:33:49 -04:00
|
|
|
|
2025-03-28 00:22:41 -04:00
|
|
|
pub status_effects: StatusEffects,
|
2025-03-28 21:28:30 -04:00
|
|
|
|
|
|
|
pub event: Option<Event>,
|
2025-03-29 12:25:22 -04:00
|
|
|
pub actors: Vec<Actor>,
|
2025-03-30 16:41:06 -04:00
|
|
|
|
|
|
|
pub ip: SocketAddr,
|
|
|
|
pub id: ClientId,
|
|
|
|
pub handle: ServerHandle,
|
|
|
|
|
|
|
|
pub database: Arc<WorldDatabase>,
|
|
|
|
pub lua: Arc<Mutex<mlua::Lua>>,
|
|
|
|
pub gamedata: Arc<Mutex<GameData>>,
|
2025-05-05 22:04:39 -04:00
|
|
|
|
|
|
|
pub exit_position: Option<Position>,
|
|
|
|
pub exit_rotation: Option<f32>,
|
2025-05-12 01:17:15 -04:00
|
|
|
|
|
|
|
pub last_keep_alive: Instant,
|
|
|
|
|
|
|
|
/// Whether the player was gracefully logged out
|
|
|
|
pub gracefully_logged_out: bool,
|
2025-06-28 09:56:04 -04:00
|
|
|
|
|
|
|
// TODO: really needs to be moved somewhere else
|
|
|
|
pub weather_id: u16,
|
2025-03-15 20:36:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ZoneConnection {
|
2025-06-26 19:09:13 -04:00
|
|
|
pub fn parse_packet(
|
2025-03-16 17:43:29 -04:00
|
|
|
&mut self,
|
|
|
|
data: &[u8],
|
|
|
|
) -> (Vec<PacketSegment<ClientZoneIpcSegment>>, ConnectionType) {
|
2025-06-26 19:09:13 -04:00
|
|
|
parse_packet(data, &mut self.state)
|
2025-03-15 20:36:39 -04:00
|
|
|
}
|
|
|
|
|
2025-03-16 17:43:29 -04:00
|
|
|
pub async fn send_segment(&mut self, segment: PacketSegment<ServerZoneIpcSegment>) {
|
2025-03-15 20:36:39 -04:00
|
|
|
send_packet(
|
|
|
|
&mut self.socket,
|
|
|
|
&mut self.state,
|
2025-03-18 19:49:52 -04:00
|
|
|
ConnectionType::Zone,
|
2025-05-02 22:41:31 -04:00
|
|
|
if self.config.enable_packet_compression {
|
|
|
|
CompressionType::Oodle
|
|
|
|
} else {
|
|
|
|
CompressionType::Uncompressed
|
|
|
|
},
|
2025-03-18 19:49:52 -04:00
|
|
|
&[segment],
|
2025-03-15 20:36:39 -04:00
|
|
|
)
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-05-01 23:34:06 -04:00
|
|
|
pub async fn send_chat_segment(&mut self, segment: PacketSegment<ServerChatIpcSegment>) {
|
2025-03-30 21:42:46 -04:00
|
|
|
send_packet(
|
|
|
|
&mut self.socket,
|
|
|
|
&mut self.state,
|
|
|
|
ConnectionType::Chat,
|
2025-05-02 22:41:31 -04:00
|
|
|
if self.config.enable_packet_compression {
|
|
|
|
CompressionType::Oodle
|
|
|
|
} else {
|
|
|
|
CompressionType::Uncompressed
|
|
|
|
},
|
2025-03-30 21:42:46 -04:00
|
|
|
&[segment],
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-05-02 00:11:37 -04:00
|
|
|
pub async fn initialize(&mut self, actor_id: u32) {
|
2025-03-30 10:49:33 -04:00
|
|
|
// some still hardcoded values
|
|
|
|
self.player_data.curr_hp = 100;
|
|
|
|
self.player_data.max_hp = 100;
|
|
|
|
self.player_data.curr_mp = 10000;
|
|
|
|
self.player_data.max_mp = 10000;
|
|
|
|
|
2025-05-02 00:11:37 -04:00
|
|
|
tracing::info!("Client {actor_id} is initializing zone session...");
|
2025-03-30 10:49:33 -04:00
|
|
|
|
2025-05-02 00:11:37 -04:00
|
|
|
// We have send THEM a keep alive
|
|
|
|
{
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
segment_type: SegmentType::KeepAliveRequest,
|
|
|
|
data: SegmentData::KeepAliveRequest {
|
|
|
|
id: 0xE0037603u32,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
},
|
2025-05-02 00:28:05 -04:00
|
|
|
..Default::default()
|
2025-05-02 00:11:37 -04:00
|
|
|
})
|
|
|
|
.await;
|
2025-03-30 10:49:33 -04:00
|
|
|
}
|
2025-05-02 00:11:37 -04:00
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
segment_type: SegmentType::Initialize,
|
|
|
|
data: SegmentData::Initialize {
|
|
|
|
player_id: self.player_data.actor_id,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
},
|
2025-05-02 00:28:05 -04:00
|
|
|
..Default::default()
|
2025-05-02 00:11:37 -04:00
|
|
|
})
|
|
|
|
.await;
|
2025-03-30 10:49:33 -04:00
|
|
|
}
|
|
|
|
|
2025-03-15 20:36:39 -04:00
|
|
|
pub async fn set_player_position(&mut self, position: Position) {
|
|
|
|
// set pos
|
|
|
|
{
|
2025-03-16 17:43:29 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
2025-05-01 23:20:56 -04:00
|
|
|
op_code: ServerZoneIpcType::Warp,
|
2025-03-15 20:36:39 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-05-02 23:24:31 -04:00
|
|
|
data: ServerZoneIpcData::Warp(Warp {
|
2025-03-15 20:36:39 -04:00
|
|
|
position,
|
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
2025-03-15 20:36:39 -04:00
|
|
|
};
|
|
|
|
|
2025-03-18 19:49:52 -04:00
|
|
|
self.send_segment(PacketSegment {
|
2025-03-21 19:56:16 -04:00
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-18 19:49:52 -04:00
|
|
|
})
|
2025-03-15 20:36:39 -04:00
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
2025-03-15 20:49:07 -04:00
|
|
|
|
2025-04-01 20:23:23 -04:00
|
|
|
pub async fn set_actor_position(&mut self, actor_id: u32, position: Position, rotation: f32) {
|
2025-03-30 17:11:41 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
2025-05-01 23:20:56 -04:00
|
|
|
op_code: ServerZoneIpcType::Move,
|
2025-03-30 17:11:41 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-05-02 23:24:31 -04:00
|
|
|
data: ServerZoneIpcData::Move(Move {
|
2025-04-11 10:32:52 -04:00
|
|
|
rotation,
|
2025-05-08 21:52:50 -04:00
|
|
|
flag1: 128,
|
|
|
|
flag2: 60,
|
2025-03-30 17:49:45 -04:00
|
|
|
position,
|
|
|
|
}),
|
2025-03-30 17:11:41 -04:00
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: actor_id,
|
|
|
|
target_actor: actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-30 17:11:41 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-06-18 20:28:03 -04:00
|
|
|
pub async fn spawn_actor(&mut self, mut actor: Actor, mut spawn: NpcSpawn) {
|
2025-03-30 21:42:46 -04:00
|
|
|
// 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);
|
|
|
|
|
2025-04-14 16:03:57 -04:00
|
|
|
actor.spawn_index = self.get_free_spawn_index() as u32;
|
2025-06-18 20:28:03 -04:00
|
|
|
spawn.common.spawn_index = actor.spawn_index as u8;
|
2025-06-18 20:49:05 -04:00
|
|
|
spawn.common.target_id = ObjectTypeId {
|
|
|
|
object_id: actor.id,
|
|
|
|
object_type: 0,
|
|
|
|
};
|
2025-04-01 19:22:13 -04:00
|
|
|
|
2025-03-30 17:11:41 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::NpcSpawn,
|
|
|
|
timestamp: timestamp_secs(),
|
2025-06-18 20:28:03 -04:00
|
|
|
data: ServerZoneIpcData::NpcSpawn(spawn),
|
2025-05-02 23:38:44 -04:00
|
|
|
..Default::default()
|
2025-03-30 17:11:41 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: actor.id.0,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-30 17:11:41 -04:00
|
|
|
})
|
|
|
|
.await;
|
2025-04-14 16:03:57 -04:00
|
|
|
|
|
|
|
self.actors.push(actor);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn remove_actor(&mut self, actor_id: u32) {
|
|
|
|
if let Some(actor) = self.get_actor(ObjectId(actor_id)).cloned() {
|
2025-04-18 01:38:47 -04:00
|
|
|
tracing::info!("Removing actor {actor_id} {}!", actor.spawn_index);
|
|
|
|
|
2025-04-14 16:03:57 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
2025-05-01 23:20:56 -04:00
|
|
|
op_code: ServerZoneIpcType::Delete,
|
2025-04-14 16:03:57 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-05-01 23:20:56 -04:00
|
|
|
data: ServerZoneIpcData::Delete {
|
|
|
|
spawn_index: actor.spawn_index as u8,
|
2025-04-14 16:03:57 -04:00
|
|
|
actor_id,
|
|
|
|
},
|
2025-05-02 23:38:44 -04:00
|
|
|
..Default::default()
|
2025-04-14 16:03:57 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: actor.id.0,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-04-14 16:03:57 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
|
|
|
|
self.actors.remove(
|
|
|
|
self.actors
|
|
|
|
.iter()
|
|
|
|
.position(|actor| actor.id == ObjectId(actor_id))
|
|
|
|
.unwrap(),
|
|
|
|
);
|
|
|
|
}
|
2025-03-30 17:11:41 -04:00
|
|
|
}
|
|
|
|
|
2025-04-01 20:39:57 -04:00
|
|
|
pub async fn update_class_info(&mut self) {
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::UpdateClassInfo,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo {
|
2025-06-22 09:18:14 -04:00
|
|
|
class_id: self.player_data.classjob_id,
|
2025-06-20 19:08:53 -04:00
|
|
|
synced_level: self.player_data.current_level() as u16,
|
|
|
|
class_level: self.player_data.current_level() as u16,
|
2025-06-22 09:18:14 -04:00
|
|
|
current_level: self.player_data.current_level() as u16,
|
|
|
|
current_exp: self.player_data.current_exp(),
|
2025-04-01 20:39:57 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-04-01 20:39:57 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-03-15 20:49:07 -04:00
|
|
|
pub async fn change_zone(&mut self, new_zone_id: u16) {
|
2025-04-14 16:03:57 -04:00
|
|
|
// tell everyone we're gone
|
|
|
|
// the connection already checks to see if the actor already exists, so it's seems harmless if we do
|
2025-05-09 19:45:08 -04:00
|
|
|
if let Some(zone) = &self.zone {
|
2025-05-09 19:27:18 -04:00
|
|
|
self.handle
|
|
|
|
.send(ToServer::LeftZone(
|
|
|
|
self.id,
|
|
|
|
self.player_data.actor_id,
|
2025-05-09 19:45:08 -04:00
|
|
|
zone.id,
|
2025-05-09 19:27:18 -04:00
|
|
|
))
|
|
|
|
.await;
|
|
|
|
}
|
2025-05-09 19:45:08 -04:00
|
|
|
|
|
|
|
// load the new zone now
|
2025-03-30 19:13:24 -04:00
|
|
|
{
|
|
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
2025-06-28 09:56:04 -04:00
|
|
|
self.zone = Some(Zone::load(&mut game_data, new_zone_id));
|
2025-03-30 19:13:24 -04:00
|
|
|
}
|
2025-05-09 19:45:08 -04:00
|
|
|
|
2025-03-29 00:15:29 -04:00
|
|
|
self.player_data.zone_id = new_zone_id;
|
2025-03-15 20:49:07 -04:00
|
|
|
|
|
|
|
// Player Class Info
|
2025-04-01 20:39:57 -04:00
|
|
|
self.update_class_info().await;
|
2025-03-15 20:49:07 -04:00
|
|
|
|
|
|
|
// Init Zone
|
|
|
|
{
|
2025-04-30 22:37:57 -04:00
|
|
|
let config = get_config();
|
|
|
|
|
2025-06-21 14:55:17 -04:00
|
|
|
{
|
|
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
2025-06-28 09:56:04 -04:00
|
|
|
self.weather_id = game_data
|
2025-06-21 14:55:17 -04:00
|
|
|
.get_weather(self.zone.as_ref().unwrap().id.into())
|
|
|
|
.unwrap_or(1) as u16;
|
|
|
|
}
|
|
|
|
|
2025-03-16 17:43:29 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::InitZone,
|
2025-03-15 20:49:07 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-03-16 17:43:29 -04:00
|
|
|
data: ServerZoneIpcData::InitZone(InitZone {
|
2025-05-01 23:20:56 -04:00
|
|
|
territory_type: self.zone.as_ref().unwrap().id,
|
2025-06-28 09:56:04 -04:00
|
|
|
weather_id: self.weather_id,
|
2025-04-30 22:37:57 -04:00
|
|
|
obsfucation_mode: if config.world.enable_packet_obsfucation {
|
|
|
|
OBFUSCATION_ENABLED_MODE
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
},
|
2025-03-15 20:49:07 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
2025-03-15 20:49:07 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
2025-03-21 19:56:16 -04:00
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-15 20:49:07 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
2025-03-16 14:07:56 -04:00
|
|
|
|
2025-05-05 22:04:39 -04:00
|
|
|
pub async fn warp(&mut self, warp_id: u32) {
|
|
|
|
let territory_type;
|
|
|
|
// find the pop range on the other side
|
|
|
|
{
|
|
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
2025-05-09 18:03:25 -04:00
|
|
|
let (pop_range_id, zone_id) = game_data
|
|
|
|
.get_warp(warp_id)
|
|
|
|
.expect("Failed to find the warp!");
|
2025-05-05 22:04:39 -04:00
|
|
|
|
2025-06-28 09:56:04 -04:00
|
|
|
let new_zone = Zone::load(&mut game_data, zone_id);
|
2025-05-05 22:04:39 -04:00
|
|
|
|
|
|
|
// find it on the other side
|
2025-06-21 14:23:56 -04:00
|
|
|
if let Some((object, _)) = new_zone.find_pop_range(pop_range_id) {
|
|
|
|
self.exit_position = Some(Position {
|
|
|
|
x: object.transform.translation[0],
|
|
|
|
y: object.transform.translation[1],
|
|
|
|
z: object.transform.translation[2],
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
tracing::warn!(
|
|
|
|
"Failed to find pop range in {}. Falling back to 0,0,0!",
|
|
|
|
new_zone.id
|
|
|
|
);
|
|
|
|
}
|
2025-05-05 22:04:39 -04:00
|
|
|
|
|
|
|
territory_type = zone_id;
|
|
|
|
}
|
|
|
|
|
2025-06-23 14:10:04 -04:00
|
|
|
self.change_zone(territory_type).await;
|
2025-05-05 22:04:39 -04:00
|
|
|
}
|
|
|
|
|
2025-05-11 10:12:02 -04:00
|
|
|
pub async fn warp_aetheryte(&mut self, aetheryte_id: u32) {
|
|
|
|
tracing::info!("Warping to aetheryte {}", aetheryte_id);
|
|
|
|
|
|
|
|
let territory_type;
|
|
|
|
// find the pop range on the other side
|
|
|
|
{
|
|
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
|
|
|
let (pop_range_id, zone_id) = game_data
|
|
|
|
.get_aetheryte(aetheryte_id)
|
|
|
|
.expect("Failed to find the aetheryte!");
|
|
|
|
|
2025-06-28 09:56:04 -04:00
|
|
|
let new_zone = Zone::load(&mut game_data, zone_id);
|
2025-05-11 10:12:02 -04:00
|
|
|
|
|
|
|
// find it on the other side
|
2025-05-11 12:39:13 -04:00
|
|
|
if let Some((object, _)) = new_zone.find_pop_range(pop_range_id) {
|
|
|
|
// set the exit position
|
|
|
|
self.exit_position = Some(Position {
|
|
|
|
x: object.transform.translation[0],
|
|
|
|
y: object.transform.translation[1],
|
|
|
|
z: object.transform.translation[2],
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
tracing::warn!(
|
|
|
|
"Failed to find pop range in {}. Falling back to 0,0,0!",
|
|
|
|
new_zone.id
|
|
|
|
);
|
|
|
|
}
|
2025-05-11 10:12:02 -04:00
|
|
|
|
|
|
|
territory_type = zone_id;
|
|
|
|
}
|
|
|
|
|
2025-06-23 14:10:04 -04:00
|
|
|
self.change_zone(territory_type).await;
|
2025-05-11 10:12:02 -04:00
|
|
|
}
|
|
|
|
|
2025-03-18 23:48:00 -04:00
|
|
|
pub async fn change_weather(&mut self, new_weather_id: u16) {
|
2025-06-28 09:56:04 -04:00
|
|
|
self.weather_id = new_weather_id;
|
|
|
|
|
2025-03-18 23:48:00 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
2025-05-01 23:20:56 -04:00
|
|
|
op_code: ServerZoneIpcType::WeatherId,
|
2025-03-18 23:48:00 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-05-01 23:20:56 -04:00
|
|
|
data: ServerZoneIpcData::WeatherId(WeatherChange {
|
2025-03-18 23:48:00 -04:00
|
|
|
weather_id: new_weather_id,
|
|
|
|
transistion_time: 1.0,
|
|
|
|
}),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
2025-03-21 19:56:16 -04:00
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-18 23:48:00 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-03-16 14:07:56 -04:00
|
|
|
pub fn get_free_spawn_index(&mut self) -> u8 {
|
|
|
|
self.spawn_index += 1;
|
2025-03-16 14:09:12 -04:00
|
|
|
self.spawn_index
|
2025-03-16 14:07:56 -04:00
|
|
|
}
|
2025-03-23 17:43:06 -04:00
|
|
|
|
2025-03-31 21:49:12 -04:00
|
|
|
pub async fn send_inventory(&mut self, send_appearance_update: bool) {
|
2025-04-01 21:37:41 -04:00
|
|
|
let mut sequence = 0;
|
|
|
|
|
2025-05-02 16:15:54 -04:00
|
|
|
for (container_type, container) in &self.player_data.inventory.clone() {
|
2025-06-24 19:15:25 -04:00
|
|
|
// currencies
|
|
|
|
if container_type == ContainerType::Currency {
|
2025-06-25 17:02:19 -04:00
|
|
|
let mut send_currency = async |item: &Item| {
|
2025-06-24 19:15:25 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::CurrencyCrystalInfo,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::CurrencyCrystalInfo(CurrencyInfo {
|
|
|
|
sequence,
|
2025-06-25 17:02:19 -04:00
|
|
|
container: container_type,
|
2025-06-24 19:15:25 -04:00
|
|
|
quantity: item.quantity,
|
|
|
|
catalog_id: item.id,
|
2025-06-25 17:02:19 -04:00
|
|
|
unk1: 1,
|
2025-06-24 19:15:25 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-31 23:23:29 -04:00
|
|
|
..Default::default()
|
2025-06-24 19:15:25 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
2025-03-31 23:23:29 -04:00
|
|
|
};
|
|
|
|
|
2025-06-24 19:15:25 -04:00
|
|
|
for i in 0..container.max_slots() {
|
2025-06-25 17:02:19 -04:00
|
|
|
send_currency(container.get_slot(i as u16)).await;
|
2025-06-24 19:15:25 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// items
|
|
|
|
|
|
|
|
let mut send_slot = async |slot_index: u16, item: &Item| {
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::UpdateItem,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::UpdateItem(ItemInfo {
|
|
|
|
sequence,
|
|
|
|
container: container_type,
|
|
|
|
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: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
};
|
2025-03-31 23:23:29 -04:00
|
|
|
|
2025-06-24 19:15:25 -04:00
|
|
|
for i in 0..container.max_slots() {
|
|
|
|
send_slot(i as u16, container.get_slot(i as u16)).await;
|
|
|
|
}
|
2025-04-01 21:37:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// inform the client of container state
|
|
|
|
{
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::ContainerInfo,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
2025-05-02 16:15:54 -04:00
|
|
|
container: container_type,
|
|
|
|
num_items: container.num_items(),
|
2025-04-01 21:37:41 -04:00
|
|
|
sequence,
|
|
|
|
..Default::default()
|
|
|
|
}),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-04-01 21:37:41 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
|
|
|
sequence += 1;
|
2025-03-31 23:23:29 -04:00
|
|
|
}
|
|
|
|
|
2025-03-31 21:49:12 -04:00
|
|
|
// send them an appearance update
|
|
|
|
if send_appearance_update {
|
2025-06-28 11:11:42 -04:00
|
|
|
let main_weapon_id;
|
|
|
|
let model_ids;
|
2025-03-31 21:49:12 -04:00
|
|
|
{
|
|
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
2025-04-30 23:05:43 -04:00
|
|
|
let inventory = &self.player_data.inventory;
|
2025-03-31 21:49:12 -04:00
|
|
|
|
2025-06-28 11:11:42 -04:00
|
|
|
main_weapon_id = inventory.get_main_weapon_id(&mut game_data);
|
|
|
|
model_ids = inventory.get_model_ids(&mut game_data);
|
2025-03-31 21:49:12 -04:00
|
|
|
}
|
|
|
|
|
2025-06-28 11:11:42 -04:00
|
|
|
self.handle
|
|
|
|
.send(ToServer::Equip(
|
|
|
|
self.id,
|
|
|
|
self.player_data.actor_id,
|
|
|
|
main_weapon_id,
|
|
|
|
model_ids,
|
|
|
|
))
|
|
|
|
.await;
|
2025-03-23 17:43:06 -04:00
|
|
|
}
|
|
|
|
}
|
2025-03-27 22:54:36 -04:00
|
|
|
|
2025-06-28 11:11:42 -04:00
|
|
|
pub async fn update_equip(&mut self, actor_id: u32, main_weapon_id: u64, model_ids: [u32; 10]) {
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::Equip,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::Equip(Equip {
|
|
|
|
main_weapon_id,
|
|
|
|
model_ids,
|
|
|
|
..Default::default()
|
|
|
|
}),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-03-27 22:54:36 -04:00
|
|
|
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(),
|
2025-06-25 10:05:34 -04:00
|
|
|
param: 0,
|
2025-03-27 22:54:36 -04:00
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-27 22:54:36 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-06-21 13:30:52 -04:00
|
|
|
pub async fn toggle_invisibility(&mut self, invisible: bool) {
|
|
|
|
self.player_data.gm_invisible = invisible;
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::ActorControlSelf,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::ActorControlSelf(ActorControlSelf {
|
|
|
|
category: ActorControlCategory::ToggleInvisibility { invisible },
|
|
|
|
}),
|
|
|
|
..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: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-03-27 22:54:36 -04:00
|
|
|
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();
|
2025-03-28 23:43:27 -04:00
|
|
|
|
|
|
|
for task in &player.queued_tasks {
|
2025-05-02 15:36:22 -04:00
|
|
|
match task {
|
|
|
|
Task::ChangeTerritory { zone_id } => self.change_zone(*zone_id).await,
|
|
|
|
Task::SetRemakeMode(remake_mode) => self
|
|
|
|
.database
|
|
|
|
.set_remake_mode(player.player_data.content_id, *remake_mode),
|
2025-05-05 22:04:39 -04:00
|
|
|
Task::Warp { warp_id } => {
|
|
|
|
self.warp(*warp_id).await;
|
|
|
|
}
|
2025-05-05 23:04:53 -04:00
|
|
|
Task::BeginLogOut => self.begin_log_out().await,
|
2025-05-05 23:45:22 -04:00
|
|
|
Task::FinishEvent { handler_id } => self.event_finish(*handler_id).await,
|
2025-05-06 22:03:31 -04:00
|
|
|
Task::SetClassJob { classjob_id } => {
|
|
|
|
self.player_data.classjob_id = *classjob_id;
|
|
|
|
self.update_class_info().await;
|
|
|
|
}
|
2025-05-11 10:12:02 -04:00
|
|
|
Task::WarpAetheryte { aetheryte_id } => {
|
|
|
|
self.warp_aetheryte(*aetheryte_id).await;
|
|
|
|
}
|
2025-06-21 12:16:27 -04:00
|
|
|
Task::ReloadScripts => {
|
2025-06-22 08:49:06 -04:00
|
|
|
self.reload_scripts();
|
2025-06-21 12:16:27 -04:00
|
|
|
}
|
2025-06-21 13:30:52 -04:00
|
|
|
Task::ToggleInvisibility { invisible } => {
|
|
|
|
self.toggle_invisibility(*invisible).await;
|
|
|
|
}
|
2025-06-27 23:01:00 -04:00
|
|
|
Task::Unlock { id } => {
|
|
|
|
let (value, index) = value_to_flag_byte_index_value(*id);
|
|
|
|
self.player_data.unlocks[index as usize] |= value;
|
|
|
|
|
|
|
|
self.actor_control_self(ActorControlSelf {
|
|
|
|
category: ActorControlCategory::ToggleUnlock {
|
|
|
|
id: *id,
|
|
|
|
unlocked: true,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
2025-06-27 23:24:30 -04:00
|
|
|
Task::UnlockAetheryte { id, on } => {
|
|
|
|
let unlock_all = *id == 0;
|
|
|
|
if unlock_all {
|
2025-06-28 08:13:20 -04:00
|
|
|
for i in 1..239 {
|
2025-06-28 00:07:56 -04:00
|
|
|
let (value, index) = value_to_flag_byte_index_value(i);
|
|
|
|
if *on {
|
|
|
|
self.player_data.aetherytes[index as usize] |= value;
|
|
|
|
} else {
|
|
|
|
self.player_data.aetherytes[index as usize] ^= value;
|
|
|
|
}
|
2025-06-27 23:24:30 -04:00
|
|
|
|
|
|
|
/* Unknown if this will make the server panic from a flood of packets.
|
|
|
|
* Needs testing once toggling aetherytes actually works. */
|
|
|
|
self.actor_control_self(ActorControlSelf {
|
|
|
|
category: ActorControlCategory::LearnTeleport {
|
|
|
|
id: i,
|
|
|
|
unlocked: *on,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let (value, index) = value_to_flag_byte_index_value(*id);
|
2025-06-28 00:07:56 -04:00
|
|
|
if *on {
|
|
|
|
self.player_data.aetherytes[index as usize] |= value;
|
|
|
|
} else {
|
|
|
|
self.player_data.aetherytes[index as usize] ^= value;
|
|
|
|
}
|
2025-06-27 23:24:30 -04:00
|
|
|
|
|
|
|
self.actor_control_self(ActorControlSelf {
|
|
|
|
category: ActorControlCategory::LearnTeleport {
|
|
|
|
id: *id,
|
|
|
|
unlocked: *on,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
2025-06-28 09:56:04 -04:00
|
|
|
Task::SetLevel { level } => {
|
|
|
|
self.player_data.set_current_level(*level);
|
|
|
|
self.update_class_info().await;
|
|
|
|
}
|
|
|
|
Task::ChangeWeather { id } => {
|
|
|
|
self.change_weather(*id).await;
|
|
|
|
}
|
|
|
|
Task::AddGil { amount } => {
|
|
|
|
self.player_data.inventory.currency.get_slot_mut(0).quantity += *amount as u32;
|
|
|
|
self.send_inventory(false).await;
|
|
|
|
}
|
|
|
|
Task::UnlockOrchestrion { id, on } => {
|
|
|
|
// id == 0 means "all"
|
|
|
|
if *id == 0 {
|
|
|
|
/* Currently 792 songs ingame.
|
|
|
|
* Commented out because this learns literally zero songs
|
|
|
|
* for some unknown reason. */
|
|
|
|
/*for i in 1..793 {
|
|
|
|
let idd = i as u16;
|
|
|
|
connection.send_message("test!").await;
|
|
|
|
connection.actor_control_self(ActorControlSelf {
|
|
|
|
category: ActorControlCategory::ToggleOrchestrionUnlock { song_id: id, unlocked: on } }).await;
|
|
|
|
}*/
|
|
|
|
} else {
|
|
|
|
self.actor_control_self(ActorControlSelf {
|
|
|
|
category: ActorControlCategory::ToggleOrchestrionUnlock {
|
|
|
|
song_id: *id,
|
|
|
|
unlocked: *on,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
2025-05-02 15:36:22 -04:00
|
|
|
}
|
2025-03-28 23:43:27 -04:00
|
|
|
}
|
|
|
|
player.queued_tasks.clear();
|
2025-03-27 22:54:36 -04:00
|
|
|
}
|
2025-03-28 00:22:41 -04:00
|
|
|
|
2025-06-22 08:49:06 -04:00
|
|
|
/// Reloads Global.lua
|
|
|
|
pub fn reload_scripts(&mut self) {
|
|
|
|
let mut lua = self.lua.lock().unwrap();
|
2025-06-25 13:25:48 -04:00
|
|
|
if let Err(err) = load_init_script(&mut lua) {
|
|
|
|
tracing::warn!("Failed to load Init.lua: {:?}", err);
|
2025-06-22 08:49:06 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-05 23:45:22 -04:00
|
|
|
pub async fn event_finish(&mut self, handler_id: u32) {
|
|
|
|
// sent event finish
|
|
|
|
{
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::EventFinish,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::EventFinish {
|
|
|
|
handler_id,
|
|
|
|
event: 1,
|
|
|
|
result: 1,
|
|
|
|
arg: 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: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
|
|
|
// give back control to the player
|
|
|
|
{
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::Unk18,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::Unk18 { unk: [0; 16] },
|
|
|
|
..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: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-05 23:04:53 -04:00
|
|
|
pub async fn begin_log_out(&mut self) {
|
2025-05-12 01:17:15 -04:00
|
|
|
self.gracefully_logged_out = true;
|
|
|
|
|
2025-05-05 23:04:53 -04:00
|
|
|
// write the player back to the database
|
|
|
|
self.database.commit_player_data(&self.player_data);
|
|
|
|
|
|
|
|
// tell the client we're ready to disconnect at any moment'
|
|
|
|
{
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::LogOutComplete,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::LogOutComplete { unk: [0; 8] },
|
|
|
|
..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: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-28 00:22:41 -04:00
|
|
|
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,
|
2025-03-29 14:52:27 -04:00
|
|
|
classjob_id: self.player_data.classjob_id,
|
2025-06-20 19:08:53 -04:00
|
|
|
level: self.player_data.current_level() as u8,
|
2025-03-29 14:52:27 -04:00
|
|
|
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,
|
2025-03-28 00:22:41 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-28 00:22:41 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
|
|
|
|
self.status_effects.dirty = false;
|
|
|
|
}
|
|
|
|
}
|
2025-03-29 12:25:22 -04:00
|
|
|
|
|
|
|
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,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-29 12:25:22 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-04-14 16:03:57 -04:00
|
|
|
pub fn get_actor_mut(&mut self, id: ObjectId) -> Option<&mut Actor> {
|
|
|
|
self.actors.iter_mut().find(|actor| actor.id == id)
|
2025-03-29 12:25:22 -04:00
|
|
|
}
|
|
|
|
|
2025-04-18 01:38:47 -04:00
|
|
|
pub fn get_actor(&self, id: ObjectId) -> Option<&Actor> {
|
2025-04-14 16:03:57 -04:00
|
|
|
self.actors.iter().find(|actor| actor.id == id)
|
2025-03-29 12:25:22 -04:00
|
|
|
}
|
2025-03-30 10:49:33 -04:00
|
|
|
|
|
|
|
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,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-03-30 10:49:33 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
2025-04-01 19:15:08 -04:00
|
|
|
|
2025-05-08 22:53:36 -04:00
|
|
|
pub async fn actor_control(&mut self, actor_id: u32, actor_control: ActorControl) {
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::ActorControl,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::ActorControl(actor_control),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn actor_control_target(&mut self, actor_id: u32, actor_control: ActorControlTarget) {
|
|
|
|
tracing::info!(
|
|
|
|
"we are sending actor control target to {actor_id}: {actor_control:#?} and WE ARE {:#?}",
|
|
|
|
self.player_data.actor_id
|
|
|
|
);
|
|
|
|
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::ActorControlTarget,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::ActorControlTarget(actor_control),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-06-25 21:30:14 -04:00
|
|
|
pub async fn update_config(&mut self, actor_id: u32, config: Config) {
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::Config,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::Config(config),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-04-01 19:15:08 -04:00
|
|
|
pub fn get_player_common_spawn(
|
2025-04-01 19:22:13 -04:00
|
|
|
&self,
|
2025-04-01 19:15:08 -04:00
|
|
|
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);
|
|
|
|
|
2025-04-30 23:05:43 -04:00
|
|
|
let inventory = &self.player_data.inventory;
|
|
|
|
|
2025-04-01 19:15:08 -04:00
|
|
|
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,
|
2025-06-20 19:08:53 -04:00
|
|
|
level: self.player_data.current_level() as u8,
|
2025-04-01 19:15:08 -04:00
|
|
|
object_kind: ObjectKind::Player(PlayerSubKind::Player),
|
|
|
|
look: chara_details.chara_make.customize,
|
|
|
|
display_flags: DisplayFlag::UNK,
|
2025-04-30 23:05:43 -04:00
|
|
|
main_weapon_model: inventory.get_main_weapon_id(&mut game_data),
|
|
|
|
models: inventory.get_model_ids(&mut game_data),
|
2025-04-11 08:32:55 -04:00
|
|
|
pos: exit_position.unwrap_or_default(),
|
2025-04-01 19:15:08 -04:00
|
|
|
rotation: exit_rotation.unwrap_or(0.0),
|
2025-05-12 22:00:23 -04:00
|
|
|
voice: chara_details.chara_make.voice_id as u8,
|
2025-04-01 19:15:08 -04:00
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
2025-04-14 16:18:03 -04:00
|
|
|
|
|
|
|
pub async fn send_stats(&mut self, chara_details: &CharacterData) {
|
|
|
|
let attributes;
|
|
|
|
{
|
|
|
|
let mut game_data = self.gamedata.lock().unwrap();
|
|
|
|
|
2025-05-09 18:35:44 -04:00
|
|
|
attributes = game_data
|
|
|
|
.get_racial_base_attributes(chara_details.chara_make.customize.subrace)
|
|
|
|
.expect("Failed to read racial attributes");
|
2025-04-14 16:18:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::PlayerStats,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::PlayerStats(PlayerStats {
|
|
|
|
strength: attributes.strength,
|
|
|
|
dexterity: attributes.dexterity,
|
|
|
|
vitality: attributes.vitality,
|
|
|
|
intelligence: attributes.intelligence,
|
|
|
|
mind: attributes.mind,
|
|
|
|
hp: self.player_data.max_hp,
|
|
|
|
mp: self.player_data.max_mp as u32,
|
|
|
|
..Default::default()
|
|
|
|
}),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_segment(PacketSegment {
|
|
|
|
source_actor: self.player_data.actor_id,
|
|
|
|
target_actor: self.player_data.actor_id,
|
2025-05-02 00:03:36 -04:00
|
|
|
segment_type: SegmentType::Ipc,
|
|
|
|
data: SegmentData::Ipc { data: ipc },
|
2025-04-14 16:18:03 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
2025-06-21 10:36:44 -04:00
|
|
|
|
|
|
|
pub async fn execute_action(&mut self, request: ActionRequest, lua_player: &mut LuaPlayer) {
|
|
|
|
let mut effects_builder = None;
|
|
|
|
|
|
|
|
// run action script
|
|
|
|
{
|
|
|
|
let lua = self.lua.lock().unwrap();
|
|
|
|
let state = lua.app_data_ref::<ExtraLuaState>().unwrap();
|
|
|
|
|
|
|
|
let key = request.action_key;
|
|
|
|
if let Some(action_script) = state.action_scripts.get(&key) {
|
|
|
|
lua.scope(|scope| {
|
|
|
|
let connection_data = scope.create_userdata_ref_mut(lua_player).unwrap();
|
|
|
|
|
|
|
|
let config = get_config();
|
|
|
|
|
|
|
|
let file_name = format!("{}/{}", &config.world.scripts_location, action_script);
|
|
|
|
lua.load(
|
|
|
|
std::fs::read(&file_name).expect("Failed to locate scripts directory!"),
|
|
|
|
)
|
|
|
|
.set_name("@".to_string() + &file_name)
|
|
|
|
.exec()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let func: Function = lua.globals().get("doAction").unwrap();
|
|
|
|
|
|
|
|
effects_builder = Some(func.call::<EffectsBuilder>(connection_data).unwrap());
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
} else {
|
|
|
|
tracing::warn!("Action {key} isn't scripted yet! Ignoring...");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// tell them the action results
|
|
|
|
if let Some(effects_builder) = effects_builder {
|
|
|
|
let mut effects = [ActionEffect::default(); 8];
|
|
|
|
effects[..effects_builder.effects.len()].copy_from_slice(&effects_builder.effects);
|
|
|
|
|
|
|
|
if let Some(actor) = self.get_actor_mut(request.target.object_id) {
|
|
|
|
for effect in &effects_builder.effects {
|
|
|
|
match effect.kind {
|
|
|
|
EffectKind::Damage { amount, .. } => {
|
|
|
|
actor.hp = actor.hp.saturating_sub(amount as u32);
|
|
|
|
}
|
|
|
|
_ => todo!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let actor = *actor;
|
|
|
|
self.update_hp_mp(actor.id, actor.hp, 10000).await;
|
|
|
|
}
|
|
|
|
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::ActionResult,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: ServerZoneIpcData::ActionResult(ActionResult {
|
|
|
|
main_target: request.target,
|
|
|
|
target_id_again: request.target,
|
|
|
|
action_id: request.action_key,
|
|
|
|
animation_lock_time: 0.6,
|
|
|
|
rotation: self.player_data.rotation,
|
|
|
|
action_animation_id: request.action_key as u16, // assuming action id == animation id
|
|
|
|
flag: 1,
|
|
|
|
effect_count: effects_builder.effects.len() as u8,
|
|
|
|
effects,
|
|
|
|
unk1: 2662353,
|
|
|
|
unk2: 3758096384,
|
|
|
|
hidden_animation: 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: SegmentData::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
|
|
|
|
if let Some(actor) = self.get_actor(request.target.object_id) {
|
|
|
|
if actor.hp == 0 {
|
|
|
|
tracing::info!("Despawning {} because they died!", actor.id.0);
|
|
|
|
// if the actor died, despawn them
|
|
|
|
/*connection.handle
|
|
|
|
* .send(ToServer::ActorDespawned(connection.id, actor.id.0))
|
|
|
|
* .await;*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn cancel_action(&mut self) {
|
|
|
|
self.actor_control_self(ActorControlSelf {
|
|
|
|
category: ActorControlCategory::CancelCast {},
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
2025-03-27 22:54:36 -04:00
|
|
|
}
|