2025-03-30 09:02:52 -04:00
|
|
|
use std::collections::HashMap;
|
2025-03-27 16:20:33 -04:00
|
|
|
use std::sync::{Arc, Mutex};
|
2025-03-21 19:56:16 -04:00
|
|
|
|
2025-03-30 21:42:46 -04:00
|
|
|
use kawari::RECEIVE_BUFFER_SIZE;
|
2025-03-21 19:56:16 -04:00
|
|
|
use kawari::common::custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType};
|
2025-03-30 17:49:45 -04:00
|
|
|
use kawari::common::{GameData, ObjectId, timestamp_secs};
|
2025-03-30 10:34:42 -04:00
|
|
|
use kawari::common::{Position, determine_initial_starting_zone};
|
2025-03-19 00:28:47 -04:00
|
|
|
use kawari::config::get_config;
|
2025-03-22 18:34:27 -04:00
|
|
|
use kawari::lobby::CharaMake;
|
2025-03-17 17:22:09 -04:00
|
|
|
use kawari::oodle::OodleNetwork;
|
2025-03-26 18:28:51 -04:00
|
|
|
use kawari::opcodes::ServerZoneIpcType;
|
2025-03-21 21:26:32 -04:00
|
|
|
use kawari::packet::{
|
|
|
|
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, send_keep_alive,
|
|
|
|
send_packet,
|
|
|
|
};
|
2025-03-16 17:43:29 -04:00
|
|
|
use kawari::world::ipc::{
|
2025-04-01 19:15:08 -04:00
|
|
|
ActionEffect, ActionResult, ClientZoneIpcData, EffectKind, GameMasterCommandType,
|
|
|
|
GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
|
2025-03-16 17:43:29 -04:00
|
|
|
};
|
2025-03-30 17:11:41 -04:00
|
|
|
use kawari::world::{
|
2025-04-01 21:37:41 -04:00
|
|
|
Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, Item, LuaPlayer,
|
|
|
|
PlayerData, ServerHandle, StatusEffects, ToServer, WorldDatabase,
|
2025-03-30 17:11:41 -04:00
|
|
|
};
|
2025-03-12 18:05:41 -04:00
|
|
|
use kawari::world::{
|
2025-04-01 18:49:42 -04:00
|
|
|
ChatHandler, Zone, ZoneConnection,
|
2025-03-16 17:43:29 -04:00
|
|
|
ipc::{
|
2025-04-01 20:23:23 -04:00
|
|
|
ActorControlCategory, ActorControlSelf, CommonSpawn, PlayerEntry, PlayerSetup, PlayerSpawn,
|
|
|
|
PlayerStats, SocialList,
|
2025-03-16 17:43:29 -04:00
|
|
|
},
|
2025-03-12 18:05:41 -04:00
|
|
|
};
|
2025-03-30 21:42:46 -04:00
|
|
|
|
2025-03-28 00:22:41 -04:00
|
|
|
use mlua::{Function, Lua};
|
2025-03-09 11:07:01 -04:00
|
|
|
use tokio::io::AsyncReadExt;
|
2025-03-30 16:41:06 -04:00
|
|
|
use tokio::join;
|
2025-03-30 17:59:17 -04:00
|
|
|
use tokio::net::TcpListener;
|
|
|
|
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-03-30 09:02:52 -04:00
|
|
|
#[derive(Default)]
|
|
|
|
struct ExtraLuaState {
|
|
|
|
action_scripts: HashMap<u32, String>,
|
|
|
|
}
|
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
#[derive(Default, Debug)]
|
|
|
|
struct Data {
|
|
|
|
clients: HashMap<ClientId, ClientHandle>,
|
2025-04-01 20:23:23 -04:00
|
|
|
// structure temporary, of course
|
|
|
|
actors: HashMap<ObjectId, CommonSpawn>,
|
2025-03-30 16:41:06 -04:00
|
|
|
}
|
2025-03-09 11:07:01 -04:00
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
async fn main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::io::Error> {
|
|
|
|
let mut data = Data::default();
|
2025-03-30 17:11:41 -04:00
|
|
|
let mut to_remove = Vec::new();
|
2025-03-09 11:07:01 -04:00
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
while let Some(msg) = recv.recv().await {
|
|
|
|
match msg {
|
|
|
|
ToServer::NewClient(handle) => {
|
|
|
|
data.clients.insert(handle.id, handle);
|
|
|
|
}
|
2025-04-01 20:23:23 -04:00
|
|
|
ToServer::ZoneLoaded(from_id) => {
|
|
|
|
for (id, handle) in &mut data.clients {
|
|
|
|
let id = *id;
|
|
|
|
|
|
|
|
if id == from_id {
|
|
|
|
// send existing player data
|
|
|
|
for (id, common) in &data.actors {
|
|
|
|
let msg =
|
|
|
|
FromServer::ActorSpawn(Actor { id: *id, hp: 100 }, common.clone());
|
|
|
|
|
|
|
|
let _ = handle.send(msg).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-03-30 16:41:06 -04:00
|
|
|
ToServer::Message(from_id, msg) => {
|
2025-03-30 17:11:41 -04:00
|
|
|
for (id, handle) in &mut data.clients {
|
2025-03-30 16:41:06 -04:00
|
|
|
let id = *id;
|
2025-03-22 16:47:21 -04:00
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
if id == from_id {
|
|
|
|
continue;
|
|
|
|
}
|
2025-03-09 11:07:01 -04:00
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
let msg = FromServer::Message(msg.clone());
|
2025-03-27 16:20:33 -04:00
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
if handle.send(msg).is_err() {
|
|
|
|
to_remove.push(id);
|
|
|
|
}
|
|
|
|
}
|
2025-03-30 17:11:41 -04:00
|
|
|
}
|
2025-04-01 19:22:13 -04:00
|
|
|
ToServer::ActorSpawned(from_id, actor, common) => {
|
2025-04-01 20:23:23 -04:00
|
|
|
data.actors.insert(actor.id, common.clone());
|
|
|
|
|
2025-03-30 17:11:41 -04:00
|
|
|
for (id, handle) in &mut data.clients {
|
|
|
|
let id = *id;
|
|
|
|
|
|
|
|
if id == from_id {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2025-04-01 19:22:13 -04:00
|
|
|
let msg = FromServer::ActorSpawn(actor, common.clone());
|
2025-03-30 09:02:52 -04:00
|
|
|
|
2025-03-30 17:11:41 -04:00
|
|
|
if handle.send(msg).is_err() {
|
|
|
|
to_remove.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-04-01 20:23:23 -04:00
|
|
|
ToServer::ActorMoved(from_id, actor_id, position, rotation) => {
|
2025-03-30 17:11:41 -04:00
|
|
|
for (id, handle) in &mut data.clients {
|
|
|
|
let id = *id;
|
|
|
|
|
|
|
|
if id == from_id {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2025-04-01 20:23:23 -04:00
|
|
|
let msg = FromServer::ActorMove(actor_id, position, rotation);
|
2025-03-30 17:11:41 -04:00
|
|
|
|
|
|
|
if handle.send(msg).is_err() {
|
|
|
|
to_remove.push(id);
|
|
|
|
}
|
2025-03-30 16:41:06 -04:00
|
|
|
}
|
|
|
|
}
|
2025-04-01 22:24:58 -04:00
|
|
|
ToServer::Disconnected(from_id) => {
|
|
|
|
to_remove.push(from_id);
|
|
|
|
}
|
2025-03-30 16:41:06 -04:00
|
|
|
ToServer::FatalError(err) => return Err(err),
|
|
|
|
}
|
|
|
|
}
|
2025-03-30 17:11:41 -04:00
|
|
|
// Remove any clients that errored out
|
|
|
|
for id in to_remove {
|
|
|
|
data.clients.remove(&id);
|
|
|
|
}
|
2025-03-30 16:41:06 -04:00
|
|
|
Ok(())
|
|
|
|
}
|
2025-03-30 09:02:52 -04:00
|
|
|
|
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 {
|
|
|
|
let res = main_loop(recv).await;
|
|
|
|
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 {
|
2025-03-31 21:58:51 -04:00
|
|
|
//id: ClientId,
|
|
|
|
// handle: ServerHandle,
|
2025-03-30 16:41:06 -04:00
|
|
|
/// 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
|
|
|
|
|
|
|
let data = ClientData {
|
2025-03-31 21:58:51 -04:00
|
|
|
//id: connection.id,
|
|
|
|
//handle: connection.handle.clone(),
|
2025-03-30 16:41:06 -04:00
|
|
|
recv,
|
2025-03-30 23:17:11 -04:00
|
|
|
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-03-30 21:42:46 -04:00
|
|
|
//kill,
|
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
|
|
|
) {
|
|
|
|
loop {
|
|
|
|
match data.recv().await {
|
2025-03-30 16:44:07 -04:00
|
|
|
Some(msg) => internal_send.send(msg).unwrap(),
|
2025-03-30 16:41:06 -04:00
|
|
|
None => break,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
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 exit_position = None;
|
|
|
|
let mut exit_rotation = None;
|
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-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-03-30 16:41:06 -04:00
|
|
|
Ok(n) = connection.socket.read(&mut buf) => {
|
2025-03-30 19:13:24 -04:00
|
|
|
if n > 0 {
|
2025-03-15 20:36:39 -04:00
|
|
|
let (segments, connection_type) = connection.parse_packet(&buf[..n]).await;
|
2025-03-09 11:07:01 -04:00
|
|
|
for segment in &segments {
|
|
|
|
match &segment.segment_type {
|
2025-03-21 19:56:16 -04:00
|
|
|
SegmentType::InitializeSession { actor_id } => {
|
2025-03-30 10:49:33 -04:00
|
|
|
// for some reason they send a string representation
|
|
|
|
let actor_id = actor_id.parse::<u32>().unwrap();
|
2025-03-21 19:56:16 -04:00
|
|
|
|
2025-03-30 21:42:46 -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-21 19:56:16 -04:00
|
|
|
// collect actor data
|
2025-03-30 10:49:33 -04:00
|
|
|
connection.initialize(&connection_type, actor_id).await;
|
2025-03-30 21:42:46 -04:00
|
|
|
|
|
|
|
if connection_type == ConnectionType::Zone {
|
|
|
|
exit_position = Some(connection.player_data.position);
|
|
|
|
exit_rotation = Some(connection.player_data.rotation);
|
|
|
|
|
|
|
|
// tell the server we exist, now that we confirmed we are a legitimate connection
|
|
|
|
connection.handle.send(ToServer::NewClient(client_handle.clone())).await;
|
|
|
|
}
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
2025-03-09 11:07:01 -04:00
|
|
|
SegmentType::Ipc { data } => {
|
2025-03-10 21:31:21 -04:00
|
|
|
match &data.data {
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::InitRequest { .. } => {
|
2025-03-10 21:31:21 -04:00
|
|
|
tracing::info!(
|
|
|
|
"Client is now requesting zone information. Sending!"
|
|
|
|
);
|
|
|
|
|
|
|
|
// IPC Init(?)
|
|
|
|
{
|
2025-03-16 17:43:29 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::InitResponse,
|
2025-03-10 21:31:21 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-03-16 17:43:29 -04:00
|
|
|
data: ServerZoneIpcData::InitResponse {
|
2025-03-10 21:31:21 -04:00
|
|
|
unk1: 0,
|
2025-03-21 19:56:16 -04:00
|
|
|
character_id: connection.player_data.actor_id,
|
2025-03-10 21:31:21 -04:00
|
|
|
unk2: 0,
|
|
|
|
},
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
2025-03-15 20:36:39 -04:00
|
|
|
connection
|
|
|
|
.send_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-03-23 17:10:47 -04:00
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
let chara_details =
|
|
|
|
database.find_chara_make(connection.player_data.content_id);
|
2025-03-23 17:10:47 -04:00
|
|
|
|
2025-03-23 17:43:06 -04:00
|
|
|
// Send inventory
|
2025-03-31 21:49:12 -04:00
|
|
|
connection.send_inventory(false).await;
|
2025-03-10 21:31:21 -04:00
|
|
|
|
2025-03-30 10:49:33 -04:00
|
|
|
// set chara gear param
|
|
|
|
connection
|
|
|
|
.actor_control_self(ActorControlSelf {
|
2025-03-30 16:41:06 -04:00
|
|
|
category: ActorControlCategory::SetCharaGearParamUI {
|
|
|
|
unk1: 1,
|
|
|
|
unk2: 1,
|
|
|
|
},
|
2025-03-30 10:49:33 -04:00
|
|
|
})
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
|
|
|
|
// Stats
|
|
|
|
{
|
2025-03-30 10:34:42 -04:00
|
|
|
let attributes;
|
|
|
|
{
|
|
|
|
let mut game_data = game_data.lock().unwrap();
|
|
|
|
|
|
|
|
attributes = game_data.get_racial_base_attributes(
|
|
|
|
chara_details.chara_make.customize.subrace,
|
|
|
|
);
|
|
|
|
}
|
2025-03-29 09:02:31 -04:00
|
|
|
|
2025-03-16 17:43:29 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::PlayerStats,
|
2025-03-10 21:31:21 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-03-16 17:43:29 -04:00
|
|
|
data: ServerZoneIpcData::PlayerStats(PlayerStats {
|
2025-03-29 09:02:31 -04:00
|
|
|
strength: attributes.strength,
|
|
|
|
dexterity: attributes.dexterity,
|
|
|
|
vitality: attributes.vitality,
|
|
|
|
intelligence: attributes.intelligence,
|
|
|
|
mind: attributes.mind,
|
2025-03-29 14:52:27 -04:00
|
|
|
hp: connection.player_data.max_hp,
|
|
|
|
mp: connection.player_data.max_mp as u32,
|
2025-03-12 18:05:41 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
2025-03-15 20:36:39 -04:00
|
|
|
connection
|
|
|
|
.send_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-03-15 20:36:39 -04:00
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Player Setup
|
|
|
|
{
|
2025-03-16 17:43:29 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::PlayerSetup,
|
2025-03-10 21:31:21 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-03-16 17:43:29 -04:00
|
|
|
data: ServerZoneIpcData::PlayerSetup(PlayerSetup {
|
2025-03-21 19:56:16 -04:00
|
|
|
content_id: connection.player_data.content_id,
|
2025-03-10 21:31:21 -04:00
|
|
|
exp: [10000; 32],
|
|
|
|
levels: [100; 32],
|
2025-03-21 19:56:16 -04:00
|
|
|
name: chara_details.name,
|
|
|
|
char_id: connection.player_data.actor_id,
|
|
|
|
race: chara_details.chara_make.customize.race,
|
2025-03-30 16:41:06 -04:00
|
|
|
gender: chara_details.chara_make.customize.gender,
|
|
|
|
tribe: chara_details.chara_make.customize.subrace,
|
2025-03-22 18:34:27 -04:00
|
|
|
city_state: chara_details.city_state,
|
2025-03-30 16:41:06 -04:00
|
|
|
nameday_month: chara_details.chara_make.birth_month
|
2025-03-21 19:56:16 -04:00
|
|
|
as u8,
|
2025-03-30 16:41:06 -04:00
|
|
|
nameday_day: chara_details.chara_make.birth_day as u8,
|
2025-03-21 19:56:16 -04:00
|
|
|
deity: chara_details.chara_make.guardian as u8,
|
2025-03-12 18:05:41 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
2025-03-15 20:36:39 -04:00
|
|
|
connection
|
|
|
|
.send_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-03-15 20:36:39 -04:00
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
2025-03-30 19:13:24 -04:00
|
|
|
let zone_id = connection.player_data.zone_id;
|
2025-03-22 18:53:53 -04:00
|
|
|
connection.change_zone(zone_id).await;
|
2025-03-11 23:52:55 -04:00
|
|
|
|
2025-03-27 16:20:33 -04:00
|
|
|
let lua = lua.lock().unwrap();
|
2025-03-27 19:24:36 -04:00
|
|
|
lua.scope(|scope| {
|
2025-03-30 16:41:06 -04:00
|
|
|
let connection_data =
|
|
|
|
scope.create_userdata_ref_mut(&mut lua_player).unwrap();
|
2025-03-27 19:24:36 -04:00
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
let func: Function = lua.globals().get("onBeginLogin").unwrap();
|
2025-03-27 19:24:36 -04:00
|
|
|
|
2025-03-27 22:54:36 -04:00
|
|
|
func.call::<()>(connection_data).unwrap();
|
2025-03-27 19:24:36 -04:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
2025-03-27 16:20:33 -04:00
|
|
|
.unwrap();
|
2025-03-16 14:49:18 -04:00
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::FinishLoading { .. } => {
|
2025-04-01 20:23:23 -04:00
|
|
|
// tell the server we loaded into the zone, so it can start sending us acors
|
|
|
|
connection.handle.send(ToServer::ZoneLoaded(connection.id)).await;
|
|
|
|
|
2025-03-12 00:32:37 -04:00
|
|
|
// send player spawn
|
|
|
|
{
|
2025-04-01 19:15:08 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
2025-03-30 10:34:42 -04:00
|
|
|
op_code: ServerZoneIpcType::PlayerSpawn,
|
|
|
|
timestamp: timestamp_secs(),
|
2025-03-30 16:41:06 -04:00
|
|
|
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,
|
|
|
|
gm_rank: GameMasterRank::Debug,
|
|
|
|
online_status: OnlineStatus::GameMasterBlue,
|
2025-04-01 19:15:08 -04:00
|
|
|
common: connection.get_player_common_spawn(exit_position, exit_rotation),
|
2025-03-30 16:41:06 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
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-03-15 20:36:39 -04:00
|
|
|
connection
|
|
|
|
.send_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-03-15 20:36:39 -04:00
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
2025-03-12 00:32:37 -04:00
|
|
|
}
|
2025-03-15 19:34:29 -04:00
|
|
|
|
|
|
|
// fade in?
|
|
|
|
{
|
2025-03-16 17:43:29 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::PrepareZoning,
|
2025-03-15 19:34:29 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-03-16 17:43:29 -04:00
|
|
|
data: ServerZoneIpcData::PrepareZoning {
|
2025-03-15 19:34:29 -04:00
|
|
|
unk: [0, 0, 0, 0],
|
|
|
|
},
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
2025-03-15 19:34:29 -04:00
|
|
|
};
|
|
|
|
|
2025-03-15 20:36:39 -04:00
|
|
|
connection
|
|
|
|
.send_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-03-15 20:36:39 -04:00
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
2025-03-15 19:34:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// wipe any exit position so it isn't accidentally reused
|
|
|
|
exit_position = None;
|
2025-03-29 00:28:49 -04:00
|
|
|
exit_rotation = None;
|
2025-03-30 17:49:45 -04:00
|
|
|
|
|
|
|
// tell the other players we're here
|
2025-04-01 19:22:13 -04:00
|
|
|
connection.handle.send(ToServer::ActorSpawned(connection.id, Actor { id: ObjectId(connection.player_data.actor_id), hp: 100 }, connection.get_player_common_spawn(None, None))).await;
|
2025-03-12 00:32:37 -04:00
|
|
|
}
|
2025-03-29 12:25:22 -04:00
|
|
|
ClientZoneIpcData::Unk1 {
|
|
|
|
category, param1, ..
|
|
|
|
} => {
|
|
|
|
tracing::info!("Recieved Unk1! {category:#?}");
|
|
|
|
|
|
|
|
match category {
|
|
|
|
3 => {
|
|
|
|
// set target
|
|
|
|
tracing::info!("Targeting actor {param1}");
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2025-03-12 00:32:37 -04:00
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::Unk2 { .. } => {
|
2025-03-12 00:32:37 -04:00
|
|
|
tracing::info!("Recieved Unk2!");
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::Unk3 { .. } => {
|
2025-03-12 00:32:37 -04:00
|
|
|
tracing::info!("Recieved Unk3!");
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::Unk4 { .. } => {
|
2025-03-12 00:32:37 -04:00
|
|
|
tracing::info!("Recieved Unk4!");
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::SetSearchInfoHandler { .. } => {
|
2025-03-12 00:32:37 -04:00
|
|
|
tracing::info!("Recieved SetSearchInfoHandler!");
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::Unk5 { .. } => {
|
2025-03-12 00:32:37 -04:00
|
|
|
tracing::info!("Recieved Unk5!");
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::SocialListRequest(request) => {
|
2025-03-16 14:07:56 -04:00
|
|
|
tracing::info!("Recieved social list request!");
|
|
|
|
|
|
|
|
match &request.request_type {
|
2025-03-16 17:43:29 -04:00
|
|
|
SocialListRequestType::Party => {
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::SocialList,
|
2025-03-16 14:07:56 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-03-30 16:41:06 -04:00
|
|
|
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()
|
|
|
|
}],
|
|
|
|
}),
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
connection
|
|
|
|
.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,
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
2025-03-16 14:07:56 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
SocialListRequestType::Friends => {
|
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::SocialList,
|
2025-03-16 14:07:56 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-03-30 16:41:06 -04:00
|
|
|
data: ServerZoneIpcData::SocialList(SocialList {
|
|
|
|
request_type: request.request_type,
|
|
|
|
sequence: request.count,
|
|
|
|
entries: Default::default(),
|
|
|
|
}),
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
connection
|
|
|
|
.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,
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
2025-03-16 14:07:56 -04:00
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
2025-03-12 00:32:37 -04:00
|
|
|
}
|
2025-03-30 16:41:06 -04:00
|
|
|
ClientZoneIpcData::UpdatePositionHandler { position, rotation } => {
|
2025-03-28 23:29:16 -04:00
|
|
|
tracing::info!(
|
|
|
|
"Character moved to {position:#?} {}",
|
|
|
|
rotation.to_degrees()
|
|
|
|
);
|
2025-03-23 10:33:49 -04:00
|
|
|
|
2025-03-29 00:15:29 -04:00
|
|
|
connection.player_data.rotation = *rotation;
|
|
|
|
connection.player_data.position = *position;
|
2025-03-30 17:49:45 -04:00
|
|
|
|
2025-04-01 20:23:23 -04:00
|
|
|
connection.handle.send(ToServer::ActorMoved(connection.id, connection.player_data.actor_id, *position, *rotation)).await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::LogOut { .. } => {
|
2025-03-12 18:44:05 -04:00
|
|
|
tracing::info!("Recieved log out from client!");
|
|
|
|
|
2025-03-29 00:15:29 -04:00
|
|
|
// write the player back to the database
|
|
|
|
database.commit_player_data(&connection.player_data);
|
|
|
|
|
2025-03-12 18:44:05 -04:00
|
|
|
// tell the client to disconnect
|
|
|
|
{
|
2025-03-16 17:43:29 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::LogOutComplete,
|
2025-03-12 18:44:05 -04:00
|
|
|
timestamp: timestamp_secs(),
|
2025-03-30 16:41:06 -04:00
|
|
|
data: ServerZoneIpcData::LogOutComplete { unk: [0; 8] },
|
2025-03-16 14:07:56 -04:00
|
|
|
..Default::default()
|
2025-03-12 18:44:05 -04:00
|
|
|
};
|
|
|
|
|
2025-03-15 20:36:39 -04:00
|
|
|
connection
|
|
|
|
.send_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-03-15 20:36:39 -04:00
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
2025-03-12 18:44:05 -04:00
|
|
|
}
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::Disconnected { .. } => {
|
2025-03-12 18:44:05 -04:00
|
|
|
tracing::info!("Client disconnected!");
|
2025-04-01 22:24:58 -04:00
|
|
|
|
|
|
|
connection.handle.send(ToServer::Disconnected(connection.id)).await;
|
2025-03-12 18:44:05 -04:00
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::ChatMessage(chat_message) => {
|
2025-03-30 16:41:06 -04:00
|
|
|
connection.handle.send(ToServer::Message(connection.id, chat_message.message.clone())).await;
|
|
|
|
|
2025-03-15 20:36:39 -04:00
|
|
|
ChatHandler::handle_chat_message(
|
|
|
|
&mut connection,
|
2025-03-28 21:28:30 -04:00
|
|
|
&mut lua_player,
|
2025-03-15 20:36:39 -04:00
|
|
|
chat_message,
|
|
|
|
)
|
|
|
|
.await
|
2025-03-12 19:34:15 -04:00
|
|
|
}
|
2025-03-30 16:41:06 -04:00
|
|
|
ClientZoneIpcData::GameMasterCommand { command, arg, .. } => {
|
2025-03-13 00:41:16 -04:00
|
|
|
tracing::info!("Got a game master command!");
|
|
|
|
|
|
|
|
match &command {
|
2025-04-01 20:39:57 -04:00
|
|
|
GameMasterCommandType::SetLevel => {
|
|
|
|
connection.player_data.level = *arg as u8;
|
|
|
|
connection.update_class_info().await;
|
|
|
|
}
|
2025-03-18 23:48:00 -04:00
|
|
|
GameMasterCommandType::ChangeWeather => {
|
|
|
|
connection.change_weather(*arg as u16).await
|
|
|
|
}
|
2025-03-13 00:41:16 -04:00
|
|
|
GameMasterCommandType::ChangeTerritory => {
|
2025-03-15 20:49:07 -04:00
|
|
|
connection.change_zone(*arg as u16).await
|
2025-03-13 00:41:16 -04:00
|
|
|
}
|
2025-03-30 16:41:06 -04:00
|
|
|
GameMasterCommandType::ToggleInvisibility => {
|
|
|
|
connection
|
|
|
|
.actor_control_self(ActorControlSelf {
|
|
|
|
category:
|
|
|
|
ActorControlCategory::ToggleInvisibility {
|
|
|
|
invisible: 1,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
GameMasterCommandType::ToggleWireframe => connection
|
|
|
|
.actor_control_self(ActorControlSelf {
|
|
|
|
category:
|
|
|
|
ActorControlCategory::ToggleWireframeRendering(),
|
|
|
|
})
|
|
|
|
.await,
|
2025-03-31 23:23:29 -04:00
|
|
|
GameMasterCommandType::GiveItem => {
|
2025-04-01 21:37:41 -04:00
|
|
|
connection.player_data.inventory.add_in_next_free_slot(Item { id: *arg, quantity: 1 });
|
2025-03-31 23:23:29 -04:00
|
|
|
connection.send_inventory(false).await;
|
|
|
|
}
|
2025-03-13 00:41:16 -04:00
|
|
|
}
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
ClientZoneIpcData::EnterZoneLine {
|
2025-03-15 19:34:29 -04:00
|
|
|
exit_box_id,
|
|
|
|
position,
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
tracing::info!(
|
|
|
|
"Character entered {exit_box_id} with a position of {position:#?}!"
|
|
|
|
);
|
|
|
|
|
|
|
|
// find the exit box id
|
2025-03-15 20:49:07 -04:00
|
|
|
let new_territory;
|
|
|
|
{
|
|
|
|
let (_, exit_box) = connection
|
|
|
|
.zone
|
2025-03-22 18:53:53 -04:00
|
|
|
.as_ref()
|
|
|
|
.unwrap()
|
2025-03-15 20:49:07 -04:00
|
|
|
.find_exit_box(*exit_box_id)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// find the pop range on the other side
|
2025-03-30 19:13:24 -04:00
|
|
|
let mut game_data = game_data.lock().unwrap();
|
|
|
|
let new_zone = Zone::load(&mut game_data.game_data, exit_box.territory_type);
|
2025-03-15 20:49:07 -04:00
|
|
|
let (destination_object, _) = new_zone
|
|
|
|
.find_pop_range(exit_box.destination_instance_id)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// set the exit position
|
|
|
|
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-15 19:34:29 -04:00
|
|
|
|
2025-03-15 20:49:07 -04:00
|
|
|
connection.change_zone(new_territory).await;
|
2025-03-15 19:34:29 -04:00
|
|
|
}
|
2025-03-19 00:28:47 -04:00
|
|
|
ClientZoneIpcData::ActionRequest(request) => {
|
2025-03-30 09:29:36 -04:00
|
|
|
let mut effects_builder = None;
|
|
|
|
|
2025-03-30 09:02:52 -04:00
|
|
|
// run action script
|
2025-03-29 14:14:03 -04:00
|
|
|
{
|
|
|
|
let lua = lua.lock().unwrap();
|
2025-03-30 16:41:06 -04:00
|
|
|
let state = lua.app_data_ref::<ExtraLuaState>().unwrap();
|
2025-03-30 09:02:52 -04:00
|
|
|
|
|
|
|
if let Some(action_script) =
|
|
|
|
state.action_scripts.get(&request.action_id)
|
|
|
|
{
|
|
|
|
lua.scope(|scope| {
|
|
|
|
let connection_data = scope
|
|
|
|
.create_userdata_ref_mut(&mut lua_player)
|
|
|
|
.unwrap();
|
|
|
|
|
2025-03-30 10:34:42 -04:00
|
|
|
let config = get_config();
|
|
|
|
|
2025-03-30 09:02:52 -04:00
|
|
|
let file_name = format!(
|
|
|
|
"{}/{}",
|
2025-03-30 16:41:06 -04:00
|
|
|
&config.world.scripts_location, action_script
|
2025-03-30 09:02:52 -04:00
|
|
|
);
|
2025-03-30 16:41:06 -04:00
|
|
|
lua.load(
|
|
|
|
std::fs::read(&file_name)
|
|
|
|
.expect("Failed to locate scripts directory!"),
|
|
|
|
)
|
2025-03-30 09:02:52 -04:00
|
|
|
.set_name("@".to_string() + &file_name)
|
|
|
|
.exec()
|
2025-03-29 14:14:03 -04:00
|
|
|
.unwrap();
|
2025-03-27 23:32:36 -04:00
|
|
|
|
2025-03-30 09:02:52 -04:00
|
|
|
let func: Function =
|
|
|
|
lua.globals().get("doAction").unwrap();
|
2025-03-27 23:32:36 -04:00
|
|
|
|
2025-03-30 09:29:36 -04:00
|
|
|
effects_builder = Some(
|
2025-03-30 16:41:06 -04:00
|
|
|
func.call::<EffectsBuilder>(connection_data)
|
|
|
|
.unwrap(),
|
2025-03-30 09:29:36 -04:00
|
|
|
);
|
2025-03-29 14:14:03 -04:00
|
|
|
|
2025-03-30 09:02:52 -04:00
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
}
|
2025-03-29 14:14:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// tell them the action results
|
2025-03-30 09:29:36 -04:00
|
|
|
if let Some(effects_builder) = effects_builder {
|
2025-03-29 14:38:40 -04:00
|
|
|
let mut effects = [ActionEffect::default(); 8];
|
2025-03-30 09:29:36 -04:00
|
|
|
effects[..effects_builder.effects.len()]
|
|
|
|
.copy_from_slice(&effects_builder.effects);
|
2025-03-29 14:38:40 -04:00
|
|
|
|
2025-03-30 09:44:37 -04:00
|
|
|
if let Some(actor) =
|
|
|
|
connection.get_actor(request.target.object_id)
|
|
|
|
{
|
|
|
|
for effect in &effects_builder.effects {
|
2025-03-30 10:01:41 -04:00
|
|
|
match effect.kind {
|
|
|
|
EffectKind::Damage => {
|
2025-03-30 09:44:37 -04:00
|
|
|
actor.hp -= effect.value as u32;
|
|
|
|
}
|
2025-03-30 17:49:45 -04:00
|
|
|
_ => todo!()
|
2025-03-30 09:44:37 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let actor = actor.clone();
|
2025-03-30 16:41:06 -04:00
|
|
|
connection.update_hp_mp(actor.id, actor.hp, 10000).await;
|
2025-03-30 09:44:37 -04:00
|
|
|
}
|
|
|
|
|
2025-03-29 14:14:03 -04:00
|
|
|
let ipc = ServerZoneIpcSegment {
|
|
|
|
op_code: ServerZoneIpcType::ActionResult,
|
|
|
|
timestamp: timestamp_secs(),
|
2025-03-30 16:41:06 -04:00
|
|
|
data: ServerZoneIpcData::ActionResult(ActionResult {
|
|
|
|
main_target: request.target,
|
|
|
|
target_id_again: request.target,
|
|
|
|
action_id: request.action_id,
|
|
|
|
animation_lock_time: 0.6,
|
|
|
|
rotation: connection.player_data.rotation,
|
|
|
|
action_animation_id: request.action_id as u16, // assuming action id == animation id
|
|
|
|
flag: 1,
|
|
|
|
effect_count: effects_builder.effects.len() as u8,
|
|
|
|
effects,
|
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-29 14:14:03 -04:00
|
|
|
..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: ipc },
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
2025-03-19 00:28:47 -04:00
|
|
|
}
|
2025-03-26 16:58:55 -04:00
|
|
|
ClientZoneIpcData::Unk16 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk16!");
|
|
|
|
}
|
|
|
|
ClientZoneIpcData::Unk17 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk17!");
|
|
|
|
}
|
|
|
|
ClientZoneIpcData::Unk18 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk18!");
|
|
|
|
}
|
2025-03-28 20:19:17 -04:00
|
|
|
ClientZoneIpcData::EventRelatedUnk {
|
|
|
|
unk1,
|
|
|
|
unk2,
|
|
|
|
unk3,
|
|
|
|
unk4,
|
|
|
|
} => {
|
|
|
|
tracing::info!(
|
|
|
|
"Recieved EventRelatedUnk! {unk1} {unk2} {unk3} {unk4}"
|
|
|
|
);
|
2025-03-28 21:28:30 -04:00
|
|
|
|
|
|
|
if let Some(event) = connection.event.as_mut() {
|
|
|
|
event.scene_finished(&mut lua_player, *unk2);
|
|
|
|
}
|
2025-03-28 20:19:17 -04:00
|
|
|
}
|
2025-03-29 16:40:33 -04:00
|
|
|
ClientZoneIpcData::Unk19 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk19!");
|
|
|
|
}
|
2025-03-31 20:05:37 -04:00
|
|
|
ClientZoneIpcData::InventoryModify(action) => {
|
|
|
|
tracing::info!("Client is modifying inventory! {action:#?}");
|
2025-03-31 21:49:12 -04:00
|
|
|
|
2025-04-01 18:49:42 -04:00
|
|
|
connection.player_data.inventory.process_action(&action);
|
2025-03-31 21:49:12 -04:00
|
|
|
connection.send_inventory(true).await;
|
2025-03-31 20:05:37 -04:00
|
|
|
}
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
2025-03-09 11:07:01 -04:00
|
|
|
}
|
|
|
|
SegmentType::KeepAlive { id, timestamp } => {
|
2025-03-16 17:43:29 -04:00
|
|
|
send_keep_alive::<ServerZoneIpcSegment>(
|
2025-03-15 20:36:39 -04:00
|
|
|
&mut connection.socket,
|
|
|
|
&mut connection.state,
|
2025-03-18 19:49:52 -04:00
|
|
|
ConnectionType::Zone,
|
2025-03-15 20:36:39 -04:00
|
|
|
*id,
|
|
|
|
*timestamp,
|
|
|
|
)
|
|
|
|
.await
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
SegmentType::KeepAliveResponse { .. } => {
|
|
|
|
tracing::info!("Got keep alive response from client... cool...");
|
2025-03-09 11:07:01 -04:00
|
|
|
}
|
2025-03-21 19:56:16 -04:00
|
|
|
SegmentType::CustomIpc { data } => {
|
|
|
|
match &data.data {
|
|
|
|
CustomIpcData::RequestCreateCharacter {
|
2025-04-05 21:36:56 -04:00
|
|
|
service_account_id,
|
2025-03-21 19:56:16 -04:00
|
|
|
name,
|
|
|
|
chara_make_json,
|
|
|
|
} => {
|
2025-03-30 16:41:06 -04:00
|
|
|
tracing::info!("creating character from: {name} {chara_make_json}");
|
2025-03-21 19:56:16 -04:00
|
|
|
|
2025-03-23 18:14:14 -04:00
|
|
|
let chara_make = CharaMake::from_json(chara_make_json);
|
2025-03-22 18:34:27 -04:00
|
|
|
|
2025-03-30 10:34:42 -04:00
|
|
|
let city_state;
|
|
|
|
{
|
|
|
|
let mut game_data = game_data.lock().unwrap();
|
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
city_state =
|
|
|
|
game_data.get_citystate(chara_make.classjob_id as u16);
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
2025-03-22 18:34:27 -04:00
|
|
|
|
2025-04-01 21:37:41 -04:00
|
|
|
let mut inventory = Inventory::default();
|
2025-04-01 18:49:42 -04:00
|
|
|
|
|
|
|
// fill inventory
|
|
|
|
inventory.equip_racial_items(
|
|
|
|
chara_make.customize.race,
|
|
|
|
chara_make.customize.gender,
|
|
|
|
);
|
|
|
|
|
2025-03-22 18:34:27 -04:00
|
|
|
let (content_id, actor_id) = database.create_player_data(
|
2025-04-05 21:36:56 -04:00
|
|
|
*service_account_id,
|
2025-03-22 18:34:27 -04:00
|
|
|
name,
|
|
|
|
chara_make_json,
|
|
|
|
city_state,
|
2025-03-22 19:05:29 -04:00
|
|
|
determine_initial_starting_zone(city_state),
|
2025-04-01 18:49:42 -04:00
|
|
|
inventory
|
2025-03-22 18:34:27 -04:00
|
|
|
);
|
2025-03-21 19:56:16 -04:00
|
|
|
|
2025-03-30 16:41:06 -04:00
|
|
|
tracing::info!("Created new player: {content_id} {actor_id}");
|
2025-03-21 19:56:16 -04:00
|
|
|
|
|
|
|
// send them the new actor and content id
|
|
|
|
{
|
|
|
|
connection
|
|
|
|
.send_segment(PacketSegment {
|
|
|
|
source_actor: 0,
|
|
|
|
target_actor: 0,
|
|
|
|
segment_type: SegmentType::CustomIpc {
|
|
|
|
data: CustomIpcSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
2025-03-30 16:41:06 -04:00
|
|
|
op_code: CustomIpcType::CharacterCreated,
|
2025-03-21 19:56:16 -04:00
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
|
|
|
data: CustomIpcData::CharacterCreated {
|
|
|
|
actor_id,
|
|
|
|
content_id,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CustomIpcData::GetActorId { content_id } => {
|
2025-03-22 16:15:29 -04:00
|
|
|
let actor_id = database.find_actor_id(*content_id);
|
2025-03-21 19:56:16 -04:00
|
|
|
|
|
|
|
tracing::info!("We found an actor id: {actor_id}");
|
|
|
|
|
|
|
|
// send them the actor id
|
|
|
|
{
|
|
|
|
connection
|
|
|
|
.send_segment(PacketSegment {
|
|
|
|
source_actor: 0,
|
|
|
|
target_actor: 0,
|
|
|
|
segment_type: SegmentType::CustomIpc {
|
|
|
|
data: CustomIpcSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: CustomIpcType::ActorIdFound,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
2025-03-30 16:41:06 -04:00
|
|
|
data: CustomIpcData::ActorIdFound { actor_id },
|
2025-03-21 19:56:16 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CustomIpcData::CheckNameIsAvailable { name } => {
|
2025-03-22 16:15:29 -04:00
|
|
|
let is_name_free = database.check_is_name_free(name);
|
2025-03-21 19:56:16 -04:00
|
|
|
let is_name_free = if is_name_free { 1 } else { 0 };
|
|
|
|
|
|
|
|
// send response
|
|
|
|
{
|
|
|
|
connection
|
2025-03-30 16:41:06 -04:00
|
|
|
.send_segment(PacketSegment {
|
|
|
|
source_actor: 0,
|
|
|
|
target_actor: 0,
|
|
|
|
segment_type: SegmentType::CustomIpc {
|
|
|
|
data: CustomIpcSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: CustomIpcType::NameIsAvailableResponse,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
|
|
|
data: CustomIpcData::NameIsAvailableResponse {
|
|
|
|
free: is_name_free,
|
|
|
|
},
|
2025-03-21 19:56:16 -04:00
|
|
|
},
|
|
|
|
},
|
2025-03-30 16:41:06 -04:00
|
|
|
})
|
|
|
|
.await;
|
2025-03-21 19:56:16 -04:00
|
|
|
}
|
|
|
|
}
|
2025-03-21 21:26:32 -04:00
|
|
|
CustomIpcData::RequestCharacterList { service_account_id } => {
|
2025-03-22 17:00:21 -04:00
|
|
|
let config = get_config();
|
|
|
|
|
2025-03-30 10:34:42 -04:00
|
|
|
let world_name;
|
|
|
|
{
|
|
|
|
let mut game_data = game_data.lock().unwrap();
|
2025-03-30 16:41:06 -04:00
|
|
|
world_name = game_data.get_world_name(config.world.world_id);
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
|
|
|
|
2025-03-22 17:00:21 -04:00
|
|
|
let characters = database.get_character_list(
|
|
|
|
*service_account_id,
|
|
|
|
config.world.world_id,
|
2025-03-30 10:34:42 -04:00
|
|
|
&world_name,
|
2025-03-22 17:00:21 -04:00
|
|
|
);
|
2025-03-21 21:26:32 -04:00
|
|
|
|
|
|
|
// send response
|
|
|
|
{
|
|
|
|
send_packet::<CustomIpcSegment>(
|
2025-03-30 16:41:06 -04:00
|
|
|
&mut connection.socket,
|
|
|
|
&mut connection.state,
|
|
|
|
ConnectionType::None,
|
|
|
|
CompressionType::Uncompressed,
|
|
|
|
&[PacketSegment {
|
|
|
|
source_actor: 0,
|
|
|
|
target_actor: 0,
|
|
|
|
segment_type: SegmentType::CustomIpc {
|
|
|
|
data: CustomIpcSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: CustomIpcType::RequestCharacterListRepsonse,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
|
|
|
data: CustomIpcData::RequestCharacterListRepsonse {
|
|
|
|
characters
|
|
|
|
},
|
|
|
|
},
|
2025-03-21 21:26:32 -04:00
|
|
|
},
|
2025-03-30 16:41:06 -04:00
|
|
|
}],
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-21 21:26:32 -04:00
|
|
|
}
|
|
|
|
}
|
2025-03-22 17:32:00 -04:00
|
|
|
CustomIpcData::DeleteCharacter { content_id } => {
|
|
|
|
database.delete_character(*content_id);
|
|
|
|
|
|
|
|
// send response
|
|
|
|
{
|
|
|
|
send_packet::<CustomIpcSegment>(
|
|
|
|
&mut connection.socket,
|
|
|
|
&mut connection.state,
|
|
|
|
ConnectionType::None,
|
|
|
|
CompressionType::Uncompressed,
|
|
|
|
&[PacketSegment {
|
|
|
|
source_actor: 0,
|
|
|
|
target_actor: 0,
|
|
|
|
segment_type: SegmentType::CustomIpc {
|
|
|
|
data: CustomIpcSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
2025-03-30 16:41:06 -04:00
|
|
|
op_code: CustomIpcType::CharacterDeleted,
|
2025-03-22 17:32:00 -04:00
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
|
|
|
data: CustomIpcData::CharacterDeleted {
|
|
|
|
deleted: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}],
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
2025-03-30 16:41:06 -04:00
|
|
|
_ => {
|
|
|
|
panic!("The server is recieving a response or unknown custom IPC!")
|
|
|
|
}
|
2025-03-21 19:56:16 -04:00
|
|
|
}
|
|
|
|
}
|
2025-03-09 11:07:01 -04:00
|
|
|
_ => {
|
|
|
|
panic!("The server is recieving a response or unknown packet!")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-03-27 22:54:36 -04:00
|
|
|
|
2025-03-28 00:22:41 -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();
|
|
|
|
|
|
|
|
// Process any queued packets from scripts and whatnot
|
2025-03-27 22:54:36 -04:00
|
|
|
connection.process_lua_player(&mut lua_player).await;
|
2025-03-28 00:22:41 -04:00
|
|
|
|
|
|
|
// check if status effects need sending
|
|
|
|
connection.process_effects_list().await;
|
|
|
|
|
|
|
|
// update lua player
|
2025-04-01 21:37:41 -04:00
|
|
|
lua_player.player_data = connection.player_data.clone();
|
2025-03-28 00:22:41 -04:00
|
|
|
lua_player.status_effects = connection.status_effects.clone();
|
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-04-01 20:23:23 -04:00
|
|
|
FromServer::Message(msg)=> connection.send_message(&msg).await,
|
2025-04-01 19:22:13 -04:00
|
|
|
FromServer::ActorSpawn(actor, common) => {
|
|
|
|
connection.spawn_actor(actor, common).await
|
2025-03-30 21:42:46 -04:00
|
|
|
},
|
2025-04-01 20:23:23 -04:00
|
|
|
FromServer::ActorMove(actor_id, position, rotation) => connection.set_actor_position(actor_id, position, rotation).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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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();
|
|
|
|
|
|
|
|
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()));
|
|
|
|
|
|
|
|
{
|
|
|
|
let lua = lua.lock().unwrap();
|
|
|
|
|
|
|
|
let register_action_func = lua
|
|
|
|
.create_function(|lua, (action_id, action_script): (u32, String)| {
|
|
|
|
tracing::info!("Registering {action_id} with {action_script}!");
|
|
|
|
let mut state = lua.app_data_mut::<ExtraLuaState>().unwrap();
|
|
|
|
let _ = state.action_scripts.insert(action_id, action_script);
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
lua.set_app_data(ExtraLuaState::default());
|
|
|
|
lua.globals()
|
|
|
|
.set("registerAction", register_action_func)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let effectsbuilder_constructor = lua
|
|
|
|
.create_function(|_, ()| Ok(EffectsBuilder::default()))
|
|
|
|
.unwrap();
|
|
|
|
lua.globals()
|
|
|
|
.set("EffectsBuilder", effectsbuilder_constructor)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let file_name = format!("{}/Global.lua", &config.world.scripts_location);
|
|
|
|
lua.load(std::fs::read(&file_name).expect("Failed to locate scripts directory!"))
|
|
|
|
.set_name("@".to_string() + &file_name)
|
|
|
|
.exec()
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2025-03-30 17:59:17 -04:00
|
|
|
let (handle, _) = spawn_main_loop();
|
2025-03-30 16:41:06 -04:00
|
|
|
|
|
|
|
loop {
|
|
|
|
let (socket, ip) = listener.accept().await.unwrap();
|
|
|
|
let id = handle.next_id();
|
|
|
|
|
|
|
|
let state = PacketState {
|
|
|
|
client_key: None,
|
|
|
|
clientbound_oodle: OodleNetwork::new(),
|
|
|
|
serverbound_oodle: OodleNetwork::new(),
|
|
|
|
};
|
|
|
|
|
|
|
|
spawn_client(ZoneConnection {
|
|
|
|
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-03-09 11:07:01 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|