diff --git a/content/outfits.toml b/content/outfits.toml index 8cbda0b..bcbc42c 100644 --- a/content/outfits.toml +++ b/content/outfits.toml @@ -5,3 +5,12 @@ space.engine = 20 engine.thrust = 100 engine.flare_sprite = "flare::ion" steering.power = 20 + + +[outfit."shield generator"] + +space.outfit = 5 + +shield.generation = 10 +shield.strength = 500 +shield.delay = 2.0 diff --git a/crates/content/src/part/outfit.rs b/crates/content/src/part/outfit.rs index e3ef793..2461e3f 100644 --- a/crates/content/src/part/outfit.rs +++ b/crates/content/src/part/outfit.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use anyhow::{bail, Result}; -use crate::{handle::SpriteHandle, Content, ContentBuildContext, OutfitSpace}; +use crate::{handle::SpriteHandle, Content, ContentBuildContext, OutfitHandle, OutfitSpace}; pub(crate) mod syntax { use crate::part::outfitspace; @@ -15,6 +15,15 @@ pub(crate) mod syntax { pub engine: Option, pub steering: Option, pub space: outfitspace::syntax::OutfitSpace, + pub shield: Option, + } + + #[derive(Debug, Deserialize)] + pub struct Shield { + pub strength: Option, + pub generation: Option, + pub delay: Option, + // more stats: permiability, shield armor, ramp, etc } #[derive(Debug, Deserialize)] @@ -32,6 +41,12 @@ pub(crate) mod syntax { /// Represents an outfit that may be attached to a ship. #[derive(Debug, Clone)] pub struct Outfit { + /// How much space this outfit requires + pub space: OutfitSpace, + + /// This outfit's handle + pub handle: OutfitHandle, + /// The name of this outfit pub name: String, @@ -46,8 +61,14 @@ pub struct Outfit { /// engine points. pub engine_flare_sprite: Option, - /// How much space this outfit requires - pub space: OutfitSpace, + /// Shield hit points + pub shield_strength: f32, + + /// Shield regeneration rate, per second + pub shield_generation: f32, + + /// Wait this many seconds after taking damage before regenerating shields + pub shield_delay: f32, } impl crate::Build for Outfit { @@ -59,12 +80,20 @@ impl crate::Build for Outfit { content: &mut Content, ) -> Result<()> { for (outfit_name, outfit) in outfits { + let handle = OutfitHandle { + index: content.outfits.len(), + }; + let mut o = Self { + handle, name: outfit_name.clone(), engine_thrust: 0.0, steer_power: 0.0, engine_flare_sprite: None, space: OutfitSpace::from(outfit.space), + shield_delay: 0.0, + shield_generation: 0.0, + shield_strength: 0.0, }; // Engine stats @@ -86,6 +115,13 @@ impl crate::Build for Outfit { o.steer_power = steer.power; } + // Shield stats + if let Some(shield) = outfit.shield { + o.shield_delay = shield.delay.unwrap_or(0.0); + o.shield_generation = shield.generation.unwrap_or(0.0); + o.shield_strength = shield.strength.unwrap_or(0.0); + } + content.outfits.push(o); } diff --git a/crates/galactica/src/game.rs b/crates/galactica/src/game.rs index 48e4c22..3634489 100644 --- a/crates/galactica/src/game.rs +++ b/crates/galactica/src/game.rs @@ -38,6 +38,7 @@ impl Game { let mut o1 = object::OutfitSet::new(ss); o1.add(&ct, content::OutfitHandle { index: 0 }); + o1.add(&ct, content::OutfitHandle { index: 1 }); o1.add_gun(&ct, content::GunHandle { index: 0 }); o1.add_gun(&ct, content::GunHandle { index: 0 }); o1.add_gun(&ct, content::GunHandle { index: 0 }); @@ -64,7 +65,7 @@ impl Game { let mut o1 = object::OutfitSet::new(ss); o1.add(&ct, content::OutfitHandle { index: 0 }); - //o1.add_gun(&ct, content::GunHandle { index: 0 }); + o1.add_gun(&ct, content::GunHandle { index: 0 }); let s = object::Ship::new( &ct, diff --git a/crates/gameobject/src/outfits.rs b/crates/gameobject/src/outfits.rs index 10b6dd4..b36f1b6 100644 --- a/crates/gameobject/src/outfits.rs +++ b/crates/gameobject/src/outfits.rs @@ -1,4 +1,4 @@ -use content::{EnginePoint, SpriteHandle}; +use content::{EnginePoint, OutfitHandle, SpriteHandle}; use galactica_content as content; /// Represents a gun attached to a specific ship at a certain gunpoint. @@ -33,6 +33,10 @@ pub struct OutfitStatSum { pub engine_thrust: f32, pub steer_power: f32, pub engine_flare_sprites: Vec, + pub shield_strength: f32, + + // Delay, generation + pub shield_generators: Vec<(OutfitHandle, f32, f32)>, } impl OutfitStatSum { @@ -41,6 +45,8 @@ impl OutfitStatSum { engine_thrust: 0.0, steer_power: 0.0, engine_flare_sprites: Vec::new(), + shield_strength: 0.0, + shield_generators: Vec::new(), } } @@ -50,6 +56,9 @@ impl OutfitStatSum { self.engine_flare_sprites.push(t); }; self.steer_power += o.steer_power; + self.shield_strength += o.shield_strength; + self.shield_generators + .push((o.handle, o.shield_delay, o.shield_generation)); } pub fn remove(&mut self, o: &content::Outfit) { @@ -63,6 +72,16 @@ impl OutfitStatSum { ); } self.steer_power -= o.steer_power; + self.shield_strength -= o.shield_strength; + { + // Assume such an index exists + let index = self + .shield_generators + .iter() + .position(|(h, _, _)| *h == o.handle) + .unwrap(); + self.shield_generators.remove(index); + } } } diff --git a/crates/gameobject/src/ship.rs b/crates/gameobject/src/ship.rs index 818663d..4563ea2 100644 --- a/crates/gameobject/src/ship.rs +++ b/crates/gameobject/src/ship.rs @@ -1,4 +1,5 @@ use rand::Rng; +use std::time::Instant; use crate::{OutfitSet, Projectile}; use galactica_content as content; @@ -8,10 +9,12 @@ pub struct Ship { pub handle: content::ShipHandle, pub faction: content::FactionHandle, pub outfits: OutfitSet, + pub last_hit: Instant, // TODO: unified ship stats struct, like space // TODO: unified outfit stats struct: check pub hull: f32, + pub shields: f32, } impl Ship { @@ -22,11 +25,14 @@ impl Ship { outfits: OutfitSet, ) -> Self { let s = ct.get_ship(handle); + let shields = outfits.stat_sum().shield_strength; Ship { handle: handle, faction, outfits, hull: s.hull, + shields, + last_hit: Instant::now(), } } @@ -41,10 +47,19 @@ impl Ship { let r = f.relationships.get(&p.faction).unwrap(); match r { content::Relationship::Hostile => { + let mut d = p.content.damage; if self.is_dead() { return true; } - self.hull -= p.content.damage; + if self.shields >= d { + self.shields -= d + } else { + d -= self.shields; + self.shields = 0.0; + self.hull -= d; + } + self.last_hit = Instant::now(); + return true; } _ => return false, @@ -79,4 +94,19 @@ impl Ship { }) .collect() } + + pub fn step(&mut self, t: f32) { + let time_since = self.last_hit.elapsed().as_secs_f32(); + if self.shields != self.outfits.stat_sum().shield_strength { + for (_, d, g) in &self.outfits.stat_sum().shield_generators { + if time_since >= *d { + self.shields += g * t; + if self.shields > self.outfits.stat_sum().shield_strength { + self.shields = self.outfits.stat_sum().shield_strength; + break; + } + } + } + } + } } diff --git a/crates/render/shaders/radialbar.wgsl b/crates/render/shaders/radialbar.wgsl index ac832a5..e1bd90f 100644 --- a/crates/render/shaders/radialbar.wgsl +++ b/crates/render/shaders/radialbar.wgsl @@ -65,12 +65,12 @@ fn fragment_main(in: VertexOutput) -> @location(0) vec4 { global_data.window_scale.x ) - in.center; - let bar_width = in.stroke; // Width of filled bar - let bar_radius = in.diameter / 2.0 - bar_width / 2.0; // Middle radius of the bar - let angle = in.angle - floor(in.angle / 6.283) * 6.28318; // Sanely handle large angles (fmod(angle, 2pi)) - let zero_vector = vec2(0.0, 1.0); // Draw bar clockwise from this vector - let frag_radius = distance(vec2(0.0, 0.0), p); // Radius of this fragment - let feather = 2.0; // Size of feather, in logical pixels + let bar_width = in.stroke; // Width of filled bar + let bar_radius = in.diameter / 2.0 - bar_width / 2.0; // Middle radius of the bar + let angle = clamp(0.001, 6.28318 + 0.001, in.angle); // Sanely handle extreme angles + let zero_vector = vec2(0.0, 1.0); // Draw bar clockwise from this vector + let frag_radius = distance(vec2(0.0, 0.0), p); // Radius of this fragment + let feather = 2.0; // Size of feather, in logical pixels // Clockwise angle between zero_vector and fragment location let frag_angle = atan2( diff --git a/crates/render/src/gpustate/hud.rs b/crates/render/src/gpustate/hud.rs index 1bee4c2..567736b 100644 --- a/crates/render/src/gpustate/hud.rs +++ b/crates/render/src/gpustate/hud.rs @@ -1,5 +1,7 @@ //! GPUState routines for drawing HUD elements +use std::f32::consts::TAU; + use cgmath::{Deg, InnerSpace, Point2, Rad, Vector2}; use galactica_world::util; @@ -264,6 +266,12 @@ impl GPUState { panic!("UI limit exceeded!") } + let (s, _) = state.world.get_ship_body(*state.player).unwrap(); + let max_shields = s.ship.outfits.stat_sum().shield_strength; + let current_shields = s.ship.shields; + let current_hull = s.ship.hull; + let max_hull = state.content.get_ship(s.ship.handle).hull; + self.queue.write_buffer( &self.vertex_buffers.ui.instances, UiInstance::SIZE * self.vertex_buffers.ui_counter, @@ -286,7 +294,6 @@ impl GPUState { panic!("Radialbar limit exceeded!") } - // TODO: counters for each buffer, remove arrays self.queue.write_buffer( &self.vertex_buffers.radialbar.instances, RadialBarInstance::SIZE * self.vertex_buffers.radialbar_counter, @@ -296,7 +303,7 @@ impl GPUState { diameter: 182.0, stroke: 5.0, color: [0.3, 0.6, 0.8, 1.0], - angle: -state.current_time / 2.0, + angle: (current_shields / max_shields) * TAU, }]), ); self.vertex_buffers.radialbar_counter += 1; @@ -310,7 +317,7 @@ impl GPUState { diameter: 166.0, stroke: 5.0, color: [0.8, 0.7, 0.5, 1.0], - angle: state.current_time / 5.0, + angle: (current_hull / max_hull) * TAU, }]), ); self.vertex_buffers.radialbar_counter += 1; diff --git a/crates/render/src/gpustate/world.rs b/crates/render/src/gpustate/world.rs index f0e8039..acc4bb8 100644 --- a/crates/render/src/gpustate/world.rs +++ b/crates/render/src/gpustate/world.rs @@ -85,7 +85,7 @@ impl GPUState { self.vertex_buffers.object_counter += 1; // Draw engine flares if necessary - if s.controls.thrust { + if s.controls.thrust && !s.ship.is_dead() { for f in s.ship.outfits.iter_enginepoints() { let flare = match s.ship.outfits.get_flare_sprite() { None => continue, diff --git a/crates/world/src/objects/ship.rs b/crates/world/src/objects/ship.rs index a5386a3..12af50e 100644 --- a/crates/world/src/objects/ship.rs +++ b/crates/world/src/objects/ship.rs @@ -216,6 +216,8 @@ impl ShipWorldObject { .step(&self.ship, ct, particles, rigid_body, collider, t); } + self.ship.step(t); + let ship_content = ct.get_ship(self.ship.handle); let ship_pos = util::rigidbody_position(&rigid_body); let ship_rot = util::rigidbody_rotation(rigid_body);