Skip to content

Commit

Permalink
Add simplistic building construction
Browse files Browse the repository at this point in the history
Fixes #23.
  • Loading branch information
Indy2222 committed Jun 27, 2022
1 parent 5ada67b commit f62ed60
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 11 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/controller/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ de_core = { path = "../core", version = "0.1.0-dev" }
de_index = { path = "../index", version = "0.1.0-dev" }
de_terrain = { path = "../terrain", version = "0.1.0-dev" }
de_pathing = { path = "../pathing", version = "0.1.0-dev" }
de_spawner = { path = "../spawner", version = "0.1.0-dev" }

# Other
bevy = "0.7.0"
Expand Down
52 changes: 43 additions & 9 deletions crates/controller/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ use bevy::{
input::{mouse::MouseButtonInput, ElementState},
prelude::*,
};
use de_core::{objects::MovableSolid, projection::ToFlat};
use de_core::{
objects::{ActiveObjectType, MovableSolid},
projection::ToFlat,
};
use de_pathing::UpdateEntityPath;
use de_spawner::Draft;
use iyes_loopless::prelude::*;

use crate::{
draft::{NewDraftEvent, SpawnDraftsEvent},
pointer::Pointer,
selection::{SelectEvent, Selected, SelectionMode},
Labels,
Expand All @@ -30,6 +35,11 @@ impl Plugin for CommandPlugin {
.run_if(on_pressed(MouseButton::Left))
.label(Labels::InputUpdate)
.after(Labels::PreInputUpdate),
)
.with_system(
key_press_handler
.label(Labels::InputUpdate)
.after(Labels::PreInputUpdate),
),
);
}
Expand Down Expand Up @@ -63,18 +73,42 @@ fn right_click_handler(
}

fn left_click_handler(
mut events: EventWriter<SelectEvent>,
mut select_events: EventWriter<SelectEvent>,
mut draft_events: EventWriter<SpawnDraftsEvent>,
keys: Res<Input<KeyCode>>,
pointer: Res<Pointer>,
drafts: Query<(), With<Draft>>,
) {
let selection_mode = if keys.pressed(KeyCode::LControl) {
SelectionMode::Add
if drafts.is_empty() {
let selection_mode = if keys.pressed(KeyCode::LControl) {
SelectionMode::Add
} else {
SelectionMode::Replace
};
let event = match pointer.entity() {
Some(entity) => SelectEvent::single(entity, selection_mode),
None => SelectEvent::none(selection_mode),
};
select_events.send(event);
} else {
SelectionMode::Replace
draft_events.send(SpawnDraftsEvent);
}
}

fn key_press_handler(
keys: Res<Input<KeyCode>>,
pointer: Res<Pointer>,
mut events: EventWriter<NewDraftEvent>,
) {
let key = match keys.get_just_pressed().last() {
Some(key) => key,
None => return,
};
let event = match pointer.entity() {
Some(entity) => SelectEvent::single(entity, selection_mode),
None => SelectEvent::none(selection_mode),
let object_type = match key {
KeyCode::B => ActiveObjectType::Base,
KeyCode::P => ActiveObjectType::PowerHub,
_ => return,
};
events.send(event);
let point = pointer.terrain_point().unwrap_or(Vec3::ZERO);
events.send(NewDraftEvent::new(point, object_type));
}
109 changes: 109 additions & 0 deletions crates/controller/src/draft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use bevy::prelude::*;
use de_core::{
gconfig::GameConfig,
objects::{ActiveObjectType, ObjectType},
};
use de_spawner::{Draft, DraftBundle, SpawnBundle};

use super::Labels;
use crate::pointer::Pointer;

pub(crate) struct DraftPlugin;

impl Plugin for DraftPlugin {
fn build(&self, app: &mut App) {
app.add_event::<SpawnDraftsEvent>()
.add_event::<NewDraftEvent>()
.add_system_set_to_stage(
CoreStage::PreUpdate,
SystemSet::new()
.with_system(spawn.after(Labels::InputUpdate))
.with_system(new_drafts.after(Labels::InputUpdate))
.with_system(
move_drafts
.label(Labels::InputUpdate)
.after(Labels::PreInputUpdate),
),
);
}
}

pub(crate) struct SpawnDraftsEvent;

pub(crate) struct NewDraftEvent {
point: Vec3,
object_type: ActiveObjectType,
}

impl NewDraftEvent {
pub(crate) fn new(point: Vec3, object_type: ActiveObjectType) -> Self {
Self { point, object_type }
}

fn point(&self) -> Vec3 {
self.point
}

fn object_type(&self) -> ActiveObjectType {
self.object_type
}
}

fn spawn(
mut commands: Commands,
game_config: Res<GameConfig>,
mut events: EventReader<SpawnDraftsEvent>,
// Use global transform, since that is the one rendered in the last frame
// (and seen by the user) and checked for collisions.
drafts: Query<(Entity, &GlobalTransform, &ObjectType, &Draft)>,
) {
if events.iter().count() == 0 {
return;
}

for (entity, &transform, &object_type, draft) in drafts.iter() {
if draft.allowed() {
commands.entity(entity).despawn_recursive();
commands
.spawn_bundle(SpawnBundle::new(object_type, transform.into()))
.insert(game_config.player());
}
}
}

fn new_drafts(
mut commands: Commands,
mut events: EventReader<NewDraftEvent>,
drafts: Query<Entity, With<Draft>>,
) {
let event = match events.iter().last() {
Some(event) => event,
None => return,
};

for entity in drafts.iter() {
// TODO: this sometimes leads to an error:
// Entity 1625v23 does not exist
commands.entity(entity).despawn_recursive();
}

commands.spawn_bundle(DraftBundle::new(
event.object_type(),
Transform {
translation: event.point(),
..Default::default()
},
));
}

fn move_drafts(pointer: Res<Pointer>, mut drafts: Query<&mut Transform, With<Draft>>) {
// TODO move by some delta, rather than to a point
// TODO: make this a no-op if the mouse was not moved
let point = match pointer.terrain_point() {
Some(point) => point,
None => return,
};
for mut transform in drafts.iter_mut() {
transform.translation = point;
}
}
5 changes: 4 additions & 1 deletion crates/controller/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
use bevy::{app::PluginGroupBuilder, prelude::*};
use command::CommandPlugin;
use draft::DraftPlugin;
use pointer::PointerPlugin;
use selection::SelectionPlugin;

mod command;
mod draft;
mod pointer;
mod selection;

Expand All @@ -16,7 +18,8 @@ impl PluginGroup for ControllerPluginGroup {
group
.add(PointerPlugin)
.add(CommandPlugin)
.add(SelectionPlugin);
.add(SelectionPlugin)
.add(DraftPlugin);
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/spawner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ repository = "https://github.com/DigitalExtinction/Game"
# DE
de_core = { path = "../core", version = "0.1.0-dev" }
de_objects = { path = "../objects", version = "0.1.0-dev" }
de_map = { path = "../map", version = "0.1.0-dev" }
de_index = { path = "../index", version = "0.1.0-dev" }

# Other
bevy = "0.7.0"
iyes_loopless = "0.5.1"
parry3d = "0.9.0"
parry2d = { git = "https://github.com/Indy2222/parry", branch = "feature/dilation" }
103 changes: 103 additions & 0 deletions crates/spawner/src/draft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//! This module implements a Bevy plugin for drafting new objects on the map.
//! An entity marked with a component [`Draft`] is automatically handled and
//! visualized by the plugin.
use bevy::prelude::*;
use de_core::{
objects::{ActiveObjectType, MovableSolid, ObjectType, StaticSolid},
projection::ToFlat,
state::GameState,
};
use de_index::{ColliderWithCache, QueryCollider, SpatialQuery};
use de_map::size::MapBounds;
use de_objects::{ColliderCache, ObjectCache};
use iyes_loopless::prelude::*;
use parry2d::bounding_volume::BoundingVolume;
use parry3d::math::Isometry;

pub(crate) struct DraftPlugin;

impl Plugin for DraftPlugin {
fn build(&self, app: &mut App) {
app.add_system_set(
SystemSet::new()
.with_system(new_draft.run_in_state(GameState::Playing))
.with_system(update_draft.run_in_state(GameState::Playing)),
);
}
}

/// Bundle to spawn a construction draft.
#[derive(Bundle)]
pub struct DraftBundle {
object_type: ObjectType,
transform: Transform,
global_transform: GlobalTransform,
draft: Draft,
}

impl DraftBundle {
pub fn new(object_type: ActiveObjectType, transform: Transform) -> Self {
Self {
object_type: ObjectType::Active(object_type),
transform,
global_transform: transform.into(),
draft: Draft::default(),
}
}
}

#[derive(Component, Default)]
pub struct Draft {
allowed: bool,
}

impl Draft {
pub fn allowed(&self) -> bool {
self.allowed
}
}

#[derive(Component)]
struct Ready;

type NonReadyDrafts<'w, 's> =
Query<'w, 's, (Entity, &'static ObjectType), (With<Draft>, Without<Ready>)>;

type Solids<'w, 's> = SpatialQuery<'w, 's, Entity, Or<(With<StaticSolid>, With<MovableSolid>)>>;

fn new_draft(mut commands: Commands, drafts: NonReadyDrafts, cache: Res<ObjectCache>) {
for (entity, object_type) in drafts.iter() {
commands
.entity(entity)
.insert(Ready)
.with_children(|parent| {
parent.spawn_scene(cache.get(*object_type).scene());
});
}
}

fn update_draft(
mut drafts: Query<(&Transform, &ObjectType, &mut Draft)>,
solids: Solids,
cache: Res<ObjectCache>,
bounds: Res<MapBounds>,
) {
for (transform, &object_type, mut draft) in drafts.iter_mut() {
let collider = QueryCollider::new(
cache.get_collider(object_type),
Isometry::new(
transform.translation.into(),
transform.rotation.to_scaled_axis().into(),
),
);

let flat_aabb = collider.world_aabb().to_flat();
let allowed = bounds.aabb().contains(&flat_aabb) && !solids.collides(&collider);
if allowed != draft.allowed {
// Access the component mutably only when really needed for optimal
// Bevy change detection.
draft.allowed = allowed
}
}
}
5 changes: 4 additions & 1 deletion crates/spawner/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
//! Object spawning and drafting functionalities.
use bevy::{app::PluginGroupBuilder, prelude::*};
use draft::DraftPlugin;
pub use draft::{Draft, DraftBundle};
pub use spawner::SpawnBundle;
use spawner::SpawnerPlugin;

mod draft;
mod spawner;

pub struct SpawnerPluginGroup;

impl PluginGroup for SpawnerPluginGroup {
fn build(&mut self, group: &mut PluginGroupBuilder) {
group.add(SpawnerPlugin);
group.add(SpawnerPlugin).add(DraftPlugin);
}
}

0 comments on commit f62ed60

Please sign in to comment.