Added proper documentation to moves.rs, support_condition.rs and support_effect.rs

This commit is contained in:
2020-04-23 04:00:36 +01:00
parent 36b32bd568
commit f2c0218096
6 changed files with 554 additions and 520 deletions

View File

@@ -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<Self, Self::FromError>
{
// 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

View File

@@ -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 {

View File

@@ -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<Self, Self::FromError>
{
/// 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<Self, Self::FromError>
{
// 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(())
}
//--------------------------------------------------------------------------------------------------
}

View File

@@ -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<DigimonProperty>,
/// 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<DigimonProperty>,
/// 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<Self, Self::FromError>
{
// 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<Self, Self::FromError>
{
// 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(())
}
//--------------------------------------------------------------------------------------------------
}

View File

@@ -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:
///
/// `<property> = ( <A> + <Y> ) + ( <C> <op> ( <B> + <X> ) )`
#[serde(rename = "Change property")]
ChangeProperty {
property: DigimonProperty,
a: Option<DigimonProperty>,
b: Option<DigimonProperty>,
c: Option<DigimonProperty>,
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:
///
/// `<property> = ( <A> + <Y> ) + ( <C> <op> ( <B> + <X> ) )`
#[serde(rename = "Change property")]
ChangeProperty {
property: DigimonProperty,
/// A player uses an attack type
#[serde(rename = "Use attack")]
UseAttack {
player: PlayerType,
attack: AttackType,
},
a: Option<DigimonProperty>,
b: Option<DigimonProperty>,
c: Option<DigimonProperty>,
/// Set the temp slot
///
/// # Equation
/// This variant uses the following equation
/// to calculate the property:
///
/// `<temp slot> = <A> + (<B> <op> <C>)`
#[serde(rename = "Set temp slot")]
SetTempSlot {
a: Option<DigimonProperty>,
b: Option<DigimonProperty>,
c: Option<DigimonProperty>,
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:
///
/// `<temp slot> = <A> + (<B> <op> <C>)`
#[serde(rename = "Set temp slot")]
SetTempSlot {
a: Option<DigimonProperty>,
b: Option<DigimonProperty>,
c: Option<DigimonProperty>,
/// 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<Self, Self::FromError>
{
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<Self, Self::FromError>
{
// 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(())
}
}

View File

@@ -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<T, !>` 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,