From 9787126a1b3d8a4ac86e8a3e333cf1de568a6217 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Fri, 9 May 2025 18:35:44 -0400 Subject: [PATCH] Replace (most of) the remaining Excel parsing with Physis Sheets The only one remaining is for Item data, but that can't be ported yet because the new API only fetches the first page for now. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/kawari-lobby.rs | 4 +- src/common/gamedata.rs | 82 +++++++++------------------------ src/inventory/mod.rs | 38 +++++---------- src/world/connection.rs | 5 +- src/world/custom_ipc_handler.rs | 8 +++- src/world/zone.rs | 22 ++------- 8 files changed, 53 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 109c592..a3da88a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1028,7 +1028,7 @@ dependencies = [ [[package]] name = "physis-sheets" version = "0.0.0" -source = "git+https://github.com/redstrate/PhysisSheets#ca42d72fc8a76211d67f6bd4698a45c7da0037fb" +source = "git+https://github.com/redstrate/PhysisSheets#4d11e27a1e31bb482184626d1e7a51fc5e673674" dependencies = [ "physis", ] diff --git a/Cargo.toml b/Cargo.toml index 2cabb8c..495c131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,4 +104,4 @@ rkon = { version = "0.1" } tower-http = { version = "0.6", features = ["fs"] } # excel sheet data -physis-sheets = { git = "https://github.com/redstrate/PhysisSheets", features = ["Warp"], default-features = false } +physis-sheets = { git = "https://github.com/redstrate/PhysisSheets", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race"], default-features = false } diff --git a/src/bin/kawari-lobby.rs b/src/bin/kawari-lobby.rs index 417b5a0..4e1ba45 100644 --- a/src/bin/kawari-lobby.rs +++ b/src/bin/kawari-lobby.rs @@ -27,7 +27,9 @@ async fn main() { tracing::info!("Server started on {addr}"); let mut game_data = GameData::new(); - let world_name = game_data.get_world_name(config.world.world_id); + let world_name = game_data + .get_world_name(config.world.world_id) + .expect("Unknown world name"); loop { let (socket, _) = listener.accept().await.unwrap(); diff --git a/src/common/gamedata.rs b/src/common/gamedata.rs index 879c044..0e3ada4 100644 --- a/src/common/gamedata.rs +++ b/src/common/gamedata.rs @@ -1,7 +1,9 @@ use physis::common::{Language, Platform}; use physis::exd::{EXD, ExcelRowKind}; use physis::exh::EXH; -use physis_sheets::Warp::Warp; +use physis_sheets::ClassJob::ClassJobSheet; +use physis_sheets::World::WorldSheet; +use physis_sheets::{Tribe::TribeSheet, Warp::WarpSheet}; use crate::{common::Attributes, config::get_config}; @@ -44,72 +46,35 @@ impl GameData { } /// Gets the world name from an id into the World Excel sheet. - pub fn get_world_name(&mut self, world_id: u16) -> String { - let exh = self.game_data.read_excel_sheet_header("World").unwrap(); - let exd = self - .game_data - .read_excel_sheet("World", &exh, Language::None, 0) - .unwrap(); + pub fn get_world_name(&mut self, world_id: u16) -> Option { + let sheet = WorldSheet::read_from(&mut self.game_data, Language::None)?; + let row = sheet.get_row(world_id as u32)?; - let ExcelRowKind::SingleRow(world_row) = &exd.get_row(world_id as u32).unwrap() else { - panic!("Expected a single row!") - }; - - let physis::exd::ColumnData::String(name) = &world_row.columns[1] else { - panic!("Unexpected type!"); - }; - - name.clone() + row.Name().into_string().map(|x| x.clone()) } /// Gets the starting city-state from a given class/job id. - pub fn get_citystate(&mut self, classjob_id: u16) -> u8 { - let exh = self.game_data.read_excel_sheet_header("ClassJob").unwrap(); - let exd = self - .game_data - .read_excel_sheet("ClassJob", &exh, Language::English, 0) - .unwrap(); + pub fn get_citystate(&mut self, classjob_id: u16) -> Option { + let sheet = ClassJobSheet::read_from(&mut self.game_data, Language::English)?; + let row = sheet.get_row(classjob_id as u32)?; - let ExcelRowKind::SingleRow(world_row) = &exd.get_row(classjob_id as u32).unwrap() else { - panic!("Expected a single row!") - }; - - let physis::exd::ColumnData::UInt8(town_id) = &world_row.columns[33] else { - panic!("Unexpected type!"); - }; - - *town_id + row.StartingTown().into_u8().map(|x| *x) } - pub fn get_racial_base_attributes(&mut self, tribe_id: u8) -> Attributes { + pub fn get_racial_base_attributes(&mut self, tribe_id: u8) -> Option { // The Tribe Excel sheet only has deltas (e.g. 2 or -2) which are applied to a base 20 number... from somewhere let base_stat = 20; - let exh = self.game_data.read_excel_sheet_header("Tribe").unwrap(); - let exd = self - .game_data - .read_excel_sheet("Tribe", &exh, Language::English, 0) - .unwrap(); + let sheet = TribeSheet::read_from(&mut self.game_data, Language::English)?; + let row = sheet.get_row(tribe_id as u32)?; - let ExcelRowKind::SingleRow(tribe_row) = &exd.get_row(tribe_id as u32).unwrap() else { - panic!("Expected a single row!") - }; - - let get_column = |column_index: usize| { - let physis::exd::ColumnData::Int8(delta) = &tribe_row.columns[column_index] else { - panic!("Unexpected type!"); - }; - - *delta - }; - - Attributes { - strength: (base_stat + get_column(4)) as u32, - dexterity: (base_stat + get_column(6)) as u32, - vitality: (base_stat + get_column(5)) as u32, - intelligence: (base_stat + get_column(7)) as u32, - mind: (base_stat + get_column(8)) as u32, - } + Some(Attributes { + strength: (base_stat + row.STR().into_i8()?) as u32, + dexterity: (base_stat + row.DEX().into_i8()?) as u32, + vitality: (base_stat + row.VIT().into_i8()?) as u32, + intelligence: (base_stat + row.INT().into_i8()?) as u32, + mind: (base_stat + row.MND().into_i8()?) as u32, + }) } /// Gets the primary model ID for a given item ID @@ -133,9 +98,8 @@ impl GameData { /// Returns the pop range object id that's associated with the warp id pub fn get_warp(&mut self, warp_id: u32) -> Option<(u32, u16)> { - let warp_sheet = Warp::read_from(&mut self.game_data, Language::English)?; - - let row = warp_sheet.get_row(warp_id)?; + let sheet = WarpSheet::read_from(&mut self.game_data, Language::English)?; + let row = sheet.get_row(warp_id)?; let pop_range_id = row.PopRange().into_u32()?; let zone_id = row.TerritoryType().into_u16()?; diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index acf3bfe..cfa15b7 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -1,4 +1,5 @@ -use physis::{common::Language, exd::ExcelRowKind}; +use physis::common::Language; +use physis_sheets::Race::RaceSheet; use serde::{Deserialize, Serialize}; use crate::common::GameData; @@ -122,34 +123,19 @@ impl<'a> Iterator for InventoryIterator<'a> { impl Inventory { /// Equip the starting items for a given race pub fn equip_racial_items(&mut self, race_id: u8, gender: u8, game_data: &mut GameData) { - let exh = game_data.game_data.read_excel_sheet_header("Race").unwrap(); - let exd = game_data - .game_data - .read_excel_sheet("Race", &exh, Language::English, 0) - .unwrap(); - - let ExcelRowKind::SingleRow(world_row) = &exd.get_row(race_id as u32).unwrap() else { - panic!("Expected a single row!") - }; - - let get_column = |column_index: usize| { - let physis::exd::ColumnData::Int32(item_id) = &world_row.columns[column_index] else { - panic!("Unexpected type!"); - }; - - *item_id - }; + let sheet = RaceSheet::read_from(&mut game_data.game_data, Language::English).unwrap(); + let row = sheet.get_row(race_id as u32).unwrap(); if gender == 0 { - self.equipped.body = Item::new(1, get_column(2) as u32); - self.equipped.hands = Item::new(1, get_column(3) as u32); - self.equipped.legs = Item::new(1, get_column(4) as u32); - self.equipped.feet = Item::new(1, get_column(5) as u32); + self.equipped.body = Item::new(1, *row.RSEMBody().into_i32().unwrap() as u32); + self.equipped.hands = Item::new(1, *row.RSEMHands().into_i32().unwrap() as u32); + self.equipped.legs = Item::new(1, *row.RSEMLegs().into_i32().unwrap() as u32); + self.equipped.feet = Item::new(1, *row.RSEMFeet().into_i32().unwrap() as u32); } else { - self.equipped.body = Item::new(1, get_column(6) as u32); - self.equipped.hands = Item::new(1, get_column(7) as u32); - self.equipped.legs = Item::new(1, get_column(8) as u32); - self.equipped.feet = Item::new(1, get_column(9) as u32); + self.equipped.body = Item::new(1, *row.RSEFBody().into_i32().unwrap() as u32); + self.equipped.hands = Item::new(1, *row.RSEFHands().into_i32().unwrap() as u32); + self.equipped.legs = Item::new(1, *row.RSEFLegs().into_i32().unwrap() as u32); + self.equipped.feet = Item::new(1, *row.RSEFFeet().into_i32().unwrap() as u32); } // TODO: don't hardcode diff --git a/src/world/connection.rs b/src/world/connection.rs index 53c1d86..d0c1a9b 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -834,8 +834,9 @@ impl ZoneConnection { { let mut game_data = self.gamedata.lock().unwrap(); - attributes = - game_data.get_racial_base_attributes(chara_details.chara_make.customize.subrace); + attributes = game_data + .get_racial_base_attributes(chara_details.chara_make.customize.subrace) + .expect("Failed to read racial attributes"); } let ipc = ServerZoneIpcSegment { diff --git a/src/world/custom_ipc_handler.rs b/src/world/custom_ipc_handler.rs index 98cdb03..9c16892 100644 --- a/src/world/custom_ipc_handler.rs +++ b/src/world/custom_ipc_handler.rs @@ -28,7 +28,9 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc { let mut game_data = connection.gamedata.lock().unwrap(); - city_state = game_data.get_citystate(chara_make.classjob_id as u16); + city_state = game_data + .get_citystate(chara_make.classjob_id as u16) + .expect("Unknown citystate"); } let mut inventory = Inventory::default(); @@ -122,7 +124,9 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc let world_name; { let mut game_data = connection.gamedata.lock().unwrap(); - world_name = game_data.get_world_name(config.world.world_id); + world_name = game_data + .get_world_name(config.world.world_id) + .expect("Couldn't read world name"); } let characters; diff --git a/src/world/zone.rs b/src/world/zone.rs index 1988272..d8f4972 100644 --- a/src/world/zone.rs +++ b/src/world/zone.rs @@ -1,11 +1,11 @@ use physis::{ common::Language, - exd::ExcelRowKind, gamedata::GameData, layer::{ ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject, }, }; +use physis_sheets::TerritoryType::TerritoryTypeSheet; /// Represents a loaded zone #[derive(Default)] @@ -27,25 +27,11 @@ impl Zone { ..Default::default() }; - let Some(exh) = game_data.read_excel_sheet_header("TerritoryType") else { - return zone; - }; - let Some(exd) = game_data.read_excel_sheet("TerritoryType", &exh, Language::None, 0) else { - return zone; - }; - - let Some(territory_type_row) = &exd.get_row(id as u32) else { - return zone; - }; - - let ExcelRowKind::SingleRow(territory_type_row) = territory_type_row else { - panic!("Expected a single row!") - }; + let sheet = TerritoryTypeSheet::read_from(game_data, Language::None).unwrap(); + let row = sheet.get_row(id as u32).unwrap(); // e.g. ffxiv/fst_f1/fld/f1f3/level/f1f3 - let physis::exd::ColumnData::String(bg_path) = &territory_type_row.columns[1] else { - panic!("Unexpected type!"); - }; + let bg_path = row.Bg().into_string().unwrap(); let Some(level_index) = bg_path.find("/level/") else { return zone;