2025-05-11 10:12:02 -04:00
|
|
|
use icarus::Aetheryte::AetheryteSheet;
|
2025-05-10 09:25:22 -04:00
|
|
|
use icarus::ClassJob::ClassJobSheet;
|
|
|
|
use icarus::World::WorldSheet;
|
|
|
|
use icarus::{Tribe::TribeSheet, Warp::WarpSheet};
|
2025-03-30 10:34:42 -04:00
|
|
|
use physis::common::{Language, Platform};
|
2025-05-09 15:25:57 -04:00
|
|
|
use physis::exd::{EXD, ExcelRowKind};
|
2025-03-30 23:48:11 -04:00
|
|
|
use physis::exh::EXH;
|
2025-03-30 10:34:42 -04:00
|
|
|
|
|
|
|
use crate::{common::Attributes, config::get_config};
|
|
|
|
|
|
|
|
/// Convenient methods built on top of Physis to access data relevant to the server
|
|
|
|
pub struct GameData {
|
2025-03-30 19:13:24 -04:00
|
|
|
pub game_data: physis::gamedata::GameData,
|
2025-03-30 23:48:11 -04:00
|
|
|
pub item_exh: EXH,
|
|
|
|
pub item_pages: Vec<EXD>,
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
|
|
|
|
2025-03-30 21:42:46 -04:00
|
|
|
impl Default for GameData {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-30 10:34:42 -04:00
|
|
|
impl GameData {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
let config = get_config();
|
|
|
|
|
2025-03-31 17:26:56 -04:00
|
|
|
let mut game_data =
|
2025-05-09 15:25:57 -04:00
|
|
|
physis::gamedata::GameData::from_existing(Platform::Win32, &config.game_location);
|
2025-03-30 23:48:11 -04:00
|
|
|
|
|
|
|
let mut item_pages = Vec::new();
|
|
|
|
|
|
|
|
let item_exh = game_data.read_excel_sheet_header("Item").unwrap();
|
|
|
|
for (i, _) in item_exh.pages.iter().enumerate() {
|
2025-03-31 17:26:56 -04:00
|
|
|
item_pages.push(
|
|
|
|
game_data
|
|
|
|
.read_excel_sheet("Item", &item_exh, Language::English, i)
|
|
|
|
.unwrap(),
|
|
|
|
);
|
2025-03-30 23:48:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Self {
|
|
|
|
game_data,
|
|
|
|
item_exh,
|
2025-03-31 17:26:56 -04:00
|
|
|
item_pages,
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the world name from an id into the World Excel sheet.
|
2025-05-09 18:35:44 -04:00
|
|
|
pub fn get_world_name(&mut self, world_id: u16) -> Option<String> {
|
|
|
|
let sheet = WorldSheet::read_from(&mut self.game_data, Language::None)?;
|
|
|
|
let row = sheet.get_row(world_id as u32)?;
|
|
|
|
|
|
|
|
row.Name().into_string().map(|x| x.clone())
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the starting city-state from a given class/job id.
|
2025-05-09 18:35:44 -04:00
|
|
|
pub fn get_citystate(&mut self, classjob_id: u16) -> Option<u8> {
|
|
|
|
let sheet = ClassJobSheet::read_from(&mut self.game_data, Language::English)?;
|
|
|
|
let row = sheet.get_row(classjob_id as u32)?;
|
|
|
|
|
|
|
|
row.StartingTown().into_u8().map(|x| *x)
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
|
|
|
|
2025-05-09 18:35:44 -04:00
|
|
|
pub fn get_racial_base_attributes(&mut self, tribe_id: u8) -> Option<Attributes> {
|
2025-03-30 10:34:42 -04:00
|
|
|
// 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;
|
|
|
|
|
2025-05-09 18:35:44 -04:00
|
|
|
let sheet = TribeSheet::read_from(&mut self.game_data, Language::English)?;
|
|
|
|
let row = sheet.get_row(tribe_id 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,
|
|
|
|
})
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the primary model ID for a given item ID
|
2025-04-18 13:20:25 -04:00
|
|
|
pub fn get_primary_model_id(&mut self, item_id: u32) -> Option<u64> {
|
2025-03-30 23:48:11 -04:00
|
|
|
for page in &self.item_pages {
|
2025-05-09 15:25:57 -04:00
|
|
|
if let Some(row) = page.get_row(item_id) {
|
|
|
|
let ExcelRowKind::SingleRow(item_row) = row else {
|
|
|
|
panic!("Expected a single row!")
|
|
|
|
};
|
2025-03-30 10:34:42 -04:00
|
|
|
|
2025-05-09 15:25:57 -04:00
|
|
|
let physis::exd::ColumnData::UInt64(id) = &item_row.columns[47] else {
|
2025-03-30 10:34:42 -04:00
|
|
|
panic!("Unexpected type!");
|
|
|
|
};
|
|
|
|
|
2025-04-18 13:20:25 -04:00
|
|
|
return Some(*id);
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-11 08:36:17 -04:00
|
|
|
None
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|
2025-05-05 22:04:39 -04:00
|
|
|
|
|
|
|
/// Returns the pop range object id that's associated with the warp id
|
2025-05-09 18:03:25 -04:00
|
|
|
pub fn get_warp(&mut self, warp_id: u32) -> Option<(u32, u16)> {
|
2025-05-09 18:35:44 -04:00
|
|
|
let sheet = WarpSheet::read_from(&mut self.game_data, Language::English)?;
|
|
|
|
let row = sheet.get_row(warp_id)?;
|
2025-05-05 22:04:39 -04:00
|
|
|
|
2025-05-09 18:03:25 -04:00
|
|
|
let pop_range_id = row.PopRange().into_u32()?;
|
|
|
|
let zone_id = row.TerritoryType().into_u16()?;
|
2025-05-05 22:04:39 -04:00
|
|
|
|
2025-05-09 18:03:25 -04:00
|
|
|
Some((*pop_range_id, *zone_id))
|
2025-05-05 22:04:39 -04:00
|
|
|
}
|
2025-05-11 10:12:02 -04:00
|
|
|
|
|
|
|
pub fn get_aetheryte(&mut self, aetheryte_id: u32) -> Option<(u32, u16)> {
|
|
|
|
let sheet = AetheryteSheet::read_from(&mut self.game_data, Language::English)?;
|
|
|
|
let row = sheet.get_row(aetheryte_id)?;
|
|
|
|
|
|
|
|
// TODO: just look in the level sheet?
|
|
|
|
let pop_range_id = row.Level()[0].into_u32()?;
|
|
|
|
let zone_id = row.Territory().into_u16()?;
|
|
|
|
|
|
|
|
Some((*pop_range_id, *zone_id))
|
|
|
|
}
|
2025-03-30 10:34:42 -04:00
|
|
|
}
|