use std::cmp::Ordering;
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display};
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct SCXVersion(pub(crate) [u8; 4]);
impl SCXVersion {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub(crate) fn to_player_version(self) -> Option<f32> {
match self.as_bytes() {
b"1.07" => Some(1.07),
b"1.09" | b"1.10" | b"1.11" => Some(1.11),
b"1.12" | b"1.13" | b"1.14" | b"1.15" | b"1.16" => Some(1.12),
b"1.18" | b"1.19" => Some(1.13),
b"1.20" | b"1.21" | b"1.32" | b"1.36" | b"1.37" => Some(1.14),
_ => None,
}
}
}
impl Default for SCXVersion {
fn default() -> Self {
Self(*b"1.21")
}
}
impl Debug for SCXVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", std::str::from_utf8(&self.0).unwrap())
}
}
impl Display for SCXVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", std::str::from_utf8(&self.0).unwrap())
}
}
impl PartialEq<[u8; 4]> for SCXVersion {
fn eq(&self, other: &[u8; 4]) -> bool {
other[0] == self.0[0] && other[1] == b'.' && other[2] == self.0[2] && other[3] == self.0[3]
}
}
impl PartialEq<SCXVersion> for [u8; 4] {
fn eq(&self, other: &SCXVersion) -> bool {
other == self
}
}
impl Ord for SCXVersion {
fn cmp(&self, other: &SCXVersion) -> Ordering {
match self.0[0].cmp(&other.0[0]) {
Ordering::Equal => {}
ord => return ord,
}
match self.0[2].cmp(&other.0[2]) {
Ordering::Equal => {}
ord => return ord,
}
self.0[3].cmp(&other.0[3])
}
}
impl PartialOrd for SCXVersion {
fn partial_cmp(&self, other: &SCXVersion) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("invalid diplomatic stance {} (must be 0/1/3)", .0)]
pub struct ParseDiplomaticStanceError(i32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiplomaticStance {
Ally = 0,
Neutral = 1,
Enemy = 3,
}
impl TryFrom<i32> for DiplomaticStance {
type Error = ParseDiplomaticStanceError;
fn try_from(n: i32) -> Result<Self, Self::Error> {
match n {
0 => Ok(DiplomaticStance::Ally),
1 => Ok(DiplomaticStance::Neutral),
3 => Ok(DiplomaticStance::Enemy),
n => Err(ParseDiplomaticStanceError(n)),
}
}
}
impl From<DiplomaticStance> for i32 {
fn from(stance: DiplomaticStance) -> i32 {
match stance {
DiplomaticStance::Ally => 0,
DiplomaticStance::Neutral => 1,
DiplomaticStance::Enemy => 3,
}
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("invalid data set {} (must be 0/1)", .0)]
pub struct ParseDataSetError(i32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataSet {
BaseGame,
Expansions,
}
impl TryFrom<i32> for DataSet {
type Error = ParseDataSetError;
fn try_from(n: i32) -> Result<Self, Self::Error> {
match n {
0 => Ok(DataSet::BaseGame),
1 => Ok(DataSet::Expansions),
n => Err(ParseDataSetError(n)),
}
}
}
impl From<DataSet> for i32 {
fn from(id: DataSet) -> i32 {
match id {
DataSet::BaseGame => 0,
DataSet::Expansions => 1,
}
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("unknown dlc package {}", .0)]
pub struct ParseDLCPackageError(i32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DLCPackage {
AgeOfKings,
AgeOfConquerors,
TheForgotten,
AfricanKingdoms,
RiseOfTheRajas,
LastKhans,
}
impl TryFrom<i32> for DLCPackage {
type Error = ParseDLCPackageError;
fn try_from(n: i32) -> Result<Self, Self::Error> {
match n {
2 => Ok(DLCPackage::AgeOfKings),
3 => Ok(DLCPackage::AgeOfConquerors),
4 => Ok(DLCPackage::TheForgotten),
5 => Ok(DLCPackage::AfricanKingdoms),
6 => Ok(DLCPackage::RiseOfTheRajas),
7 => Ok(DLCPackage::LastKhans),
n => Err(ParseDLCPackageError(n)),
}
}
}
impl From<DLCPackage> for i32 {
fn from(dlc_id: DLCPackage) -> i32 {
match dlc_id {
DLCPackage::AgeOfKings => 2,
DLCPackage::AgeOfConquerors => 3,
DLCPackage::TheForgotten => 4,
DLCPackage::AfricanKingdoms => 5,
DLCPackage::RiseOfTheRajas => 6,
DLCPackage::LastKhans => 7,
}
}
}
fn expected_range(version: f32) -> &'static str {
if version < 1.25 {
"-1-4"
} else {
"-1-6"
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("invalid starting age {} (must be {})", .found, expected_range(*.version))]
pub struct ParseStartingAgeError {
version: f32,
found: i32,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum StartingAge {
Default = -1,
Nomad = -2,
DarkAge = 0,
FeudalAge = 1,
CastleAge = 2,
ImperialAge = 3,
PostImperialAge = 4,
}
impl StartingAge {
pub fn try_from(n: i32, version: f32) -> Result<Self, ParseStartingAgeError> {
if version < 1.25 {
match n {
-1 => Ok(StartingAge::Default),
0 => Ok(StartingAge::DarkAge),
1 => Ok(StartingAge::FeudalAge),
2 => Ok(StartingAge::CastleAge),
3 => Ok(StartingAge::ImperialAge),
4 => Ok(StartingAge::PostImperialAge),
_ => Err(ParseStartingAgeError { version, found: n }),
}
} else {
match n {
-1 | 0 => Ok(StartingAge::Default),
1 => Ok(StartingAge::Nomad),
2 => Ok(StartingAge::DarkAge),
3 => Ok(StartingAge::FeudalAge),
4 => Ok(StartingAge::CastleAge),
5 => Ok(StartingAge::ImperialAge),
6 => Ok(StartingAge::PostImperialAge),
_ => Err(ParseStartingAgeError { version, found: n }),
}
}
}
pub fn to_i32(self, version: f32) -> i32 {
if version < 1.25 {
match self {
StartingAge::Default => -1,
StartingAge::Nomad | StartingAge::DarkAge => 0,
StartingAge::FeudalAge => 1,
StartingAge::CastleAge => 2,
StartingAge::ImperialAge => 3,
StartingAge::PostImperialAge => 4,
}
} else {
match self {
StartingAge::Default => 0,
StartingAge::Nomad => 1,
StartingAge::DarkAge => 2,
StartingAge::FeudalAge => 3,
StartingAge::CastleAge => 4,
StartingAge::ImperialAge => 5,
StartingAge::PostImperialAge => 6,
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VictoryCondition {
Capture,
Create,
Destroy,
DestroyMultiple,
BringToArea,
BringToObject,
Attribute,
Explore,
CreateInArea,
DestroyAll,
DestroyPlayer,
Points,
Other(u8),
}
impl From<u8> for VictoryCondition {
fn from(n: u8) -> Self {
match n {
0 => VictoryCondition::Capture,
1 => VictoryCondition::Create,
2 => VictoryCondition::Destroy,
3 => VictoryCondition::DestroyMultiple,
4 => VictoryCondition::BringToArea,
5 => VictoryCondition::BringToObject,
6 => VictoryCondition::Attribute,
7 => VictoryCondition::Explore,
8 => VictoryCondition::CreateInArea,
9 => VictoryCondition::DestroyAll,
10 => VictoryCondition::DestroyPlayer,
11 => VictoryCondition::Points,
n => VictoryCondition::Other(n),
}
}
}
impl From<VictoryCondition> for u8 {
fn from(condition: VictoryCondition) -> Self {
match condition {
VictoryCondition::Capture => 0,
VictoryCondition::Create => 1,
VictoryCondition::Destroy => 2,
VictoryCondition::DestroyMultiple => 3,
VictoryCondition::BringToArea => 4,
VictoryCondition::BringToObject => 5,
VictoryCondition::Attribute => 6,
VictoryCondition::Explore => 7,
VictoryCondition::CreateInArea => 8,
VictoryCondition::DestroyAll => 9,
VictoryCondition::DestroyPlayer => 10,
VictoryCondition::Points => 11,
VictoryCondition::Other(n) => n,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VersionBundle {
pub format: SCXVersion,
pub header: u32,
pub dlc_options: Option<i32>,
pub data: f32,
pub picture: u32,
pub victory: f32,
pub triggers: Option<f64>,
pub map: u32,
}
impl VersionBundle {
pub fn aoe() -> Self {
unimplemented!()
}
pub fn ror() -> Self {
Self {
format: SCXVersion(*b"1.11"),
header: 2,
dlc_options: None,
data: 1.15,
picture: 1,
victory: 2.0,
triggers: None,
map: 0,
}
}
pub fn aok() -> Self {
Self {
format: SCXVersion(*b"1.18"),
header: 2,
dlc_options: None,
data: 1.2,
picture: 1,
victory: 2.0,
triggers: Some(1.6),
map: 0,
}
}
pub fn aoc() -> Self {
Self {
format: SCXVersion(*b"1.21"),
header: 2,
dlc_options: None,
data: 1.22,
picture: 1,
victory: 2.0,
triggers: Some(1.6),
map: 0,
}
}
pub fn userpatch_14() -> Self {
Self::aoc()
}
pub fn userpatch_15() -> Self {
Self::userpatch_14()
}
pub fn hd_edition() -> Self {
Self {
format: SCXVersion(*b"1.21"),
header: 3,
dlc_options: Some(1000),
data: 1.26,
picture: 3,
victory: 2.0,
triggers: Some(1.6),
map: 0,
}
}
pub fn aoe2_de() -> Self {
Self {
format: SCXVersion(*b"1.37"),
header: 5,
dlc_options: Some(1000),
data: 1.37,
picture: 3,
victory: 2.0,
triggers: Some(2.2),
map: 2,
}
}
pub fn is_aok(&self) -> bool {
match self.format.as_bytes() {
b"1.18" | b"1.19" | b"1.20" => true,
_ => false,
}
}
pub fn is_aoc(&self) -> bool {
self.format == *b"1.21" && self.data <= 1.22
}
pub fn is_hd_edition(&self) -> bool {
self.format == *b"1.21" || self.format == *b"1.22" && self.data > 1.22
}
pub fn is_age2_de(&self) -> bool {
self.data >= 1.28
}
}