330 lines
10 KiB
Rust
330 lines
10 KiB
Rust
use crate::types::{FastTravelLocation, FastTravelType};
|
|
use crate::xml_parser::{
|
|
parse_fast_travel_canoe_xml, parse_fast_travel_locations_xml, parse_fast_travel_portals_xml,
|
|
XmlParseError,
|
|
};
|
|
use diesel::prelude::*;
|
|
use diesel::sqlite::SqliteConnection;
|
|
use std::collections::HashMap;
|
|
use std::path::Path;
|
|
|
|
/// A database for managing Fast Travel Locations loaded from XML files
|
|
#[derive(Debug, Clone)]
|
|
pub struct FastTravelDatabase {
|
|
locations: Vec<FastTravelLocation>,
|
|
// Map ID -> location index
|
|
locations_by_id: HashMap<i32, usize>,
|
|
// Map name -> list of location indices
|
|
locations_by_name: HashMap<String, Vec<usize>>,
|
|
// Map type -> list of location indices
|
|
locations_by_type: HashMap<FastTravelType, Vec<usize>>,
|
|
}
|
|
|
|
impl FastTravelDatabase {
|
|
/// Create a new empty FastTravelDatabase
|
|
pub fn new() -> Self {
|
|
Self {
|
|
locations: Vec::new(),
|
|
locations_by_id: HashMap::new(),
|
|
locations_by_name: HashMap::new(),
|
|
locations_by_type: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Load all fast travel types from their respective XML files in a directory
|
|
/// Expects the directory structure:
|
|
/// - dir/FastTravelLocations/FastTravelLocations.xml
|
|
/// - dir/FastTravelCanoe/FastTravelCanoe.xml
|
|
/// - dir/FastTravelPortals/FastTravelPortals.xml
|
|
pub fn load_from_directory<P: AsRef<Path>>(dir: P) -> Result<Self, XmlParseError> {
|
|
let dir = dir.as_ref();
|
|
let mut db = Self::new();
|
|
|
|
// Load regular locations
|
|
let locations_path = dir.join("FastTravelLocations/FastTravelLocations.xml");
|
|
if locations_path.exists() {
|
|
let locations = parse_fast_travel_locations_xml(&locations_path)?;
|
|
db.add_locations(locations);
|
|
}
|
|
|
|
// Load canoe locations
|
|
let canoe_path = dir.join("FastTravelCanoe/FastTravelCanoe.xml");
|
|
if canoe_path.exists() {
|
|
let canoe_locations = parse_fast_travel_canoe_xml(&canoe_path)?;
|
|
db.add_locations(canoe_locations);
|
|
}
|
|
|
|
// Load portal locations
|
|
let portals_path = dir.join("FastTravelPortals/FastTravelPortals.xml");
|
|
if portals_path.exists() {
|
|
let portals = parse_fast_travel_portals_xml(&portals_path)?;
|
|
db.add_locations(portals);
|
|
}
|
|
|
|
Ok(db)
|
|
}
|
|
|
|
/// Load only regular fast travel locations from XML
|
|
pub fn load_locations_from_xml<P: AsRef<Path>>(path: P) -> Result<Self, XmlParseError> {
|
|
let locations = parse_fast_travel_locations_xml(path)?;
|
|
let mut db = Self::new();
|
|
db.add_locations(locations);
|
|
Ok(db)
|
|
}
|
|
|
|
/// Load only canoe fast travel locations from XML
|
|
pub fn load_canoe_from_xml<P: AsRef<Path>>(path: P) -> Result<Self, XmlParseError> {
|
|
let locations = parse_fast_travel_canoe_xml(path)?;
|
|
let mut db = Self::new();
|
|
db.add_locations(locations);
|
|
Ok(db)
|
|
}
|
|
|
|
/// Load only portal fast travel locations from XML
|
|
pub fn load_portals_from_xml<P: AsRef<Path>>(path: P) -> Result<Self, XmlParseError> {
|
|
let locations = parse_fast_travel_portals_xml(path)?;
|
|
let mut db = Self::new();
|
|
db.add_locations(locations);
|
|
Ok(db)
|
|
}
|
|
|
|
/// Add fast travel locations to the database
|
|
pub fn add_locations(&mut self, locations: Vec<FastTravelLocation>) {
|
|
for location in locations {
|
|
let index = self.locations.len();
|
|
|
|
// Index by ID
|
|
self.locations_by_id.insert(location.id, index);
|
|
|
|
// Index by name
|
|
self.locations_by_name
|
|
.entry(location.name.clone())
|
|
.or_insert_with(Vec::new)
|
|
.push(index);
|
|
|
|
// Index by type
|
|
self.locations_by_type
|
|
.entry(location.travel_type)
|
|
.or_insert_with(Vec::new)
|
|
.push(index);
|
|
|
|
self.locations.push(location);
|
|
}
|
|
}
|
|
|
|
/// Get a fast travel location by ID
|
|
pub fn get_by_id(&self, id: i32) -> Option<&FastTravelLocation> {
|
|
self.locations_by_id
|
|
.get(&id)
|
|
.and_then(|&index| self.locations.get(index))
|
|
}
|
|
|
|
/// Get fast travel locations by name (returns all locations with matching name)
|
|
pub fn get_by_name(&self, name: &str) -> Vec<&FastTravelLocation> {
|
|
self.locations_by_name
|
|
.get(name)
|
|
.map(|indices| {
|
|
indices
|
|
.iter()
|
|
.filter_map(|&index| self.locations.get(index))
|
|
.collect()
|
|
})
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// Get all locations
|
|
pub fn all_locations(&self) -> &[FastTravelLocation] {
|
|
&self.locations
|
|
}
|
|
|
|
/// Get all locations of a specific type
|
|
pub fn get_by_type(&self, travel_type: FastTravelType) -> Vec<&FastTravelLocation> {
|
|
self.locations_by_type
|
|
.get(&travel_type)
|
|
.map(|indices| {
|
|
indices
|
|
.iter()
|
|
.filter_map(|&index| self.locations.get(index))
|
|
.collect()
|
|
})
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// Get all regular fast travel locations
|
|
pub fn get_locations(&self) -> Vec<&FastTravelLocation> {
|
|
self.get_by_type(FastTravelType::Location)
|
|
}
|
|
|
|
/// Get all canoe fast travel locations
|
|
pub fn get_canoe_locations(&self) -> Vec<&FastTravelLocation> {
|
|
self.get_by_type(FastTravelType::Canoe)
|
|
}
|
|
|
|
/// Get all portal fast travel locations
|
|
pub fn get_portals(&self) -> Vec<&FastTravelLocation> {
|
|
self.get_by_type(FastTravelType::Portal)
|
|
}
|
|
|
|
/// Get all unlocked locations (regular locations only)
|
|
pub fn get_unlocked_locations(&self) -> Vec<&FastTravelLocation> {
|
|
self.locations
|
|
.iter()
|
|
.filter(|loc| loc.unlocked)
|
|
.collect()
|
|
}
|
|
|
|
/// Get all locations with requirements
|
|
pub fn get_locations_with_requirements(&self) -> Vec<&FastTravelLocation> {
|
|
self.locations
|
|
.iter()
|
|
.filter(|loc| loc.has_requirements())
|
|
.collect()
|
|
}
|
|
|
|
/// Get all locations that have connections to other locations
|
|
pub fn get_connected_locations(&self) -> Vec<&FastTravelLocation> {
|
|
self.locations
|
|
.iter()
|
|
.filter(|loc| loc.has_connections())
|
|
.collect()
|
|
}
|
|
|
|
/// Get locations that are connected to a specific location ID
|
|
pub fn get_locations_connected_to(&self, id: i32) -> Vec<&FastTravelLocation> {
|
|
self.locations
|
|
.iter()
|
|
.filter(|loc| loc.get_connections().contains(&id))
|
|
.collect()
|
|
}
|
|
|
|
/// Get locations that require a specific quest
|
|
pub fn get_locations_requiring_quest(&self, quest_id: &str) -> Vec<&FastTravelLocation> {
|
|
self.locations
|
|
.iter()
|
|
.filter(|loc| loc.requires_quest(quest_id))
|
|
.collect()
|
|
}
|
|
|
|
/// Get locations that require a specific trait
|
|
pub fn get_locations_requiring_trait(&self, trait_id: i32) -> Vec<&FastTravelLocation> {
|
|
self.locations
|
|
.iter()
|
|
.filter(|loc| loc.requires_trait(trait_id))
|
|
.collect()
|
|
}
|
|
|
|
/// Get all unique location names
|
|
pub fn get_all_names(&self) -> Vec<String> {
|
|
self.locations_by_name.keys().cloned().collect()
|
|
}
|
|
|
|
/// Get count by type
|
|
pub fn count_by_type(&self, travel_type: FastTravelType) -> usize {
|
|
self.locations_by_type
|
|
.get(&travel_type)
|
|
.map(|v| v.len())
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
/// Get number of locations in database
|
|
pub fn len(&self) -> usize {
|
|
self.locations.len()
|
|
}
|
|
|
|
/// Check if database is empty
|
|
pub fn is_empty(&self) -> bool {
|
|
self.locations.is_empty()
|
|
}
|
|
|
|
/// Prepare fast travel locations 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, String)> {
|
|
self.locations
|
|
.iter()
|
|
.map(|location| {
|
|
let json =
|
|
serde_json::to_string(location).unwrap_or_else(|_| "{}".to_string());
|
|
(
|
|
location.id,
|
|
location.name.clone(),
|
|
location.travel_type.to_string(),
|
|
json,
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// Save all fast travel locations to SQLite database
|
|
pub fn save_to_db(&self, conn: &mut SqliteConnection) -> Result<usize, diesel::result::Error> {
|
|
use crate::schema::fast_travel_locations;
|
|
|
|
let records: Vec<_> = self
|
|
.locations
|
|
.iter()
|
|
.map(|location| {
|
|
let json = serde_json::to_string(location).unwrap_or_else(|_| "{}".to_string());
|
|
(
|
|
fast_travel_locations::id.eq(location.id),
|
|
fast_travel_locations::name.eq(&location.name),
|
|
fast_travel_locations::map_name.eq(""), // TODO: determine actual map name
|
|
fast_travel_locations::data.eq(json),
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
let mut count = 0;
|
|
for record in records {
|
|
diesel::insert_into(fast_travel_locations::table)
|
|
.values(&record)
|
|
.execute(conn)?;
|
|
count += 1;
|
|
}
|
|
|
|
Ok(count)
|
|
}
|
|
|
|
/// Load all fast travel locations from SQLite database
|
|
pub fn load_from_db(conn: &mut SqliteConnection) -> Result<Self, diesel::result::Error> {
|
|
use crate::schema::fast_travel_locations::dsl::*;
|
|
|
|
#[derive(Queryable)]
|
|
struct FastTravelLocationRecord {
|
|
id: Option<i32>,
|
|
name: String,
|
|
map_name: String,
|
|
data: String,
|
|
}
|
|
|
|
let records = fast_travel_locations.load::<FastTravelLocationRecord>(conn)?;
|
|
|
|
let mut loaded_locations = Vec::new();
|
|
for record in records {
|
|
if let Ok(location) = serde_json::from_str::<FastTravelLocation>(&record.data) {
|
|
loaded_locations.push(location);
|
|
}
|
|
}
|
|
|
|
let mut db = Self::new();
|
|
db.add_locations(loaded_locations);
|
|
Ok(db)
|
|
}
|
|
}
|
|
|
|
impl Default for FastTravelDatabase {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_fast_travel_database_basic() {
|
|
let mut db = FastTravelDatabase::new();
|
|
assert!(db.is_empty());
|
|
assert_eq!(db.len(), 0);
|
|
}
|
|
}
|