add block

This commit is contained in:
Corwin 2023-08-27 19:49:37 +01:00
parent a0ab3e9fb4
commit 36603d5446
No known key found for this signature in database
8 changed files with 349 additions and 177 deletions

View file

@ -10,25 +10,9 @@ use proc_macro2::TokenStream;
const LEVEL_NAMES: &[&str] = &[ const LEVEL_NAMES: &[&str] = &[
"a_familiar_sight", "a_familiar_sight",
"level1", "block_push_1",
"level2", "block_push_2",
"level3", "block_push_3",
"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",
]; ];
fn main() { fn main() {
@ -102,6 +86,8 @@ enum Entity {
SquidUp, SquidUp,
SquidDown, SquidDown,
Ice, Ice,
MovableBlock,
Glove,
} }
impl FromStr for Entity { impl FromStr for Entity {
@ -126,6 +112,8 @@ impl FromStr for Entity {
"SQUID_UP" => SquidUp, "SQUID_UP" => SquidUp,
"SQUID_DOWN" => SquidDown, "SQUID_DOWN" => SquidDown,
"ICE" => Ice, "ICE" => Ice,
"BLOCK" => MovableBlock,
"GLOVE" => Glove,
_ => return Err(()), _ => return Err(()),
}) })
} }
@ -151,6 +139,8 @@ impl quote::ToTokens for Entity {
SquidUp => quote!(Item::SquidUp), SquidUp => quote!(Item::SquidUp),
SquidDown => quote!(Item::SquidDown), SquidDown => quote!(Item::SquidDown),
Ice => quote!(Item::Ice), Ice => quote!(Item::Ice),
MovableBlock => quote!(Item::MovableBlock),
Glove => quote!(Item::Glove),
}) })
} }
} }

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="11" height="10" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="7">
<properties>
<property name="DIRECTIONS" value="LLLLLLLLUUUU"/>
<property name="ITEMS" value="BLOCK,BLOCK"/>
<property name="NAME" value="Less familiar, but exciting nonetheless"/>
</properties>
<tileset firstgid="1" source="../level16.tsx"/>
<layer id="1" name="Tile Layer 1" width="11" height="10">
<data encoding="csv">
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
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="3" name="HERO" x="119.458" y="71.7665">
<point/>
</object>
<object id="4" name="SWITCH" x="71.308" y="71.5373">
<point/>
</object>
<object id="5" name="STAIRS" x="86.8994" y="39.6665">
<point/>
</object>
<object id="6" name="DOOR_SWITCHED" x="87.1287" y="54.3408">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="11" height="10" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="14">
<properties>
<property name="DIRECTIONS" value="RRUURRDD"/>
<property name="ITEMS" value="GLOVE"/>
<property name="NAME" value="Should be simple for a dungoen puzzler such as yourself"/>
</properties>
<tileset firstgid="1" source="../level16.tsx"/>
<layer id="1" name="Tile Layer 1" width="11" height="10">
<data encoding="csv">
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
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="7" name="STAIRS" x="103.179" y="87.8166">
<point/>
</object>
<object id="9" name="BLOCK" x="86.8994" y="88.2751">
<point/>
</object>
<object id="11" name="BLOCK" x="103.408" y="105.013">
<point/>
</object>
<object id="12" name="HERO" x="69.9323" y="87.1287">
<point/>
</object>
<object id="13" name="BLOCK" x="119.687" y="86.6701">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="11" height="10" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="20">
<properties>
<property name="DIRECTIONS" value="RRUURRDD"/>
<property name="ITEMS" value="BLOCK,ICE"/>
<property name="NAME" value="Wow, finally a level with nothing new introduced!"/>
</properties>
<tileset firstgid="1" source="../level16.tsx"/>
<layer id="1" name="Tile Layer 1" width="11" height="10">
<data encoding="csv">
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
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="14" name="HERO" x="54.7994" y="55.9458">
<point/>
</object>
<object id="16" name="BLOCK" x="103.408" y="71.7665">
<point/>
</object>
<object id="17" name="SWITCH" x="119.458" y="55.4872">
<point/>
</object>
<object id="18" name="STAIRS" x="103.408" y="87.358">
<point/>
</object>
<object id="19" name="DOOR_SWITCHED" x="104.325" y="103.179">
<point/>
</object>
</objectgroup>
</map>

View file

@ -85,6 +85,195 @@ impl EntityMap {
AnimationInstruction::Add(idx, entity, location, None) AnimationInstruction::Add(idx, entity, location, None)
} }
fn attempt_move_in_direction(
&mut self,
map: &Map,
animations: &mut Vec<AnimationInstruction>,
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<AnimationInstruction>) { pub fn tick(&mut self, map: &Map, hero: Action) -> (Outcome, Vec<AnimationInstruction>) {
let mut hero_has_died = false; let mut hero_has_died = false;
let mut win_has_triggered = 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 { let (_, hero_has_died_result, win_has_triggered_result) = self
continue; .attempt_move_in_direction(
}; map,
&mut animations,
let entity_location = entity_to_update.location; &mut entities_to_try_update,
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, entity_to_update_key,
direction, direction,
self.map self.map
.get(entity_to_update_key) .get(entity_to_update_key)
.and_then(|e| e.fake_out_wall_effect()), .and_then(|e| e.push_depth())
)); .unwrap_or(0),
continue; );
}
let (can_move, explicit_stay_put, fake_out_effect) = { hero_has_died |= hero_has_died_result;
let mut can_move = true; win_has_triggered |= win_has_triggered_result;
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())
})
},
));
}
} }
} }
} }
@ -331,6 +372,7 @@ fn resolve_overlap(me: &Entity, other: &Entity) -> OverlapResolution {
match (&me.entity, &other.entity) { match (&me.entity, &other.entity) {
(EntityType::Hero(_), EntityType::Stairs) => OverlapResolution::Win, (EntityType::Hero(_), EntityType::Stairs) => OverlapResolution::Win,
(_, EntityType::Item(_)) => OverlapResolution::Pickup, (_, EntityType::Item(_)) => OverlapResolution::Pickup,
(EntityType::MovableBlock, EntityType::Spikes(_)) => OverlapResolution::CoExist,
(_, EntityType::Spikes(switch)) => resolve_spikes(switch), (_, EntityType::Spikes(switch)) => resolve_spikes(switch),
(_, EntityType::Switch(switch)) => OverlapResolution::ToggleSystem(switch.system), (_, EntityType::Switch(switch)) => OverlapResolution::ToggleSystem(switch.system),
(_, EntityType::Enemy(_) | EntityType::Hero(_)) => OverlapResolution::Die, (_, 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()) holding_door_resolve(squid.holding.as_deref())
} }
(_, EntityType::Door) => MoveAttemptResolution::StayPut, (_, EntityType::Door) => MoveAttemptResolution::StayPut,
(_, EntityType::MovableBlock) => MoveAttemptResolution::AttemptPush,
(_, _) => MoveAttemptResolution::CoExist, (_, _) => MoveAttemptResolution::CoExist,
} }
} }
@ -439,6 +482,7 @@ pub enum EntityType {
Switch(Switchable), Switch(Switchable),
Spikes(Switchable), Spikes(Switchable),
Ice, Ice,
MovableBlock,
} }
#[derive(Debug)] #[derive(Debug)]
@ -457,6 +501,7 @@ pub enum Enemy {
pub enum Item { pub enum Item {
Sword, Sword,
Key, Key,
Glove,
} }
#[derive(PartialEq, Eq, Clone, Copy, Debug)] #[derive(PartialEq, Eq, Clone, Copy, Debug)]
@ -552,6 +597,14 @@ impl Entity {
} }
} }
fn push_depth(&self) -> Option<i32> {
if matches!(self.holding(), Some(&EntityType::Item(Item::Glove))) {
Some(i32::MAX)
} else {
Some(1)
}
}
fn holding(&self) -> Option<&EntityType> { fn holding(&self) -> Option<&EntityType> {
match &self.entity { match &self.entity {
EntityType::Hero(hero) => hero.holding.as_deref(), EntityType::Hero(hero) => hero.holding.as_deref(),
@ -721,6 +774,8 @@ impl From<level::Item> for EntityType {
holding: None, holding: None,
})), })),
level::Item::Ice => EntityType::Ice, level::Item::Ice => EntityType::Ice,
level::Item::MovableBlock => EntityType::MovableBlock,
level::Item::Glove => EntityType::Item(Item::Glove),
} }
} }
} }

View file

@ -19,6 +19,8 @@ pub enum Item {
SquidUp, SquidUp,
SquidDown, SquidDown,
Ice, Ice,
MovableBlock,
Glove,
} }
impl Item { impl Item {
@ -39,6 +41,8 @@ impl Item {
Item::SquidUp => resources::SQUID_UP_SHADOW, Item::SquidUp => resources::SQUID_UP_SHADOW,
Item::SquidDown => resources::SQUID_DOWN_SHADOW, Item::SquidDown => resources::SQUID_DOWN_SHADOW,
Item::Ice => resources::ICE, Item::Ice => resources::ICE,
Item::MovableBlock => resources::BLOCK,
Item::Glove => resources::GLOVE,
} }
} }
@ -59,6 +63,8 @@ impl Item {
Item::SquidUp => resources::SQUID_UP, Item::SquidUp => resources::SQUID_UP,
Item::SquidDown => resources::SQUID_DOWN, Item::SquidDown => resources::SQUID_DOWN,
Item::Ice => resources::ICE, Item::Ice => resources::ICE,
Item::MovableBlock => resources::BLOCK,
Item::Glove => resources::GLOVE,
} }
} }
@ -82,6 +88,8 @@ impl Item {
Item::SquidUp => STANDARD, Item::SquidUp => STANDARD,
Item::SquidDown => STANDARD, Item::SquidDown => STANDARD,
Item::Ice => ZERO, Item::Ice => ZERO,
Item::MovableBlock => ZERO,
Item::Glove => STANDARD,
} }
} }
} }

View file

@ -50,6 +50,8 @@ named_tag!(
SQUID_UP_SHADOW, SQUID_UP_SHADOW,
SQUID_DOWN_SHADOW, SQUID_DOWN_SHADOW,
ICE, ICE,
BLOCK,
GLOVE,
] ]
); );