1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-05-13 07:07:45 +00:00

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.
This commit is contained in:
Joshua Goins 2025-05-09 18:35:44 -04:00
parent df789b52c9
commit 9787126a1b
8 changed files with 53 additions and 110 deletions

2
Cargo.lock generated
View file

@ -1028,7 +1028,7 @@ dependencies = [
[[package]] [[package]]
name = "physis-sheets" name = "physis-sheets"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/redstrate/PhysisSheets#ca42d72fc8a76211d67f6bd4698a45c7da0037fb" source = "git+https://github.com/redstrate/PhysisSheets#4d11e27a1e31bb482184626d1e7a51fc5e673674"
dependencies = [ dependencies = [
"physis", "physis",
] ]

View file

@ -104,4 +104,4 @@ rkon = { version = "0.1" }
tower-http = { version = "0.6", features = ["fs"] } tower-http = { version = "0.6", features = ["fs"] }
# excel sheet data # 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 }

View file

@ -27,7 +27,9 @@ async fn main() {
tracing::info!("Server started on {addr}"); tracing::info!("Server started on {addr}");
let mut game_data = GameData::new(); 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 { loop {
let (socket, _) = listener.accept().await.unwrap(); let (socket, _) = listener.accept().await.unwrap();

View file

@ -1,7 +1,9 @@
use physis::common::{Language, Platform}; use physis::common::{Language, Platform};
use physis::exd::{EXD, ExcelRowKind}; use physis::exd::{EXD, ExcelRowKind};
use physis::exh::EXH; 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}; 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. /// Gets the world name from an id into the World Excel sheet.
pub fn get_world_name(&mut self, world_id: u16) -> String { pub fn get_world_name(&mut self, world_id: u16) -> Option<String> {
let exh = self.game_data.read_excel_sheet_header("World").unwrap(); let sheet = WorldSheet::read_from(&mut self.game_data, Language::None)?;
let exd = self let row = sheet.get_row(world_id as u32)?;
.game_data
.read_excel_sheet("World", &exh, Language::None, 0)
.unwrap();
let ExcelRowKind::SingleRow(world_row) = &exd.get_row(world_id as u32).unwrap() else { row.Name().into_string().map(|x| x.clone())
panic!("Expected a single row!")
};
let physis::exd::ColumnData::String(name) = &world_row.columns[1] else {
panic!("Unexpected type!");
};
name.clone()
} }
/// Gets the starting city-state from a given class/job id. /// Gets the starting city-state from a given class/job id.
pub fn get_citystate(&mut self, classjob_id: u16) -> u8 { pub fn get_citystate(&mut self, classjob_id: u16) -> Option<u8> {
let exh = self.game_data.read_excel_sheet_header("ClassJob").unwrap(); let sheet = ClassJobSheet::read_from(&mut self.game_data, Language::English)?;
let exd = self let row = sheet.get_row(classjob_id as u32)?;
.game_data
.read_excel_sheet("ClassJob", &exh, Language::English, 0)
.unwrap();
let ExcelRowKind::SingleRow(world_row) = &exd.get_row(classjob_id as u32).unwrap() else { row.StartingTown().into_u8().map(|x| *x)
panic!("Expected a single row!")
};
let physis::exd::ColumnData::UInt8(town_id) = &world_row.columns[33] else {
panic!("Unexpected type!");
};
*town_id
} }
pub fn get_racial_base_attributes(&mut self, tribe_id: u8) -> Attributes { pub fn get_racial_base_attributes(&mut self, tribe_id: u8) -> Option<Attributes> {
// The Tribe Excel sheet only has deltas (e.g. 2 or -2) which are applied to a base 20 number... from somewhere // 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 base_stat = 20;
let exh = self.game_data.read_excel_sheet_header("Tribe").unwrap(); let sheet = TribeSheet::read_from(&mut self.game_data, Language::English)?;
let exd = self let row = sheet.get_row(tribe_id as u32)?;
.game_data
.read_excel_sheet("Tribe", &exh, Language::English, 0)
.unwrap();
let ExcelRowKind::SingleRow(tribe_row) = &exd.get_row(tribe_id as u32).unwrap() else { Some(Attributes {
panic!("Expected a single row!") 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,
let get_column = |column_index: usize| { intelligence: (base_stat + row.INT().into_i8()?) as u32,
let physis::exd::ColumnData::Int8(delta) = &tribe_row.columns[column_index] else { mind: (base_stat + row.MND().into_i8()?) as u32,
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,
}
} }
/// Gets the primary model ID for a given item ID /// 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 /// 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)> { 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 sheet = WarpSheet::read_from(&mut self.game_data, Language::English)?;
let row = sheet.get_row(warp_id)?;
let row = warp_sheet.get_row(warp_id)?;
let pop_range_id = row.PopRange().into_u32()?; let pop_range_id = row.PopRange().into_u32()?;
let zone_id = row.TerritoryType().into_u16()?; let zone_id = row.TerritoryType().into_u16()?;

View file

@ -1,4 +1,5 @@
use physis::{common::Language, exd::ExcelRowKind}; use physis::common::Language;
use physis_sheets::Race::RaceSheet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::common::GameData; use crate::common::GameData;
@ -122,34 +123,19 @@ impl<'a> Iterator for InventoryIterator<'a> {
impl Inventory { impl Inventory {
/// Equip the starting items for a given race /// Equip the starting items for a given race
pub fn equip_racial_items(&mut self, race_id: u8, gender: u8, game_data: &mut GameData) { 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 sheet = RaceSheet::read_from(&mut game_data.game_data, Language::English).unwrap();
let exd = game_data let row = sheet.get_row(race_id as u32).unwrap();
.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
};
if gender == 0 { if gender == 0 {
self.equipped.body = Item::new(1, get_column(2) as u32); self.equipped.body = Item::new(1, *row.RSEMBody().into_i32().unwrap() as u32);
self.equipped.hands = Item::new(1, get_column(3) as u32); self.equipped.hands = Item::new(1, *row.RSEMHands().into_i32().unwrap() as u32);
self.equipped.legs = Item::new(1, get_column(4) as u32); self.equipped.legs = Item::new(1, *row.RSEMLegs().into_i32().unwrap() as u32);
self.equipped.feet = Item::new(1, get_column(5) as u32); self.equipped.feet = Item::new(1, *row.RSEMFeet().into_i32().unwrap() as u32);
} else { } else {
self.equipped.body = Item::new(1, get_column(6) as u32); self.equipped.body = Item::new(1, *row.RSEFBody().into_i32().unwrap() as u32);
self.equipped.hands = Item::new(1, get_column(7) as u32); self.equipped.hands = Item::new(1, *row.RSEFHands().into_i32().unwrap() as u32);
self.equipped.legs = Item::new(1, get_column(8) as u32); self.equipped.legs = Item::new(1, *row.RSEFLegs().into_i32().unwrap() as u32);
self.equipped.feet = Item::new(1, get_column(9) as u32); self.equipped.feet = Item::new(1, *row.RSEFFeet().into_i32().unwrap() as u32);
} }
// TODO: don't hardcode // TODO: don't hardcode

View file

@ -834,8 +834,9 @@ impl ZoneConnection {
{ {
let mut game_data = self.gamedata.lock().unwrap(); let mut game_data = self.gamedata.lock().unwrap();
attributes = attributes = game_data
game_data.get_racial_base_attributes(chara_details.chara_make.customize.subrace); .get_racial_base_attributes(chara_details.chara_make.customize.subrace)
.expect("Failed to read racial attributes");
} }
let ipc = ServerZoneIpcSegment { let ipc = ServerZoneIpcSegment {

View file

@ -28,7 +28,9 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
{ {
let mut game_data = connection.gamedata.lock().unwrap(); 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(); let mut inventory = Inventory::default();
@ -122,7 +124,9 @@ pub async fn handle_custom_ipc(connection: &mut ZoneConnection, data: &CustomIpc
let world_name; let world_name;
{ {
let mut game_data = connection.gamedata.lock().unwrap(); 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; let characters;

View file

@ -1,11 +1,11 @@
use physis::{ use physis::{
common::Language, common::Language,
exd::ExcelRowKind,
gamedata::GameData, gamedata::GameData,
layer::{ layer::{
ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject, ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject,
}, },
}; };
use physis_sheets::TerritoryType::TerritoryTypeSheet;
/// Represents a loaded zone /// Represents a loaded zone
#[derive(Default)] #[derive(Default)]
@ -27,25 +27,11 @@ impl Zone {
..Default::default() ..Default::default()
}; };
let Some(exh) = game_data.read_excel_sheet_header("TerritoryType") else { let sheet = TerritoryTypeSheet::read_from(game_data, Language::None).unwrap();
return zone; let row = sheet.get_row(id as u32).unwrap();
};
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!")
};
// e.g. ffxiv/fst_f1/fld/f1f3/level/f1f3 // e.g. ffxiv/fst_f1/fld/f1f3/level/f1f3
let physis::exd::ColumnData::String(bg_path) = &territory_type_row.columns[1] else { let bg_path = row.Bg().into_string().unwrap();
panic!("Unexpected type!");
};
let Some(level_index) = bg_path.find("/level/") else { let Some(level_index) = bg_path.find("/level/") else {
return zone; return zone;