mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-16 22:43:58 +00:00
442 lines
18 KiB
Rust
442 lines
18 KiB
Rust
//! A digimon card
|
|
//!
|
|
//! This module contains the [`Digimon`] struct, which describes a digimon card.
|
|
//!
|
|
//! # Layout
|
|
//! The digimon card has a size of `0x138` bytes, and it's layout is the following:
|
|
//!
|
|
//! | Offset | Size | Type | Name | Location | Details |
|
|
//! |--------|------|---------------------|---------------------------|------------------------|-------------------------------------------------------------------------------------|
|
|
//! | 0x0 | 0x15 | `[char; 0x15]` | Name | `name` | Null-terminated |
|
|
//! | 0x15 | 0x2 | `u16` | Unknown | `unknown_15` | |
|
|
//! | 0x17 | 0x1 | `u8` | Speciality & Level | `speciality level` | The bottom nibble of this byte is the level, while the top nibble is the speciality |
|
|
//! | 0x18 | 0x1 | `u8` | DP | `dp_cost` | |
|
|
//! | 0x19 | 0x1 | `u8` | +P | `dp_give` | |
|
|
//! | 0x1a | 0x1 | `u8` | Unknown | `unknown_1a` | Is `0` for all digimon |
|
|
//! | 0x1b | 0x2 | `u16` | Health | `hp` | |
|
|
//! | 0x1d | 0x1c | [`Move`] | Circle Move | `move_circle` | |
|
|
//! | 0x39 | 0x1c | [`Move`] | Triangle move | `move_triangle` | |
|
|
//! | 0x55 | 0x1c | [`Move`] | Cross move | `move_cross` | |
|
|
//! | 0x71 | 0x20 | [`EffectCondition`] | First condition | `effect_conditions[0]` | |
|
|
//! | 0x91 | 0x20 | [`EffectCondition`] | Second condition | `effect_conditions[1]` | |
|
|
//! | 0xb1 | 0x10 | [`Effect`] | First effect | `effects[0]` | |
|
|
//! | 0xc1 | 0x10 | [`Effect`] | Second effect | `effects[1]` | |
|
|
//! | 0xd1 | 0x10 | [`Effect`] | Third effect | `effects[2]` | |
|
|
//! | 0xe1 | 0x1 | [`CrossMoveEffect`] | Cross move effect | `cross_move_effect` | |
|
|
//! | 0xe2 | 0x1 | `u8` | Unknown | `unknown_e2` | |
|
|
//! | 0xe3 | 0x1 | [`ArrowColor`] | Effect arrow color | `effect_arrow_color` | |
|
|
//! | 0xe4 | 0x54 | `[[char; 0x15]; 4]` | Effect description lines | `effect_description` | Each line is` 0x15` bytes, split over 4 lines, each null terminated |
|
|
|
|
// byteorder
|
|
use byteorder::{ByteOrder, LittleEndian};
|
|
|
|
// Crate
|
|
use crate::game::{
|
|
util,
|
|
Bytes,
|
|
card::property::{
|
|
self,
|
|
Speciality,
|
|
Level,
|
|
Move,
|
|
CrossMoveEffect,
|
|
EffectCondition,
|
|
Effect,
|
|
ArrowColor,
|
|
}
|
|
};
|
|
|
|
/// A digimon card
|
|
///
|
|
/// Contains all information about each digimon card stored in the [`Card Table`](crate::game::card::table::Table)
|
|
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
pub struct Digimon
|
|
{
|
|
/// The digimon's name
|
|
///
|
|
/// An ascii string with 20 characters at most
|
|
pub name: ascii::AsciiString,
|
|
|
|
/// The digimon's speciality
|
|
///
|
|
/// Stored alongside with the level in a single byte
|
|
pub speciality: Speciality,
|
|
|
|
/// The digimon's level
|
|
///
|
|
/// Stored alongside with the speciality in a single byte
|
|
pub level: Level,
|
|
|
|
/// The digimon's health points
|
|
pub hp: u16,
|
|
|
|
/// The DP cost to play this digimon card
|
|
///
|
|
/// `DP` in the game.
|
|
pub dp_cost: u8,
|
|
|
|
/// The number of DP given when discarded
|
|
///
|
|
/// `+P` in the game.
|
|
pub dp_give: u8,
|
|
|
|
/// The digimon's circle move
|
|
pub move_circle: Move,
|
|
|
|
/// The digimon's triangle move
|
|
pub move_triangle: Move,
|
|
|
|
/// The digimon's cross move
|
|
pub move_cross: Move,
|
|
|
|
/// The digimon's cross move effect, if any
|
|
#[serde(default)]
|
|
pub cross_move_effect: Option<CrossMoveEffect>,
|
|
|
|
/// The digimon's effect description.
|
|
///
|
|
/// The description is split along 4 lines, each
|
|
/// being an ascii string with 20 characters at most.
|
|
pub effect_description: [ascii::AsciiString; 4],
|
|
|
|
/// The effect arrow color
|
|
#[serde(default)]
|
|
pub effect_arrow_color: Option<ArrowColor>,
|
|
|
|
/// The effect conditions
|
|
#[serde(default)]
|
|
pub effect_conditions: [Option<EffectCondition>; 2],
|
|
|
|
/// The effects themselves
|
|
#[serde(default)]
|
|
pub effects: [Option<Effect>; 3],
|
|
|
|
// Unknown fields
|
|
pub unknown_1a: u8,
|
|
pub unknown_15: u16,
|
|
pub unknown_e2: u8,
|
|
}
|
|
|
|
/// Error type for [`Bytes::from_bytes`]
|
|
#[derive(Debug)]
|
|
#[derive(derive_more::Display, err_impl::Error)]
|
|
pub enum FromBytesError
|
|
{
|
|
/// Unable to read the digimon name
|
|
#[display(fmt = "Unable to read the digimon name")]
|
|
Name( #[error(source)] util::ReadNullAsciiStringError ),
|
|
|
|
/// Unable to read the first support effect description
|
|
#[display(fmt = "Unable to read the first line of the effect description")]
|
|
EffectDescriptionFirst( #[error(source)] util::ReadNullAsciiStringError ),
|
|
|
|
/// Unable to read the second support effect description
|
|
#[display(fmt = "Unable to read the second line of the effect description")]
|
|
EffectDescriptionSecond( #[error(source)] util::ReadNullAsciiStringError ),
|
|
|
|
/// Unable to read the third support effect description
|
|
#[display(fmt = "Unable to read the third line of the effect description")]
|
|
EffectDescriptionThird( #[error(source)] util::ReadNullAsciiStringError ),
|
|
|
|
/// Unable to read the fourth support effect description
|
|
#[display(fmt = "Unable to read the fourth line of the effect description")]
|
|
EffectDescriptionFourth( #[error(source)] util::ReadNullAsciiStringError ),
|
|
|
|
/// An unknown speciality was found
|
|
#[display(fmt = "Unknown speciality found")]
|
|
Speciality( #[error(source)] property::speciality::FromBytesError ),
|
|
|
|
/// An unknown level was found
|
|
#[display(fmt = "Unknown level found")]
|
|
Level( #[error(source)] property::level::FromBytesError ),
|
|
|
|
/// An unknown effect arrow color was found
|
|
#[display(fmt = "Unknown effect arrow color found")]
|
|
ArrowColor( #[error(source)] property::arrow_color::FromBytesError ),
|
|
|
|
/// An unknown cross move effect was found
|
|
#[display(fmt = "Unknown cross move effect found")]
|
|
CrossMoveEffect( #[error(source)] property::cross_move_effect::FromBytesError ),
|
|
|
|
/// Unable to read the circle move
|
|
#[display(fmt = "Unable to read the circle move")]
|
|
MoveCircle( #[error(source)] property::moves::FromBytesError ),
|
|
|
|
/// Unable to read the triangle move
|
|
#[display(fmt = "Unable to read the triangle move")]
|
|
MoveTriangle( #[error(source)] property::moves::FromBytesError ),
|
|
|
|
/// Unable to read the cross move
|
|
#[display(fmt = "Unable to read the cross move")]
|
|
MoveCross( #[error(source)] property::moves::FromBytesError ),
|
|
|
|
/// Unable to read the first effect condition
|
|
#[display(fmt = "Unable to read the first effect condition")]
|
|
EffectConditionFirst( #[error(source)] property::effect_condition::FromBytesError ),
|
|
|
|
/// Unable to read the second effect condition
|
|
#[display(fmt = "Unable to read the second effect condition")]
|
|
EffectConditionSecond( #[error(source)] property::effect_condition::FromBytesError ),
|
|
|
|
/// Unable to read the first effect
|
|
#[display(fmt = "Unable to read the first effect")]
|
|
EffectFirst( #[error(source)] property::effect::FromBytesError ),
|
|
|
|
/// Unable to read the second effect
|
|
#[display(fmt = "Unable to read the second effect")]
|
|
EffectSecond( #[error(source)] property::effect::FromBytesError ),
|
|
|
|
/// Unable to read the third effect
|
|
#[display(fmt = "Unable to read the third effect")]
|
|
EffectThird( #[error(source)] property::effect::FromBytesError ),
|
|
}
|
|
|
|
/// Error type for [`Bytes::to_bytes`]
|
|
#[derive(Debug)]
|
|
#[derive(derive_more::Display, err_impl::Error)]
|
|
pub enum ToBytesError
|
|
{
|
|
/// Unable to write the digimon name
|
|
#[display(fmt = "Unable to write the digimon name")]
|
|
Name( #[error(source)] util::WriteNullAsciiStringError ),
|
|
|
|
/// Unable to write the first support effect description
|
|
#[display(fmt = "Unable to write the first line of the effect description")]
|
|
EffectDescriptionFirst( #[error(source)] util::WriteNullAsciiStringError ),
|
|
|
|
/// Unable to write the second support effect description
|
|
#[display(fmt = "Unable to write the second line of the effect description")]
|
|
EffectDescriptionSecond( #[error(source)] util::WriteNullAsciiStringError ),
|
|
|
|
/// Unable to write the third support effect description
|
|
#[display(fmt = "Unable to write the third line of the effect description")]
|
|
EffectDescriptionThird( #[error(source)] util::WriteNullAsciiStringError ),
|
|
|
|
/// Unable to write the fourth support effect description
|
|
#[display(fmt = "Unable to write the fourth line of the effect description")]
|
|
EffectDescriptionFourth( #[error(source)] util::WriteNullAsciiStringError ),
|
|
|
|
/// Unable to write the circle move
|
|
#[display(fmt = "Unable to write the circle move")]
|
|
MoveCircle( #[error(source)] property::moves::ToBytesError ),
|
|
|
|
/// Unable to write the triangle move
|
|
#[display(fmt = "Unable to write the triangle move")]
|
|
MoveTriangle( #[error(source)] property::moves::ToBytesError ),
|
|
|
|
/// Unable to write the cross move
|
|
#[display(fmt = "Unable to write the cross move")]
|
|
MoveCross( #[error(source)] property::moves::ToBytesError ),
|
|
|
|
/// Unable to write the first effect
|
|
#[display(fmt = "Unable to write the first effect")]
|
|
EffectFirst( #[error(source)] property::effect::ToBytesError ),
|
|
|
|
/// Unable to write the second effect
|
|
#[display(fmt = "Unable to write the second effect")]
|
|
EffectSecond( #[error(source)] property::effect::ToBytesError ),
|
|
|
|
/// Unable to write the third effect
|
|
#[display(fmt = "Unable to write the third effect")]
|
|
EffectThird( #[error(source)] property::effect::ToBytesError ),
|
|
}
|
|
|
|
impl Bytes for Digimon
|
|
{
|
|
type ByteArray = [u8; 0x138];
|
|
|
|
type FromError = FromBytesError;
|
|
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError>
|
|
{
|
|
// Split bytes
|
|
let bytes = util::array_split!(bytes,
|
|
name : [0x15],
|
|
unknown_15 : [0x2],
|
|
speciality_level : 0x1,
|
|
dp_cost : 0x1,
|
|
dp_give : 0x1,
|
|
unknown_1a : 0x1,
|
|
hp : [0x2],
|
|
move_circle : [0x1c],
|
|
move_triangle : [0x1c],
|
|
move_cross : [0x1c],
|
|
condition_first : [0x20],
|
|
condition_second : [0x20],
|
|
effect_first : [0x10],
|
|
effect_second : [0x10],
|
|
effect_third : [0x10],
|
|
cross_move_effect : 1,
|
|
unknown_e2 : 1,
|
|
effect_arrow_color : 1,
|
|
effect_description_0: [0x15],
|
|
effect_description_1: [0x15],
|
|
effect_description_2: [0x15],
|
|
effect_description_3: [0x15],
|
|
);
|
|
|
|
// Return the struct after building it
|
|
Ok( Self {
|
|
name: util::read_null_ascii_string(bytes.name)
|
|
.map_err(FromBytesError::Name)?
|
|
.chars().collect(),
|
|
|
|
speciality: Speciality::from_bytes( &( (bytes.speciality_level & 0xF0) >> 4 ) )
|
|
.map_err(FromBytesError::Speciality)?,
|
|
|
|
level: Level::from_bytes( &( (bytes.speciality_level & 0x0F) >> 0 ) )
|
|
.map_err(FromBytesError::Level)?,
|
|
|
|
dp_cost : *bytes.dp_cost,
|
|
dp_give : *bytes.dp_give,
|
|
|
|
hp: LittleEndian::read_u16( bytes.hp ),
|
|
|
|
// Moves
|
|
move_circle: Move::from_bytes( bytes.move_circle )
|
|
.map_err(FromBytesError::MoveCircle)?,
|
|
move_triangle: Move::from_bytes( bytes.move_triangle )
|
|
.map_err(FromBytesError::MoveTriangle)?,
|
|
move_cross: Move::from_bytes( bytes.move_cross )
|
|
.map_err(FromBytesError::MoveCross)?,
|
|
|
|
// Effects
|
|
effect_conditions: [
|
|
Option::<EffectCondition>::from_bytes( bytes.condition_first )
|
|
.map_err(FromBytesError::EffectConditionFirst)?,
|
|
|
|
Option::<EffectCondition>::from_bytes( bytes.condition_second )
|
|
.map_err(FromBytesError::EffectConditionSecond)?,
|
|
],
|
|
|
|
effects: [
|
|
Option::<Effect>::from_bytes( bytes.effect_first )
|
|
.map_err(FromBytesError::EffectFirst)?,
|
|
|
|
Option::<Effect>::from_bytes( bytes.effect_second )
|
|
.map_err(FromBytesError::EffectSecond)?,
|
|
|
|
Option::<Effect>::from_bytes( bytes.effect_third )
|
|
.map_err(FromBytesError::EffectThird)?,
|
|
],
|
|
|
|
cross_move_effect: Option::<CrossMoveEffect>::from_bytes(bytes.cross_move_effect)
|
|
.map_err(FromBytesError::CrossMoveEffect)?,
|
|
|
|
effect_arrow_color: Option::<ArrowColor>::from_bytes(bytes.effect_arrow_color)
|
|
.map_err(FromBytesError::ArrowColor)?,
|
|
|
|
effect_description: [
|
|
util::read_null_ascii_string( bytes.effect_description_0 )
|
|
.map_err(FromBytesError::EffectDescriptionFirst)?
|
|
.chars().collect(),
|
|
util::read_null_ascii_string( bytes.effect_description_1 )
|
|
.map_err(FromBytesError::EffectDescriptionSecond)?
|
|
.chars().collect(),
|
|
util::read_null_ascii_string( bytes.effect_description_2 )
|
|
.map_err(FromBytesError::EffectDescriptionThird)?
|
|
.chars().collect(),
|
|
util::read_null_ascii_string( bytes.effect_description_3 )
|
|
.map_err(FromBytesError::EffectDescriptionFourth)?
|
|
.chars().collect(),
|
|
],
|
|
|
|
// Unknown
|
|
unknown_15: LittleEndian::read_u16(bytes.unknown_15),
|
|
unknown_1a: *bytes.unknown_1a,
|
|
unknown_e2: *bytes.unknown_e2,
|
|
})
|
|
}
|
|
|
|
type ToError = ToBytesError;
|
|
fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError>
|
|
{
|
|
// Split bytes
|
|
let bytes = util::array_split_mut!(bytes,
|
|
name : [0x15],
|
|
unknown_15 : [0x2],
|
|
speciality_level : 0x1,
|
|
dp_cost : 0x1,
|
|
dp_give : 0x1,
|
|
unknown_1a : 0x1,
|
|
hp : [0x2],
|
|
move_circle : [0x1c],
|
|
move_triangle : [0x1c],
|
|
move_cross : [0x1c],
|
|
condition_first : [0x20],
|
|
condition_second : [0x20],
|
|
effect_first : [0x10],
|
|
effect_second : [0x10],
|
|
effect_third : [0x10],
|
|
cross_move_effect : 1,
|
|
unknown_e2 : 1,
|
|
effect_arrow_color : 1,
|
|
effect_description_0: [0x15],
|
|
effect_description_1: [0x15],
|
|
effect_description_2: [0x15],
|
|
effect_description_3: [0x15],
|
|
);
|
|
|
|
// Name
|
|
util::write_null_ascii_string(self.name.as_ref(), bytes.name)
|
|
.map_err(ToBytesError::Name)?;
|
|
|
|
// Speciality / Level
|
|
{
|
|
let (mut speciality_byte, mut level_byte) = ( 0u8, 0u8 );
|
|
|
|
// Note: Buffers have 1 byte, so this can't fail
|
|
self.speciality.to_bytes(&mut speciality_byte)?;
|
|
self.level.to_bytes(&mut level_byte)?;
|
|
|
|
// Merge them
|
|
*bytes.speciality_level = (speciality_byte << 4) | level_byte;
|
|
}
|
|
|
|
// DP / +P
|
|
*bytes.dp_cost = self.dp_cost;
|
|
*bytes.dp_give = self.dp_give;
|
|
|
|
// Health
|
|
LittleEndian::write_u16(bytes.hp, self.hp);
|
|
|
|
// Moves
|
|
self.move_circle .to_bytes( bytes.move_circle ).map_err(ToBytesError::MoveCircle )?;
|
|
self.move_triangle.to_bytes( bytes.move_triangle ).map_err(ToBytesError::MoveTriangle)?;
|
|
self.move_cross .to_bytes( bytes.move_cross ).map_err(ToBytesError::MoveCross )?;
|
|
|
|
// Effect conditions
|
|
self.effect_conditions[0].to_bytes( bytes.condition_first ).into_ok();
|
|
self.effect_conditions[1].to_bytes( bytes.condition_second ).into_ok();
|
|
|
|
// Effects
|
|
self.effects[0].to_bytes( bytes.effect_first ).map_err(ToBytesError::EffectFirst )?;
|
|
self.effects[1].to_bytes( bytes.effect_second ).map_err(ToBytesError::EffectSecond)?;
|
|
self.effects[2].to_bytes( bytes.effect_third ).map_err(ToBytesError::EffectThird )?;
|
|
|
|
// Cross move
|
|
Option::<CrossMoveEffect>::to_bytes(&self.cross_move_effect, bytes.cross_move_effect).into_ok();
|
|
|
|
// Support arrow color
|
|
Option::<ArrowColor>::to_bytes(&self.effect_arrow_color, bytes.effect_arrow_color).into_ok();
|
|
|
|
// effect_description
|
|
util::write_null_ascii_string(self.effect_description[0].as_ref(), bytes.effect_description_0)
|
|
.map_err(ToBytesError::EffectDescriptionFirst)?;
|
|
util::write_null_ascii_string(self.effect_description[1].as_ref(), bytes.effect_description_1)
|
|
.map_err(ToBytesError::EffectDescriptionSecond)?;
|
|
util::write_null_ascii_string(self.effect_description[2].as_ref(), bytes.effect_description_2)
|
|
.map_err(ToBytesError::EffectDescriptionThird)?;
|
|
util::write_null_ascii_string(self.effect_description[3].as_ref(), bytes.effect_description_3)
|
|
.map_err(ToBytesError::EffectDescriptionFourth)?;
|
|
|
|
// Unknown
|
|
LittleEndian::write_u16(bytes.unknown_15, self.unknown_15);
|
|
*bytes.unknown_1a = self.unknown_1a;
|
|
*bytes.unknown_e2 = self.unknown_e2;
|
|
|
|
// Return Ok
|
|
Ok(())
|
|
}
|
|
}
|