mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-03 16:16:33 +00:00
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:
parent
748a07b208
commit
2c1d33797c
@ -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",
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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(),
|
||||
)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -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
146
dcb/src/card/card.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
40
dcb/src/card/card/error.rs
Normal file
40
dcb/src/card/card/error.rs
Normal 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),
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user