Galactica/crates/content/src/lib.rs

341 lines
8.6 KiB
Rust
Raw Normal View History

2023-12-29 15:46:09 -08:00
#![warn(missing_docs)]
//! This subcrate is responsible for loading, parsing, validating game content,
//! which is usually stored in `./content`.
2023-12-30 16:57:03 -08:00
mod handle;
mod part;
2023-12-29 15:14:04 -08:00
mod util;
2023-12-24 23:03:00 -08:00
2023-12-30 16:57:03 -08:00
use anyhow::{Context, Result};
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
use std::{
collections::HashMap,
fs::File,
io::Read,
path::{Path, PathBuf},
};
2023-12-27 19:51:58 -08:00
use toml;
2023-12-25 09:01:12 -08:00
use walkdir::WalkDir;
2024-01-05 12:09:59 -08:00
pub use handle::{
EffectHandle, FactionHandle, GunHandle, OutfitHandle, ShipHandle, SpriteHandle, SystemHandle,
};
2023-12-30 21:05:06 -08:00
pub use part::{
2024-01-05 12:09:59 -08:00
Effect, EnginePoint, Faction, Gun, GunPoint, ImpactInheritVelocity, Outfit, OutfitSpace,
Projectile, ProjectileCollider, Relationship, RepeatMode, Ship, Sprite, System,
2023-12-30 21:05:06 -08:00
};
2023-12-30 16:57:03 -08:00
2023-12-27 19:51:58 -08:00
mod syntax {
2024-01-01 15:41:47 -08:00
use anyhow::{bail, Context, Result};
2023-12-27 19:51:58 -08:00
use serde::Deserialize;
2024-01-01 15:41:47 -08:00
use std::{collections::HashMap, fmt::Display, hash::Hash};
2023-12-30 16:57:03 -08:00
2024-01-05 12:09:59 -08:00
use crate::part::{effect, faction, gun, outfit, ship, sprite, system};
2023-12-27 19:51:58 -08:00
#[derive(Debug, Deserialize)]
pub struct Root {
2023-12-27 20:13:39 -08:00
pub gun: Option<HashMap<String, gun::syntax::Gun>>,
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
pub system: Option<HashMap<String, system::syntax::System>>,
2023-12-30 20:27:53 -08:00
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
pub sprite: Option<HashMap<String, sprite::syntax::Sprite>>,
2023-12-30 16:57:03 -08:00
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
2024-01-05 12:09:59 -08:00
pub effect: Option<HashMap<String, effect::syntax::Effect>>,
}
2024-01-01 15:41:47 -08:00
fn merge_hashmap<K, V>(
to: &mut Option<HashMap<K, V>>,
mut from: Option<HashMap<K, V>>,
) -> Result<()>
where
K: Hash + Eq + Display,
{
if to.is_none() {
*to = from.take();
return Ok(());
}
if let Some(from_inner) = from {
let to_inner = to.as_mut().unwrap();
for (k, v) in from_inner {
if to_inner.contains_key(&k) {
bail!("Found duplicate key `{k}`");
} else {
to_inner.insert(k, v);
}
}
}
return Ok(());
}
impl Root {
pub fn new() -> Self {
Self {
gun: None,
ship: None,
system: None,
2023-12-30 20:27:53 -08:00
outfit: None,
sprite: None,
2023-12-30 16:57:03 -08:00
faction: None,
2024-01-05 12:09:59 -08:00
effect: None,
}
}
pub fn merge(&mut self, other: Root) -> Result<()> {
2024-01-01 15:41:47 -08:00
merge_hashmap(&mut self.gun, other.gun).with_context(|| "while merging guns")?;
merge_hashmap(&mut self.ship, other.ship).with_context(|| "while merging ships")?;
merge_hashmap(&mut self.system, other.system)
.with_context(|| "while merging systems")?;
merge_hashmap(&mut self.outfit, other.outfit)
.with_context(|| "while merging outfits")?;
merge_hashmap(&mut self.sprite, other.sprite)
.with_context(|| "while merging sprites")?;
2024-01-01 15:41:47 -08:00
merge_hashmap(&mut self.faction, other.faction)
.with_context(|| "while merging factions")?;
2024-01-05 12:09:59 -08:00
merge_hashmap(&mut self.effect, other.effect)
.with_context(|| "while merging effects")?;
return Ok(());
}
2023-12-27 19:51:58 -08:00
}
}
trait Build {
type InputSyntaxType;
2023-12-27 19:51:58 -08:00
/// Build a processed System struct from raw serde data
2024-01-05 12:09:59 -08:00
fn build(
root: Self::InputSyntaxType,
build_context: &mut ContentBuildContext,
content: &mut Content,
) -> Result<()>
2023-12-27 19:51:58 -08:00
where
Self: Sized;
}
2024-01-05 12:09:59 -08:00
/// Stores temporary data while building context objects
#[derive(Debug)]
pub(crate) struct ContentBuildContext {
pub effect_index: HashMap<String, EffectHandle>,
}
impl ContentBuildContext {
fn new() -> Self {
Self {
effect_index: HashMap::new(),
}
}
}
2023-12-30 17:39:19 -08:00
/// Represents static game content
2023-12-27 19:51:58 -08:00
#[derive(Debug)]
pub struct Content {
2024-01-01 15:41:47 -08:00
/* Configuration values */
/// Root directory for image
image_root: PathBuf,
/// Name of starfield sprite
starfield_sprite_name: String,
2023-12-27 19:51:58 -08:00
/// Sprites
2024-01-05 12:09:59 -08:00
pub sprites: Vec<Sprite>,
2024-01-01 15:46:39 -08:00
/// Map strings to texture names.
/// This is only necessary because we need to hard-code a few texture names for UI elements.
2024-01-05 12:09:59 -08:00
sprite_index: HashMap<String, SpriteHandle>,
/// The texture to use for starfield stars
2024-01-05 12:09:59 -08:00
starfield_handle: Option<SpriteHandle>,
/// Keeps track of which images are in which texture
sprite_atlas: SpriteAtlas,
2024-01-05 12:09:59 -08:00
outfits: Vec<Outfit>,
guns: Vec<Gun>, // TODO: merge with outfit
ships: Vec<Ship>,
systems: Vec<System>,
factions: Vec<Faction>,
effects: Vec<Effect>,
2023-12-27 20:13:39 -08:00
}
2023-12-27 19:51:58 -08:00
2024-01-01 15:41:47 -08:00
// Loading methods
2023-12-27 20:13:39 -08:00
impl Content {
fn try_parse(path: &Path) -> Result<syntax::Root> {
let mut file_string = String::new();
let _ = File::open(path)?.read_to_string(&mut file_string);
let file_string = file_string.trim();
return Ok(toml::from_str(&file_string)?);
}
2023-12-25 09:01:12 -08:00
2023-12-29 15:46:09 -08:00
/// Load content from a directory.
pub fn load_dir(
path: PathBuf,
texture_root: PathBuf,
atlas_index: PathBuf,
starfield_texture_name: String,
) -> Result<Self> {
let mut root = syntax::Root::new();
2023-12-27 19:51:58 -08:00
for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
if e.metadata().unwrap().is_file() {
// TODO: better warnings
match e.path().extension() {
Some(t) => {
if t.to_str() != Some("toml") {
println!("[WARNING] {e:#?} is not a toml file, skipping.");
continue;
}
}
None => {
println!("[WARNING] {e:#?} is not a toml file, skipping.");
continue;
}
}
let path = e.path();
let this_root = Self::try_parse(path)
.with_context(|| format!("Could not read {}", path.display()))?;
root.merge(this_root)
2023-12-27 19:51:58 -08:00
.with_context(|| format!("Could not parse {}", path.display()))?;
2023-12-25 09:01:12 -08:00
}
}
let atlas: SpriteAtlas = {
let mut file_string = String::new();
let _ = File::open(atlas_index)?.read_to_string(&mut file_string);
let file_string = file_string.trim();
toml::from_str(&file_string)?
};
2024-01-05 12:09:59 -08:00
let mut build_context = ContentBuildContext::new();
let mut content = Self {
sprite_atlas: atlas,
systems: Vec::new(),
ships: Vec::new(),
guns: Vec::new(),
2023-12-30 20:27:53 -08:00
outfits: Vec::new(),
sprites: Vec::new(),
2023-12-30 16:57:03 -08:00
factions: Vec::new(),
2024-01-05 12:09:59 -08:00
effects: Vec::new(),
sprite_index: HashMap::new(),
starfield_handle: None,
image_root: texture_root,
starfield_sprite_name: starfield_texture_name,
};
2024-01-05 12:09:59 -08:00
// TODO: enforce sprite and image limits
// Order matters. Some content types require another to be fully initialized
if root.sprite.is_some() {
2024-01-05 12:09:59 -08:00
part::sprite::Sprite::build(
root.sprite.take().unwrap(),
&mut build_context,
&mut content,
)?;
}
if root.effect.is_some() {
part::effect::Effect::build(
root.effect.take().unwrap(),
&mut build_context,
&mut content,
)?;
}
2024-01-05 12:09:59 -08:00
// Order below this line does not matter
if root.ship.is_some() {
2024-01-05 12:09:59 -08:00
part::ship::Ship::build(root.ship.take().unwrap(), &mut build_context, &mut content)?;
}
if root.gun.is_some() {
2024-01-05 12:09:59 -08:00
part::gun::Gun::build(root.gun.take().unwrap(), &mut build_context, &mut content)?;
}
2023-12-30 20:27:53 -08:00
if root.outfit.is_some() {
2024-01-05 12:09:59 -08:00
part::outfit::Outfit::build(
root.outfit.take().unwrap(),
&mut build_context,
&mut content,
)?;
}
if root.system.is_some() {
2024-01-05 12:09:59 -08:00
part::system::System::build(
root.system.take().unwrap(),
&mut build_context,
&mut content,
)?;
2023-12-30 16:57:03 -08:00
}
if root.faction.is_some() {
2024-01-05 12:09:59 -08:00
part::faction::Faction::build(
root.faction.take().unwrap(),
&mut build_context,
&mut content,
)?;
}
2023-12-27 19:51:58 -08:00
return Ok(content);
}
2023-12-25 09:01:12 -08:00
}
2024-01-01 15:41:47 -08:00
// Access methods
impl Content {
/// Get the handle for the starfield sprite
pub fn get_starfield_handle(&self) -> SpriteHandle {
2024-01-01 15:41:47 -08:00
match self.starfield_handle {
Some(h) => h,
None => unreachable!("Starfield sprite hasn't been loaded yet!"),
2024-01-01 15:41:47 -08:00
}
}
/// Get a handle from a sprite name
pub fn get_sprite_handle(&self, name: &str) -> SpriteHandle {
return match self.sprite_index.get(name) {
2024-01-01 15:41:47 -08:00
Some(s) => *s,
None => unreachable!("get_sprite_handle was called with a bad name!"),
2024-01-01 15:41:47 -08:00
};
}
/// Get a sprite from a handle
pub fn get_sprite(&self, h: SpriteHandle) -> &Sprite {
2024-01-01 15:41:47 -08:00
// In theory, this could fail if h has a bad index, but that shouldn't ever happen.
// The only handles that exist should be created by this crate.
2024-01-04 18:15:30 -08:00
return &self.sprites[h.index as usize];
}
2024-01-04 22:17:34 -08:00
/// Get the list of atlas files we may use
pub fn atlas_files(&self) -> &Vec<String> {
return &self.sprite_atlas.atlas_list;
}
/// Get a sprite from a path
pub fn get_image(&self, p: &Path) -> &SpriteAtlasImage {
self.sprite_atlas.index.get(p).unwrap()
2024-01-01 15:41:47 -08:00
}
/// Get an outfit from a handle
pub fn get_outfit(&self, h: OutfitHandle) -> &Outfit {
return &self.outfits[h.index];
}
/// Get a gun from a handle
pub fn get_gun(&self, h: GunHandle) -> &Gun {
return &self.guns[h.index];
}
/// Get a ship from a handle
2024-01-01 15:41:47 -08:00
pub fn get_ship(&self, h: ShipHandle) -> &Ship {
return &self.ships[h.index];
}
/// Get a system from a handle
pub fn get_system(&self, h: SystemHandle) -> &System {
return &self.systems[h.index];
}
/// Get a faction from a handle
pub fn get_faction(&self, h: FactionHandle) -> &Faction {
return &self.factions[h.index];
}
2024-01-05 12:09:59 -08:00
/// Get an effect from a handle
pub fn get_effect(&self, h: EffectHandle) -> &Effect {
return &self.effects[h.index];
}
2024-01-01 15:41:47 -08:00
}