diff --git a/Cargo.toml b/Cargo.toml index 6840d29..c0e4fd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,4 +104,4 @@ rkon = { version = "0.1" } tower-http = { version = "0.6", features = ["fs", "cors"] } # excel sheet data -icarus = { git = "https://github.com/redstrate/Icarus", branch = "ver/2025.04.16.0000.0000", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race", "Aetheryte", "EquipSlotCategory", "Action", "WeatherRate"], default-features = false } +icarus = { git = "https://github.com/redstrate/Icarus", branch = "ver/2025.04.16.0000.0000", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race", "Aetheryte", "EquipSlotCategory", "Action", "WeatherRate", "PlaceName"], default-features = false } diff --git a/USAGE.md b/USAGE.md index 095912a..7a0da6a 100644 --- a/USAGE.md +++ b/USAGE.md @@ -131,4 +131,5 @@ These GM commands are implemented in the FFXIV protocol, but only some of them a * `//gm aetheryte `: Unlock an Aetheryte. * `//gm speed `: Increases your movement speed by `multiplier`. * `//gm orchestrion `: Unlock an Orchestrion song. -* `//gm exp ` Adds the specified amount of EXP to the current class/job. +* `//gm exp `: Adds the specified amount of EXP to the current class/job. +* `//gm teri_info`: Displays information about the current zone. Currently displays zone id, weather, internal zone name, parent region name, and place/display name. diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index f7970eb..9d1374a 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -4,7 +4,7 @@ use std::time::{Duration, Instant}; use kawari::RECEIVE_BUFFER_SIZE; use kawari::common::Position; -use kawari::common::{GameData, timestamp_secs}; +use kawari::common::{GameData, TerritoryNameKind, timestamp_secs}; use kawari::config::get_config; use kawari::inventory::Item; use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment}; @@ -692,6 +692,31 @@ async fn client_loop( connection.player_data.set_current_exp(connection.player_data.current_exp() + amount); connection.update_class_info().await; } + + GameMasterCommandType::TerritoryInfo => { + let id: u32 = connection.zone.as_ref().unwrap().id.into(); + let weather_id; + let internal_name; + let place_name; + let region_name; + { + let mut game_data = connection.gamedata.lock().unwrap(); + // TODO: Maybe the current weather should be cached somewhere like the zone id? + weather_id = game_data + .get_weather(id) + .unwrap_or(1) as u16; + let fallback = ""; + internal_name = game_data.get_territory_name(id, TerritoryNameKind::Internal).unwrap_or(fallback.to_string()); + region_name = game_data.get_territory_name(id, TerritoryNameKind::Region).unwrap_or(fallback.to_string()); + place_name = game_data.get_territory_name(id, TerritoryNameKind::Place).unwrap_or(fallback.to_string()); + } + + connection.send_message(format!(concat!("Territory Info for zone {}:\n", + "Current weather: {}\n", + "Internal name: {}\n", + "Region name: {}\n", + "Place name: {}"), id, weather_id, internal_name, region_name, place_name).as_str()).await; + } } } ClientZoneIpcData::ZoneJump { diff --git a/src/common/gamedata.rs b/src/common/gamedata.rs index a6e2a9e..fbd7a25 100644 --- a/src/common/gamedata.rs +++ b/src/common/gamedata.rs @@ -2,6 +2,7 @@ use icarus::Action::ActionSheet; use icarus::Aetheryte::AetheryteSheet; use icarus::ClassJob::ClassJobSheet; use icarus::EquipSlotCategory::EquipSlotCategorySheet; +use icarus::PlaceName::PlaceNameSheet; use icarus::TerritoryType::TerritoryTypeSheet; use icarus::WeatherRate::WeatherRateSheet; use icarus::World::WorldSheet; @@ -125,6 +126,31 @@ impl GameData { Some((*pop_range_id, *zone_id)) } + // Retrieves a zone's internal name, place name or parent region name. + pub fn get_territory_name( + &mut self, + zone_id: u32, + which: TerritoryNameKind, + ) -> Option { + let sheet = TerritoryTypeSheet::read_from(&mut self.game_data, Language::None)?; + let row = sheet.get_row(zone_id)?; + + let offset = match which { + TerritoryNameKind::Internal => { + return row.Name().into_string().cloned(); + } + TerritoryNameKind::Region => row.PlaceNameRegion().into_u16()?, + TerritoryNameKind::Place => row.PlaceName().into_u16()?, + }; + + let sheet = PlaceNameSheet::read_from(&mut self.game_data, Language::English)?; + let row = sheet.get_row(*offset as u32)?; + + let value = row.Name().into_string()?; + + Some(value.clone()) + } + /// Find an item's equip category and id by name, if it exists. pub fn get_item_by_name(&mut self, name: &str) -> Option<(u8, u32)> { for page in &self.item_pages { @@ -217,7 +243,7 @@ impl GameData { return Some(12); } - let soul_crystal = row.FingerL().into_i8()?; + let soul_crystal = row.SoulCrystal().into_i8()?; if *soul_crystal == 1 { return Some(13); } @@ -291,3 +317,10 @@ impl GameData { self.get_weather_rate(*weather_rate_id as u32) } } + +// Simple enum for GameData::get_territory_name +pub enum TerritoryNameKind { + Internal, + Region, + Place, +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 3604ad1..6a9755c 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -12,6 +12,7 @@ pub use position::Position; mod gamedata; pub use gamedata::GameData; +pub use gamedata::TerritoryNameKind; pub mod workdefinitions; diff --git a/src/ipc/zone/mod.rs b/src/ipc/zone/mod.rs index 93cde92..ea4e287 100644 --- a/src/ipc/zone/mod.rs +++ b/src/ipc/zone/mod.rs @@ -148,6 +148,7 @@ pub enum GameMasterCommandType { Orchestrion = 0x74, GiveItem = 0xC8, Aetheryte = 0x5E, + TerritoryInfo = 0x25D, } #[binrw]