fish-lib 0.2.3

A work-in-progress fishing game library containing the game/storage logic for a discord fishing game I'm working on.
Documentation
use crate::data::season_data::SeasonData;
use crate::enums::season::Season;
use chrono::{DateTime, Utc};
use chrono_tz::Tz;
use serde::{Deserialize, Serialize};

const SECONDS_PER_YEAR: f64 = 31_556_925.190_8;

#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct LocationData {
    #[serde(skip, default)]
    pub id: i32,
    pub name: String,
    pub timezone: Tz,
    pub weather_seed: u32,
    pub spring: SeasonData,
    pub summer: SeasonData,
    pub autumn: SeasonData,
    pub winter: SeasonData,
    #[serde(default)]
    /// The IDs of the locations that need to be unlocked before this can
    pub required_locations_unlocked: Vec<i32>,
    #[serde(default)]
    /// The IDs of the species that need to be caught before this location can be unlocked
    pub required_species_caught: Vec<i32>,
}

impl LocationData {
    fn season_index_and_progress(&self, time: DateTime<Tz>, time_multiplier: f32) -> (usize, f64) {
        let year_progress = (time.timestamp() as f64 * time_multiplier as f64) % SECONDS_PER_YEAR
            / SECONDS_PER_YEAR;

        let current_season = year_progress * 4.0;
        let season_index = current_season.floor() as usize;
        let season_progress = current_season % 1.0;

        (season_index, season_progress)
    }

    pub fn current_season_data(&self, time: DateTime<Tz>, time_multiplier: f32) -> SeasonData {
        let (index, progress) = self.season_index_and_progress(time, time_multiplier);

        let (prev_data, current_data, next_data) = match index {
            0 => (&self.winter, &self.spring, &self.summer),
            1 => (&self.spring, &self.summer, &self.autumn),
            2 => (&self.summer, &self.autumn, &self.winter),
            3 => (&self.autumn, &self.winter, &self.spring),
            _ => unreachable!(),
        };

        if progress < 0.5 {
            let adjusted_progress = progress * 2.0;
            let start = prev_data.interpolate(current_data, 0.5);
            start.interpolate(current_data, adjusted_progress as f32)
        } else {
            let adjusted_progress = (progress - 0.5) * 2.0;
            let start = current_data.interpolate(next_data, 0.5);
            current_data.interpolate(&start, adjusted_progress as f32)
        }
    }

    pub fn current_season(&self, time: DateTime<Tz>, time_multiplier: f32) -> (Season, f64) {
        let (index, progress) = self.season_index_and_progress(time, time_multiplier);
        let season = Season::from_index(index);
        (season, progress)
    }

    pub fn full_season_information(
        &self,
        time: DateTime<Tz>,
        time_multiplier: f32,
    ) -> (SeasonData, Season, f64) {
        let data = self.current_season_data(time, time_multiplier);
        let (season, progress) = self.current_season(time, time_multiplier);
        (data, season, progress)
    }

    pub fn get_local_time(&self) -> DateTime<Tz> {
        Utc::now().with_timezone(&self.timezone)
    }
}