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::xml_parser::{parse_items_xml, XmlParseError};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::path::Path;
/// A database for managing game items loaded from XML files
@@ -9,6 +12,8 @@ pub struct ItemDatabase {
items: Vec<Item>,
items_by_id: HashMap<i32, usize>,
items_by_name: HashMap<String, Vec<usize>>,
stackable_item_ids: HashSet<i32>,
storage_item_ids: HashSet<i32>,
}
impl ItemDatabase {
@@ -18,10 +23,12 @@ impl ItemDatabase {
items: Vec::new(),
items_by_id: 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> {
let items = parse_items_xml(path)?;
let mut db = Self::new();
@@ -29,22 +36,68 @@ impl ItemDatabase {
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
pub fn add_items(&mut self, items: Vec<Item>) {
for item in items {
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)
self.items_by_name
.entry(item.name.clone())
.entry(item.item_name.clone())
.or_insert_with(Vec::new)
.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);
}
}
/// 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
pub fn get_by_id(&self, id: i32) -> Option<&Item> {
self.items_by_id
@@ -72,41 +125,62 @@ impl ItemDatabase {
/// Get items by category
pub fn get_by_category(&self, category: &str) -> Vec<&Item> {
self.items
.iter()
.filter(|item| {
item.category
.as_ref()
.map(|c| c == category)
.unwrap_or(false)
})
.collect()
use crate::types::ItemCategory;
use std::str::FromStr;
if let Ok(cat) = ItemCategory::from_str(category) {
self.items
.iter()
.filter(|item| item.has_category(cat))
.collect()
} else {
Vec::new()
}
}
/// Get items by slot
/// Get items by slot/item type
pub fn get_by_slot(&self, slot: &str) -> Vec<&Item> {
self.items
.iter()
.filter(|item| {
item.slot
.as_ref()
.map(|s| s == slot)
.unwrap_or(false)
})
.collect()
use crate::types::ItemType;
use std::str::FromStr;
if let Ok(item_type) = ItemType::from_str(slot) {
self.items
.iter()
.filter(|item| item.item_type == item_type)
.collect()
} else {
Vec::new()
}
}
/// Get items by skill requirement
pub fn get_by_skill(&self, skill: &str) -> Vec<&Item> {
self.items
.iter()
.filter(|item| {
item.skill
.as_ref()
.map(|s| s == skill)
.unwrap_or(false)
})
.collect()
use crate::types::SkillType;
use std::str::FromStr;
if let Ok(skill_type) = SkillType::from_str(skill) {
self.items
.iter()
.filter(|item| item.skill == skill_type)
.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
@@ -132,7 +206,7 @@ impl ItemDatabase {
.iter()
.map(|item| {
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()
}

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,7 @@
pub mod types;
mod xml_parser;
mod item_loader;
mod item_database;
mod npc_database;
mod quest_database;
@@ -63,10 +64,39 @@ pub use quest_database::QuestDatabase;
pub use harvestable_database::HarvestableDatabase;
pub use loot_database::LootDatabase;
pub use types::{
Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule, InteractableResource,
Npc, NpcStat, NpcLevel, RightClick, BarkGroup, Bark, QuestMarker, NpcAnimationSet,
Quest, QuestPhase, QuestReward,
Harvestable, HarvestableDrop,
LootTable, LootDrop,
// Items
Item,
ItemStat,
CraftingRecipe,
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;

View File

@@ -1,158 +1,649 @@
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)]
pub struct Item {
// Required fields
pub id: i32,
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>,
pub struct Stat {
pub stat_type: StatType,
pub value: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemStat {
// Damage stats
pub damagephysical: Option<i32>,
pub damagemagical: Option<i32>,
pub damageranged: Option<i32>,
pub struct ItemXpBoost {
pub skill_type: SkillType,
pub multiplier: f32,
}
// Accuracy stats
pub accuracyphysical: Option<i32>,
pub accuracymagical: Option<i32>,
pub accuracyranged: Option<i32>,
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermanentStatBoost {
pub stat: StatType,
pub amount: i32,
}
// Resistance stats
pub resistancephysical: Option<i32>,
pub resistancemagical: Option<i32>,
pub resistanceranged: Option<i32>,
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomItemName {
pub checks: String,
pub item_name: String,
}
// Core stats
pub health: Option<i32>,
pub mana: Option<i32>,
pub manaregen: Option<i32>,
pub healing: Option<i32>,
// Harvesting stats
pub harvestingspeedwoodcutting: Option<i32>,
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CraftingRecipeItem {
pub item_id: i32,
pub amount: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CraftingRecipe {
pub workbench: Option<i32>,
pub craftingitems: Option<String>,
pub craftingskill: Option<String>,
pub product: i32,
pub level: i32,
pub skill: SkillType,
pub workbench_id: i32,
pub items: Vec<CraftingRecipeItem>,
pub unlocked_by_default: bool,
pub xp: i32,
pub checks: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimationSet {
pub idle: Option<String>,
pub walk: Option<String>,
pub run: Option<String>,
pub weaponattack: Option<String>,
pub takehit: Option<String>,
pub idle: i32,
pub walk: i32,
pub run: i32,
pub takehit: i32,
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)]
pub struct GenerateRule {
pub generatestats: Option<String>,
pub generatecrafting: Option<i32>,
pub generateicon: Option<i32>,
pub generate_stats: Option<String>,
pub generate_crafting: bool,
}
// ============================================================================
// 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 {
pub fn new(id: i32, name: String) -> Self {
pub fn new(type_id: i32, name: String) -> Self {
Self {
id,
name,
level: None,
description: None,
price: None,
slot: None,
category: None,
skill: None,
tool: None,
stackable: None,
maxstack: None,
abilityid: None,
swap: None,
twohanded: None,
foodamount: None,
foodfrequency: None,
foodtime: None,
foodlevel: None,
craftingskill: None,
workbench: None,
craftingitems: None,
handmodel: None,
groundmodel: None,
usingitemmodel: None,
dropsfx: None,
pickupsfx: None,
hitgfx: None,
attackanimations: None,
attackanimationspeed: None,
attackhitsounds: None,
storageitem: None,
storagesize: None,
hidemilestone: None,
generateicon: None,
comment: None,
stats: Vec::new(),
type_id,
item_name: name,
item_type: ItemType::Resource,
item_categories: vec![ItemCategory::None],
level: 1,
undroppable: false,
undroppable_on_death: false,
two_handed: false,
unequip_destroy: false,
generate_icon: false,
hide_milestone: false,
cannot_craft_exceptional: false,
storage_all_items: false,
comment: String::new(),
description: String::new(),
effect_string: String::new(),
use_text: String::new(),
using_item_model: String::new(),
handmodel: String::new(),
ground_model: 0,
copy_model: 0,
drop_sfx: 0,
pickup_sfx: 0,
max_stack: 1,
storage_items: Vec::new(),
storage_size: 0,
ability_id: 0,
special_ability: 0,
learn_ability_id: 0,
skill: SkillType::None,
tool: Tool::None,
price: 0,
setup_price: false,
food_level: 0,
food_time: 0,
food_frequency: 0,
food_amount: 0,
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,
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;
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 quest::{Quest, QuestPhase, QuestReward};
pub use harvestable::{Harvestable, HarvestableDrop};

View File

@@ -1,5 +1,5 @@
use crate::types::{
Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule,
Item, ItemStat, AnimationSet,
Npc, NpcStat, NpcLevel, RightClick, BarkGroup, QuestMarker, NpcAnimationSet,
Quest, QuestPhase, QuestReward,
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);
// Parse optional attributes
if let Some(v) = attrs.get("level") { item.level = v.parse().ok(); }
if let Some(v) = attrs.get("description") { item.description = Some(v.clone()); }
if let Some(v) = attrs.get("price") { item.price = v.parse().ok(); }
if let Some(v) = attrs.get("slot") { item.slot = Some(v.clone()); }
if let Some(v) = attrs.get("category") { item.category = Some(v.clone()); }
if let Some(v) = attrs.get("skill") { item.skill = Some(v.clone()); }
if let Some(v) = attrs.get("tool") { item.tool = Some(v.clone()); }
if let Some(v) = attrs.get("stackable") { item.stackable = v.parse().ok(); }
if let Some(v) = attrs.get("maxstack") { item.maxstack = v.parse().ok(); }
if let Some(v) = attrs.get("abilityid") { item.abilityid = v.parse().ok(); }
if let Some(v) = attrs.get("swap") { item.swap = v.parse().ok(); }
if let Some(v) = attrs.get("twohanded") { item.twohanded = v.parse().ok(); }
if let Some(v) = attrs.get("foodamount") { item.foodamount = v.parse().ok(); }
if let Some(v) = attrs.get("foodfrequency") { item.foodfrequency = v.parse().ok(); }
if let Some(v) = attrs.get("foodtime") { item.foodtime = v.parse().ok(); }
if let Some(v) = attrs.get("foodlevel") { item.foodlevel = v.parse().ok(); }
if let Some(v) = attrs.get("craftingskill") { item.craftingskill = Some(v.clone()); }
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("handmodel") { item.handmodel = Some(v.clone()); }
if let Some(v) = attrs.get("groundmodel") { item.groundmodel = Some(v.clone()); }
if let Some(v) = attrs.get("usingitemmodel") { item.usingitemmodel = Some(v.clone()); }
if let Some(v) = attrs.get("dropsfx") { item.dropsfx = Some(v.clone()); }
if let Some(v) = attrs.get("pickupsfx") { item.pickupsfx = Some(v.clone()); }
if let Some(v) = attrs.get("hitgfx") { item.hitgfx = Some(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()); }
// Note: This is a legacy simple parser. For full functionality,
// use the item_loader module's load_items_from_directory function.
// Parse optional attributes using defaults for new structure
if let Some(v) = attrs.get("level") { item.level = v.parse().unwrap_or(1); }
if let Some(v) = attrs.get("description") { item.description = v.clone(); }
if let Some(v) = attrs.get("price") { item.price = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("maxstack") { item.max_stack = v.parse().unwrap_or(1); }
if let Some(v) = attrs.get("abilityid") { item.ability_id = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("swap") { item.swap_item = v.parse().unwrap_or(0); }
if attrs.get("twohanded").is_some() { item.two_handed = true; }
if let Some(v) = attrs.get("foodamount") { item.food_amount = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("foodfrequency") { item.food_frequency = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("foodtime") { item.food_time = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("foodlevel") { item.food_level = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("handmodel") { item.handmodel = v.clone(); }
if let Some(v) = attrs.get("groundmodel") {
item.ground_model = v.parse().unwrap_or(item.type_id);
}
if let Some(v) = attrs.get("usingitemmodel") { item.using_item_model = v.clone(); }
if let Some(v) = attrs.get("dropsfx") { item.drop_sfx = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("pickupsfx") { item.pickup_sfx = v.parse().unwrap_or(0); }
if let Some(v) = attrs.get("storagesize") { item.storage_size = v.parse().unwrap_or(0); }
if attrs.get("hidemilestone").is_some() { item.hide_milestone = true; }
if attrs.get("generateicon").is_some() { item.generate_icon = true; }
if let Some(v) = attrs.get("comment") { item.comment = v.clone(); }
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 {
let attrs = parse_attributes(&e)?;
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" => {
if let Some(ref mut item) = current_item {
let attrs = parse_attributes(&e)?;
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);
}
// Crafting recipes are now handled by item_loader for full functionality
// This is kept for backwards compatibility but doesn't fully populate the new structure
}
b"anim" => {
if let Some(ref mut item) = current_item {
let attrs = parse_attributes(&e)?;
let anim = AnimationSet {
idle: attrs.get("idle").cloned(),
walk: attrs.get("walk").cloned(),
run: attrs.get("run").cloned(),
weaponattack: attrs.get("weaponattack").cloned(),
takehit: attrs.get("takehit").cloned(),
idle: attrs.get("idle").and_then(|v| v.parse().ok()).unwrap_or(0),
walk: attrs.get("walk").and_then(|v| v.parse().ok()).unwrap_or(0),
run: attrs.get("run").and_then(|v| v.parse().ok()).unwrap_or(0),
weapon_attack: attrs.get("weaponattack").and_then(|v| v.parse().ok()).unwrap_or(0),
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);
}
}
b"generate" => {
if let Some(ref mut item) = current_item {
let attrs = parse_attributes(&e)?;
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);
}
// Generate rules are now handled by item_loader for full functionality
// This is kept for backwards compatibility but doesn't process them
}
_ => {}
}