diff --git a/.gitignore b/.gitignore index c640ca5..ae094f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target +/.vscode +/resources Cargo.lock -.vscode diff --git a/src/game/card/digimon.rs b/src/game/card/digimon.rs index ff2ae48..8d577a7 100644 --- a/src/game/card/digimon.rs +++ b/src/game/card/digimon.rs @@ -94,21 +94,21 @@ pub struct Digimon #[serde(default)] pub cross_move_effect: Option, - /// The digimon's effect description. + /// The effect's 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 + /// The effect's arrow color #[serde(default)] pub effect_arrow_color: Option, - /// The effect conditions + /// The effect's conditions #[serde(default)] pub effect_conditions: [Option; 2], - /// The effects themselves + /// The effects #[serde(default)] pub effects: [Option; 3], @@ -405,22 +405,18 @@ impl Bytes for Digimon 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 + // Effects 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::::to_bytes(&self.cross_move_effect, bytes.cross_move_effect).into_ok(); - // Support arrow color Option::::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) diff --git a/src/game/card/digivolve.rs b/src/game/card/digivolve.rs index beb4015..64d1d1b 100644 --- a/src/game/card/digivolve.rs +++ b/src/game/card/digivolve.rs @@ -1,228 +1,183 @@ -//! Digivolve +//! A digivolve card +//! +//! This module contains the [`Digivolve`] struct, which describes a digivolve card. +//! +//! # Layout +//! The digivolve card has a size of `0x6c` bytes, and it's layout is the following: +//! +//! | Offset | Size | Type | Name | Location | Details | +//! |--------|------|---------------------|---------------------------|------------------------|-------------------------------------------------------------------------------------| +//! | 0x0 | 0x15 | `[char; 0x15]` | Name | `name` | Null-terminated | +//! | 0x15 | 0x4 | `u32` | Unknown | `unknown_15` | | +//! | 0x19 | 0x20 | [`EffectCondition`] | First condition | `effect_conditions[0]` | | +//! | 0x39 | 0x20 | [`EffectCondition`] | Second condition | `effect_conditions[1]` | | +//! | 0x59 | 0x10 | [`Effect`] | First effect | `effects[0]` | | +//! | 0x69 | 0x10 | [`Effect`] | Second effect | `effects[1]` | | +//! | 0x79 | 0x10 | [`Effect`] | Third effect | `effects[2]` | | +//! | 0x89 | 0x1 | [`ArrowColor`] | Effect arrow color | `effect_arrow_color` | | +//! | 0x8a | 0x54 | `[[char; 0x15]; 4]` | Effect description lines | `effect_description` | Each line is` 0x15` bytes, split over 4 lines, each null terminated | // Crate -//-------------------------------------------------------------------------------------------------- - // Game - use crate::game::util; - use crate::game::Bytes; -//-------------------------------------------------------------------------------------------------- +use crate::game::{ + util, + Bytes, +}; -// byteorder -//use byteorder::ByteOrder; -//use byteorder::LittleEndian; +/// A digivolve card +/// +/// Contains all information about each digivolve card stored in the [`Card Table`](crate::game::card::table::Table) +#[derive(PartialEq, Eq, Clone, Hash, Debug)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Digivolve +{ + /// The item's name + /// + /// An ascii string with 20 characters at most + pub name: ascii::AsciiString, + + /// The effect's description. + /// + /// The description is split along 4 lines, each + /// being an ascii string with 20 characters at most. + pub effect_description: [ascii::AsciiString; 4], + + pub value0: u8, + pub value1: u8, + pub value2: u8, +} -// Macros -use serde::Serialize; -use serde::Deserialize; +/// 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 ), +} -// Types -//-------------------------------------------------------------------------------------------------- - /// A digivolve card - #[derive(Debug, Serialize, Deserialize)] - pub struct Digivolve - { - /// The basic info of the digivolve - pub basic: Basic, - - /// The effects - pub effects: Effects, - } +/// 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 ), - /// The basic properties of a digivolve - #[derive(Debug, Serialize, Deserialize)] - pub struct Basic - { - pub name: String, - - //unknown: u16, - } + /// 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 ), - #[derive(Debug, Serialize, Deserialize)] - pub struct Effects - { - pub description: [String; 4], - - pub value0: u8, - pub value1: u8, - pub value2: u8, - - //arrow_color: Option, - - //conditions: SupportEffectConditions, - //effects : SupportEffects, - } + /// 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 ), - /* - #[derive(Debug, Serialize, Deserialize)] - struct SupportEffects - { - first : Option, - second: Option, - third : Option, - } + /// 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 ), - #[derive(Debug, Serialize, Deserialize)] - struct SupportEffectConditions - { - first : Option, - second: Option, - } - */ - - /// The error type thrown by `FromBytes` - #[derive(Debug, derive_more::Display)] - pub enum FromBytesError - { - /// Unable to convert name to a string - #[display(fmt = "Unable to convert name to a string")] - NameToString( util::ReadNullTerminatedStringError ), - - /// Unable to read the effect arrow color - #[display(fmt = "Unable to read the effect arrow color")] - EffectArrowColor( crate::game::card::property::arrow_color::FromBytesError ), - - /// Unable to convert one of the support effect descriptions to a string - #[display(fmt = "Unable to convert the {} support effect description to a string", rank)] - SupportEffectDescriptionToString { - rank: &'static str, - err: util::ReadNullTerminatedStringError, - }, - - /// Unable to read a support effect condition - #[display(fmt = "Unable to read the {0} support effect condition [digivolve:0x{1:x}]", rank, digivolve_pos)] - SupportEffectCondition { - rank: &'static str, - digivolve_pos: u64, - err: crate::game::card::property::effect_condition::FromBytesError, - }, - - /// Unable to read a support effect - #[display(fmt = "Unable to read the {} support effect", rank)] - SupportEffect { - rank: &'static str, - err: crate::game::card::property::effect::FromBytesError, - }, - } - - impl std::error::Error for FromBytesError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::NameToString(err) | - Self::SupportEffectDescriptionToString{err, ..} => Some(err), - - Self::EffectArrowColor(err) => Some(err), - - Self::SupportEffectCondition{err, ..} => Some(err), - Self::SupportEffect{err, ..} => Some(err), - } - } - } - - /// The error type thrown by `ToBytes` - #[derive(Debug, derive_more::Display)] - pub enum ToBytesError - { - /// The name was too big to be written to file - #[display(fmt = "The name \"{}\" is too long to be written to file (max is 20)", _0)] - NameTooLong( String ), - - /// The name was too big to be written to file - #[display(fmt = "The {0} support effect description \"{1}\" is too long to be written to file (max is 21)", rank, string)] - SupportEffectDescriptionTooLong { - rank: &'static str, - string: String, - }, - } - - impl std::error::Error for ToBytesError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::NameTooLong(..) | - Self::SupportEffectDescriptionTooLong{ .. } => None, - } - } - } -//-------------------------------------------------------------------------------------------------- + /// 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 ), +} -// Impl -//-------------------------------------------------------------------------------------------------- - // Bytes - impl Bytes for Digivolve +impl Bytes for Digivolve +{ + type ByteArray = [u8; 0x6c]; + + type FromError = FromBytesError; + fn from_bytes(bytes: &Self::ByteArray) -> Result { - type ByteArray = [u8; 0x6c]; + // Split bytes + let bytes = util::array_split!(bytes, + name : [0x15], + value0 : 1, + value1 : 1, + value2 : 1, + effect_description_0: [0x15], + effect_description_1: [0x15], + effect_description_2: [0x15], + effect_description_3: [0x15], + ); - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { - Ok( Self { - basic: Basic { - name: util::read_null_terminated_string( &bytes[0x0..0x15] ).map_err(FromBytesError::NameToString)?.to_string(), - }, - - effects: Effects { - description: [ - util::read_null_terminated_string( &bytes[0x18..0x2d] ).map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "1st", err })?.to_string(), - util::read_null_terminated_string( &bytes[0x2d..0x42] ).map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "2nd", err })?.to_string(), - util::read_null_terminated_string( &bytes[0x42..0x57] ).map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "3rd", err })?.to_string(), - util::read_null_terminated_string( &bytes[0x57..0x6c] ).map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "4th", err })?.to_string(), - ], - - value0: bytes[0x15], - value1: bytes[0x16], - value2: bytes[0x17], - } - }) - } - - type ToError = ToBytesError; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { - // Basic - //-------------------------------------------------------------------------------------------------- - // Write the name - bytes[0x0..0x15].copy_from_slice( &{ - // Check if our name is too big - if self.basic.name.len() >= 0x15 { return Err( ToBytesError::NameTooLong( self.basic.name.clone() ) ); } - - // Else make the buffer and copy everything over - let mut buf = [0u8; 0x15]; - buf[ 0..self.basic.name.len() ].copy_from_slice( self.basic.name.as_bytes() ); - buf - }); - //-------------------------------------------------------------------------------------------------- + Ok( Self { + name: util::read_null_ascii_string(bytes.name) + .map_err(FromBytesError::Name)? + .chars().collect(), - // Effects - //-------------------------------------------------------------------------------------------------- - // Write the support effects - for (index, line) in self.effects.description.iter().enumerate() - { - bytes[0x18 + (0x15 * index) .. 0x2d + (0x15 * index)].copy_from_slice( &{ - // If the line is too big, return Err - if line.len() >= 0x15 { - return Err( ToBytesError::SupportEffectDescriptionTooLong { - rank: match index { - 0 => "1st", 1 => "2nd", - 2 => "3rd", 3 => "4th", - _ => unreachable!(), - }, - - string: line.clone() - }); - } - - let mut buf = [0u8; 0x15]; - buf[ 0..line.len() ].copy_from_slice( line.as_bytes() ); - buf - }); - } - - bytes[0x15] = self.effects.value0; - bytes[0x16] = self.effects.value1; - bytes[0x17] = self.effects.value2; - //-------------------------------------------------------------------------------------------------- + // Effect + value0: *bytes.value0, + value1: *bytes.value1, + value2: *bytes.value2, - // Return the bytes - Ok(()) - } + 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(), + ], + }) } -//-------------------------------------------------------------------------------------------------- + + 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], + value0 : 1, + value1 : 1, + value2 : 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)?; + + // Effects + *bytes.value0 = self.value0; + *bytes.value1 = self.value1; + *bytes.value2 = self.value2; + + 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)?; + + // Return Ok + Ok(()) + } +} diff --git a/src/game/card/item.rs b/src/game/card/item.rs index 486b8f1..c477bc0 100644 --- a/src/game/card/item.rs +++ b/src/game/card/item.rs @@ -32,31 +32,33 @@ use crate::game::{ } }; -/// A item card +/// An item card +/// +/// Contains all information about each item card stored in the [`Card Table`](crate::game::card::table::Table) #[derive(PartialEq, Eq, Clone, Hash, Debug)] #[derive(serde::Serialize, serde::Deserialize)] pub struct Item { - /// The digimon's name + /// The item's name /// /// An ascii string with 20 characters at most pub name: ascii::AsciiString, - /// The digimon's effect description. + /// The effect's 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 + /// The effect's arrow color #[serde(default)] pub effect_arrow_color: Option, - /// The effect conditions + /// The effect's conditions #[serde(default)] pub effect_conditions: [Option; 2], - /// The effects themselves + /// The effects #[serde(default)] pub effects: [Option; 3], @@ -248,19 +250,16 @@ impl Bytes for Item util::write_null_ascii_string(self.name.as_ref(), bytes.name) .map_err(ToBytesError::Name)?; - // Effect conditions + // Effects 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 )?; - // Support arrow color Option::::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) diff --git a/src/game/util.rs b/src/game/util.rs index f2b1369..03979a9 100644 --- a/src/game/util.rs +++ b/src/game/util.rs @@ -103,58 +103,6 @@ pub macro array_split_mut { }} } -// Types -//-------------------------------------------------------------------------------------------------- - /// Error type for `read_null_terminated_string` - #[derive(Debug, derive_more::Display)] - pub enum ReadNullTerminatedStringError - { - /// No null was found on a string - #[display(fmt = "No null was found on a null terminated string")] - NoNull, - - /// A string could not be converted to utf8 - #[display(fmt = "Could not convert the string to utf8")] - Utf8( std::str::Utf8Error ), - } - - impl std::error::Error for ReadNullTerminatedStringError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::NoNull => None, - Self::Utf8(err) => Some(err), - } - } - } -//-------------------------------------------------------------------------------------------------- - -// Impl -//-------------------------------------------------------------------------------------------------- - -//-------------------------------------------------------------------------------------------------- - -// Functions -//-------------------------------------------------------------------------------------------------- - - - /// Reads a string from a buffer, stopping at the first null character found - /// - /// # Errors - /// - `NoNull`: If no null character was found until the end of the buffer. - /// - `Utf8`: If the buffer was not valid utf8. - pub fn read_null_terminated_string(mut buf: &[u8]) -> Result<&str, ReadNullTerminatedStringError> - { - // Search for the first occurence of null and reduce the buffer to before it. - // If not found, then the string was not null terminated, so return Err - if let Some(first_null) = buf.iter().position(|&b| b == 0) { buf = &buf[0..first_null]; } - else { return Err( ReadNullTerminatedStringError::NoNull ); } - - // Else try to conver the buffer into a utf8 str. - Ok( std::str::from_utf8( buf ).map_err(ReadNullTerminatedStringError::Utf8)? ) - } -//-------------------------------------------------------------------------------------------------- - - /// Error type for [`read_null_ascii_string`] #[derive(Debug)]