548 lines
20 KiB
Rust
548 lines
20 KiB
Rust
use ::rltk::{GameState, Point, Rltk};
|
|
use ::specs::prelude::*;
|
|
|
|
use crate::components::*;
|
|
use crate::game_log::GameLog;
|
|
use crate::gui::{self, show_cheat_mode, CheatMenuResult, MainMenuSelection};
|
|
use crate::hunger_system::HungerSystem;
|
|
use crate::inventory_system::{
|
|
ItemCollectionSystem, ItemDropSystem, ItemEquipOnUse, ItemIdentificationSystem,
|
|
ItemRemoveSystem, ItemUseSystem,
|
|
};
|
|
use crate::lighting_system::LightingSystem;
|
|
use crate::map::{self, *};
|
|
use crate::map_indexing_system::MapIndexingSystem;
|
|
use crate::melee_combat_system::MeleeCombatSystem;
|
|
use crate::movement_system::MovementSystem;
|
|
use crate::particle_system::{self, ParticleSpawnSystem};
|
|
use crate::player::*;
|
|
use crate::raws::*;
|
|
use crate::trigger_system::TriggerSystem;
|
|
use crate::visibility_system::VisibilitySystem;
|
|
use crate::{ai, camera, damage_system, effects, saveload_system, spawner};
|
|
|
|
pub const SHOW_MAPGEN_VISUALIZER: bool = false;
|
|
|
|
#[derive(PartialEq, Copy, Clone)]
|
|
pub enum VendorMode {
|
|
Buy,
|
|
Sell,
|
|
}
|
|
|
|
#[derive(PartialEq, Copy, Clone)]
|
|
pub enum RunState {
|
|
AwaitingInput,
|
|
PreRun,
|
|
Ticking,
|
|
ShowInventory,
|
|
ShowDropItem,
|
|
ShowTargeting { range: i32, item: Entity },
|
|
MainMenu { menu_selection: MainMenuSelection },
|
|
SaveGame,
|
|
NextLevel,
|
|
PreviousLevel,
|
|
TownPortal,
|
|
ShowRemoveItem,
|
|
GameOver,
|
|
MagicMapReveal { row: i32 },
|
|
MapGeneration,
|
|
ShowCheatMenu,
|
|
ShowVendor { vendor: Entity, mode: VendorMode },
|
|
TeleportingToOtherLevel { x: i32, y: i32, depth: i32 },
|
|
}
|
|
|
|
pub struct State {
|
|
pub ecs: World,
|
|
mapgen_next_state: Option<RunState>,
|
|
mapgen_history: Vec<Map>,
|
|
mapgen_index: usize,
|
|
mapgen_timer: f32,
|
|
}
|
|
|
|
impl State {
|
|
pub(super) fn new() -> Self {
|
|
State {
|
|
ecs: World::new(),
|
|
mapgen_next_state: Some(RunState::MainMenu {
|
|
menu_selection: MainMenuSelection::NewGame,
|
|
}),
|
|
mapgen_index: 0,
|
|
mapgen_history: Vec::new(),
|
|
mapgen_timer: 0.0,
|
|
}
|
|
}
|
|
|
|
fn run_systems(&mut self) {
|
|
let mut mapindex = MapIndexingSystem {};
|
|
mapindex.run_now(&self.ecs);
|
|
|
|
let mut vis = VisibilitySystem {};
|
|
vis.run_now(&self.ecs);
|
|
|
|
let mut encumbrance = ai::EncumbranceSystem {};
|
|
encumbrance.run_now(&self.ecs);
|
|
|
|
let mut initiative = ai::InitiativeSystem {};
|
|
initiative.run_now(&self.ecs);
|
|
|
|
let mut turnstatus = ai::TurnStatusSystem {};
|
|
turnstatus.run_now(&self.ecs);
|
|
|
|
let mut quipper = ai::QuipSystem {};
|
|
quipper.run_now(&self.ecs);
|
|
|
|
let mut adjacent = ai::AdjacentAI {};
|
|
adjacent.run_now(&self.ecs);
|
|
|
|
let mut visible = ai::VisibleAI {};
|
|
visible.run_now(&self.ecs);
|
|
|
|
let mut approach = ai::ApproachAI {};
|
|
approach.run_now(&self.ecs);
|
|
|
|
let mut flee = ai::FleeAI {};
|
|
flee.run_now(&self.ecs);
|
|
|
|
let mut chase = ai::ChaseAI {};
|
|
chase.run_now(&self.ecs);
|
|
|
|
let mut defaultmove = ai::DefaultMoveAI {};
|
|
defaultmove.run_now(&self.ecs);
|
|
|
|
let mut moving = MovementSystem {};
|
|
moving.run_now(&self.ecs);
|
|
|
|
let mut triggers = TriggerSystem {};
|
|
triggers.run_now(&self.ecs);
|
|
|
|
let mut melee = MeleeCombatSystem {};
|
|
melee.run_now(&self.ecs);
|
|
|
|
let mut pickup = ItemCollectionSystem {};
|
|
pickup.run_now(&self.ecs);
|
|
|
|
let mut itemequip = ItemEquipOnUse {};
|
|
itemequip.run_now(&self.ecs);
|
|
|
|
let mut itemuse = ItemUseSystem {};
|
|
itemuse.run_now(&self.ecs);
|
|
|
|
let mut item_id = ItemIdentificationSystem {};
|
|
item_id.run_now(&self.ecs);
|
|
|
|
let mut drop_items = ItemDropSystem {};
|
|
drop_items.run_now(&self.ecs);
|
|
|
|
let mut item_remove = ItemRemoveSystem {};
|
|
item_remove.run_now(&self.ecs);
|
|
|
|
let mut hunger = HungerSystem {};
|
|
hunger.run_now(&self.ecs);
|
|
|
|
effects::run_effects_queue(&mut self.ecs);
|
|
|
|
let mut particles = ParticleSpawnSystem {};
|
|
particles.run_now(&self.ecs);
|
|
|
|
let mut lighting = LightingSystem {};
|
|
lighting.run_now(&self.ecs);
|
|
|
|
self.ecs.maintain();
|
|
}
|
|
|
|
fn goto_level(&mut self, offset: i32) {
|
|
freeze_level_entities(&mut self.ecs);
|
|
|
|
// Build a new map and place the player
|
|
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 change level.");
|
|
}
|
|
|
|
fn game_over_cleanup(&mut self) {
|
|
// Delete everything
|
|
let mut to_delete = Vec::new();
|
|
for e in self.ecs.entities().join() {
|
|
to_delete.push(e);
|
|
}
|
|
for del in to_delete.iter() {
|
|
self.ecs
|
|
.delete_entity(*del)
|
|
.expect("Failed to delete entity");
|
|
}
|
|
|
|
// Spawn a new player
|
|
{
|
|
let player_entity = spawner::player(&mut self.ecs, 0, 0);
|
|
let mut player_entity_writer = self.ecs.write_resource::<Entity>();
|
|
*player_entity_writer = player_entity;
|
|
}
|
|
|
|
// Replace the world maps
|
|
self.ecs.insert(map::MasterDungeonMap::new());
|
|
|
|
// Build a new map and place the player
|
|
self.generate_world_map(1, 0);
|
|
}
|
|
|
|
pub(super) 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, offset) {
|
|
self.mapgen_history = history;
|
|
} else {
|
|
map::thaw_level_entities(&mut self.ecs);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GameState for State {
|
|
fn tick(&mut self, ctx: &mut Rltk) {
|
|
let mut newrunstate;
|
|
{
|
|
let runstate = self.ecs.fetch::<RunState>();
|
|
newrunstate = *runstate;
|
|
}
|
|
|
|
ctx.cls();
|
|
particle_system::cull_dead_particles(&mut self.ecs, ctx);
|
|
|
|
match newrunstate {
|
|
RunState::MainMenu { .. } => {}
|
|
RunState::GameOver { .. } => {}
|
|
_ => {
|
|
camera::render_camera(&self.ecs, ctx);
|
|
gui::draw_ui(&self.ecs, ctx);
|
|
}
|
|
}
|
|
|
|
match newrunstate {
|
|
RunState::MapGeneration => {
|
|
if !SHOW_MAPGEN_VISUALIZER {
|
|
newrunstate = self.mapgen_next_state.unwrap();
|
|
}
|
|
ctx.cls();
|
|
if self.mapgen_index < self.mapgen_history.len() {
|
|
camera::render_debug_map(&self.mapgen_history[self.mapgen_index], ctx);
|
|
}
|
|
|
|
self.mapgen_timer += ctx.frame_time_ms;
|
|
if self.mapgen_timer > 300.0 {
|
|
self.mapgen_timer = 0.0;
|
|
self.mapgen_index += 1;
|
|
if self.mapgen_index >= self.mapgen_history.len() {
|
|
newrunstate = self.mapgen_next_state.unwrap();
|
|
}
|
|
}
|
|
}
|
|
RunState::PreRun => {
|
|
self.run_systems();
|
|
self.ecs.maintain();
|
|
newrunstate = RunState::AwaitingInput;
|
|
}
|
|
RunState::AwaitingInput => {
|
|
newrunstate = player_input(self, ctx);
|
|
}
|
|
RunState::Ticking => {
|
|
while newrunstate == RunState::Ticking {
|
|
self.run_systems();
|
|
self.ecs.maintain();
|
|
|
|
newrunstate = match *self.ecs.fetch::<RunState>() {
|
|
RunState::AwaitingInput => RunState::AwaitingInput,
|
|
RunState::MagicMapReveal { .. } => RunState::MagicMapReveal { row: 0 },
|
|
RunState::TownPortal => RunState::TownPortal,
|
|
RunState::TeleportingToOtherLevel { x, y, depth } => {
|
|
RunState::TeleportingToOtherLevel { x, y, depth }
|
|
}
|
|
_ => RunState::Ticking,
|
|
};
|
|
}
|
|
}
|
|
RunState::ShowInventory => {
|
|
let result = gui::show_inventory(self, ctx);
|
|
match result.0 {
|
|
gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
|
|
gui::ItemMenuResult::NoResponse => {}
|
|
gui::ItemMenuResult::Selected => {
|
|
let item_entity = result.1.unwrap();
|
|
let is_ranged = self.ecs.read_storage::<Ranged>();
|
|
|
|
if let Some(is_item_ranged) = is_ranged.get(item_entity) {
|
|
newrunstate = RunState::ShowTargeting {
|
|
range: is_item_ranged.range,
|
|
item: item_entity,
|
|
};
|
|
} else {
|
|
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
|
|
intent
|
|
.insert(
|
|
*self.ecs.fetch::<Entity>(),
|
|
WantsToUseItem {
|
|
item: item_entity,
|
|
target: None,
|
|
},
|
|
)
|
|
.expect("failed to add intent to use item");
|
|
|
|
newrunstate = RunState::Ticking;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RunState::ShowDropItem => {
|
|
let result = gui::drop_item_menu(self, ctx);
|
|
match result.0 {
|
|
gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
|
|
gui::ItemMenuResult::NoResponse => {}
|
|
gui::ItemMenuResult::Selected => {
|
|
let item_entity = result.1.unwrap();
|
|
let mut intent = self.ecs.write_storage::<WantsToDropItem>();
|
|
intent
|
|
.insert(
|
|
*self.ecs.fetch::<Entity>(),
|
|
WantsToDropItem { item: item_entity },
|
|
)
|
|
.expect("failed to add intent to drop item");
|
|
|
|
newrunstate = RunState::Ticking;
|
|
}
|
|
}
|
|
}
|
|
RunState::ShowRemoveItem => {
|
|
let result = gui::remove_item_menu(self, ctx);
|
|
match result.0 {
|
|
gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
|
|
gui::ItemMenuResult::NoResponse => {}
|
|
gui::ItemMenuResult::Selected => {
|
|
let item_entity = result.1.unwrap();
|
|
let mut intent = self.ecs.write_storage::<WantsToRemoveItem>();
|
|
intent
|
|
.insert(
|
|
*self.ecs.fetch::<Entity>(),
|
|
WantsToRemoveItem { item: item_entity },
|
|
)
|
|
.expect("Unable to insert intent to remove item");
|
|
|
|
newrunstate = RunState::Ticking;
|
|
}
|
|
}
|
|
}
|
|
RunState::ShowTargeting { range, item } => {
|
|
let result = gui::ranged_target(self, ctx, range);
|
|
match result.0 {
|
|
gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
|
|
gui::ItemMenuResult::NoResponse => {}
|
|
gui::ItemMenuResult::Selected => {
|
|
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
|
|
|
|
intent
|
|
.insert(
|
|
*self.ecs.fetch::<Entity>(),
|
|
WantsToUseItem {
|
|
item,
|
|
target: result.1,
|
|
},
|
|
)
|
|
.expect("failed to add intent to use item");
|
|
|
|
newrunstate = RunState::Ticking;
|
|
}
|
|
}
|
|
}
|
|
RunState::MainMenu { .. } => match gui::main_menu(self, ctx) {
|
|
gui::MainMenuResult::NoSelection { selected } => {
|
|
newrunstate = RunState::MainMenu {
|
|
menu_selection: selected,
|
|
}
|
|
}
|
|
gui::MainMenuResult::Selected { selected } => match selected {
|
|
gui::MainMenuSelection::NewGame => newrunstate = RunState::PreRun,
|
|
gui::MainMenuSelection::LoadGame => {
|
|
saveload_system::load_game(&mut self.ecs);
|
|
newrunstate = RunState::AwaitingInput;
|
|
saveload_system::delete_save();
|
|
}
|
|
gui::MainMenuSelection::Quit => {
|
|
::std::process::exit(0);
|
|
}
|
|
},
|
|
},
|
|
RunState::GameOver => match gui::game_over(ctx) {
|
|
gui::GameOverResult::NoSelection => {}
|
|
gui::GameOverResult::QuitToMenu => {
|
|
self.game_over_cleanup();
|
|
newrunstate = RunState::MainMenu {
|
|
menu_selection: gui::MainMenuSelection::NewGame,
|
|
};
|
|
}
|
|
},
|
|
RunState::SaveGame => {
|
|
saveload_system::save_game(&mut self.ecs);
|
|
|
|
newrunstate = RunState::MainMenu {
|
|
menu_selection: gui::MainMenuSelection::LoadGame,
|
|
}
|
|
}
|
|
RunState::NextLevel => {
|
|
self.goto_level(1);
|
|
newrunstate = RunState::PreRun;
|
|
}
|
|
RunState::PreviousLevel => {
|
|
self.goto_level(-1);
|
|
self.mapgen_next_state = Some(RunState::PreRun);
|
|
newrunstate = RunState::MapGeneration;
|
|
}
|
|
RunState::MagicMapReveal { row } => {
|
|
let mut map = self.ecs.fetch_mut::<Map>();
|
|
for x in 0..map.width {
|
|
let idx = map.xy_idx(x as i32, row);
|
|
map.revealed_tiles[idx] = true;
|
|
}
|
|
|
|
if row == map.height - 1 {
|
|
newrunstate = RunState::Ticking;
|
|
} else {
|
|
newrunstate = RunState::MagicMapReveal { row: row + 1 };
|
|
}
|
|
}
|
|
RunState::ShowCheatMenu => match show_cheat_mode(self, ctx) {
|
|
CheatMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
|
|
CheatMenuResult::NoResponse => {}
|
|
CheatMenuResult::TeleportToExit => {
|
|
self.goto_level(1);
|
|
self.mapgen_next_state = Some(RunState::PreRun);
|
|
|
|
newrunstate = RunState::MapGeneration
|
|
}
|
|
CheatMenuResult::Heal => {
|
|
let player = self.ecs.fetch::<Entity>();
|
|
let mut pools = self.ecs.write_storage::<Pools>();
|
|
let mut player_pools = pools.get_mut(*player).unwrap();
|
|
player_pools.hit_points.current = player_pools.hit_points.max;
|
|
|
|
newrunstate = RunState::AwaitingInput;
|
|
}
|
|
CheatMenuResult::Reveal => {
|
|
let mut map = self.ecs.fetch_mut::<Map>();
|
|
for v in map.revealed_tiles.iter_mut() {
|
|
*v = true;
|
|
}
|
|
|
|
newrunstate = RunState::AwaitingInput;
|
|
}
|
|
CheatMenuResult::GodMode => {
|
|
let player = self.ecs.fetch::<Entity>();
|
|
let mut pools = self.ecs.write_storage::<Pools>();
|
|
let mut player_pools = pools.get_mut(*player).unwrap();
|
|
player_pools.god_mode = true;
|
|
|
|
newrunstate = RunState::AwaitingInput;
|
|
}
|
|
},
|
|
RunState::ShowVendor { vendor, mode } => {
|
|
let result = gui::show_vendor_menu(self, ctx, vendor, mode);
|
|
match result.0 {
|
|
gui::VendorResult::Cancel => newrunstate = RunState::AwaitingInput,
|
|
gui::VendorResult::NoResponse => {}
|
|
gui::VendorResult::Sell => {
|
|
let price = self
|
|
.ecs
|
|
.read_storage::<Item>()
|
|
.get(result.1.unwrap())
|
|
.unwrap()
|
|
.base_value
|
|
* 0.8;
|
|
self.ecs
|
|
.write_storage::<Pools>()
|
|
.get_mut(*self.ecs.fetch::<Entity>())
|
|
.unwrap()
|
|
.gold += price;
|
|
self.ecs
|
|
.delete_entity(result.1.unwrap())
|
|
.expect("Unable to delete sold item");
|
|
}
|
|
gui::VendorResult::Buy => {
|
|
let tag = result.2.unwrap();
|
|
let price = result.3.unwrap();
|
|
let mut pools = self.ecs.write_storage::<Pools>();
|
|
let player_entity = self.ecs.fetch::<Entity>();
|
|
|
|
let mut identified = self.ecs.write_storage::<IdentifiedItem>();
|
|
identified
|
|
.insert(*player_entity, IdentifiedItem { name: tag.clone() })
|
|
.expect("Unable to identify item");
|
|
std::mem::drop(identified);
|
|
|
|
let player_pools = pools.get_mut(*player_entity).unwrap();
|
|
std::mem::drop(player_entity);
|
|
if player_pools.gold >= price {
|
|
player_pools.gold -= price;
|
|
std::mem::drop(pools);
|
|
|
|
let player_entity = *self.ecs.fetch::<Entity>();
|
|
spawn_named_item(
|
|
&RAWS.lock().unwrap(),
|
|
&mut self.ecs,
|
|
&tag,
|
|
SpawnType::Carried { by: player_entity },
|
|
);
|
|
}
|
|
}
|
|
gui::VendorResult::BuyMode => {
|
|
newrunstate = RunState::ShowVendor {
|
|
vendor,
|
|
mode: VendorMode::Buy,
|
|
}
|
|
}
|
|
gui::VendorResult::SellMode => {
|
|
newrunstate = RunState::ShowVendor {
|
|
vendor,
|
|
mode: VendorMode::Sell,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RunState::TownPortal => {
|
|
// Spawn the portal
|
|
spawner::spawn_town_portal(&mut self.ecs);
|
|
|
|
// Transition
|
|
let map_depth = self.ecs.fetch::<Map>().depth;
|
|
let destination_offset = 0 - (map_depth - 1);
|
|
self.goto_level(destination_offset);
|
|
self.mapgen_next_state = Some(RunState::PreRun);
|
|
|
|
newrunstate = RunState::MapGeneration;
|
|
}
|
|
RunState::TeleportingToOtherLevel { x, y, depth } => {
|
|
self.goto_level(depth - 1);
|
|
let player_entity = self.ecs.fetch::<Entity>();
|
|
if let Some(pos) = self.ecs.write_storage::<Position>().get_mut(*player_entity) {
|
|
pos.x = x;
|
|
pos.y = y;
|
|
}
|
|
|
|
let mut ppos = self.ecs.fetch_mut::<Point>();
|
|
ppos.x = x;
|
|
ppos.y = y;
|
|
self.mapgen_next_state = Some(RunState::PreRun);
|
|
|
|
newrunstate = RunState::MapGeneration;
|
|
}
|
|
}
|
|
|
|
{
|
|
let mut runwriter = self.ecs.write_resource::<RunState>();
|
|
*runwriter = newrunstate;
|
|
}
|
|
|
|
damage_system::delete_the_dead(&mut self.ecs);
|
|
}
|
|
}
|