From a97248cd68abe6abd5dccc3ce7cbdf68fe9b1de7 Mon Sep 17 00:00:00 2001 From: Corwin Date: Fri, 1 Sep 2023 00:42:43 +0100 Subject: [PATCH] add hole and rotating enemy --- examples/the-dungeon-puzzlers-lament/build.rs | 17 ++ .../gfx/sprites16x16.aseprite | Bin 15452 -> 15618 bytes .../maps/levels/hole_introduction.tmx | 34 +++ .../maps/levels/rotator_1.tmx | 43 ++++ .../src/game/simulation/entity.rs | 210 ++++++++++++++---- .../the-dungeon-puzzlers-lament/src/level.rs | 20 ++ .../src/resources.rs | 5 + 7 files changed, 286 insertions(+), 43 deletions(-) create mode 100644 examples/the-dungeon-puzzlers-lament/maps/levels/hole_introduction.tmx create mode 100644 examples/the-dungeon-puzzlers-lament/maps/levels/rotator_1.tmx diff --git a/examples/the-dungeon-puzzlers-lament/build.rs b/examples/the-dungeon-puzzlers-lament/build.rs index edeb23c1..805a7839 100644 --- a/examples/the-dungeon-puzzlers-lament/build.rs +++ b/examples/the-dungeon-puzzlers-lament/build.rs @@ -44,6 +44,8 @@ const LEVEL_NAMES: &[&str] = &[ "another_ice_2", "another_ice_3", "another_ice_4", + "hole_introduction", + "rotator_1", ]; fn main() { @@ -131,6 +133,11 @@ enum Entity { MovableBlock, Glove, Teleporter, + Hole, + RotatorUp, + RotatorDown, + RotatorLeft, + RotatorRight, } impl FromStr for Entity { @@ -158,6 +165,11 @@ impl FromStr for Entity { "BLOCK" => MovableBlock, "GLOVE" => Glove, "TELEPORTER" => Teleporter, + "HOLE" => Hole, + "ROTATOR_LEFT" => RotatorLeft, + "ROTATOR_RIGHT" => RotatorRight, + "ROTATOR_UP" => RotatorUp, + "ROTATOR_DOWN" => RotatorDown, _ => return Err(()), }) } @@ -186,6 +198,11 @@ impl quote::ToTokens for Entity { MovableBlock => quote!(Item::MovableBlock), Glove => quote!(Item::Glove), Teleporter => quote!(Item::Teleporter), + Hole => quote!(Item::Hole), + RotatorUp => quote!(Item::RotatorUp), + RotatorDown => quote!(Item::RotatorDown), + RotatorLeft => quote!(Item::RotatorLeft), + RotatorRight => quote!(Item::RotatorRight), }) } } diff --git a/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite index 304e937bf7f5136dede2f7390687a95f4630557e..0cfa162ed2a1730b1d1332280a669b8e8d666cb7 100644 GIT binary patch delta 674 zcmcap(Nx9GWXr(tV5$2=_DWuMHU@@YA2}FO7#J8hCiZ@u{EjV#-HC;PK|(=wb1UOY z=6WXvC!ib>U}5m^_i=S*a7GdXGI<$-{6ic={Db0yJl#D)To_zX<+~uyTI~9mo6+kQ|hW|`J3Jmyx z4y%}xoZ!%)ZoT(^vw_ik#(tYkJLaq~kvM!sVe`eOg)fh`pS;?>Zn{YAjxYQzcbB|c z8fFn}-S=T;tG|7f#e(I!G4GD0sp;7gFN>l`E)B>s_OGT6P#fbJDo z9apk-$CZVvSyvuz)ij(Q^8FF>mM&%iHlU{~7KewUx!Ikto8eBj7rAoi%7V)~&aj?5 TY_RoiH=jqZHqczrMHPzyz)Z7^ delta 506 zcmZpwx>La(W5dAkV5!qY_DWt;b_RxDA2k?K7#J8dCiZ@0-@wekAfceJ`61&<=6XJ$ osDc6$kOYGNbm!FrZ5=@C8G!Z=r1cEWRIq+>9b?R988#tP0EntQJOBUy diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/hole_introduction.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/hole_introduction.tmx new file mode 100644 index 00000000..8da8ed63 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/hole_introduction.tmx @@ -0,0 +1,34 @@ + + + + + + + + + + +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,6,5,2,2,6,4,8,9,0, +0,10,17,17,12,15,13,17,17,38,0, +0,19,20,20,20,25,20,22,21,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,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/maps/levels/rotator_1.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/rotator_1.tmx new file mode 100644 index 00000000..3847f552 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/rotator_1.tmx @@ -0,0 +1,43 @@ + + + + + + + + + + +1,6,7,7,4,5,3,2,4,7,9, +37,12,14,13,12,15,12,16,14,13,47, +19,22,26,26,26,25,23,22,24,26,27, +1,7,4,7,2,2,8,2,2,5,9, +10,11,14,13,15,11,14,14,12,17,38, +46,12,12,14,12,16,17,15,17,13,18, +10,14,17,14,17,14,16,15,16,11,38, +10,15,16,16,11,11,11,13,16,15,18, +10,12,14,16,14,12,14,13,12,17,47, +19,23,24,26,22,20,24,25,26,25,27 + + + + + + + + + + + + + + + + + + + + + + + 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 cf173c29..1b4d5421 100644 --- a/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs +++ b/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs @@ -223,32 +223,82 @@ impl EntityMap { desired_location, move_effect, )); - } else if explicit_stay_put + } else if !should_die_later + && explicit_stay_put && can_turn_around && self.map.get(entity_to_update_key).map(|e| e.turns_around()) == Some(true) { - if let Some((Some(change), change_effect)) = self + if let Some(directions_to_attempt) = self .map - .get_mut(entity_to_update_key) - .map(|e| (e.change_direction(), e.change_effect())) + .get(entity_to_update_key) + .and_then(|e| e.directions_to_attempt()) { - animations.push(AnimationInstruction::PriorityChange( + #[allow(clippy::indexing_slicing)] + for &direction_to_attempt in directions_to_attempt { + let (can_move, action) = self.attempt_move_in_direction( + map, + animations, + entity_to_update_key, + direction_to_attempt, + false, + push_depth, + entities_that_have_moved, + ); + + if can_move.0 { + if let Some((Some(change), change_effect)) = self + .map + .get_mut(entity_to_update_key) + .map(|e| (e.change_direction(direction_to_attempt), e.change_effect())) + { + animations.push(AnimationInstruction::PriorityChange( + entity_to_update_key, + change, + change_effect, + )); + } + + return ( + can_move, + ActionResult::new( + hero_has_died || action.hero_has_died, + win_has_triggered || action.win_has_triggered, + ), + ); + } + } + + let last_direction_attempt = *directions_to_attempt.last().unwrap(); + + animations.push(AnimationInstruction::FakeOutMove( entity_to_update_key, - change, - change_effect, + last_direction_attempt, + self.map + .get(entity_to_update_key) + .and_then(|e| e.fake_out_wall_effect()), )); - return self.attempt_move_in_direction( - map, - animations, - entity_to_update_key, - -direction, - false, - push_depth, - entities_that_have_moved, + if let Some((Some(change), change_effect)) = + self.map.get_mut(entity_to_update_key).map(|e| { + ( + e.change_direction(last_direction_attempt), + e.change_effect(), + ) + }) + { + animations.push(AnimationInstruction::PriorityChange( + entity_to_update_key, + change, + change_effect, + )); + } + + return ( + HasMoved(false), + ActionResult::new(hero_has_died, win_has_triggered), ); } - } else { + } else if can_turn_around { animations.push(AnimationInstruction::FakeOutMove( entity_to_update_key, direction, @@ -348,6 +398,11 @@ impl EntityMap { hero_has_died |= self.kill_entity(entity_to_update_key, animations); break; } + OverlapResolution::KillDie => { + hero_has_died |= self.kill_entity(other_entity_key, animations); + hero_has_died |= self.kill_entity(entity_to_update_key, animations); + break; + } OverlapResolution::MoveAgain => { should_move_again = true; } @@ -468,6 +523,7 @@ enum OverlapResolution { Win, ToggleSystem(SwitchSystem), Die, + KillDie, MoveAgain, Teleport, } @@ -483,7 +539,7 @@ fn resolve_spikes(switable: &Switchable) -> OverlapResolution { fn resolve_overlap(me: &Entity, other: &Entity) -> OverlapResolution { match (&me.entity, &other.entity) { (EntityType::Hero(_), EntityType::Stairs) => OverlapResolution::Win, - (EntityType::Hero(_) | EntityType::Enemy(Enemy::Squid(_)), EntityType::Item(_)) => { + (EntityType::Hero(_) | EntityType::Enemy(Enemy::Moving(_)), EntityType::Item(_)) => { OverlapResolution::Pickup } (EntityType::MovableBlock, EntityType::Spikes(_)) => OverlapResolution::CoExist, @@ -492,6 +548,8 @@ fn resolve_overlap(me: &Entity, other: &Entity) -> OverlapResolution { (_, EntityType::Enemy(_)) => OverlapResolution::Die, (_, EntityType::Ice) => OverlapResolution::MoveAgain, (_, EntityType::Teleporter) => OverlapResolution::Teleport, + (EntityType::MovableBlock, EntityType::Hole) => OverlapResolution::KillDie, + (_, EntityType::Hole) => OverlapResolution::Die, _ => OverlapResolution::CoExist, } @@ -504,18 +562,18 @@ fn holding_attack_resolve( ) -> MoveAttemptResolution { match (holding, &other.entity) { (Some(&EntityType::Item(Item::Sword)), _) => MoveAttemptResolution::Kill, - (_, EntityType::Enemy(Enemy::Squid(squid))) => { + (_, EntityType::Enemy(Enemy::Moving(squid))) => { hero_walk_into_squid_interaction(squid, direction) } _ => MoveAttemptResolution::CoExist, } } -fn squid_holding_attack_resolve(me: &Squid, other: &Entity) -> MoveAttemptResolution { +fn squid_holding_attack_resolve(me: &Moving, other: &Entity) -> MoveAttemptResolution { match (me.holding.as_deref(), &other.entity, other.holding()) { ( Some(&EntityType::Item(Item::Sword)), - EntityType::Enemy(Enemy::Squid(squid)), + EntityType::Enemy(Enemy::Moving(squid)), Some(&EntityType::Item(Item::Sword)), ) => { if squid.direction == -me.direction { @@ -527,7 +585,7 @@ fn squid_holding_attack_resolve(me: &Squid, other: &Entity) -> MoveAttemptResolu (Some(&EntityType::Item(Item::Sword)), EntityType::Enemy(_), None) => { MoveAttemptResolution::Kill } - (_, EntityType::Enemy(Enemy::Squid(squid)), Some(&EntityType::Item(Item::Sword))) => { + (_, EntityType::Enemy(Enemy::Moving(squid)), Some(&EntityType::Item(Item::Sword))) => { if squid.direction == -me.direction { MoveAttemptResolution::Die } else { @@ -555,7 +613,7 @@ fn switch_door_resolve(door: &Switchable) -> MoveAttemptResolution { } } -fn hero_walk_into_squid_interaction(squid: &Squid, direction: Direction) -> MoveAttemptResolution { +fn hero_walk_into_squid_interaction(squid: &Moving, direction: Direction) -> MoveAttemptResolution { if direction == -squid.direction { MoveAttemptResolution::DieLater } else { @@ -569,14 +627,14 @@ fn resolve_move(mover: &Entity, into: &Entity, direction: Direction) -> MoveAtte holding_attack_resolve(hero.holding.as_deref(), into, direction) } (EntityType::Hero(hero), EntityType::Door) => holding_door_resolve(hero.holding.as_deref()), - (EntityType::Enemy(Enemy::Squid(squid)), EntityType::Hero(_) | EntityType::Enemy(_)) => { + (EntityType::Enemy(Enemy::Moving(squid)), EntityType::Hero(_) | EntityType::Enemy(_)) => { squid_holding_attack_resolve(squid, into) } (EntityType::Enemy(_), EntityType::Hero(_) | EntityType::Enemy(_)) => { MoveAttemptResolution::Kill } (_, EntityType::SwitchedDoor(door)) => switch_door_resolve(door), - (EntityType::Enemy(Enemy::Squid(squid)), EntityType::Door) => { + (EntityType::Enemy(Enemy::Moving(squid)), EntityType::Door) => { holding_door_resolve(squid.holding.as_deref()) } (_, EntityType::Door) => MoveAttemptResolution::StayPut, @@ -611,6 +669,7 @@ pub enum EntityType { Enemy(Enemy), Stairs, Door, + Hole, SwitchedDoor(Switchable), Switch(Switchable), Spikes(Switchable), @@ -620,15 +679,22 @@ pub enum EntityType { } #[derive(Debug)] -pub struct Squid { +pub struct Moving { direction: Direction, holding: Option>, + movable_enemy_type: MovableEnemyType, +} + +#[derive(Debug, PartialEq, Eq)] +enum MovableEnemyType { + Squid, + Rotator, } #[derive(Debug)] pub enum Enemy { Slime, - Squid(Squid), + Moving(Moving), } #[derive(Debug)] @@ -686,19 +752,19 @@ impl Entity { fn desired_action(&self, hero_action: Action) -> Action { match &self.entity { EntityType::Hero(_) => hero_action, - EntityType::Enemy(Enemy::Squid(squid)) => Action::Direction(squid.direction), + EntityType::Enemy(Enemy::Moving(squid)) => Action::Direction(squid.direction), _ => Action::Nothing, } } fn turns_around(&self) -> bool { - matches!(self.entity, EntityType::Enemy(Enemy::Squid(_))) + matches!(self.entity, EntityType::Enemy(Enemy::Moving(_))) } fn pickup(&mut self, item: EntityType) -> Option { let holding = match &mut self.entity { EntityType::Hero(hero) => &mut hero.holding, - EntityType::Enemy(Enemy::Squid(squid)) => &mut squid.holding, + EntityType::Enemy(Enemy::Moving(squid)) => &mut squid.holding, _ => panic!("this entity can't pick up things"), }; @@ -709,7 +775,7 @@ impl Entity { fn take_holding(&mut self) -> Option { match &mut self.entity { EntityType::Hero(hero) => hero.holding.take().map(|x| *x), - EntityType::Enemy(Enemy::Squid(squid)) => squid.holding.take().map(|x| *x), + EntityType::Enemy(Enemy::Moving(squid)) => squid.holding.take().map(|x| *x), _ => None, } } @@ -719,7 +785,7 @@ impl Entity { Some(i32::MAX) } else if matches!( self.entity, - EntityType::Hero(_) | EntityType::Enemy(Enemy::Squid(_)) + EntityType::Hero(_) | EntityType::Enemy(Enemy::Moving(_)) ) { Some(1) } else { @@ -730,7 +796,7 @@ impl Entity { fn holding(&self) -> Option<&EntityType> { match &self.entity { EntityType::Hero(hero) => hero.holding.as_deref(), - EntityType::Enemy(Enemy::Squid(squid)) => squid.holding.as_deref(), + EntityType::Enemy(Enemy::Moving(squid)) => squid.holding.as_deref(), _ => None, } } @@ -740,7 +806,11 @@ impl Entity { EntityType::Hero(_) => Some(SoundEffect::HeroDie), EntityType::Door => Some(SoundEffect::DoorOpen), EntityType::Enemy(Enemy::Slime) => Some(SoundEffect::SlimeDie), - EntityType::Enemy(Enemy::Squid(_)) => Some(SoundEffect::SquidDie), + EntityType::Enemy(Enemy::Moving(e)) + if e.movable_enemy_type == MovableEnemyType::Squid => + { + Some(SoundEffect::SquidDie) + } _ => None, } } @@ -792,15 +862,46 @@ impl Entity { None } - fn change_direction(&mut self) -> Option { - match &mut self.entity { - EntityType::Enemy(Enemy::Squid(squid)) => { - squid.direction = -squid.direction; + fn directions_to_attempt(&self) -> Option<&'static [Direction]> { + match &self.entity { + EntityType::Enemy(Enemy::Moving(moving_type)) => { + Some(match moving_type.movable_enemy_type { + MovableEnemyType::Squid => match moving_type.direction { + Direction::Up => &[Direction::Down], + Direction::Down => &[Direction::Up], + _ => panic!("Left and right movements are not valid for a squid"), + }, + MovableEnemyType::Rotator => match moving_type.direction { + Direction::Up => &[Direction::Right, Direction::Left, Direction::Down], + Direction::Down => &[Direction::Left, Direction::Right, Direction::Up], + Direction::Left => &[Direction::Up, Direction::Down, Direction::Right], + Direction::Right => &[Direction::Down, Direction::Up, Direction::Left], + }, + }) + } + _ => None, + } + } - if squid.direction == Direction::Up { - Some(level::Item::SquidUp) - } else { - Some(level::Item::SquidDown) + fn change_direction(&mut self, direction: Direction) -> Option { + match &mut self.entity { + EntityType::Enemy(Enemy::Moving(moving)) => { + moving.direction = direction; + + match moving.movable_enemy_type { + MovableEnemyType::Squid => { + if direction == Direction::Up { + Some(level::Item::SquidUp) + } else { + Some(level::Item::SquidDown) + } + } + MovableEnemyType::Rotator => Some(match direction { + Direction::Up => level::Item::RotatorUp, + Direction::Down => level::Item::RotatorDown, + Direction::Left => level::Item::RotatorLeft, + Direction::Right => level::Item::RotatorRight, + }), } } _ => None, @@ -887,18 +988,41 @@ impl From for EntityType { system: SwitchSystem(0), active: false, }), - level::Item::SquidUp => EntityType::Enemy(Enemy::Squid(Squid { + level::Item::SquidUp => EntityType::Enemy(Enemy::Moving(Moving { direction: Direction::Up, holding: None, + movable_enemy_type: MovableEnemyType::Squid, })), - level::Item::SquidDown => EntityType::Enemy(Enemy::Squid(Squid { + level::Item::SquidDown => EntityType::Enemy(Enemy::Moving(Moving { direction: Direction::Down, holding: None, + movable_enemy_type: MovableEnemyType::Squid, })), level::Item::Ice => EntityType::Ice, level::Item::MovableBlock => EntityType::MovableBlock, level::Item::Glove => EntityType::Item(Item::Glove), level::Item::Teleporter => EntityType::Teleporter, + level::Item::Hole => EntityType::Hole, + level::Item::RotatorRight => EntityType::Enemy(Enemy::Moving(Moving { + direction: Direction::Right, + holding: None, + movable_enemy_type: MovableEnemyType::Rotator, + })), + level::Item::RotatorLeft => EntityType::Enemy(Enemy::Moving(Moving { + direction: Direction::Left, + holding: None, + movable_enemy_type: MovableEnemyType::Rotator, + })), + level::Item::RotatorUp => EntityType::Enemy(Enemy::Moving(Moving { + direction: Direction::Up, + holding: None, + movable_enemy_type: MovableEnemyType::Rotator, + })), + level::Item::RotatorDown => EntityType::Enemy(Enemy::Moving(Moving { + direction: Direction::Down, + holding: None, + movable_enemy_type: MovableEnemyType::Rotator, + })), } } } diff --git a/examples/the-dungeon-puzzlers-lament/src/level.rs b/examples/the-dungeon-puzzlers-lament/src/level.rs index f87f1723..f883d59d 100644 --- a/examples/the-dungeon-puzzlers-lament/src/level.rs +++ b/examples/the-dungeon-puzzlers-lament/src/level.rs @@ -22,6 +22,11 @@ pub enum Item { MovableBlock, Glove, Teleporter, + Hole, + RotatorRight, + RotatorLeft, + RotatorUp, + RotatorDown, } impl Item { @@ -45,6 +50,11 @@ impl Item { Item::MovableBlock => resources::ROCK_SHADOW, Item::Glove => resources::POW_GLOVE_SHADOW, Item::Teleporter => resources::TELEPORTER_SHADOW, + Item::Hole => resources::HOLE, + Item::RotatorRight => resources::ROTATOR_RIGHT, + Item::RotatorLeft => resources::ROTATOR_LEFT, + Item::RotatorUp => resources::ROTATOR_UP, + Item::RotatorDown => resources::ROTATOR_DOWN, } } @@ -68,6 +78,11 @@ impl Item { Item::MovableBlock => resources::ROCK, Item::Glove => resources::POW_GLOVE, Item::Teleporter => resources::TELEPORTER, + Item::Hole => resources::HOLE, + Item::RotatorRight => resources::ROTATOR_RIGHT, + Item::RotatorLeft => resources::ROTATOR_LEFT, + Item::RotatorUp => resources::ROTATOR_UP, + Item::RotatorDown => resources::ROTATOR_DOWN, } } @@ -94,6 +109,11 @@ impl Item { Item::MovableBlock => ZERO, Item::Glove => STANDARD, Item::Teleporter => ZERO, + Item::Hole => ZERO, + Item::RotatorRight => STANDARD, + Item::RotatorLeft => STANDARD, + Item::RotatorUp => STANDARD, + Item::RotatorDown => STANDARD, } } } diff --git a/examples/the-dungeon-puzzlers-lament/src/resources.rs b/examples/the-dungeon-puzzlers-lament/src/resources.rs index 69672669..a4dfef88 100644 --- a/examples/the-dungeon-puzzlers-lament/src/resources.rs +++ b/examples/the-dungeon-puzzlers-lament/src/resources.rs @@ -56,6 +56,11 @@ named_tag!( POW_GLOVE_SHADOW, TELEPORTER, TELEPORTER_SHADOW, + HOLE, + ROTATOR_RIGHT, + ROTATOR_UP, + ROTATOR_LEFT, + ROTATOR_DOWN, ] );