Files
cursebreaker-parser-rust/cursebreaker-parser/src/databases/npc_database.rs
2026-01-10 09:22:58 +00:00

194 lines
5.1 KiB
Rust

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<Npc>,
npcs_by_id: HashMap<i32, usize>,
npcs_by_name: HashMap<String, Vec<usize>>,
}
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<P: AsRef<Path>>(path: P) -> Result<Self, XmlParseError> {
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<Npc>) {
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<usize, diesel::result::Error> {
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<Self, diesel::result::Error> {
use crate::schema::npcs::dsl::*;
#[derive(Queryable)]
struct NpcRecord {
id: Option<i32>,
name: String,
data: String,
}
let records = npcs.load::<NpcRecord>(conn)?;
let mut loaded_npcs = Vec::new();
for record in records {
if let Ok(npc) = serde_json::from_str::<Npc>(&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()
}
}