Compare commits

...

8 Commits

Author SHA1 Message Date
Mark 7272f2a00a
Added stats & purchase buttons 2024-02-16 18:27:46 -08:00
Mark de07e763c3
Minor bugfix 2024-02-16 18:26:55 -08:00
Mark d3195983ec
Updated TODO 2024-02-16 18:26:44 -08:00
Mark 82f2f5fc85
API tweaks 2024-02-16 18:26:37 -08:00
Mark 9f19becf0c
Added outfit purchase directives 2024-02-16 18:26:21 -08:00
Mark a6bc1a021d
Minor edits 2024-02-16 18:25:59 -08:00
Mark f5c9540fb7
Added outfitspace to_hashmap 2024-02-16 18:24:28 -08:00
Mark ab17930449
Added outfit cost 2024-02-16 18:24:10 -08:00
19 changed files with 341 additions and 65 deletions

View File

@ -17,6 +17,7 @@
- fps textbox positioning - fps textbox positioning
- shield generation curve - shield generation curve
- clippy & rules - clippy & rules
- reject unknown toml
## Small jobs ## Small jobs
- Better planet icons in radar - Better planet icons in radar

View File

@ -1,6 +1,7 @@
[outfit."plasma engines"] [outfit."plasma engines"]
name = "Plasma Engines" name = "Plasma Engines"
thumbnail = "icon::engine" thumbnail = "icon::engine"
cost = 30
desc = """ desc = """
This is the smallest of the plasma propulsion systems produced by This is the smallest of the plasma propulsion systems produced by
@ -21,6 +22,7 @@ steering.power = 20
[outfit."shield generator"] [outfit."shield generator"]
thumbnail = "icon::shield" thumbnail = "icon::shield"
name = "Shield Generator" name = "Shield Generator"
cost = 10
desc = """ desc = """
This is the standard shield generator for fighters and interceptors, This is the standard shield generator for fighters and interceptors,
@ -40,6 +42,7 @@ shield.delay = 2.0
[outfit."blaster"] [outfit."blaster"]
thumbnail = "icon::blaster" thumbnail = "icon::blaster"
name = "Blaster" name = "Blaster"
cost = 20
desc = """ desc = """
Although not the most accurate or damaging of weapons, the Energy Blaster is popular because it Although not the most accurate or damaging of weapons, the Energy Blaster is popular because it

View File

@ -113,10 +113,15 @@ fn event(state, event) {
} }
fn step(state) { fn step(state) {
radialbar::set_val("shield", if state.player_ship().stat_shield_strength() == 0.0 {
state.player_ship().current_shields() radialbar::set_val("shield", 0.0);
/ state.player_ship().stat_shield_strength() } else {
); radialbar::set_val("shield",
state.player_ship().current_shields()
/ state.player_ship().stat_shield_strength()
);
}
radialbar::set_val("hull", radialbar::set_val("hull",
state.player_ship().current_hull() state.player_ship().current_hull()
/ state.player_ship().total_hull() / state.player_ship().total_hull()

View File

@ -347,6 +347,9 @@ fn init(state) {
fn event(state, event) { fn event(state, event) {
// TODO: update on ship outfit change only
update_ship_info(state);
if type_of(event) == "MouseHoverEvent" { if type_of(event) == "MouseHoverEvent" {
let element = event.element(); let element = event.element();
@ -394,6 +397,24 @@ fn event(state, event) {
return PlayerDirective::None; return PlayerDirective::None;
} }
if element == "buy_button" {
if selected_outfit != false {
let outfit = ct::get_outfit(selected_outfit);
if outfit.is_some() {
return PlayerDirective::BuyOutfit(outfit, 1);
}
}
}
if element == "sell_button" {
if selected_outfit != false {
let outfit = ct::get_outfit(selected_outfit);
if outfit.is_some() {
return PlayerDirective::SellOutfit(outfit, 1);
}
}
}
if ( if (
element.starts_with("outfit.backg.") && element.starts_with("outfit.backg.") &&
event.element().split("outfit.backg.".len())[1] != selected_outfit event.element().split("outfit.backg.".len())[1] != selected_outfit
@ -475,6 +496,41 @@ fn update_outfit_info(selected_outfit) {
let stats = ""; let stats = "";
let tlen = 20; let tlen = 20;
{
let s = "cost ";
s.pad(tlen, " ");
stats += s + outfit.cost();
stats += "\n";
}
{
let s = "needs gun port ";
s.pad(tlen, " ");
if outfit.stat_is_gun() {
stats += s + "yes";
} else {
stats += s + "no"
}
stats += "\n";
}
{
let s = outfit.required_space();
for k in s.keys() {
let v = s[k];
let s = k + " space needed ";
s.pad(tlen, " ");
if v != 0 {
stats += s + v;
stats += "\n";
}
}
}
stats += "\n";
if outfit.stat_thrust() != 0 { if outfit.stat_thrust() != 0 {
let s = "thrust "; let s = "thrust ";
s.pad(tlen, " "); s.pad(tlen, " ");
@ -527,8 +583,28 @@ fn update_ship_info(state) {
let tlen = 20; let tlen = 20;
{
let u = ship.used_space();
let t = ship.total_space();
for k in u.keys() {
let vu = u[k];
let vt = t[k];
let s = k + " space ";
s.pad(tlen, " ");
stats += s + (vt - vu) + "/" + vt;
stats += "\n";
}
}
{
let s = "gun points ";
s.pad(tlen, " ");
stats += s + ship.free_gun_points() + "/" + ship.total_gun_points();
stats += "\n\n";
}
// TODO: outfits add mass // TODO: outfits add mass
// TODO: calculate radial acceleration // TODO: calculate acceleration
{ {
let s = "shield strength "; let s = "shield strength ";
s.pad(tlen, " "); s.pad(tlen, " ");

View File

@ -24,6 +24,7 @@ pub(crate) mod syntax {
pub thumbnail: ContentIndex, pub thumbnail: ContentIndex,
pub name: String, pub name: String,
pub desc: String, pub desc: String,
pub cost: u32,
pub engine: Option<Engine>, pub engine: Option<Engine>,
pub steering: Option<Steering>, pub steering: Option<Steering>,
pub space: outfitspace::syntax::OutfitSpace, pub space: outfitspace::syntax::OutfitSpace,
@ -140,6 +141,9 @@ pub struct Outfit {
/// This outfit's thumbnail /// This outfit's thumbnail
pub thumbnail: Arc<Sprite>, pub thumbnail: Arc<Sprite>,
/// The cost of this outfit, in credits
pub cost: u32,
/// How much space this outfit requires /// How much space this outfit requires
pub space: OutfitSpace, pub space: OutfitSpace,
@ -324,6 +328,7 @@ impl crate::Build for Outfit {
let mut o = Self { let mut o = Self {
index: ContentIndex::new(&outfit_name), index: ContentIndex::new(&outfit_name),
display_name: outfit.name, display_name: outfit.name,
cost: outfit.cost,
thumbnail, thumbnail,
gun, gun,
desc: outfit.desc, desc: outfit.desc,

View File

@ -1,4 +1,7 @@
use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::{
collections::HashMap,
ops::{Add, AddAssign, Sub, SubAssign},
};
pub(crate) mod syntax { pub(crate) mod syntax {
use serde::Deserialize; use serde::Deserialize;
@ -44,6 +47,15 @@ impl OutfitSpace {
&& self.weapon >= smaller.weapon && self.weapon >= smaller.weapon
&& self.engine >= smaller.engine && self.engine >= smaller.engine
} }
/// Return a map of "space name" -> number
pub fn to_hashmap(&self) -> HashMap<String, u32> {
let mut m = HashMap::new();
m.insert("outfit".to_string(), self.outfit);
m.insert("weapon".to_string(), self.weapon);
m.insert("engine".to_string(), self.engine);
m
}
} }
impl Sub for OutfitSpace { impl Sub for OutfitSpace {

View File

@ -5,6 +5,7 @@ use galactica_system::phys::{PhysImage, PhysSim, PhysSimShipHandle, PhysStepReso
use galactica_system::PlayerDirective; use galactica_system::PlayerDirective;
use galactica_util::timing::Timing; use galactica_util::timing::Timing;
use nalgebra::Point2; use nalgebra::Point2;
use std::iter;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
@ -51,16 +52,6 @@ impl<'a> Game {
.get(&ContentIndex::new("shield generator")) .get(&ContentIndex::new("shield generator"))
.unwrap() .unwrap()
.clone(), .clone(),
self.ct
.outfits
.get(&ContentIndex::new("blaster"))
.unwrap()
.clone(),
self.ct
.outfits
.get(&ContentIndex::new("blaster"))
.unwrap()
.clone(),
]); ]);
return player; return player;
@ -131,9 +122,43 @@ impl<'a> Game {
} }
} }
pub fn apply_directive(&mut self, directive: PlayerDirective, player: &PlayerAgent) { pub fn apply_directive(&mut self, directive: PlayerDirective, player: &mut PlayerAgent) {
match directive { match directive {
_ => self.phys_sim.apply_directive(directive, player), PlayerDirective::None => {}
PlayerDirective::BuyOutfit { outfit, count } => {
let cost = outfit.cost * count;
if player.credits >= cost {
player.credits -= cost;
let ship = self
.phys_sim
.get_ship_mut(&PhysSimShipHandle(player.ship.unwrap()))
.unwrap();
ship.add_outfits(iter::repeat(outfit).take(count as usize))
}
}
PlayerDirective::SellOutfit { outfit, count } => {
let ship = self
.phys_sim
.get_ship_mut(&PhysSimShipHandle(player.ship.unwrap()))
.unwrap();
if ship.get_data().get_outfits().has_outfit(&outfit, count) {
for _ in 0..count {
ship.remove_outfit(&outfit);
}
}
player.credits += outfit.cost * count;
}
PlayerDirective::Engine(_)
| PlayerDirective::Guns(_)
| PlayerDirective::Land
| PlayerDirective::TurnLeft(_)
| PlayerDirective::TurnRight(_)
| PlayerDirective::UnLand => self.phys_sim.apply_directive(directive, player),
} }
} }

View File

@ -15,6 +15,7 @@ use log4rs::{
Config, Config,
}; };
use std::{ use std::{
cell::RefCell,
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
@ -127,7 +128,7 @@ fn try_main() -> Result<()> {
current_time: game.get_current_time(), current_time: game.get_current_time(),
ct: content.clone(), ct: content.clone(),
phys_img: PhysImage::new(), phys_img: PhysImage::new(),
player: PlayerAgent::new(&content, p.0), player: RefCell::new(PlayerAgent::new(&content, p.0)),
// TODO: this is a hack for testing. // TODO: this is a hack for testing.
current_system: content.systems.values().next().unwrap().clone(), current_system: content.systems.values().next().unwrap().clone(),
timing: game.get_timing().clone(), timing: game.get_timing().clone(),
@ -184,13 +185,13 @@ fn try_main() -> Result<()> {
}, },
) )
.unwrap(); .unwrap();
game.apply_directive(directive, &input.player); game.apply_directive(directive, &mut input.player.borrow_mut());
} }
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
let directive = gpu let directive = gpu
.process_input(&input, InputEvent::MouseMove(position.cast())) .process_input(&input, InputEvent::MouseMove(position.cast()))
.unwrap(); .unwrap();
game.apply_directive(directive, &input.player); game.apply_directive(directive, &mut input.player.borrow_mut());
} }
WindowEvent::MouseInput { state, button, .. } => { WindowEvent::MouseInput { state, button, .. } => {
let down = state == &ElementState::Pressed; let down = state == &ElementState::Pressed;
@ -201,7 +202,7 @@ fn try_main() -> Result<()> {
}; };
if let Some(event) = event { if let Some(event) = event {
let directive = gpu.process_input(&input, event).unwrap(); let directive = gpu.process_input(&input, event).unwrap();
game.apply_directive(directive, &input.player); game.apply_directive(directive, &mut input.player.borrow_mut());
} }
} }
WindowEvent::MouseWheel { delta, .. } => { WindowEvent::MouseWheel { delta, .. } => {
@ -214,7 +215,7 @@ fn try_main() -> Result<()> {
}), }),
) )
.unwrap(); .unwrap();
game.apply_directive(directive, &input.player); game.apply_directive(directive, &mut input.player.borrow_mut());
} }
WindowEvent::Resized(_) => { WindowEvent::Resized(_) => {
gpu.resize(&content); gpu.resize(&content);

View File

@ -29,13 +29,17 @@ pub struct PlayerAgent {
/// Which ship this player is controlling /// Which ship this player is controlling
pub ship: Option<ColliderHandle>, pub ship: Option<ColliderHandle>,
/// What the player has selected /// What this player has selected
pub selection: PlayerSelection, pub selection: PlayerSelection,
/// The amount of money this player has
pub credits: u32,
} }
impl PlayerAgent { impl PlayerAgent {
pub fn new(ct: &Content, ship: ColliderHandle) -> Self { pub fn new(ct: &Content, ship: ColliderHandle) -> Self {
Self { Self {
credits: 1000,
ship: Some(ship), ship: Some(ship),
selection: PlayerSelection::OrbitingBody( selection: PlayerSelection::OrbitingBody(
ct.systems ct.systems

View File

@ -287,7 +287,7 @@ impl GPUState {
/// Main render function. Draws sprites on a window. /// Main render function. Draws sprites on a window.
pub fn render(&mut self, input: &Arc<RenderInput>) -> Result<(), wgpu::SurfaceError> { pub fn render(&mut self, input: &Arc<RenderInput>) -> Result<(), wgpu::SurfaceError> {
if let Some(ship) = input.player.ship { if let Some(ship) = input.player.borrow().ship {
let o = input.phys_img.get_ship(&PhysSimShipHandle(ship)); let o = input.phys_img.get_ship(&PhysSimShipHandle(ship));
if let Some(o) = o { if let Some(o) = o {
match o.ship.get_data().get_state() { match o.ship.get_data().get_state() {

View File

@ -1,4 +1,4 @@
use std::sync::Arc; use std::{cell::RefCell, sync::Arc};
use galactica_content::{Content, System}; use galactica_content::{Content, System};
use galactica_playeragent::PlayerAgent; use galactica_playeragent::PlayerAgent;
@ -9,7 +9,7 @@ use galactica_util::timing::Timing;
#[derive(Debug)] #[derive(Debug)]
pub struct RenderInput { pub struct RenderInput {
/// Player ship data /// Player ship data
pub player: PlayerAgent, pub player: RefCell<PlayerAgent>,
/// The system we're currently in /// The system we're currently in
pub current_system: Arc<System>, pub current_system: Arc<System>,

View File

@ -4,6 +4,7 @@ use rhai::{plugin::*, Dynamic, Module};
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub mod player_directive_module { pub mod player_directive_module {
use crate::ui::api::OutfitState;
use galactica_system::PlayerDirective; use galactica_system::PlayerDirective;
pub const None: PlayerDirective = PlayerDirective::None; pub const None: PlayerDirective = PlayerDirective::None;
@ -25,4 +26,24 @@ pub mod player_directive_module {
pub fn Guns(state: bool) -> PlayerDirective { pub fn Guns(state: bool) -> PlayerDirective {
PlayerDirective::Guns(state) PlayerDirective::Guns(state)
} }
pub fn BuyOutfit(outfit: OutfitState, count: i64) -> PlayerDirective {
if count <= 0 {
return PlayerDirective::None;
}
PlayerDirective::BuyOutfit {
outfit: outfit.get_outfit().unwrap(),
count: count as u32,
}
}
pub fn SellOutfit(outfit: OutfitState, count: i64) -> PlayerDirective {
if count <= 0 {
return PlayerDirective::None;
}
PlayerDirective::SellOutfit {
outfit: outfit.get_outfit().unwrap(),
count: count as u32,
}
}
} }

View File

@ -1,6 +1,8 @@
use galactica_content::{Content, Outfit}; use galactica_content::{Content, Outfit, OutfitSpace};
use log::error; use log::error;
use rhai::{CustomType, FnNamespace, FuncRegistration, ImmutableString, Module, TypeBuilder}; use rhai::{
CustomType, Dynamic, FnNamespace, FuncRegistration, ImmutableString, Map, Module, TypeBuilder,
};
use std::sync::Arc; use std::sync::Arc;
pub fn build_ct_module(ct_src: Arc<Content>) -> Module { pub fn build_ct_module(ct_src: Arc<Content>) -> Module {
@ -40,6 +42,10 @@ impl OutfitState {
pub fn new_none() -> Self { pub fn new_none() -> Self {
Self { outfit: None } Self { outfit: None }
} }
pub fn get_outfit(&self) -> Option<Arc<Outfit>> {
self.outfit.as_ref().cloned()
}
} }
impl CustomType for OutfitState { impl CustomType for OutfitState {
@ -71,6 +77,20 @@ impl CustomType for OutfitState {
.map(|x| x.thumbnail.index.to_string()) .map(|x| x.thumbnail.index.to_string())
.unwrap_or("".to_string()) .unwrap_or("".to_string())
}) })
.with_fn("cost", |s: &mut Self| {
s.outfit.as_ref().map(|x| x.cost).unwrap_or(0)
})
.with_fn("required_space", |s: &mut Self| {
Map::from_iter(
s.outfit
.as_ref()
.map(|x| x.space)
.unwrap_or(OutfitSpace::new())
.to_hashmap()
.iter()
.map(|(k, v)| (ImmutableString::from(k).into(), Dynamic::from(*v as i64))),
)
})
// //
// Stat getters // Stat getters
// //
@ -104,6 +124,9 @@ impl CustomType for OutfitState {
.map(|x| x.stats.shield_delay) .map(|x| x.stats.shield_delay)
.unwrap_or(0.0) .unwrap_or(0.0)
}) })
.with_fn("stat_is_gun", |s: &mut Self| {
s.outfit.as_ref().map(|x| x.gun.is_some()).unwrap_or(false)
})
.with_fn("stat_shield_dps", |s: &mut Self| { .with_fn("stat_shield_dps", |s: &mut Self| {
s.outfit s.outfit
.as_ref() .as_ref()

View File

@ -7,7 +7,7 @@ use galactica_util::to_degrees;
use log::error; use log::error;
use nalgebra::Vector2; use nalgebra::Vector2;
use rapier2d::dynamics::RigidBody; use rapier2d::dynamics::RigidBody;
use rhai::{Array, CustomType, Dynamic, ImmutableString, TypeBuilder}; use rhai::{Array, CustomType, Dynamic, ImmutableString, Map, TypeBuilder};
use std::sync::Arc; use std::sync::Arc;
use super::{functions::OutfitState, Color, UiVector}; use super::{functions::OutfitState, Color, UiVector};
@ -94,14 +94,6 @@ impl CustomType for ShipState {
s.get_content().thumbnail.index.clone().into() s.get_content().thumbnail.index.clone().into()
}) })
.with_fn("landed_on", |s: &mut Self| s.landed_on()) .with_fn("landed_on", |s: &mut Self| s.landed_on())
.with_fn("current_shields", |s: &mut Self| {
s.get_ship().get_data().get_shields()
})
.with_fn("total_hull", |s: &mut Self| s.get_content().hull)
.with_fn("empty_mass", |s: &mut Self| s.get_content().mass)
.with_fn("current_hull", |s: &mut Self| {
s.get_ship().get_data().get_hull()
})
.with_fn("get_size", |s: &mut Self| s.get_content().size) .with_fn("get_size", |s: &mut Self| s.get_content().size)
.with_fn("phys_uid", |s: &mut Self| format!("{}", s.get_ship().uid)) .with_fn("phys_uid", |s: &mut Self| format!("{}", s.get_ship().uid))
.with_fn("get_pos", |s: &mut Self| { .with_fn("get_pos", |s: &mut Self| {
@ -114,7 +106,46 @@ impl CustomType for ShipState {
Color::new(c[0], c[1], c[2], 1.0) Color::new(c[0], c[1], c[2], 1.0)
}) })
// //
// Stat getters // Ship stat getters
//
.with_fn("used_space", |s: &mut Self| {
Map::from_iter(
s.get_ship()
.get_data()
.get_outfits()
.get_used_space()
.to_hashmap()
.iter()
.map(|(k, v)| (ImmutableString::from(k).into(), Dynamic::from(*v as i64))),
)
})
.with_fn("total_space", |s: &mut Self| {
Map::from_iter(
s.get_ship()
.get_data()
.get_outfits()
.get_total_space()
.to_hashmap()
.iter()
.map(|(k, v)| (ImmutableString::from(k).into(), Dynamic::from(*v as i64))),
)
})
.with_fn("total_gun_points", |s: &mut Self| {
s.get_ship().get_data().get_outfits().total_gun_points() as i64
})
.with_fn("free_gun_points", |s: &mut Self| {
s.get_ship().get_data().get_outfits().free_gun_points() as i64
})
.with_fn("current_shields", |s: &mut Self| {
s.get_ship().get_data().get_shields()
})
.with_fn("total_hull", |s: &mut Self| s.get_content().hull)
.with_fn("empty_mass", |s: &mut Self| s.get_content().mass)
.with_fn("current_hull", |s: &mut Self| {
s.get_ship().get_data().get_hull()
})
//
// Outfit stat getters
// //
.with_fn("stat_thrust", |s: &mut Self| { .with_fn("stat_thrust", |s: &mut Self| {
s.get_ship() s.get_ship()
@ -264,7 +295,12 @@ impl State {
pub fn player_ship(&mut self) -> ShipState { pub fn player_ship(&mut self) -> ShipState {
ShipState { ShipState {
input: self.input.clone(), input: self.input.clone(),
ship: self.input.player.ship.map(|x| PhysSimShipHandle(x)), ship: self
.input
.player
.borrow()
.ship
.map(|x| PhysSimShipHandle(x)),
} }
} }

View File

@ -301,7 +301,7 @@ impl UiScriptExecutor {
// Send player state change events // Send player state change events
if { if {
let player = input.player.ship; let player = input.player.borrow().ship;
if let Some(player) = player { if let Some(player) = player {
let ship = input.phys_img.get_ship(&PhysSimShipHandle(player)).unwrap(); let ship = input.phys_img.get_ship(&PhysSimShipHandle(player)).unwrap();
if self.last_player_state == 0 if self.last_player_state == 0

View File

@ -104,6 +104,7 @@ impl OutfitSet {
if outfit.is_none() { if outfit.is_none() {
*outfit = Some(o.clone()); *outfit = Some(o.clone());
added = true; added = true;
break;
} }
} }
if !added { if !added {
@ -112,14 +113,15 @@ impl OutfitSet {
} }
self.used_space += o.space; self.used_space += o.space;
self.stats.add(&o.stats); self.stats.add(&o.stats);
self.shield_generators.push(ShieldGenerator { if o.stats.shield_generation != 0.0 {
outfit: o.clone(), self.shield_generators.push(ShieldGenerator {
delay: o.stats.shield_delay, outfit: o.clone(),
generation: o.stats.shield_generation, delay: o.stats.shield_delay,
}); generation: o.stats.shield_generation,
});
}
if self.outfits.contains_key(&o.index) { if self.outfits.contains_key(&o.index) {
self.outfits.get_mut(&o.index).unwrap().1 += 1; self.outfits.get_mut(&o.index).unwrap().1 += 1;
@ -133,26 +135,32 @@ impl OutfitSet {
pub(super) fn remove(&mut self, o: &Arc<Outfit>) -> OutfitRemoveResult { pub(super) fn remove(&mut self, o: &Arc<Outfit>) -> OutfitRemoveResult {
if !self.outfits.contains_key(&o.index) { if !self.outfits.contains_key(&o.index) {
return OutfitRemoveResult::NotExist; return OutfitRemoveResult::NotExist;
}
let n = self.outfits.get_mut(&o.index).unwrap();
if n.1 == 1u32 {
self.outfits.remove(&o.index);
} else { } else {
let n = self.outfits.get_mut(&o.index).unwrap(); self.outfits.get_mut(&o.index).unwrap().1 -= 1;
if n.1 == 1u32 { }
self.outfits.remove(&o.index);
} else { if o.gun.is_some() {
self.outfits.get_mut(&o.index).unwrap().1 -= 1; let (_, x) = self
} .gun_points
.iter_mut()
.find(|(_, x)| x.is_some() && x.as_ref().unwrap().index == o.index)
.unwrap();
*x = None;
} }
self.used_space -= o.space; self.used_space -= o.space;
self.stats.subtract(&o.stats); self.stats.subtract(&o.stats);
{ let index = self
// This index will exist, since we checked the hashmap .shield_generators
let index = self .iter()
.shield_generators .position(|g| g.outfit.index == o.index);
.iter() if let Some(index) = index {
.position(|g| g.outfit.index == o.index)
.unwrap();
self.shield_generators.remove(index); self.shield_generators.remove(index);
} }
@ -198,6 +206,29 @@ impl OutfitSet {
&self.used_space &self.used_space
} }
/// Number of available (used & free) gun points
pub fn total_gun_points(&self) -> usize {
self.gun_points.len()
}
/// Number of free gun points
pub fn free_gun_points(&self) -> usize {
self.iter_gun_points().filter(|(_, o)| o.is_none()).count()
}
/// Does this set contain `count` of `outfit`?
pub fn has_outfit(&self, outfit: &Arc<Outfit>, mut count: u32) -> bool {
for i in self.iter_outfits() {
if count <= 0 {
return true;
}
if i.0.index == outfit.index {
count -= 1;
}
}
return count <= 0;
}
/// Get the combined stats of all outfits in this set. /// Get the combined stats of all outfits in this set.
/// There are two things to note here: /// There are two things to note here:
/// First, shield_delay is always zero. That is handled /// First, shield_delay is always zero. That is handled

View File

@ -14,7 +14,7 @@ use rapier2d::{
use super::{autopilot, collapse::ShipCollapseSequence, controller::ShipController, ShipControls}; use super::{autopilot, collapse::ShipCollapseSequence, controller::ShipController, ShipControls};
use crate::{ use crate::{
data::{ShipAutoPilot, ShipData, ShipPersonality, ShipState}, data::{OutfitRemoveResult, ShipAutoPilot, ShipData, ShipPersonality, ShipState},
phys::{ phys::{
get_phys_id, get_phys_id,
objects::{PhysEffect, PhysProjectile}, objects::{PhysEffect, PhysProjectile},
@ -505,8 +505,8 @@ impl PhysShip {
} }
/// Add one outfit to this ship /// Add one outfit to this ship
pub fn add_outfit(&mut self, o: Arc<Outfit>) { pub fn add_outfit(&mut self, o: &Arc<Outfit>) {
self.data.add_outfit(&o); self.data.add_outfit(o);
self.update_flares(); self.update_flares();
} }
@ -517,6 +517,13 @@ impl PhysShip {
} }
self.update_flares(); self.update_flares();
} }
/// Remove one outfit from this ship
pub fn remove_outfit(&mut self, o: &Arc<Outfit>) -> OutfitRemoveResult {
let r = self.data.remove_outfit(o);
self.update_flares();
return r;
}
} }
/// Public immutable /// Public immutable

View File

@ -192,7 +192,7 @@ impl PhysSim {
} }
/// Update a player ship's controls /// Update a player ship's controls
pub fn apply_directive(&mut self, directive: PlayerDirective, player: &PlayerAgent) { pub fn apply_directive(&mut self, directive: PlayerDirective, player: &mut PlayerAgent) {
if player.ship.is_none() { if player.ship.is_none() {
return; return;
} }

View File

@ -1,3 +1,7 @@
use std::sync::Arc;
use galactica_content::Outfit;
/// An action the player wants to take in the game. /// An action the player wants to take in the game.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum PlayerDirective { pub enum PlayerDirective {
@ -21,4 +25,26 @@ pub enum PlayerDirective {
/// Take off from the planet we're landed on /// Take off from the planet we're landed on
UnLand, UnLand,
/// Buy an outfit.
/// This directive is ignored if the player does not have enough
/// credits to buy the given number of outfits.
BuyOutfit {
/// The outfit to buy
outfit: Arc<Outfit>,
/// How many of this outfit to buy
count: u32,
},
/// Sell an outfit.
/// This directive is ignored if the player does not have enough
/// outfits to sell.
SellOutfit {
/// The outfit to sell
outfit: Arc<Outfit>,
/// How many of this outfit to sell
count: u32,
},
} }