improved items
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
1217
cursebreaker-parser/src/item_loader.rs
Normal file
1217
cursebreaker-parser/src/item_loader.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user