use mlua::{FromLua, Lua, LuaSerdeExt, UserData, UserDataFields, UserDataMethods, Value}; use crate::{ common::{ ObjectId, ObjectTypeId, Position, timestamp_secs, workdefinitions::RemakeMode, write_quantized_rotation, }, config::get_config, ipc::zone::{ ActionEffect, ActorControlCategory, ActorControlSelf, DamageElement, DamageKind, DamageType, EffectKind, EventScene, ServerZoneIpcData, ServerZoneIpcSegment, Warp, }, opcodes::ServerZoneIpcType, packet::{PacketSegment, SegmentData, SegmentType}, world::ExtraLuaState, }; use super::{PlayerData, StatusEffects, Zone, connection::TeleportQuery}; pub enum Task { ChangeTerritory { zone_id: u16 }, SetRemakeMode(RemakeMode), Warp { warp_id: u32 }, BeginLogOut, FinishEvent { handler_id: u32 }, SetClassJob { classjob_id: u8 }, WarpAetheryte { aetheryte_id: u32 }, ReloadScripts, ToggleInvisibility { invisible: bool }, Unlock { id: u32 }, UnlockAetheryte { id: u32, on: bool }, SetLevel { level: i32 }, ChangeWeather { id: u16 }, AddGil { amount: u32 }, UnlockOrchestrion { id: u16, on: bool }, AddItem { id: u32 }, } #[derive(Default, Clone)] pub struct LuaZone { pub zone_id: u16, pub weather_id: u16, pub internal_name: String, pub region_name: String, pub place_name: String, pub intended_use: u8, } impl UserData for LuaZone { fn add_fields>(fields: &mut F) { fields.add_field_method_get("id", |_, this| Ok(this.zone_id)); fields.add_field_method_get("weather_id", |_, this| Ok(this.weather_id)); fields.add_field_method_get("internal_name", |_, this| Ok(this.internal_name.clone())); fields.add_field_method_get("region_name", |_, this| Ok(this.region_name.clone())); fields.add_field_method_get("place_name", |_, this| Ok(this.place_name.clone())); fields.add_field_method_get("intended_use", |_, this| Ok(this.intended_use)); } } #[derive(Default)] pub struct LuaPlayer { pub player_data: PlayerData, pub status_effects: StatusEffects, pub queued_segments: Vec>, pub queued_tasks: Vec, pub zone_data: LuaZone, } impl LuaPlayer { fn queue_segment(&mut self, segment: PacketSegment) { self.queued_segments.push(segment); } fn create_segment_target( &mut self, op_code: ServerZoneIpcType, data: ServerZoneIpcData, source_actor: u32, target_actor: u32, ) { let ipc = ServerZoneIpcSegment { op_code, timestamp: timestamp_secs(), data, ..Default::default() }; self.queue_segment(PacketSegment { source_actor, target_actor, segment_type: SegmentType::Ipc, data: SegmentData::Ipc { data: ipc }, }); } fn create_segment_self(&mut self, op_code: ServerZoneIpcType, data: ServerZoneIpcData) { self.create_segment_target( op_code, data, self.player_data.actor_id, self.player_data.actor_id, ); } fn send_message(&mut self, message: &str, param: u8) { let op_code = ServerZoneIpcType::ServerChatMessage; let data = ServerZoneIpcData::ServerChatMessage { message: message.to_string(), param, }; self.create_segment_self(op_code, data); } fn give_status_effect(&mut self, effect_id: u16, duration: f32) { self.status_effects.add(effect_id, duration); } fn play_scene( &mut self, target: ObjectTypeId, event_id: u32, scene: u16, scene_flags: u32, params: &[u32], ) { let op_code; let data; match params.len() { // TODO: it would be nice to de-duplicate these 0..=2 => { let mut scene = EventScene { actor_id: target, event_id, scene, scene_flags, params_count: params.len() as u8, ..Default::default() }; scene.params[..params.len()].copy_from_slice(¶ms[0..params.len()]); op_code = ServerZoneIpcType::EventScene; data = ServerZoneIpcData::EventScene(scene); } 3..=4 => { let mut scene = EventScene::<4> { actor_id: target, event_id, scene, scene_flags, params_count: params.len() as u8, ..Default::default() }; scene.params[..params.len()].copy_from_slice(¶ms[0..params.len()]); op_code = ServerZoneIpcType::EventScene4; data = ServerZoneIpcData::EventScene4(scene); } 5..=8 => { let mut scene = EventScene::<8> { actor_id: target, event_id, scene, scene_flags, params_count: params.len() as u8, ..Default::default() }; scene.params[..params.len()].copy_from_slice(¶ms[0..params.len()]); op_code = ServerZoneIpcType::EventScene8; data = ServerZoneIpcData::EventScene8(scene); } 9..=16 => { let mut scene = EventScene::<16> { actor_id: target, event_id, scene, scene_flags, params_count: params.len() as u8, ..Default::default() }; scene.params[..params.len()].copy_from_slice(¶ms[0..params.len()]); op_code = ServerZoneIpcType::EventScene16; data = ServerZoneIpcData::EventScene16(scene); } 17..=32 => { let mut scene = EventScene::<32> { actor_id: target, event_id, scene, scene_flags, params_count: params.len() as u8, ..Default::default() }; scene.params[..params.len()].copy_from_slice(¶ms[0..params.len()]); op_code = ServerZoneIpcType::EventScene32; data = ServerZoneIpcData::EventScene32(scene); } 33..=64 => { let mut scene = EventScene::<64> { actor_id: target, event_id, scene, scene_flags, params_count: params.len() as u8, ..Default::default() }; scene.params.copy_from_slice(¶ms[0..params.len()]); op_code = ServerZoneIpcType::EventScene64; data = ServerZoneIpcData::EventScene64(scene); } 65..=128 => { let mut scene = EventScene::<128> { actor_id: target, event_id, scene, scene_flags, params_count: params.len() as u8, ..Default::default() }; scene.params[..params.len()].copy_from_slice(¶ms[0..params.len()]); op_code = ServerZoneIpcType::EventScene128; data = ServerZoneIpcData::EventScene128(scene); } 129..255 => { let mut scene = EventScene::<255> { actor_id: target, event_id, scene, scene_flags, params_count: params.len() as u8, ..Default::default() }; scene.params[..params.len()].copy_from_slice(¶ms[0..params.len()]); op_code = ServerZoneIpcType::EventScene255; data = ServerZoneIpcData::EventScene255(scene); } _ => { tracing::warn!("Unsupported amount of parameters in play_scene!"); return; } } self.create_segment_self(op_code, data); } fn set_position(&mut self, position: Position, rotation: f32) { let op_code = ServerZoneIpcType::Warp; let data = ServerZoneIpcData::Warp(Warp { dir: write_quantized_rotation(&rotation), position, ..Default::default() }); self.create_segment_self(op_code, data); } fn set_festival(&mut self, festival1: u32, festival2: u32, festival3: u32, festival4: u32) { let op_code = ServerZoneIpcType::ActorControlSelf; let data = ServerZoneIpcData::ActorControlSelf(ActorControlSelf { category: ActorControlCategory::SetFestival { festival1, festival2, festival3, festival4, }, }); self.create_segment_self(op_code, data); } fn unlock(&mut self, id: u32) { self.queued_tasks.push(Task::Unlock { id }); } fn set_speed(&mut self, speed: u16) { let op_code = ServerZoneIpcType::ActorControlSelf; let data = ServerZoneIpcData::ActorControlSelf(ActorControlSelf { category: ActorControlCategory::Flee { speed }, }); self.create_segment_self(op_code, data); } fn toggle_wireframe(&mut self) { let op_code = ServerZoneIpcType::ActorControlSelf; let data = ServerZoneIpcData::ActorControlSelf(ActorControlSelf { category: ActorControlCategory::ToggleWireframeRendering(), }); self.create_segment_self(op_code, data); } fn unlock_aetheryte(&mut self, unlocked: u32, id: u32) { self.queued_tasks.push(Task::UnlockAetheryte { id, on: unlocked == 1, }); } fn change_territory(&mut self, zone_id: u16) { self.queued_tasks.push(Task::ChangeTerritory { zone_id }); } fn set_remake_mode(&mut self, mode: RemakeMode) { self.queued_tasks.push(Task::SetRemakeMode(mode)); } fn warp(&mut self, warp_id: u32) { self.queued_tasks.push(Task::Warp { warp_id }); } fn begin_log_out(&mut self) { self.queued_tasks.push(Task::BeginLogOut); } fn finish_event(&mut self, handler_id: u32) { self.queued_tasks.push(Task::FinishEvent { handler_id }); } fn set_classjob(&mut self, classjob_id: u8) { self.queued_tasks.push(Task::SetClassJob { classjob_id }); } fn warp_aetheryte(&mut self, aetheryte_id: u32) { self.queued_tasks.push(Task::WarpAetheryte { aetheryte_id }); } fn reload_scripts(&mut self) { self.queued_tasks.push(Task::ReloadScripts); } fn toggle_invisiblity(&mut self) { self.queued_tasks.push(Task::ToggleInvisibility { invisible: !self.player_data.gm_invisible, }); } fn set_level(&mut self, level: i32) { self.queued_tasks.push(Task::SetLevel { level }); } fn change_weather(&mut self, id: u16) { self.queued_tasks.push(Task::ChangeWeather { id }); } fn add_gil(&mut self, amount: u32) { self.queued_tasks.push(Task::AddGil { amount }); } fn unlock_orchestrion(&mut self, unlocked: u32, id: u16) { self.queued_tasks.push(Task::UnlockOrchestrion { id, on: unlocked == 1, }); } fn add_item(&mut self, id: u32) { self.queued_tasks.push(Task::AddItem { id }); } } impl UserData for LuaPlayer { fn add_methods>(methods: &mut M) { methods.add_method_mut( "send_message", |lua, this, (message, param): (String, Value)| { let param: u8 = lua.from_value(param).unwrap_or(0); this.send_message(&message, param); Ok(()) }, ); methods.add_method_mut( "give_status_effect", |_, this, (effect_id, duration): (u16, f32)| { this.give_status_effect(effect_id, duration); Ok(()) }, ); methods.add_method_mut( "play_scene", |_, this, (target, event_id, scene, scene_flags, params): ( ObjectTypeId, u32, u16, u32, Vec, )| { this.play_scene(target, event_id, scene, scene_flags, ¶ms); return Ok(()); }, ); methods.add_method_mut( "set_position", |lua, this, (position, rotation): (Value, Value)| { let position: Position = lua.from_value(position).unwrap(); let rotation: f32 = lua.from_value(rotation).unwrap(); this.set_position(position, rotation); Ok(()) }, ); methods.add_method_mut( "set_festival", |_, this, (festival1, festival2, festival3, festival4): (u32, u32, u32, u32)| { this.set_festival(festival1, festival2, festival3, festival4); Ok(()) }, ); methods.add_method_mut("unlock_aetheryte", |_, this, (unlock, id): (u32, u32)| { this.unlock_aetheryte(unlock, id); Ok(()) }); methods.add_method_mut("unlock", |_, this, action_id: u32| { this.unlock(action_id); Ok(()) }); methods.add_method_mut("set_speed", |_, this, speed: u16| { this.set_speed(speed); Ok(()) }); methods.add_method_mut("toggle_wireframe", |_, this, _: Value| { this.toggle_wireframe(); Ok(()) }); methods.add_method_mut("toggle_invisibility", |_, this, _: Value| { this.toggle_invisiblity(); Ok(()) }); methods.add_method_mut("change_territory", |_, this, zone_id: u16| { this.change_territory(zone_id); Ok(()) }); methods.add_method_mut("set_remake_mode", |lua, this, mode: Value| { let mode: RemakeMode = lua.from_value(mode).unwrap(); this.set_remake_mode(mode); Ok(()) }); methods.add_method_mut("warp", |_, this, warp_id: u32| { this.warp(warp_id); Ok(()) }); methods.add_method_mut("begin_log_out", |_, this, _: ()| { this.begin_log_out(); Ok(()) }); methods.add_method_mut("finish_event", |_, this, handler_id: u32| { this.finish_event(handler_id); Ok(()) }); methods.add_method_mut("set_classjob", |_, this, classjob_id: u8| { this.set_classjob(classjob_id); Ok(()) }); methods.add_method_mut("warp_aetheryte", |_, this, aetheryte_id: u32| { this.warp_aetheryte(aetheryte_id); Ok(()) }); methods.add_method_mut("reload_scripts", |_, this, _: ()| { this.reload_scripts(); Ok(()) }); methods.add_method_mut("set_level", |_, this, level: i32| { this.set_level(level); Ok(()) }); methods.add_method_mut("change_weather", |_, this, id: u16| { this.change_weather(id); Ok(()) }); methods.add_method_mut("add_gil", |_, this, amount: u32| { this.add_gil(amount); Ok(()) }); methods.add_method_mut("unlock_orchestrion", |_, this, (unlock, id): (u32, u16)| { this.unlock_orchestrion(unlock, id); Ok(()) }); methods.add_method_mut("add_item", |_, this, id: u32| { this.add_item(id); Ok(()) }); } fn add_fields>(fields: &mut F) { fields.add_field_method_get("id", |_, this| { Ok(ObjectTypeId { object_id: ObjectId(this.player_data.actor_id), object_type: 0, }) }); fields.add_field_method_get("teleport_query", |_, this| { Ok(this.player_data.teleport_query.clone()) }); fields.add_field_method_get("rotation", |_, this| Ok(this.player_data.rotation)); fields.add_field_method_get("position", |_, this| Ok(this.player_data.position)); fields.add_field_method_get("zone", |_, this| Ok(this.zone_data.clone())); } } impl UserData for TeleportQuery { fn add_fields>(fields: &mut F) { fields.add_field_method_get("aetheryte_id", |_, this| Ok(this.aetheryte_id)); } } impl UserData for Position { fn add_fields>(fields: &mut F) { fields.add_field_method_get("x", |_, this| Ok(this.x)); fields.add_field_method_get("y", |_, this| Ok(this.y)); fields.add_field_method_get("z", |_, this| Ok(this.z)); } } impl UserData for ObjectTypeId {} impl FromLua for ObjectTypeId { fn from_lua(value: Value, _: &Lua) -> mlua::Result { match value { Value::UserData(ud) => Ok(*ud.borrow::()?), _ => unreachable!(), } } } impl UserData for Zone { fn add_methods>(methods: &mut M) { methods.add_method( "get_pop_range", |lua: &Lua, this, id: u32| -> mlua::Result { if let Some(pop_range) = this.find_pop_range(id) { let trans = pop_range.0.transform.translation; return lua.pack(Position { x: trans[0], y: trans[1], z: trans[2], }); } else { tracing::warn!("Failed to find pop range for {id}!"); } Ok(mlua::Nil) }, ); } } #[derive(Clone, Debug, Default)] pub struct EffectsBuilder { pub effects: Vec, } impl UserData for EffectsBuilder { fn add_methods>(methods: &mut M) { methods.add_method_mut("damage", |lua, this, (damage_kind, damage_type, damage_element, amount): (Value, Value, Value, u16)| { let damage_kind: DamageKind = lua.from_value(damage_kind).unwrap(); let damage_type: DamageType = lua.from_value(damage_type).unwrap(); let damage_element: DamageElement = lua.from_value(damage_element).unwrap(); this.effects.push(ActionEffect { kind: EffectKind::Damage { damage_kind, damage_type, damage_element, bonus_percent: 0, unk3: 0, unk4: 0, amount, }, }); Ok(()) }); } } impl FromLua for EffectsBuilder { fn from_lua(value: Value, _: &Lua) -> mlua::Result { match value { Value::UserData(ud) => Ok(ud.borrow::()?.clone()), _ => unreachable!(), } } } /// Loads `Init.lua` pub fn load_init_script(lua: &mut Lua) -> mlua::Result<()> { let register_action_func = lua.create_function(|lua, (action_id, action_script): (u32, String)| { let mut state = lua.app_data_mut::().unwrap(); let _ = state.action_scripts.insert(action_id, action_script); Ok(()) })?; let register_event_func = lua.create_function(|lua, (event_id, event_script): (u32, String)| { let mut state = lua.app_data_mut::().unwrap(); let _ = state.event_scripts.insert(event_id, event_script); Ok(()) })?; let register_command_func = lua.create_function(|lua, (command_name, command_script): (String, String)| { let mut state = lua.app_data_mut::().unwrap(); let _ = state.command_scripts.insert(command_name, command_script); Ok(()) })?; let register_gm_command_func = lua.create_function(|lua, (command_type, command_script): (u32, String)| { let mut state = lua.app_data_mut::().unwrap(); let _ = state .gm_command_scripts .insert(command_type, command_script); Ok(()) })?; lua.set_app_data(ExtraLuaState::default()); lua.globals().set("registerAction", register_action_func)?; lua.globals().set("registerEvent", register_event_func)?; lua.globals() .set("registerCommand", register_command_func)?; lua.globals() .set("registerGMCommand", register_gm_command_func)?; let effectsbuilder_constructor = lua.create_function(|_, ()| Ok(EffectsBuilder::default()))?; lua.globals() .set("EffectsBuilder", effectsbuilder_constructor)?; let config = get_config(); let file_name = format!("{}/Init.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()?; Ok(()) }