diff --git a/examples/the-dungeon-puzzlers-lament/build.rs b/examples/the-dungeon-puzzlers-lament/build.rs
index f0a4f1dd..2981feca 100644
--- a/examples/the-dungeon-puzzlers-lament/build.rs
+++ b/examples/the-dungeon-puzzlers-lament/build.rs
@@ -10,25 +10,9 @@ use proc_macro2::TokenStream;
const LEVEL_NAMES: &[&str] = &[
"a_familiar_sight",
- "level1",
- "level2",
- "level3",
- "level4",
- "level5",
- "level6",
- "level_switch",
- "level_spikes",
- "level_spikes2",
- "level_squid_force_button",
- "level_squid_intro",
- "level_squid2",
- "level_squid1",
- "level_squid_item",
- "level_squid_button",
- "level_squid_drop",
- "level_spikes3",
- "level_around",
- "level_squidprogramming",
+ "block_push_1",
+ "block_push_2",
+ "block_push_3",
];
fn main() {
@@ -102,6 +86,8 @@ enum Entity {
SquidUp,
SquidDown,
Ice,
+ MovableBlock,
+ Glove,
}
impl FromStr for Entity {
@@ -126,6 +112,8 @@ impl FromStr for Entity {
"SQUID_UP" => SquidUp,
"SQUID_DOWN" => SquidDown,
"ICE" => Ice,
+ "BLOCK" => MovableBlock,
+ "GLOVE" => Glove,
_ => return Err(()),
})
}
@@ -151,6 +139,8 @@ impl quote::ToTokens for Entity {
SquidUp => quote!(Item::SquidUp),
SquidDown => quote!(Item::SquidDown),
Ice => quote!(Item::Ice),
+ MovableBlock => quote!(Item::MovableBlock),
+ Glove => quote!(Item::Glove),
})
}
}
diff --git a/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite
index 9bcfb935..112b25d7 100644
Binary files a/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite and b/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite differ
diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_1.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_1.tmx
new file mode 100644
index 00000000..4deac7d6
--- /dev/null
+++ b/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_1.tmx
@@ -0,0 +1,37 @@
+
+
diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_2.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_2.tmx
new file mode 100644
index 00000000..560a1c96
--- /dev/null
+++ b/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_2.tmx
@@ -0,0 +1,40 @@
+
+
diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_3.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_3.tmx
new file mode 100644
index 00000000..178c36c1
--- /dev/null
+++ b/examples/the-dungeon-puzzlers-lament/maps/levels/block_push_3.tmx
@@ -0,0 +1,40 @@
+
+
diff --git a/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs b/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs
index e7be632e..ada631a5 100644
--- a/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs
+++ b/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs
@@ -85,6 +85,195 @@ impl EntityMap {
AnimationInstruction::Add(idx, entity, location, None)
}
+ fn attempt_move_in_direction(
+ &mut self,
+ map: &Map,
+ animations: &mut Vec,
+ entities_to_try_update: &mut VecDeque<(EntityKey, Action)>,
+ entity_to_update_key: EntityKey,
+ direction: Direction,
+ depth: i32,
+ ) -> (bool, bool, bool) {
+ let mut hero_has_died = false;
+ let mut win_has_triggered = false;
+
+ let Some(entity_to_update) = self.map.get(entity_to_update_key) else {
+ return (false, hero_has_died, win_has_triggered);
+ };
+
+ let entity_location = entity_to_update.location;
+
+ let desired_location = entity_location + direction.into();
+ let surface = map.get(desired_location);
+ if surface == MapElement::Wall {
+ animations.push(AnimationInstruction::FakeOutMove(
+ entity_to_update_key,
+ direction,
+ self.map
+ .get(entity_to_update_key)
+ .and_then(|e| e.fake_out_wall_effect()),
+ ));
+ return (false, hero_has_died, win_has_triggered);
+ }
+
+ let (can_move, explicit_stay_put, fake_out_effect) = {
+ let mut can_move = true;
+ let mut explicit_stay_put = false;
+ let mut fake_out_effect = None;
+
+ let move_attempt_resolutions: Vec<_> = self
+ .whats_at(desired_location)
+ .filter(|(k, _)| *k != entity_to_update_key)
+ .map(|(key, other_entity)| (key, resolve_move(entity_to_update, other_entity)))
+ .collect();
+
+ for (other_entity_key, move_resolution) in move_attempt_resolutions {
+ match move_resolution {
+ MoveAttemptResolution::KillDie => {
+ hero_has_died |= self.kill_entity(other_entity_key, animations);
+ hero_has_died |= self.kill_entity(entity_to_update_key, animations);
+ can_move = false;
+ }
+ MoveAttemptResolution::Kill => {
+ hero_has_died |= self.kill_entity(other_entity_key, animations);
+ fake_out_effect = self
+ .map
+ .get(entity_to_update_key)
+ .and_then(|x| x.kill_sound_effect());
+ can_move = false;
+ }
+ MoveAttemptResolution::Die => {
+ hero_has_died |= self.kill_entity(entity_to_update_key, animations);
+ can_move = false;
+ }
+ MoveAttemptResolution::CoExist => {}
+ MoveAttemptResolution::StayPut => {
+ can_move = false;
+ explicit_stay_put = true;
+ }
+ MoveAttemptResolution::AttemptPush => {
+ let depth = depth - 1;
+ if depth >= 0 {
+ let (can_move_result, hero_has_died_result, win_has_triggered_result) =
+ self.attempt_move_in_direction(
+ map,
+ animations,
+ entities_to_try_update,
+ other_entity_key,
+ direction,
+ depth,
+ );
+
+ if !can_move_result {
+ can_move = false;
+ }
+ hero_has_died |= hero_has_died_result;
+ win_has_triggered |= win_has_triggered_result;
+ } else {
+ can_move = false;
+ }
+ }
+ }
+ }
+
+ (can_move, explicit_stay_put, fake_out_effect)
+ };
+
+ if can_move {
+ if let Some(e) = self.map.get_mut(entity_to_update_key) {
+ e.location = desired_location;
+ }
+ let Some(entity_to_update) = self.map.get(entity_to_update_key) else {
+ return (can_move, hero_has_died, win_has_triggered);
+ };
+
+ animations.push(AnimationInstruction::Move(
+ entity_to_update_key,
+ desired_location,
+ entity_to_update.move_effect(),
+ ));
+
+ let overlap_resolutions: Vec<_> = self
+ .whats_at(desired_location)
+ .filter(|(k, _)| *k != entity_to_update_key)
+ .map(|(key, other_entity)| (key, resolve_overlap(entity_to_update, other_entity)))
+ .collect();
+
+ for (other_entity_key, move_resolution) in overlap_resolutions {
+ match move_resolution {
+ OverlapResolution::Pickup => {
+ animations.push(AnimationInstruction::Attach(
+ entity_to_update_key,
+ other_entity_key,
+ self.map
+ .get(other_entity_key)
+ .and_then(|x| x.pickup_sound_effect()),
+ ));
+ let other = self.map.remove(other_entity_key).unwrap();
+
+ if let Some((location, dropped)) = self
+ .map
+ .get_mut(entity_to_update_key)
+ .and_then(|x| x.pickup(other.entity).map(|y| (x.location, y)))
+ {
+ let new_key = self.map.insert(Entity {
+ location,
+ entity: dropped,
+ });
+
+ animations.push(AnimationInstruction::Detatch(
+ entity_to_update_key,
+ new_key,
+ self.map.get(new_key).and_then(|x| x.drop_effect()),
+ ));
+ }
+ }
+ OverlapResolution::CoExist => {}
+ OverlapResolution::Win => {
+ win_has_triggered = true;
+ }
+ OverlapResolution::ToggleSystem(system) => {
+ for (k, e) in self.map.iter_mut() {
+ if let Some(change) = e.switch(system) {
+ animations.push(AnimationInstruction::Change(
+ k,
+ change,
+ e.change_effect(),
+ ));
+ }
+ }
+ }
+ OverlapResolution::Die => {
+ hero_has_died |= self.kill_entity(entity_to_update_key, animations);
+ break;
+ }
+ OverlapResolution::MoveAgain => {
+ entities_to_try_update
+ .push_front((entity_to_update_key, Action::Direction(direction)));
+ }
+ }
+ }
+ } else {
+ animations.push(AnimationInstruction::FakeOutMove(
+ entity_to_update_key,
+ direction,
+ if explicit_stay_put {
+ self.map
+ .get(entity_to_update_key)
+ .and_then(|e| e.fake_out_wall_effect())
+ } else {
+ fake_out_effect.or_else(|| {
+ self.map
+ .get(entity_to_update_key)
+ .and_then(|e| e.fake_out_effect())
+ })
+ },
+ ));
+ }
+
+ (can_move, hero_has_died, win_has_triggered)
+ }
+
pub fn tick(&mut self, map: &Map, hero: Action) -> (Outcome, Vec) {
let mut hero_has_died = false;
let mut win_has_triggered = false;
@@ -117,169 +306,21 @@ impl EntityMap {
}
}
- let Some(entity_to_update) = self.map.get(entity_to_update_key) else {
- continue;
- };
-
- let entity_location = entity_to_update.location;
-
- let desired_location = entity_location + direction.into();
- let surface = map.get(desired_location);
- if surface == MapElement::Wall {
- animations.push(AnimationInstruction::FakeOutMove(
+ let (_, hero_has_died_result, win_has_triggered_result) = self
+ .attempt_move_in_direction(
+ map,
+ &mut animations,
+ &mut entities_to_try_update,
entity_to_update_key,
direction,
self.map
.get(entity_to_update_key)
- .and_then(|e| e.fake_out_wall_effect()),
- ));
- continue;
- }
+ .and_then(|e| e.push_depth())
+ .unwrap_or(0),
+ );
- let (can_move, explicit_stay_put, fake_out_effect) = {
- let mut can_move = true;
- let mut explicit_stay_put = false;
- let mut fake_out_effect = None;
-
- let move_attempt_resolutions: Vec<_> = self
- .whats_at(desired_location)
- .filter(|(k, _)| *k != entity_to_update_key)
- .map(|(key, other_entity)| {
- (key, resolve_move(entity_to_update, other_entity))
- })
- .collect();
-
- for (other_entity_key, move_resolution) in move_attempt_resolutions {
- match move_resolution {
- MoveAttemptResolution::KillDie => {
- hero_has_died |=
- self.kill_entity(other_entity_key, &mut animations);
- hero_has_died |=
- self.kill_entity(entity_to_update_key, &mut animations);
- can_move = false;
- }
- MoveAttemptResolution::Kill => {
- hero_has_died |=
- self.kill_entity(other_entity_key, &mut animations);
- fake_out_effect = self
- .map
- .get(entity_to_update_key)
- .and_then(|x| x.kill_sound_effect());
- can_move = false;
- }
- MoveAttemptResolution::Die => {
- hero_has_died |=
- self.kill_entity(entity_to_update_key, &mut animations);
- can_move = false;
- }
- MoveAttemptResolution::CoExist => {}
- MoveAttemptResolution::StayPut => {
- can_move = false;
- explicit_stay_put = true;
- }
- MoveAttemptResolution::AttemptPush => todo!(),
- }
- }
-
- (can_move, explicit_stay_put, fake_out_effect)
- };
-
- if can_move {
- if let Some(e) = self.map.get_mut(entity_to_update_key) {
- e.location = desired_location;
- }
- let Some(entity_to_update) = self.map.get(entity_to_update_key) else {
- continue;
- };
-
- animations.push(AnimationInstruction::Move(
- entity_to_update_key,
- desired_location,
- entity_to_update.move_effect(),
- ));
-
- let overlap_resolutions: Vec<_> = self
- .whats_at(desired_location)
- .filter(|(k, _)| *k != entity_to_update_key)
- .map(|(key, other_entity)| {
- (key, resolve_overlap(entity_to_update, other_entity))
- })
- .collect();
-
- for (other_entity_key, move_resolution) in overlap_resolutions {
- match move_resolution {
- OverlapResolution::Pickup => {
- animations.push(AnimationInstruction::Attach(
- entity_to_update_key,
- other_entity_key,
- self.map
- .get(other_entity_key)
- .and_then(|x| x.pickup_sound_effect()),
- ));
- let other = self.map.remove(other_entity_key).unwrap();
-
- if let Some((location, dropped)) =
- self.map.get_mut(entity_to_update_key).and_then(|x| {
- x.pickup(other.entity).map(|y| (x.location, y))
- })
- {
- let new_key = self.map.insert(Entity {
- location,
- entity: dropped,
- });
-
- animations.push(AnimationInstruction::Detatch(
- entity_to_update_key,
- new_key,
- self.map.get(new_key).and_then(|x| x.drop_effect()),
- ));
- }
- }
- OverlapResolution::CoExist => {}
- OverlapResolution::Win => {
- win_has_triggered = true;
- }
- OverlapResolution::ToggleSystem(system) => {
- for (k, e) in self.map.iter_mut() {
- if let Some(change) = e.switch(system) {
- animations.push(AnimationInstruction::Change(
- k,
- change,
- e.change_effect(),
- ));
- }
- }
- }
- OverlapResolution::Die => {
- hero_has_died |=
- self.kill_entity(entity_to_update_key, &mut animations);
- break;
- }
- OverlapResolution::MoveAgain => {
- entities_to_try_update.push_front((
- entity_to_update_key,
- Action::Direction(direction),
- ));
- }
- }
- }
- } else {
- animations.push(AnimationInstruction::FakeOutMove(
- entity_to_update_key,
- direction,
- if explicit_stay_put {
- self.map
- .get(entity_to_update_key)
- .and_then(|e| e.fake_out_wall_effect())
- } else {
- fake_out_effect.or_else(|| {
- self.map
- .get(entity_to_update_key)
- .and_then(|e| e.fake_out_effect())
- })
- },
- ));
- }
+ hero_has_died |= hero_has_died_result;
+ win_has_triggered |= win_has_triggered_result;
}
}
}
@@ -331,6 +372,7 @@ fn resolve_overlap(me: &Entity, other: &Entity) -> OverlapResolution {
match (&me.entity, &other.entity) {
(EntityType::Hero(_), EntityType::Stairs) => OverlapResolution::Win,
(_, EntityType::Item(_)) => OverlapResolution::Pickup,
+ (EntityType::MovableBlock, EntityType::Spikes(_)) => OverlapResolution::CoExist,
(_, EntityType::Spikes(switch)) => resolve_spikes(switch),
(_, EntityType::Switch(switch)) => OverlapResolution::ToggleSystem(switch.system),
(_, EntityType::Enemy(_) | EntityType::Hero(_)) => OverlapResolution::Die,
@@ -408,6 +450,7 @@ fn resolve_move(mover: &Entity, into: &Entity) -> MoveAttemptResolution {
holding_door_resolve(squid.holding.as_deref())
}
(_, EntityType::Door) => MoveAttemptResolution::StayPut,
+ (_, EntityType::MovableBlock) => MoveAttemptResolution::AttemptPush,
(_, _) => MoveAttemptResolution::CoExist,
}
}
@@ -439,6 +482,7 @@ pub enum EntityType {
Switch(Switchable),
Spikes(Switchable),
Ice,
+ MovableBlock,
}
#[derive(Debug)]
@@ -457,6 +501,7 @@ pub enum Enemy {
pub enum Item {
Sword,
Key,
+ Glove,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
@@ -552,6 +597,14 @@ impl Entity {
}
}
+ fn push_depth(&self) -> Option {
+ if matches!(self.holding(), Some(&EntityType::Item(Item::Glove))) {
+ Some(i32::MAX)
+ } else {
+ Some(1)
+ }
+ }
+
fn holding(&self) -> Option<&EntityType> {
match &self.entity {
EntityType::Hero(hero) => hero.holding.as_deref(),
@@ -721,6 +774,8 @@ impl From for EntityType {
holding: None,
})),
level::Item::Ice => EntityType::Ice,
+ level::Item::MovableBlock => EntityType::MovableBlock,
+ level::Item::Glove => EntityType::Item(Item::Glove),
}
}
}
diff --git a/examples/the-dungeon-puzzlers-lament/src/level.rs b/examples/the-dungeon-puzzlers-lament/src/level.rs
index 48da44cb..2825d7ba 100644
--- a/examples/the-dungeon-puzzlers-lament/src/level.rs
+++ b/examples/the-dungeon-puzzlers-lament/src/level.rs
@@ -19,6 +19,8 @@ pub enum Item {
SquidUp,
SquidDown,
Ice,
+ MovableBlock,
+ Glove,
}
impl Item {
@@ -39,6 +41,8 @@ impl Item {
Item::SquidUp => resources::SQUID_UP_SHADOW,
Item::SquidDown => resources::SQUID_DOWN_SHADOW,
Item::Ice => resources::ICE,
+ Item::MovableBlock => resources::BLOCK,
+ Item::Glove => resources::GLOVE,
}
}
@@ -59,6 +63,8 @@ impl Item {
Item::SquidUp => resources::SQUID_UP,
Item::SquidDown => resources::SQUID_DOWN,
Item::Ice => resources::ICE,
+ Item::MovableBlock => resources::BLOCK,
+ Item::Glove => resources::GLOVE,
}
}
@@ -82,6 +88,8 @@ impl Item {
Item::SquidUp => STANDARD,
Item::SquidDown => STANDARD,
Item::Ice => ZERO,
+ Item::MovableBlock => ZERO,
+ Item::Glove => STANDARD,
}
}
}
diff --git a/examples/the-dungeon-puzzlers-lament/src/resources.rs b/examples/the-dungeon-puzzlers-lament/src/resources.rs
index af80b31a..afcc1646 100644
--- a/examples/the-dungeon-puzzlers-lament/src/resources.rs
+++ b/examples/the-dungeon-puzzlers-lament/src/resources.rs
@@ -50,6 +50,8 @@ named_tag!(
SQUID_UP_SHADOW,
SQUID_DOWN_SHADOW,
ICE,
+ BLOCK,
+ GLOVE,
]
);