mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-21 15:07:45 +00:00
Load the starting position from the LGB
It turns out (amazingly) that this data exists client-side, I guess because the server and the client share the same planevent LGB file. So instead of hardcoding the starting location in each city, it's now literally like retail* * Except for the fact that we don't support rotation yet, and positions in pop ranges are probably randomized too. But it's close!
This commit is contained in:
parent
c4b9ad060b
commit
2bf9385079
6 changed files with 117 additions and 27 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -31,9 +31,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.8.1"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
|
checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum-core",
|
"axum-core",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -64,12 +64,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.5.0"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
|
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-core",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
|
@ -601,9 +601,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.2"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
|
@ -637,7 +637,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "physis"
|
name = "physis"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/redstrate/physis#eee7d5867fb7424592ff1249f28c90dbe1daec13"
|
source = "git+https://github.com/redstrate/physis#a34361b9560a95bdd257ee4fe721dbe1156a5610"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"binrw",
|
"binrw",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
--- TODO: find a way to hardcode it this way
|
--- TODO: find a way to hardcode it this way
|
||||||
EVENT_ID = 1245187
|
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)
|
function Scene00000(player)
|
||||||
player:play_scene(EVENT_ID, 00000, 4959237, 1)
|
player:play_scene(EVENT_ID, 00000, 4959237, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Scene00001(player)
|
function Scene00001(player)
|
||||||
--- todo put player in correct position
|
|
||||||
player:play_scene(EVENT_ID, 00001, 4959237, 1)
|
player:play_scene(EVENT_ID, 00001, 4959237, 1)
|
||||||
end
|
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);
|
Scene00000(player);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
Event,
|
Event,
|
||||||
ipc::{
|
ipc::{
|
||||||
ActorControl, ActorControlCategory, BattleNpcSubKind, CommonSpawn, DisplayFlag,
|
ActorControl, ActorControlCategory, BattleNpcSubKind, CommonSpawn, DisplayFlag,
|
||||||
EventPlay, EventStart, NpcSpawn, ObjectKind, OnlineStatus, PlayerSpawn, PlayerSubKind,
|
EventStart, NpcSpawn, ObjectKind, OnlineStatus, PlayerSpawn, PlayerSubKind,
|
||||||
ServerZoneIpcData, ServerZoneIpcSegment,
|
ServerZoneIpcData, ServerZoneIpcSegment,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -334,7 +334,7 @@ impl ChatHandler {
|
||||||
.event
|
.event
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.enter_territory(lua_player);
|
.enter_territory(lua_player, connection.zone.as_ref().unwrap());
|
||||||
}
|
}
|
||||||
_ => tracing::info!("Unrecognized debug command!"),
|
_ => tracing::info!("Unrecognized debug command!"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use mlua::{Function, Lua};
|
||||||
|
|
||||||
use crate::config::get_config;
|
use crate::config::get_config;
|
||||||
|
|
||||||
use super::LuaPlayer;
|
use super::{LuaPlayer, Zone};
|
||||||
|
|
||||||
pub struct Event {
|
pub struct Event {
|
||||||
lua: Lua,
|
lua: Lua,
|
||||||
|
@ -22,14 +22,15 @@ impl Event {
|
||||||
Self { lua }
|
Self { lua }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enter_territory(&mut self, player: &mut LuaPlayer) {
|
pub fn enter_territory(&mut self, player: &mut LuaPlayer, zone: &Zone) {
|
||||||
self.lua
|
self.lua
|
||||||
.scope(|scope| {
|
.scope(|scope| {
|
||||||
let player = scope.create_userdata_ref_mut(player).unwrap();
|
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();
|
let func: Function = self.lua.globals().get("onEnterTerritory").unwrap();
|
||||||
|
|
||||||
func.call::<()>(player).unwrap();
|
func.call::<()>((player, zone)).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use mlua::{UserData, UserDataMethods};
|
use mlua::{FromLua, Lua, UserData, UserDataMethods, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{ObjectId, ObjectTypeId, timestamp_secs},
|
common::{ObjectId, ObjectTypeId, Position, timestamp_secs},
|
||||||
opcodes::ServerZoneIpcType,
|
opcodes::ServerZoneIpcType,
|
||||||
packet::{PacketSegment, SegmentType},
|
packet::{PacketSegment, SegmentType},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
PlayerData, StatusEffects,
|
PlayerData, StatusEffects, Zone,
|
||||||
ipc::{EventPlay, ServerZoneIpcData, ServerZoneIpcSegment},
|
ipc::{ActorSetPos, EventPlay, ServerZoneIpcData, ServerZoneIpcSegment},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -71,6 +71,25 @@ impl LuaPlayer {
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
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 {
|
impl UserData for LuaPlayer {
|
||||||
|
@ -93,5 +112,40 @@ impl UserData for LuaPlayer {
|
||||||
Ok(())
|
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<Self> {
|
||||||
|
match value {
|
||||||
|
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for Zone {
|
||||||
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_method(
|
||||||
|
"get_pop_range",
|
||||||
|
|_, this, id: u32| -> mlua::Result<Position> {
|
||||||
|
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())
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,16 @@ use physis::{
|
||||||
use crate::config::get_config;
|
use crate::config::get_config;
|
||||||
|
|
||||||
/// Represents a loaded zone
|
/// Represents a loaded zone
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Zone {
|
pub struct Zone {
|
||||||
pub id: u16,
|
pub id: u16,
|
||||||
layer_group: Option<LayerGroup>,
|
planevent: Option<LayerGroup>,
|
||||||
|
vfx: Option<LayerGroup>,
|
||||||
|
planmap: Option<LayerGroup>,
|
||||||
|
planner: Option<LayerGroup>,
|
||||||
|
bg: Option<LayerGroup>,
|
||||||
|
sound: Option<LayerGroup>,
|
||||||
|
planlive: Option<LayerGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Zone {
|
impl Zone {
|
||||||
|
@ -20,7 +27,7 @@ impl Zone {
|
||||||
|
|
||||||
let mut zone = Self {
|
let mut zone = Self {
|
||||||
id,
|
id,
|
||||||
layer_group: None,
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(mut game_data) = GameData::from_existing(Platform::Win32, &config.game_location)
|
let Some(mut game_data) = GameData::from_existing(Platform::Win32, &config.game_location)
|
||||||
|
@ -49,11 +56,22 @@ impl Zone {
|
||||||
return zone;
|
return zone;
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = format!("bg/{}/level/planmap.lgb", &bg_path[..level_index]);
|
let mut load_lgb = |name: &str| -> Option<LayerGroup> {
|
||||||
|
let path = format!("bg/{}/level/{}.lgb", &bg_path[..level_index], name);
|
||||||
|
tracing::info!("Loading {path}");
|
||||||
let Some(lgb) = game_data.extract(&path) else {
|
let Some(lgb) = game_data.extract(&path) else {
|
||||||
return zone;
|
return None;
|
||||||
};
|
};
|
||||||
zone.layer_group = LayerGroup::from_existing(&lgb);
|
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
|
zone
|
||||||
}
|
}
|
||||||
|
@ -64,7 +82,7 @@ impl Zone {
|
||||||
instance_id: u32,
|
instance_id: u32,
|
||||||
) -> Option<(&InstanceObject, &ExitRangeInstanceObject)> {
|
) -> Option<(&InstanceObject, &ExitRangeInstanceObject)> {
|
||||||
// TODO: also check position!
|
// 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 {
|
for object in &group.objects {
|
||||||
if let LayerEntryData::ExitRange(exit_range) = &object.data {
|
if let LayerEntryData::ExitRange(exit_range) = &object.data {
|
||||||
if object.instance_id == instance_id {
|
if object.instance_id == instance_id {
|
||||||
|
@ -82,7 +100,18 @@ impl Zone {
|
||||||
instance_id: u32,
|
instance_id: u32,
|
||||||
) -> Option<(&InstanceObject, &PopRangeInstanceObject)> {
|
) -> Option<(&InstanceObject, &PopRangeInstanceObject)> {
|
||||||
// TODO: also check position!
|
// 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 {
|
for object in &group.objects {
|
||||||
if let LayerEntryData::PopRange(pop_range) = &object.data {
|
if let LayerEntryData::PopRange(pop_range) = &object.data {
|
||||||
if object.instance_id == instance_id {
|
if object.instance_id == instance_id {
|
||||||
|
|
Loading…
Add table
Reference in a new issue