2025-04-18 01:17:51 -04:00
use std ::net ::SocketAddr ;
2025-03-27 16:20:33 -04:00
use std ::sync ::{ Arc , Mutex } ;
2025-05-12 01:17:15 -04:00
use std ::time ::{ Duration , Instant } ;
2025-03-21 19:56:16 -04:00
2025-03-30 21:42:46 -04:00
use kawari ::RECEIVE_BUFFER_SIZE ;
2025-06-28 09:56:04 -04:00
use kawari ::common ::Position ;
use kawari ::common ::{ GameData , timestamp_secs } ;
2025-03-19 00:28:47 -04:00
use kawari ::config ::get_config ;
2025-06-30 20:26:15 -04:00
use kawari ::inventory ::Item ;
2025-05-02 00:47:11 -04:00
use kawari ::ipc ::chat ::{ ServerChatIpcData , ServerChatIpcSegment } ;
use kawari ::ipc ::zone ::{
2025-06-21 10:36:44 -04:00
ActorControlCategory , ActorControlSelf , PlayerEntry , PlayerSpawn , PlayerStatus , SocialList ,
2025-05-02 00:47:11 -04:00
} ;
use kawari ::ipc ::zone ::{
2025-06-28 11:11:42 -04:00
ClientTriggerCommand , ClientZoneIpcData , EventStart , GameMasterRank , OnlineStatus ,
2025-06-28 09:56:04 -04:00
ServerZoneIpcData , ServerZoneIpcSegment , SocialListRequestType ,
2025-05-02 00:47:11 -04:00
} ;
2025-05-02 00:11:37 -04:00
use kawari ::opcodes ::{ ServerChatIpcType , ServerZoneIpcType } ;
2025-05-02 00:52:26 -04:00
use kawari ::packet ::oodle ::OodleNetwork ;
2025-03-21 21:26:32 -04:00
use kawari ::packet ::{
2025-05-05 21:03:47 -04:00
ConnectionType , PacketSegment , PacketState , SegmentData , SegmentType , send_keep_alive ,
2025-03-21 21:26:32 -04:00
} ;
2025-07-03 16:12:19 -04:00
use kawari ::world ::{
ChatHandler , ExtraLuaState , LuaZone , ObsfucationData , Zone , ZoneConnection , load_init_script ,
} ;
2025-03-30 17:11:41 -04:00
use kawari ::world ::{
2025-06-21 12:16:27 -04:00
ClientHandle , Event , FromServer , LuaPlayer , PlayerData , ServerHandle , StatusEffects , ToServer ,
WorldDatabase , handle_custom_ipc , server_main_loop ,
2025-03-30 17:11:41 -04:00
} ;
2025-03-30 21:42:46 -04:00
2025-03-28 00:22:41 -04:00
use mlua ::{ Function , Lua } ;
2025-04-15 23:35:49 -04:00
use tokio ::io ::{ AsyncReadExt , AsyncWriteExt } ;
2025-03-30 16:41:06 -04:00
use tokio ::join ;
2025-04-18 01:17:51 -04:00
use tokio ::net ::{ TcpListener , TcpStream } ;
2025-03-30 17:59:17 -04:00
use tokio ::sync ::mpsc ::{ Receiver , UnboundedReceiver , UnboundedSender , channel , unbounded_channel } ;
2025-03-30 16:41:06 -04:00
use tokio ::sync ::oneshot ;
use tokio ::task ::JoinHandle ;
2025-03-09 11:07:01 -04:00
2025-07-05 19:05:43 -04:00
use kawari ::{
INVENTORY_ACTION_ACK_GENERAL , /* INVENTORY_ACTION_ACK_SHOP, */ INVENTORY_ACTION_DISCARD ,
} ;
2025-03-30 16:41:06 -04:00
fn spawn_main_loop ( ) -> ( ServerHandle , JoinHandle < ( ) > ) {
let ( send , recv ) = channel ( 64 ) ;
2025-03-30 09:29:36 -04:00
2025-03-30 16:41:06 -04:00
let handle = ServerHandle {
chan : send ,
next_id : Default ::default ( ) ,
} ;
2025-03-21 19:56:16 -04:00
2025-03-30 16:41:06 -04:00
let join = tokio ::spawn ( async move {
2025-05-02 23:51:34 -04:00
let res = server_main_loop ( recv ) . await ;
2025-03-30 16:41:06 -04:00
match res {
Ok ( ( ) ) = > { }
Err ( err ) = > {
tracing ::error! ( " {} " , err ) ;
}
}
} ) ;
2025-03-09 11:07:01 -04:00
2025-03-30 16:41:06 -04:00
( handle , join )
}
2025-03-21 19:56:16 -04:00
2025-03-30 16:41:06 -04:00
struct ClientData {
/// Socket for data recieved from the global server
recv : Receiver < FromServer > ,
connection : ZoneConnection ,
}
2025-03-09 11:07:01 -04:00
2025-03-30 16:41:06 -04:00
/// Spawn a new client actor.
2025-03-30 23:17:11 -04:00
pub fn spawn_client ( connection : ZoneConnection ) {
2025-03-30 16:41:06 -04:00
let ( send , recv ) = channel ( 64 ) ;
2025-03-30 23:17:11 -04:00
let id = & connection . id . clone ( ) ;
let ip = & connection . ip . clone ( ) ;
2025-03-30 16:41:06 -04:00
2025-05-09 19:45:08 -04:00
let data = ClientData { recv , connection } ;
2025-03-30 16:41:06 -04:00
// Spawn a new client task
let ( my_send , my_recv ) = oneshot ::channel ( ) ;
2025-03-31 21:58:51 -04:00
let _kill = tokio ::spawn ( start_client ( my_recv , data ) ) ;
2025-03-30 16:41:06 -04:00
// Send client information to said task
let handle = ClientHandle {
id : * id ,
ip : * ip ,
channel : send ,
2025-05-09 19:42:23 -04:00
actor_id : 0 ,
2025-03-30 16:41:06 -04:00
} ;
let _ = my_send . send ( handle ) ;
}
2025-03-15 20:36:39 -04:00
2025-03-30 21:42:46 -04:00
async fn start_client ( my_handle : oneshot ::Receiver < ClientHandle > , data : ClientData ) {
2025-03-30 16:41:06 -04:00
// Recieve client information from global
let my_handle = match my_handle . await {
Ok ( my_handle ) = > my_handle ,
Err ( _ ) = > return ,
} ;
2025-03-30 17:59:17 -04:00
let connection = data . connection ;
2025-03-30 16:41:06 -04:00
let recv = data . recv ;
// communication channel between client_loop and client_server_loop
let ( internal_send , internal_recv ) = unbounded_channel ( ) ;
2025-03-30 23:17:11 -04:00
let _ = join! (
tokio ::spawn ( client_loop ( connection , internal_recv , my_handle ) ) ,
tokio ::spawn ( client_server_loop ( recv , internal_send ) )
) ;
2025-03-30 16:41:06 -04:00
}
2025-03-27 22:54:36 -04:00
2025-03-30 16:41:06 -04:00
async fn client_server_loop (
mut data : Receiver < FromServer > ,
2025-03-30 16:44:07 -04:00
internal_send : UnboundedSender < FromServer > ,
2025-03-30 16:41:06 -04:00
) {
2025-04-14 16:24:46 -04:00
while let Some ( msg ) = data . recv ( ) . await {
internal_send . send ( msg ) . unwrap ( )
2025-03-30 16:41:06 -04:00
}
}
2025-03-29 14:42:38 -04:00
2025-03-30 16:41:06 -04:00
async fn client_loop (
mut connection : ZoneConnection ,
2025-03-30 16:44:07 -04:00
mut internal_recv : UnboundedReceiver < FromServer > ,
2025-03-30 21:42:46 -04:00
client_handle : ClientHandle ,
2025-03-30 16:41:06 -04:00
) {
let database = connection . database . clone ( ) ;
let game_data = connection . gamedata . clone ( ) ;
let lua = connection . lua . clone ( ) ;
let config = get_config ( ) ;
2025-03-29 14:42:38 -04:00
2025-03-30 16:41:06 -04:00
let mut lua_player = LuaPlayer ::default ( ) ;
2025-03-09 11:07:01 -04:00
2025-05-12 16:00:23 -04:00
// TODO: this is terrible, just have a separate zone/chat connection
let mut is_zone_connection = false ;
2025-03-30 21:42:46 -04:00
let mut buf = vec! [ 0 ; RECEIVE_BUFFER_SIZE ] ;
2025-03-30 16:41:06 -04:00
loop {
tokio ::select! {
2025-03-30 21:42:46 -04:00
biased ; // client data should always be prioritized
2025-05-12 01:17:15 -04:00
n = connection . socket . read ( & mut buf ) = > {
match n {
Ok ( n ) = > {
// if the last response was over >5 seconds, the client is probably gone
if n = = 0 {
let now = Instant ::now ( ) ;
if now . duration_since ( connection . last_keep_alive ) > Duration ::from_secs ( 5 ) {
2025-05-12 16:00:23 -04:00
tracing ::info! ( " Connection {:#?} was killed because of timeout " , client_handle . id ) ;
2025-05-12 01:17:15 -04:00
break ;
2025-03-10 21:31:21 -04:00
}
2025-05-12 01:17:15 -04:00
}
2025-03-10 21:31:21 -04:00
2025-05-12 01:17:15 -04:00
if n > 0 {
connection . last_keep_alive = Instant ::now ( ) ;
2025-03-23 17:10:47 -04:00
2025-06-26 19:09:13 -04:00
let ( segments , connection_type ) = connection . parse_packet ( & buf [ .. n ] ) ;
2025-05-12 01:17:15 -04:00
for segment in & segments {
match & segment . data {
2025-06-21 14:04:09 -04:00
SegmentData ::None ( ) = > { } ,
2025-05-12 01:17:15 -04:00
SegmentData ::Setup { ticket } = > {
// for some reason they send a string representation
let actor_id = ticket . parse ::< u32 > ( ) . unwrap ( ) ;
2025-03-23 17:10:47 -04:00
2025-05-12 01:17:15 -04:00
// initialize player data if it doesn't exist'
if connection . player_data . actor_id = = 0 {
connection . player_data = database . find_player_data ( actor_id ) ;
}
2025-03-10 21:31:21 -04:00
2025-05-12 01:17:15 -04:00
if connection_type = = ConnectionType ::Zone {
2025-05-12 16:00:23 -04:00
is_zone_connection = true ;
2025-05-12 01:17:15 -04:00
// collect actor data
connection . initialize ( actor_id ) . await ;
connection . exit_position = Some ( connection . player_data . position ) ;
connection . exit_rotation = Some ( connection . player_data . rotation ) ;
let mut client_handle = client_handle . clone ( ) ;
client_handle . actor_id = actor_id ;
// tell the server we exist, now that we confirmed we are a legitimate connection
connection . handle . send ( ToServer ::NewClient ( client_handle ) ) . await ;
} else if connection_type = = ConnectionType ::Chat {
// We have send THEM a keep alive
connection . send_chat_segment ( PacketSegment {
segment_type : SegmentType ::KeepAliveRequest ,
data : SegmentData ::KeepAliveRequest {
id : 0xE0037603 u32 ,
timestamp : timestamp_secs ( ) ,
2025-03-30 16:41:06 -04:00
} ,
2025-05-12 01:17:15 -04:00
.. Default ::default ( )
2025-03-30 10:49:33 -04:00
} )
. await ;
2025-03-10 21:31:21 -04:00
2025-05-12 01:17:15 -04:00
// initialize connection
connection . send_chat_segment ( PacketSegment {
segment_type : SegmentType ::Initialize ,
data : SegmentData ::Initialize {
player_id : connection . player_data . actor_id ,
timestamp : timestamp_secs ( ) ,
} ,
2025-03-16 14:07:56 -04:00
.. Default ::default ( )
2025-05-12 01:17:15 -04:00
} )
. await ;
2025-05-11 12:38:55 -04:00
2025-05-12 01:17:15 -04:00
// we need the actor id at this point!
assert! ( connection . player_data . actor_id ! = 0 ) ;
2025-05-11 12:38:55 -04:00
2025-05-12 01:17:15 -04:00
// send login reply
{
let ipc = ServerChatIpcSegment {
op_code : ServerChatIpcType ::LoginReply ,
2025-03-30 10:34:42 -04:00
timestamp : timestamp_secs ( ) ,
2025-05-12 01:17:15 -04:00
data : ServerChatIpcData ::LoginReply {
timestamp : 0 ,
sid : 0 ,
} ,
2025-03-12 18:22:17 -04:00
.. Default ::default ( )
2025-03-30 10:34:42 -04:00
} ;
2025-03-11 23:52:55 -04:00
2025-05-12 01:17:15 -04:00
connection . send_chat_segment ( PacketSegment {
2025-03-21 19:56:16 -04:00
source_actor : connection . player_data . actor_id ,
target_actor : connection . 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:36:39 -04:00
} )
. await ;
2025-05-12 01:17:15 -04:00
}
2025-03-15 19:34:29 -04:00
}
2025-03-12 00:32:37 -04:00
}
2025-05-12 01:17:15 -04:00
SegmentData ::Ipc { data } = > {
match & data . data {
ClientZoneIpcData ::InitRequest { .. } = > {
tracing ::info! (
" Client is now requesting zone information. Sending! "
) ;
// IPC Init(?)
{
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::InitResponse ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::InitResponse {
unk1 : 0 ,
character_id : connection . player_data . actor_id ,
unk2 : 0 ,
} ,
.. Default ::default ( )
} ;
2025-03-16 14:07:56 -04:00
2025-05-12 01:17:15 -04:00
connection
2025-03-16 14:07:56 -04:00
. send_segment ( PacketSegment {
2025-03-30 16:41:06 -04:00
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
2025-05-02 00:03:36 -04:00
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
2025-03-16 14:07:56 -04:00
} )
. await ;
2025-05-12 01:17:15 -04:00
}
let chara_details =
database . find_chara_make ( connection . player_data . content_id ) ;
// Send inventory
connection . send_inventory ( false ) . await ;
2025-03-16 14:07:56 -04:00
2025-05-12 01:17:15 -04:00
// set chara gear param
2025-03-16 14:07:56 -04:00
connection
2025-05-12 01:17:15 -04:00
. actor_control_self ( ActorControlSelf {
category : ActorControlCategory ::SetCharaGearParamUI {
unk1 : 1 ,
unk2 : 1 ,
} ,
} )
. await ;
// Stats
connection . send_stats ( & chara_details ) . await ;
2025-06-28 15:23:56 -04:00
let current_class ;
{
let game_data = connection . gamedata . lock ( ) . unwrap ( ) ;
current_class = game_data . get_exp_array_index ( connection . player_data . classjob_id as u16 ) . unwrap ( ) ;
}
2025-05-12 01:17:15 -04:00
// Player Setup
{
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::PlayerStatus ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::PlayerStatus ( PlayerStatus {
content_id : connection . player_data . content_id ,
2025-06-28 15:27:53 -04:00
// Disabled for now until the client stops freaking out
//exp: connection.player_data.classjob_exp,
2025-06-28 15:10:57 -04:00
max_level : 100 ,
2025-06-28 15:23:56 -04:00
expansion : 5 ,
2025-05-12 01:17:15 -04:00
name : chara_details . name ,
char_id : connection . player_data . actor_id ,
race : chara_details . chara_make . customize . race ,
gender : chara_details . chara_make . customize . gender ,
tribe : chara_details . chara_make . customize . subrace ,
city_state : chara_details . city_state ,
nameday_month : chara_details . chara_make . birth_month
as u8 ,
nameday_day : chara_details . chara_make . birth_day as u8 ,
deity : chara_details . chara_make . guardian as u8 ,
2025-06-28 15:23:56 -04:00
current_class : current_class as u8 ,
2025-06-21 11:50:09 -04:00
current_job : connection . player_data . classjob_id ,
levels : connection . player_data . classjob_levels . map ( | x | x as u16 ) ,
2025-06-27 23:01:00 -04:00
unlocks : connection . player_data . unlocks . clone ( ) ,
2025-06-27 23:24:30 -04:00
aetherytes : connection . player_data . aetherytes . clone ( ) ,
2025-05-12 01:17:15 -04:00
.. Default ::default ( )
} ) ,
.. Default ::default ( )
} ;
connection
2025-03-16 14:07:56 -04:00
. send_segment ( PacketSegment {
2025-03-30 16:41:06 -04:00
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
2025-05-02 00:03:36 -04:00
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
2025-03-16 14:07:56 -04:00
} )
. await ;
2025-05-12 01:17:15 -04:00
}
2025-03-12 18:44:05 -04:00
2025-07-01 19:49:25 -04:00
connection . send_quest_information ( ) . await ;
2025-05-12 01:17:15 -04:00
let zone_id = connection . player_data . zone_id ;
connection . change_zone ( zone_id ) . await ;
2025-04-01 22:24:58 -04:00
2025-05-12 01:17:15 -04:00
let lua = lua . lock ( ) . unwrap ( ) ;
lua . scope ( | scope | {
let connection_data =
scope . create_userdata_ref_mut ( & mut lua_player ) . unwrap ( ) ;
2025-03-30 16:41:06 -04:00
2025-05-12 01:17:15 -04:00
let func : Function = lua . globals ( ) . get ( " onBeginLogin " ) . unwrap ( ) ;
2025-05-06 21:57:52 -04:00
2025-05-12 01:17:15 -04:00
func . call ::< ( ) > ( connection_data ) . unwrap ( ) ;
2025-05-06 21:57:52 -04:00
2025-05-12 01:17:15 -04:00
Ok ( ( ) )
} )
. unwrap ( ) ;
}
ClientZoneIpcData ::FinishLoading { .. } = > {
let common = connection . get_player_common_spawn ( connection . exit_position , connection . exit_rotation ) ;
2025-05-06 21:57:52 -04:00
2025-06-28 11:11:42 -04:00
// tell the server we loaded into the zone, so it can start sending us acors
connection . handle . send ( ToServer ::ZoneLoaded ( connection . id , connection . zone . as_ref ( ) . unwrap ( ) . id , common . clone ( ) ) ) . await ;
2025-05-12 01:17:15 -04:00
let chara_details = database . find_chara_make ( connection . player_data . content_id ) ;
2025-05-06 21:57:52 -04:00
2025-05-12 01:17:15 -04:00
connection . send_inventory ( false ) . await ;
connection . send_stats ( & chara_details ) . await ;
2025-05-12 15:44:39 -04:00
let online_status = if connection . player_data . gm_rank = = GameMasterRank ::NormalUser {
OnlineStatus ::Online
} else {
OnlineStatus ::GameMasterBlue
} ;
2025-05-12 01:17:15 -04:00
// send player spawn
{
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::PlayerSpawn ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::PlayerSpawn ( PlayerSpawn {
account_id : connection . player_data . account_id ,
content_id : connection . player_data . content_id ,
current_world_id : config . world . world_id ,
home_world_id : config . world . world_id ,
2025-05-12 15:44:39 -04:00
gm_rank : connection . player_data . gm_rank ,
online_status ,
2025-05-12 01:17:15 -04:00
common : common . clone ( ) ,
.. Default ::default ( )
} ) ,
.. Default ::default ( )
} ;
2025-05-06 21:57:52 -04:00
2025-05-12 01:17:15 -04:00
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
}
2025-05-06 21:57:52 -04:00
2025-05-12 01:17:15 -04:00
// fade in?
{
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::PrepareZoning ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::PrepareZoning {
unk : [ 0 , 0 , 0 , 0 ] ,
} ,
.. Default ::default ( )
} ;
2025-05-06 21:57:52 -04:00
2025-05-12 01:17:15 -04:00
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
}
2025-05-06 21:57:52 -04:00
2025-05-12 01:17:15 -04:00
// wipe any exit position so it isn't accidentally reused
connection . exit_position = None ;
connection . exit_rotation = None ;
2025-05-06 21:57:52 -04:00
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::ClientTrigger ( trigger ) = > {
// store the query for scripts
if let ClientTriggerCommand ::TeleportQuery { aetheryte_id } = trigger . trigger {
connection . player_data . teleport_query . aetheryte_id = aetheryte_id as u16 ;
}
2025-03-13 00:41:16 -04:00
2025-05-12 01:17:15 -04:00
// inform the server of our trigger, it will handle sending it to other clients
connection . handle . send ( ToServer ::ClientTrigger ( connection . id , connection . player_data . actor_id , trigger . clone ( ) ) ) . await ;
2025-04-01 20:39:57 -04:00
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::Unk2 { .. } = > {
2025-06-19 16:01:31 -04:00
// no-op
2025-03-18 23:48:00 -04:00
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::Unk3 { .. } = > {
2025-06-19 16:01:31 -04:00
// no-op
2025-03-13 00:41:16 -04:00
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::Unk4 { .. } = > {
2025-06-19 16:01:31 -04:00
// no-op
2025-03-30 16:41:06 -04:00
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::SetSearchInfoHandler { .. } = > {
tracing ::info! ( " Recieved SetSearchInfoHandler! " ) ;
2025-03-31 23:23:29 -04:00
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::Unk5 { .. } = > {
2025-06-19 16:01:31 -04:00
// no-op
2025-05-12 01:17:15 -04:00
}
ClientZoneIpcData ::SocialListRequest ( request ) = > {
tracing ::info! ( " Recieved social list request! " ) ;
match & request . request_type {
SocialListRequestType ::Party = > {
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::SocialList ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::SocialList ( SocialList {
request_type : request . request_type ,
sequence : request . count ,
entries : vec ! [ PlayerEntry {
// TODO: fill with actual player data, it also shows up wrong in game
content_id : connection . player_data . content_id ,
zone_id : connection . zone . as_ref ( ) . unwrap ( ) . id ,
zone_id1 : 0x0100 ,
class_job : 36 ,
level : 100 ,
one : 1 ,
name : " INVALID " . to_string ( ) ,
.. Default ::default ( )
} ] ,
} ) ,
.. Default ::default ( )
} ;
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
}
SocialListRequestType ::Friends = > {
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::SocialList ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::SocialList ( SocialList {
request_type : request . request_type ,
sequence : request . count ,
entries : Default ::default ( ) ,
} ) ,
.. Default ::default ( )
} ;
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
2025-05-11 12:38:55 -04:00
}
}
2025-05-11 09:27:29 -04:00
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::UpdatePositionHandler { position , rotation } = > {
connection . player_data . rotation = * rotation ;
connection . player_data . position = * position ;
2025-03-15 20:49:07 -04:00
2025-05-12 01:17:15 -04:00
connection . handle . send ( ToServer ::ActorMoved ( connection . id , connection . player_data . actor_id , * position , * rotation ) ) . await ;
}
ClientZoneIpcData ::LogOut { .. } = > {
tracing ::info! ( " Recieved log out from client! " ) ;
2025-03-15 20:49:07 -04:00
2025-05-12 01:17:15 -04:00
connection . begin_log_out ( ) . await ;
}
ClientZoneIpcData ::Disconnected { .. } = > {
tracing ::info! ( " Client disconnected! " ) ;
2025-03-15 19:34:29 -04:00
2025-05-12 01:17:15 -04:00
connection . handle . send ( ToServer ::Disconnected ( connection . id ) ) . await ;
2025-03-30 09:29:36 -04:00
2025-05-12 01:17:15 -04:00
break ;
}
ClientZoneIpcData ::ChatMessage ( chat_message ) = > {
connection . handle . send ( ToServer ::Message ( connection . id , chat_message . message . clone ( ) ) ) . await ;
let mut handled = false ;
2025-06-22 01:16:31 +00:00
let command_trigger : char = '!' ;
if chat_message . message . starts_with ( command_trigger )
2025-05-12 01:17:15 -04:00
{
let parts : Vec < & str > = chat_message . message . split ( ' ' ) . collect ( ) ;
let command_name = & parts [ 0 ] [ 1 .. ] ;
2025-06-22 09:28:50 -04:00
{
let lua = lua . lock ( ) . unwrap ( ) ;
let state = lua . app_data_ref ::< ExtraLuaState > ( ) . unwrap ( ) ;
2025-05-12 01:17:15 -04:00
2025-06-22 09:28:50 -04:00
// If a Lua command exists, try using that first
if let Some ( command_script ) =
state . command_scripts . get ( command_name )
2025-05-12 01:17:15 -04:00
{
handled = true ;
2025-06-22 08:27:21 -04:00
let file_name = format! (
" {}/{} " ,
& config . world . scripts_location , command_script
) ;
let mut run_script = | | -> mlua ::Result < ( ) > {
lua . scope ( | scope | {
let connection_data = scope
. create_userdata_ref_mut ( & mut lua_player ) ? ;
2025-07-04 10:40:45 -04:00
/* TODO: Instead of panicking we ought to send a message to the player
* and the console log , and abandon execution . * /
2025-06-22 08:27:21 -04:00
lua . load (
2025-07-04 10:40:45 -04:00
std ::fs ::read ( & file_name ) . unwrap_or_else ( | _ | panic! ( " Failed to load script file {} ! " , & file_name ) ) ,
2025-06-22 08:27:21 -04:00
)
. set_name ( " @ " . to_string ( ) + & file_name )
. exec ( ) ? ;
let required_rank = lua . globals ( ) . get ( " required_rank " ) ;
if let Err ( error ) = required_rank {
tracing ::info! ( " Script is missing required_rank! Unable to run command, sending error to user. Additional information: {} " , error ) ;
let func : Function =
lua . globals ( ) . get ( " onCommandRequiredRankMissingError " ) ? ;
func . call ::< ( ) > ( ( error . to_string ( ) , connection_data ) ) ? ;
return Ok ( ( ) ) ;
}
/* Reset state for future commands. Without this it'll stay set to the last value
* and allow other commands that omit required_rank to run , which is undesirable . * /
lua . globals ( ) . set ( " required_rank " , mlua ::Value ::Nil ) ? ;
if connection . player_data . gm_rank as u8 > = required_rank ? {
2025-06-28 10:12:10 -04:00
let mut func_args = Vec ::new ( ) ;
2025-06-22 08:27:21 -04:00
if parts . len ( ) > 1 {
2025-07-04 10:41:43 -04:00
func_args = ( parts [ 1 .. ] ) . to_vec ( ) ;
2025-06-28 10:12:10 -04:00
tracing ::info! ( " Args passed to Lua command {}: {:?} " , command_name , func_args ) ;
2025-06-22 08:27:21 -04:00
} else {
tracing ::info! ( " No additional args passed to Lua command {}. " , command_name ) ;
}
let func : Function =
lua . globals ( ) . get ( " onCommand " ) ? ;
func . call ::< ( ) > ( ( func_args , connection_data ) ) ? ;
2025-06-25 18:24:21 -04:00
/* `command_sender` is an optional variable scripts can define to identify themselves in print messages.
* It ' s okay if this global isn ' t set . We also don ' t care what its value is , just that it exists .
* This is reset - after - running the command intentionally . Resetting beforehand will never display the command ' s identifier .
* /
let command_sender : Result < mlua ::prelude ::LuaValue , mlua ::prelude ::LuaError > = lua . globals ( ) . get ( " command_sender " ) ;
2025-07-04 10:32:59 -04:00
if command_sender . is_ok ( ) {
2025-06-25 18:24:21 -04:00
lua . globals ( ) . set ( " command_sender " , mlua ::Value ::Nil ) ? ;
}
2025-06-22 08:27:21 -04:00
Ok ( ( ) )
2025-06-20 09:27:37 -04:00
} else {
2025-06-22 08:27:21 -04:00
tracing ::info! ( " User with account_id {} tried to invoke GM command {} with insufficient privileges! " ,
connection . player_data . account_id , command_name ) ;
let func : Function =
lua . globals ( ) . get ( " onCommandRequiredRankInsufficientError " ) ? ;
func . call ::< ( ) > ( connection_data ) ? ;
Ok ( ( ) )
2025-06-20 09:27:37 -04:00
}
2025-06-22 08:27:21 -04:00
} )
} ;
if let Err ( err ) = run_script ( ) {
tracing ::warn! ( " Lua error in {file_name}: {:?} " , err ) ;
}
2025-06-22 09:28:50 -04:00
}
}
2025-06-21 12:23:25 -04:00
2025-06-22 09:28:50 -04:00
// Fallback to Rust implemented commands
if ! handled {
handled = ChatHandler ::handle_chat_message (
& mut connection ,
chat_message ,
)
. await ;
}
2025-06-22 08:35:03 -04:00
2025-06-22 09:28:50 -04:00
// If it's truly not existent:
if ! handled {
tracing ::info! ( " Unknown command {command_name} " ) ;
let lua = lua . lock ( ) . unwrap ( ) ;
let mut call_func = | | {
lua . scope ( | scope | {
let connection_data = scope
. create_userdata_ref_mut ( & mut lua_player ) ? ;
let func : Function =
lua . globals ( ) . get ( " onUnknownCommandError " ) ? ;
func . call ::< ( ) > ( ( command_name , connection_data ) ) ? ;
Ok ( ( ) )
} )
} ;
2025-03-30 09:02:52 -04:00
2025-06-22 09:28:50 -04:00
if let Err ( err ) = call_func ( ) {
tracing ::warn! ( " Lua error in Global.lua: {:?} " , err ) ;
}
}
2025-05-12 01:17:15 -04:00
}
}
2025-06-28 10:07:00 -04:00
ClientZoneIpcData ::GMCommand { command , arg0 , arg1 , arg2 , arg3 , .. } = > {
2025-06-28 09:56:04 -04:00
let lua = lua . lock ( ) . unwrap ( ) ;
let state = lua . app_data_ref ::< ExtraLuaState > ( ) . unwrap ( ) ;
2025-06-27 23:24:30 -04:00
2025-06-28 09:56:04 -04:00
if let Some ( command_script ) =
state . gm_command_scripts . get ( command )
{
let file_name = format! (
" {}/{} " ,
& config . world . scripts_location , command_script
) ;
let mut run_script = | | -> mlua ::Result < ( ) > {
lua . scope ( | scope | {
let connection_data = scope
. create_userdata_ref_mut ( & mut lua_player ) ? ;
2025-07-04 10:40:45 -04:00
/* TODO: Instead of panicking we ought to send a message to the player
* and the console log , and abandon execution . * /
2025-06-28 09:56:04 -04:00
lua . load (
2025-07-04 10:40:45 -04:00
std ::fs ::read ( & file_name ) . unwrap_or_else ( | _ | panic! ( " Failed to load script file {} ! " , & file_name ) ) ,
2025-06-28 09:56:04 -04:00
)
. set_name ( " @ " . to_string ( ) + & file_name )
. exec ( ) ? ;
let required_rank = lua . globals ( ) . get ( " required_rank " ) ;
if let Err ( error ) = required_rank {
tracing ::info! ( " Script is missing required_rank! Unable to run command, sending error to user. Additional information: {} " , error ) ;
let func : Function =
lua . globals ( ) . get ( " onCommandRequiredRankMissingError " ) ? ;
func . call ::< ( ) > ( ( error . to_string ( ) , connection_data ) ) ? ;
return Ok ( ( ) ) ;
2025-05-12 01:17:15 -04:00
}
2025-06-28 09:56:04 -04:00
/* Reset state for future commands. Without this it'll stay set to the last value
* and allow other commands that omit required_rank to run , which is undesirable . * /
lua . globals ( ) . set ( " required_rank " , mlua ::Value ::Nil ) ? ;
if connection . player_data . gm_rank as u8 > = required_rank ? {
let func : Function =
lua . globals ( ) . get ( " onCommand " ) ? ;
2025-06-28 10:07:00 -04:00
func . call ::< ( ) > ( ( [ * arg0 , * arg1 , * arg2 , * arg3 ] , connection_data ) ) ? ;
2025-06-28 09:56:04 -04:00
/* `command_sender` is an optional variable scripts can define to identify themselves in print messages.
* It ' s okay if this global isn ' t set . We also don ' t care what its value is , just that it exists .
* This is reset - after - running the command intentionally . Resetting beforehand will never display the command ' s identifier .
* /
let command_sender : Result < mlua ::prelude ::LuaValue , mlua ::prelude ::LuaError > = lua . globals ( ) . get ( " command_sender " ) ;
2025-07-04 10:32:59 -04:00
if command_sender . is_ok ( ) {
2025-06-28 09:56:04 -04:00
lua . globals ( ) . set ( " command_sender " , mlua ::Value ::Nil ) ? ;
}
Ok ( ( ) )
2025-06-28 00:07:56 -04:00
} else {
2025-06-28 09:56:04 -04:00
tracing ::info! ( " User with account_id {} tried to invoke GM command {} with insufficient privileges! " ,
connection . player_data . account_id , command ) ;
let func : Function =
lua . globals ( ) . get ( " onCommandRequiredRankInsufficientError " ) ? ;
func . call ::< ( ) > ( connection_data ) ? ;
Ok ( ( ) )
2025-06-28 00:07:56 -04:00
}
2025-06-28 09:56:04 -04:00
} )
} ;
2025-06-27 23:24:30 -04:00
2025-06-28 09:56:04 -04:00
if let Err ( err ) = run_script ( ) {
tracing ::warn! ( " Lua error in {file_name}: {:?} " , err ) ;
2025-05-12 01:17:15 -04:00
}
}
}
ClientZoneIpcData ::ZoneJump {
exit_box ,
position ,
..
} = > {
tracing ::info! (
" Character entered {exit_box} with a position of {position:#?}! "
) ;
// find the exit box id
let new_territory ;
{
let ( _ , exit_box ) = connection
. zone
. as_ref ( )
. unwrap ( )
. find_exit_box ( * exit_box )
2025-03-29 14:14:03 -04:00
. unwrap ( ) ;
2025-03-27 23:32:36 -04:00
2025-05-12 01:17:15 -04:00
// find the pop range on the other side
let mut game_data = game_data . lock ( ) . unwrap ( ) ;
2025-06-28 09:56:04 -04:00
let new_zone = Zone ::load ( & mut game_data , exit_box . territory_type ) ;
2025-05-12 01:17:15 -04:00
let ( destination_object , _ ) = new_zone
. find_pop_range ( exit_box . destination_instance_id )
. unwrap ( ) ;
2025-03-27 23:32:36 -04:00
2025-05-12 01:17:15 -04:00
// set the exit position
connection . exit_position = Some ( Position {
x : destination_object . transform . translation [ 0 ] ,
y : destination_object . transform . translation [ 1 ] ,
z : destination_object . transform . translation [ 2 ] ,
} ) ;
new_territory = exit_box . territory_type ;
}
2025-03-29 14:14:03 -04:00
2025-05-12 01:17:15 -04:00
connection . change_zone ( new_territory ) . await ;
2025-03-30 09:02:52 -04:00
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::ActionRequest ( request ) = > {
2025-06-21 10:36:44 -04:00
connection
. handle
. send ( ToServer ::ActionRequest (
connection . id ,
connection . player_data . actor_id ,
request . clone ( ) ,
) )
. await ;
2025-05-12 01:17:15 -04:00
}
ClientZoneIpcData ::Unk16 { .. } = > {
2025-06-19 16:01:31 -04:00
// no-op
2025-05-12 01:17:15 -04:00
}
2025-06-25 23:11:49 -04:00
ClientZoneIpcData ::Unk17 { unk1 , .. } = > {
// this is *usually* sent in response, but not always
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::UnkCall ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::UnkCall {
unk1 : * unk1 , // copied from here
unk2 : 333 , // always this for some reason
} ,
.. Default ::default ( )
} ;
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
2025-05-12 01:17:15 -04:00
}
ClientZoneIpcData ::Unk18 { .. } = > {
2025-06-19 16:01:31 -04:00
// no-op
2025-05-12 01:17:15 -04:00
}
ClientZoneIpcData ::EventRelatedUnk {
unk1 ,
unk2 ,
unk3 ,
unk4 ,
} = > {
tracing ::info! (
" Recieved EventRelatedUnk! {unk1} {unk2} {unk3} {unk4} "
) ;
if let Some ( event ) = connection . event . as_mut ( ) {
event . scene_finished ( & mut lua_player , * unk2 ) ;
2025-03-30 09:44:37 -04:00
}
2025-05-12 01:17:15 -04:00
}
ClientZoneIpcData ::Unk19 { .. } = > {
2025-06-19 16:01:31 -04:00
// no-op
2025-05-12 01:17:15 -04:00
}
ClientZoneIpcData ::ItemOperation ( action ) = > {
tracing ::info! ( " Client is modifying inventory! {action:#?} " ) ;
2025-04-18 12:38:02 -04:00
2025-07-05 19:05:43 -04:00
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::InventoryActionAck ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::InventoryActionAck {
sequence : action . context_id ,
action_type : INVENTORY_ACTION_ACK_GENERAL as u16 ,
} ,
.. Default ::default ( )
} ;
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
if action . operation_type = = INVENTORY_ACTION_DISCARD {
tracing ::info! ( " Player is discarding from their inventory! " ) ;
let sequence = 0 ; // TODO: How is this decided? It seems to be a sequence value but it's not sent by the client! Perhaps it's a 'lifetime-of-the-character' value that simply gets increased for every inventory action ever taken?
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::InventorySlotDiscard ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::InventorySlotDiscard {
unk1 : sequence ,
operation_type : action . operation_type ,
src_actor_id : action . src_actor_id ,
src_storage_id : action . src_storage_id ,
src_container_index : action . src_container_index ,
src_stack : action . src_stack ,
src_catalog_id : action . src_catalog_id ,
dst_actor_id : 0xE0000000 ,
dst_storage_id : 0xFFFF ,
dst_container_index : 0xFFFF ,
dst_catalog_id : 0x0000FFFF ,
} ,
.. Default ::default ( )
} ;
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::InventorySlotDiscard ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::InventorySlotDiscardFin {
unk1 : sequence ,
unk2 : sequence , // yes, this repeats, it's not a copy paste error
unk3 : 0x90 ,
unk4 : 0x200 ,
} ,
.. Default ::default ( )
} ;
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
}
2025-05-12 01:17:15 -04:00
connection . player_data . inventory . process_action ( action ) ;
2025-07-05 19:05:43 -04:00
// TODO: This seems incorrect, the server wasn't observed to send updates here, but if we don't then the client doesn't realize the items have been modified
2025-05-12 01:17:15 -04:00
connection . send_inventory ( true ) . await ;
2025-03-30 09:44:37 -04:00
}
2025-06-30 15:21:08 -04:00
// TODO: Likely rename this opcode if non-gil shops also use this same opcode
ClientZoneIpcData ::GilShopTransaction { event_id , unk1 : _ , buy_sell_mode , item_index , item_quantity , unk2 : _ } = > {
tracing ::info! ( " Client is interacting with a shop! {event_id:#?} {buy_sell_mode:#?} {item_quantity:#?} {item_index:#?} " ) ;
2025-07-01 21:21:47 -04:00
const BUY : u32 = 1 ;
const SELL : u32 = 2 ;
2025-06-30 15:21:08 -04:00
2025-07-01 21:21:47 -04:00
if * buy_sell_mode = = BUY {
let result ;
{
let mut game_data = connection . gamedata . lock ( ) . unwrap ( ) ;
result = game_data . get_gilshop_item ( * event_id , * item_index as u16 ) ;
}
2025-06-30 20:26:15 -04:00
2025-07-02 13:30:48 -04:00
if let Some ( item_info ) = result {
2025-07-04 10:30:04 -04:00
if connection . player_data . inventory . currency . gil . quantity > = item_info . price_mid {
2025-07-01 21:21:47 -04:00
// TODO: send the proper response packets!
2025-07-04 10:30:04 -04:00
connection . player_data . inventory . currency . gil . quantity - = item_info . price_mid ;
connection . player_data . inventory . add_in_next_free_slot ( Item ::new ( 1 , item_info . id ) ) ;
2025-07-01 21:21:47 -04:00
connection . send_inventory ( false ) . await ;
// TODO: send an actual system notice, this is just a placeholder to provide feedback that the player actually bought something.
2025-07-02 13:30:48 -04:00
connection . send_message ( & format! ( " You obtained one or more items: {} (id: {} )! " , item_info . name , item_info . id ) ) . await ;
2025-07-01 21:21:47 -04:00
} else {
connection . send_message ( " Insufficient gil to buy item. Nice try bypassing the client-side check! " ) . await ;
}
} else {
2025-07-04 10:28:21 -04:00
connection . send_message ( " Unable to find shop item, this is a bug in Kawari! " ) . await ;
2025-07-01 21:21:47 -04:00
}
} else if * buy_sell_mode = = SELL {
// TODO: Implement selling items back to shops
connection . send_message ( " Selling items to shops is not yet implemented. Cancelling event... " ) . await ;
2025-06-30 20:26:15 -04:00
} else {
2025-07-01 21:21:47 -04:00
tracing ::error! ( " Received unknown transaction mode {buy_sell_mode}! " ) ;
2025-06-30 20:26:15 -04:00
}
2025-06-30 15:21:08 -04:00
// Cancel the event for now so the client doesn't get stuck
connection . event_finish ( * event_id ) . await ;
}
2025-05-12 01:17:15 -04:00
ClientZoneIpcData ::StartTalkEvent { actor_id , event_id } = > {
// load event
{
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::EventStart ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::EventStart ( EventStart {
target_id : * actor_id ,
event_id : * event_id ,
event_type : 1 , // talk?
.. Default ::default ( )
} ) ,
.. Default ::default ( )
} ;
2025-03-30 09:44:37 -04:00
2025-05-12 01:17:15 -04:00
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
}
2025-03-29 14:14:03 -04:00
2025-06-30 15:21:08 -04:00
/* TODO: ServerZoneIpcType::Unk18 with data [64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
* was observed to always be sent by the server upon interacting with shops . They open and function fine without
* it , but should we send it anyway , for the sake of accuracy ? It ' s also still unclear if this
* happens for - every - NPC / actor . * /
2025-05-12 01:17:15 -04:00
let mut should_cancel = false ;
{
let lua = lua . lock ( ) . unwrap ( ) ;
let state = lua . app_data_ref ::< ExtraLuaState > ( ) . unwrap ( ) ;
if let Some ( event_script ) =
state . event_scripts . get ( event_id )
{
2025-07-04 10:26:43 -04:00
connection . event = Some ( Event ::new ( * event_id , event_script ) ) ;
2025-05-12 01:17:15 -04:00
connection
. event
. as_mut ( )
. unwrap ( )
. talk ( * actor_id , & mut lua_player ) ;
} else {
tracing ::warn! ( " Event {event_id} isn't scripted yet! Ignoring... " ) ;
should_cancel = true ;
}
}
2025-04-18 01:38:47 -04:00
2025-05-12 01:17:15 -04:00
if should_cancel {
// give control back to the player so they aren't stuck
connection . event_finish ( * event_id ) . await ;
2025-07-04 10:25:23 -04:00
connection . send_message ( & format! ( " Event {event_id} tried to start, but it doesn't have a script associated with it! " ) ) . await ;
2025-04-18 01:38:47 -04:00
}
}
2025-06-28 15:57:45 -04:00
ClientZoneIpcData ::EventYieldHandler ( handler ) = > {
tracing ::info! ( " Finishing this event... {} {} {} {:?} " , handler . handler_id , handler . error_code , handler . scene , & handler . params [ .. handler . num_results as usize ] ) ;
2025-05-05 20:51:49 -04:00
2025-05-05 21:15:03 -04:00
connection
2025-05-12 01:17:15 -04:00
. event
2025-06-28 15:57:45 -04:00
. as_mut ( )
. unwrap ( )
. finish ( handler . scene , & handler . params [ .. handler . num_results as usize ] , & mut lua_player ) ;
}
ClientZoneIpcData ::EventYieldHandler8 ( handler ) = > {
tracing ::info! ( " Finishing this event... {} {} {} {:?} " , handler . handler_id , handler . error_code , handler . scene , & handler . params [ .. handler . num_results as usize ] ) ;
connection
. event
. as_mut ( )
. unwrap ( )
. finish ( handler . scene , & handler . params [ .. handler . num_results as usize ] , & mut lua_player ) ;
2025-05-05 23:45:22 -04:00
}
2025-06-25 21:30:14 -04:00
ClientZoneIpcData ::Config ( config ) = > {
connection
. handle
. send ( ToServer ::Config (
connection . id ,
connection . player_data . actor_id ,
config . clone ( ) ,
) )
. await ;
2025-06-25 23:11:49 -04:00
}
ClientZoneIpcData ::EventUnkRequest { event_id , unk1 , unk2 , unk3 } = > {
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::EventUnkReply ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::EventUnkReply {
event_id : * event_id ,
unk1 : * unk1 ,
unk2 : * unk2 ,
unk3 : * unk3 + 1 ,
} ,
.. Default ::default ( )
} ;
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
2025-06-25 21:30:14 -04:00
}
2025-07-04 16:18:39 -04:00
ClientZoneIpcData ::UnkCall2 { .. } = > {
let ipc = ServerZoneIpcSegment {
op_code : ServerZoneIpcType ::UnkResponse2 ,
timestamp : timestamp_secs ( ) ,
data : ServerZoneIpcData ::UnkResponse2 {
unk1 : 1 ,
} ,
.. Default ::default ( )
} ;
connection
. send_segment ( PacketSegment {
source_actor : connection . player_data . actor_id ,
target_actor : connection . player_data . actor_id ,
segment_type : SegmentType ::Ipc ,
data : SegmentData ::Ipc { data : ipc } ,
} )
. await ;
}
2025-06-28 08:48:59 -04:00
ClientZoneIpcData ::Unknown { .. } = > {
tracing ::warn! ( " Unknown packet {:?} recieved, this should be handled! " , data . op_code ) ;
}
2025-05-05 23:45:22 -04:00
}
2025-05-05 20:51:49 -04:00
}
2025-05-12 01:17:15 -04:00
SegmentData ::KeepAliveRequest { id , timestamp } = > {
send_keep_alive ::< ServerZoneIpcSegment > (
& mut connection . socket ,
& mut connection . state ,
ConnectionType ::Zone ,
* id ,
* timestamp ,
)
. await
}
SegmentData ::KeepAliveResponse { .. } = > {
tracing ::info! ( " Got keep alive response from client... cool... " ) ;
}
SegmentData ::KawariIpc { data } = > handle_custom_ipc ( & mut connection , data ) . await ,
_ = > {
2025-06-21 14:04:09 -04:00
panic! ( " The server is recieving a response or unknown packet: {segment:#?} " )
2025-05-05 20:51:49 -04:00
}
2025-03-10 21:31:21 -04:00
}
2025-03-09 11:07:01 -04:00
}
2025-03-27 22:54:36 -04:00
2025-05-12 01:17:15 -04:00
// copy from lua player state, as they modify the status effects list
// TODO: i dunno?
connection . status_effects = lua_player . status_effects . clone ( ) ;
2025-03-28 00:22:41 -04:00
2025-05-12 01:17:15 -04:00
// Process any queued packets from scripts and whatnot
connection . process_lua_player ( & mut lua_player ) . await ;
2025-03-28 00:22:41 -04:00
2025-05-12 01:17:15 -04:00
// check if status effects need sending
connection . process_effects_list ( ) . await ;
2025-03-28 00:22:41 -04:00
2025-05-12 01:17:15 -04:00
// update lua player
lua_player . player_data = connection . player_data . clone ( ) ;
lua_player . status_effects = connection . status_effects . clone ( ) ;
2025-06-28 09:56:04 -04:00
if let Some ( zone ) = & connection . zone {
lua_player . zone_data = LuaZone {
zone_id : zone . id ,
weather_id : connection . weather_id ,
internal_name : zone . internal_name . clone ( ) ,
region_name : zone . region_name . clone ( ) ,
place_name : zone . place_name . clone ( ) ,
2025-06-28 10:15:51 -04:00
intended_use : zone . intended_use ,
2025-06-28 09:56:04 -04:00
} ;
}
2025-05-12 01:17:15 -04:00
}
} ,
Err ( _ ) = > {
2025-05-12 16:00:23 -04:00
tracing ::info! ( " Connection {:#?} was killed because of a network error! " , client_handle . id ) ;
2025-05-12 01:17:15 -04:00
break ;
} ,
2025-03-09 11:07:01 -04:00
}
}
2025-03-30 23:17:11 -04:00
msg = internal_recv . recv ( ) = > match msg {
2025-03-30 16:41:06 -04:00
Some ( msg ) = > match msg {
2025-05-09 20:09:22 -04:00
FromServer ::Message ( msg ) = > connection . send_message ( & msg ) . await ,
2025-06-18 20:28:03 -04:00
FromServer ::ActorSpawn ( actor , spawn ) = > connection . spawn_actor ( actor , spawn ) . await ,
2025-04-01 20:23:23 -04:00
FromServer ::ActorMove ( actor_id , position , rotation ) = > connection . set_actor_position ( actor_id , position , rotation ) . await ,
2025-05-08 22:53:36 -04:00
FromServer ::ActorDespawn ( actor_id ) = > connection . remove_actor ( actor_id ) . await ,
FromServer ::ActorControl ( actor_id , actor_control ) = > connection . actor_control ( actor_id , actor_control ) . await ,
FromServer ::ActorControlTarget ( actor_id , actor_control ) = > connection . actor_control_target ( actor_id , actor_control ) . await ,
2025-05-11 10:12:02 -04:00
FromServer ::ActorControlSelf ( actor_control ) = > connection . actor_control_self ( actor_control ) . await ,
2025-06-21 10:36:44 -04:00
FromServer ::ActionComplete ( request ) = > connection . execute_action ( request , & mut lua_player ) . await ,
FromServer ::ActionCancelled ( ) = > connection . cancel_action ( ) . await ,
2025-06-25 21:30:14 -04:00
FromServer ::UpdateConfig ( actor_id , config ) = > connection . update_config ( actor_id , config ) . await ,
2025-06-28 11:11:42 -04:00
FromServer ::ActorEquip ( actor_id , main_weapon_id , model_ids ) = > connection . update_equip ( actor_id , main_weapon_id , model_ids ) . await ,
2025-03-30 16:41:06 -04:00
} ,
None = > break ,
2025-03-30 23:17:11 -04:00
}
2025-03-30 16:41:06 -04:00
}
}
2025-05-12 01:17:15 -04:00
// forcefully log out the player if they weren't logging out but force D/C'd
2025-05-12 16:00:23 -04:00
if connection . player_data . actor_id ! = 0
& & ! connection . gracefully_logged_out
& & is_zone_connection
{
tracing ::info! (
" Forcefully logging out connection {:#?}... " ,
client_handle . id
) ;
2025-05-12 01:17:15 -04:00
connection . begin_log_out ( ) . await ;
connection
. handle
. send ( ToServer ::Disconnected ( connection . id ) )
. await ;
}
2025-03-30 16:41:06 -04:00
}
2025-04-18 01:17:51 -04:00
async fn handle_rcon ( listener : & Option < TcpListener > ) -> Option < ( TcpStream , SocketAddr ) > {
match listener {
Some ( listener ) = > Some ( listener . accept ( ) . await . ok ( ) ? ) ,
None = > None ,
}
}
2025-03-30 16:41:06 -04:00
#[ tokio::main ]
async fn main ( ) {
tracing_subscriber ::fmt ::init ( ) ;
let config = get_config ( ) ;
let addr = config . world . get_socketaddr ( ) ;
let listener = TcpListener ::bind ( addr ) . await . unwrap ( ) ;
2025-04-15 23:35:49 -04:00
let rcon_listener = if ! config . world . rcon_password . is_empty ( ) {
Some (
TcpListener ::bind ( config . world . get_rcon_socketaddr ( ) )
. await
. unwrap ( ) ,
)
} else {
None
} ;
2025-03-30 16:41:06 -04:00
tracing ::info! ( " Server started on {addr} " ) ;
let database = Arc ::new ( WorldDatabase ::new ( ) ) ;
let lua = Arc ::new ( Mutex ::new ( Lua ::new ( ) ) ) ;
let game_data = Arc ::new ( Mutex ::new ( GameData ::new ( ) ) ) ;
{
2025-06-21 12:16:27 -04:00
let mut lua = 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:19:42 -04:00
}
2025-03-30 16:41:06 -04:00
}
2025-03-30 17:59:17 -04:00
let ( handle , _ ) = spawn_main_loop ( ) ;
2025-03-30 16:41:06 -04:00
loop {
2025-04-15 23:35:49 -04:00
tokio ::select! {
Ok ( ( socket , ip ) ) = listener . accept ( ) = > {
let id = handle . next_id ( ) ;
let state = PacketState {
client_key : None ,
clientbound_oodle : OodleNetwork ::new ( ) ,
serverbound_oodle : OodleNetwork ::new ( ) ,
} ;
spawn_client ( ZoneConnection {
2025-05-02 22:41:31 -04:00
config : get_config ( ) . world ,
2025-04-15 23:35:49 -04:00
socket ,
state ,
player_data : PlayerData ::default ( ) ,
spawn_index : 0 ,
zone : None ,
status_effects : StatusEffects ::default ( ) ,
event : None ,
actors : Vec ::new ( ) ,
ip ,
id ,
handle : handle . clone ( ) ,
database : database . clone ( ) ,
lua : lua . clone ( ) ,
gamedata : game_data . clone ( ) ,
2025-05-12 01:17:15 -04:00
exit_position : None ,
exit_rotation : None ,
last_keep_alive : Instant ::now ( ) ,
2025-06-28 09:56:04 -04:00
gracefully_logged_out : false ,
weather_id : 0 ,
2025-07-03 16:12:19 -04:00
obsfucation_data : ObsfucationData ::default ( ) ,
2025-04-15 23:35:49 -04:00
} ) ;
}
2025-04-18 01:17:51 -04:00
Some ( ( mut socket , _ ) ) = handle_rcon ( & rcon_listener ) = > {
2025-04-15 23:35:49 -04:00
let mut authenticated = false ;
loop {
// read from client
let mut resp_bytes = [ 0 u8 ; rkon ::MAX_PACKET_SIZE ] ;
let n = socket . read ( & mut resp_bytes ) . await . unwrap ( ) ;
if n > 0 {
let request = rkon ::Packet ::decode ( & resp_bytes ) . unwrap ( ) ;
match request . packet_type {
rkon ::PacketType ::Command = > {
if authenticated {
let response = rkon ::Packet {
request_id : request . request_id ,
packet_type : rkon ::PacketType ::Command ,
body : " hello world! " . to_string ( )
} ;
let encoded = response . encode ( ) ;
socket . write_all ( & encoded ) . await . unwrap ( ) ;
}
} ,
rkon ::PacketType ::Login = > {
let config = get_config ( ) ;
if request . body = = config . world . rcon_password {
authenticated = true ;
let response = rkon ::Packet {
request_id : request . request_id ,
packet_type : rkon ::PacketType ::Command ,
body : String ::default ( )
} ;
let encoded = response . encode ( ) ;
socket . write_all ( & encoded ) . await . unwrap ( ) ;
} else {
authenticated = false ;
let response = rkon ::Packet {
request_id : - 1 ,
packet_type : rkon ::PacketType ::Command ,
body : String ::default ( )
} ;
let encoded = response . encode ( ) ;
socket . write_all ( & encoded ) . await . unwrap ( ) ;
}
} ,
_ = > tracing ::warn! ( " Ignoring unknown RCON packet " )
}
}
}
}
2025-03-30 16:41:06 -04:00
} ;
2025-03-09 11:07:01 -04:00
}
}