mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-22 07:27:44 +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]]
|
||||
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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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!"),
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
})
|
||||
|
|
|
@ -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<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;
|
||||
|
||||
/// Represents a loaded zone
|
||||
#[derive(Default)]
|
||||
pub struct Zone {
|
||||
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 {
|
||||
|
@ -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<LayerGroup> {
|
||||
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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue