use serde::Serialize;
use super::{ItemId, Type};
use crate::Result;
use crate::formats::narc::Narc;
use crate::io::Reader;
const RECORD_LEN: usize = 36;
const NO_TYPE: u8 = 31;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Item {
pub id: ItemId,
pub name: Option<String>,
pub description: Option<String>,
pub price: u32,
pub pocket: Pocket,
pub battle_pockets: BattlePockets,
pub registrable: bool,
pub held_effect: u8,
pub held_parameter: u8,
pub fling_power: u8,
pub fling_effect: u8,
pub natural_gift: Option<NaturalGift>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct NaturalGift {
pub r#type: Type,
pub power: u8,
}
pub(super) fn parse(table: &[u8], names: &[String], descriptions: &[String]) -> Result<Vec<Item>> {
let narc = Narc::parse(table)?;
(1..narc.len())
.map(|index| {
let bytes = narc.member(table, index)?;
let mut record = Reader::new("Gen 5 item", &bytes);
if bytes.len() != RECORD_LEN {
return Err(record.error_at(0, "record is not 36 bytes"));
}
let price = u32::from(record.u16()?) * 10;
let held_effect = record.u8()?;
let held_parameter = record.u8()?;
record.skip(1)?; let fling_effect = record.u8()?;
let fling_power = record.u8()?;
let gift_power = record.u8()?;
let packed = record.u16()?;
let gift_type = (packed & 0x1F) as u8;
Ok(Item {
id: ItemId(index as u16),
name: names.get(index).cloned(),
description: descriptions.get(index).cloned(),
price,
pocket: Pocket::from_raw((packed >> 7 & 0xF) as u8),
battle_pockets: BattlePockets((packed >> 11) as u8),
registrable: packed & 1 << 6 != 0,
held_effect,
held_parameter,
fling_power,
fling_effect,
natural_gift: (gift_type != NO_TYPE && gift_power != 0).then_some(NaturalGift {
r#type: Type::from_raw(gift_type),
power: gift_power,
}),
})
})
.collect()
}
raw_enum! {
pub enum Pocket: u8 {
Items = 0,
Medicine = 1,
Machines = 2,
Berries = 3,
KeyItems = 4,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct BattlePockets(pub u8);
impl BattlePockets {
pub const NAMES: [&str; 4] = [
"poke_balls",
"battle_items",
"hp_pp_restore",
"status_restore",
];
pub fn names(self) -> impl Iterator<Item = String> {
(0..8)
.filter(move |bit| self.0 & 1 << bit != 0)
.map(|bit| match Self::NAMES.get(bit) {
Some(name) => (*name).to_owned(),
None => format!("bit_{bit}"),
})
}
}
impl Serialize for BattlePockets {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_seq(self.names())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::narc;
#[test]
fn parses_records() {
let potion = [&[0x1E, 0, 0, 20, 0, 0, 30, 0, 0x9F, 0x20][..], &[0; 26]].concat();
let oran = [&[2, 0, 1, 10, 7, 7, 10, 60, 0x83, 0x21][..], &[0; 26]].concat();
let table = narc(&[&[0; 36], &potion, &oran]);
let items = parse(&table, &["", "Potion"].map(str::to_owned), &[]).unwrap();
let potion = &items[0];
assert_eq!(potion.name.as_deref(), Some("Potion"));
assert_eq!((potion.price, potion.pocket), (300, Pocket::Medicine));
assert_eq!((potion.fling_power, potion.natural_gift), (30, None));
assert_eq!(
serde_json::to_value(potion.battle_pockets).unwrap(),
serde_json::json!(["hp_pp_restore"])
);
let oran = &items[1];
assert_eq!(oran.pocket, Pocket::Berries);
assert_eq!(
oran.natural_gift,
Some(NaturalGift {
r#type: Type::Poison,
power: 60
})
);
assert!(parse(&narc(&[&[0; 36], &[0; 20]]), &[], &[]).is_err());
}
}