use crate::types::Npc; use crate::xml_parser::{parse_npcs_xml, XmlParseError}; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; use std::collections::HashMap; use std::path::Path; /// A database for managing NPCs loaded from XML files #[derive(Debug, Clone)] pub struct NpcDatabase { npcs: Vec, npcs_by_id: HashMap, npcs_by_name: HashMap>, } impl NpcDatabase { /// Create a new empty NpcDatabase pub fn new() -> Self { Self { npcs: Vec::new(), npcs_by_id: HashMap::new(), npcs_by_name: HashMap::new(), } } /// Load NPCs from an XML file pub fn load_from_xml>(path: P) -> Result { let npcs = parse_npcs_xml(path)?; let mut db = Self::new(); db.add_npcs(npcs); Ok(db) } /// Add NPCs to the database pub fn add_npcs(&mut self, npcs: Vec) { for npc in npcs { let index = self.npcs.len(); self.npcs_by_id.insert(npc.id, index); self.npcs_by_name .entry(npc.name.clone()) .or_insert_with(Vec::new) .push(index); self.npcs.push(npc); } } /// Get an NPC by ID pub fn get_by_id(&self, id: i32) -> Option<&Npc> { self.npcs_by_id .get(&id) .and_then(|&index| self.npcs.get(index)) } /// Get NPCs by name pub fn get_by_name(&self, name: &str) -> Vec<&Npc> { self.npcs_by_name .get(name) .map(|indices| { indices .iter() .filter_map(|&index| self.npcs.get(index)) .collect() }) .unwrap_or_default() } /// Get all NPCs pub fn all_npcs(&self) -> &[Npc] { &self.npcs } /// Get all hostile NPCs (can fight and aggressive) pub fn get_hostile(&self) -> Vec<&Npc> { self.npcs .iter() .filter(|npc| { npc.canfight == Some(1) && npc.aggressive == Some(1) }) .collect() } /// Get all interactable NPCs pub fn get_interactable(&self) -> Vec<&Npc> { self.npcs .iter() .filter(|npc| npc.interactable == Some(1)) .collect() } /// Get NPCs by tag pub fn get_by_tag(&self, tag: &str) -> Vec<&Npc> { self.npcs .iter() .filter(|npc| { npc.tags .as_ref() .map(|tags| tags.split(',').any(|t| t.trim() == tag)) .unwrap_or(false) }) .collect() } /// Get NPCs that offer shops pub fn get_shopkeepers(&self) -> Vec<&Npc> { self.npcs .iter() .filter(|npc| npc.shop.is_some()) .collect() } /// Get number of NPCs in database pub fn len(&self) -> usize { self.npcs.len() } /// Check if database is empty pub fn is_empty(&self) -> bool { self.npcs.is_empty() } /// Prepare NPCs for SQL insertion (deprecated - use save_to_db instead) #[deprecated(note = "Use save_to_db() to save directly to SQLite database")] pub fn prepare_for_sql(&self) -> Vec<(i32, String, String)> { self.npcs .iter() .map(|npc| { let json = serde_json::to_string(npc).unwrap_or_else(|_| "{}".to_string()); (npc.id, npc.name.clone(), json) }) .collect() } /// Save all NPCs to SQLite database pub fn save_to_db(&self, conn: &mut SqliteConnection) -> Result { use crate::schema::npcs; let records: Vec<_> = self .npcs .iter() .map(|npc| { let json = serde_json::to_string(npc).unwrap_or_else(|_| "{}".to_string()); ( npcs::id.eq(npc.id), npcs::name.eq(&npc.name), npcs::data.eq(json), ) }) .collect(); let mut count = 0; for record in records { diesel::insert_into(npcs::table) .values(&record) .execute(conn)?; count += 1; } Ok(count) } /// Load all NPCs from SQLite database pub fn load_from_db(conn: &mut SqliteConnection) -> Result { use crate::schema::npcs::dsl::*; #[derive(Queryable)] struct NpcRecord { id: Option, name: String, data: String, } let records = npcs.load::(conn)?; let mut loaded_npcs = Vec::new(); for record in records { if let Ok(npc) = serde_json::from_str::(&record.data) { loaded_npcs.push(npc); } } let mut db = Self::new(); db.add_npcs(loaded_npcs); Ok(db) } } impl Default for NpcDatabase { fn default() -> Self { Self::new() } }