Compare commits
No commits in common. "389803eae9254d8c29b1cad8208fcceabb83c9e7" and "b69538b8c6a2c04d50499161b0eb97bae6eafee1" have entirely different histories.
389803eae9
...
b69538b8c6
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 5a444ca5c63aac4b4aca4866684859c08972f9e7
|
Subproject commit e4972a4daff7bac077fb2d32c298e3d6af46da91
|
|
@ -1,5 +0,0 @@
|
||||||
[gun."blaster"]
|
|
||||||
projectile.sprite = "projectile::blaster"
|
|
||||||
projectile.size = 100
|
|
||||||
projectile.speed = 300
|
|
||||||
projectile.lifetime = 2.0
|
|
|
@ -1,6 +1,7 @@
|
||||||
[ship."Gypsum"]
|
# content type: ship
|
||||||
|
[ship]
|
||||||
|
name = "Gypsum"
|
||||||
sprite = "ship::gypsum"
|
sprite = "ship::gypsum"
|
||||||
size = 100
|
size = 100
|
||||||
|
|
||||||
engines = [{ x = 0.0, y = -105, size = 50.0 }]
|
engines = [{ x = 0.0, y = -105, size = 50.0 }]
|
||||||
guns = [{ x = 0.0, y = 100 }]
|
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
[system."12 Autumn Above"]
|
# content type: system
|
||||||
|
[system]
|
||||||
|
name = "12 Autumn above"
|
||||||
|
|
||||||
object.star.sprite = "star::star"
|
[object.star]
|
||||||
object.star.position = [0.0, 0.0, 30.0]
|
sprite = "star::star"
|
||||||
object.star.size = 2000
|
position = [0.0, 0.0, 30.0]
|
||||||
|
size = 2000
|
||||||
|
|
||||||
object.earth.sprite = "planet::earth"
|
|
||||||
object.earth.position.center = "star"
|
|
||||||
object.earth.position.radius = 4000
|
|
||||||
object.earth.position.angle = 0
|
|
||||||
object.earth.position.z = 10.0
|
|
||||||
object.earth.size = 1000
|
|
||||||
|
|
||||||
object.luna.sprite = "planet::luna"
|
[object.earth]
|
||||||
object.luna.position.center = "earth"
|
sprite = "planet::earth"
|
||||||
object.luna.position.radius = 1600
|
position.center = "star"
|
||||||
object.luna.position.angle = 135
|
position.radius = 4000
|
||||||
object.luna.position.z = 7.8
|
position.angle = 0
|
||||||
object.luna.size = 500
|
position.z = 10.0
|
||||||
object.luna.angle = -45
|
size = 1000
|
||||||
|
|
||||||
|
|
||||||
|
[object.luna]
|
||||||
|
sprite = "planet::luna"
|
||||||
|
position.center = "earth"
|
||||||
|
position.radius = 1600
|
||||||
|
position.angle = 135
|
||||||
|
position.z = 7.8
|
||||||
|
size = 500
|
||||||
|
angle = -45
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use super::{syntax, ContentType};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Content {
|
||||||
|
pub systems: Vec<syntax::system::System>,
|
||||||
|
pub ships: Vec<syntax::ship::Ship>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content {
|
||||||
|
pub fn new(cv: Vec<(PathBuf, ContentType)>) -> Result<Self> {
|
||||||
|
let mut systems = Vec::new();
|
||||||
|
let mut ships = Vec::new();
|
||||||
|
|
||||||
|
// These methods check intra-file consistency
|
||||||
|
for (p, c) in cv {
|
||||||
|
match c {
|
||||||
|
ContentType::System(v) => systems.push(
|
||||||
|
syntax::system::System::parse(v)
|
||||||
|
.with_context(|| format!("Could not parse {}", p.display()))?,
|
||||||
|
),
|
||||||
|
|
||||||
|
ContentType::Ship(v) => ships.push(
|
||||||
|
syntax::ship::Ship::parse(v)
|
||||||
|
.with_context(|| format!("Could not parse {}", p.display()))?,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Self { systems, ships });
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use std::{fs::File, io::Read, path::Path};
|
||||||
|
|
||||||
|
use super::syntax;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ContentType {
|
||||||
|
System(syntax::system::toml::SystemRoot),
|
||||||
|
Ship(syntax::ship::toml::ShipRoot),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check content without loading game
|
||||||
|
impl ContentType {
|
||||||
|
pub fn try_parse(file_string: &str) -> Result<Option<Self>> {
|
||||||
|
// TODO: More advanced parsing, read the whole top comment
|
||||||
|
let first = match file_string.split_once("\n") {
|
||||||
|
None => bail!("This file is empty."),
|
||||||
|
Some((first, _)) => first,
|
||||||
|
};
|
||||||
|
let type_spec = first[1..].trim(); // Remove hash
|
||||||
|
|
||||||
|
let type_spec = if type_spec.starts_with("content type: ") {
|
||||||
|
type_spec[14..].to_owned()
|
||||||
|
} else {
|
||||||
|
bail!("No content type specified")
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(match &type_spec[..] {
|
||||||
|
"system" => Some(Self::System(toml::from_str(&file_string)?)),
|
||||||
|
"ship" => Some(Self::Ship(toml::from_str(&file_string)?)),
|
||||||
|
_ => bail!("Invalid content type `{}`", type_spec),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_path(path: &Path) -> Result<Option<Self>> {
|
||||||
|
let mut file_string = String::new();
|
||||||
|
let _ = File::open(path)?.read_to_string(&mut file_string);
|
||||||
|
let file_string = file_string.trim();
|
||||||
|
return Self::try_parse(&file_string);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
pub(super) mod syntax {
|
|
||||||
use serde::Deserialize;
|
|
||||||
// Raw serde syntax structs.
|
|
||||||
// These are never seen by code outside this crate.
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Gun {
|
|
||||||
pub projectile: Projectile,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Projectile {
|
|
||||||
pub sprite: String,
|
|
||||||
pub size: f32,
|
|
||||||
pub speed: f32,
|
|
||||||
pub lifetime: f32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Gun {
|
|
||||||
pub name: String,
|
|
||||||
pub projectile: Projectile,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Projectile {
|
|
||||||
pub sprite: String,
|
|
||||||
pub size: f32,
|
|
||||||
pub speed: f32,
|
|
||||||
pub lifetime: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Build for Gun {
|
|
||||||
fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
|
|
||||||
let gun = if let Some(gun) = &root.gun {
|
|
||||||
gun
|
|
||||||
} else {
|
|
||||||
return Ok(vec![]);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for (gun_name, gun) in gun {
|
|
||||||
out.push(Self {
|
|
||||||
name: gun_name.to_owned(),
|
|
||||||
projectile: Projectile {
|
|
||||||
sprite: gun.projectile.sprite.to_owned(),
|
|
||||||
size: gun.projectile.size,
|
|
||||||
speed: gun.projectile.speed,
|
|
||||||
lifetime: gun.projectile.lifetime,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(out);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +1,42 @@
|
||||||
#![allow(dead_code)]
|
mod content;
|
||||||
mod gun;
|
mod contenttype;
|
||||||
mod ship;
|
mod syntax;
|
||||||
mod system;
|
|
||||||
|
|
||||||
pub use gun::{Gun, Projectile};
|
pub use content::Content;
|
||||||
pub use ship::{Engine, Ship, ShipGun};
|
pub use contenttype::ContentType;
|
||||||
pub use system::{Object, System};
|
pub use syntax::ship;
|
||||||
|
pub use syntax::system;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::{fs::File, io::Read, path::Path};
|
|
||||||
use toml;
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
mod syntax {
|
pub fn load_content_dir(path: &str) -> Result<Content> {
|
||||||
use super::HashMap;
|
let mut raw_content = Vec::new();
|
||||||
use super::{gun, ship, system};
|
for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
|
||||||
use serde::Deserialize;
|
if e.metadata().unwrap().is_file() {
|
||||||
|
// TODO: better warnings
|
||||||
#[derive(Debug, Deserialize)]
|
match e.path().extension() {
|
||||||
pub struct Root {
|
Some(t) => {
|
||||||
pub gun: Option<HashMap<String, gun::syntax::Gun>>,
|
if t.to_str() != Some("toml") {
|
||||||
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
|
|
||||||
pub system: Option<HashMap<String, system::syntax::System>>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Build {
|
|
||||||
/// Build a processed System struct from raw serde data
|
|
||||||
fn build(root: &syntax::Root) -> Result<Vec<Self>>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Content {
|
|
||||||
pub systems: Vec<system::System>,
|
|
||||||
pub ships: Vec<ship::Ship>,
|
|
||||||
pub guns: Vec<gun::Gun>,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! quick_name_dup_check {
|
|
||||||
($array:expr, $root:ident, $build:expr) => {{
|
|
||||||
let mut p = $build(&$root)?;
|
|
||||||
for s in &$array {
|
|
||||||
for o in &p {
|
|
||||||
if s.name == o.name {
|
|
||||||
bail!(
|
|
||||||
"Error parsing content: duplicate ship names `{}` and `{}`",
|
|
||||||
s.name,
|
|
||||||
o.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$array.append(&mut p);
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
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)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_root(&mut self, root: syntax::Root) -> Result<()> {
|
|
||||||
quick_name_dup_check!(self.systems, root, system::System::build);
|
|
||||||
quick_name_dup_check!(self.guns, root, gun::Gun::build);
|
|
||||||
quick_name_dup_check!(self.ships, root, ship::Ship::build);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_dir(path: &str) -> Result<Self> {
|
|
||||||
let mut content = Self {
|
|
||||||
systems: Vec::new(),
|
|
||||||
ships: Vec::new(),
|
|
||||||
guns: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
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.");
|
println!("[WARNING] {e:#?} is not a toml file, skipping.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
println!("[WARNING] {e:#?} is not a toml file, skipping.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let path = e.path();
|
let c = crate::content::ContentType::from_path(e.path())
|
||||||
let root = Self::try_parse(path)
|
.with_context(|| format!("Could not load {:#?}", e.path()))?;
|
||||||
.with_context(|| format!("Could not load {:#?}", e.path()))?;
|
|
||||||
content
|
match c {
|
||||||
.add_root(root)
|
Some(c) => raw_content.push((e.path().to_path_buf(), c)),
|
||||||
.with_context(|| format!("Could not parse {}", path.display()))?;
|
None => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return crate::content::Content::new(raw_content);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use cgmath::Point2;
|
|
||||||
|
|
||||||
pub(super) mod syntax {
|
|
||||||
use serde::Deserialize;
|
|
||||||
// Raw serde syntax structs.
|
|
||||||
// These are never seen by code outside this crate.
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Ship {
|
|
||||||
pub sprite: String,
|
|
||||||
pub size: f32,
|
|
||||||
pub engines: Vec<Engine>,
|
|
||||||
pub guns: Vec<Gun>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Engine {
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
pub size: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Gun {
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processed data structs.
|
|
||||||
// These are exported.
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Ship {
|
|
||||||
pub name: String,
|
|
||||||
pub sprite: String,
|
|
||||||
pub size: f32,
|
|
||||||
pub engines: Vec<Engine>,
|
|
||||||
pub guns: Vec<ShipGun>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Engine {
|
|
||||||
pub pos: Point2<f32>,
|
|
||||||
pub size: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ShipGun {
|
|
||||||
pub pos: Point2<f32>,
|
|
||||||
pub cooldown: f32,
|
|
||||||
pub active_cooldown: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Build for Ship {
|
|
||||||
fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
|
|
||||||
let ship = if let Some(ship) = &root.ship {
|
|
||||||
ship
|
|
||||||
} else {
|
|
||||||
return Ok(vec![]);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for (ship_name, ship) in ship {
|
|
||||||
out.push(Self {
|
|
||||||
name: ship_name.to_owned(),
|
|
||||||
sprite: ship.sprite.to_owned(),
|
|
||||||
size: ship.size,
|
|
||||||
engines: ship
|
|
||||||
.engines
|
|
||||||
.iter()
|
|
||||||
.map(|e| Engine {
|
|
||||||
pos: Point2 { x: e.x, y: e.y },
|
|
||||||
size: e.size,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
guns: ship
|
|
||||||
.guns
|
|
||||||
.iter()
|
|
||||||
.map(|e| ShipGun {
|
|
||||||
pos: Point2 { x: e.x, y: e.y },
|
|
||||||
cooldown: 0.2,
|
|
||||||
active_cooldown: 0.0,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(out);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
pub mod ship;
|
||||||
|
pub mod system;
|
|
@ -0,0 +1,60 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use cgmath::Point2;
|
||||||
|
|
||||||
|
/// Toml file syntax
|
||||||
|
pub(in crate::content) mod toml {
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ShipRoot {
|
||||||
|
pub ship: Ship,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Ship {
|
||||||
|
pub name: String,
|
||||||
|
pub sprite: String,
|
||||||
|
pub size: f32,
|
||||||
|
pub engines: Vec<Engine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Engine {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Ship {
|
||||||
|
pub name: String,
|
||||||
|
pub sprite: String,
|
||||||
|
pub size: f32,
|
||||||
|
pub engines: Vec<Engine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Engine {
|
||||||
|
pub pos: Point2<f32>,
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ship {
|
||||||
|
pub fn parse(value: toml::ShipRoot) -> Result<Self> {
|
||||||
|
return Ok(Self {
|
||||||
|
name: value.ship.name,
|
||||||
|
sprite: value.ship.sprite,
|
||||||
|
size: value.ship.size,
|
||||||
|
engines: value
|
||||||
|
.ship
|
||||||
|
.engines
|
||||||
|
.iter()
|
||||||
|
.map(|e| Engine {
|
||||||
|
pos: Point2 { x: e.x, y: e.y },
|
||||||
|
size: e.size,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,15 +4,20 @@ use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::physics::Polar;
|
use crate::physics::Polar;
|
||||||
|
|
||||||
pub(super) mod syntax {
|
/// Toml file syntax
|
||||||
use super::HashMap;
|
pub(in crate::content) mod toml {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
// Raw serde syntax structs.
|
use std::collections::HashMap;
|
||||||
// These are never seen by code outside this crate.
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct SystemRoot {
|
||||||
|
pub system: System,
|
||||||
|
pub object: HashMap<String, Object>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct System {
|
pub struct System {
|
||||||
pub object: HashMap<String, Object>,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -74,9 +79,6 @@ pub(super) mod syntax {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processed data structs.
|
|
||||||
// These are exported.
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct System {
|
pub struct System {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -91,15 +93,15 @@ pub struct Object {
|
||||||
pub angle: Deg<f32>,
|
pub angle: Deg<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for resolve_position, never called on its own.
|
// Helper function for resolve_position, never called on its own.
|
||||||
fn resolve_coordinates(
|
fn resolve_coordinates(
|
||||||
objects: &HashMap<String, syntax::Object>,
|
objects: &HashMap<String, toml::Object>,
|
||||||
cor: &syntax::CoordinatesThree,
|
cor: &toml::CoordinatesThree,
|
||||||
mut cycle_detector: HashSet<String>,
|
mut cycle_detector: HashSet<String>,
|
||||||
) -> Result<Point3<f32>> {
|
) -> Result<Point3<f32>> {
|
||||||
match cor {
|
match cor {
|
||||||
syntax::CoordinatesThree::Coords(c) => Ok((*c).into()),
|
toml::CoordinatesThree::Coords(c) => Ok((*c).into()),
|
||||||
syntax::CoordinatesThree::Label(l) => {
|
toml::CoordinatesThree::Label(l) => {
|
||||||
if cycle_detector.contains(l) {
|
if cycle_detector.contains(l) {
|
||||||
bail!(
|
bail!(
|
||||||
"Found coordinate cycle: `{}`",
|
"Found coordinate cycle: `{}`",
|
||||||
|
@ -124,19 +126,19 @@ fn resolve_coordinates(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given an object, resolve its position as a Point3.
|
/// Given an object, resolve it's position as a Point3.
|
||||||
fn resolve_position(
|
fn resolve_position(
|
||||||
objects: &HashMap<String, syntax::Object>,
|
objects: &HashMap<String, toml::Object>,
|
||||||
obj: &syntax::Object,
|
obj: &toml::Object,
|
||||||
cycle_detector: HashSet<String>,
|
cycle_detector: HashSet<String>,
|
||||||
) -> Result<Point3<f32>> {
|
) -> Result<Point3<f32>> {
|
||||||
match &obj.position {
|
match &obj.position {
|
||||||
syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
|
toml::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
|
||||||
syntax::Position::Polar(p) => {
|
toml::Position::Polar(p) => {
|
||||||
let three = match &p.center {
|
let three = match &p.center {
|
||||||
syntax::CoordinatesTwo::Label(s) => syntax::CoordinatesThree::Label(s.clone()),
|
toml::CoordinatesTwo::Label(s) => toml::CoordinatesThree::Label(s.clone()),
|
||||||
syntax::CoordinatesTwo::Coords(v) => {
|
toml::CoordinatesTwo::Coords(v) => {
|
||||||
syntax::CoordinatesThree::Coords([v[0], v[1], f32::NAN])
|
toml::CoordinatesThree::Coords([v[0], v[1], f32::NAN])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let r = resolve_coordinates(&objects, &three, cycle_detector)?;
|
let r = resolve_coordinates(&objects, &three, cycle_detector)?;
|
||||||
|
@ -155,37 +157,26 @@ fn resolve_position(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Build for System {
|
impl System {
|
||||||
fn build(root: &super::syntax::Root) -> Result<Vec<Self>> {
|
pub fn parse(value: toml::SystemRoot) -> Result<Self> {
|
||||||
let system = if let Some(system) = &root.system {
|
let mut objects = Vec::new();
|
||||||
system
|
|
||||||
} else {
|
|
||||||
return Ok(vec![]);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
for (label, obj) in &value.object {
|
||||||
for (system_name, system) in system {
|
let mut cycle_detector = HashSet::new();
|
||||||
let mut objects = Vec::new();
|
cycle_detector.insert(label.to_owned());
|
||||||
|
|
||||||
for (label, obj) in &system.object {
|
objects.push(Object {
|
||||||
let mut cycle_detector = HashSet::new();
|
sprite: obj.sprite.clone(),
|
||||||
cycle_detector.insert(label.to_owned());
|
position: resolve_position(&value.object, &obj, cycle_detector)
|
||||||
|
.with_context(|| format!("In object {:#?}", label))?,
|
||||||
objects.push(Object {
|
size: obj.size,
|
||||||
sprite: obj.sprite.clone(),
|
angle: Deg(obj.angle.unwrap_or(0.0)),
|
||||||
position: resolve_position(&system.object, &obj, cycle_detector)
|
|
||||||
.with_context(|| format!("In object {:#?}", label))?,
|
|
||||||
size: obj.size,
|
|
||||||
angle: Deg(obj.angle.unwrap_or(0.0)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
out.push(Self {
|
|
||||||
name: system_name.to_owned(),
|
|
||||||
objects,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(out);
|
return Ok(Self {
|
||||||
|
name: value.system.name.clone(),
|
||||||
|
objects,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,33 +1,9 @@
|
||||||
use cgmath::{Deg, Point2, Point3, Vector2};
|
use cgmath::Deg;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
||||||
|
|
||||||
use super::{Camera, InputStatus, Ship, System};
|
use super::{Camera, InputStatus, Ship, System};
|
||||||
use crate::{
|
use crate::{consts, content::Content, render::Sprite, render::Spriteable};
|
||||||
consts,
|
|
||||||
content::Content,
|
|
||||||
render::Spriteable,
|
|
||||||
render::{Sprite, SpriteTexture},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Projectile {
|
|
||||||
pub position: Point2<f32>,
|
|
||||||
pub velocity: Vector2<f32>,
|
|
||||||
pub sprite: SpriteTexture,
|
|
||||||
pub angle: Deg<f32>,
|
|
||||||
pub lifetime: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Projectile {
|
|
||||||
pub fn tick(&mut self, t: f32) {
|
|
||||||
self.position += self.velocity * t;
|
|
||||||
self.lifetime -= t;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_expired(&self) -> bool {
|
|
||||||
return self.lifetime < 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
pub input: InputStatus,
|
pub input: InputStatus,
|
||||||
|
@ -37,7 +13,6 @@ pub struct Game {
|
||||||
pub system: System,
|
pub system: System,
|
||||||
pub camera: Camera,
|
pub camera: Camera,
|
||||||
paused: bool,
|
paused: bool,
|
||||||
pub projectiles: Vec<Projectile>,
|
|
||||||
pub time_scale: f32,
|
pub time_scale: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,14 +21,12 @@ impl Game {
|
||||||
Game {
|
Game {
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
input: InputStatus::new(),
|
input: InputStatus::new(),
|
||||||
projectiles: Vec::new(),
|
|
||||||
player: Ship::new(&ct.ships[0], (0.0, 0.0).into()),
|
player: Ship::new(&ct.ships[0], (0.0, 0.0).into()),
|
||||||
camera: Camera {
|
camera: Camera {
|
||||||
pos: (0.0, 0.0).into(),
|
pos: (0.0, 0.0).into(),
|
||||||
zoom: 500.0,
|
zoom: 500.0,
|
||||||
},
|
},
|
||||||
system: System::new(&ct.systems[0]),
|
system: System::new(&ct.systems[0]),
|
||||||
|
|
||||||
paused: false,
|
paused: false,
|
||||||
time_scale: 1.0,
|
time_scale: 1.0,
|
||||||
test: Ship::new(&ct.ships[0], (100.0, 100.0).into()),
|
test: Ship::new(&ct.ships[0], (100.0, 100.0).into()),
|
||||||
|
@ -84,16 +57,17 @@ impl Game {
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
||||||
|
|
||||||
self.projectiles.retain_mut(|p| {
|
self.player.engines_on = self.input.key_thrust;
|
||||||
p.tick(t);
|
if self.input.key_thrust {
|
||||||
!p.is_expired()
|
self.player.physicsbody.thrust(50.0 * t);
|
||||||
});
|
}
|
||||||
|
|
||||||
// Update player and handle result
|
if self.input.key_right {
|
||||||
self.player.update_controls(&self.input);
|
self.player.physicsbody.rot(Deg(35.0) * t);
|
||||||
let mut p = self.player.tick(t);
|
}
|
||||||
if p.projectiles.len() != 0 {
|
|
||||||
self.projectiles.append(&mut p.projectiles);
|
if self.input.key_left {
|
||||||
|
self.player.physicsbody.rot(Deg(-35.0) * t);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.input.v_scroll != 0.0 {
|
if self.input.v_scroll != 0.0 {
|
||||||
|
@ -102,6 +76,7 @@ impl Game {
|
||||||
self.input.v_scroll = 0.0;
|
self.input.v_scroll = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.player.physicsbody.tick(t);
|
||||||
self.camera.pos = self.player.physicsbody.pos;
|
self.camera.pos = self.player.physicsbody.pos;
|
||||||
|
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
|
@ -121,21 +96,6 @@ impl Game {
|
||||||
// I've tried this, but it doesn't seem to work with transparent textures.
|
// I've tried this, but it doesn't seem to work with transparent textures.
|
||||||
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
||||||
|
|
||||||
// Don't waste time sorting these, they should always be on top.
|
|
||||||
for p in &self.projectiles {
|
|
||||||
sprites.push(Sprite {
|
|
||||||
texture: p.sprite.clone(),
|
|
||||||
pos: Point3 {
|
|
||||||
x: p.position.x,
|
|
||||||
y: p.position.y,
|
|
||||||
z: 1.0,
|
|
||||||
},
|
|
||||||
size: 10.0,
|
|
||||||
angle: p.angle,
|
|
||||||
children: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprites;
|
return sprites;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ pub struct InputStatus {
|
||||||
pub key_left: bool,
|
pub key_left: bool,
|
||||||
pub key_right: bool,
|
pub key_right: bool,
|
||||||
pub key_thrust: bool,
|
pub key_thrust: bool,
|
||||||
pub key_guns: bool,
|
|
||||||
pub v_scroll: f32,
|
pub v_scroll: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +14,6 @@ impl InputStatus {
|
||||||
key_left: false,
|
key_left: false,
|
||||||
key_right: false,
|
key_right: false,
|
||||||
key_thrust: false,
|
key_thrust: false,
|
||||||
key_guns: false,
|
|
||||||
v_scroll: 0.0,
|
v_scroll: 0.0,
|
||||||
scroll_speed: 10.0,
|
scroll_speed: 10.0,
|
||||||
}
|
}
|
||||||
|
@ -25,7 +23,6 @@ impl InputStatus {
|
||||||
self.key_left = false;
|
self.key_left = false;
|
||||||
self.key_right = false;
|
self.key_right = false;
|
||||||
self.key_thrust = false;
|
self.key_thrust = false;
|
||||||
self.key_guns = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
|
pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
|
||||||
|
@ -34,7 +31,6 @@ impl InputStatus {
|
||||||
VirtualKeyCode::Left => self.key_left = down,
|
VirtualKeyCode::Left => self.key_left = down,
|
||||||
VirtualKeyCode::Right => self.key_right = down,
|
VirtualKeyCode::Right => self.key_right = down,
|
||||||
VirtualKeyCode::Up => self.key_thrust = down,
|
VirtualKeyCode::Up => self.key_thrust = down,
|
||||||
VirtualKeyCode::Space => self.key_guns = down,
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,118 +1,35 @@
|
||||||
use cgmath::{Deg, EuclideanSpace, Matrix2, Point2, Point3, Vector2};
|
use cgmath::{Deg, Point2, Point3};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
content,
|
content::{self, ship::Engine},
|
||||||
physics::PhysicsBody,
|
physics::PhysicsBody,
|
||||||
render::{Sprite, SpriteTexture, Spriteable, SubSprite},
|
render::{Sprite, SpriteTexture, Spriteable, SubSprite},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{game::Projectile, InputStatus};
|
|
||||||
|
|
||||||
pub struct ShipControls {
|
|
||||||
pub left: bool,
|
|
||||||
pub right: bool,
|
|
||||||
pub thrust: bool,
|
|
||||||
pub guns: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipControls {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
ShipControls {
|
|
||||||
left: false,
|
|
||||||
right: false,
|
|
||||||
thrust: false,
|
|
||||||
guns: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ShipTickResult {
|
|
||||||
pub projectiles: Vec<Projectile>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
pub physicsbody: PhysicsBody,
|
pub physicsbody: PhysicsBody,
|
||||||
pub controls: ShipControls,
|
pub engines_on: bool,
|
||||||
|
|
||||||
sprite: SpriteTexture,
|
sprite: SpriteTexture,
|
||||||
size: f32,
|
size: f32,
|
||||||
engines: Vec<content::Engine>,
|
engines: Vec<Engine>,
|
||||||
guns: Vec<content::ShipGun>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ship {
|
impl Ship {
|
||||||
pub fn new(ct: &content::Ship, pos: Point2<f32>) -> Self {
|
pub fn new(ct: &content::ship::Ship, pos: Point2<f32>) -> Self {
|
||||||
Ship {
|
Ship {
|
||||||
physicsbody: PhysicsBody::new(pos),
|
physicsbody: PhysicsBody::new(pos),
|
||||||
sprite: SpriteTexture(ct.sprite.clone()),
|
sprite: SpriteTexture(ct.sprite.clone()),
|
||||||
size: ct.size,
|
size: ct.size,
|
||||||
engines: ct.engines.clone(),
|
engines: ct.engines.clone(),
|
||||||
guns: ct.guns.clone(),
|
engines_on: false,
|
||||||
controls: ShipControls::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_controls(&mut self, input: &InputStatus) {
|
|
||||||
self.controls.thrust = input.key_thrust;
|
|
||||||
self.controls.right = input.key_right;
|
|
||||||
self.controls.left = input.key_left;
|
|
||||||
self.controls.guns = input.key_guns;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fire_guns(&mut self) -> Vec<Projectile> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for i in &mut self.guns {
|
|
||||||
if i.active_cooldown > 0.0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
i.active_cooldown = i.cooldown;
|
|
||||||
|
|
||||||
let p = self.physicsbody.pos
|
|
||||||
+ (Matrix2::from_angle(self.physicsbody.angle) * i.pos.to_vec());
|
|
||||||
|
|
||||||
out.push(Projectile {
|
|
||||||
position: p,
|
|
||||||
velocity: self.physicsbody.vel
|
|
||||||
+ (Matrix2::from_angle(self.physicsbody.angle) * Vector2 { x: 0.0, y: 400.0 }),
|
|
||||||
angle: self.physicsbody.angle,
|
|
||||||
sprite: SpriteTexture("projectile::blaster".into()),
|
|
||||||
lifetime: 5.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(&mut self, t: f32) -> ShipTickResult {
|
|
||||||
if self.controls.thrust {
|
|
||||||
self.physicsbody.thrust(50.0 * t);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.controls.right {
|
|
||||||
self.physicsbody.rot(Deg(35.0) * t);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.controls.left {
|
|
||||||
self.physicsbody.rot(Deg(-35.0) * t);
|
|
||||||
}
|
|
||||||
|
|
||||||
let p = if self.controls.guns {
|
|
||||||
self.fire_guns()
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
self.physicsbody.tick(t);
|
|
||||||
for i in &mut self.guns {
|
|
||||||
i.active_cooldown -= t;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShipTickResult { projectiles: p };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spriteable for Ship {
|
impl Spriteable for Ship {
|
||||||
fn get_sprite(&self) -> Sprite {
|
fn get_sprite(&self) -> Sprite {
|
||||||
let engines = if self.controls.thrust {
|
let engines = if self.engines_on {
|
||||||
Some(
|
Some(
|
||||||
self.engines
|
self.engines
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub struct System {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl System {
|
impl System {
|
||||||
pub fn new(ct: &content::System) -> Self {
|
pub fn new(ct: &content::system::System) -> Self {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let sz = consts::STARFIELD_SIZE as f32 / 2.0;
|
let sz = consts::STARFIELD_SIZE as f32 / 2.0;
|
||||||
let mut s = System {
|
let mut s = System {
|
||||||
|
|
|
@ -12,7 +12,7 @@ use winit::{
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let content = content::Content::load_dir(consts::CONTENT_ROOT)?;
|
let content = content::load_content_dir(consts::CONTENT_ROOT)?;
|
||||||
let game = game::Game::new(content);
|
let game = game::Game::new(content);
|
||||||
|
|
||||||
pollster::block_on(run(game))?;
|
pollster::block_on(run(game))?;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use cgmath::Matrix4;
|
||||||
|
|
||||||
// We can draw at most this many sprites on the screen.
|
// We can draw at most this many sprites on the screen.
|
||||||
// TODO: compile-time option
|
// TODO: compile-time option
|
||||||
pub const SPRITE_INSTANCE_LIMIT: u64 = 500;
|
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
|
||||||
|
|
||||||
// Must be small enough to fit in an i32
|
// Must be small enough to fit in an i32
|
||||||
pub const STARFIELD_INSTANCE_LIMIT: u64 = consts::STARFIELD_COUNT * 24;
|
pub const STARFIELD_INSTANCE_LIMIT: u64 = consts::STARFIELD_COUNT * 24;
|
||||||
|
|
Loading…
Reference in New Issue