use crate::{
grid::*,
ruleset::{enums::Nation, *},
};
use core::debug_assert;
use enum_map::Enum;
use rand::{SeedableRng, rngs::StdRng, seq::IndexedRandom};
use std::time::{SystemTime, UNIX_EPOCH};
pub struct MapParameters {
pub ruleset: Box<Ruleset>,
pub seed: u64,
pub map_type: MapType,
pub world_grid: WorldGrid,
pub world_size_type_profile: WorldSizeTypeProfile,
pub num_large_lakes: u32,
pub max_lake_area_size: u32,
pub coast_expand_chance: Vec<f64>,
pub sea_level: SeaLevel,
pub world_age: WorldAge,
pub temperature: Temperature,
pub rainfall: Rainfall,
pub enable_tectonic_islands: bool,
pub region_divide_method: RegionDivideMethod,
pub civilization_list: Vec<Nation>,
pub city_state_list: Vec<Nation>,
pub civ_require_coastal_land_start: bool,
pub disable_start_bias_of_civ: bool,
pub resource_setting: ResourceSetting,
}
impl MapParameters {
pub const MAX_CIVILIZATION_COUNT: u32 = 22;
pub const MAX_CITY_STATE_COUNT: u32 = 41;
pub const MAX_REGIONS_PER_EXCLUSIVE_LUXURY_TYPE: u32 = 3;
pub const NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_REGIONS: usize =
Self::MAX_CIVILIZATION_COUNT.div_ceil(Self::MAX_REGIONS_PER_EXCLUSIVE_LUXURY_TYPE) as usize;
pub const NUM_MAX_ALLOWED_LUXURY_TYPES_FOR_CITY_STATES: usize = 3;
}
pub struct MapParametersBuilder {
ruleset: Box<Ruleset>,
seed: u64,
world_grid: WorldGrid,
map_type: MapType,
world_size_type_profile: WorldSizeTypeProfile,
num_large_lakes: u32,
max_lake_area_size: u32,
coast_expand_chance: Vec<f64>,
sea_level: SeaLevel,
world_age: WorldAge,
temperature: Temperature,
rainfall: Rainfall,
enable_tectonic_islands: bool,
region_divide_method: RegionDivideMethod,
civilization_list: Vec<Nation>,
city_state_list: Vec<Nation>,
civ_require_coastal_land_start: bool,
disable_start_bias_of_civ: bool,
resource_setting: ResourceSetting,
}
impl MapParametersBuilder {
pub fn new(world_grid: WorldGrid) -> Self {
let ruleset = Box::new(Ruleset::default());
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
Self {
ruleset,
seed,
world_grid,
map_type: Default::default(),
world_size_type_profile: WorldSizeTypeProfile::from_world_size_type(
world_grid.world_size(),
),
num_large_lakes: 2,
max_lake_area_size: 9,
coast_expand_chance: vec![0.25, 0.25], sea_level: SeaLevel::Normal,
world_age: WorldAge::Normal,
temperature: Temperature::Normal,
rainfall: Rainfall::Normal,
enable_tectonic_islands: false,
region_divide_method: RegionDivideMethod::Continent,
civilization_list: vec![], city_state_list: vec![], civ_require_coastal_land_start: false,
disable_start_bias_of_civ: false,
resource_setting: ResourceSetting::Standard,
}
}
pub fn ruleset(mut self, ruleset: Ruleset) -> Self {
self.ruleset = Box::new(ruleset);
self
}
pub fn seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
pub fn map_type(mut self, map_type: MapType) -> Self {
self.map_type = map_type;
self
}
pub fn world_size_type_profile(mut self, profile: WorldSizeTypeProfile) -> Self {
self.world_size_type_profile = profile;
self
}
pub fn num_large_lakes(mut self, count: u32) -> Self {
self.num_large_lakes = count;
self
}
pub fn max_lake_area_size(mut self, size: u32) -> Self {
self.max_lake_area_size = size;
self
}
pub fn coast_expand_chance(mut self, chances: Vec<f64>) -> Self {
self.coast_expand_chance = chances;
self
}
pub fn sea_level(mut self, sea_level: SeaLevel) -> Self {
self.sea_level = sea_level;
self
}
pub fn world_age(mut self, age: WorldAge) -> Self {
self.world_age = age;
self
}
pub fn temperature(mut self, temperature: Temperature) -> Self {
self.temperature = temperature;
self
}
pub fn rainfall(mut self, rainfall: Rainfall) -> Self {
self.rainfall = rainfall;
self
}
pub fn enable_tectonic_islands(mut self, enable: bool) -> Self {
self.enable_tectonic_islands = enable;
self
}
pub fn region_divide_method(mut self, method: RegionDivideMethod) -> Self {
self.region_divide_method = method;
self
}
pub fn civilization_list(mut self, civ_list: Vec<Nation>) -> Self {
assert!(
civ_list.len() >= 2 && civ_list.len() <= MapParameters::MAX_CIVILIZATION_COUNT as usize
);
self.civilization_list = civ_list;
self
}
pub fn city_state_list(mut self, city_state_list: Vec<Nation>) -> Self {
assert!(
!city_state_list.is_empty()
&& city_state_list.len() <= MapParameters::MAX_CITY_STATE_COUNT as usize
);
self.city_state_list = city_state_list;
self
}
pub fn civ_require_coastal_land_start(mut self, require: bool) -> Self {
self.civ_require_coastal_land_start = require;
self
}
pub fn disable_start_bias_of_civ(mut self, disable: bool) -> Self {
self.disable_start_bias_of_civ = disable;
self
}
pub fn resource_setting(mut self, setting: ResourceSetting) -> Self {
self.resource_setting = setting;
self
}
pub fn build(self) -> MapParameters {
let mut rng = StdRng::seed_from_u64(self.seed);
let (num_civilizations, civilization_list) = if !self.civilization_list.is_empty() {
(self.civilization_list.len() as u32, self.civilization_list)
} else {
let num_civilizations = self.world_size_type_profile.num_civilizations;
let all_civilizations = (0..Nation::LENGTH)
.map(Nation::from_usize)
.filter(|&nation| {
matches!(
self.ruleset.nations[nation].nation_type,
NationType::Civilization
)
})
.collect::<Vec<_>>();
let civilization_list = all_civilizations
.sample(&mut rng, num_civilizations as usize)
.copied()
.collect();
(num_civilizations, civilization_list)
};
let (num_city_states, city_state_list) = if !self.city_state_list.is_empty() {
(self.city_state_list.len() as u32, self.city_state_list)
} else {
let num_city_states = self.world_size_type_profile.num_city_states;
let all_city_states = (0..Nation::LENGTH)
.map(Nation::from_usize)
.filter(|&nation| {
matches!(
self.ruleset.nations[nation].nation_type,
NationType::CityState(_)
)
})
.collect::<Vec<_>>();
let city_state_list = all_city_states
.sample(&mut rng, num_city_states as usize)
.copied()
.collect();
(num_city_states, city_state_list)
};
let world_size_type_profile = WorldSizeTypeProfile {
num_civilizations,
num_city_states,
..self.world_size_type_profile
};
MapParameters {
ruleset: self.ruleset,
map_type: self.map_type,
world_grid: self.world_grid,
seed: self.seed,
world_size_type_profile,
num_large_lakes: self.num_large_lakes,
max_lake_area_size: self.max_lake_area_size,
coast_expand_chance: self.coast_expand_chance,
sea_level: self.sea_level,
world_age: self.world_age,
temperature: self.temperature,
rainfall: self.rainfall,
enable_tectonic_islands: self.enable_tectonic_islands,
region_divide_method: self.region_divide_method,
civilization_list,
city_state_list,
civ_require_coastal_land_start: self.civ_require_coastal_land_start,
disable_start_bias_of_civ: self.disable_start_bias_of_civ,
resource_setting: self.resource_setting,
}
}
}
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct WorldGrid {
pub grid: HexGrid,
pub world_size_type: WorldSizeType,
}
impl WorldGrid {
pub fn new(grid: HexGrid, world_size: WorldSizeType) -> Self {
debug_assert!(
grid.world_size_type() == world_size,
"Grid size does not match the specified world size"
);
Self {
grid,
world_size_type: world_size,
}
}
pub fn from_grid(grid: HexGrid) -> Self {
let world_size = grid.world_size_type();
Self {
grid,
world_size_type: world_size,
}
}
pub fn size(&self) -> Size {
self.grid.size
}
pub fn world_size(&self) -> WorldSizeType {
self.world_size_type
}
}
impl Default for WorldGrid {
fn default() -> Self {
let world_size = WorldSizeType::Standard;
let grid = HexGrid {
size: HexGrid::default_size(world_size),
layout: HexLayout {
orientation: HexOrientation::Pointy,
size: [50., 50.],
origin: [0., 0.],
},
wrap_flags: WrapFlags::WrapX,
offset: Offset::Odd,
};
Self {
grid,
world_size_type: world_size,
}
}
}
#[derive(Default)]
pub enum MapType {
#[default]
Fractal,
Pangaea,
}
#[derive(Default)]
pub enum SeaLevel {
Low,
#[default]
Normal,
High,
Random,
}
#[derive(Default)]
pub enum WorldAge {
Old,
#[default]
Normal,
New,
}
#[derive(Default)]
pub enum Temperature {
Cool,
#[default]
Normal,
Hot,
}
#[derive(Default)]
pub enum Rainfall {
Arid,
#[default]
Normal,
Wet,
Random,
}
#[derive(Default)]
pub enum RegionDivideMethod {
Pangaea,
#[default]
Continent,
WholeMapRectangle,
CustomRectangle(Rectangle),
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum ResourceSetting {
Sparse,
#[default]
Standard,
Abundant,
LegendaryStart,
StrategicBalance,
}
pub struct WorldSizeTypeProfile {
pub num_civilizations: u32,
pub num_city_states: u32,
pub num_natural_wonders: u32,
pub max_religions: u32,
pub unhappiness_per_city: f32,
pub unhappiness_annexed: f32,
pub tech_cost_base: f32,
pub tech_cost_per_city: f32,
pub policy_cost_per_city: f32,
}
impl WorldSizeTypeProfile {
#[allow(clippy::too_many_arguments)]
pub fn new(
num_civilizations: u32,
num_city_states: u32,
num_natural_wonders: u32,
max_religions: u32,
unhappiness_per_city: f32,
unhappiness_annexed: f32,
tech_cost_base: f32,
tech_cost_per_city: f32,
policy_cost_per_city: f32,
) -> Self {
Self {
num_civilizations,
num_city_states,
num_natural_wonders,
max_religions,
unhappiness_per_city,
unhappiness_annexed,
tech_cost_base,
tech_cost_per_city,
policy_cost_per_city,
}
}
pub fn from_world_size_type(world_size_type: WorldSizeType) -> Self {
match world_size_type {
WorldSizeType::Duel => Self::new(2, 4, 2, 2, 3.0, 5.0, 1.0, 0.05, 0.10),
WorldSizeType::Tiny => Self::new(4, 8, 3, 3, 3.0, 5.0, 1.0, 0.05, 0.10),
WorldSizeType::Small => Self::new(6, 12, 4, 4, 3.0, 5.0, 1.0, 0.05, 0.10),
WorldSizeType::Standard => Self::new(8, 16, 5, 5, 3.0, 5.0, 1.1, 0.05, 0.10),
WorldSizeType::Large => Self::new(10, 20, 6, 6, 2.4, 4.0, 1.2, 0.03, 0.075),
WorldSizeType::Huge => Self::new(12, 24, 7, 7, 1.8, 3.0, 1.3, 0.02, 0.05),
}
}
}