Allow restoring of NPCs on level changes

This commit is contained in:
Timothy Warren 2022-01-06 09:34:17 -05:00
parent dd6a4c26d9
commit b4fc2ba28f
4 changed files with 91 additions and 86 deletions

View File

@ -278,6 +278,13 @@ pub struct LootTable {
pub table: String,
}
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct OtherLevelPosition {
pub x: i32,
pub y: i32,
pub depth: i32,
}
// Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an
// Entity.

View File

@ -343,11 +343,11 @@ impl GameState for State {
}
}
RunState::NextLevel => {
self.goto_next_level();
self.goto_level(1);
newrunstate = RunState::PreRun;
}
RunState::PreviousLevel => {
self.goto_previous_level();
self.goto_level(-1);
self.mapgen_next_state = Some(RunState::PreRun);
newrunstate = RunState::MapGeneration;
}
@ -376,86 +376,16 @@ impl GameState for State {
}
impl State {
fn entities_to_remove_on_level_change(&mut self) -> Vec<Entity> {
let entities = self.ecs.entities();
let player = self.ecs.read_storage::<Player>();
let backpack = self.ecs.read_storage::<InBackpack>();
let player_entity = self.ecs.fetch::<Entity>();
let equipped = self.ecs.read_storage::<Equipped>();
let mut to_delete: Vec<Entity> = Vec::new();
for entity in entities.join() {
let mut should_delete = true;
// Don't delete the player
if let Some(_p) = player.get(entity) {
should_delete = false;
}
// Don't delete the player's equipment
if let Some(bp) = backpack.get(entity) {
if bp.owner == *player_entity {
should_delete = false;
}
}
if let Some(eq) = equipped.get(entity) {
if eq.owner == *player_entity {
should_delete = false;
}
}
if should_delete {
to_delete.push(entity);
}
}
to_delete
}
fn goto_next_level(&mut self) {
// Delete entities that aren't the palyer or their equipment
let to_delete = self.entities_to_remove_on_level_change();
for target in to_delete {
self.ecs
.delete_entity(target)
.expect("failed to delete entity");
}
fn goto_level(&mut self, offset: i32) {
freeze_level_entities(&mut self.ecs);
// Build a new map and place the player
#[allow(unused_assignments)]
let mut current_depth = 1;
{
let worldmap_resource = self.ecs.fetch::<Map>();
current_depth = worldmap_resource.depth;
}
self.generate_world_map(current_depth + 1);
let current_depth = self.ecs.fetch::<Map>().depth;
self.generate_world_map(current_depth + offset, offset);
// Notify the player
let mut gamelog = self.ecs.fetch_mut::<GameLog>();
gamelog.append("You descend to the next level.");
}
fn goto_previous_level(&mut self) {
// Delete entities that aren't the palyer or their equipment
let to_delete = self.entities_to_remove_on_level_change();
for target in to_delete {
self.ecs
.delete_entity(target)
.expect("failed to delete entity");
}
// Build a new map and place the player
#[allow(unused_assignments)]
let current_depth;
{
let worldmap_resource = self.ecs.fetch::<Map>();
current_depth = worldmap_resource.depth;
}
self.generate_world_map(current_depth - 1);
// Notify the player
let mut gamelog = self.ecs.fetch_mut::<GameLog>();
gamelog.append("You ascend to the previous level.");
gamelog.append("You change level.");
}
fn game_over_cleanup(&mut self) {
@ -481,16 +411,18 @@ impl State {
self.ecs.insert(map::MasterDungeonMap::new());
// Build a new map and place the player
self.generate_world_map(1);
self.generate_world_map(1, 0);
}
fn generate_world_map(&mut self, new_depth: i32) {
fn generate_world_map(&mut self, new_depth: i32, offset: i32) {
self.mapgen_index = 0;
self.mapgen_timer = 0.0;
self.mapgen_history.clear();
if let Some(history) = map::level_transition(&mut self.ecs, new_depth) {
if let Some(history) = map::level_transition(&mut self.ecs, new_depth, offset) {
self.mapgen_history = history;
} else {
map::thaw_level_entities(&mut self.ecs);
}
}
}
@ -530,6 +462,7 @@ fn main() -> ::rltk::BError {
Monster,
Name,
NaturalAttackDefense,
OtherLevelPosition,
ParticleLifetime,
Player,
Pools,
@ -571,7 +504,7 @@ fn main() -> ::rltk::BError {
gs.ecs.insert(particle_system::ParticleBuilder::new());
gs.ecs.insert(rex_assets::RexAssets::new());
gs.generate_world_map(1);
gs.generate_world_map(1, 0);
rltk::main_loop(context, gs)
}

View File

@ -4,7 +4,7 @@ use ::rltk::{Point, RandomNumberGenerator};
use ::serde::{Deserialize, Serialize};
use ::specs::prelude::*;
use crate::components::{Position, Viewshed};
use crate::components::{OtherLevelPosition, Position, Viewshed};
use crate::map::{Map, TileType};
use crate::map_builders::level_builder;
@ -36,14 +36,14 @@ impl MasterDungeonMap {
}
}
pub fn level_transition(ecs: &mut World, new_depth: i32) -> Option<Vec<Map>> {
pub fn level_transition(ecs: &mut World, new_depth: i32, offset: i32) -> Option<Vec<Map>> {
// Obtain the master dungeon map
let dungeon_master = ecs.read_resource::<MasterDungeonMap>();
// Do we already have a map?
if dungeon_master.get_map(new_depth).is_some() {
std::mem::drop(dungeon_master);
transition_to_existing_map(ecs, new_depth);
transition_to_existing_map(ecs, new_depth, offset);
None
} else {
@ -102,7 +102,7 @@ fn transition_to_new_map(ecs: &mut World, new_depth: i32) -> Vec<Map> {
mapgen_history
}
fn transition_to_existing_map(ecs: &mut World, new_depth: i32) {
fn transition_to_existing_map(ecs: &mut World, new_depth: i32, offset: i32) {
let dungeon_master = ecs.write_resource::<MasterDungeonMap>();
let map = dungeon_master.get_map(new_depth).unwrap();
let mut worldmap_resource = ecs.write_resource::<Map>();
@ -110,8 +110,13 @@ fn transition_to_existing_map(ecs: &mut World, new_depth: i32) {
// Find the down stairs and place the player
let w = map.width;
let stair_type = if offset < 0 {
TileType::DownStairs
} else {
TileType::UpStairs
};
for (idx, tt) in map.tiles.iter().enumerate() {
if *tt == TileType::DownStairs {
if *tt == stair_type {
let mut player_position = ecs.write_resource::<Point>();
*player_position = Point::new(idx as i32 % w, idx as i32 / w);
let mut position_components = ecs.write_storage::<Position>();
@ -130,3 +135,61 @@ fn transition_to_existing_map(ecs: &mut World, new_depth: i32) {
vs.dirty = true;
}
}
pub fn freeze_level_entities(ecs: &mut World) {
// Obtain ECS access
let entities = ecs.entities();
let mut positions = ecs.write_storage::<Position>();
let mut other_level_positions = ecs.write_storage::<OtherLevelPosition>();
let player_entity = ecs.fetch::<Entity>();
let map_depth = ecs.fetch::<Map>().depth;
// Find positions and make OtherLevelPosition
let mut pos_to_delete: Vec<Entity> = Vec::new();
for (entity, pos) in (&entities, &positions).join() {
if entity != *player_entity {
other_level_positions
.insert(
entity,
OtherLevelPosition {
x: pos.x,
y: pos.y,
depth: map_depth,
},
)
.expect("Failed to insert OtherLevelPosition");
pos_to_delete.push(entity);
}
}
// Remove positions
for p in pos_to_delete.iter() {
positions.remove(*p);
}
}
pub fn thaw_level_entities(ecs: &mut World) {
// Obtain ECS access
let entities = ecs.entities();
let mut positions = ecs.write_storage::<Position>();
let mut other_level_positions = ecs.write_storage::<OtherLevelPosition>();
let player_entity = ecs.fetch::<Entity>();
let map_depth = ecs.fetch::<Map>().depth;
// Find OtherLevelPosition
let mut pos_to_delete: Vec<Entity> = Vec::new();
for (entity, pos) in (&entities, &other_level_positions).join() {
if entity != *player_entity && pos.depth == map_depth {
positions
.insert(entity, Position { x: pos.x, y: pos.y })
.expect("Failed to insert Position");
pos_to_delete.push(entity);
}
}
// Remove positions
for p in pos_to_delete.iter() {
other_level_positions.remove(*p);
}
}

View File

@ -77,6 +77,7 @@ pub fn save_game(ecs: &mut World) {
Monster,
Name,
NaturalAttackDefense,
OtherLevelPosition,
ParticleLifetime,
Player,
Pools,
@ -177,6 +178,7 @@ pub fn load_game(ecs: &mut World) {
Monster,
Name,
NaturalAttackDefense,
OtherLevelPosition,
ParticleLifetime,
Player,
Pools,