use crate::bits::{BitReader, BitWriter};
use crate::enums::{CharacterClass, CharacterFlags, GameVersion, ItemFlags, ItemQuality, Side};
use crate::error::Result;
use crate::ItemCode;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Character {
pub weapon_switch: u32,
pub name: String,
pub flags: CharacterFlags,
pub class: CharacterClass,
pub num_stats: u8,
pub num_skills: u8,
pub level: u8,
pub create_time: u32,
pub last_save_time: u32,
pub play_time: u32,
pub skill_hotkeys: [Skill; 16],
pub left_skill: Skill,
pub right_skill: Skill,
pub switch_left_skill: Skill,
pub switch_right_skill: Skill,
pub appearance_graphics: [u8; 16],
pub appearance_tints: [u8; 16],
pub town_difficulty: [u8; 3],
pub map_seed: u32,
pub merc: MercData,
pub preview: PreviewData,
}
impl Default for Character {
fn default() -> Self {
Character {
weapon_switch: 0,
name: String::new(),
flags: CharacterFlags::empty(),
class: CharacterClass::Amazon,
num_stats: 16,
num_skills: 30,
level: 1,
create_time: 0,
last_save_time: 0,
play_time: 0xFFFF_FFFF,
skill_hotkeys: Default::default(),
left_skill: Skill::default(),
right_skill: Skill::default(),
switch_left_skill: Skill::default(),
switch_right_skill: Skill::default(),
appearance_graphics: [0xFF; 16],
appearance_tints: [0xFF; 16],
town_difficulty: [0; 3],
map_seed: 0,
merc: MercData::default(),
preview: PreviewData::default(),
}
}
}
impl Character {
pub const SIZE_V103: usize = 319;
pub const SIZE_V104: usize = 387;
pub fn size(save_version: u32) -> usize {
if save_version >= 104 {
Self::SIZE_V104
} else {
Self::SIZE_V103
}
}
pub fn read(r: &mut BitReader<'_>, save_version: u32) -> Result<Self> {
let weapon_switch = r.read_u32()?;
let name = if save_version < 104 { r.read_fixed_string(16)? } else { String::new() };
let flags = CharacterFlags::from_bits_retain(r.read_u32()?);
let class = CharacterClass::from_raw(r.read_u8()?);
let num_stats = r.read_u8()?;
let num_skills = r.read_u8()?;
let level = r.read_u8()?;
let create_time = r.read_u32()?;
let last_save_time = r.read_u32()?;
let play_time = r.read_u32()?;
let mut skill_hotkeys = <[Skill; 16]>::default();
for hotkey in &mut skill_hotkeys {
*hotkey = Skill::read(r)?;
}
Ok(Character {
weapon_switch,
name,
flags,
class,
num_stats,
num_skills,
level,
create_time,
last_save_time,
play_time,
skill_hotkeys,
left_skill: Skill::read(r)?,
right_skill: Skill::read(r)?,
switch_left_skill: Skill::read(r)?,
switch_right_skill: Skill::read(r)?,
appearance_graphics: r.read_array()?,
appearance_tints: r.read_array()?,
town_difficulty: r.read_array()?,
map_seed: r.read_u32()?,
merc: MercData::read(r)?,
preview: PreviewData::read(r, save_version)?,
})
}
pub fn write(&self, w: &mut BitWriter<'_>, save_version: u32) -> Result<()> {
w.write_u32(self.weapon_switch)?;
if save_version < 104 {
w.write_fixed_string(&self.name, 16)?;
}
w.write_u32(self.flags.bits())?;
w.write_u8(self.class.to_raw())?;
w.write_u8(self.num_stats)?;
w.write_u8(self.num_skills)?;
w.write_u8(self.level)?;
w.write_u32(self.create_time)?;
w.write_u32(self.last_save_time)?;
w.write_u32(self.play_time)?;
for hotkey in &self.skill_hotkeys {
hotkey.write(w)?;
}
self.left_skill.write(w)?;
self.right_skill.write(w)?;
self.switch_left_skill.write(w)?;
self.switch_right_skill.write(w)?;
w.write_bytes(&self.appearance_graphics)?;
w.write_bytes(&self.appearance_tints)?;
w.write_bytes(&self.town_difficulty)?;
w.write_u32(self.map_seed)?;
self.merc.write(w)?;
self.preview.write(w, save_version)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Skill {
pub skill_id: u16,
pub side: Side,
pub item_index: u16,
}
impl Skill {
pub fn read(r: &mut BitReader<'_>) -> Result<Self> {
let raw = r.read_u16()?;
Ok(Skill {
skill_id: raw & 0x7FFF,
side: if raw >> 15 != 0 { Side::Left } else { Side::Right },
item_index: r.read_u16()?,
})
}
pub fn write(&self, w: &mut BitWriter<'_>) -> Result<()> {
w.write_u16((self.skill_id & 0x7FFF) | ((self.side as u16) << 15))?;
w.write_u16(self.item_index)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MercData {
pub flags: u32,
pub seed: u32,
pub name_index: u16,
pub hireling_id: u16,
pub experience: u32,
}
impl MercData {
const DEAD: u32 = 0x0001_0000;
pub fn is_dead(&self) -> bool {
self.flags & Self::DEAD != 0
}
pub fn set_dead(&mut self, dead: bool) {
if dead {
self.flags |= Self::DEAD;
} else {
self.flags &= !Self::DEAD;
}
}
pub fn has_merc(&self) -> bool {
self.seed != 0 || self.name_index != 0 || self.experience != 0
}
pub fn read(r: &mut BitReader<'_>) -> Result<Self> {
Ok(MercData {
flags: r.read_u32()?,
seed: r.read_u32()?,
name_index: r.read_u16()?,
hireling_id: r.read_u16()?,
experience: r.read_u32()?,
})
}
pub fn write(&self, w: &mut BitWriter<'_>) -> Result<()> {
w.write_u32(self.flags)?;
w.write_u32(self.seed)?;
w.write_u16(self.name_index)?;
w.write_u16(self.hireling_id)?;
w.write_u32(self.experience)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PreviewData {
pub save_times: [u64; 6],
pub experiences: [u32; 6],
pub guild_emblem_color: u8,
pub game_version: GameVersion,
pub preview_padding: u16,
pub left_hand: PreviewItem,
pub right_hand: PreviewItem,
pub torso: PreviewItem,
pub head: PreviewItem,
pub name: String,
pub reserved: u64,
}
impl PreviewData {
pub fn expansion_save_time(&self) -> u64 {
self.save_times[0]
}
pub fn classic_save_time(&self) -> u64 {
self.save_times[1]
}
pub fn expansion_experience(&self) -> u32 {
self.experiences[0]
}
pub fn classic_experience(&self) -> u32 {
self.experiences[1]
}
pub fn read(r: &mut BitReader<'_>, save_version: u32) -> Result<Self> {
let mut data = PreviewData::default();
if save_version >= 104 {
for t in &mut data.save_times {
*t = r.read_u64()?;
}
for e in &mut data.experiences {
*e = r.read_u32()?;
}
data.read_emblem_and_version(r)?;
} else {
data.save_times[0] = r.read_u64()?;
data.save_times[1] = r.read_u64()?;
data.read_emblem_and_version(r)?;
data.experiences[0] = r.read_u32()?;
data.experiences[1] = r.read_u32()?;
}
data.left_hand = PreviewItem::read(r)?;
data.right_hand = PreviewItem::read(r)?;
data.torso = PreviewItem::read(r)?;
data.head = PreviewItem::read(r)?;
data.name = r.read_fixed_string(Self::name_size(save_version))?;
data.reserved = r.read_u64()?;
Ok(data)
}
fn read_emblem_and_version(&mut self, r: &mut BitReader<'_>) -> Result<()> {
self.guild_emblem_color = r.read_u8()?;
self.game_version = GameVersion::from_raw(r.read_u8()?);
self.preview_padding = r.read_u16()?;
Ok(())
}
fn write_emblem_and_version(&self, w: &mut BitWriter<'_>) -> Result<()> {
w.write_u8(self.guild_emblem_color)?;
w.write_u8(self.game_version.to_raw())?;
w.write_u16(self.preview_padding)
}
pub fn write(&self, w: &mut BitWriter<'_>, save_version: u32) -> Result<()> {
if save_version >= 104 {
for &t in &self.save_times {
w.write_u64(t)?;
}
for &e in &self.experiences {
w.write_u32(e)?;
}
self.write_emblem_and_version(w)?;
} else {
w.write_u64(self.save_times[0])?;
w.write_u64(self.save_times[1])?;
self.write_emblem_and_version(w)?;
w.write_u32(self.experiences[0])?;
w.write_u32(self.experiences[1])?;
}
self.left_hand.write(w)?;
self.right_hand.write(w)?;
self.torso.write(w)?;
self.head.write(w)?;
w.write_fixed_string(&self.name, Self::name_size(save_version))?;
w.write_u64(self.reserved)
}
fn name_size(save_version: u32) -> usize {
if save_version >= 104 {
96
} else {
60
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PreviewItem {
pub code: ItemCode,
pub transform: u8,
pub quality: ItemQuality,
pub file_index: u16,
pub flags: ItemFlags,
}
impl PreviewItem {
pub fn read(r: &mut BitReader<'_>) -> Result<Self> {
Ok(PreviewItem {
code: ItemCode(r.read_u32()?),
transform: r.read_u8()?,
quality: ItemQuality::from_raw(r.read_u8()?),
file_index: r.read_u16()?,
flags: ItemFlags::from_bits_retain(r.read_u32()?),
})
}
pub fn write(&self, w: &mut BitWriter<'_>) -> Result<()> {
w.write_u32(self.code.0)?;
w.write_u8(self.transform)?;
w.write_u8(self.quality.to_raw())?;
w.write_u16(self.file_index)?;
w.write_u32(self.flags.bits())
}
}