From f2c0218096c4c0d298a6ff730495fc364ecc3c95 Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Thu, 23 Apr 2020 04:00:36 +0100 Subject: [PATCH] Added proper documentation to `moves.rs`, `support_condition.rs` and `support_effect.rs` --- src/game/card/digimon.rs | 47 +- src/game/card/property.rs | 2 +- src/game/card/property/moves.rs | 163 +++--- src/game/card/property/support_condition.rs | 252 ++++---- src/game/card/property/support_effect.rs | 608 +++++++++++--------- src/lib.rs | 2 + 6 files changed, 554 insertions(+), 520 deletions(-) diff --git a/src/game/card/digimon.rs b/src/game/card/digimon.rs index d678c8d..9c82f37 100644 --- a/src/game/card/digimon.rs +++ b/src/game/card/digimon.rs @@ -1,9 +1,9 @@ //! A digimon card //! -//! This module stores the [Digimon] struct, which describes a digimon card. +//! This module stores 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: +//! The digimon card has a size of `0x138` bytes, and it's layout is the following: //! //! | Offset | Size | Type | Name | Location | Details | //! |--------|------|----------------------|---------------------------|------------------------|-------------------------------------------------------------------------------------| @@ -12,7 +12,7 @@ //! | 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 | +//! | 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` | | @@ -48,7 +48,7 @@ use crate::game::{ /// A digimon card /// -/// Contains all information about each digimon stored in the [`Card Table`](table::Table) +/// 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 @@ -192,34 +192,19 @@ pub enum FromBytesError EffectThird( #[error(source)] property::support_effect::FromBytesError ), } -/// The error type thrown by [`Bytes::to_bytes`] -#[derive(Debug)] -#[derive(derive_more::Display, err_impl::Error)] -pub enum ToBytesError -{ - /// Unable to write a move - #[display(fmt = "Unable to write the {} move", name)] - Move { - name: &'static str, - #[error(source)] - err: property::moves::ToBytesError, - }, -} - impl Bytes for Digimon { type ByteArray = [u8; 0x138]; type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result { // Get all byte arrays we need util::array_split!(bytes, 0x00..0x1d => _, - 0x1d..0x39 => move_circle, + 0x1d..0x39 => move_circle, 0x39..0x55 => move_triangle, - 0x55..0x71 => move_cross, + 0x55..0x71 => move_cross, 0x71..0x91 => condition_first, 0x91..0xb1 => condition_second, 0xb1..0xc1 => effect_first, @@ -316,7 +301,7 @@ impl Bytes for Digimon }) } - type ToError = ToBytesError; + type ToError = !; fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { // Get all byte arrays we need @@ -328,9 +313,9 @@ impl Bytes for Digimon 0x19..0x1a => dp_give, 0x1a..0x1b => unknown_1a, 0x1b..0x1d => hp, - 0x1d..0x39 => move_circle, + 0x1d..0x39 => move_circle, 0x39..0x55 => move_triangle, - 0x55..0x71 => move_cross, + 0x55..0x71 => move_cross, 0x71..0x91 => condition_first, 0x91..0xb1 => condition_second, 0xb1..0xc1 => effect_first, @@ -375,9 +360,9 @@ impl Bytes for Digimon LittleEndian::write_u16(hp, self.hp); // Moves - self. move_circle.to_bytes( move_circle ).map_err(|err| ToBytesError::Move{ name: "circle" , err })?; - self.move_triangle.to_bytes( move_triangle ).map_err(|err| ToBytesError::Move{ name: "triangle", err })?; - self. move_cross.to_bytes( move_cross ).map_err(|err| ToBytesError::Move{ name: "cross" , err })?; + self. move_circle.to_bytes( move_circle )?; + self.move_triangle.to_bytes( move_triangle )?; + self. move_cross.to_bytes( move_cross )?; // Support conditions // Note: Although support conditions and effects aren't written if they're None, @@ -391,17 +376,13 @@ impl Bytes for Digimon if let Some(support_effect) = &self.effects[2] { support_effect.to_bytes( effect_third )?; } // Cross move - if let Some(move_cross) = self.cross_move_effect { move_cross.to_bytes( &mut cross_move_effect[0] ) - .expect("Unable to convert cross move effect to bytes") - }; + if let Some(move_cross) = self.cross_move_effect { move_cross.to_bytes( &mut cross_move_effect[0] )? }; // Unknown unknown_e2[0] = self.unknown_e2; // Support arrow color - if let Some(arrow_color) = self.effect_arrow_color { arrow_color.to_bytes( &mut effect_arrow_color[1] ) - .expect("Unable to convert arrow color to bytes"); - } + if let Some(arrow_color) = self.effect_arrow_color { arrow_color.to_bytes( &mut effect_arrow_color[0] )? } // effect_description // Note: Each string is at most [char; 20], this cannot fail diff --git a/src/game/card/property.rs b/src/game/card/property.rs index d2bfc47..7583a6c 100644 --- a/src/game/card/property.rs +++ b/src/game/card/property.rs @@ -68,7 +68,7 @@ macro_rules! generate_enum_property_mod )* } - /// Error type for [`Bytes::from_bytes`] + /// Error type for [`$crate::game::Bytes::from_bytes`] #[derive(Debug)] #[derive(::derive_more::Display, ::err_impl::Error)] $mod_vis enum FromBytesError { diff --git a/src/game/card/property/moves.rs b/src/game/card/property/moves.rs index ef1ffde..de57f4b 100644 --- a/src/game/card/property/moves.rs +++ b/src/game/card/property/moves.rs @@ -1,88 +1,95 @@ -// Crate -//-------------------------------------------------------------------------------------------------- - // Game - use crate::game::util; - use crate::game::Bytes; -//-------------------------------------------------------------------------------------------------- +//! A digimon's move +//! +//! This module contains the [`Move`] struct, which describes a generic move. +//! +//! # Layout +//! Each move has a size of `0x1c` bytes, and it's layout is the following: +//! +//! | Offset | Size | Type | Name | Location | Details | +//! |--------|------|----------------------|---------------------------|------------------------|-----------------------------------| +//! | 0x0 | 0x2 | `u16` | Power | `power` | | +//! | 0x2 | 0x4 | `u32` | Unknown | `unknown` | Most likely stores animation data | +//! | 0x4 | 0x16 | `[char; 0x16]` | Name | `name` | Null-terminated | // byteorder -use byteorder::ByteOrder; -use byteorder::LittleEndian; +use byteorder::{ByteOrder, LittleEndian}; -// Types -//-------------------------------------------------------------------------------------------------- - /// A digimon's move - #[derive(PartialEq, Eq, Clone, Hash, Debug)] - #[derive(serde::Serialize, serde::Deserialize)] - pub struct Move +// Crate +use crate::game::{util, Bytes}; + +/// A digimon's move +#[derive(PartialEq, Eq, Clone, Hash, Debug)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Move +{ + /// The move's name + name: arrayvec::ArrayVec<[ascii::AsciiChar; 21]>, + + /// The move's power + power: u16, + + /// The unknown data + unknown: u32, +} + +/// Error type for [`Bytes::FromBytes`] +#[derive(Debug, derive_more::Display, err_impl::Error)] +pub enum FromBytesError +{ + /// Unable to read the move name + #[display(fmt = "Unable to read the move name")] + Name( #[error(source)] util::ReadNullAsciiStringError ), +} + +/// Error type for [`Bytes::ToBytes`] +#[derive(Debug, derive_more::Display, err_impl::Error)] +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 21)", _0)] + NameTooLong( String ), +} + +// Bytes +impl Bytes for Move +{ + type ByteArray = [u8; 0x1c]; + + type FromError = FromBytesError; + fn from_bytes(bytes: &Self::ByteArray) -> Result { - /// The move's name - name: String, - - /// The move's power - power: u16, - - /// The unknown data - unknown: u32, + // And return the move + Ok( Self { + name : util::read_null_ascii_string( &bytes[0x0..0x15] ) + .map_err(FromBytesError::Name)? + .chars().collect(), + power : LittleEndian::read_u16( &bytes[0x0..0x2] ), + unknown: LittleEndian::read_u32( &bytes[0x2..0x6] ), + }) } - /// The error type thrown by `FromBytes` - #[derive(Debug, derive_more::Display, err_impl::Error)] - pub enum FromBytesError + type ToError = !; + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { - /// Unable to convert name to a string - #[display(fmt = "Unable to convert name to a string")] - NameToString( #[error(source)] util::ReadNullTerminatedStringError ), - } - - /// The error type thrown by `ToBytes` - #[derive(Debug, derive_more::Display, err_impl::Error)] - 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 21)", _0)] - NameTooLong( String ), - } -//-------------------------------------------------------------------------------------------------- - -// Impl -//-------------------------------------------------------------------------------------------------- - // Bytes - impl Bytes for Move - { - type ByteArray = [u8; 0x1c]; + // Get all byte arrays we need + util::array_split_mut!(bytes, + 0x0..0x02 => power, + 0x2..0x04 => unknown, + 0x4..0x1c => name, + ); - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { - // And return the move - Ok( Self { - name : util::read_null_terminated_string( &bytes[0x6..0x1c] ).map_err(FromBytesError::NameToString)?.to_string(), - power : LittleEndian::read_u16( &bytes[0x0..0x2] ), - unknown: LittleEndian::read_u32( &bytes[0x2..0x6] ), - }) - } + // Write the name + name.copy_from_slice( + // Note: `self.name` is at most [char; 21], this cannot fail + util::write_null_ascii_string(self.name.as_ref().as_ref(), &mut [0u8; 22]) + .expect("Name was too large for output buffer") + ); - type ToError = ToBytesError; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { - // Write the name - bytes[0x6..0x1c].copy_from_slice( &{ - // Check if our name is too big - if self.name.len() >= 0x16 { return Err( ToBytesError::NameTooLong( self.name.clone() ) ); } - - // Else make the buffer and copy everything over - let mut buf = [0u8; 0x16]; - buf[ 0..self.name.len() ].copy_from_slice( self.name.as_bytes() ); - buf - }); - - // Then write the power and the unknown - LittleEndian::write_u16(&mut bytes[0x0..0x2], self.power); - LittleEndian::write_u32(&mut bytes[0x2..0x6], self.unknown); - - // And return Ok - Ok(()) - } + // Then write the power and the unknown + LittleEndian::write_u16(power , self.power ); + LittleEndian::write_u32(unknown, self.unknown); + + // And return Ok + Ok(()) } -//-------------------------------------------------------------------------------------------------- +} diff --git a/src/game/card/property/support_condition.rs b/src/game/card/property/support_condition.rs index 05f6474..262e54b 100644 --- a/src/game/card/property/support_condition.rs +++ b/src/game/card/property/support_condition.rs @@ -1,146 +1,134 @@ -// Crate -//-------------------------------------------------------------------------------------------------- - // Game - use crate::game::{Bytes}; - use crate::game::card::property::{DigimonProperty, SupportConditionOperation}; -//-------------------------------------------------------------------------------------------------- +//! A digimon's support condition +//! +//! This module contains the [`SupportCondition`] struct, which describes a condition for a support effect. +//! +//! # Layout +//! Each support condition has a size of `0x20` bytes, and it's layout is the following: +//! +//! TODO: Layout +//! | Offset | Size | Type | Name | Location | Details | +//! |--------|------|----------------------|---------------------------|------------------------|-----------------------------------| // byteorder use byteorder::{ByteOrder, LittleEndian}; -// Types -//-------------------------------------------------------------------------------------------------- - /// A digimon's support effect condition - #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] - #[derive(serde::Serialize, serde::Deserialize)] - pub struct SupportCondition - { - /// If the effect should throw a misfire if the condition isn't met - misfire: bool, - - /// The condition type - cond: DigimonProperty, - - /// The type argument - type_arg: Option, - - /// The number argument - num_arg: u16, - - /// The operation - operation: SupportConditionOperation, - - /// Unknown - unknown: [u8; 16], - } - - /// The error type thrown by `FromBytes` - #[derive(Debug, derive_more::Display)] - pub enum FromBytesError - { - /// Unable to read the condition - #[display(fmt = "Unable to read the effect condition")] - Condition( crate::game::card::property::digimon_property::FromBytesError ), - - /// Unable to read a property argument - #[display(fmt = "Unable to read the property argument")] - PropertyArgument( crate::game::card::property::digimon_property::FromBytesError ), - - /// Unable to read the effect operation - #[display(fmt = "Unable to read the effect operation")] - Operation( crate::game::card::property::support_condition_operation::FromBytesError ), - } - - impl std::error::Error for FromBytesError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Condition(err) | - Self::PropertyArgument(err) => Some(err), - Self::Operation(err) => Some(err), - } - } - } -//-------------------------------------------------------------------------------------------------- +// Crate +use crate::game::{ + Bytes, + card::property::{ + self, DigimonProperty, SupportConditionOperation + }, +}; -// Impl -//-------------------------------------------------------------------------------------------------- - impl SupportCondition +/// A digimon's support effect condition +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct SupportCondition +{ + /// If the effect should throw a misfire if the condition isn't met + misfire: bool, + + /// The condition type + cond: DigimonProperty, + + /// The type argument + type_arg: Option, + + /// The number argument + num_arg: u16, + + /// The operation + operation: SupportConditionOperation, + + /// Unknown + unknown: [u8; 16], +} + +/// The error type thrown by `FromBytes` +#[derive(Debug)] +#[derive(derive_more::Display, err_impl::Error)] +pub enum FromBytesError +{ + /// Unable to read the condition + #[display(fmt = "Unable to read the effect condition")] + Condition( #[error(source)] property::digimon_property::FromBytesError ), + + /// Unable to read a property argument + #[display(fmt = "Unable to read the property argument")] + PropertyArgument( #[error(source)] property::digimon_property::FromBytesError ), + + /// Unable to read the effect operation + #[display(fmt = "Unable to read the effect operation")] + Operation( #[error(source)] property::support_condition_operation::FromBytesError ), +} + +// Bytes +impl Bytes for SupportCondition +{ + type ByteArray = [u8; 0x20]; + + type FromError = FromBytesError; + fn from_bytes(bytes: &Self::ByteArray) -> Result { + // Get the condition + let cond = DigimonProperty::from_bytes( &bytes[0x2] ) + .map_err(FromBytesError::Condition)?; + // And return the move + Ok( Self { + misfire: (bytes[0x0] != 0), + cond, + + type_arg: (bytes[0x8] != 0) + .then(|| DigimonProperty::from_bytes( &bytes[0x8] )) + .transpose() + .map_err(FromBytesError::PropertyArgument)?, + + num_arg: LittleEndian::read_u16( &bytes[0x14..0x16] ), + + operation: SupportConditionOperation::from_bytes( &bytes[0x1a] ) + .map_err(FromBytesError::Operation)?, + + unknown: [ + bytes[0x3], bytes[0x4], bytes[0x5], bytes[0x6], bytes[0x7], + + bytes[0x9], bytes[0xa ], bytes[0xb ], bytes[0xc ], bytes[0xd ], bytes[0xe], + bytes[0xf], bytes[0x10], bytes[0x11], bytes[0x12], bytes[0x13], + ] + }) } - // Bytes - impl Bytes for SupportCondition + type ToError = !; + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { - type ByteArray = [u8; 0x20]; + // 0x0 - Misfire + bytes[0x0] = if self.misfire { 1 } else { 0 }; - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { - // Get the condition - let cond = DigimonProperty::from_bytes( &bytes[0x2] ).map_err(FromBytesError::Condition)?; - - // And return the move - Ok( Self { - misfire: { bytes[0x0] != 0 }, - cond, - - type_arg: if bytes[0x8] != 0 { Some( - DigimonProperty::from_bytes( &bytes[0x8] ).map_err(FromBytesError::PropertyArgument)? - )} else { None }, - - num_arg: LittleEndian::read_u16( &bytes[0x14..0x16] ), - - operation: SupportConditionOperation::from_bytes( &bytes[0x1a] ).map_err(FromBytesError::Operation)?, - - unknown: [ - bytes[0x3], bytes[0x4], bytes[0x5], bytes[0x6], bytes[0x7], - - bytes[0x9], bytes[0xa ], bytes[0xb ], bytes[0xc ], bytes[0xd ], bytes[0xe], - bytes[0xf], bytes[0x10], bytes[0x11], bytes[0x12], bytes[0x13], - ] - }) - } + // 0x1 - Always zero + bytes[0x1] = 0; - type ToError = !; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { - // 0x0 - Misfire - bytes[0x0] = if self.misfire { 1 } else { 0 }; - - // 0x1 - Always zero - bytes[0x1] = 0; - - // 0x2 - Condition - #[allow(clippy::diverging_sub_expression)] { // False positive - self.cond.to_bytes(&mut bytes[0x2]) - .expect("Unable to convert condition to bytes"); - } - - // 0x3..0x8 - Unknown[0..5] - bytes[0x3..0x8].copy_from_slice( &self.unknown[0..5] ); - - // 0x8 - Type arg / 0 if None - if let Some(type_arg) = self.type_arg { - type_arg.to_bytes(&mut bytes[0x8]) - .expect("Unable to convert type argument to bytes") - } - else { bytes[0x8] = 0; } - - // 0x9..0x14 - Unknown[0x5..0x10] - bytes[0x9..0x14].copy_from_slice( &self.unknown[0x5..0x10] ); - - // 0x14..0x16 - Number arg - LittleEndian::write_u16(&mut bytes[0x14..0x16], self.num_arg); - - // 0x1a - Operation arg - #[allow(clippy::diverging_sub_expression)] { // False positive - self.operation.to_bytes(&mut bytes[0x1a]) - .expect("Unable to convert operation to bytes"); - } - - // And return OK - Ok(()) + // 0x2 - Condition + self.cond.to_bytes(&mut bytes[0x2])?; + + // 0x3..0x8 - Unknown[0..5] + bytes[0x3..0x8].copy_from_slice( &self.unknown[0..5] ); + + // 0x8 - Type arg / 0 if None + if let Some(type_arg) = self.type_arg { + type_arg.to_bytes(&mut bytes[0x8])?; } + else { bytes[0x8] = 0; } + + // 0x9..0x14 - Unknown[0x5..0x10] + bytes[0x9..0x14].copy_from_slice( &self.unknown[0x5..0x10] ); + + // 0x14..0x16 - Number arg + LittleEndian::write_u16(&mut bytes[0x14..0x16], self.num_arg); + + // 0x1a - Operation arg + self.operation.to_bytes(&mut bytes[0x1a])?; + + // And return OK + Ok(()) } -//-------------------------------------------------------------------------------------------------- +} diff --git a/src/game/card/property/support_effect.rs b/src/game/card/property/support_effect.rs index 394e71e..ae918d5 100644 --- a/src/game/card/property/support_effect.rs +++ b/src/game/card/property/support_effect.rs @@ -1,298 +1,354 @@ -//! Support effects - -// Lints -#![allow( - // We have a lot of `a, b, c, x, y` from the formulas, - // but we can't change those names since they're the actual - // names of the variables in the formulas - clippy::many_single_char_names -)] +//! A digimon's support effect +//! +//! This module contains the [`SupportEffect`] struct, which describes a support effect. +//! +//! # Layout +//! Each support effect has a size of `0x10` bytes, and it's general layout is the following: +//! +//! | Offset | Size | Type | Name | Location | Details | +//! |--------|------|----------------------|---------------------------|------------------------|---------------------------------------------------------------| +//! | 0x0 | 0x1 | `bool` | Exists | N/A | If `0`, the effect does not exist | +//! | 0x1 | 0x1 | N/A | Effect Type | N/A | Determines which [`SupportEffect`] variant is used. | +//! | 0x2 | 0xe | N/A | Arguments | N/A | The arguments used for the current [`SupportEffect`] variant. | // byteorder use byteorder::{ByteOrder, LittleEndian}; // Crate -use crate::{ - game::{ - Bytes, - card::property::{DigimonProperty, SupportEffectOperation, AttackType, PlayerType, Slot}, +use crate::game::{ + Bytes, + util, + card::property::{ + self, DigimonProperty, SupportEffectOperation, AttackType, PlayerType, Slot }, }; -// Types -//-------------------------------------------------------------------------------------------------- - /// A digimon's support effects - #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] - #[derive(serde::Serialize, serde::Deserialize)] - #[serde(tag = "type")] - // TODO: Move this `allow` to the variant once clippy allows - #[allow(clippy::pub_enum_variant_names)] // `Effect` on `VoidOpponentSupportEffect` isn't refering to the enum - pub enum SupportEffect - { - /// Changes a property of either digimon - /// - /// # Valid properties - /// Only the following properties are valid for this effect: - /// - `OwnSpeciality` / `OpnSpeciality` , - /// - `OwnHP` / `OpnHP` , - /// - `OwnCircleAttack` / `OpnCircleAttack` , - /// - `OwnTriangleAttack` / `OpnTriangleAttack`, - /// - `OwnCrossAttack` / `OpnCrossAttack` , - /// - `OwnAttack` / `OpnAttack` , - /// - `OwnLevel` / `OpnLevel` , - /// - /// # Equation - /// This variant uses the following equation - /// to calculate the property: - /// - /// ` = ( + ) + ( ( + ) )` - #[serde(rename = "Change property")] - ChangeProperty { - property: DigimonProperty, - - a: Option, - b: Option, - c: Option, - - x: u16, - y: u16, - - op: SupportEffectOperation, - }, +/// A digimon's support effects +/// +/// As this type is wildly volatile in which arguments it uses and from where, +/// it is an `enum` with struct variants instead of a struct. This simplifices argument +/// verification and, from a language perspective, makes more sense as an implementation. +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(tag = "type")] +// TODO: Move this `allow` to the variant once clippy allows +#[allow(clippy::pub_enum_variant_names)] // `Effect` on `VoidOpponentSupportEffect` isn't refering to the enum +pub enum SupportEffect +{ + /// Changes a property of either digimon + /// + /// # Valid properties + /// Only the following properties are valid for this effect: + /// - `OwnSpeciality` / `OpnSpeciality` , + /// - `OwnHP` / `OpnHP` , + /// - `OwnCircleAttack` / `OpnCircleAttack` , + /// - `OwnTriangleAttack` / `OpnTriangleAttack`, + /// - `OwnCrossAttack` / `OpnCrossAttack` , + /// - `OwnAttack` / `OpnAttack` , + /// - `OwnLevel` / `OpnLevel` , + /// + /// # Equation + /// This variant uses the following equation + /// to calculate the property: + /// + /// ` = ( + ) + ( ( + ) )` + #[serde(rename = "Change property")] + ChangeProperty { + property: DigimonProperty, - /// A player uses an attack type - #[serde(rename = "Use attack")] - UseAttack { - player: PlayerType, - attack: AttackType, - }, + a: Option, + b: Option, + c: Option, - /// Set the temp slot - /// - /// # Equation - /// This variant uses the following equation - /// to calculate the property: - /// - /// ` = + ( )` - #[serde(rename = "Set temp slot")] - SetTempSlot { - a: Option, - b: Option, - c: Option, - - op: SupportEffectOperation, - }, + x: u16, + y: u16, - /// Moves cards from a slot to another - /// - /// # Valid moves - /// Only the following moves are valid for this effect, for both the player and opponent: - /// - `Hand` -> `Offline` - /// - `Hand` -> `Online` - /// - `Online` -> `Offline` - /// - `Offline` -> `Online` - /// - `Dp` -> `Offline` - #[serde(rename = "Move cards")] - MoveCards { - player : PlayerType, - source : Slot, - destination: Slot, - - count: u16, - }, - - /// Shuffles a player's online deck - #[serde(rename = "Shuffle online deck")] - ShuffleOnlineDeck { - player: PlayerType, - }, - - /// Voids the opponent's support effect - #[serde(rename = "Void opponent support effect")] - VoidOpponentSupportEffect, - - /// Voids the opponent's support option effect - #[serde(rename = "Void opponent support option effect")] - VoidOpponentSupportOptionEffect, - - /// Picks the partner from the online deck and puts it onto the hand - #[serde(rename = "Pick partner card")] - PickPartnerCard, - - /// Cycles the opponent's attack types - /// - /// # Order - /// The order is the following: - /// - `Circle` -> `Triangle` - /// - `Triangle` -> `Cross` - /// - `Cross` -> `Circle` - #[serde(rename = "Cycle opponent attack type")] - CycleOpponentAttackType, - - /// If the digimon is Ko'd it revives with health - #[serde(rename = "Ko'd digimon revives")] - KoDigimonRevives { - health: u16, - }, - - /// A player draws cards - #[serde(rename = "Draw cards")] - DrawCards { - player: PlayerType, - - count: u16, - }, - - /// Own attack becomes Eat Up HP - #[serde(rename = "Own attack becomes Eat Up HP")] - OwnAttackBecomesEatUpHP, - - /// A player attacks first - #[serde(rename = "Attack first")] - AttackFirst { - player: PlayerType - }, - } + op: SupportEffectOperation, + }, + /// A player uses an attack type + #[serde(rename = "Use attack")] + UseAttack { + player: PlayerType, + attack: AttackType, + }, - /// The error type thrown by `FromBytes` - #[derive(Debug, derive_more::Display)] - pub enum FromBytesError - { - /// An unknown effect type was found - #[display(fmt = "Unknown byte for an effect type: {}", byte)] - UnknownEffectType { byte: u8 }, + /// Set the temp slot + /// + /// # Equation + /// This variant uses the following equation + /// to calculate the property: + /// + /// ` = + ( )` + #[serde(rename = "Set temp slot")] + SetTempSlot { + a: Option, + b: Option, + c: Option, - /// An unknown property argument was found - #[display(fmt = "An unknown property was found for the {} property", rank)] - PropertyArgument { - rank: &'static str, - - - err: crate::game::card::property::digimon_property::FromBytesError, - }, - - /// An unknown operation was found - #[display(fmt = "An unknown operation was found")] - Operation( crate::game::card::property::support_effect_operation::FromBytesError ), - - /// An unknown attack type was found - #[display(fmt = "An unknown attack type was found")] - AttackType( crate::game::card::property::attack_type::FromBytesError ), - } + op: SupportEffectOperation, + }, - impl std::error::Error for FromBytesError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::UnknownEffectType{ .. } => None, - Self::PropertyArgument{ err, ..} => Some(err), - Self::Operation(err) => Some(err), - Self::AttackType(err) => Some(err), - } - } - } -//-------------------------------------------------------------------------------------------------- + /// Moves cards from a slot to another + /// + /// # Valid moves + /// Only the following moves are valid for this effect, for both the player and opponent: + /// - `Hand` -> `Offline` + /// - `Hand` -> `Online` + /// - `Online` -> `Offline` + /// - `Offline` -> `Online` + /// - `Dp` -> `Offline` + #[serde(rename = "Move cards")] + MoveCards { + player : PlayerType, + source : Slot, + destination: Slot, + + count: u16, + }, + + /// Shuffles a player's online deck + #[serde(rename = "Shuffle online deck")] + ShuffleOnlineDeck { + player: PlayerType, + }, + + /// Voids the opponent's support effect + #[serde(rename = "Void opponent support effect")] + VoidOpponentSupportEffect, + + /// Voids the opponent's support option effect + #[serde(rename = "Void opponent support option effect")] + VoidOpponentSupportOptionEffect, + + /// Picks the partner from the online deck and puts it onto the hand + #[serde(rename = "Pick partner card")] + PickPartnerCard, + + /// Cycles the opponent's attack types + /// + /// # Order + /// The order is the following: + /// - `Circle` -> `Triangle` + /// - `Triangle` -> `Cross` + /// - `Cross` -> `Circle` + #[serde(rename = "Cycle opponent attack type")] + CycleOpponentAttackType, + + /// If the digimon is Ko'd it revives with health + #[serde(rename = "Ko'd digimon revives")] + KoDigimonRevives { + health: u16, + }, + + /// A player draws cards + #[serde(rename = "Draw cards")] + DrawCards { + player: PlayerType, + count: u16, + }, + + /// Own attack becomes Eat Up HP + #[serde(rename = "Own attack becomes Eat Up HP")] + OwnAttackBecomesEatUpHP, + + /// A player attacks first + #[serde(rename = "Attack first")] + AttackFirst { + player: PlayerType + }, +} -// Impl -//-------------------------------------------------------------------------------------------------- - impl Bytes for SupportEffect +/// Error type for [`Bytes::from_bytes`] +#[derive(Debug)] +#[derive(derive_more::Display, err_impl::Error)] +pub enum FromBytesError +{ + /// Unknown property for first property argument + #[display(fmt = "Unknown property for first property argument")] + FirstProperty( #[error(source)] property::digimon_property::FromBytesError ), + + /// Unknown property for second property argument + #[display(fmt = "Unknown property for second property argument")] + SecondProperty( #[error(source)] property::digimon_property::FromBytesError ), + + /// Unknown property for third property argument + #[display(fmt = "Unknown property for third property argument")] + ThirdProperty( #[error(source)] property::digimon_property::FromBytesError ), + + /// Unknown operation argument + #[display(fmt = "Unknown operation argument")] + Operation( #[error(source)] property::support_effect_operation::FromBytesError ), + + /// Unknown attack type for [`SupportEffect::UseAttack`] + #[display(fmt = "Unknown attack type")] + UseAttackAttackType( #[error(source)] property::attack_type::FromBytesError ), + + /// Unknown effect type + #[display(fmt = "Unknown byte for an effect type: {}", "byte")] + EffectType { byte: u8 }, +} + +impl Bytes for SupportEffect +{ + type ByteArray = [u8; 0x10]; + + type FromError = FromBytesError; + + /// `bytes` should include the `exists` byte + fn from_bytes(bytes: &Self::ByteArray) -> Result { - type ByteArray = [u8; 0x10]; + // Utility uses + use PlayerType::{Player, Opponent}; + use Slot::{Hand, Online as OnlineDeck, Offline as OfflineDeck, Dp as DpSlot}; - type FromError = FromBytesError; - /// `bytes` should include the `exists` byte - fn from_bytes(bytes: &Self::ByteArray) -> Result - { - // Assert that we do exist - assert_ne!(bytes[0x0], 0); - - // The effect type byte - let effect_type_byte = bytes[0x1]; - - // The properties - let a = if bytes[0x2] != 0 { - Some( DigimonProperty::from_bytes( &bytes[0x2] ) .map_err(|err| FromBytesError::PropertyArgument{ rank: "1st", err })? ) - } else { None }; - - let b = if bytes[0x4] != 0 { - Some( DigimonProperty::from_bytes( &bytes[0x4] ) .map_err(|err| FromBytesError::PropertyArgument{ rank: "2nd", err })? ) - } else { None }; - - let c = if bytes[0x6] != 0 { - Some( DigimonProperty::from_bytes( &bytes[0x6] ) .map_err(|err| FromBytesError::PropertyArgument{ rank: "3rd", err })? ) - } else { None }; - - // The numbers - let x = LittleEndian::read_u16( &bytes[0xa..0xc] ); - let y = LittleEndian::read_u16( &bytes[0xc..0xe] ); - - // The operation - let op = SupportEffectOperation::from_bytes( &bytes[0xf] ) .map_err(FromBytesError::Operation)?; - - // Check what the effect type is - match effect_type_byte - { - 0..=13 => { - Ok( Self::ChangeProperty { - // Note: unwrapping is fine here because we know that `effect_type_byte+1` is between 1 and 14 inclusive - property: DigimonProperty::from_bytes( &(effect_type_byte+1) ) - .expect("Unable to get digimon property from bytes"), - a, b, c, x, y, op, - }) - }, - - // Take lower byte from `x` for these - 16 => { Ok( Self::UseAttack{ player: PlayerType::Player , attack: AttackType::from_bytes( &x.to_le_bytes()[0] ) .map_err(FromBytesError::AttackType)? } ) }, - 17 => { Ok( Self::UseAttack{ player: PlayerType::Opponent, attack: AttackType::from_bytes( &x.to_le_bytes()[0] ) .map_err(FromBytesError::AttackType)? } ) }, - - - 25 => { Ok( Self::SetTempSlot{ a, b, c, op } ) }, - - 26 => { Ok( Self::MoveCards{ player: PlayerType::Player , source: Slot::Hand , destination: Slot::Offline, count: y } ) }, - 27 => { Ok( Self::MoveCards{ player: PlayerType::Opponent, source: Slot::Hand , destination: Slot::Offline, count: y } ) }, - - 30 => { Ok( Self::MoveCards{ player: PlayerType::Player , source: Slot::Hand , destination: Slot::Online , count: y } ) }, - 31 => { Ok( Self::MoveCards{ player: PlayerType::Opponent, source: Slot::Hand , destination: Slot::Online , count: y } ) }, - - 32 => { Ok( Self::MoveCards{ player: PlayerType::Player , source: Slot::Online , destination: Slot::Offline, count: y } ) }, - 33 => { Ok( Self::MoveCards{ player: PlayerType::Opponent, source: Slot::Online , destination: Slot::Offline, count: y } ) }, - - 34 => { Ok( Self::MoveCards{ player: PlayerType::Player , source: Slot::Offline, destination: Slot::Online , count: y } ) }, - 35 => { Ok( Self::MoveCards{ player: PlayerType::Opponent, source: Slot::Offline, destination: Slot::Online , count: y } ) }, - - 36 => { Ok( Self::MoveCards{ player: PlayerType::Player , source: Slot::Dp , destination: Slot::Offline, count: y } ) }, - 37 => { Ok( Self::MoveCards{ player: PlayerType::Opponent, source: Slot::Dp , destination: Slot::Offline, count: y } ) }, - - - 42 => { Ok( Self::ShuffleOnlineDeck{ player: PlayerType::Player } ) }, - 43 => { Ok( Self::ShuffleOnlineDeck{ player: PlayerType::Opponent } ) }, - - 44 => { Ok( Self::VoidOpponentSupportEffect ) }, - 45 => { Ok( Self::VoidOpponentSupportOptionEffect ) }, - - 46 => { Ok( Self::PickPartnerCard ) }, - - 47 => { Ok( Self::CycleOpponentAttackType ) }, - - 48 => { Ok( Self::KoDigimonRevives{ health: y } ) }, - - 49 => { Ok( Self::DrawCards{ player: PlayerType::Player , count: y } ) }, - 50 => { Ok( Self::DrawCards{ player: PlayerType::Opponent, count: y } ) }, - - 51 => { Ok( Self::OwnAttackBecomesEatUpHP ) }, - - 52 => { Ok( Self::AttackFirst{ player: PlayerType::Player } ) }, - 53 => { Ok( Self::AttackFirst{ player: PlayerType::Opponent } ) }, - - _ => Err( FromBytesError::UnknownEffectType{ byte: effect_type_byte } ), - } - } + // The effect type byte + let effect_type_byte = bytes[0x1]; - type ToError = !; - fn to_bytes(&self, _bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> + // The property argument getters + let get_a = || (bytes[0x2] != 0) + .then(|| DigimonProperty::from_bytes( &bytes[0x2] )) + .transpose() + .map_err(FromBytesError::FirstProperty); + let get_b = || (bytes[0x4] != 0) + .then(|| DigimonProperty::from_bytes( &bytes[0x4] )) + .transpose() + .map_err(FromBytesError::SecondProperty); + let get_c = || (bytes[0x6] != 0) + .then(|| DigimonProperty::from_bytes( &bytes[0x6] )) + .transpose() + .map_err(FromBytesError::ThirdProperty); + let get_attack_type = || AttackType::from_bytes( &bytes[0xa] ) // Lower byte of `x` + .map_err(FromBytesError::UseAttackAttackType); + + // The number arguments + let x = LittleEndian::read_u16( &bytes[0xa..0xc] ); + let y = LittleEndian::read_u16( &bytes[0xc..0xe] ); + + // The operation argument + let op = SupportEffectOperation::from_bytes( &bytes[0xf] ) + .map_err(FromBytesError::Operation)?; + + // Check what the effect type is + match effect_type_byte { - // Match which effect we are - todo!() + 0..=13 => Ok( Self::ChangeProperty { + // Note: unwrapping is fine here because we know that `effect_type_byte+1` is between 1 and 14 inclusive + property: DigimonProperty::from_bytes( &(effect_type_byte+1) ) + .expect("Unable to get digimon property from bytes"), + a: get_a()?, b: get_b()?, c: get_c()?, x, y, op, + }), + + + // Lower byte of `x` contains the attack type + 16 => Ok( Self::UseAttack{ player: Player , attack: get_attack_type()? }), + 17 => Ok( Self::UseAttack{ player: Opponent, attack: get_attack_type()? }), + + + 25 => Ok( Self::SetTempSlot{ a: get_a()?, b: get_b()?, c: get_c()?, op } ), + + 26 => Ok( Self::MoveCards{ player: Player , source: Hand, destination: OfflineDeck, count: y } ), + 27 => Ok( Self::MoveCards{ player: Opponent, source: Hand, destination: OfflineDeck, count: y } ), + + 30 => Ok( Self::MoveCards{ player: Player , source: Hand, destination: OnlineDeck, count: y } ), + 31 => Ok( Self::MoveCards{ player: Opponent, source: Hand, destination: OnlineDeck, count: y } ), + + 32 => Ok( Self::MoveCards{ player: Player , source: OnlineDeck, destination: OfflineDeck, count: y } ), + 33 => Ok( Self::MoveCards{ player: Opponent, source: OnlineDeck, destination: OfflineDeck, count: y } ), + + 34 => Ok( Self::MoveCards{ player: Player , source: OfflineDeck, destination: OnlineDeck, count: y } ), + 35 => Ok( Self::MoveCards{ player: Opponent, source: OfflineDeck, destination: OnlineDeck, count: y } ), + + 36 => Ok( Self::MoveCards{ player: Player , source: DpSlot, destination: OfflineDeck, count: y } ), + 37 => Ok( Self::MoveCards{ player: Opponent, source: DpSlot, destination: OfflineDeck, count: y } ), + + + 42 => Ok( Self::ShuffleOnlineDeck{ player: Player } ), + 43 => Ok( Self::ShuffleOnlineDeck{ player: Opponent } ), + + 44 => Ok( Self::VoidOpponentSupportEffect ), + 45 => Ok( Self::VoidOpponentSupportOptionEffect ), + + 46 => Ok( Self::PickPartnerCard ), + + 47 => Ok( Self::CycleOpponentAttackType ), + + 48 => Ok( Self::KoDigimonRevives{ health: y } ), + + 49 => Ok( Self::DrawCards{ player: Player , count: y } ), + 50 => Ok( Self::DrawCards{ player: Opponent, count: y } ), + + 51 => Ok( Self::OwnAttackBecomesEatUpHP ), + + 52 => Ok( Self::AttackFirst{ player: Player } ), + 53 => Ok( Self::AttackFirst{ player: Opponent } ), + + _ => Err( FromBytesError::EffectType{ byte: effect_type_byte } ), } } -//-------------------------------------------------------------------------------------------------- + + type ToError = !; + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> + { + // Get all byte arrays we need + util::array_split_mut!(bytes, + 0x0..0x01 => exists, + 0x1..0x02 => effect_type, + 0x2..0x03 => bytes_a, + 0x3..0x04 => _, + 0x4..0x05 => bytes_b, + 0x5..0x06 => _, + 0x6..0x07 => bytes_c, + 0x7..0x0a => _, + 0xa..0x0c => bytes_x, + 0xc..0x0e => bytes_y, + 0xe..0x0f => _, + 0xf..0x10 => bytes_op, + ); + + // Set that the effect exists + exists[0] = 1; + + // Check our variant and fill `bytes` with info + #[allow(clippy::unneeded_field_pattern)] // Placeholder + match self { + Self::ChangeProperty { property, a, b, c, x, y, op } => { + property.to_bytes(&mut effect_type[0])?; + effect_type[0] -= 1; + if let Some(a) = a { a.to_bytes(&mut bytes_a[0])?; } + if let Some(b) = b { b.to_bytes(&mut bytes_b[0])?; } + if let Some(c) = c { c.to_bytes(&mut bytes_c[0])?; } + LittleEndian::write_u16(bytes_x, *x); + LittleEndian::write_u16(bytes_y, *y); + op.to_bytes(&mut bytes_op[0])?; + }, + + Self::UseAttack { player: _, attack: _ } => todo!(), + + Self::SetTempSlot { a: _, b: _, c: _, op: _ } => todo!(), + + Self::MoveCards { player: _, source: _, destination: _, count: _ } => todo!(), + + Self::ShuffleOnlineDeck { player: _ } => todo!(), + + Self::VoidOpponentSupportEffect => todo!(), + + Self::VoidOpponentSupportOptionEffect => todo!(), + + Self::PickPartnerCard => todo!(), + + Self::CycleOpponentAttackType => todo!(), + + Self::KoDigimonRevives { health: _ } => todo!(), + + Self::DrawCards { player: _, count: _ } => todo!(), + + Self::OwnAttackBecomesEatUpHP => todo!(), + + Self::AttackFirst { player: _ } => todo!(), + } + + // And return Ok + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 97d2f5e..51a1367 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,8 @@ clippy::identity_op, // Makes sense sometimes for symmetry clippy::items_after_statements, // Sometimes we only introduce items when we first use them. clippy::unseparated_literal_suffix, // We only separate them when they are long + clippy::diverging_sub_expression, // We use `?` on `Result` for extracting the result currently, once a method is done for it, we'll use it. + clippy::match_same_arms, // Sometimes we separate them for clarify and order // TODO: Deal with casts eventually clippy::cast_possible_wrap,