diff --git a/Cargo.lock b/Cargo.lock index 1521e6b..c3c9f25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288" dependencies = [ "axum-core", "bytes", @@ -64,12 +64,12 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" @@ -637,7 +637,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "physis" version = "0.4.0" -source = "git+https://github.com/redstrate/physis#eee7d5867fb7424592ff1249f28c90dbe1daec13" +source = "git+https://github.com/redstrate/physis#a34361b9560a95bdd257ee4fe721dbe1156a5610" dependencies = [ "binrw", "bitflags 1.3.2", diff --git a/resources/scripts/opening/OpeningUldah.lua b/resources/scripts/opening/OpeningUldah.lua index a7c30c7..ef33e2b 100644 --- a/resources/scripts/opening/OpeningUldah.lua +++ b/resources/scripts/opening/OpeningUldah.lua @@ -1,16 +1,22 @@ --- TODO: find a way to hardcode it this way EVENT_ID = 1245187 +--- load defines from Opening Excel sheet, which has this and we don't need to hardcode it' +POS_START = 4101669 + function Scene00000(player) player:play_scene(EVENT_ID, 00000, 4959237, 1) end function Scene00001(player) - --- todo put player in correct position player:play_scene(EVENT_ID, 00001, 4959237, 1) end -function onEnterTerritory(player) +function onEnterTerritory(player, zone) + --- move the player into the starting position + start_pos = zone:get_pop_range(POS_START) + player:set_position(start_pos) + Scene00000(player); end diff --git a/src/world/chat_handler.rs b/src/world/chat_handler.rs index 60e9da2..8abd31a 100644 --- a/src/world/chat_handler.rs +++ b/src/world/chat_handler.rs @@ -7,7 +7,7 @@ use crate::{ Event, ipc::{ ActorControl, ActorControlCategory, BattleNpcSubKind, CommonSpawn, DisplayFlag, - EventPlay, EventStart, NpcSpawn, ObjectKind, OnlineStatus, PlayerSpawn, PlayerSubKind, + EventStart, NpcSpawn, ObjectKind, OnlineStatus, PlayerSpawn, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, }, }, @@ -334,7 +334,7 @@ impl ChatHandler { .event .as_mut() .unwrap() - .enter_territory(lua_player); + .enter_territory(lua_player, connection.zone.as_ref().unwrap()); } _ => tracing::info!("Unrecognized debug command!"), } diff --git a/src/world/event.rs b/src/world/event.rs index 98ee868..1c85905 100644 --- a/src/world/event.rs +++ b/src/world/event.rs @@ -2,7 +2,7 @@ use mlua::{Function, Lua}; use crate::config::get_config; -use super::LuaPlayer; +use super::{LuaPlayer, Zone}; pub struct Event { lua: Lua, @@ -22,14 +22,15 @@ impl Event { Self { lua } } - pub fn enter_territory(&mut self, player: &mut LuaPlayer) { + pub fn enter_territory(&mut self, player: &mut LuaPlayer, zone: &Zone) { self.lua .scope(|scope| { let player = scope.create_userdata_ref_mut(player).unwrap(); + let zone = scope.create_userdata_ref(zone).unwrap(); let func: Function = self.lua.globals().get("onEnterTerritory").unwrap(); - func.call::<()>(player).unwrap(); + func.call::<()>((player, zone)).unwrap(); Ok(()) }) diff --git a/src/world/lua.rs b/src/world/lua.rs index f90dee0..f4339bc 100644 --- a/src/world/lua.rs +++ b/src/world/lua.rs @@ -1,14 +1,14 @@ -use mlua::{UserData, UserDataMethods}; +use mlua::{FromLua, Lua, UserData, UserDataMethods, Value}; use crate::{ - common::{ObjectId, ObjectTypeId, timestamp_secs}, + common::{ObjectId, ObjectTypeId, Position, timestamp_secs}, opcodes::ServerZoneIpcType, packet::{PacketSegment, SegmentType}, }; use super::{ - PlayerData, StatusEffects, - ipc::{EventPlay, ServerZoneIpcData, ServerZoneIpcSegment}, + PlayerData, StatusEffects, Zone, + ipc::{ActorSetPos, EventPlay, ServerZoneIpcData, ServerZoneIpcSegment}, }; #[derive(Default)] @@ -71,6 +71,25 @@ impl LuaPlayer { segment_type: SegmentType::Ipc { data: ipc }, }); } + + fn set_position(&mut self, position: Position) { + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::ActorSetPos, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::ActorSetPos(ActorSetPos { + unk: 0x020fa3b8, + position, + ..Default::default() + }), + ..Default::default() + }; + + self.queue_segment(PacketSegment { + source_actor: self.player_data.actor_id, + target_actor: self.player_data.actor_id, + segment_type: SegmentType::Ipc { data: ipc }, + }); + } } impl UserData for LuaPlayer { @@ -93,5 +112,40 @@ impl UserData for LuaPlayer { Ok(()) }, ); + methods.add_method_mut("set_position", |_, this, position: Position| { + this.set_position(position); + Ok(()) + }); + } +} + +impl UserData for Position {} + +impl FromLua for Position { + 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", + |_, this, id: u32| -> mlua::Result { + if let Some(pop_range) = this.find_pop_range(id) { + let trans = pop_range.0.transform.translation; + return Ok(Position { + x: trans[0], + y: trans[1], + z: trans[2], + }); + } + // FIXME: return nil? + Ok(Position::default()) + }, + ); } } diff --git a/src/world/zone.rs b/src/world/zone.rs index c3fffd1..acb234f 100644 --- a/src/world/zone.rs +++ b/src/world/zone.rs @@ -9,9 +9,16 @@ use physis::{ use crate::config::get_config; /// Represents a loaded zone +#[derive(Default)] pub struct Zone { pub id: u16, - layer_group: Option, + planevent: Option, + vfx: Option, + planmap: Option, + planner: Option, + bg: Option, + sound: Option, + planlive: Option, } impl Zone { @@ -20,7 +27,7 @@ impl Zone { let mut zone = Self { id, - layer_group: None, + ..Default::default() }; let Some(mut game_data) = GameData::from_existing(Platform::Win32, &config.game_location) @@ -49,11 +56,22 @@ impl Zone { return zone; }; - let path = format!("bg/{}/level/planmap.lgb", &bg_path[..level_index]); - let Some(lgb) = game_data.extract(&path) else { - return zone; + let mut load_lgb = |name: &str| -> Option { + let path = format!("bg/{}/level/{}.lgb", &bg_path[..level_index], name); + tracing::info!("Loading {path}"); + let Some(lgb) = game_data.extract(&path) else { + return None; + }; + LayerGroup::from_existing(&lgb) }; - zone.layer_group = LayerGroup::from_existing(&lgb); + + zone.planevent = load_lgb("planevent"); + zone.vfx = load_lgb("vfx"); + zone.planmap = load_lgb("planmap"); + zone.planner = load_lgb("planner"); + zone.bg = load_lgb("bg"); + zone.sound = load_lgb("sound"); + zone.planlive = load_lgb("planlive"); zone } @@ -64,7 +82,7 @@ impl Zone { instance_id: u32, ) -> Option<(&InstanceObject, &ExitRangeInstanceObject)> { // TODO: also check position! - for group in &self.layer_group.as_ref().unwrap().layers { + for group in &self.planmap.as_ref().unwrap().layers { for object in &group.objects { if let LayerEntryData::ExitRange(exit_range) = &object.data { if object.instance_id == instance_id { @@ -82,7 +100,18 @@ impl Zone { instance_id: u32, ) -> Option<(&InstanceObject, &PopRangeInstanceObject)> { // TODO: also check position! - for group in &self.layer_group.as_ref().unwrap().layers { + for group in &self.planmap.as_ref().unwrap().layers { + for object in &group.objects { + if let LayerEntryData::PopRange(pop_range) = &object.data { + if object.instance_id == instance_id { + return Some((object, pop_range)); + } + } + } + } + + // For certain PopRanges (e.g. the starting position in the opening zones) + for group in &self.planevent.as_ref().unwrap().layers { for object in &group.objects { if let LayerEntryData::PopRange(pop_range) = &object.data { if object.instance_id == instance_id {