use crate::{json_utils::*, ruleset::enums::*};
use enum_map::{Enum, EnumArray, EnumMap};
use serde::de::DeserializeOwned;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
pub mod enums;
mod base_terrain;
mod belief;
mod building;
mod city_state_type;
mod common;
mod difficulty;
mod era;
mod feature;
mod global_unique;
mod nation;
mod natural_wonder;
mod policy;
mod quest;
mod resource;
mod ruin;
mod specialist;
mod speed;
mod tech;
mod terrain_type;
mod tile_improvement;
mod unit;
mod unit_promotion;
mod unit_type;
mod victory_type;
pub use crate::ruleset::{
base_terrain::*, belief::*, building::*, city_state_type::*, common::*, difficulty::*, era::*,
feature::*, global_unique::*, nation::*, natural_wonder::*, policy::*, quest::*, resource::*,
ruin::*, specialist::*, speed::*, tech::*, terrain_type::*, tile_improvement::*, unit::*,
unit_promotion::*, unit_type::*, victory_type::*,
};
fn create_enum_map_from_json_file<M, T>(path: PathBuf) -> EnumMap<M, T>
where
M: EnumStr + EnumArray<T>,
T: DeserializeOwned,
{
let path_str = format!("{:?}", path);
let json_string_without_comment = load_json_file_and_strip_json_comments(path);
let items: Vec<T> = serde_json::from_str(&json_string_without_comment)
.unwrap_or_else(|_| panic!("Failed to parse JSON file:{:?}", path_str));
let mut items_iter = items.into_iter();
EnumMap::from_fn(|_| items_iter.next().expect("Not enough items in JSON file"))
}
#[derive(Debug)]
pub struct Ruleset {
pub terrain_types: EnumMap<TerrainType, TerrainTypeInfo>,
pub base_terrains: EnumMap<BaseTerrain, BaseTerrainInfo>,
pub features: EnumMap<Feature, FeatureInfo>,
pub natural_wonders: EnumMap<NaturalWonder, NaturalWonderInfo>,
pub resources: EnumMap<Resource, ResourceInfo>,
pub ruins: EnumMap<Ruin, RuinInfo>,
pub tile_improvements: EnumMap<TileImprovement, TileImprovementInfo>,
pub buildings: EnumMap<Building, BuildingInfo>,
pub specialists: EnumMap<Specialist, SpecialistInfo>,
pub units: EnumMap<Unit, UnitInfo>,
pub unit_promotions: EnumMap<UnitPromotion, UnitPromotionInfo>,
pub unit_types: EnumMap<UnitType, UnitTypeInfo>,
pub beliefs: EnumMap<Belief, BeliefInfo>,
pub nations: EnumMap<Nation, NationInfo>,
pub city_state_types: EnumMap<CityStateType, CityStateTypeInfo>,
pub policy_branches: EnumMap<PolicyBranch, PolicyBranchInfo>,
pub policies: EnumMap<Policy, PolicyInfo>,
pub technologies: EnumMap<Technology, TechnologyInfo>,
pub quests: EnumMap<Quest, QuestInfo>,
pub difficulties: EnumMap<Difficulty, DifficultyInfo>,
pub speeds: EnumMap<Speed, SpeedInfo>,
pub eras: EnumMap<Era, EraInfo>,
pub victory_types: EnumMap<VictoryType, VictoryTypeInfo>,
pub global_uniques: GlobalUnique,
pub religions: Vec<Religion>,
}
impl Default for Ruleset {
fn default() -> Self {
let ruleset_json_folder =
Path::new(env!("CARGO_MANIFEST_DIR")).join("src/jsons/Civ V - Gods & Kings");
Self::new(ruleset_json_folder)
}
}
impl Ruleset {
pub fn new(ruleset_json_folder: PathBuf) -> Self {
let terrain_types: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("TerrainType.json"));
let base_terrains: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("BaseTerrain.json"));
let features: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Feature.json"));
let natural_wonders: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("NaturalWonder.json"));
let resources: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Resource.json"));
let ruins: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Ruin.json"));
let tile_improvements: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("TileImprovement.json"));
let specialists: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Specialist.json"));
let mut units: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Unit.json"));
units.values_mut().for_each(|unit_info: &mut UnitInfo| {
let is_range = unit_info.ranged_strength > 0;
if is_range && unit_info.range == 0 {
unit_info.range = 2;
}
if !is_range && unit_info.strength > 0 {
unit_info.range = 1;
}
});
let unit_promotions: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("UnitPromotion.json"));
let unit_types: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("UnitType.json"));
let beliefs: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Belief.json"));
let mut buildings: EnumMap<_, BuildingInfo> =
create_enum_map_from_json_file(ruleset_json_folder.join("Building.json"));
let difficulties: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Difficulty.json"));
let eras: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Era.json"));
let nations: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Nation.json"));
let city_state_types: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("CityStateType.json"));
let policy_branches: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("PolicyBranch.json"));
let quests: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Quest.json"));
let victory_types: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("VictoryType.json"));
let speeds: EnumMap<_, _> =
create_enum_map_from_json_file(ruleset_json_folder.join("Speed.json"));
let religions: Vec<Religion> = (0..Religion::LENGTH).map(Religion::from_usize).collect();
let json_string_without_comment =
load_json_file_and_strip_json_comments(ruleset_json_folder.join("GlobalUnique.json"));
let global_uniques: GlobalUnique =
serde_json::from_str(&json_string_without_comment).unwrap();
let json_string_without_comment =
load_json_file_and_strip_json_comments(ruleset_json_folder.join("Technology.json"));
let mut tech_columnes: Vec<TechColumn> = serde_json::from_str(&json_string_without_comment)
.expect("Failed to parse Technology.json");
let mut tech_and_wonder_or_building_cost = HashMap::new();
tech_columnes.iter_mut().for_each(|tech_column| {
for technology in tech_column.techs.iter_mut() {
if technology.cost == 0 {
technology.cost = tech_column.tech_cost;
}
technology.column = tech_column.column_number;
technology.era = tech_column.era.clone();
tech_and_wonder_or_building_cost.insert(
&technology.name,
(tech_column.wonder_cost, tech_column.building_cost),
);
}
});
for building in buildings.values_mut() {
if building.cost != 0
|| building
.required_terrain
.extra_conditions
.iter()
.any(|condition| condition == "Unbuildable")
{
continue;
}
let Some(&(wonder_cost, building_cost)) =
tech_and_wonder_or_building_cost.get(&building.required_tech)
else {
unreachable!(
"Building {} requires tech {}, which is not in the tech column",
building.name, building.required_tech
);
};
building.cost = if building.is_wonder || building.is_national_wonder {
wonder_cost
} else {
building_cost
};
}
let mut technology_info_iter = tech_columnes.into_iter().flat_map(|x| x.techs);
let technologies: EnumMap<Technology, TechnologyInfo> = EnumMap::from_fn(|_| {
technology_info_iter
.next()
.expect("Not enough items in JSON file")
});
let mut policy_info_iter = policy_branches
.values()
.flat_map(|policy_branch: &PolicyBranchInfo| policy_branch.policies.clone());
let policies: EnumMap<Policy, PolicyInfo> = EnumMap::from_fn(|_| {
policy_info_iter
.next()
.expect("Not enough items in JSON file")
});
Self {
terrain_types,
base_terrains,
features,
natural_wonders,
resources,
ruins,
tile_improvements,
buildings,
specialists,
units,
unit_promotions,
unit_types,
religions,
beliefs,
nations,
city_state_types,
policy_branches,
policies,
technologies,
quests,
difficulties,
speeds,
victory_types,
eras,
global_uniques,
}
}
}