diff --git a/Cargo.toml b/Cargo.toml index 365c802..6840d29 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"], 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"], default-features = false } diff --git a/src/common/gamedata.rs b/src/common/gamedata.rs index e8020df..a6e2a9e 100644 --- a/src/common/gamedata.rs +++ b/src/common/gamedata.rs @@ -2,6 +2,8 @@ use icarus::Action::ActionSheet; use icarus::Aetheryte::AetheryteSheet; use icarus::ClassJob::ClassJobSheet; use icarus::EquipSlotCategory::EquipSlotCategorySheet; +use icarus::TerritoryType::TerritoryTypeSheet; +use icarus::WeatherRate::WeatherRateSheet; use icarus::World::WorldSheet; use icarus::{Tribe::TribeSheet, Warp::WarpSheet}; use physis::common::{Language, Platform}; @@ -10,6 +12,8 @@ use physis::exh::EXH; use crate::{common::Attributes, config::get_config}; +use super::timestamp_secs; + /// Convenient methods built on top of Physis to access data relevant to the server pub struct GameData { pub game_data: physis::gamedata::GameData, @@ -227,4 +231,63 @@ impl GameData { row.Cast100ms().into_u16().copied() } + + /// Calculates the current weather at the current time + // TODO: instead allow targetting a specific time to calculate forcecasts + pub fn get_weather_rate(&mut self, weather_rate_id: u32) -> Option { + let sheet = WeatherRateSheet::read_from(&mut self.game_data, Language::None)?; + let row = sheet.get_row(weather_rate_id)?; + + let target = Self::calculate_target(); + let weather_and_rates: Vec<(i32, i32)> = row + .Weather() + .iter() + .cloned() + .zip(row.Rate().clone()) + .map(|(x, y)| (*x.into_i32().unwrap(), *y.into_u8().unwrap() as i32)) + .collect(); + + Some( + weather_and_rates + .iter() + .filter(|(_, rate)| target < *rate) + .take(1) + .collect::>() + .first()? + .0, + ) + } + + /// Calculate target window for weather calculations + fn calculate_target() -> i32 { + // Based off of https://github.com/Rogueadyn/SaintCoinach/blob/master/SaintCoinach/Xiv/WeatherRate.cs + // TODO: this isn't correct still and doesn't seem to match up with the retail server + + let real_to_eorzean_factor = (60.0 * 24.0) / 70.0; + let unix = (timestamp_secs() as f32 / real_to_eorzean_factor) as u64; + // Get Eorzea hour for weather start + let bell = unix / 175; + // Do the magic 'cause for calculations 16:00 is 0, 00:00 is 8 and 08:00 is 16 + let increment = ((bell + 8 - (bell % 8)) as u32) % 24; + + // Take Eorzea days since unix epoch + let total_days = (unix / 4200) as u32; + + let calc_base = (total_days * 0x64) + increment; + + let step1 = (calc_base << 0xB) ^ calc_base; + let step2 = (step1 >> 8) ^ step1; + + (step2 % 0x64) as i32 + } + + /// Gets the current weather for the given zone id + pub fn get_weather(&mut self, zone_id: u32) -> Option { + let sheet = TerritoryTypeSheet::read_from(&mut self.game_data, Language::None)?; + let row = sheet.get_row(zone_id)?; + + let weather_rate_id = row.WeatherRate().into_u8()?; + + self.get_weather_rate(*weather_rate_id as u32) + } } diff --git a/src/world/connection.rs b/src/world/connection.rs index eb6229f..67a0fc7 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -346,12 +346,20 @@ impl ZoneConnection { { let config = get_config(); + let weather_id; + { + let mut game_data = self.gamedata.lock().unwrap(); + weather_id = game_data + .get_weather(self.zone.as_ref().unwrap().id.into()) + .unwrap_or(1) as u16; + } + let ipc = ServerZoneIpcSegment { op_code: ServerZoneIpcType::InitZone, timestamp: timestamp_secs(), data: ServerZoneIpcData::InitZone(InitZone { territory_type: self.zone.as_ref().unwrap().id, - weather_id: 1, + weather_id, obsfucation_mode: if config.world.enable_packet_obsfucation { OBFUSCATION_ENABLED_MODE } else {