|
|
|
|
@ -1,17 +1,14 @@
|
|
|
|
|
#![allow(clippy::single_match)]
|
|
|
|
|
#![allow(clippy::type_complexity)]
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
|
// - When shape asset is updated, shape layout update in real time
|
|
|
|
|
// - Debug: gizmo outline entities
|
|
|
|
|
// - conditional enable/disable
|
|
|
|
|
// - entities without mesh should draw empty cross
|
|
|
|
|
// - Debug: gizmo draw relationship lines
|
|
|
|
|
// - conditional enable/disable
|
|
|
|
|
// - Debug: show FPS
|
|
|
|
|
// - conditional enable/disable
|
|
|
|
|
// - Line entities
|
|
|
|
|
// - Debug: Nametags
|
|
|
|
|
// - If it has a Name component, display that at the same Translation
|
|
|
|
|
// - Disable shape when placed
|
|
|
|
|
@ -31,6 +28,7 @@ impl Plugin for BlocksPlugin {
|
|
|
|
|
app.init_asset::<ShapeAsset>()
|
|
|
|
|
.init_asset_loader::<ShapeAssetLoader>()
|
|
|
|
|
.add_message::<Movement>()
|
|
|
|
|
.add_message::<ShapeEvent>()
|
|
|
|
|
.add_systems(OnEnter(Loading(true)), load_assets.run_if(run_once))
|
|
|
|
|
.add_systems(
|
|
|
|
|
OnEnter(GameState::Setup),
|
|
|
|
|
@ -49,6 +47,7 @@ impl Plugin for BlocksPlugin {
|
|
|
|
|
propogate_relative_position.run_if(any_component_changed::<RelativePosition>),
|
|
|
|
|
handle_kb_input.run_if(on_message::<KeyboardInput>),
|
|
|
|
|
handle_movement.run_if(on_message::<Movement>),
|
|
|
|
|
handle_shape_event.run_if(on_message::<ShapeEvent>),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.add_observer(add_shape)
|
|
|
|
|
@ -108,11 +107,11 @@ struct GridPosition {
|
|
|
|
|
#[derive(Debug, Error)]
|
|
|
|
|
enum GridPositionError {
|
|
|
|
|
#[error("X > X_MAX")]
|
|
|
|
|
XOver,
|
|
|
|
|
HitRight,
|
|
|
|
|
#[error("X < 0")]
|
|
|
|
|
XUnder,
|
|
|
|
|
HitLeft,
|
|
|
|
|
#[error("Y < 0")]
|
|
|
|
|
YUnder,
|
|
|
|
|
HitFloor,
|
|
|
|
|
#[error("Y < 0 && X < 0")]
|
|
|
|
|
BothUnder,
|
|
|
|
|
}
|
|
|
|
|
@ -130,17 +129,17 @@ impl GridPosition {
|
|
|
|
|
// Both underflow
|
|
|
|
|
(None, None) => Err(GridPositionError::BothUnder),
|
|
|
|
|
// Just y underflows
|
|
|
|
|
(None, Some(_)) => Err(GridPositionError::XUnder),
|
|
|
|
|
(None, Some(_)) => Err(GridPositionError::HitLeft),
|
|
|
|
|
// y underflow or x overflow
|
|
|
|
|
(Some(x), None) => match x {
|
|
|
|
|
0..X_MAX => Err(GridPositionError::YUnder),
|
|
|
|
|
_ => Err(GridPositionError::XOver),
|
|
|
|
|
}
|
|
|
|
|
0..X_MAX => Err(GridPositionError::HitFloor),
|
|
|
|
|
_ => Err(GridPositionError::HitRight),
|
|
|
|
|
},
|
|
|
|
|
// both good, may x overflow
|
|
|
|
|
(Some(x), Some(y)) => match x {
|
|
|
|
|
0..X_MAX => Ok(GridPosition { x, y }),
|
|
|
|
|
_ => Err(GridPositionError::XOver),
|
|
|
|
|
}
|
|
|
|
|
_ => Err(GridPositionError::HitRight),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -379,7 +378,7 @@ fn setup_grid(
|
|
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
|
mut checklist: ResMut<SetupChecklist>,
|
|
|
|
|
) {
|
|
|
|
|
let m: Mesh = Rectangle::new(SCALE, SCALE).into();
|
|
|
|
|
let m: Mesh = Rectangle::new(SCALE * 0.9, SCALE * 0.9).into();
|
|
|
|
|
let mh: Handle<Mesh> = meshes.add(m);
|
|
|
|
|
let m2d = Mesh2d(mh);
|
|
|
|
|
let c: Color = WHITE.with_alpha(0.5).into();
|
|
|
|
|
@ -387,10 +386,15 @@ fn setup_grid(
|
|
|
|
|
let mat = MeshMaterial2d(ch);
|
|
|
|
|
let t = Transform::from_xyz(0.0, 0.0, -1.0);
|
|
|
|
|
|
|
|
|
|
for x in 0..X_MAX {
|
|
|
|
|
for y in 0..Y_MAX {
|
|
|
|
|
// Spawn this line
|
|
|
|
|
let gp = GridPosition { x: 0, y };
|
|
|
|
|
commands.spawn((gp, Line(y as u8), LineBlocks::default(), t));
|
|
|
|
|
|
|
|
|
|
// Spawn the grid squares for this line
|
|
|
|
|
for x in 0..X_MAX {
|
|
|
|
|
let gp = GridPosition { x, y };
|
|
|
|
|
commands.spawn((gp, m2d.clone(), mat.clone(), t.clone()));
|
|
|
|
|
commands.spawn((gp, m2d.clone(), mat.clone(), t));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -407,6 +411,20 @@ struct ShapeBlock(Entity);
|
|
|
|
|
#[relationship_target(relationship = ShapeBlock)]
|
|
|
|
|
struct ShapeBlocks(Vec<Entity>);
|
|
|
|
|
|
|
|
|
|
/// Line number component
|
|
|
|
|
#[derive(Component)]
|
|
|
|
|
struct Line(u8);
|
|
|
|
|
|
|
|
|
|
/// Blocks <- Line Relationship
|
|
|
|
|
#[derive(Component)]
|
|
|
|
|
#[relationship(relationship_target = LineBlocks)]
|
|
|
|
|
struct LineBlock(Entity);
|
|
|
|
|
|
|
|
|
|
/// Line -> Blocks Relationship
|
|
|
|
|
#[derive(Component, Default)]
|
|
|
|
|
#[relationship_target(relationship = LineBlock)]
|
|
|
|
|
struct LineBlocks(Vec<Entity>);
|
|
|
|
|
|
|
|
|
|
/// Event handler for transforming a handle component into a thing
|
|
|
|
|
fn add_shape(
|
|
|
|
|
event: On<Add, AssetComponent<ShapeAsset>>,
|
|
|
|
|
@ -520,6 +538,12 @@ enum Movement {
|
|
|
|
|
Right,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Things that can happen to a shape
|
|
|
|
|
#[derive(Debug, Message, PartialEq)]
|
|
|
|
|
enum ShapeEvent {
|
|
|
|
|
Deactivate,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Handle KeyBoard input
|
|
|
|
|
/// Nothing is handled directly in this method,
|
|
|
|
|
/// All key presses result in either a message, event, or state change
|
|
|
|
|
@ -573,40 +597,54 @@ impl Orientation {
|
|
|
|
|
fn handle_movement(
|
|
|
|
|
mut moves: MessageReader<Movement>,
|
|
|
|
|
mut shapes: Query<(&ShapeLayout, &mut GridPosition, &mut Orientation)>,
|
|
|
|
|
mut writer: MessageWriter<ShapeEvent>,
|
|
|
|
|
) {
|
|
|
|
|
moves.read().for_each(|m| {
|
|
|
|
|
shapes.iter_mut().for_each(|(shape_layout, mut gp, mut o)| {
|
|
|
|
|
'outer: for m in moves.read() {
|
|
|
|
|
for (shape_layout, mut gp, mut o) in shapes.iter_mut() {
|
|
|
|
|
// First determine if proposed positions are valid
|
|
|
|
|
// Determine next orientation
|
|
|
|
|
let proposed_orientation = if *m == Movement::Rotate {
|
|
|
|
|
o.rotated()
|
|
|
|
|
} else {
|
|
|
|
|
*o
|
|
|
|
|
let proposed_orientation = match *m == Movement::Rotate {
|
|
|
|
|
true => o.rotated(),
|
|
|
|
|
false => *o,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// And next position
|
|
|
|
|
let proposed_position = match m {
|
|
|
|
|
Movement::Left => gp.add((-1, 0).into()),
|
|
|
|
|
Movement::Right => gp.add((1, 0).into()),
|
|
|
|
|
Movement::Down => gp.add((0, -1).into()),
|
|
|
|
|
Movement::Rotate => Ok(gp.clone()),
|
|
|
|
|
let offset: RelativePosition = match m {
|
|
|
|
|
Movement::Left => (-1, 0).into(),
|
|
|
|
|
Movement::Right => (1, 0).into(),
|
|
|
|
|
Movement::Down => (0, -1).into(),
|
|
|
|
|
Movement::Rotate => (0, 0).into(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Ok(pp) = proposed_position {
|
|
|
|
|
// Check that new positions are all valid
|
|
|
|
|
if shape_layout
|
|
|
|
|
.positions(proposed_orientation)
|
|
|
|
|
.iter()
|
|
|
|
|
.all(|rp| pp.add(*rp).is_ok()) {
|
|
|
|
|
// then commit the movement
|
|
|
|
|
*gp = pp;
|
|
|
|
|
// TODO: See if the proposed position would collide with another block
|
|
|
|
|
// If would collide downward, deactivate shape
|
|
|
|
|
|
|
|
|
|
// Check that new positions are all valid for all blocks
|
|
|
|
|
for pos in shape_layout.positions(proposed_orientation).iter() {
|
|
|
|
|
match gp.add(*pos).unwrap().add(offset) {
|
|
|
|
|
Err(e) => {
|
|
|
|
|
match e {
|
|
|
|
|
GridPositionError::HitFloor => {
|
|
|
|
|
writer.write(ShapeEvent::Deactivate);
|
|
|
|
|
}
|
|
|
|
|
_ => (), // Other errors are just ignored
|
|
|
|
|
}
|
|
|
|
|
// Either way skip the rest of this iteration of the loop
|
|
|
|
|
continue 'outer;
|
|
|
|
|
}
|
|
|
|
|
Ok(_) => (), // Ignore OKs as no [bad] news is good news
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If those checks went well, commit the proposed changes
|
|
|
|
|
*gp = gp.add(offset).unwrap();
|
|
|
|
|
*o = proposed_orientation;
|
|
|
|
|
} else {
|
|
|
|
|
// invalid position!
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Invalid move!
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_shape_event(mut reader: MessageReader<ShapeEvent>) {
|
|
|
|
|
reader.read().for_each(|msg| match msg {
|
|
|
|
|
ShapeEvent::Deactivate => warn!("TODO: Deactivate shape"),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|