mod availability;
mod force;
mod player;
mod random_tables;
mod version;
pub use availability::{TechAvailabilityChange, UpgradeAvailabilityChange};
pub use force::Force;
pub use player::Player;
pub use random_tables::{RandomItem, RandomItemSet, RandomItemTable, RandomUnit, RandomUnitTable};
pub use version::FormatVersion;
use crate::error::Result;
use crate::reader::{parse_counted, ByteReader};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct LegacyV8Fields {
pub unk1: f32,
pub unk2: i32,
pub unk3: f32,
pub unk4: f32,
pub unk5: f32,
pub unk6: i32,
pub unk7: i32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct War3MapW3i {
pub version: FormatVersion,
pub saves: Option<u32>,
pub editor_version: Option<u32>,
pub build_version: Option<[u32; 4]>,
pub name: String,
pub author: String,
pub description: String,
pub recommended_players: String,
pub legacy_v8: Option<LegacyV8Fields>,
pub camera_bounds: [f32; 8],
pub camera_bounds_complements: Option<[i32; 4]>,
pub playable_size: [i32; 2],
pub flags: u32,
pub tileset: u8,
pub loading_screen_background: Option<i32>,
pub campaign_background: Option<i32>,
pub loading_screen_model: Option<String>,
pub loading_screen_text: Option<String>,
pub loading_screen_title: Option<String>,
pub loading_screen_subtitle: Option<String>,
pub game_data_set: Option<i32>,
pub loading_screen_index: Option<i32>,
pub prologue_screen_model: Option<String>,
pub prologue_screen_text: Option<String>,
pub prologue_screen_title: Option<String>,
pub prologue_screen_subtitle: Option<String>,
pub fog_style: Option<i32>,
pub fog_height: Option<[f32; 2]>,
pub fog_density: Option<f32>,
pub fog_color: Option<[u8; 4]>,
pub global_weather: Option<i32>,
pub sound_environment: Option<String>,
pub light_environment_tileset: Option<u8>,
pub water_vertex_color: Option<[u8; 4]>,
pub script_mode: Option<u32>,
pub graphics_mode: Option<u32>,
pub game_data_version: Option<u32>,
pub default_camera_zoom: Option<i32>,
pub max_camera_zoom: Option<i32>,
pub min_camera_zoom: Option<i32>,
pub skipped_optional_sections: bool,
pub players: Vec<Player>,
pub forces: Vec<Force>,
pub upgrade_availability_changes: Vec<UpgradeAvailabilityChange>,
pub tech_availability_changes: Vec<TechAvailabilityChange>,
pub random_unit_tables: Vec<RandomUnitTable>,
pub random_item_tables: Vec<RandomItemTable>,
}
impl War3MapW3i {
pub fn parse(data: &[u8]) -> Result<Self> {
Self::parse_reader(&mut ByteReader::new(data))
}
fn parse_reader(r: &mut ByteReader<'_>) -> Result<Self> {
use FormatVersion as V;
let version = V(r.u32()?);
let v = version;
let (saves, editor_version) = if v >= V::V18 {
(Some(r.u32()?), Some(r.u32()?))
} else {
(None, None)
};
let build_version = (v >= V::V27).then(|| r.u32s()).transpose()?;
let name = r.cstr_lossy()?;
let author = r.cstr_lossy()?;
let description = r.cstr_lossy()?;
let recommended_players = r.cstr_lossy()?;
let mut legacy_v8 = (v == V::V8)
.then(|| -> Result<LegacyV8Fields> {
Ok(LegacyV8Fields {
unk1: r.f32()?,
unk2: r.i32()?,
unk3: r.f32()?,
unk4: r.f32()?,
unk5: r.f32()?,
unk6: r.i32()?,
unk7: 0,
})
})
.transpose()?;
let camera_bounds = r.f32s()?;
let camera_bounds_complements = (v >= V::V15).then(|| r.i32s()).transpose()?;
let playable_size = r.i32s()?;
if let Some(legacy) = legacy_v8.as_mut() {
legacy.unk7 = r.i32()?;
}
let flags = r.u32()?;
let tileset = r.u8()?;
let (loading_screen_background, campaign_background, loading_screen_model) = if v >= V::V23
{
(Some(r.i32()?), None, Some(r.cstr_lossy()?))
} else if v >= V::V18 {
(None, Some(r.i32()?), None)
} else if v >= V::V15 {
(None, None, Some(r.cstr_lossy()?))
} else {
(None, None, None)
};
let mut loading_screen_text = None;
let mut loading_screen_title = None;
let mut loading_screen_subtitle = None;
let mut game_data_set = None;
let mut loading_screen_index = None;
let mut prologue_screen_model = None;
let mut prologue_screen_text = None;
let mut prologue_screen_title = None;
let mut prologue_screen_subtitle = None;
let mut fog_style = None;
let mut fog_height = None;
let mut fog_density = None;
let mut fog_color = None;
let mut global_weather = None;
let mut sound_environment = None;
let mut light_environment_tileset = None;
let mut water_vertex_color = None;
let mut script_mode = None;
let mut graphics_mode = None;
let mut game_data_version = None;
let mut default_camera_zoom = None;
let mut max_camera_zoom = None;
let mut min_camera_zoom = None;
if v >= V::V10 {
loading_screen_text = Some(r.cstr_lossy()?);
loading_screen_title = Some(r.cstr_lossy()?);
if v >= V::V15 {
loading_screen_subtitle = Some(r.cstr_lossy()?);
}
if v >= V::V23 {
game_data_set = Some(r.i32()?);
prologue_screen_model = Some(r.cstr_lossy()?);
} else if v >= V::V18 {
loading_screen_index = Some(r.i32()?);
} else if v >= V::V15 {
prologue_screen_model = Some(r.cstr_lossy()?);
}
if v >= V::V11 {
prologue_screen_text = Some(r.cstr_lossy()?);
prologue_screen_title = Some(r.cstr_lossy()?);
if v >= V::V15 {
prologue_screen_subtitle = Some(r.cstr_lossy()?);
}
}
if v >= V::V23 {
fog_style = Some(r.i32()?);
fog_height = Some(r.f32s()?);
fog_density = Some(r.f32()?);
fog_color = Some(r.bytes()?);
if v >= V::V25 {
global_weather = Some(r.i32()?);
}
sound_environment = Some(r.cstr_lossy()?);
light_environment_tileset = Some(r.u8()?);
water_vertex_color = Some(r.bytes()?);
}
if v >= V::V28 {
script_mode = Some(r.u32()?);
}
if v >= V::V31 {
graphics_mode = Some(r.u32()?);
game_data_version = Some(r.u32()?);
}
if v >= V::V32 {
default_camera_zoom = Some(r.i32()?);
max_camera_zoom = Some(r.i32()?);
}
if v >= V::V33 {
min_camera_zoom = Some(r.i32()?);
}
}
let players = parse_counted(r, |r| Player::parse(r, v))?;
let forces = parse_counted(r, Force::parse)?;
let mut skipped_optional_sections = false;
let mut upgrade_availability_changes = Vec::new();
let mut tech_availability_changes = Vec::new();
let mut random_unit_tables = Vec::new();
let mut random_item_tables = Vec::new();
if r.peek_u8() == Some(0xFF) {
r.skip(1)?;
skipped_optional_sections = true;
} else {
upgrade_availability_changes = parse_counted(r, UpgradeAvailabilityChange::parse)?;
if !r.is_at_end() {
tech_availability_changes = parse_counted(r, TechAvailabilityChange::parse)?;
if v >= V::V15 {
random_unit_tables = parse_counted(r, RandomUnitTable::parse)?;
}
if v >= V::V24 {
random_item_tables = parse_counted(r, RandomItemTable::parse)?;
}
if v >= V::V26 && v < V::V28 && r.remaining() >= 4 {
let _ = r.i32()?;
}
}
}
Ok(Self {
version,
saves,
editor_version,
build_version,
name,
author,
description,
recommended_players,
legacy_v8,
camera_bounds,
camera_bounds_complements,
playable_size,
flags,
tileset,
loading_screen_background,
campaign_background,
loading_screen_model,
loading_screen_text,
loading_screen_title,
loading_screen_subtitle,
game_data_set,
loading_screen_index,
prologue_screen_model,
prologue_screen_text,
prologue_screen_title,
prologue_screen_subtitle,
fog_style,
fog_height,
fog_density,
fog_color,
global_weather,
sound_environment,
light_environment_tileset,
water_vertex_color,
script_mode,
graphics_mode,
game_data_version,
default_camera_zoom,
max_camera_zoom,
min_camera_zoom,
skipped_optional_sections,
players,
forces,
upgrade_availability_changes,
tech_availability_changes,
random_unit_tables,
random_item_tables,
})
}
pub fn get_build_version(&self) -> u32 {
match self.build_version {
Some(version) => version[0] * 100 + version[1],
None => 0,
}
}
pub fn visit_strings(&mut self, mut f: impl FnMut(&mut String)) {
f(&mut self.name);
f(&mut self.author);
f(&mut self.description);
f(&mut self.recommended_players);
for s in [
&mut self.loading_screen_text,
&mut self.loading_screen_title,
&mut self.loading_screen_subtitle,
&mut self.prologue_screen_text,
&mut self.prologue_screen_title,
&mut self.prologue_screen_subtitle,
]
.into_iter()
.flatten()
{
f(s);
}
for player in &mut self.players {
f(&mut player.name);
}
for force in &mut self.forces {
f(&mut force.name);
}
for table in &mut self.random_unit_tables {
f(&mut table.name);
}
for table in &mut self.random_item_tables {
f(&mut table.name);
}
}
}