card::Table now stores a single vector of Cards instead of 3 separate.

`Digimon`, `Item` and `Digivolve` are now `Copy`.
This commit is contained in:
Filipe Rodrigues 2021-06-02 11:54:30 +01:00
parent 748a07b208
commit 2c1d33797c
15 changed files with 346 additions and 401 deletions

View File

@ -1,9 +1,10 @@
//! Edit screen
// Imports
use crate::loaded_game::LoadedGame;
use dcb::card::Card;
use eframe::egui;
use crate::{loaded_game::LoadedGame, Card};
/// An edit screen
pub struct EditScreen {
/// Currently selected card
@ -27,7 +28,7 @@ impl EditScreen {
pub fn display_all(screens: &mut Vec<Self>, ctx: &egui::CtxRef, loaded_game: &mut LoadedGame) {
let screen_width = ctx.available_rect().width() / (screens.len() as f32);
for screen in screens {
let card = loaded_game.get_card_from_idx(screen.card_idx);
let card = &mut loaded_game.card_table.cards[screen.card_idx];
egui::SidePanel::left(screen as *const _)
.min_width(screen_width)
@ -35,7 +36,7 @@ impl EditScreen {
.show(ctx, |ui| {
// Header for the card
ui.vertical(|ui| {
ui.heading(card.name());
ui.heading(card.name().as_str());
ui.label(match card {
Card::Digimon(_) => "Digimon",
Card::Item(_) => "Item",

View File

@ -2,7 +2,7 @@
// Imports
use crate::{Card, EditScreen};
use crate::EditScreen;
use anyhow::Context;
use dcb::CardTable;
use dcb_util::StrContainsCaseInsensitive;
@ -11,7 +11,6 @@ use std::{
cell::Cell,
fs,
io::{self, Read, Seek},
ops::Range,
path::{Path, PathBuf},
};
@ -21,7 +20,7 @@ pub struct LoadedGame {
file_path: PathBuf,
/// Card table
card_table: CardTable,
pub card_table: CardTable,
/// Hash of `card_table` within disk
saved_card_table_hash: Cell<u64>,
@ -102,78 +101,15 @@ impl LoadedGame {
dcb_util::hash_of(&self.card_table) != self.saved_card_table_hash.get()
}
/// Returns the digimon's indexes
pub fn digimon_idxs(&self) -> Range<usize> {
0..self.card_table.digimons.len()
}
/// Returns the item's indexes
pub fn item_idxs(&self) -> Range<usize> {
self.card_table.digimons.len()..(self.card_table.digimons.len() + self.card_table.items.len())
}
/// Returns the digivolve's indexes
pub fn digivolve_idxs(&self) -> Range<usize> {
(self.card_table.digimons.len() + self.card_table.items.len())..
(self.card_table.digimons.len() + self.card_table.items.len() + self.card_table.digivolves.len())
}
/// Returns a card given it's index
pub fn get_card_from_idx(&mut self, idx: usize) -> Card {
let digimons_len = self.card_table.digimons.len();
let items_len = self.card_table.items.len();
if self.digimon_idxs().contains(&idx) {
Card::Digimon(&mut self.card_table.digimons[idx])
} else if self.item_idxs().contains(&idx) {
Card::Item(&mut self.card_table.items[idx - digimons_len])
} else if self.digivolve_idxs().contains(&idx) {
Card::Digivolve(&mut self.card_table.digivolves[idx - digimons_len - items_len])
} else {
panic!("Invalid card index");
}
}
/// Swaps two cards in the card table
pub fn swap_cards(&mut self, lhs_idx: usize, rhs_idx: usize) {
let digimon_idxs = self.digimon_idxs();
let item_idxs = self.item_idxs();
let digivolve_idxs = self.digivolve_idxs();
let digimons_len = self.card_table.digimons.len();
let items_len = self.card_table.items.len();
if digimon_idxs.contains(&lhs_idx) && digimon_idxs.contains(&rhs_idx) {
self.card_table.digimons.swap(lhs_idx, rhs_idx);
} else if item_idxs.contains(&lhs_idx) && item_idxs.contains(&rhs_idx) {
self.card_table
.items
.swap(lhs_idx - digimons_len, rhs_idx - digimons_len);
} else if digivolve_idxs.contains(&lhs_idx) && digivolve_idxs.contains(&rhs_idx) {
self.card_table
.digivolves
.swap(lhs_idx - digimons_len - items_len, rhs_idx - digimons_len - items_len);
} else {
panic!("Invalid indexes {} & {}", lhs_idx, rhs_idx);
}
}
/// Displays the card selection menu
pub fn display_card_selection(
&self, card_search: &str, ui: &mut egui::Ui, open_edit_screens: &mut Vec<EditScreen>,
) {
let names = self
.card_table
.digimons
.cards
.iter()
.map(|digimon| digimon.name.as_str())
.chain(self.card_table.items.iter().map(|item| item.name.as_str()))
.chain(
self.card_table
.digivolves
.iter()
.map(|digivolve| digivolve.name.as_str()),
)
.map(|card| card.name().as_str())
.enumerate()
.map(|(idx, name)| (idx, format!("{idx}. {name}")))
.filter(|(_, name)| name.contains_case_insensitive(card_search));
@ -193,9 +129,4 @@ impl LoadedGame {
}
});
}
/// Get a reference to the loaded game's card table.
pub fn card_table(&self) -> &CardTable {
&self.card_table
}
}

View File

@ -10,9 +10,12 @@ mod overview_screen;
mod swap_screen;
// Imports
use dcb::card::property::{
ArrowColor, AttackType, CardType, CrossMoveEffect, DigimonProperty, DigivolveEffect, Effect, EffectCondition,
EffectConditionOperation, EffectOperation, Level, Move, PlayerType, Slot, Speciality,
use dcb::card::{
property::{
ArrowColor, AttackType, CardType, CrossMoveEffect, DigimonProperty, DigivolveEffect, Effect, EffectCondition,
EffectConditionOperation, EffectOperation, Level, Move, PlayerType, Slot, Speciality,
},
Card,
};
use dcb_bytes::Validate;
use dcb_util::{alert, AsciiTextBuffer, StrContainsCaseInsensitive};
@ -237,27 +240,8 @@ impl epi::App for CardEditor {
}
}
/// Digimon, Item or digivolve
pub enum Card<'a> {
Digimon(&'a mut dcb::Digimon),
Item(&'a mut dcb::Item),
Digivolve(&'a mut dcb::Digivolve),
}
impl<'a> Card<'a> {
/// Returns the name of this card
pub fn name(&self) -> &str {
match self {
Card::Digimon(digimon) => digimon.name.as_str(),
Card::Item(item) => item.name.as_str(),
Card::Digivolve(digivolve) => digivolve.name.as_str(),
}
}
}
/// Renders a card
fn render_card(ui: &mut egui::Ui, card: Card) {
fn render_card(ui: &mut egui::Ui, card: &mut Card) {
match card {
Card::Digimon(digimon) => self::render_digimon_card(ui, digimon),
Card::Item(item) => self::render_item_card(ui, item),

View File

@ -24,17 +24,16 @@ pub struct OverviewScreen {
impl OverviewScreen {
/// Creates a new screen
pub fn new(loaded_game: &LoadedGame) -> Self {
let card_table = loaded_game.card_table();
let total_digimons = card_table.digimons.len();
let total_cards = card_table.digimons.len() + card_table.items.len() + card_table.digivolves.len();
let card_table = &loaded_game.card_table;
let total_digimons = card_table.digimons().count();
let total_cards = card_table.cards.len();
let cards_per_speciality = Speciality::iter()
.map(|speciality| {
(
speciality,
card_table
.digimons
.iter()
.digimons()
.filter(|digimon| digimon.speciality == speciality)
.count(),
)

View File

@ -37,14 +37,16 @@ impl SwapScreen {
ui.label("Card type");
crate::render_card_type(ui, &mut self.card_type);
});
let range = match self.card_type {
CardType::Digimon => loaded_game.digimon_idxs(),
CardType::Item => loaded_game.item_idxs(),
CardType::Digivolve => loaded_game.digivolve_idxs(),
};
let range = 0..loaded_game.card_table.cards.len();
if range.is_empty() {
return Results { should_close: false };
}
self.lhs_idx = self.lhs_idx.clamp(range.start, range.end - 1);
self.rhs_idx = self.rhs_idx.clamp(range.start, range.end - 1);
let range = range.start..=(range.end - 1);
ui.horizontal(|ui| {
ui.label("Left");
ui.add(egui::Slider::new(&mut self.lhs_idx, range.clone()));
@ -54,7 +56,7 @@ impl SwapScreen {
ui.add(egui::Slider::new(&mut self.rhs_idx, range));
});
if ui.button("Swap").clicked() {
loaded_game.swap_cards(self.lhs_idx, self.rhs_idx);
loaded_game.card_table.cards.swap(self.lhs_idx, self.rhs_idx);
should_close = true;
}

View File

@ -11,9 +11,9 @@ use eframe::{egui, epi, NativeOptions};
use native_dialog::{FileDialog, MessageDialog, MessageType};
use ref_cast::RefCast;
use std::{
convert::TryFrom,
fs,
io::{self, Read, Seek},
ops::Range,
path::{Path, PathBuf},
};
@ -109,38 +109,6 @@ impl DeckEditor {
Ok(())
}
/// Returns the digimon's indexes
pub fn digimon_idxs(card_table: &CardTable) -> Range<usize> {
0..card_table.digimons.len()
}
/// Returns the item's indexes
pub fn item_idxs(card_table: &CardTable) -> Range<usize> {
card_table.digimons.len()..(card_table.digimons.len() + card_table.items.len())
}
/// Returns the digivolve's indexes
pub fn digivolve_idxs(card_table: &CardTable) -> Range<usize> {
(card_table.digimons.len() + card_table.items.len())..
(card_table.digimons.len() + card_table.items.len() + card_table.digivolves.len())
}
/// Returns a card given it's index
pub fn get_card_from_idx(card_table: &mut CardTable, idx: usize) -> Card {
let digimons_len = card_table.digimons.len();
let items_len = card_table.items.len();
if Self::digimon_idxs(card_table).contains(&idx) {
Card::Digimon(&mut card_table.digimons[idx])
} else if Self::item_idxs(card_table).contains(&idx) {
Card::Item(&mut card_table.items[idx - digimons_len])
} else if Self::digivolve_idxs(card_table).contains(&idx) {
Card::Digivolve(&mut card_table.digivolves[idx - digimons_len - items_len])
} else {
panic!("Invalid card index");
}
}
}
impl Default for DeckEditor {
@ -327,24 +295,6 @@ pub struct LoadedGame {
deck_table: DeckTable,
}
/// Digimon, Item or digivolve
pub enum Card<'a> {
Digimon(&'a mut dcb::Digimon),
Item(&'a mut dcb::Item),
Digivolve(&'a mut dcb::Digivolve),
}
impl<'a> Card<'a> {
/// Returns the name of this card
pub fn name(&self) -> &str {
match self {
Card::Digimon(digimon) => digimon.name.as_str(),
Card::Item(item) => item.name.as_str(),
Card::Digivolve(digivolve) => digivolve.name.as_str(),
}
}
}
/// Renders a deck
fn render_deck(ui: &mut egui::Ui, deck: &mut Deck, card_table: &mut CardTable) {
// Name
@ -363,10 +313,16 @@ fn render_deck(ui: &mut egui::Ui, deck: &mut Deck, card_table: &mut CardTable) {
ui.label("Cards");
for card_id in &mut deck.cards {
ui.horizontal(|ui| {
let card = DeckEditor::get_card_from_idx(card_table, usize::from(card_id.0));
let range = 0..u16::try_from(card_table.cards.len()).expect("Too many cards");
if range.is_empty() {
return;
}
ui.add(egui::Slider::new(&mut card_id.0, 0..=300).clamp_to_range(true));
ui.label(card.name());
let range = 0..=(range.end - 1);
let card = &card_table.cards[usize::from(card_id.0)];
ui.add(egui::Slider::new(&mut card_id.0, range).clamp_to_range(true));
ui.label(card.name().as_str());
});
}
});

View File

@ -4,6 +4,8 @@
//! as well as the card table itself, of all of the cards in the game.
// Modules
#[allow(clippy::module_inception)] // We need to put the `Card` type somewhere
pub mod card;
pub mod digimon;
pub mod digivolve;
pub mod header;
@ -12,6 +14,7 @@ pub mod property;
pub mod table;
// Exports
pub use card::Card;
pub use digimon::Digimon;
pub use digivolve::Digivolve;
pub use header::CardHeader;

146
dcb/src/card/card.rs Normal file
View File

@ -0,0 +1,146 @@
//! A card
// Modules
pub mod error;
// Exports
pub use error::{DeserializeError, SerializeError};
// Imports
use super::property::CardType;
use crate::{Digimon, Digivolve, Item};
use dcb_bytes::{ByteArray, Bytes};
use dcb_util::AsciiStrArr;
use std::io;
/// A card
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub enum Card {
/// Digimon
Digimon(Digimon),
/// Item
Item(Item),
/// Digivolve
Digivolve(Digivolve),
}
impl Card {
/// Deserializes a card
pub fn deserialize<R: io::Read>(card_type: CardType, reader: &mut R) -> Result<Self, DeserializeError> {
let card = match card_type {
CardType::Digimon => {
let mut bytes = <Digimon as Bytes>::ByteArray::zeros();
reader.read_exact(&mut bytes).map_err(DeserializeError::Read)?;
Digimon::from_bytes(&bytes)
.map(Self::Digimon)
.map_err(DeserializeError::ParseDigimon)?
},
CardType::Item => {
let mut bytes = <Item as Bytes>::ByteArray::zeros();
reader.read_exact(&mut bytes).map_err(DeserializeError::Read)?;
Item::from_bytes(&bytes)
.map(Self::Item)
.map_err(DeserializeError::ParseItem)?
},
CardType::Digivolve => {
let mut bytes = <Digivolve as Bytes>::ByteArray::zeros();
reader.read_exact(&mut bytes).map_err(DeserializeError::Read)?;
Digivolve::from_bytes(&bytes)
.map(Self::Digivolve)
.map_err(DeserializeError::ParseDigivolve)?
},
};
Ok(card)
}
/// Serializes a card
pub fn serialize<W: io::Write>(&self, writer: &mut W) -> Result<(), SerializeError> {
match self {
Card::Digimon(digimon) => {
let bytes = digimon.bytes().map_err(SerializeError::SerializeDigimon)?;
writer.write_all(&bytes).map_err(SerializeError::Write)?;
},
Card::Item(item) => {
let bytes = item.bytes().map_err(SerializeError::SerializeItem)?;
writer.write_all(&bytes).map_err(SerializeError::Write)?;
},
Card::Digivolve(digivolve) => {
let bytes = digivolve.bytes().into_ok();
writer.write_all(&bytes).map_err(SerializeError::Write)?;
},
}
Ok(())
}
}
impl Card {
/// Returns the name of this card
#[must_use]
pub const fn name(&self) -> &AsciiStrArr<0x14> {
match self {
Self::Digimon(digimon) => &digimon.name,
Self::Item(item) => &item.name,
Self::Digivolve(digivolve) => &digivolve.name,
}
}
/// Returns this card's type
#[must_use]
pub const fn ty(&self) -> CardType {
match self {
Card::Digimon(_) => CardType::Digimon,
Card::Item(_) => CardType::Item,
Card::Digivolve(_) => CardType::Digivolve,
}
}
/// Returns `true` if the card is [`Digimon`].
#[must_use]
pub const fn is_digimon(&self) -> bool {
matches!(self, Self::Digimon(..))
}
/// Returns `true` if the card is [`Item`].
#[must_use]
pub const fn is_item(&self) -> bool {
matches!(self, Self::Item(..))
}
/// Returns `true` if the card is [`Digivolve`].
#[must_use]
pub const fn is_digivolve(&self) -> bool {
matches!(self, Self::Digivolve(..))
}
/// Returns this card a digimon
#[must_use]
pub const fn as_digimon(&self) -> Option<&Digimon> {
match self {
Self::Digimon(v) => Some(v),
_ => None,
}
}
/// Returns this card an item
#[must_use]
pub const fn as_item(&self) -> Option<&Item> {
match self {
Self::Item(v) => Some(v),
_ => None,
}
}
/// Returns this card a digivolve
#[must_use]
pub const fn as_digivolve(&self) -> Option<&Digivolve> {
match self {
Self::Digivolve(v) => Some(v),
_ => None,
}
}
}

View File

@ -0,0 +1,40 @@
//! Errors
// Imports
use crate::card;
/// Error type for [`Table::deserialize`](super::Table::deserialize)
#[derive(Debug, thiserror::Error)]
pub enum DeserializeError {
/// Unable to read card
#[error("Unable to read card")]
Read(#[source] std::io::Error),
/// Unable to deserialize a digimon card
#[error("Unable to deserialize digimon card")]
ParseDigimon(#[source] card::digimon::FromBytesError),
/// Unable to deserialize an item card
#[error("Unable to deserialize item card")]
ParseItem(#[source] card::item::FromBytesError),
/// Unable to deserialize a digivolve card
#[error("Unable to deserialize digivolve card")]
ParseDigivolve(#[source] card::digivolve::FromBytesError),
}
/// Error type for [`Table::serialize`](super::Table::serialize)
#[derive(Debug, thiserror::Error)]
pub enum SerializeError {
/// Unable to write a card
#[error("Unable to write card")]
Write(#[source] std::io::Error),
/// Unable to serialize a digimon card
#[error("Unable to serialize digimon card")]
SerializeDigimon(#[source] card::digimon::ToBytesError),
/// Unable to serialize an item card
#[error("Unable to serialize item card")]
SerializeItem(#[source] card::item::ToBytesError),
}

View File

@ -19,7 +19,7 @@ use ref_cast::RefCast;
/// A digimon card
///
/// Contains all information about each digimon card stored in the [`Card Table`](crate::card::table::Table)
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Digimon {
/// The digimon's name

View File

@ -12,7 +12,7 @@ use dcb_util::{
/// A digivolve card
///
/// Contains all information about each digivolve card stored in the [`Card Table`](crate::card::table::Table)
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Digivolve {
/// The item's name

View File

@ -16,7 +16,7 @@ use ref_cast::RefCast;
/// An item card
///
/// Contains all information about each item card stored in the [`Card Table`](crate::card::table::Table)
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Item {
/// The item's name

View File

@ -14,7 +14,7 @@ use dcb_util::{
};
/// A digimon's move
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Move {
/// The move's name

View File

@ -9,24 +9,17 @@ pub use error::{DeserializeError, SerializeError};
pub use header::Header;
// Imports
use super::CardHeader;
use crate::card::{self, property::CardType, Digimon, Digivolve, Item};
use super::{Card, CardHeader};
use crate::card::{self, Digimon, Digivolve, Item};
use dcb_bytes::Bytes;
use std::{convert::TryInto, io};
/// Table storing all cards.
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
#[allow(clippy::unsafe_derive_deserialize)] // False positive
pub struct Table {
/// All digimons in this table
pub digimons: Vec<Digimon>,
/// All items in this table
pub items: Vec<Item>,
/// All digivolves in this table
pub digivolves: Vec<Digivolve>,
/// All cards
pub cards: Vec<Card>,
}
// Constants
@ -35,16 +28,22 @@ impl Table {
pub const PATH: &'static str = "B:\\CARD2.CDD";
}
// Utils
impl Table {
/// Returns how many cards are in this table
#[must_use]
pub fn card_count(&self) -> usize {
self.digimons.len() + self.items.len() + self.digivolves.len()
/// Returns all digimons
pub fn digimons(&self) -> impl Iterator<Item = &Digimon> {
self.cards.iter().filter_map(Card::as_digimon)
}
/// Returns all items
pub fn items(&self) -> impl Iterator<Item = &Item> {
self.cards.iter().filter_map(Card::as_item)
}
/// Returns all digivolve
pub fn digivolves(&self) -> impl Iterator<Item = &Digivolve> {
self.cards.iter().filter_map(Card::as_digivolve)
}
}
impl Table {
/// Deserializes the card table from it's file
pub fn deserialize<R: io::Read>(reader: &mut R) -> Result<Self, DeserializeError> {
// Read header
@ -55,176 +54,89 @@ impl Table {
let header = Header::from_bytes(&header_bytes).map_err(DeserializeError::ParseHeader)?;
// Then check the number of each card
let digimon_cards: usize = header.digimons_len.into();
let item_cards: usize = header.items_len.into();
let digivolve_cards: usize = header.digivolves_len.into();
log::trace!("Found {digimon_cards} digimon, {item_cards} item, {digivolve_cards} digivolve cards");
// And calculate the number of cards
let digimon_cards = header.digimons_len;
let item_cards = u16::from(header.items_len);
let digivolve_cards = u16::from(header.digivolves_len);
let cards_len = digimon_cards + item_cards + digivolve_cards;
log::trace!(
"Found {cards_len} cards: {digimon_cards} digimons, {item_cards} items, {digivolve_cards} digivolves"
);
// Create the arrays with capacity
let mut digimons = Vec::with_capacity(digimon_cards);
let mut items = Vec::with_capacity(item_cards);
let mut digivolves = Vec::with_capacity(digivolve_cards);
// Create the cards
let cards = (0..cards_len)
.map(|id| {
// Read card header bytes
let mut card_header_bytes = [0u8; 0x3];
reader
.read_exact(&mut card_header_bytes)
.map_err(|err| DeserializeError::ReadCardHeader { id, err })?;
// Read until the table is over
for card_id in 0..cards_len {
// Read card header bytes
let mut card_header_bytes = [0u8; 0x3];
reader
.read_exact(&mut card_header_bytes)
.map_err(|err| DeserializeError::ReadCardHeader { id: card_id, err })?;
// Read the header
let card_header = CardHeader::from_bytes(&card_header_bytes)
.map_err(|err| DeserializeError::ParseCardHeader { id, err })?;
log::trace!("#{}: {}", id, card_header.ty);
// Read the header
let card_header = CardHeader::from_bytes(&card_header_bytes)
.map_err(|err| DeserializeError::ParseCardHeader { id: card_id, err })?;
// If the card id isn't what we expected, log warning
if card_header.id != id {
log::warn!("Card with id {} had unexpected id {}", id, card_header.id);
}
// And create the card
let card = Card::deserialize(card_header.ty, reader)
.map_err(|err| DeserializeError::DeserializeCard { id, err })?;
log::trace!("Found #{}: {}", card_id, card_header.ty);
// Skip null terminator
let mut terminator = 0;
reader
.read_exact(std::slice::from_mut(&mut terminator))
.map_err(|err| DeserializeError::ReadCardFooter { id, err })?;
if terminator != 0 {
return Err(DeserializeError::NullTerminator { id, terminator });
}
// If the card id isn't what we expected, log warning
if usize::from(card_header.id) != card_id {
log::warn!("Card with id {} had unexpected id {}", card_id, card_header.id);
}
// And create / push the card
match card_header.ty {
CardType::Digimon => {
let mut digimon_bytes = [0; std::mem::size_of::<<Digimon as Bytes>::ByteArray>()];
reader
.read_exact(&mut digimon_bytes)
.map_err(|err| DeserializeError::ReadCard {
id: card_id,
card_type: card_header.ty,
err,
})?;
let digimon = Digimon::from_bytes(&digimon_bytes)
.map_err(|err| DeserializeError::ParseDigimonCard { id: card_id, err })?;
digimons.push(digimon);
},
CardType::Item => {
let mut item_bytes = [0; std::mem::size_of::<<Item as Bytes>::ByteArray>()];
reader
.read_exact(&mut item_bytes)
.map_err(|err| DeserializeError::ReadCard {
id: card_id,
card_type: card_header.ty,
err,
})?;
let item = Item::from_bytes(&item_bytes)
.map_err(|err| DeserializeError::ParseItemCard { id: card_id, err })?;
items.push(item);
},
CardType::Digivolve => {
let mut digivolve_bytes = [0; std::mem::size_of::<<Digivolve as Bytes>::ByteArray>()];
reader
.read_exact(&mut digivolve_bytes)
.map_err(|err| DeserializeError::ReadCard {
id: card_id,
card_type: card_header.ty,
err,
})?;
let digivolve = Digivolve::from_bytes(&digivolve_bytes)
.map_err(|err| DeserializeError::ParseDigivolveCard { id: card_id, err })?;
digivolves.push(digivolve);
},
}
// Skip null terminator
let mut null_terminator = [0; 1];
reader
.read_exact(&mut null_terminator)
.map_err(|err| DeserializeError::ReadCardFooter { id: card_id, err })?;
if null_terminator[0] != 0 {
log::warn!(
"Card with id {}'s null terminator was {} instead of 0",
card_id,
null_terminator[0]
);
}
}
Ok(card)
})
.collect::<Result<_, _>>()?;
// Return the table
Ok(Self {
digimons,
items,
digivolves,
})
Ok(Self { cards })
}
/// Serializes this card table to a file
pub fn serialize<R: io::Write>(&self, mut writer: R) -> Result<(), SerializeError> {
// Write header
let digimons_len = self.digimons().count();
let items_len = self.items().count();
let digivolves_len = self.digivolves().count();
let header = Header {
digimons_len: self
.digimons
.len()
digimons_len: digimons_len
.try_into()
.map_err(|_err| SerializeError::TooManyDigimon(self.digimons.len()))?,
items_len: self
.items
.len()
.map_err(|_err| SerializeError::TooManyDigimon(digimons_len))?,
items_len: items_len
.try_into()
.map_err(|_err| SerializeError::TooManyItems(self.items.len()))?,
digivolves_len: self
.digivolves
.len()
.map_err(|_err| SerializeError::TooManyItems(items_len))?,
digivolves_len: digimons_len
.try_into()
.map_err(|_err| SerializeError::TooManyDigivolves(self.digivolves.len()))?,
.map_err(|_err| SerializeError::TooManyDigivolves(digivolves_len))?,
};
let mut header_bytes = [0u8; 0x8];
header.to_bytes(&mut header_bytes).into_ok();
writer.write_all(&header_bytes).map_err(SerializeError::WriteHeader)?;
writer
.write_all(&header.bytes().into_ok())
.map_err(SerializeError::WriteHeader)?;
// Macro to help write all cards to file
macro_rules! write_card {
($cards:expr, $prev_ids:expr, $card_type:ident, $($on_err:tt)*) => {{
for (rel_id, card) in $cards.iter().enumerate() {
// Current id through the whole table
let cur_id = $prev_ids + rel_id;
for (card, id) in self.cards.iter().zip(0..) {
// Write the header
let header = CardHeader { id, ty: card.ty() };
writer
.write_all(&header.bytes().into_ok())
.map_err(|err| SerializeError::WriteCardHeader { id, err })?;
// Card bytes
let mut card_bytes = [0; 0x3 + CardType::$card_type.byte_size() + 0x1];
let bytes = dcb_util::array_split_mut!(&mut card_bytes,
header : [0x3],
card : [CardType::$card_type.byte_size()],
footer : 1,
);
// Then serialize the card
card.serialize(&mut writer)
.map_err(|err| SerializeError::SerializeCard { id, err })?;
let card_header = CardHeader {
id: cur_id.try_into().expect("Card id didn't fit into a `u16`"),
ty: CardType::$card_type,
};
card_header.to_bytes(bytes.header).into_ok();
// Write the card
#[allow(unreachable_code)] // Might be `!`
card
.to_bytes(bytes.card)
.map_err(|err| {$($on_err)*}(err, cur_id))?;
// Write the footer
*bytes.footer = 0;
log::trace!("#{}: Writing {}", cur_id, CardType::$card_type);
writer.write_all(&card_bytes)
.map_err(|err| SerializeError::WriteCard {
id: cur_id,
card_type: CardType::$card_type,
err
})?;
}
}}
}
// Write all cards
write_card! { self.digimons, 0, Digimon,
|err, cur_id| SerializeError::SerializeDigimonCard { id: cur_id, err }
}
write_card! { self.items, self.digimons.len(), Item,
|err, cur_id| SerializeError::SerializeItemCard { id: cur_id, err }
}
write_card! { self.digivolves, self.digimons.len() + self.items.len(), Digivolve,
|err, _| err
// And write the footer
writer
.write_all(&[0])
.map_err(|err| SerializeError::WriteCardFooter { id, err })?;
}
// And return Ok

View File

@ -1,7 +1,7 @@
//! Errors
// Imports
use super::{card, header, CardType};
use super::{card, header};
/// Error type for [`Table::deserialize`](super::Table::deserialize)
#[derive(Debug, thiserror::Error)]
@ -15,10 +15,10 @@ pub enum DeserializeError {
ParseHeader(#[source] header::FromBytesError),
/// Unable to read card header
#[error("Unable to read card header for card id {}", id)]
#[error("Unable to read card header for card {id}")]
ReadCardHeader {
/// Id of card
id: usize,
id: u16,
/// Underlying error
#[source]
@ -26,73 +26,47 @@ pub enum DeserializeError {
},
/// Unable to parse a card header
#[error("Unable to parse a card header for card id {id}")]
#[error("Unable to parse a card header for card {id}")]
ParseCardHeader {
/// Id of card
id: usize,
id: u16,
/// Underlying error
#[source]
err: card::header::FromBytesError,
},
/// Unable to read a card
#[error("Unable to read {} with id {}", card_type, id)]
ReadCard {
/// Unable to deserialize card
#[error("Unable to deserialize card {id}")]
DeserializeCard {
/// Id of card
id: usize,
/// Card type
card_type: CardType,
id: u16,
/// Underlying error
#[source]
err: std::io::Error,
},
/// Unable to deserialize a digimon card
#[error("Unable to deserialize digimon card with id {}", id)]
ParseDigimonCard {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: card::digimon::FromBytesError,
},
/// Unable to deserialize an item card
#[error("Unable to deserialize item card with id {}", id)]
ParseItemCard {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: card::item::FromBytesError,
},
/// Unable to deserialize a digivolve card
#[error("Unable to deserialize digivolve card with id {}", id)]
ParseDigivolveCard {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: card::digivolve::FromBytesError,
err: card::card::DeserializeError,
},
/// Unable to read card footer
#[error("Unable to read card footer for card id {}", id)]
#[error("Unable to read card footer for card {id}")]
ReadCardFooter {
/// Id of card
id: usize,
id: u16,
/// Underlying error
#[source]
err: std::io::Error,
},
/// Null terminator wasn't null
#[error("Null terminator for card {id} was {terminator}")]
NullTerminator {
/// Id of card
id: u16,
/// Terminator
terminator: u8,
},
}
/// Error type for [`Table::serialize`](super::Table::serialize)
@ -114,39 +88,36 @@ pub enum SerializeError {
#[error("Unable to write table header")]
WriteHeader(#[source] std::io::Error),
/// Unable to write a card
#[error("Unable to write {} card with id {}", card_type, id)]
WriteCard {
/// Unable to write card header
#[error("Unable to write header for card {id}")]
WriteCardHeader {
/// Id of card
id: usize,
/// Card type
card_type: CardType,
id: u16,
/// Underlying error
#[source]
err: std::io::Error,
},
/// Unable to serialize a digimon card
#[error("Unable to serialize digimon card with id {}", id)]
SerializeDigimonCard {
/// Unable to serialize card
#[error("Unable to serialize card {id}")]
SerializeCard {
/// Id of card
id: usize,
id: u16,
/// Underlying error
#[source]
err: card::digimon::ToBytesError,
err: card::card::SerializeError,
},
/// Unable to serialize an item card
#[error("Unable to serialize item card with id {}", id)]
SerializeItemCard {
/// Unable to write card footer
#[error("Unable to write footer for card {id}")]
WriteCardFooter {
/// Id of card
id: usize,
id: u16,
/// Underlying error
#[source]
err: card::item::ToBytesError,
err: std::io::Error,
},
}