M crates/unified/src/client/crafting/ui.rs => crates/unified/src/client/crafting/ui.rs +41 -66
@@ 37,7 37,23 @@ struct RecipeCollection {
}
// TODO: use recipe inputs for client-side validation?
#[derive(Component, Clone)]
-struct RecipeElement(Entity, String, HashMap<String, u32>); // stores corresponding part and recipe's part name and inputs
+struct RecipeElement {
+ part_entity: Entity,
+ module_name: String,
+ inputs: HashMap<String, u32>,
+}
+
+fn click_released(current: Interaction, previous: Interaction) -> bool {
+ current == Interaction::Hovered && previous == Interaction::Pressed
+}
+
+fn button_color(interaction: Interaction, pressed: Color, hovered: Color, none: Color) -> BackgroundColor {
+ match interaction {
+ Interaction::Pressed => pressed.into(),
+ Interaction::Hovered => hovered.into(),
+ Interaction::None => none.into(),
+ }
+}
fn load_recipes(asset_server: Res<AssetServer>, mut recipe_collection: ResMut<RecipeCollection>) {
recipe_collection.handle = Some(asset_server.load("config/recipes.rc.toml"));
@@ 239,7 255,7 @@ fn create_recipe_list(
width: Val::Auto,
..Default::default()
},
- RecipeElement(parent_entity, module_name.clone(), recipe.inputs.clone()),
+ RecipeElement { part_entity: parent_entity, module_name: module_name.clone(), inputs: recipe.inputs.clone() },
BackgroundColor(colors::MANTLE),
PreviousInteraction(Interaction::None),
Button),
@@ 269,24 285,13 @@ fn recipe_buttons(
mut crafting_message_writer: MessageWriter<CraftPartRequest>,
) {
for (interaction, mut previous_interaction, mut color, recipe) in &mut interaction_query {
- match *interaction {
- Interaction::Pressed => {
- *color = colors::SURFACE_1.into();
- }
- Interaction::Hovered => {
- *color = colors::SURFACE_0.into();
- if previous_interaction.0 == Interaction::Pressed {
- // released
- crafting_message_writer.write(CraftPartRequest {
- crafting_part: recipe.0,
- crafted_part: recipe.1.clone(),
- inputs: recipe.2.clone(),
- });
- }
- }
- Interaction::None => {
- *color = colors::MANTLE.into();
- }
+ *color = button_color(*interaction, colors::SURFACE_1, colors::SURFACE_0, colors::MANTLE);
+ if click_released(*interaction, previous_interaction.0) {
+ crafting_message_writer.write(CraftPartRequest {
+ crafting_part: recipe.part_entity,
+ crafted_part: recipe.module_name.clone(),
+ inputs: recipe.inputs.clone(),
+ });
}
previous_interaction.0 = *interaction;
}
@@ 309,35 314,18 @@ fn drill_button(
drills: Query<&Drill>,
) {
for (interaction, mut previous_interaction, mut color, drill_button, _button, children) in &mut interaction_query {
- match *interaction {
- Interaction::Pressed => {
- *color = colors::SURFACE_1.into();
- }
- Interaction::Hovered => {
- *color = colors::SURFACE_0.into();
- if previous_interaction.0 == Interaction::Pressed {
- // released
- let mut text = text_query.get_mut(children[0]).unwrap();
- let Ok(drill) = drills.get(drill_button.0) else {
- error!("A former drill is now not a drill, causing a problem in the drill button");
- previous_interaction.0 = *interaction;
- return
- };
- // don't allow drill toggling while not on a planet
- if drill.on_planet.is_none() { return }
- // the text is flipped because drill.drilling is an old value,
- // which was now toggled
- if drill.drilling {
- **text = "Start Drill".to_string();
- } else {
- **text = "Stop Drill".to_string();
- }
- toggle_drill_writer.write(ToggleDrillEvent { drill_entity: drill_button.0 });
- }
- }
- Interaction::None => {
- *color = colors::CRUST.into();
- }
+ *color = button_color(*interaction, colors::SURFACE_1, colors::SURFACE_0, colors::CRUST);
+ if click_released(*interaction, previous_interaction.0) {
+ let mut text = text_query.get_mut(children[0]).unwrap();
+ let Ok(drill) = drills.get(drill_button.0) else {
+ error!("A former drill is now not a drill, causing a problem in the drill button");
+ previous_interaction.0 = *interaction;
+ return
+ };
+ if drill.on_planet.is_none() { return }
+ // text is flipped because drill.drilling is the pre-toggle value
+ **text = if drill.drilling { "Start Drill" } else { "Stop Drill" }.to_string();
+ toggle_drill_writer.write(ToggleDrillEvent { drill_entity: drill_button.0 });
}
previous_interaction.0 = *interaction;
}
@@ 396,24 384,11 @@ fn close_button(
(&Interaction, &mut PreviousInteraction, &mut BackgroundColor, &CloseButton, &mut Button),
Changed<Interaction>,
>,
- _mouse: Res<ButtonInput<MouseButton>>,
) {
- for (interaction, mut previous_interaction, mut color,
- close_button, _button) in &mut interaction_query
- {
- match *interaction {
- Interaction::Pressed => {
- *color = colors::MAROON.into();
- }
- Interaction::Hovered => {
- *color = colors::PINK.into();
- if previous_interaction.0 == Interaction::Pressed {
- commands.entity(close_button.0).despawn();
- }
- }
- Interaction::None => {
- *color = colors::RED.into();
- }
+ for (interaction, mut previous_interaction, mut color, close_button, _button) in &mut interaction_query {
+ *color = button_color(*interaction, colors::MAROON, colors::PINK, colors::RED);
+ if click_released(*interaction, previous_interaction.0) {
+ commands.entity(close_button.0).despawn();
}
previous_interaction.0 = *interaction;
}
M crates/unified/src/client/parts.rs => crates/unified/src/client/parts.rs +4 -4
@@ 33,14 33,14 @@ fn temp_to_color(temperature: &Temperature) -> Color {
fn build_part_sprite(part: &Part, temperature: &Temperature, is_connected: bool, asset_server: &AssetServer) -> Sprite {
let path = if is_connected {
- &part.strong_config.part.sprite_connected
+ &part.config.part.sprite_connected
} else {
- &part.strong_config.part.sprite_disconnected
+ &part.config.part.sprite_disconnected
};
let mut sprite = Sprite::from_image(asset_server.load(path));
sprite.custom_size = Some(Vec2::new(
- part.strong_config.physics.width as f32,
- part.strong_config.physics.height as f32,
+ part.config.physics.width as f32,
+ part.config.physics.height as f32,
));
sprite.color = temp_to_color(temperature);
sprite
M crates/unified/src/client/planet/incoming_planets.rs => crates/unified/src/client/planet/incoming_planets.rs +14 -31
@@ 6,54 6,37 @@ pub fn incoming_planets_plugin(app: &mut App) {
app.add_systems(Update, (handle_incoming_planets, handle_updated_planets));
}
+fn build_planet_sprite(planet: &Planet, asset_server: &AssetServer) -> Sprite {
+ let mut sprite = Sprite::from_image(asset_server.load(&planet.sprite));
+ sprite.custom_size = Some(Vec2::splat(planet.radius as f32 * 2.0));
+ if let Some(SpecialSpriteProperties::ForceColor(c)) = planet.special_sprite_properties {
+ sprite.color = c;
+ }
+ sprite
+}
+
fn handle_incoming_planets(
mut commands: Commands,
new_planets: Query<(Entity, &Planet), Added<Planet>>,
asset_server: Res<AssetServer>,
) {
for (new_entity, new_planet) in new_planets.iter() {
- let mut sprite = Sprite::from_image(asset_server.load(&new_planet.sprite));
- sprite.custom_size = Some(Vec2::new(new_planet.radius as f32 * 2.0, new_planet.radius as f32 * 2.0));
-
- if let Some(SpecialSpriteProperties::ForceColor(c)) = new_planet.special_sprite_properties {
- sprite.color = c;
- }
-
- let mut commands = commands.entity(new_entity);
-
- commands
- //.insert(AdditionalMassProperties::Mass(new_planet.mass))
+ commands.entity(new_entity)
.insert(MAIN_STAR_LAYERS.clone())
- .insert(sprite);
-
+ .insert(build_planet_sprite(new_planet, &asset_server));
trace!(?new_planet, "prepared new planet");
}
}
+
fn handle_updated_planets(
mut commands: Commands,
updated_planets: Query<(Entity, &Planet), Changed<Planet>>,
asset_server: Res<AssetServer>,
) {
for (updated_entity, updated_planet) in updated_planets.iter() {
- let mut sprite = Sprite::from_image(asset_server.load(&updated_planet.sprite));
- sprite.custom_size = Some(Vec2::new(
- updated_planet.radius as f32 * 2.0,
- updated_planet.radius as f32 * 2.0,
- ));
-
- if let Some(SpecialSpriteProperties::ForceColor(c)) =
- updated_planet.special_sprite_properties
- {
- sprite.color = c;
- }
-
- let mut commands = commands.entity(updated_entity);
- commands
- //.remove::<AdditionalMassProperties>()
- //.insert(AdditionalMassProperties::Mass(updated_planet.mass))
+ commands.entity(updated_entity)
.remove::<Sprite>()
- .insert(sprite);
-
+ .insert(build_planet_sprite(updated_planet, &asset_server));
trace!(?updated_planet, "updated planet");
}
}
M crates/unified/src/client/planet/indicators.rs => crates/unified/src/client/planet/indicators.rs +7 -3
@@ 4,6 4,10 @@ use crate::client::components::MainCamera;
use crate::prelude::*;
use bevy::window::PrimaryWindow;
+const INDICATOR_SPRITE_SIZE: f32 = 25.0;
+const INDICATOR_LARGE_SPRITE_SIZE: f32 = 50.0;
+const INDICATOR_CAMERA_SCALE_FACTOR: f32 = 32.0;
+
pub fn indicators_plugin(app: &mut App) {
app.add_systems(PreUpdate, (add_indicators, update_indicators))
.add_systems(PostUpdate, update_indicators_position);
@@ 28,7 32,7 @@ fn add_indicators(
continue;
};
let mut sprite = Sprite::from_image(asset_server.load(indicator_url));
- sprite.custom_size = Some(Vec2::new(25.0, 25.0));
+ sprite.custom_size = Some(Vec2::splat(INDICATOR_SPRITE_SIZE));
let indicator = commands
.spawn((
ChildOf(me),
@@ 50,7 54,7 @@ fn update_indicators(
continue;
};
let mut sprite = Sprite::from_image(asset_server.load(indicator_sprite));
- sprite.custom_size = Some(Vec2::new(50.0, 50.0));
+ sprite.custom_size = Some(Vec2::splat(INDICATOR_LARGE_SPRITE_SIZE));
commands
.entity(indicator.0)
.remove::<Sprite>()
@@ 80,7 84,7 @@ fn update_indicators_position(
for (planet_position, indicator_id) in &planets_w_indicator {
let mut offset = planet_position.translation - player_position.translation;
- let sprite_size = 32.0 * camera.scale.z;
+ let sprite_size = INDICATOR_CAMERA_SCALE_FACTOR * camera.scale.z;
let half_window_height = window.height() * camera.scale.z / 2.0 - (sprite_size / 2.0);
let half_window_width = window.width() * camera.scale.z / 2.0 - (sprite_size / 2.0);
M crates/unified/src/client/starfield.rs => crates/unified/src/client/starfield.rs +19 -29
@@ 2,8 2,6 @@ use bevy::{
app::{App, Startup, Update},
asset::{AssetEvent, AssetServer, Assets},
ecs::{
- //entity::Entity,
- //entity_disabling::Disabled,
query::{With, Without},
system::{Commands, Query, Res, Single},
},
@@ 202,11 200,6 @@ pub fn resize_starfield(
return
};
- /*if camera.scale.z > 10.0 { // arbitrary
- // TODO: find out how to disable sprites // done
- } else {
- // TODO: find out how to reenable them without toggling on every update :(
- }*/
starfield_back.custom_size = Some(
Vec2::new(event.width, event.height) * projection.scale
+ Vec2::splat(BACK_STARFIELD_SIZE * 2.0),
@@ 222,6 215,21 @@ pub fn resize_starfield(
}
}
+pub fn parallax_layer_translation(
+ player_pos: Vec3,
+ window_size: Vec2,
+ camera_scale: f32,
+ parallax_factor: f32,
+ field_size: f32,
+ z: f32,
+) -> Vec3 {
+ player_pos
+ + (-player_pos / parallax_factor) % field_size
+ + (Vec3::new(window_size.x, -window_size.y, 0.0) * camera_scale / 2.0) % field_size
+ + Vec3::new(0.0, field_size, 0.0)
+ - Vec3::new(0.0, 0.0, z)
+}
+
macro_rules! fix_negative_field_translations {
($field:ident, $size:expr) => {
if $field.translation.y < $size / 2.0 {
@@ 283,28 291,10 @@ pub fn update_starfield(
let mut starfield_back_pos = starfield_back.single_mut().unwrap();
let mut starfield_mid_pos = starfield_mid.single_mut().unwrap();
let mut starfield_front_pos = starfield_front.single_mut().unwrap();
- //starfield_pos.translation = (player.translation / STARFIELD_SIZE).round() * STARFIELD_SIZE;
- starfield_back_pos.translation = player.translation
- + (-player.translation / 3.0) % BACK_STARFIELD_SIZE
- + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0) * projection.scale
- / 2.0)
- % BACK_STARFIELD_SIZE
- + Vec3::new(0.0, BACK_STARFIELD_SIZE, 0.0)
- - Vec3::new(0.0, 0.0, 5.0);
- starfield_mid_pos.translation = player.translation
- + (-player.translation / 2.5) % MID_STARFIELD_SIZE
- + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0) * projection.scale
- / 2.0)
- % MID_STARFIELD_SIZE
- + Vec3::new(0.0, MID_STARFIELD_SIZE, 0.0)
- - Vec3::new(0.0, 0.0, 4.5);
- starfield_front_pos.translation = player.translation
- + (-player.translation / 2.0) % FRONT_STARFIELD_SIZE
- + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0) * projection.scale
- / 2.0)
- % FRONT_STARFIELD_SIZE
- + Vec3::new(0.0, FRONT_STARFIELD_SIZE, 0.0)
- - Vec3::new(0.0, 0.0, 4.0);
+ let win = window.size();
+ starfield_back_pos.translation = parallax_layer_translation(player.translation, win, projection.scale, 3.0, BACK_STARFIELD_SIZE, 5.0);
+ starfield_mid_pos.translation = parallax_layer_translation(player.translation, win, projection.scale, 2.5, MID_STARFIELD_SIZE, 4.5);
+ starfield_front_pos.translation = parallax_layer_translation(player.translation, win, projection.scale, 2.0, FRONT_STARFIELD_SIZE, 4.0);
fix_negative_field_translations!(starfield_back_pos, BACK_STARFIELD_SIZE);
fix_negative_field_translations!(starfield_mid_pos, MID_STARFIELD_SIZE);
M crates/unified/src/client/starguide/init.rs => crates/unified/src/client/starguide/init.rs +6 -4
@@ 6,6 6,8 @@ use crate::client::starguide::components::{StarguideCamera, StarguideMe, Stargui
use crate::prelude::*;
use crate::shared::ecs::{Part, STARGUIDE_LAYER};
+const STARGUIDE_RENDER_SCALE: f32 = 10.0;
+
pub fn starguide_init_plugin(app: &mut App) {
app
.add_systems(Startup, init_starguide)
@@ 33,13 35,13 @@ pub fn player_init(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
- let mut sprite = Sprite::from_image(asset_server.load(&me.2.strong_config.part.sprite_connected));
+ let mut sprite = Sprite::from_image(asset_server.load(&me.2.config.part.sprite_connected));
sprite.custom_size = Some(Vec2::new(
- me.2.strong_config.physics.width as f32,
- me.2.strong_config.physics.height as f32,
+ me.2.config.physics.width as f32,
+ me.2.config.physics.height as f32,
));
commands.spawn((sprite, StarguideMe, STARGUIDE_LAYER,
- Transform::from_scale(Vec3::splat(10.0))));
+ Transform::from_scale(Vec3::splat(STARGUIDE_RENDER_SCALE))));
}
fn player_position_update(
M crates/unified/src/client/starguide/input.rs => crates/unified/src/client/starguide/input.rs +0 -2
@@ 35,12 35,10 @@ fn on_click(
fn starguide_drag(
drag: ResMut<StarguideDrag>,
mut camera: Single<&mut Transform, With<StarguideCamera>>,
- //mut orbit: Single<&mut Transform, (With<StarguideOrbit>, Without<StarguideCamera>)>,
cursor: Res<CursorWorldCoordinates>,
) {
if !drag.is_dragging { return }
let Some(cursor) = cursor.0 else { return };
camera.translation = drag.init_camera_pos.extend(0.0) - (cursor - drag.init_cursor_pos).extend(0.0);
- //orbit.translation = drag.init_camera_pos.extend(0.0) - (cursor - drag.init_cursor_pos).extend(0.0);
}
M crates/unified/src/client/starguide/orbit.rs => crates/unified/src/client/starguide/orbit.rs +4 -2
@@ 5,6 5,8 @@ use crate::prelude::*;
use crate::shared::config::planet::Planet;
use crate::shared::world_config::WorldConfigResource;
+const ORBIT_CURVE_STEPS: usize = 200;
+
pub fn starguide_orbit_plugin(app: &mut App) {
app
.add_systems(Update, update_orbits);
@@ 68,8 70,8 @@ fn update_orbits(
// 200 steps in the revolution
let mut first_pos = None;
let mut last_pos = None;
- for i in 0..200 {
- let theta = 2.0*PI*(i as f64)/200.0;
+ for i in 0..ORBIT_CURVE_STEPS {
+ let theta = 2.0*PI*(i as f64)/(ORBIT_CURVE_STEPS as f64);
let r = (1.0/2.0) * ((f_x*f_x + f_y*f_y - 4.0*a*a) / (-2.0*a - f_x*theta.cos() - f_y*theta.sin()));
if r < 0.0 { continue }
M crates/unified/src/client/ui.rs => crates/unified/src/client/ui.rs +0 -10
@@ 36,16 36,6 @@ fn setup_ui(camera: Single<Entity, (With<MainCamera>, With<Camera>)>, mut comman
children![
(TextColor(colors::PEACH), Text::new("Fuel: 25"), FuelText,),
(TextColor(colors::PEACH), Text::new("Power: 25"), PowerText,),
- /*(
- Node {
- width: Val::Percent(100.0),
- height: Val::Px(20.0),
- margin: UiRect::all(Val::Px(5.0)),
- ..Default::default()
- },
- BorderRadius::all(Val::Px(5.0)),
- BackgroundColor(colors::CRUST),
- )*/
],
)],
Visibility::Visible
M crates/unified/src/client/zoom.rs => crates/unified/src/client/zoom.rs +19 -78
@@ 3,9 3,20 @@ use bevy::{
prelude::*,
};
use crate::client::components::{MainCamera, Me};
-use crate::client::starfield::{StarfieldSize, BACK_STARFIELD_SIZE, FRONT_STARFIELD_SIZE, MID_STARFIELD_SIZE};
+use crate::client::starfield::{parallax_layer_translation, StarfieldSize, BACK_STARFIELD_SIZE, FRONT_STARFIELD_SIZE, MID_STARFIELD_SIZE};
use crate::client::starguide::components::{StarfieldBack, StarfieldFront, StarfieldMid, StarguideCamera};
use crate::shared::ecs::GameplayState;
+
+fn restore_tiled(sprite: &mut Sprite, size: Option<&StarfieldSize>) {
+ if let Some(size) = size {
+ sprite.image_mode = SpriteImageMode::Tiled {
+ tile_x: true,
+ tile_y: true,
+ stretch_value: size.0,
+ };
+ }
+}
+
pub fn zoom_plugin(app: &mut App) {
app.add_systems(Update, on_scroll);
}
@@ 50,18 61,6 @@ fn on_scroll(
Without<StarfieldBack>,
),
>,
- /*mut orbit_camera: Single<
- &mut Camera,
- (
-
- Without<StarguideCamera>,
- Without<MainCamera>,
- Without<Me>,
- Without<StarfieldFront>,
- Without<StarfieldMid>,
- Without<StarfieldBack>,
- ),
- >,*/
mut camera: Single<
(&mut Camera, &mut Projection),
(
@@ 83,19 82,6 @@ fn on_scroll(
Without<MainCamera>,
),
>,
- /*mut starguide_orbit: Single<
- &mut Transform,
- (
- With<StarguideOrbit>,
- Without<Me>,
- Without<StarfieldFront>,
- Without<StarfieldMid>,
- Without<StarfieldBack>,
- Without<MainCamera>,
-
- Without<StarguideCamera>,
- ),
- >,*/
) {
let (mut starfield_back, mut starfield_back_pos, mut visibility_back, size_back) =
starfield_back.into_inner();
@@ 114,15 100,10 @@ fn on_scroll(
camera_projection.scale *= 1.03;
starguide_projection.scale *= 1.03;
}
- //starguide_orbit.scale = Vec3::splat(starguide_projection.scale);
-
if camera_projection.scale > 20.0 && matches!(gameplay_state.get(), GameplayState::Main) {
camera.0.is_active = false;
starguide_camera.0.is_active = true;
- //orbit_camera.is_active = true;
-
starguide_camera.2.translation = player.translation;
- //starguide_orbit.translation = player.translation;
gameplay_next_state.set(GameplayState::Starguide);
starfield_back.image_mode = SpriteImageMode::Auto;
@@ 135,30 116,10 @@ fn on_scroll(
camera.0.is_active = true;
gameplay_next_state.set(GameplayState::Main);
starguide_camera.0.is_active = false;
- //orbit_camera.is_active = false;
-
if matches!(*visibility_back, Visibility::Hidden) {
- if let Some(size_back) = size_back {
- starfield_back.image_mode = SpriteImageMode::Tiled {
- tile_x: true,
- tile_y: true,
- stretch_value: size_back.0,
- };
- }
- if let Some(size_mid) = size_mid {
- starfield_mid.image_mode = SpriteImageMode::Tiled {
- tile_x: true,
- tile_y: true,
- stretch_value: size_mid.0,
- };
- }
- if let Some(size_front) = size_front{
- starfield_front.image_mode = SpriteImageMode::Tiled {
- tile_x: true,
- tile_y: true,
- stretch_value: size_front.0,
- };
- }
+ restore_tiled(&mut starfield_back, size_back);
+ restore_tiled(&mut starfield_mid, size_mid);
+ restore_tiled(&mut starfield_front, size_front);
}
*visibility_back = Visibility::Inherited;
*visibility_mid = Visibility::Inherited;
@@ 170,30 131,10 @@ fn on_scroll(
Some(window.size() * camera_projection.scale + Vec2::splat(MID_STARFIELD_SIZE * 2.0));
starfield_front.custom_size =
Some(window.size() * camera_projection.scale + Vec2::splat(FRONT_STARFIELD_SIZE * 2.0));
- starfield_back_pos.translation = player.translation
- + (-player.translation / 3.0) % BACK_STARFIELD_SIZE
- + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0)
- * camera_projection.scale
- / 2.0)
- % BACK_STARFIELD_SIZE
- + Vec3::new(0.0, BACK_STARFIELD_SIZE, 0.0)
- - Vec3::new(0.0, 0.0, 5.0);
- starfield_mid_pos.translation = player.translation
- + (-player.translation / 2.5) % MID_STARFIELD_SIZE
- + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0)
- * camera_projection.scale
- / 2.0)
- % MID_STARFIELD_SIZE
- + Vec3::new(0.0, MID_STARFIELD_SIZE, 0.0)
- - Vec3::new(0.0, 0.0, 4.5);
- starfield_front_pos.translation = player.translation
- + (-player.translation / 2.0) % FRONT_STARFIELD_SIZE
- + (Vec3::new(window.resolution.width(), -window.resolution.height(), 0.0)
- * camera_projection.scale
- / 2.0)
- % FRONT_STARFIELD_SIZE
- + Vec3::new(0.0, FRONT_STARFIELD_SIZE, 0.0)
- - Vec3::new(0.0, 0.0, 4.0);
+ let win = window.size();
+ starfield_back_pos.translation = parallax_layer_translation(player.translation, win, camera_projection.scale, 3.0, BACK_STARFIELD_SIZE, 5.0);
+ starfield_mid_pos.translation = parallax_layer_translation(player.translation, win, camera_projection.scale, 2.5, MID_STARFIELD_SIZE, 4.5);
+ starfield_front_pos.translation = parallax_layer_translation(player.translation, win, camera_projection.scale, 2.0, FRONT_STARFIELD_SIZE, 4.0);
}
}
}
M crates/unified/src/server/craft.rs => crates/unified/src/server/craft.rs +3 -1
@@ 4,6 4,8 @@ use crate::{prelude::*, server::part::{SpawnPartBundle, SpawnPartRequest}};
use crate::shared::attachment::{PartInShip, Parts};
use crate::shared::ecs::{CraftPartRequest, Part, Player, SingleStorage, VariableStorage};
+const CRAFTED_PART_SPAWN_OFFSET: f32 = 50.0;
+
pub fn craft_plugin(app: &mut App) {
app.add_systems(Update, receive_crafting_request);
}
@@ 114,7 116,7 @@ fn receive_crafting_request(
commands.spawn(SpawnPartBundle {
req: SpawnPartRequest(asset_server.load(
format!("config/parts/{}.part.toml", request.crafted_part.to_lowercase()))),
- transform: transform.with_translation(transform.translation + vec3(50.0, 0.0, 0.0)),
+ transform: transform.with_translation(transform.translation + vec3(CRAFTED_PART_SPAWN_OFFSET, 0.0, 0.0)),
vel: *vel,
});
}
M crates/unified/src/server/drill.rs => crates/unified/src/server/drill.rs +32 -60
@@ 1,7 1,7 @@
use crate::server::components::PlanetSensor;
use crate::prelude::*;
use crate::shared::attachment::{PartInShip, Parts};
-use crate::shared::config::planet::Planet;
+use crate::shared::config::planet::{Planet, PlanetResource};
use crate::shared::ecs::{Drill, Part, Player, SingleStorage, ToggleDrillEvent, VariableStorage};
pub fn drill_plugin(app: &mut App) {
@@ 59,69 59,41 @@ fn do_drilling(
time: Res<Time>,
) {
for (entity, drill) in hearty_drills {
- if !drill.drilling || drill.on_planet.is_none() {
- continue
- }
- let planet_name = drill.on_planet.clone().unwrap();
- let mut planet = None;
- for q_planet in planet_query {
- if q_planet.name == planet_name {
- planet = Some(q_planet);
- break;
- }
- }
- // if the planet name doesn't match a planet, we have a big problem
- let planet = planet.expect("In do_drilling, a planet name didn't match a planet");
- let Some(ref planet_resource) = planet.planet_resource else {
- continue
- };
- 'adding_resources: {
- if let Ok(parts_list) = parts_query.get(entity) {
- for part_entity in parts_list.iter() {
- let Ok(mut storage) = single_storage_part_query.get_mut(part_entity) else {
- continue
- };
- let to_add = planet_resource.mining_speed * drill.resource_multiplier * time.delta_secs();
- add_to_single_storage(&mut storage, to_add, &planet_resource.name);
- }
- }
- let Ok(mut storage) = variable_storage_part_query.get_mut(entity) else {
- break 'adding_resources;
- };
+ let Some(planet_resource) = find_drill_resource(drill, &planet_query) else { continue };
+ drill_into_parts(entity, drill, planet_resource, &parts_query, &mut single_storage_part_query, &time);
+ if let Ok(mut storage) = variable_storage_part_query.get_mut(entity) {
let to_add = planet_resource.mining_speed * drill.resource_multiplier * time.delta_secs();
add_to_variable_storage(&mut storage, to_add, &planet_resource.name);
- };
+ }
}
for (drill, part_in_ship) in drills {
- debug!("drill");
- if !drill.drilling || drill.on_planet.is_none() {
- continue
- }
- let planet_name = drill.on_planet.clone().unwrap();
- let mut planet = None;
- for q_planet in planet_query {
- if q_planet.name == planet_name {
- planet = Some(q_planet);
- break;
- }
- }
- // if the planet name doesn't match a planet, we have a big problem
- let planet = planet.expect("In do_drilling, a planet name didn't match a planet");
- let Some(ref planet_resource) = planet.planet_resource else {
- continue
- };
- let player = part_in_ship.0;
- let Ok(parts_list) = parts_query.get(player) else {
- error!("In do_drilling, there was a player without a Parts");
- continue
- };
- for part_entity in parts_list.iter() {
- let Ok(mut storage) = single_storage_part_query.get_mut(part_entity) else {
- continue
- };
- let to_add = planet_resource.mining_speed * drill.resource_multiplier * time.delta_secs();
- add_to_single_storage(&mut storage, to_add, &planet_resource.name);
- }
+ let Some(planet_resource) = find_drill_resource(drill, &planet_query) else { continue };
+ drill_into_parts(part_in_ship.0, drill, planet_resource, &parts_query, &mut single_storage_part_query, &time);
+ }
+}
+
+fn find_drill_resource<'a>(drill: &Drill, planet_query: &'a Query<&Planet>) -> Option<&'a PlanetResource> {
+ if !drill.drilling { return None; }
+ let planet_name = drill.on_planet.as_ref()?;
+ planet_query.iter()
+ .find(|p| p.name == *planet_name)
+ .expect("In do_drilling, a planet name didn't match a planet")
+ .planet_resource.as_ref()
+}
+
+fn drill_into_parts(
+ ship_entity: Entity,
+ drill: &Drill,
+ planet_resource: &PlanetResource,
+ parts_query: &Query<&Parts>,
+ single_storage_query: &mut Query<&mut SingleStorage, With<Part>>,
+ time: &Time,
+) {
+ let Ok(parts_list) = parts_query.get(ship_entity) else { return };
+ for part_entity in parts_list.iter() {
+ let Ok(mut storage) = single_storage_query.get_mut(part_entity) else { continue };
+ let to_add = planet_resource.mining_speed * drill.resource_multiplier * time.delta_secs();
+ add_to_single_storage(&mut storage, to_add, &planet_resource.name);
}
}
M crates/unified/src/server/heat/radiation.rs => crates/unified/src/server/heat/radiation.rs +3 -2
@@ 3,6 3,7 @@ use crate::shared::ecs::{Part, Radiator, Temperature};
const STEFAN_BOLTZMANN: f64 = 5.670374419E-8;
const T_ENV: f64 = 4.0; // units: Kelvin
+const NEWTON_ITERATIONS: u32 = 3;
pub fn heat_radiation_plugin(app: &mut App) {
app.add_systems(Update, part_radiation);
@@ 28,12 29,12 @@ fn part_radiation(
// where x_n is the previous guess for T_n+1
let initial_temp = temperature.0;
let k = (radiator.emissivity * STEFAN_BOLTZMANN * radiator.surface_area)
- / (part.strong_config.physics.mass * part.strong_config.part.specific_heat);
+ / (part.config.physics.mass * part.config.part.specific_heat);
let dt = time.delta_secs() as f64;
// initial guess
let mut next_temp = initial_temp;
- for _ in 0..3 {
+ for _ in 0..NEWTON_ITERATIONS {
let g = next_temp + k*dt*next_temp.powi(4) - initial_temp - k*dt*T_ENV;
let g_prime = 1.0 + 4.0*k*dt*(next_temp).powi(3);
M crates/unified/src/server/orbit/mod.rs => crates/unified/src/server/orbit/mod.rs +18 -6
@@ 8,6 8,9 @@ use crate::shared::config::planet::{Planet, PlanetSpring};
use crate::prelude::{App, Query, Res, Update, Without};
use crate::shared::world_config::WorldConfigResource;
+const KEPLER_MAX_ITERATIONS: u32 = 100;
+const KEPLER_CONVERGENCE_THRESHOLD: f64 = 1e-10;
+
pub struct OrbitPlugin;
impl Plugin for OrbitPlugin {
fn build(&self, app: &mut App) {
@@ 31,9 34,18 @@ fn update_orbits(
// find parent
let Some(parent) = planets_2.iter().find(|u| u.0.name == orbit_data.orbiting) else { continue; };
- let a = (planet.default_transform[0] as f64 - parent.0.default_transform[0] as f64) / (1.0 - orbit_data.eccentricity);
+ let (parent_data, parent_transform, parent_mass) = parent;
+ // Orbital elements:
+ // a = semi-major axis
+ // e = eccentricity
+ // t = orbital period
+ // m = mean anomaly
+ // e_k = eccentric anomaly (solution to Kepler's equation)
+ // nu = true anomaly
+ // r = radial distance
+ let a = (planet.default_position.x as f64 - parent_data.default_position.x as f64) / (1.0 - orbit_data.eccentricity);
let e = orbit_data.eccentricity;
- let t = 2.0*PI*((a*a*a)/(world_config.world.gravity*(**parent.2 as f64))).sqrt();
+ let t = 2.0*PI*((a*a*a)/(world_config.world.gravity*(**parent_mass as f64))).sqrt();
let time = time.elapsed_secs_f64();
@@ 49,8 61,8 @@ fn update_orbits(
// find the spring
let Some(mut planet_spring) = planet_springs.iter_mut().find(|u| u.0.name == planet.name) else { continue; };
- planet_spring.1.translation.x = x as f32 + parent.1.translation.x;
- planet_spring.1.translation.y = y as f32 + parent.1.translation.y;
+ planet_spring.1.translation.x = x as f32 + parent_transform.translation.x;
+ planet_spring.1.translation.y = y as f32 + parent_transform.translation.y;
let Some(parent_velocity) = parent_velocities.get(&orbit_data.orbiting) else { continue; };
let de_dt = (TAU / t) / (1.0 - e * e_k.cos());
@@ 64,10 76,10 @@ fn update_orbits(
fn iterative_kepler(m: f64, e: f64) -> f64 {
let mut output = m;
- for _ in 0..100 {
+ for _ in 0..KEPLER_MAX_ITERATIONS {
let d = (m - output + e * output.sin()) / (1.0 - e * output.cos());
output += d;
- if d.abs() < 1e-10 {
+ if d.abs() < KEPLER_CONVERGENCE_THRESHOLD {
break;
}
}
M crates/unified/src/server/part.rs => crates/unified/src/server/part.rs +1 -1
@@ 129,7 129,7 @@ fn handle_part_reloading(
fn calculate_bundle(config: &PartConfig, handle: &Handle<PartConfig>) -> impl Bundle {
let part = Part {
- strong_config: config.clone(),
+ config: config.clone(),
};
let part_handle = PartHandle(handle.clone());
let collider = Collider::rectangle(config.physics.width, config.physics.height);
M crates/unified/src/server/planets.rs => crates/unified/src/server/planets.rs +10 -27
@@ 5,6 5,8 @@ use crate::shared::config::planet::{PlanetSpring, PlanetSpringJoint};
use crate::shared::config::planet::{Planet, PlanetBundle, PlanetConfigCollection};
use crate::shared::world_config::WorldConfigResource;
+const PLANET_SENSOR_PADDING: f64 = 2.0;
+
pub fn planets_plugin(app: &mut App) {
app.init_resource::<PlanetConfigResource>()
.add_systems(Startup, start_loading_planets)
@@ 50,22 52,18 @@ pub fn update_planets(
debug!("planet config loaded - creating planets");
let planet_config = assets.get(*id).unwrap();
for planet in &planet_config.planets {
- let planet_position = vec3(planet.default_transform[0], planet.default_transform[1], planet.default_transform[2]);
+ let planet_position = planet.default_position;
let mut planet_entity = commands
.spawn((PlanetBundle {
planet: planet.clone(),
- transform: Transform::from_xyz(
- planet.default_transform[0],
- planet.default_transform[1],
- planet.default_transform[2],
- ),
+ transform: Transform::from_translation(planet.default_position),
collider: Collider::circle(planet.radius),
mass: Mass(planet.mass)
},
SleepingDisabled
));
planet_entity.with_child((
- Collider::circle(planet.radius+2.0),
+ Collider::circle(planet.radius + PLANET_SENSOR_PADDING),
Sensor,
PlanetSensor(planet.name.clone()),
CollisionEventsEnabled,
@@ 73,8 71,7 @@ pub fn update_planets(
let planet_entity_id = planet_entity.id();
if let Some(orbit) = &planet.orbit {
let Some(parent_planet) = planet_config.planets.iter().find(|u| orbit.orbiting == u.name) else { continue };
- let parent_planet_position = vec3(parent_planet.default_transform[0],
- parent_planet.default_transform[1], parent_planet.default_transform[2]);
+ let parent_planet_position = parent_planet.default_position;
let r = (planet_position - parent_planet_position).as_dvec3();
let g = world_config.world.gravity * parent_planet.mass as f64
/ r.length_squared();
@@ 88,11 85,7 @@ pub fn update_planets(
PlanetSpring {
name: planet.name.clone()
},
- Transform::from_xyz(
- planet.default_transform[0],
- planet.default_transform[1],
- planet.default_transform[2],
- ),
+ Transform::from_translation(planet.default_position),
RigidBody::Kinematic,
)).id();
commands.spawn((
@@ 138,15 131,9 @@ pub fn update_planets(
let planet_entity = commands
.spawn(PlanetBundle {
planet: planet.clone(),
- transform: Transform::from_xyz(
- planet.default_transform[0],
- planet.default_transform[1],
- planet.default_transform[2],
- ),
+ transform: Transform::from_translation(planet.default_position),
collider: Collider::circle(planet.radius),
- mass: Mass(
- planet.mass,
- ),
+ mass: Mass(planet.mass),
}).id();
if planet.orbit.is_some() {
@@ 154,11 141,7 @@ pub fn update_planets(
PlanetSpring {
name: planet.name.clone()
},
- Transform::from_xyz(
- planet.default_transform[0],
- planet.default_transform[1],
- planet.default_transform[2],
- )
+ Transform::from_translation(planet.default_position),
)).id();
commands.spawn((
PlanetSpringJoint {
M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +3 -1
@@ 1,6 1,8 @@
pub mod join;
pub mod thrust;
+const FUEL_REGEN_RATE: f32 = 5.0;
+
use crate::shared::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::shared::ecs::{DragAction, DragRequestEvent, Part, Player, PlayerStorage};
use crate::server::damping::ModuleJointDamping;
@@ 377,6 379,6 @@ fn dragging(
fn magic_fuel_regen(players: Query<&mut PlayerStorage, With<Player>>, time: Res<Time>) {
for mut storage in players {
- storage.fuel = (storage.fuel + 5.0 * time.delta_secs()).min(storage.fuel_capacity);
+ storage.fuel = (storage.fuel + FUEL_REGEN_RATE * time.delta_secs()).min(storage.fuel_capacity);
}
}
M crates/unified/src/server/player/join.rs => crates/unified/src/server/player/join.rs +9 -5
@@ 7,6 7,10 @@ use crate::server::ConnectedGameEntity;
use crate::server::part::SpawnPartRequest;
use crate::shared::world_config::WorldConfigResource;
+const SPAWN_ORBIT_OFFSET: f64 = 150.0;
+const INITIAL_FUEL_CAPACITY: f32 = 25.0;
+const INITIAL_POWER_CAPACITY: f32 = 25.0;
+
fn join_player(joined_player: Entity, mut commands: Commands, wc: &GlobalWorldConfig,
planets: Query<(&Transform, &LinearVelocity, &Planet)>, asset_server: &AssetServer
) {
@@ 27,7 31,7 @@ fn join_player(joined_player: Entity, mut commands: Commands, wc: &GlobalWorldCo
)
});
let angle = rand::random::<f32>() * std::f32::consts::TAU;
- let offset = spawn_planet.radius + 150.0;
+ let offset = spawn_planet.radius + SPAWN_ORBIT_OFFSET;
let mut new_transform =
Transform::from_xyz(angle.cos() * offset as f32, angle.sin() * offset as f32, 0.0);
new_transform.rotate_z(angle);
@@ 42,10 46,10 @@ fn join_player(joined_player: Entity, mut commands: Commands, wc: &GlobalWorldCo
asset_server.load("config/parts/hearty.part.toml"),
))
.insert(PlayerStorage {
- fuel_capacity: 25.0,
- fuel: 25.0,
- power_capacity: 25.0,
- power: 25.0,
+ fuel_capacity: INITIAL_FUEL_CAPACITY,
+ fuel: INITIAL_FUEL_CAPACITY,
+ power_capacity: INITIAL_POWER_CAPACITY,
+ power: INITIAL_POWER_CAPACITY,
})
.insert(Player {
client: joined_player,
M crates/unified/src/shared/config/planet.rs => crates/unified/src/shared/config/planet.rs +2 -1
@@ 14,7 14,8 @@ pub struct Planet {
pub indicator_sprite: Option<String>,
pub radius: f64,
pub mass: f32,
- pub default_transform: [f32; 3],
+ #[serde(rename = "default_transform")]
+ pub default_position: Vec3,
pub planet_resource: Option<PlanetResource>,
pub special_sprite_properties: Option<SpecialSpriteProperties>,
pub orbit: Option<OrbitData>
M crates/unified/src/shared/config/world.rs => crates/unified/src/shared/config/world.rs +2 -2
@@ 5,7 5,7 @@ use serde::Deserialize;
#[derive(Deserialize, Asset, TypePath, Clone)]
pub struct GlobalWorldConfig {
pub world: WorldConfig,
- pub part: WPartConfig,
+ pub part: WorldPartConfig,
pub hearty: HeartyConfig
}
@@ 18,7 18,7 @@ pub struct WorldConfig {
}
#[derive(Deserialize, Asset, TypePath, Clone, Debug)]
-pub struct WPartConfig {
+pub struct WorldPartConfig {
pub default_width: f64,
pub default_height: f64,
pub default_mass: f64,
M crates/unified/src/shared/ecs.rs => crates/unified/src/shared/ecs.rs +1 -1
@@ 29,7 29,7 @@ pub const ORBIT_LAYER: RenderLayers = RenderLayers::layer(2);
ConstantForce,
)]
pub struct Part {
- pub strong_config: PartConfig,
+ pub config: PartConfig,
}
#[derive(Component, Debug)]
pub struct PartHandle(pub Handle<PartConfig>);
M crates/unified/src/shared/plugins.rs => crates/unified/src/shared/plugins.rs +5 -15
@@ 9,17 9,20 @@ use crate::shared::config::recipe::RecipesConfig;
use crate::shared::config::world::GlobalWorldConfig;
use crate::shared::world_config::world_config_plugin;
+const PHYSICS_TICK_RATE: f64 = 20.0;
+const PHYSICS_LENGTH_UNIT: f64 = 100.0;
+
pub struct SharedPluginGroup;
impl PluginGroup for SharedPluginGroup {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(|app: &mut App| {
- app.insert_resource(Time::from_hz(20.0));
+ app.insert_resource(Time::from_hz(PHYSICS_TICK_RATE));
})
.add_group(
PhysicsPlugins::default()
- .with_length_unit(100.0)
+ .with_length_unit(PHYSICS_LENGTH_UNIT)
.set(PhysicsInterpolationPlugin::interpolate_all())
.build()
.disable::<IslandPlugin>()
@@ 49,16 52,3 @@ fn physics_setup_plugin(app: &mut App) {
}
fn setup_physics() {}
-
-/*
-fn setup_physics(
- mut rapier_config: Query<&mut RapierConfiguration>,
- mut rapier_context: Query<&mut RapierContextSimulation>,
-) {
- let mut cfg = rapier_config.single_mut().unwrap();
- cfg.gravity = Vec2::ZERO;
- let ctx = rapier_context.single_mut().unwrap();
- let mut params = ctx.integration_parameters;
- params.num_internal_stabilization_iterations = 16;
-}
-*/