basic forecasting
This commit is contained in:
parent
f427c0fbae
commit
6e1713c967
3 changed files with 158 additions and 9 deletions
64
src/forecast.rs
Normal file
64
src/forecast.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use chrono::{DateTime, NaiveTime, Timelike, Utc};
|
||||
|
||||
/// A forecast is used to divine specific weather patterns for a zone.
|
||||
#[derive(Debug)]
|
||||
pub struct Forecast {
|
||||
pub zone_id: u32,
|
||||
pub rates: Vec<Rate>,
|
||||
}
|
||||
|
||||
/// A weather rate consists of the Weather ID and the likelihood in which it occurs (also called "target").
|
||||
/// The weather algorithm that the game uses generates a random integer between 0 and 100, and whichever rate
|
||||
/// first satisfies the "is larger than target" condition gets selected.
|
||||
/// This means that rates are:
|
||||
/// 1. Sorted ascending by rate
|
||||
/// 2. The larger the rate, the less likely the weather is to occur, statistically
|
||||
#[derive(Debug)]
|
||||
pub struct Rate {
|
||||
pub weather_id: u32,
|
||||
pub rate: u32,
|
||||
}
|
||||
|
||||
/// A hash map of zones, indexed by "rate ID", which is unique in our source data.
|
||||
pub type ForecastSet = HashMap<u32, Forecast>;
|
||||
|
||||
impl Forecast {
|
||||
pub fn weather_for_target(&self, target: u32) -> &Rate {
|
||||
// TODO: Don't unwrap here!
|
||||
self.rates.iter().find(|r| target < r.rate).unwrap()
|
||||
}
|
||||
|
||||
pub fn weather_now(&self) -> &Rate {
|
||||
let utc = Utc::now();
|
||||
let target = calculate_target(utc);
|
||||
self.weather_for_target(target)
|
||||
}
|
||||
}
|
||||
|
||||
/// Rounds to the last weather "start". These happen three times a day, at 0:00,
|
||||
/// at 8:00, and at 16:00.
|
||||
pub fn round_to_last_weather_time(date: &DateTime<Utc>) -> DateTime<Utc> {
|
||||
let cur_hour = date.hour();
|
||||
// This essentially performs division without rest.
|
||||
let last_hour = (cur_hour / 8) * 8;
|
||||
date.date_naive()
|
||||
.and_hms_opt(last_hour, date.minute(), date.second())
|
||||
.unwrap()
|
||||
.and_utc()
|
||||
}
|
||||
|
||||
/// Calculates the magic target number for a specific date.
|
||||
/// It's important to note that this expects a human time, not an Eorzean time.
|
||||
pub fn calculate_target(m: DateTime<Utc>) -> u32 {
|
||||
let unix_time = m.timestamp().abs();
|
||||
let ez_hour = unix_time / 175;
|
||||
let inc = (ez_hour + 8 - (ez_hour % 8)) % 24;
|
||||
let total_days = unix_time / 4200;
|
||||
let calc_base: i32 = ((total_days * 100) + inc) as i32; // Needs to be i32 to assure correct shifting overflow!
|
||||
let step1 = ((calc_base << 11) ^ calc_base) as u32; // Cast to u32 here to literally interpret the two's complement, I suppose
|
||||
let step2 = (step1 >> 8) ^ step1;
|
||||
|
||||
step2 % 100
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue