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 @@ + + + + + + + + + + +0,1,3,2,6,9,0,0,0,0,0, +0,46,15,14,13,3221225500,4,6,7,7,9, +0,46,12,11,15,16,11,13,15,14,38, +0,46,12,13,14,16,17,14,11,12,18, +0,10,13,16,15,14,12,14,13,15,18, +0,46,16,17,17,13,15,14,16,15,18, +0,19,28,17,11,16,12,15,17,15,38, +0,0,19,20,28,17,17,15,17,2147483676,27, +0,0,0,0,19,21,25,20,23,27,0, +0,0,0,0,0,0,0,0,0,0,0 + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,1,4,3,9,0,0,0, +0,0,0,1,1073741852,16,15,3221225500,8,9,0, +0,0,0,46,12,15,13,17,11,38,0, +0,0,0,19,24,28,17,2147483676,25,27,0, +0,0,0,0,0,10,13,47,0,0,0, +0,0,0,0,0,19,20,27,0,0,0, +0,0,0,0,0,0,0,0,0,0,0 + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0, +0,0,1,6,2,8,8,9,0,0,0, +0,1,1073741852,17,12,16,12,3221225500,9,0,0, +0,46,11,16,12,17,17,12,38,0,0, +0,46,17,16,12,16,11,14,38,0,0, +0,19,26,28,14,12,14,15,38,0,0, +0,0,0,19,24,28,11,2147483676,27,0,0, +0,0,0,0,0,19,23,27,0,0,0, +0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0 + + + + + + + + + + + + + + + + + + + + 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, ] );