use crate::bits::{BitReader, BitWriter};
use crate::data::ExternalData;
use crate::enums::{DemonType, Difficulty, GameVersion};
use crate::error::{Error, Result};
use crate::model::stats::{PlayerStats, Stat};
use crate::StatId;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DemonSection {
pub section_version: u16,
pub has_demon: bool,
pub padding: u8,
pub demon: Option<DemonData>,
}
impl Default for DemonSection {
fn default() -> Self {
DemonSection { section_version: 1, has_demon: false, padding: 0, demon: None }
}
}
impl DemonSection {
pub const MAGIC: u16 = 0x666C;
pub fn try_read(
r: &mut BitReader<'_>,
data: &(impl ExternalData + ?Sized),
save_version: u32,
) -> Result<Option<Self>> {
if save_version < 103 || r.bits_remaining() < 48 {
return Ok(None);
}
let start = r.bit_position();
let version = r.read_u16()?;
let magic = r.read_u16()?;
if magic != Self::MAGIC || version != 1 {
r.set_bit_position(start);
return Ok(None);
}
let has_demon = r.read_u8()? != 0;
let padding = r.read_u8()?;
let demon = if has_demon { Some(DemonData::read(r, data, save_version)?) } else { None };
Ok(Some(DemonSection { section_version: version, has_demon, padding, demon }))
}
pub fn write(&self, w: &mut BitWriter<'_>, data: &(impl ExternalData + ?Sized), save_version: u32) -> Result<()> {
w.write_u16(self.section_version)?;
w.write_u16(Self::MAGIC)?;
w.write_u8(self.has_demon as u8)?;
w.write_u8(self.padding)?;
if let (true, Some(demon)) = (self.has_demon, &self.demon) {
demon.write(w, data, save_version)?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DemonData {
pub demon_type: DemonType,
pub class_id: u16,
pub name_seed: u16,
pub mon_flags: u16,
pub num_stats: u16,
pub difficulty: Difficulty,
pub game_version: GameVersion,
pub padding_0e: u16,
pub is_ladder: u32,
pub game_type: u8,
pub padding_15: [u8; 3],
pub level_id: u32,
pub monster_level: u8,
pub is_terrorized: u8,
pub padding_1e: u16,
pub terror_zone_param: u32,
pub level_bonus: u32,
pub bonus_damage_percent: u32,
pub player_count_hp_bonus: i32,
pub player_count_experience_bonus: i32,
pub player_count_skill_bonus: i32,
pub player_count_difficulty: i32,
pub player_count: i32,
pub components: [u8; 16],
pub mon_umod: [u8; 9],
pub padding_59: [u8; 3],
pub stats: Vec<Stat>,
}
impl Default for DemonData {
fn default() -> Self {
DemonData {
demon_type: DemonType::default(),
class_id: 0,
name_seed: 0,
mon_flags: 0,
num_stats: 16,
difficulty: Difficulty::default(),
game_version: GameVersion::default(),
padding_0e: 0,
is_ladder: 0,
game_type: 0,
padding_15: [0; 3],
level_id: 0,
monster_level: 0,
is_terrorized: 0,
padding_1e: 0,
terror_zone_param: 0,
level_bonus: 0,
bonus_damage_percent: 0,
player_count_hp_bonus: 0,
player_count_experience_bonus: 0,
player_count_skill_bonus: 0,
player_count_difficulty: 0,
player_count: 0,
components: [0; 16],
mon_umod: [0; 9],
padding_59: [0; 3],
stats: Vec::new(),
}
}
}
impl DemonData {
const MAX_STAT_BLOB: usize = 16384;
pub fn read(r: &mut BitReader<'_>, data: &(impl ExternalData + ?Sized), save_version: u32) -> Result<Self> {
let stat_size = r.read_u16()? as usize;
let mut demon = DemonData {
demon_type: DemonType::from_raw(r.read_u16()?),
class_id: r.read_u16()?.wrapping_sub(1),
name_seed: r.read_u16()?,
mon_flags: r.read_u16()?,
num_stats: r.read_u16()?,
difficulty: Difficulty::from_raw(r.read_u8()?),
game_version: GameVersion::from_raw(r.read_u8()?),
padding_0e: r.read_u16()?,
is_ladder: r.read_u32()?,
game_type: r.read_u8()?,
padding_15: r.read_array()?,
level_id: r.read_u32()?,
monster_level: r.read_u8()?,
is_terrorized: r.read_u8()?,
padding_1e: r.read_u16()?,
terror_zone_param: r.read_u32()?,
level_bonus: r.read_u32()?,
bonus_damage_percent: r.read_u32()?,
player_count_hp_bonus: r.read_u32()? as i32,
player_count_experience_bonus: r.read_u32()? as i32,
player_count_skill_bonus: r.read_u32()? as i32,
player_count_difficulty: r.read_u32()? as i32,
player_count: r.read_u32()? as i32,
components: r.read_array()?,
mon_umod: r.read_array()?,
padding_59: r.read_array()?,
stats: Vec::new(),
};
if stat_size > 0 {
let blob = r.read_slice(stat_size)?;
demon.stats = read_stats(blob, data, save_version)?;
}
Ok(demon)
}
pub fn write(&self, w: &mut BitWriter<'_>, data: &(impl ExternalData + ?Sized), save_version: u32) -> Result<()> {
let mut blob = Vec::with_capacity(4 + self.stats.len() * 6);
write_stats(&mut blob, &self.stats, data, save_version)?;
if blob.len() > Self::MAX_STAT_BLOB {
return Err(Error::invalid_data("demon stat data too large"));
}
w.write_u16(blob.len() as u16)?;
w.write_u16(self.demon_type.to_raw())?;
w.write_u16(self.class_id.wrapping_add(1))?;
w.write_u16(self.name_seed)?;
w.write_u16(self.mon_flags)?;
w.write_u16(self.num_stats)?;
w.write_u8(self.difficulty.to_raw())?;
w.write_u8(self.game_version.to_raw())?;
w.write_u16(self.padding_0e)?;
w.write_u32(self.is_ladder)?;
w.write_u8(self.game_type)?;
w.write_bytes(&self.padding_15)?;
w.write_u32(self.level_id)?;
w.write_u8(self.monster_level)?;
w.write_u8(self.is_terrorized)?;
w.write_u16(self.padding_1e)?;
w.write_u32(self.terror_zone_param)?;
w.write_u32(self.level_bonus)?;
w.write_u32(self.bonus_damage_percent)?;
w.write_u32(self.player_count_hp_bonus as u32)?;
w.write_u32(self.player_count_experience_bonus as u32)?;
w.write_u32(self.player_count_skill_bonus as u32)?;
w.write_u32(self.player_count_difficulty as u32)?;
w.write_u32(self.player_count as u32)?;
w.write_bytes(&self.components)?;
w.write_bytes(&self.mon_umod)?;
w.write_bytes(&self.padding_59)?;
w.write_bytes(&blob)
}
}
fn read_stats(blob: &[u8], data: &(impl ExternalData + ?Sized), save_version: u32) -> Result<Vec<Stat>> {
let mut r = BitReader::new(blob);
let magic = r.read_u16()?;
if magic != PlayerStats::MAGIC {
return Err(Error::InvalidMagic { section: "demon stats", found: magic as u32 });
}
let mut stats = Vec::new();
loop {
let id = StatId(r.read_bits(9)? as u16);
if id == StatId::TERMINATOR {
break;
}
let info = data.stat_info(id, save_version)?;
let layer = if info.csv_param > 0 { r.read_signed_bits(info.csv_param as u32)? } else { 0 };
stats.push(Stat { id, layer, value: r.read_u32()? as i64 });
}
Ok(stats)
}
fn write_stats(
buf: &mut Vec<u8>,
stats: &[Stat],
data: &(impl ExternalData + ?Sized),
save_version: u32,
) -> Result<()> {
let mut w = BitWriter::new(buf);
w.write_u16(PlayerStats::MAGIC)?;
for stat in stats {
let info = data.stat_info(stat.id, save_version)?;
w.write_bits(stat.id.0 as u32, 9)?;
if info.csv_param > 0 {
w.write_bits(stat.layer as u32, info.csv_param as u32)?;
}
let value = u32::try_from(stat.value)
.map_err(|_| Error::invalid_data(format!("demon stat value {} out of range", stat.value)))?;
w.write_u32(value)?;
}
w.write_bits(StatId::TERMINATOR.0 as u32, 9)?;
w.align_to_byte();
Ok(())
}