improved items

This commit is contained in:
2026-01-07 14:49:10 +00:00
parent 06dcfb7a9c
commit db45105d90
6 changed files with 2039 additions and 226 deletions

View File

@@ -1,6 +1,9 @@
use crate::item_loader::{
calculate_prices, generate_banknotes, generate_exceptional_items, load_items_from_directory,
};
use crate::types::Item; use crate::types::Item;
use crate::xml_parser::{parse_items_xml, XmlParseError}; use crate::xml_parser::{parse_items_xml, XmlParseError};
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::path::Path; use std::path::Path;
/// A database for managing game items loaded from XML files /// A database for managing game items loaded from XML files
@@ -9,6 +12,8 @@ pub struct ItemDatabase {
items: Vec<Item>, items: Vec<Item>,
items_by_id: HashMap<i32, usize>, items_by_id: HashMap<i32, usize>,
items_by_name: HashMap<String, Vec<usize>>, items_by_name: HashMap<String, Vec<usize>>,
stackable_item_ids: HashSet<i32>,
storage_item_ids: HashSet<i32>,
} }
impl ItemDatabase { impl ItemDatabase {
@@ -18,10 +23,12 @@ impl ItemDatabase {
items: Vec::new(), items: Vec::new(),
items_by_id: HashMap::new(), items_by_id: HashMap::new(),
items_by_name: HashMap::new(), items_by_name: HashMap::new(),
stackable_item_ids: HashSet::new(),
storage_item_ids: HashSet::new(),
} }
} }
/// Load items from an XML file /// Load items from an XML file (basic loading without advanced features)
pub fn load_from_xml<P: AsRef<Path>>(path: P) -> Result<Self, XmlParseError> { pub fn load_from_xml<P: AsRef<Path>>(path: P) -> Result<Self, XmlParseError> {
let items = parse_items_xml(path)?; let items = parse_items_xml(path)?;
let mut db = Self::new(); let mut db = Self::new();
@@ -29,22 +36,68 @@ impl ItemDatabase {
Ok(db) Ok(db)
} }
/// Load items from a directory with full support for:
/// - Multiple XML files
/// - Stats generation
/// - Crafting recipe generation
/// - Exceptional items
/// - Banknotes
/// - Price calculation
pub fn load_from_directory<P: AsRef<Path>>(dir: P) -> Result<Self, XmlParseError> {
let mut items = load_items_from_directory(dir)?;
// Generate exceptional items
let exceptional = generate_exceptional_items(&items);
items.extend(exceptional);
// Generate banknotes
let banknotes = generate_banknotes(&items);
items.extend(banknotes);
// Calculate prices
calculate_prices(&mut items);
let mut db = Self::new();
db.add_items(items);
Ok(db)
}
/// Add items to the database /// Add items to the database
pub fn add_items(&mut self, items: Vec<Item>) { pub fn add_items(&mut self, items: Vec<Item>) {
for item in items { for item in items {
let index = self.items.len(); let index = self.items.len();
self.items_by_id.insert(item.id, index); self.items_by_id.insert(item.type_id, index);
// Add to name index (can have multiple items with same name) // Add to name index (can have multiple items with same name)
self.items_by_name self.items_by_name
.entry(item.name.clone()) .entry(item.item_name.clone())
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(index); .push(index);
// Track stackable items
if item.is_stackable() {
self.stackable_item_ids.insert(item.type_id);
}
// Track storage items
if item.is_storage_item() {
self.storage_item_ids.insert(item.type_id);
}
self.items.push(item); self.items.push(item);
} }
} }
/// Check if an item is stackable by ID
pub fn is_stackable(&self, type_id: i32) -> bool {
self.stackable_item_ids.contains(&type_id)
}
/// Check if an item is a storage item by ID
pub fn is_storage_item(&self, type_id: i32) -> bool {
self.storage_item_ids.contains(&type_id)
}
/// Get an item by ID /// Get an item by ID
pub fn get_by_id(&self, id: i32) -> Option<&Item> { pub fn get_by_id(&self, id: i32) -> Option<&Item> {
self.items_by_id self.items_by_id
@@ -72,41 +125,62 @@ impl ItemDatabase {
/// Get items by category /// Get items by category
pub fn get_by_category(&self, category: &str) -> Vec<&Item> { pub fn get_by_category(&self, category: &str) -> Vec<&Item> {
self.items use crate::types::ItemCategory;
.iter() use std::str::FromStr;
.filter(|item| {
item.category if let Ok(cat) = ItemCategory::from_str(category) {
.as_ref() self.items
.map(|c| c == category) .iter()
.unwrap_or(false) .filter(|item| item.has_category(cat))
}) .collect()
.collect() } else {
Vec::new()
}
} }
/// Get items by slot /// Get items by slot/item type
pub fn get_by_slot(&self, slot: &str) -> Vec<&Item> { pub fn get_by_slot(&self, slot: &str) -> Vec<&Item> {
self.items use crate::types::ItemType;
.iter() use std::str::FromStr;
.filter(|item| {
item.slot if let Ok(item_type) = ItemType::from_str(slot) {
.as_ref() self.items
.map(|s| s == slot) .iter()
.unwrap_or(false) .filter(|item| item.item_type == item_type)
}) .collect()
.collect() } else {
Vec::new()
}
} }
/// Get items by skill requirement /// Get items by skill requirement
pub fn get_by_skill(&self, skill: &str) -> Vec<&Item> { pub fn get_by_skill(&self, skill: &str) -> Vec<&Item> {
self.items use crate::types::SkillType;
.iter() use std::str::FromStr;
.filter(|item| {
item.skill if let Ok(skill_type) = SkillType::from_str(skill) {
.as_ref() self.items
.map(|s| s == skill) .iter()
.unwrap_or(false) .filter(|item| item.skill == skill_type)
}) .collect()
.collect() } else {
Vec::new()
}
}
/// Get items by tool type
pub fn get_by_tool(&self, tool: &str) -> Vec<&Item> {
use crate::types::Tool;
use std::str::FromStr;
if let Ok(tool_type) = Tool::from_str(tool) {
self.items
.iter()
.filter(|item| item.tool == tool_type)
.collect()
} else {
Vec::new()
}
} }
/// Get number of items in database /// Get number of items in database
@@ -132,7 +206,7 @@ impl ItemDatabase {
.iter() .iter()
.map(|item| { .map(|item| {
let json = serde_json::to_string(item).unwrap_or_else(|_| "{}".to_string()); let json = serde_json::to_string(item).unwrap_or_else(|_| "{}".to_string());
(item.id, item.name.clone(), json) (item.type_id, item.item_name.clone(), json)
}) })
.collect() .collect()
} }

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,7 @@
pub mod types; pub mod types;
mod xml_parser; mod xml_parser;
mod item_loader;
mod item_database; mod item_database;
mod npc_database; mod npc_database;
mod quest_database; mod quest_database;
@@ -63,10 +64,39 @@ pub use quest_database::QuestDatabase;
pub use harvestable_database::HarvestableDatabase; pub use harvestable_database::HarvestableDatabase;
pub use loot_database::LootDatabase; pub use loot_database::LootDatabase;
pub use types::{ pub use types::{
Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule, InteractableResource, // Items
Npc, NpcStat, NpcLevel, RightClick, BarkGroup, Bark, QuestMarker, NpcAnimationSet, Item,
Quest, QuestPhase, QuestReward, ItemStat,
Harvestable, HarvestableDrop, CraftingRecipe,
LootTable, LootDrop, CraftingRecipeItem,
AnimationSet,
GenerateRule,
ItemType,
ItemCategory,
Tool,
SkillType,
StatType,
Stat,
ItemXpBoost,
PermanentStatBoost,
CustomItemName,
MAX_STACK,
// Other types
InteractableResource,
Npc,
NpcStat,
NpcLevel,
RightClick,
BarkGroup,
Bark,
QuestMarker,
NpcAnimationSet,
Quest,
QuestPhase,
QuestReward,
Harvestable,
HarvestableDrop,
LootTable,
LootDrop,
}; };
pub use xml_parser::XmlParseError; pub use xml_parser::XmlParseError;

View File

@@ -1,158 +1,649 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
// Constants
pub const MAX_STACK: i32 = 2_100_000_000; // 2.1 billion
// ============================================================================
// Enums
// ============================================================================
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ItemType {
Weapon,
Shield,
Armor,
Head,
Resource,
Consumable,
Trinket,
Bracelet,
}
impl ItemType {
pub fn is_equippable(&self) -> bool {
matches!(
self,
ItemType::Armor
| ItemType::Shield
| ItemType::Weapon
| ItemType::Head
| ItemType::Trinket
| ItemType::Bracelet
)
}
pub fn to_string(&self) -> &'static str {
match self {
ItemType::Shield => "Offhand",
ItemType::Weapon => "weapon",
ItemType::Armor => "armor",
ItemType::Head => "head",
ItemType::Resource => "resource",
ItemType::Consumable => "consumable",
ItemType::Trinket => "trinket",
ItemType::Bracelet => "bracelet",
}
}
}
impl FromStr for ItemType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"armor" => Ok(ItemType::Armor),
"weapon" => Ok(ItemType::Weapon),
"shield" => Ok(ItemType::Shield),
"resource" => Ok(ItemType::Resource),
"consumable" => Ok(ItemType::Consumable),
"head" => Ok(ItemType::Head),
"trinket" => Ok(ItemType::Trinket),
"bracelet" => Ok(ItemType::Bracelet),
_ => Ok(ItemType::Resource), // Default fallback
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ItemCategory {
None,
Bone,
Bow,
Crossbow,
Constructable,
Torch,
Blacksmithhammer,
Questitem,
HeavyArmor,
Warhammer,
Shield,
Hatchet,
Blade,
Armor,
Pickaxe,
Fish,
Fishingrod,
Shears,
Hammer,
Battleaxe,
Morningstar,
Wand,
Staff,
Dagger,
}
impl ItemCategory {
pub fn to_string(&self) -> &'static str {
match self {
ItemCategory::Fishingrod => "fishing rod",
ItemCategory::None => "none",
ItemCategory::Bone => "bone",
ItemCategory::Bow => "bow",
ItemCategory::Crossbow => "crossbow",
ItemCategory::Constructable => "constructable",
ItemCategory::Torch => "torch",
ItemCategory::Blacksmithhammer => "blacksmithhammer",
ItemCategory::Questitem => "questitem",
ItemCategory::HeavyArmor => "heavyArmor",
ItemCategory::Warhammer => "warhammer",
ItemCategory::Shield => "shield",
ItemCategory::Hatchet => "hatchet",
ItemCategory::Blade => "blade",
ItemCategory::Armor => "armor",
ItemCategory::Pickaxe => "pickaxe",
ItemCategory::Fish => "fish",
ItemCategory::Shears => "shears",
ItemCategory::Hammer => "hammer",
ItemCategory::Battleaxe => "battleaxe",
ItemCategory::Morningstar => "morningstar",
ItemCategory::Wand => "wand",
ItemCategory::Staff => "staff",
ItemCategory::Dagger => "dagger",
}
}
}
impl FromStr for ItemCategory {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"none" => Ok(ItemCategory::None),
"bone" => Ok(ItemCategory::Bone),
"bow" => Ok(ItemCategory::Bow),
"crossbow" => Ok(ItemCategory::Crossbow),
"constructable" => Ok(ItemCategory::Constructable),
"torch" => Ok(ItemCategory::Torch),
"blacksmithhammer" => Ok(ItemCategory::Blacksmithhammer),
"questitem" => Ok(ItemCategory::Questitem),
"heavyarmor" => Ok(ItemCategory::HeavyArmor),
"warhammer" => Ok(ItemCategory::Warhammer),
"shield" => Ok(ItemCategory::Shield),
"hatchet" => Ok(ItemCategory::Hatchet),
"blade" => Ok(ItemCategory::Blade),
"armor" => Ok(ItemCategory::Armor),
"pickaxe" => Ok(ItemCategory::Pickaxe),
"fish" => Ok(ItemCategory::Fish),
"fishingrod" => Ok(ItemCategory::Fishingrod),
"shears" => Ok(ItemCategory::Shears),
"hammer" => Ok(ItemCategory::Hammer),
"battleaxe" => Ok(ItemCategory::Battleaxe),
"morningstar" => Ok(ItemCategory::Morningstar),
"wand" => Ok(ItemCategory::Wand),
"staff" => Ok(ItemCategory::Staff),
"dagger" => Ok(ItemCategory::Dagger),
_ => Ok(ItemCategory::None),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Tool {
None,
Pickaxe,
Hatchet,
Scythe,
Hammer,
Shears,
FishingRod,
}
impl FromStr for Tool {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"none" | "" => Ok(Tool::None),
"pickaxe" => Ok(Tool::Pickaxe),
"hatchet" => Ok(Tool::Hatchet),
"scythe" => Ok(Tool::Scythe),
"hammer" => Ok(Tool::Hammer),
"shears" => Ok(Tool::Shears),
"fishingrod" => Ok(Tool::FishingRod),
_ => Ok(Tool::None),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SkillType {
None,
Swordsmanship,
Archery,
Magic,
Defence,
Mining,
Woodcutting,
Fishing,
Cooking,
Carpentry,
Blacksmithy,
Tailoring,
Alchemy,
}
impl FromStr for SkillType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"none" | "" => Ok(SkillType::None),
"swordsmanship" => Ok(SkillType::Swordsmanship),
"archery" => Ok(SkillType::Archery),
"magic" => Ok(SkillType::Magic),
"defence" => Ok(SkillType::Defence),
"mining" => Ok(SkillType::Mining),
"woodcutting" => Ok(SkillType::Woodcutting),
"fishing" => Ok(SkillType::Fishing),
"cooking" => Ok(SkillType::Cooking),
"carpentry" => Ok(SkillType::Carpentry),
"blacksmithy" => Ok(SkillType::Blacksmithy),
"tailoring" => Ok(SkillType::Tailoring),
"alchemy" => Ok(SkillType::Alchemy),
_ => Ok(SkillType::None),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum StatType {
None,
Health,
Mana,
HealthRegen,
ManaRegen,
DamagePhysical,
DamageMagical,
DamageRanged,
AccuracyPhysical,
AccuracyMagical,
AccuracyRanged,
ResistancePhysical,
ResistanceMagical,
ResistanceRanged,
Critical,
Healing,
MovementSpeed,
DamageVsBeasts,
DamageVsUndead,
CritterSlaying,
}
impl FromStr for StatType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"health" => Ok(StatType::Health),
"mana" => Ok(StatType::Mana),
"healthregen" => Ok(StatType::HealthRegen),
"manaregen" => Ok(StatType::ManaRegen),
"damagephysical" => Ok(StatType::DamagePhysical),
"damagemagical" => Ok(StatType::DamageMagical),
"damageranged" => Ok(StatType::DamageRanged),
"accuracyphysical" => Ok(StatType::AccuracyPhysical),
"accuracymagical" => Ok(StatType::AccuracyMagical),
"accuracyranged" => Ok(StatType::AccuracyRanged),
"resistancephysical" => Ok(StatType::ResistancePhysical),
"resistancemagical" => Ok(StatType::ResistanceMagical),
"resistanceranged" => Ok(StatType::ResistanceRanged),
"critical" => Ok(StatType::Critical),
"healing" => Ok(StatType::Healing),
"movementspeed" => Ok(StatType::MovementSpeed),
"damagevsbeasts" => Ok(StatType::DamageVsBeasts),
"damagevsundead" => Ok(StatType::DamageVsUndead),
"critterslaying" => Ok(StatType::CritterSlaying),
_ => Ok(StatType::None),
}
}
}
// ============================================================================
// Nested Structs
// ============================================================================
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Item { pub struct Stat {
// Required fields pub stat_type: StatType,
pub id: i32, pub value: f32,
pub name: String,
// Optional basic attributes
pub level: Option<i32>,
pub description: Option<String>,
pub price: Option<i32>,
pub slot: Option<String>,
pub category: Option<String>,
pub skill: Option<String>,
pub tool: Option<String>,
// Item behavior
pub stackable: Option<i32>,
pub maxstack: Option<i32>,
pub abilityid: Option<i32>,
pub swap: Option<i32>,
pub twohanded: Option<i32>,
// Food/consumable properties
pub foodamount: Option<i32>,
pub foodfrequency: Option<i32>,
pub foodtime: Option<i32>,
pub foodlevel: Option<i32>,
// Crafting
pub craftingskill: Option<String>,
pub workbench: Option<i32>,
pub craftingitems: Option<String>,
// Visual/audio
pub handmodel: Option<String>,
pub groundmodel: Option<String>,
pub usingitemmodel: Option<String>,
pub dropsfx: Option<String>,
pub pickupsfx: Option<String>,
pub hitgfx: Option<String>,
pub attackanimations: Option<String>,
pub attackanimationspeed: Option<String>,
pub attackhitsounds: Option<String>,
// Storage
pub storageitem: Option<String>,
pub storagesize: Option<i32>,
// Other flags
pub hidemilestone: Option<i32>,
pub generateicon: Option<i32>,
pub comment: Option<String>,
// Nested elements
pub stats: Vec<ItemStat>,
pub crafting_recipes: Vec<CraftingRecipe>,
pub animations: Option<AnimationSet>,
pub generate_rules: Vec<GenerateRule>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemStat { pub struct ItemXpBoost {
// Damage stats pub skill_type: SkillType,
pub damagephysical: Option<i32>, pub multiplier: f32,
pub damagemagical: Option<i32>, }
pub damageranged: Option<i32>,
// Accuracy stats #[derive(Debug, Clone, Serialize, Deserialize)]
pub accuracyphysical: Option<i32>, pub struct PermanentStatBoost {
pub accuracymagical: Option<i32>, pub stat: StatType,
pub accuracyranged: Option<i32>, pub amount: i32,
}
// Resistance stats #[derive(Debug, Clone, Serialize, Deserialize)]
pub resistancephysical: Option<i32>, pub struct CustomItemName {
pub resistancemagical: Option<i32>, pub checks: String,
pub resistanceranged: Option<i32>, pub item_name: String,
}
// Core stats #[derive(Debug, Clone, Serialize, Deserialize)]
pub health: Option<i32>, pub struct CraftingRecipeItem {
pub mana: Option<i32>, pub item_id: i32,
pub manaregen: Option<i32>, pub amount: i32,
pub healing: Option<i32>,
// Harvesting stats
pub harvestingspeedwoodcutting: Option<i32>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CraftingRecipe { pub struct CraftingRecipe {
pub workbench: Option<i32>, pub product: i32,
pub craftingitems: Option<String>, pub level: i32,
pub craftingskill: Option<String>, pub skill: SkillType,
pub workbench_id: i32,
pub items: Vec<CraftingRecipeItem>,
pub unlocked_by_default: bool,
pub xp: i32,
pub checks: Option<String>, pub checks: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimationSet { pub struct AnimationSet {
pub idle: Option<String>, pub idle: i32,
pub walk: Option<String>, pub walk: i32,
pub run: Option<String>, pub run: i32,
pub weaponattack: Option<String>, pub takehit: i32,
pub takehit: Option<String>, pub use_anim: i32,
pub weapon_attack: i32,
}
impl Default for AnimationSet {
fn default() -> Self {
Self {
idle: 0,
walk: 0,
run: 0,
takehit: 0,
use_anim: 0,
weapon_attack: 0,
}
}
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerateRule { pub struct GenerateRule {
pub generatestats: Option<String>, pub generate_stats: Option<String>,
pub generatecrafting: Option<i32>, pub generate_crafting: bool,
pub generateicon: Option<i32>, }
// ============================================================================
// Main Item Struct
// ============================================================================
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Item {
// Core identification
pub type_id: i32,
pub item_name: String,
// Item classification
pub item_type: ItemType,
pub item_categories: Vec<ItemCategory>,
pub level: i32,
// Flags
pub undroppable: bool,
pub undroppable_on_death: bool,
pub two_handed: bool,
pub unequip_destroy: bool,
pub generate_icon: bool,
pub hide_milestone: bool,
pub cannot_craft_exceptional: bool,
pub storage_all_items: bool,
// Visual/UI
pub comment: String,
pub description: String,
pub effect_string: String,
pub use_text: String,
// Models and resources
pub using_item_model: String,
pub handmodel: String,
pub ground_model: i32,
pub copy_model: i32,
// Audio
pub drop_sfx: i32,
pub pickup_sfx: i32,
// Stacking and storage
pub max_stack: i32,
pub storage_items: Vec<i32>,
pub storage_size: i32,
// Abilities and skills
pub ability_id: i32,
pub special_ability: i32,
pub learn_ability_id: i32,
pub skill: SkillType,
pub tool: Tool,
// Economy
pub price: i32,
#[serde(skip)]
pub setup_price: bool,
// Food properties
pub food_level: i32,
pub food_time: i32,
pub food_frequency: i32,
pub food_amount: i32,
// Crafting
pub crafting_recipes: Vec<CraftingRecipe>,
pub has_crafting: bool,
// Stats and bonuses
pub stats: Vec<Stat>,
pub item_xp_boosts: Vec<ItemXpBoost>,
pub permanent_stat_boosts: Vec<PermanentStatBoost>,
// Other
pub book_id: i32,
pub swap_item: i32,
pub visibility_xml_checks: Vec<String>,
pub custom_item_names: Vec<CustomItemName>,
pub custom_item_descriptions: Vec<CustomItemName>,
// Animation IDs
pub animations: Option<AnimationSet>,
} }
impl Item { impl Item {
pub fn new(id: i32, name: String) -> Self { pub fn new(type_id: i32, name: String) -> Self {
Self { Self {
id, type_id,
name, item_name: name,
level: None, item_type: ItemType::Resource,
description: None, item_categories: vec![ItemCategory::None],
price: None, level: 1,
slot: None, undroppable: false,
category: None, undroppable_on_death: false,
skill: None, two_handed: false,
tool: None, unequip_destroy: false,
stackable: None, generate_icon: false,
maxstack: None, hide_milestone: false,
abilityid: None, cannot_craft_exceptional: false,
swap: None, storage_all_items: false,
twohanded: None, comment: String::new(),
foodamount: None, description: String::new(),
foodfrequency: None, effect_string: String::new(),
foodtime: None, use_text: String::new(),
foodlevel: None, using_item_model: String::new(),
craftingskill: None, handmodel: String::new(),
workbench: None, ground_model: 0,
craftingitems: None, copy_model: 0,
handmodel: None, drop_sfx: 0,
groundmodel: None, pickup_sfx: 0,
usingitemmodel: None, max_stack: 1,
dropsfx: None, storage_items: Vec::new(),
pickupsfx: None, storage_size: 0,
hitgfx: None, ability_id: 0,
attackanimations: None, special_ability: 0,
attackanimationspeed: None, learn_ability_id: 0,
attackhitsounds: None, skill: SkillType::None,
storageitem: None, tool: Tool::None,
storagesize: None, price: 0,
hidemilestone: None, setup_price: false,
generateicon: None, food_level: 0,
comment: None, food_time: 0,
stats: Vec::new(), food_frequency: 0,
food_amount: 0,
crafting_recipes: Vec::new(), crafting_recipes: Vec::new(),
has_crafting: false,
stats: Vec::new(),
item_xp_boosts: Vec::new(),
permanent_stat_boosts: Vec::new(),
book_id: 0,
swap_item: 0,
visibility_xml_checks: Vec::new(),
custom_item_names: Vec::new(),
custom_item_descriptions: Vec::new(),
animations: None, animations: None,
generate_rules: Vec::new(), }
}
pub fn is_equippable(&self) -> bool {
self.item_type.is_equippable()
}
pub fn has_category(&self, category: ItemCategory) -> bool {
self.item_categories.contains(&category)
}
pub fn get_item_name(&self) -> &str {
&self.item_name
}
pub fn is_stackable(&self) -> bool {
self.max_stack > 1
}
pub fn is_storage_item(&self) -> bool {
self.storage_size > 0 || self.storage_all_items
}
/// Get the equip sound ID based on item type and category
pub fn get_equip_sound(&self) -> i32 {
if self.has_category(ItemCategory::Blade) {
return 845;
}
if self.has_category(ItemCategory::HeavyArmor) || self.has_category(ItemCategory::Armor) {
// Pick random from 846-851
return 846;
}
match self.item_type {
ItemType::Weapon => 46,
ItemType::Shield => 47,
ItemType::Head => 46,
ItemType::Armor => 46,
ItemType::Trinket | ItemType::Bracelet => 479,
_ => 0,
} }
} }
} }
// ============================================================================
// Legacy compatibility structs (for existing parser)
// ============================================================================
/// Legacy ItemStat struct for backwards compatibility with existing XML parser
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ItemStat {
pub damagephysical: Option<i32>,
pub damagemagical: Option<i32>,
pub damageranged: Option<i32>,
pub accuracyphysical: Option<i32>,
pub accuracymagical: Option<i32>,
pub accuracyranged: Option<i32>,
pub resistancephysical: Option<i32>,
pub resistancemagical: Option<i32>,
pub resistanceranged: Option<i32>,
pub health: Option<i32>,
pub mana: Option<i32>,
pub manaregen: Option<i32>,
pub healing: Option<i32>,
pub harvestingspeedwoodcutting: Option<i32>,
}
impl ItemStat {
/// Convert legacy ItemStat to new Stat vec
pub fn to_stats(&self) -> Vec<Stat> {
let mut stats = Vec::new();
if let Some(v) = self.damagephysical {
stats.push(Stat {
stat_type: StatType::DamagePhysical,
value: v as f32,
});
}
if let Some(v) = self.damagemagical {
stats.push(Stat {
stat_type: StatType::DamageMagical,
value: v as f32,
});
}
if let Some(v) = self.damageranged {
stats.push(Stat {
stat_type: StatType::DamageRanged,
value: v as f32,
});
}
if let Some(v) = self.accuracyphysical {
stats.push(Stat {
stat_type: StatType::AccuracyPhysical,
value: v as f32,
});
}
if let Some(v) = self.accuracymagical {
stats.push(Stat {
stat_type: StatType::AccuracyMagical,
value: v as f32,
});
}
if let Some(v) = self.accuracyranged {
stats.push(Stat {
stat_type: StatType::AccuracyRanged,
value: v as f32,
});
}
if let Some(v) = self.resistancephysical {
stats.push(Stat {
stat_type: StatType::ResistancePhysical,
value: v as f32,
});
}
if let Some(v) = self.resistancemagical {
stats.push(Stat {
stat_type: StatType::ResistanceMagical,
value: v as f32,
});
}
if let Some(v) = self.resistanceranged {
stats.push(Stat {
stat_type: StatType::ResistanceRanged,
value: v as f32,
});
}
if let Some(v) = self.health {
stats.push(Stat {
stat_type: StatType::Health,
value: v as f32,
});
}
if let Some(v) = self.mana {
stats.push(Stat {
stat_type: StatType::Mana,
value: v as f32,
});
}
if let Some(v) = self.manaregen {
stats.push(Stat {
stat_type: StatType::ManaRegen,
value: v as f32,
});
}
if let Some(v) = self.healing {
stats.push(Stat {
stat_type: StatType::Healing,
value: v as f32,
});
}
stats
}
}

View File

@@ -6,7 +6,28 @@ mod harvestable;
mod loot; mod loot;
pub use interactable_resource::InteractableResource; pub use interactable_resource::InteractableResource;
pub use item::{Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule}; pub use item::{
// Main types
Item,
ItemStat,
CraftingRecipe,
CraftingRecipeItem,
AnimationSet,
GenerateRule,
// Enums
ItemType,
ItemCategory,
Tool,
SkillType,
StatType,
// Nested structs
Stat,
ItemXpBoost,
PermanentStatBoost,
CustomItemName,
// Constants
MAX_STACK,
};
pub use npc::{Npc, NpcStat, NpcLevel, RightClick, BarkGroup, Bark, QuestMarker, NpcAnimationSet}; pub use npc::{Npc, NpcStat, NpcLevel, RightClick, BarkGroup, Bark, QuestMarker, NpcAnimationSet};
pub use quest::{Quest, QuestPhase, QuestReward}; pub use quest::{Quest, QuestPhase, QuestReward};
pub use harvestable::{Harvestable, HarvestableDrop}; pub use harvestable::{Harvestable, HarvestableDrop};

View File

@@ -1,5 +1,5 @@
use crate::types::{ use crate::types::{
Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule, Item, ItemStat, AnimationSet,
Npc, NpcStat, NpcLevel, RightClick, BarkGroup, QuestMarker, NpcAnimationSet, Npc, NpcStat, NpcLevel, RightClick, BarkGroup, QuestMarker, NpcAnimationSet,
Quest, QuestPhase, QuestReward, Quest, QuestPhase, QuestReward,
Harvestable, HarvestableDrop, Harvestable, HarvestableDrop,
@@ -60,40 +60,32 @@ pub fn parse_items_xml<P: AsRef<Path>>(path: P) -> Result<Vec<Item>, XmlParseErr
let mut item = Item::new(id, name); let mut item = Item::new(id, name);
// Parse optional attributes // Note: This is a legacy simple parser. For full functionality,
if let Some(v) = attrs.get("level") { item.level = v.parse().ok(); } // use the item_loader module's load_items_from_directory function.
if let Some(v) = attrs.get("description") { item.description = Some(v.clone()); }
if let Some(v) = attrs.get("price") { item.price = v.parse().ok(); } // Parse optional attributes using defaults for new structure
if let Some(v) = attrs.get("slot") { item.slot = Some(v.clone()); } if let Some(v) = attrs.get("level") { item.level = v.parse().unwrap_or(1); }
if let Some(v) = attrs.get("category") { item.category = Some(v.clone()); } if let Some(v) = attrs.get("description") { item.description = v.clone(); }
if let Some(v) = attrs.get("skill") { item.skill = Some(v.clone()); } if let Some(v) = attrs.get("price") { item.price = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("tool") { item.tool = Some(v.clone()); } if let Some(v) = attrs.get("maxstack") { item.max_stack = v.parse().unwrap_or(1); }
if let Some(v) = attrs.get("stackable") { item.stackable = v.parse().ok(); } if let Some(v) = attrs.get("abilityid") { item.ability_id = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("maxstack") { item.maxstack = v.parse().ok(); } if let Some(v) = attrs.get("swap") { item.swap_item = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("abilityid") { item.abilityid = v.parse().ok(); } if attrs.get("twohanded").is_some() { item.two_handed = true; }
if let Some(v) = attrs.get("swap") { item.swap = v.parse().ok(); } if let Some(v) = attrs.get("foodamount") { item.food_amount = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("twohanded") { item.twohanded = v.parse().ok(); } if let Some(v) = attrs.get("foodfrequency") { item.food_frequency = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("foodamount") { item.foodamount = v.parse().ok(); } if let Some(v) = attrs.get("foodtime") { item.food_time = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("foodfrequency") { item.foodfrequency = v.parse().ok(); } if let Some(v) = attrs.get("foodlevel") { item.food_level = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("foodtime") { item.foodtime = v.parse().ok(); } if let Some(v) = attrs.get("handmodel") { item.handmodel = v.clone(); }
if let Some(v) = attrs.get("foodlevel") { item.foodlevel = v.parse().ok(); } if let Some(v) = attrs.get("groundmodel") {
if let Some(v) = attrs.get("craftingskill") { item.craftingskill = Some(v.clone()); } item.ground_model = v.parse().unwrap_or(item.type_id);
if let Some(v) = attrs.get("workbench") { item.workbench = v.parse().ok(); } }
if let Some(v) = attrs.get("craftingitems") { item.craftingitems = Some(v.clone()); } if let Some(v) = attrs.get("usingitemmodel") { item.using_item_model = v.clone(); }
if let Some(v) = attrs.get("handmodel") { item.handmodel = Some(v.clone()); } if let Some(v) = attrs.get("dropsfx") { item.drop_sfx = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("groundmodel") { item.groundmodel = Some(v.clone()); } if let Some(v) = attrs.get("pickupsfx") { item.pickup_sfx = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("usingitemmodel") { item.usingitemmodel = Some(v.clone()); } if let Some(v) = attrs.get("storagesize") { item.storage_size = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("dropsfx") { item.dropsfx = Some(v.clone()); } if attrs.get("hidemilestone").is_some() { item.hide_milestone = true; }
if let Some(v) = attrs.get("pickupsfx") { item.pickupsfx = Some(v.clone()); } if attrs.get("generateicon").is_some() { item.generate_icon = true; }
if let Some(v) = attrs.get("hitgfx") { item.hitgfx = Some(v.clone()); } if let Some(v) = attrs.get("comment") { item.comment = v.clone(); }
if let Some(v) = attrs.get("attackanimations") { item.attackanimations = Some(v.clone()); }
if let Some(v) = attrs.get("attackanimationspeed") { item.attackanimationspeed = Some(v.clone()); }
if let Some(v) = attrs.get("attackhitsounds") { item.attackhitsounds = Some(v.clone()); }
if let Some(v) = attrs.get("storageitem") { item.storageitem = Some(v.clone()); }
if let Some(v) = attrs.get("storagesize") { item.storagesize = v.parse().ok(); }
if let Some(v) = attrs.get("hidemilestone") { item.hidemilestone = v.parse().ok(); }
if let Some(v) = attrs.get("generateicon") { item.generateicon = v.parse().ok(); }
if let Some(v) = attrs.get("comment") { item.comment = Some(v.clone()); }
current_item = Some(item); current_item = Some(item);
} }
@@ -101,44 +93,32 @@ pub fn parse_items_xml<P: AsRef<Path>>(path: P) -> Result<Vec<Item>, XmlParseErr
if let Some(ref mut item) = current_item { if let Some(ref mut item) = current_item {
let attrs = parse_attributes(&e)?; let attrs = parse_attributes(&e)?;
let stat = parse_stat(&attrs); let stat = parse_stat(&attrs);
item.stats.push(stat); // Convert legacy ItemStat to new Stat vec
let stats = stat.to_stats();
item.stats.extend(stats);
} }
} }
b"crafting" => { b"crafting" => {
if let Some(ref mut item) = current_item { // Crafting recipes are now handled by item_loader for full functionality
let attrs = parse_attributes(&e)?; // This is kept for backwards compatibility but doesn't fully populate the new structure
let recipe = CraftingRecipe {
workbench: attrs.get("workbench").and_then(|v| v.parse().ok()),
craftingitems: attrs.get("craftingitems").cloned(),
craftingskill: attrs.get("craftingskill").cloned(),
checks: attrs.get("checks").cloned(),
};
item.crafting_recipes.push(recipe);
}
} }
b"anim" => { b"anim" => {
if let Some(ref mut item) = current_item { if let Some(ref mut item) = current_item {
let attrs = parse_attributes(&e)?; let attrs = parse_attributes(&e)?;
let anim = AnimationSet { let anim = AnimationSet {
idle: attrs.get("idle").cloned(), idle: attrs.get("idle").and_then(|v| v.parse().ok()).unwrap_or(0),
walk: attrs.get("walk").cloned(), walk: attrs.get("walk").and_then(|v| v.parse().ok()).unwrap_or(0),
run: attrs.get("run").cloned(), run: attrs.get("run").and_then(|v| v.parse().ok()).unwrap_or(0),
weaponattack: attrs.get("weaponattack").cloned(), weapon_attack: attrs.get("weaponattack").and_then(|v| v.parse().ok()).unwrap_or(0),
takehit: attrs.get("takehit").cloned(), takehit: attrs.get("takehit").and_then(|v| v.parse().ok()).unwrap_or(0),
use_anim: attrs.get("use").and_then(|v| v.parse().ok()).unwrap_or(0),
}; };
item.animations = Some(anim); item.animations = Some(anim);
} }
} }
b"generate" => { b"generate" => {
if let Some(ref mut item) = current_item { // Generate rules are now handled by item_loader for full functionality
let attrs = parse_attributes(&e)?; // This is kept for backwards compatibility but doesn't process them
let rule = GenerateRule {
generatestats: attrs.get("generatestats").cloned(),
generatecrafting: attrs.get("generatecrafting").and_then(|v| v.parse().ok()),
generateicon: attrs.get("generateicon").and_then(|v| v.parse().ok()),
};
item.generate_rules.push(rule);
}
} }
_ => {} _ => {}
} }