Victory
This commit is contained in:
parent
4fbb2a881a
commit
fa4bfbb10e
@ -35,3 +35,12 @@ pub struct Health {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Name(pub String);
|
pub struct Name(pub String);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct ChasingPlayer;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct Item;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct AmuletOfYala;
|
||||||
|
68
src/main.rs
68
src/main.rs
@ -40,6 +40,7 @@ impl State {
|
|||||||
let map_builder = MapBuilder::new(&mut rng);
|
let map_builder = MapBuilder::new(&mut rng);
|
||||||
|
|
||||||
spawn_player(&mut ecs, map_builder.player_start);
|
spawn_player(&mut ecs, map_builder.player_start);
|
||||||
|
spawn_amulet_of_yala(&mut ecs, map_builder.amulet_start);
|
||||||
map_builder
|
map_builder
|
||||||
.rooms
|
.rooms
|
||||||
.iter()
|
.iter()
|
||||||
@ -58,6 +59,71 @@ impl State {
|
|||||||
monster_systems: monster_build_scheduler(),
|
monster_systems: monster_build_scheduler(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn game_over(&mut self, ctx: &mut BTerm) {
|
||||||
|
ctx.set_active_console(2);
|
||||||
|
ctx.print_color_centered(2, RED, BLACK, "Your quest has ended");
|
||||||
|
ctx.print_color_centered(
|
||||||
|
4,
|
||||||
|
WHITE,
|
||||||
|
BLACK,
|
||||||
|
"Slain by a monster, your hero's journey has come to a premature end.",
|
||||||
|
);
|
||||||
|
ctx.print_color_centered(
|
||||||
|
5,
|
||||||
|
WHITE,
|
||||||
|
BLACK,
|
||||||
|
"The Amulet of Yala remains unclaimed, and your home town is not saved.",
|
||||||
|
);
|
||||||
|
ctx.print_color_centered(
|
||||||
|
8,
|
||||||
|
YELLOW,
|
||||||
|
BLACK,
|
||||||
|
"Don't worry, you can always try again with a new hero.",
|
||||||
|
);
|
||||||
|
ctx.print_color_centered(9, GREEN, BLACK, "Press 1 to play again.");
|
||||||
|
if let Some(VirtualKeyCode::Key1) = ctx.key {
|
||||||
|
self.reset_game_state()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn reset_game_state(&mut self) {
|
||||||
|
self.ecs = World::default();
|
||||||
|
self.resources = Resources::default();
|
||||||
|
let mut rng = RandomNumberGenerator::new();
|
||||||
|
let map_builder = MapBuilder::new(&mut rng);
|
||||||
|
spawn_player(&mut self.ecs, map_builder.player_start);
|
||||||
|
spawn_amulet_of_yala(&mut self.ecs, map_builder.amulet_start);
|
||||||
|
map_builder
|
||||||
|
.rooms
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|r| r.center())
|
||||||
|
.for_each(|pos| spawn_monster(&mut self.ecs, &mut rng, pos));
|
||||||
|
self.resources.insert(map_builder.map);
|
||||||
|
self.resources.insert(Camera::new(map_builder.player_start));
|
||||||
|
self.resources.insert(TurnState::AwaitingInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn victory(&mut self, ctx: &mut BTerm) {
|
||||||
|
ctx.set_active_console(2);
|
||||||
|
ctx.print_color_centered(2, GREEN, BLACK, "You have won!");
|
||||||
|
ctx.print_color_centered(
|
||||||
|
4,
|
||||||
|
WHITE,
|
||||||
|
BLACK,
|
||||||
|
"You put on the Amulet of Yala and feel its power course through your veins.",
|
||||||
|
);
|
||||||
|
ctx.print_color_centered(
|
||||||
|
5,
|
||||||
|
WHITE,
|
||||||
|
BLACK,
|
||||||
|
"Your town is saved, and you can return to your normal life.",
|
||||||
|
);
|
||||||
|
ctx.print_color_centered(7, GREEN, BLACK, "Press 1 to play again");
|
||||||
|
if let Some(VirtualKeyCode::Key1) = ctx.key {
|
||||||
|
self.reset_game_state()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl GameState for State {
|
impl GameState for State {
|
||||||
fn tick(&mut self, ctx: &mut BTerm) {
|
fn tick(&mut self, ctx: &mut BTerm) {
|
||||||
@ -82,6 +148,8 @@ impl GameState for State {
|
|||||||
TurnState::MonsterTurn => self
|
TurnState::MonsterTurn => self
|
||||||
.monster_systems
|
.monster_systems
|
||||||
.execute(&mut self.ecs, &mut self.resources),
|
.execute(&mut self.ecs, &mut self.resources),
|
||||||
|
TurnState::GameOver => self.game_over(ctx),
|
||||||
|
TurnState::Victory => self.victory(ctx),
|
||||||
}
|
}
|
||||||
render_draw_buffer(ctx).expect("Render error");
|
render_draw_buffer(ctx).expect("Render error");
|
||||||
}
|
}
|
||||||
|
47
src/map.rs
47
src/map.rs
@ -37,4 +37,51 @@ impl Map {
|
|||||||
Some(map_idx(point.x, point.y))
|
Some(map_idx(point.x, point.y))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn valid_exit(&self, loc: Point, delta: Point) -> Option<usize> {
|
||||||
|
let destinaton = loc + delta;
|
||||||
|
if self.in_bounds(destinaton) {
|
||||||
|
if self.can_enter_tile(destinaton) {
|
||||||
|
let idx = self.point2d_to_index(destinaton);
|
||||||
|
Some(idx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Algorithm2D for Map {
|
||||||
|
fn dimensions(&self) -> Point {
|
||||||
|
Point::new(SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||||
|
}
|
||||||
|
fn in_bounds(&self, pos: Point) -> bool {
|
||||||
|
self.in_bounds(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BaseMap for Map {
|
||||||
|
fn get_available_exits(&self, idx: usize) -> SmallVec<[(usize, f32); 10]> {
|
||||||
|
let mut exits = SmallVec::new();
|
||||||
|
let location = self.index_to_point2d(idx);
|
||||||
|
|
||||||
|
if let Some(idx) = self.valid_exit(location, Point::new(-1, 0)) {
|
||||||
|
exits.push((idx, 1.0))
|
||||||
|
}
|
||||||
|
if let Some(idx) = self.valid_exit(location, Point::new(1, 0)) {
|
||||||
|
exits.push((idx, 1.0))
|
||||||
|
}
|
||||||
|
if let Some(idx) = self.valid_exit(location, Point::new(0, -1)) {
|
||||||
|
exits.push((idx, 1.0))
|
||||||
|
}
|
||||||
|
if let Some(idx) = self.valid_exit(location, Point::new(0, 1)) {
|
||||||
|
exits.push((idx, 1.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
exits
|
||||||
|
}
|
||||||
|
fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 {
|
||||||
|
DistanceAlg::Pythagoras.distance2d(self.index_to_point2d(idx1), self.index_to_point2d(idx2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ pub struct MapBuilder {
|
|||||||
pub map: Map,
|
pub map: Map,
|
||||||
pub rooms: Vec<Rect>,
|
pub rooms: Vec<Rect>,
|
||||||
pub player_start: Point,
|
pub player_start: Point,
|
||||||
|
pub amulet_start: Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapBuilder {
|
impl MapBuilder {
|
||||||
@ -13,11 +14,32 @@ impl MapBuilder {
|
|||||||
map: Map::new(),
|
map: Map::new(),
|
||||||
rooms: Vec::new(),
|
rooms: Vec::new(),
|
||||||
player_start: Point::zero(),
|
player_start: Point::zero(),
|
||||||
|
amulet_start: Point::zero(),
|
||||||
};
|
};
|
||||||
mb.fill(TileType::Wall);
|
mb.fill(TileType::Wall);
|
||||||
mb.build_random_rooms(rng);
|
mb.build_random_rooms(rng);
|
||||||
mb.build_corridors(rng);
|
mb.build_corridors(rng);
|
||||||
mb.player_start = mb.rooms[0].center();
|
mb.player_start = mb.rooms[0].center();
|
||||||
|
|
||||||
|
let dijkstra_map = DijkstraMap::new(
|
||||||
|
SCREEN_WIDTH,
|
||||||
|
SCREEN_HEIGHT,
|
||||||
|
&vec![mb.map.point2d_to_index(mb.player_start)],
|
||||||
|
&mb.map,
|
||||||
|
1024.0,
|
||||||
|
);
|
||||||
|
const UNREACHABLE: &f32 = &f32::MAX;
|
||||||
|
mb.amulet_start = mb.map.index_to_point2d(
|
||||||
|
dijkstra_map
|
||||||
|
.map
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, dist)| *dist < UNREACHABLE)
|
||||||
|
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.0,
|
||||||
|
);
|
||||||
|
|
||||||
mb
|
mb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ pub fn spawn_monster(ecs: &mut World, rng: &mut RandomNumberGenerator, pos: Poin
|
|||||||
color: ColorPair::new(WHITE, BLACK),
|
color: ColorPair::new(WHITE, BLACK),
|
||||||
glyph,
|
glyph,
|
||||||
},
|
},
|
||||||
MovingRandomly {},
|
ChasingPlayer {},
|
||||||
Health {
|
Health {
|
||||||
current: hp,
|
current: hp,
|
||||||
max: hp,
|
max: hp,
|
||||||
@ -43,3 +43,16 @@ fn goblin() -> (i32, String, FontCharType) {
|
|||||||
fn orc() -> (i32, String, FontCharType) {
|
fn orc() -> (i32, String, FontCharType) {
|
||||||
(2, "orc".to_string(), to_cp437('o'))
|
(2, "orc".to_string(), to_cp437('o'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spawn_amulet_of_yala(ecs: &mut World, pos: Point) {
|
||||||
|
ecs.push((
|
||||||
|
Item,
|
||||||
|
AmuletOfYala,
|
||||||
|
pos,
|
||||||
|
Render {
|
||||||
|
color: ColorPair::new(WHITE, BLACK),
|
||||||
|
glyph: to_cp437('|'),
|
||||||
|
},
|
||||||
|
Name("Amulet of Yala".to_string()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
60
src/systems/chasing.rs
Normal file
60
src/systems/chasing.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[system]
|
||||||
|
#[read_component(Point)]
|
||||||
|
#[read_component(ChasingPlayer)]
|
||||||
|
#[read_component(Health)]
|
||||||
|
#[read_component(Player)]
|
||||||
|
pub fn chasing(#[resource] map: &Map, ecs: &SubWorld, commands: &mut CommandBuffer) {
|
||||||
|
let mut movers = <(Entity, &Point, &ChasingPlayer)>::query();
|
||||||
|
let mut positions = <(Entity, &Point, &Health)>::query();
|
||||||
|
let mut player = <(&Point, &Player)>::query();
|
||||||
|
|
||||||
|
let player_pos = player.iter(ecs).nth(0).unwrap().0;
|
||||||
|
let player_idx = map_idx(player_pos.x, player_pos.y);
|
||||||
|
|
||||||
|
let search_targets = vec![player_idx];
|
||||||
|
let dijkstra_map = DijkstraMap::new(SCREEN_WIDTH, SCREEN_HEIGHT, &search_targets, map, 1024.0);
|
||||||
|
|
||||||
|
movers.iter(ecs).for_each(|(entity, pos, _)| {
|
||||||
|
let idx = map_idx(pos.x, pos.y);
|
||||||
|
if let Some(destination) = DijkstraMap::find_lowest_exit(&dijkstra_map, idx, map) {
|
||||||
|
let distance = DistanceAlg::Pythagoras.distance2d(*pos, *player_pos);
|
||||||
|
let destination = if distance > 1.2 {
|
||||||
|
map.index_to_point2d(destination)
|
||||||
|
} else {
|
||||||
|
*player_pos
|
||||||
|
};
|
||||||
|
let mut attacked = false;
|
||||||
|
positions
|
||||||
|
.iter(ecs)
|
||||||
|
.filter(|(_, target_pos, _)| **target_pos == destination)
|
||||||
|
.for_each(|(victim, _, _)| {
|
||||||
|
if ecs
|
||||||
|
.entry_ref(*victim)
|
||||||
|
.unwrap()
|
||||||
|
.get_component::<Player>()
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
commands.push((
|
||||||
|
(),
|
||||||
|
WantsToAttack {
|
||||||
|
attacker: *entity,
|
||||||
|
victim: *victim,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
attacked = true;
|
||||||
|
});
|
||||||
|
if !attacked {
|
||||||
|
commands.push((
|
||||||
|
(),
|
||||||
|
WantsToMove {
|
||||||
|
entity: *entity,
|
||||||
|
destination,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -2,6 +2,7 @@ use crate::prelude::*;
|
|||||||
|
|
||||||
#[system]
|
#[system]
|
||||||
#[read_component(WantsToAttack)]
|
#[read_component(WantsToAttack)]
|
||||||
|
#[read_component(Player)]
|
||||||
#[write_component(Health)]
|
#[write_component(Health)]
|
||||||
pub fn combat(ecs: &mut SubWorld, commands: &mut CommandBuffer) {
|
pub fn combat(ecs: &mut SubWorld, commands: &mut CommandBuffer) {
|
||||||
let mut attackers = <(Entity, &WantsToAttack)>::query();
|
let mut attackers = <(Entity, &WantsToAttack)>::query();
|
||||||
@ -10,6 +11,11 @@ pub fn combat(ecs: &mut SubWorld, commands: &mut CommandBuffer) {
|
|||||||
.map(|(entity, attack)| (*entity, attack.victim))
|
.map(|(entity, attack)| (*entity, attack.victim))
|
||||||
.collect();
|
.collect();
|
||||||
victims.iter().for_each(|(message, victim)| {
|
victims.iter().for_each(|(message, victim)| {
|
||||||
|
let is_player = ecs
|
||||||
|
.entry_ref(*victim)
|
||||||
|
.unwrap()
|
||||||
|
.get_component::<Player>()
|
||||||
|
.is_ok();
|
||||||
if let Ok(mut health) = ecs
|
if let Ok(mut health) = ecs
|
||||||
.entry_mut(*victim)
|
.entry_mut(*victim)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -17,7 +23,7 @@ pub fn combat(ecs: &mut SubWorld, commands: &mut CommandBuffer) {
|
|||||||
{
|
{
|
||||||
println!("Health before attack: {}", health.current);
|
println!("Health before attack: {}", health.current);
|
||||||
health.current -= 1;
|
health.current -= 1;
|
||||||
if health.current < 1 {
|
if health.current < 1 && !is_player {
|
||||||
commands.remove(*victim);
|
commands.remove(*victim);
|
||||||
}
|
}
|
||||||
println!("Health after attack: {}", health.current);
|
println!("Health after attack: {}", health.current);
|
||||||
|
@ -1,11 +1,29 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[system]
|
#[system]
|
||||||
pub fn end_turn(#[resource] turn_state: &mut TurnState) {
|
#[read_component(Health)]
|
||||||
let new_state = match turn_state {
|
#[read_component(Point)]
|
||||||
|
#[read_component(Player)]
|
||||||
|
#[read_component(AmuletOfYala)]
|
||||||
|
pub fn end_turn(ecs: &SubWorld, #[resource] turn_state: &mut TurnState) {
|
||||||
|
let mut player_hp = <(&Health, &Point)>::query().filter(component::<Player>());
|
||||||
|
let mut amulet = <&Point>::query().filter(component::<AmuletOfYala>());
|
||||||
|
let amulet_pos = amulet.iter(ecs).nth(0).unwrap();
|
||||||
|
|
||||||
|
let current_state = turn_state.clone();
|
||||||
|
let mut new_state = match turn_state {
|
||||||
TurnState::AwaitingInput => return,
|
TurnState::AwaitingInput => return,
|
||||||
TurnState::PlayerTurn => TurnState::MonsterTurn,
|
TurnState::PlayerTurn => TurnState::MonsterTurn,
|
||||||
TurnState::MonsterTurn => TurnState::AwaitingInput,
|
TurnState::MonsterTurn => TurnState::AwaitingInput,
|
||||||
|
_ => current_state,
|
||||||
};
|
};
|
||||||
|
player_hp.iter(ecs).for_each(|(hp, pos)| {
|
||||||
|
if hp.current < 1 {
|
||||||
|
new_state = TurnState::GameOver;
|
||||||
|
}
|
||||||
|
if pos == amulet_pos {
|
||||||
|
new_state = TurnState::Victory;
|
||||||
|
}
|
||||||
|
});
|
||||||
*turn_state = new_state;
|
*turn_state = new_state;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
mod chasing;
|
||||||
mod combat;
|
mod combat;
|
||||||
mod end_turn;
|
mod end_turn;
|
||||||
mod entity_render;
|
mod entity_render;
|
||||||
@ -34,6 +35,7 @@ pub fn player_build_scheduler() -> Schedule {
|
|||||||
pub fn monster_build_scheduler() -> Schedule {
|
pub fn monster_build_scheduler() -> Schedule {
|
||||||
Schedule::builder()
|
Schedule::builder()
|
||||||
.add_system(random_move::random_move_system())
|
.add_system(random_move::random_move_system())
|
||||||
|
.add_system(chasing::chasing_system())
|
||||||
.flush()
|
.flush()
|
||||||
.add_system(combat::combat_system())
|
.add_system(combat::combat_system())
|
||||||
.flush()
|
.flush()
|
||||||
|
@ -3,4 +3,6 @@ pub enum TurnState {
|
|||||||
AwaitingInput,
|
AwaitingInput,
|
||||||
PlayerTurn,
|
PlayerTurn,
|
||||||
MonsterTurn,
|
MonsterTurn,
|
||||||
|
GameOver,
|
||||||
|
Victory,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user