pub mod academy;
pub mod farm;
pub mod iron_mine;
pub mod prefecture;
pub mod quarry;
pub mod sawmill;
pub mod silo;
pub mod stable;
pub mod wall;
pub mod warehouse;
pub mod workshop;
use crate::error::{Error, Result};
use crate::infrastructure::requirements::InfrastructureRequirements;
use crate::ranking::score::Score;
use crate::resources::prelude::*;
use derive_more::{Deref, Into};
use nil_num::growth::growth;
use serde::{Deserialize, Serialize};
use std::cmp;
use std::collections::HashMap;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use strum::{EnumIs, EnumIter};
use subenum::subenum;
pub trait Building: Send + Sync {
fn id(&self) -> BuildingId;
fn is_enabled(&self) -> bool;
fn toggle(&mut self, enabled: bool);
fn level(&self) -> BuildingLevel;
fn min_level(&self) -> BuildingLevel;
fn max_level(&self) -> BuildingLevel;
fn set_level(&mut self, level: BuildingLevel);
fn set_min_level(&mut self) {
self.set_level(self.min_level());
}
fn set_max_level(&mut self) {
self.set_level(self.max_level());
}
fn increase_level(&mut self) {
self.increase_level_by(1);
}
fn increase_level_by(&mut self, amount: u8);
fn decrease_level(&mut self) {
self.decrease_level_by(1);
}
fn decrease_level_by(&mut self, amount: u8);
fn is_min_level(&self) -> bool {
self.level() == self.min_level()
}
fn is_max_level(&self) -> bool {
self.level() >= self.max_level()
}
fn min_cost(&self) -> Cost;
fn max_cost(&self) -> Cost;
fn wood_ratio(&self) -> ResourceRatio;
fn stone_ratio(&self) -> ResourceRatio;
fn iron_ratio(&self) -> ResourceRatio;
fn maintenance(&self, stats: &BuildingStatsTable) -> Result<Maintenance>;
fn maintenance_ratio(&self) -> MaintenanceRatio;
fn min_workforce(&self) -> Workforce;
fn max_workforce(&self) -> Workforce;
fn score(&self, stats: &BuildingStatsTable) -> Result<Score>;
fn min_score(&self) -> Score;
fn max_score(&self) -> Score;
fn infrastructure_requirements(&self) -> &InfrastructureRequirements;
fn is_civil(&self) -> bool {
self.id().is_civil()
}
fn is_military(&self) -> bool {
self.id().is_military()
}
fn is_mine(&self) -> bool {
self.id().is_mine()
}
fn is_storage(&self) -> bool {
self.id().is_storage()
}
}
#[subenum(CivilBuildingId, MilitaryBuildingId, MineId, StorageId)]
#[derive(
Clone, Copy, Debug, strum::Display, EnumIs, EnumIter, PartialEq, Eq, Hash, Deserialize, Serialize,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum BuildingId {
#[subenum(MilitaryBuildingId)]
Academy,
#[subenum(CivilBuildingId, MineId)]
Farm,
#[subenum(CivilBuildingId, MineId)]
IronMine,
#[subenum(CivilBuildingId)]
Prefecture,
#[subenum(CivilBuildingId, MineId)]
Quarry,
#[subenum(CivilBuildingId, MineId)]
Sawmill,
#[subenum(CivilBuildingId, StorageId)]
Silo,
#[subenum(MilitaryBuildingId)]
Stable,
Wall,
#[subenum(CivilBuildingId, StorageId)]
Warehouse,
#[subenum(MilitaryBuildingId)]
Workshop,
}
impl BuildingId {
#[inline]
pub fn is_civil(self) -> bool {
CivilBuildingId::try_from(self).is_ok()
}
#[inline]
pub fn is_military(self) -> bool {
MilitaryBuildingId::try_from(self).is_ok()
}
#[inline]
pub fn is_mine(self) -> bool {
MineId::try_from(self).is_ok()
}
#[inline]
pub fn is_storage(self) -> bool {
StorageId::try_from(self).is_ok()
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildingStats {
pub level: BuildingLevel,
pub cost: Cost,
pub resources: Resources,
pub maintenance: Maintenance,
pub workforce: Workforce,
pub score: Score,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildingStatsTable {
id: BuildingId,
min_level: BuildingLevel,
max_level: BuildingLevel,
table: HashMap<BuildingLevel, BuildingStats>,
}
impl BuildingStatsTable {
pub(crate) fn new(building: &dyn Building) -> Self {
let min_level = building.min_level();
let max_level = building.max_level();
let mut table = HashMap::with_capacity((max_level.0).into());
let mut cost = f64::from(building.min_cost());
let cost_growth = growth()
.floor(cost)
.ceil(building.max_cost())
.max_level(max_level)
.call();
let mut workforce = f64::from(building.min_workforce());
let workforce_growth = growth()
.floor(workforce)
.ceil(building.max_workforce())
.max_level(max_level)
.call();
let mut score = f64::from(building.min_score());
let score_growth = growth()
.floor(score)
.ceil(building.max_score())
.max_level(max_level)
.call();
let wood_ratio = *building.wood_ratio();
let stone_ratio = *building.stone_ratio();
let iron_ratio = *building.iron_ratio();
let maintenance_ratio = *building.maintenance_ratio();
let mut maintenance = cost * maintenance_ratio;
for level in 1..=max_level.0 {
let level = BuildingLevel::new(level);
let resources = Resources {
food: Food::MIN,
iron: Iron::from((cost * iron_ratio).round()),
stone: Stone::from((cost * stone_ratio).round()),
wood: Wood::from((cost * wood_ratio).round()),
};
table.insert(
level,
BuildingStats {
level,
cost: Cost::from(cost.round()),
resources,
maintenance: Maintenance::from(maintenance.round()),
workforce: Workforce::from(workforce.round()),
score: Score::from(score.round()),
},
);
debug_assert!(cost.is_normal());
debug_assert!(workforce.is_normal());
debug_assert!(maintenance.is_finite());
debug_assert!(maintenance >= 0.0);
debug_assert!(score.is_finite());
debug_assert!(score >= 0.0);
cost += cost * cost_growth;
workforce += workforce * workforce_growth;
score += score * score_growth;
maintenance = cost * maintenance_ratio;
}
table.shrink_to_fit();
Self {
id: building.id(),
min_level,
max_level,
table,
}
}
#[inline]
pub fn id(&self) -> BuildingId {
self.id
}
#[inline]
pub fn min_level(&self) -> BuildingLevel {
self.min_level
}
#[inline]
pub fn max_level(&self) -> BuildingLevel {
self.max_level
}
#[inline]
pub fn get(&self, level: BuildingLevel) -> Result<&BuildingStats> {
self
.table
.get(&level)
.ok_or(Error::BuildingStatsNotFoundForLevel(self.id, level))
}
}
#[derive(
Clone,
Copy,
Debug,
Default,
Deref,
derive_more::Display,
Into,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Deserialize,
Serialize,
nil_num::F64Ops,
)]
#[into(i16, i32, u8, u16, u32, u64, usize, f64)]
pub struct BuildingLevel(u8);
impl BuildingLevel {
pub const ZERO: BuildingLevel = BuildingLevel(0);
#[inline]
pub const fn new(level: u8) -> Self {
Self(level)
}
}
impl From<BuildingLevel> for i8 {
fn from(level: BuildingLevel) -> Self {
debug_assert!(i8::try_from(level.0).is_ok());
i8::try_from(level.0).unwrap_or(i8::MAX)
}
}
impl PartialEq<u8> for BuildingLevel {
fn eq(&self, other: &u8) -> bool {
self.0.eq(other)
}
}
impl PartialEq<BuildingLevel> for u8 {
fn eq(&self, other: &BuildingLevel) -> bool {
self.eq(&other.0)
}
}
impl PartialEq<f64> for BuildingLevel {
fn eq(&self, other: &f64) -> bool {
f64::from(self.0).eq(other)
}
}
impl PartialEq<BuildingLevel> for f64 {
fn eq(&self, other: &BuildingLevel) -> bool {
self.eq(&f64::from(other.0))
}
}
impl PartialOrd<u8> for BuildingLevel {
fn partial_cmp(&self, other: &u8) -> Option<cmp::Ordering> {
self.0.partial_cmp(other)
}
}
impl PartialOrd<BuildingLevel> for u8 {
fn partial_cmp(&self, other: &BuildingLevel) -> Option<cmp::Ordering> {
self.partial_cmp(&other.0)
}
}
impl PartialOrd<f64> for BuildingLevel {
fn partial_cmp(&self, other: &f64) -> Option<cmp::Ordering> {
f64::from(self.0).partial_cmp(other)
}
}
impl PartialOrd<BuildingLevel> for f64 {
fn partial_cmp(&self, other: &BuildingLevel) -> Option<cmp::Ordering> {
self.partial_cmp(&f64::from(other.0))
}
}
impl Add for BuildingLevel {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0.saturating_add(rhs.0))
}
}
impl Add<u8> for BuildingLevel {
type Output = Self;
fn add(self, rhs: u8) -> Self {
Self(self.0.saturating_add(rhs))
}
}
impl Add<i8> for BuildingLevel {
type Output = Self;
fn add(self, rhs: i8) -> Self {
Self(self.0.saturating_add_signed(rhs))
}
}
impl Add<BuildingLevelDiff> for BuildingLevel {
type Output = Self;
fn add(self, rhs: BuildingLevelDiff) -> Self {
self + rhs.0
}
}
impl AddAssign for BuildingLevel {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl AddAssign<u8> for BuildingLevel {
fn add_assign(&mut self, rhs: u8) {
*self = *self + rhs;
}
}
impl AddAssign<i8> for BuildingLevel {
fn add_assign(&mut self, rhs: i8) {
*self = *self + rhs;
}
}
impl AddAssign<BuildingLevelDiff> for BuildingLevel {
fn add_assign(&mut self, rhs: BuildingLevelDiff) {
*self = *self + rhs;
}
}
impl Sub for BuildingLevel {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0.saturating_sub(rhs.0))
}
}
impl Sub<u8> for BuildingLevel {
type Output = Self;
fn sub(self, rhs: u8) -> Self {
Self(self.0.saturating_sub(rhs))
}
}
impl Sub<i8> for BuildingLevel {
type Output = Self;
fn sub(self, rhs: i8) -> Self {
Self(self.0.saturating_sub_signed(rhs))
}
}
impl Sub<BuildingLevelDiff> for BuildingLevel {
type Output = Self;
fn sub(self, rhs: BuildingLevelDiff) -> Self {
self - rhs.0
}
}
impl SubAssign for BuildingLevel {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl SubAssign<u8> for BuildingLevel {
fn sub_assign(&mut self, rhs: u8) {
*self = *self - rhs;
}
}
impl SubAssign<i8> for BuildingLevel {
fn sub_assign(&mut self, rhs: i8) {
*self = *self - rhs;
}
}
impl SubAssign<BuildingLevelDiff> for BuildingLevel {
fn sub_assign(&mut self, rhs: BuildingLevelDiff) {
*self = *self - rhs;
}
}
impl Neg for BuildingLevel {
type Output = BuildingLevelDiff;
fn neg(self) -> BuildingLevelDiff {
BuildingLevelDiff::new(i8::from(self).neg())
}
}
#[derive(
Clone,
Copy,
Debug,
Default,
Deref,
derive_more::Display,
Into,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Deserialize,
Serialize,
)]
pub struct BuildingLevelDiff(i8);
impl BuildingLevelDiff {
pub const ZERO: BuildingLevelDiff = BuildingLevelDiff(0);
#[inline]
pub const fn new(level_diff: i8) -> Self {
Self(level_diff)
}
}
impl From<f64> for BuildingLevelDiff {
fn from(mut value: f64) -> Self {
value = value.round();
debug_assert!(value.is_finite());
debug_assert!(value >= f64::from(i8::MIN));
debug_assert!(value <= f64::from(i8::MAX));
Self::new(value as i8)
}
}
impl PartialEq<i8> for BuildingLevelDiff {
fn eq(&self, other: &i8) -> bool {
self.0.eq(other)
}
}
impl PartialOrd<i8> for BuildingLevelDiff {
fn partial_cmp(&self, other: &i8) -> Option<cmp::Ordering> {
self.0.partial_cmp(other)
}
}
#[macro_export]
macro_rules! lv {
($level:expr) => {
const { $crate::infrastructure::building::BuildingLevel::new($level) }
};
}
#[macro_export]
macro_rules! with_random_level {
($building:ident) => {{ $crate::infrastructure::prelude::$building::with_random_level() }};
($building:ident, $max:expr) => {{
$crate::infrastructure::prelude::$building::with_random_level_in()
.max($crate::lv!($max))
.call()
}};
($building:ident, $min:expr, $max:expr) => {{
$crate::infrastructure::prelude::$building::with_random_level_in()
.min($crate::lv!($min))
.max($crate::lv!($max))
.call()
}};
}