This commit is contained in:
Llywelwyn 2023-07-27 21:15:20 +01:00
parent 5f091bc2ab
commit b940142f16
14 changed files with 529 additions and 45 deletions

View file

@ -1,11 +1,107 @@
[
{
"id": "npc_barkeep",
"name": "barkeep",
"renderable": { "glyph": "@", "fg": "#EE82EE", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 1, "power": 1 },
"vision_range": 4,
"ai": "bystander"
},
{
"id": "npc_townsperson",
"name": "townsperson",
"renderable": { "glyph": "@", "fg": "#9fa86c", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
"vision_range": 4,
"ai": "bystander",
"quips": [ "I don't get paid nearly enough to"]
},
{
"id": "npc_drunk",
"name": "drunk",
"renderable": { "glyph": "@", "fg": "#a0a83c", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
"vision_range": 4,
"ai": "bystander",
"quips": [ "Hic!", "H-Hic'."]
},
{
"id": "npc_fisher",
"name": "fisher",
"renderable": { "glyph": "@", "fg": "#3ca3a8", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
"vision_range": 4,
"ai": "bystander",
"quips": [ "Placeholder."]
},
{
"id": "npc_dockworker",
"name": "dock worker",
"renderable": { "glyph": "@", "fg": "#68d8de", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 1, "power": 1 },
"vision_range": 4,
"ai": "bystander",
"quips": [ "Placeholder."]
},
{
"id": "npc_priest",
"name": "priest",
"renderable": { "glyph": "@", "fg": "#FFFFFF", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
"vision_range": 4,
"ai": "bystander"
},
{
"id": "npc_miner",
"name": "miner",
"renderable": { "glyph": "@", "fg": "#946123", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
"vision_range": 4,
"ai": "bystander"
},
{
"id": "npc_guard",
"name": "smalltown guard",
"renderable": { "glyph": "@", "fg": "#034efc", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 12, "hp": 12, "defence": 3, "power": 3 },
"vision_range": 4,
"ai": "bystander",
"quips": ["I don't get paid nearly enough to go down the mine."]
},
{
"id": "dog_little",
"name": "little dog",
"renderable": { "glyph": "d", "fg": "#FFFFFF", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 1 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "rat",
"name": "rat",
"renderable": { "glyph": "r", "fg": "#aa6000", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 4, "hp": 4, "defence": 0, "power": 1 },
"vision_range": 8,
"ai": "melee"
},
{
"id": "rat_giant",
"name": "giant rat",
"renderable": { "glyph": "r", "fg": "#bb8000", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 4, "hp": 4, "defence": 0, "power": 1 },
"vision_range": 8,
"ai": "melee"
},
{
"id": "dog",
@ -13,7 +109,8 @@
"renderable": { "glyph": "d", "fg": "#EEEEEE", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 2 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "dog_large",
@ -21,7 +118,8 @@
"renderable": { "glyph": "d", "fg": "#DDDDDD", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 12, "hp": 12, "defence": 0, "power": 3 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "goblin",
@ -29,7 +127,8 @@
"renderable": { "glyph": "g", "fg": "#00FF00", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 2 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "kobold",
@ -37,7 +136,8 @@
"renderable": { "glyph": "k", "fg": "#AA5500", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 1 },
"vision_range": 7
"vision_range": 7,
"ai": "melee"
},
{
"id": "jackal",
@ -45,7 +145,8 @@
"renderable": { "glyph": "d", "fg": "#AA5500", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 1 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "fox",
@ -53,7 +154,8 @@
"renderable": { "glyph": "d", "fg": "#FF0000", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 4, "hp": 4, "defence": 0, "power": 1 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "coyote",
@ -61,7 +163,8 @@
"renderable": { "glyph": "d", "fg": "#6E3215", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 2 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "goblin_chieftain",
@ -69,7 +172,8 @@
"renderable": { "glyph": "G", "fg": "#00FF00", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 1, "power": 2 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "orc",
@ -77,7 +181,8 @@
"renderable": { "glyph": "o", "fg": "#00FF00", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 3 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "orc_large",
@ -85,7 +190,8 @@
"renderable": { "glyph": "o", "fg": "#008000", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 12, "hp": 12, "defence": 1, "power": 3 },
"vision_range": 12
"vision_range": 12,
"ai": "melee"
},
{
"id": "ogre",
@ -93,6 +199,7 @@
"renderable": { "glyph": "O", "fg": "#00FF00", "bg": "#000000", "order": 1 },
"flags": ["BLOCKS_TILE"],
"stats": { "max_hp": 24, "hp": 24, "defence": 3, "power": 3 },
"vision_range": 8
"vision_range": 8,
"ai": "melee"
}
]

View file

@ -3,7 +3,37 @@
"id": "door",
"name": "door",
"renderable": { "glyph": "+", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
"flags": ["BLOCKS_TILE", "BLOCKS_VISIBILITY", "DOOR"]
"flags": ["BLOCKS_TILE", "BLOCKS_VISIBILITY", "DOOR", "PROP"]
},
{
"id": "prop_keg",
"name": "keg",
"renderable": { "glyph": "φ", "fg": "#AAAAAA", "bg": "#000000", "order": 2 },
"flags": ["PROP"]
},
{
"id": "prop_table",
"name": "table",
"renderable": { "glyph": "-", "fg": "#AAAAAA", "bg": "#000000", "order": 2 },
"flags": ["PROP"]
},
{
"id": "prop_bed",
"name": "bed",
"renderable": { "glyph": "=", "fg": "#AAAAAA", "bg": "#000000", "order": 2 },
"flags": ["PROP"]
},
{
"id": "prop_chair",
"name": "chair",
"renderable": { "glyph": "└", "fg": "#AAAAAA", "bg": "#000000", "order": 2 },
"flags": ["PROP"]
},
{
"id": "prop_candle",
"name": "candle",
"renderable": { "glyph": "Ä", "fg": "#FFA500", "bg": "#000000", "order": 2 },
"flags": ["PROP"]
},
{
"id": "trap_bear",

View file

@ -46,11 +46,13 @@
{
"id": "mobs",
"table": [
{ "id": "rat", "weight": 1, "difficulty": 1},
{ "id": "goblin", "weight": 3, "difficulty": 1},
{ "id": "kobold", "weight": 1, "difficulty": 1},
{ "id": "fox", "weight": 1, "difficulty": 1},
{ "id": "jackal", "weight": 4, "difficulty": 1},
{ "id": "rat_giant", "weight": 2, "difficulty": 2},
{ "id": "coyote", "weight": 4, "difficulty": 2},
{ "id": "dog_little", "weight": 1, "difficulty": 3},

View file

@ -0,0 +1,92 @@
use super::{gamelog, Bystander, EntityMoved, Map, Name, Point, Position, Quips, RunState, Viewshed};
use specs::prelude::*;
pub struct BystanderAI {}
impl<'a> System<'a> for BystanderAI {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteExpect<'a, Map>,
ReadExpect<'a, RunState>,
Entities<'a>,
WriteStorage<'a, Viewshed>,
ReadStorage<'a, Bystander>,
WriteStorage<'a, Position>,
WriteStorage<'a, EntityMoved>,
WriteExpect<'a, rltk::RandomNumberGenerator>,
ReadExpect<'a, Point>,
WriteStorage<'a, Quips>,
ReadStorage<'a, Name>,
);
fn run(&mut self, data: Self::SystemData) {
let (
mut map,
runstate,
entities,
mut viewshed,
bystander,
mut position,
mut entity_moved,
mut rng,
player_pos,
mut quips,
names,
) = data;
if *runstate != RunState::MonsterTurn {
return;
}
for (entity, mut viewshed, _bystander, mut pos) in (&entities, &mut viewshed, &bystander, &mut position).join()
{
// Possibly quip
let quip = quips.get_mut(entity);
if let Some(quip) = quip {
if !quip.available.is_empty()
&& viewshed.visible_tiles.contains(&player_pos)
&& rng.roll_dice(1, 20) == 1
{
let name = names.get(entity);
let quip_index = if quip.available.len() == 1 {
0
} else {
(rng.roll_dice(1, quip.available.len() as i32) - 1) as usize
};
gamelog::Logger::new()
.npc_name(&name.unwrap().name)
.append_n("says \"")
.append_n(&quip.available[quip_index])
.append("\"")
.log();
quip.available.remove(quip_index);
}
}
// Try to move randomly
let mut x = pos.x;
let mut y = pos.y;
let move_roll = rng.roll_dice(1, 8);
match move_roll {
1 => x -= 1,
2 => x += 1,
3 => y -= 1,
4 => y += 1,
_ => {}
}
if x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1 {
let dest_idx = map.xy_idx(x, y);
if !map.blocked[dest_idx] {
let idx = map.xy_idx(pos.x, pos.y);
map.blocked[idx] = false;
pos.x = x;
pos.y = y;
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
map.blocked[dest_idx] = true;
viewshed.dirty = true;
}
}
}
}
}

View file

@ -1,6 +1,7 @@
use super::{Door, Hidden, Map, Mind, Position, Renderable};
use super::{Hidden, Map, Mind, Position, Prop, Renderable};
use rltk::{Point, Rltk, RGB};
use specs::prelude::*;
use std::ops::Mul;
const SHOW_BOUNDARIES: bool = false;
@ -53,7 +54,7 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) {
let renderables = ecs.read_storage::<Renderable>();
let minds = ecs.read_storage::<Mind>();
let hidden = ecs.read_storage::<Hidden>();
let doors = ecs.write_storage::<Door>();
let props = ecs.write_storage::<Prop>();
let map = ecs.fetch::<Map>();
let entities = ecs.entities();
@ -66,11 +67,13 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) {
if entity_offset_x > 0 && entity_offset_x < map_width && entity_offset_y > 0 && entity_offset_y < map_height
{
let mut draw = false;
let fg = render.fg;
let mut fg = render.fg;
let (_glyph, _fg, bg) = crate::map::themes::get_tile_glyph(idx, &*map);
// Draw entities on visible tiles
if map.visible_tiles[idx] {
draw = true;
} else {
fg = fg.mul(0.75);
}
// Draw entities with minds within telepath range
if map.telepath_tiles[idx] {
@ -79,9 +82,9 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) {
draw = true;
}
}
// Draw all doors
let is_door = doors.get(*ent);
if let Some(_) = is_door {
// Draw all props
let is_prop = props.get(*ent);
if let Some(_) = is_prop {
if map.revealed_tiles[idx] {
draw = true;
}

View file

@ -34,9 +34,20 @@ pub struct Renderable {
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Player {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Prop {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Monster {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Bystander {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Quips {
pub available: Vec<String>,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Mind {}

View file

@ -22,6 +22,7 @@ mod visibility_system;
use visibility_system::VisibilitySystem;
mod monster_ai_system;
use monster_ai_system::MonsterAI;
pub mod bystander_ai_system;
mod map_indexing_system;
use map_indexing_system::MapIndexingSystem;
mod damage_system;
@ -123,6 +124,7 @@ impl State {
fn run_systems(&mut self) {
let mut vis = VisibilitySystem {};
let mut mob = MonsterAI {};
let mut bystanders = bystander_ai_system::BystanderAI {};
let mut mapindex = MapIndexingSystem {};
let mut trigger_system = trigger_system::TriggerSystem {};
let mut melee_system = MeleeCombatSystem {};
@ -136,6 +138,7 @@ impl State {
vis.run_now(&self.ecs);
mob.run_now(&self.ecs);
bystanders.run_now(&self.ecs);
mapindex.run_now(&self.ecs);
trigger_system.run_now(&self.ecs);
inventory_system.run_now(&self.ecs);
@ -499,8 +502,11 @@ fn main() -> rltk::BError {
gs.ecs.register::<Position>();
gs.ecs.register::<Renderable>();
gs.ecs.register::<Prop>();
gs.ecs.register::<Player>();
gs.ecs.register::<Monster>();
gs.ecs.register::<Bystander>();
gs.ecs.register::<Quips>();
gs.ecs.register::<Mind>();
gs.ecs.register::<Viewshed>();
gs.ecs.register::<Telepath>();

View file

@ -47,8 +47,7 @@ pub fn get_tile_glyph(idx: usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) {
}
TileType::Bridge => {
glyph = rltk::to_cp437('.');
fg = default_bg;
bg = default_bg;
bg = RGB::from_u8(59, 49, 43);
}
TileType::Gravel => {
glyph = rltk::to_cp437(';');

View file

@ -328,6 +328,7 @@ pub fn random_builder(
}
pub fn level_builder(new_id: i32, rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain {
// TODO: With difficulty and ID/depth decoupled, this can be used for branches later.
let difficulty = new_id;
match new_id {
1 => town_builder(new_id, rng, width, height),

View file

@ -19,6 +19,16 @@ impl InitialMapBuilder for TownBuilder {
}
}
enum BuildingTag {
Tavern,
Temple,
PlayerHouse,
NPCHouse,
Mine,
Abandoned,
Unassigned,
}
impl TownBuilder {
pub fn new() -> Box<TownBuilder> {
return Box::new(TownBuilder {});
@ -30,6 +40,7 @@ impl TownBuilder {
*t = true;
}
// Build map
self.grass_layer(build_data);
let piers = self.water_and_piers(rng, build_data);
let (mut available_building_tiles, wall_gap_y) = self.town_walls(rng, build_data);
@ -38,23 +49,198 @@ impl TownBuilder {
self.path_from_tiles_to_nearest_tiletype(build_data, &doors, TileType::Road, TileType::Road, true);
self.path_from_tiles_to_nearest_tiletype(build_data, &piers, TileType::Road, TileType::Road, false);
build_data.take_snapshot();
// Spawn entities
let building_size = self.sort_buildings(&buildings);
self.building_factory(rng, build_data, &buildings, &building_size);
self.spawn_dockers(build_data, rng);
self.spawn_townsfolk(build_data, rng, &mut available_building_tiles);
// Sort buildings by size
let mut building_size: Vec<(usize, i32)> = Vec::new();
build_data.take_snapshot();
}
fn sort_buildings(&mut self, buildings: &[(i32, i32, i32, i32)]) -> Vec<(usize, i32, BuildingTag)> {
// Sort buildings by size, defaulting them to Unassigned buildings
let mut building_size: Vec<(usize, i32, BuildingTag)> = Vec::new();
for (i, building) in buildings.iter().enumerate() {
building_size.push((i, building.2 * building.3));
building_size.push((i, building.2 * building.3, BuildingTag::Unassigned));
}
building_size.sort_by(|a, b| b.1.cmp(&a.1));
// Largest building as start position
let tavern = &buildings[building_size[0].0];
build_data.starting_position = Some(Position { x: tavern.0 + (tavern.2 / 2), y: tavern.1 + (tavern.3 / 2) });
// Set individual buildings to their correct tags
building_size[0].2 = BuildingTag::Tavern;
building_size[1].2 = BuildingTag::Temple;
building_size[2].2 = BuildingTag::Mine;
building_size[3].2 = BuildingTag::PlayerHouse;
for b in building_size.iter_mut().skip(3) {
b.2 = BuildingTag::NPCHouse
}
let last_idx = building_size.len() - 1;
building_size[last_idx].2 = BuildingTag::Abandoned;
// Smallest building as mine entrance
let mine = &buildings[building_size[building_size.len() - 1].0];
let exit_idx = build_data.map.xy_idx(mine.0 + (mine.2 / 2), mine.1 + (mine.3 / 2));
return building_size;
}
fn building_factory(
&mut self,
rng: &mut rltk::RandomNumberGenerator,
build_data: &mut BuilderMap,
buildings: &[(i32, i32, i32, i32)],
building_index: &[(usize, i32, BuildingTag)],
) {
for (i, building) in buildings.iter().enumerate() {
let build_tag = &building_index[i].2;
match build_tag {
BuildingTag::Tavern => self.build_tavern(&building, build_data, rng),
BuildingTag::Temple => self.build_temple(&building, build_data, rng),
BuildingTag::Mine => self.build_mine(&building, build_data, rng),
BuildingTag::PlayerHouse => self.build_playerhouse(&building, build_data, rng),
BuildingTag::NPCHouse => self.build_npchouse(&building, build_data, rng),
BuildingTag::Abandoned => self.build_abandoned(&building, build_data, rng),
_ => {}
}
}
}
fn spawn_dockers(&mut self, build_data: &mut BuilderMap, rng: &mut rltk::RandomNumberGenerator) {
for (idx, tt) in build_data.map.tiles.iter().enumerate() {
if *tt == TileType::Bridge && rng.roll_dice(1, 20) == 1 {
let roll = rng.roll_dice(1, 2);
match roll {
1 => build_data.spawn_list.push((idx, "npc_fisher".to_string())),
_ => build_data.spawn_list.push((idx, "npc_dockworker".to_string())),
}
}
}
}
fn spawn_townsfolk(
&mut self,
build_data: &mut BuilderMap,
rng: &mut rltk::RandomNumberGenerator,
available_building_tiles: &mut HashSet<usize>,
) {
for idx in available_building_tiles.iter() {
if rng.roll_dice(1, 40) == 1 {
let roll = rng.roll_dice(1, 3);
match roll {
1 => build_data.spawn_list.push((*idx, "npc_fisher".to_string())),
2 => build_data.spawn_list.push((*idx, "npc_dockworker".to_string())),
3 => build_data.spawn_list.push((*idx, "npc_townsperson".to_string())),
_ => build_data.spawn_list.push((*idx, "npc_drunk".to_string())),
}
}
}
}
fn random_building_spawn(
&mut self,
building: &(i32, i32, i32, i32),
build_data: &mut BuilderMap,
rng: &mut rltk::RandomNumberGenerator,
to_place: &mut Vec<&str>,
avoid_tile: usize,
) {
for y in building.1..building.1 + building.3 {
for x in building.0..building.0 + building.2 {
let idx = build_data.map.xy_idx(x, y);
if build_data.map.tiles[idx] == TileType::WoodFloor
&& idx != avoid_tile
&& rng.roll_dice(1, 3) == 1
&& !to_place.is_empty()
{
let entity_tag = to_place[0];
to_place.remove(0);
build_data.spawn_list.push((idx, entity_tag.to_string()));
}
}
}
}
fn build_tavern(
&mut self,
building: &(i32, i32, i32, i32),
build_data: &mut BuilderMap,
rng: &mut rltk::RandomNumberGenerator,
) {
// Place player
build_data.starting_position =
Some(Position { x: building.0 + (building.2 / 2), y: building.1 + (building.3 / 2) });
let player_idx = build_data.map.xy_idx(building.0 + (building.2 / 2), building.1 + (building.3 / 2));
// Place other items
let mut to_place: Vec<&str> = vec![
"npc_barkeep",
"npc_townsperson",
"npc_townsperson",
"npc_drunk",
"npc_drunk",
"npc_guard",
"prop_keg",
"prop_table",
"prop_chair",
"prop_chair",
];
self.random_building_spawn(building, build_data, rng, &mut to_place, player_idx);
}
fn build_temple(
&mut self,
building: &(i32, i32, i32, i32),
build_data: &mut BuilderMap,
rng: &mut rltk::RandomNumberGenerator,
) {
let mut to_place: Vec<&str> =
vec!["npc_priest", "prop_chair", "prop_chair", "prop_table", "prop_candle", "prop_candle"];
self.random_building_spawn(building, build_data, rng, &mut to_place, 0)
}
fn build_mine(
&mut self,
building: &(i32, i32, i32, i32),
build_data: &mut BuilderMap,
rng: &mut rltk::RandomNumberGenerator,
) {
// Place exit
let exit_idx = build_data.map.xy_idx(building.0 + (building.2 / 2), building.1 + (building.3 / 2));
build_data.map.tiles[exit_idx] = TileType::DownStair;
let mut to_place: Vec<&str> = vec!["npc_miner", "npc_miner", "npc_guard"];
self.random_building_spawn(building, build_data, rng, &mut to_place, exit_idx)
}
fn build_playerhouse(
&mut self,
building: &(i32, i32, i32, i32),
build_data: &mut BuilderMap,
rng: &mut rltk::RandomNumberGenerator,
) {
let mut to_place: Vec<&str> = vec!["prop_bed", "prop_table"];
self.random_building_spawn(building, build_data, rng, &mut to_place, 0);
}
fn build_npchouse(
&mut self,
building: &(i32, i32, i32, i32),
build_data: &mut BuilderMap,
rng: &mut rltk::RandomNumberGenerator,
) {
let mut to_place: Vec<&str> = vec!["prop_bed", "prop_table"];
self.random_building_spawn(building, build_data, rng, &mut to_place, 0);
}
fn build_abandoned(
&mut self,
building: &(i32, i32, i32, i32),
build_data: &mut BuilderMap,
rng: &mut rltk::RandomNumberGenerator,
) {
for y in building.1..building.1 + building.3 {
for x in building.0..building.0 + building.2 {
let idx = build_data.map.xy_idx(x, y);
if build_data.map.tiles[idx] == TileType::WoodFloor && idx != 0 && rng.roll_dice(1, 3) == 1 {
build_data.spawn_list.push((idx, "rat".to_string()));
}
}
}
}
fn grass_layer(&mut self, build_data: &mut BuilderMap) {
@ -69,7 +255,7 @@ impl TownBuilder {
let mut n = (rng.roll_dice(1, 65535) as f32) / 65535f32;
let mut water_width: Vec<i32> = Vec::new();
let variance = 5;
let minimum_width = variance + 10;
let minimum_width = variance + 5;
let shallow_width = 6;
let sand_width = shallow_width + 4;
@ -123,9 +309,9 @@ impl TownBuilder {
let idx = build_data.map.xy_idx(x, y);
build_data.map.tiles[idx] = TileType::Fence;
let idx = build_data.map.xy_idx(x, y + 1);
build_data.map.tiles[idx] = TileType::WoodFloor;
build_data.map.tiles[idx] = TileType::Bridge;
let idx = build_data.map.xy_idx(x, y + 2);
build_data.map.tiles[idx] = TileType::WoodFloor;
build_data.map.tiles[idx] = TileType::Bridge;
let idx = build_data.map.xy_idx(x, y + 3);
build_data.map.tiles[idx] = TileType::Fence;
}
@ -154,7 +340,7 @@ impl TownBuilder {
let mut available_building_tiles: HashSet<usize> = HashSet::new();
const BORDER: i32 = 4;
const OFFSET_FROM_LEFT: i32 = 30 + BORDER;
const OFFSET_FROM_LEFT: i32 = 25 + BORDER;
const PATH_OFFSET_FROM_CENTRE: i32 = 4;
const HALF_PATH_THICKNESS: i32 = 3;
@ -216,8 +402,8 @@ impl TownBuilder {
const BORDER: i32 = 2;
const REQUIRED_BUILDINGS: i32 = 8;
const OFFSET_FROM_LEFT: i32 = 30;
const MIN_BUILDING_SIZE: i32 = 4;
const OFFSET_FROM_LEFT: i32 = 25;
const MIN_BUILDING_SIZE: i32 = 6;
const MAX_BUILDING_SIZE: i32 = 10;
while n_buildings < REQUIRED_BUILDINGS {

View file

@ -1,7 +1,7 @@
use super::{
gamelog, BlocksTile, BlocksVisibility, CombatStats, Door, EntityMoved, Hidden, HungerClock, HungerState, Item, Map,
Monster, Name, ParticleBuilder, Player, Position, Renderable, RunState, State, SufferDamage, Telepath, TileType,
Viewshed, WantsToMelee, WantsToPickupItem,
gamelog, BlocksTile, BlocksVisibility, Bystander, CombatStats, Door, EntityMoved, Hidden, HungerClock, HungerState,
Item, Map, Monster, Name, ParticleBuilder, Player, Position, Renderable, RunState, State, SufferDamage, Telepath,
TileType, Viewshed, WantsToMelee, WantsToPickupItem,
};
use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode};
use specs::prelude::*;
@ -272,6 +272,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
let mut viewsheds = ecs.write_storage::<Viewshed>();
let mut telepaths = ecs.write_storage::<Telepath>();
let mut entity_moved = ecs.write_storage::<EntityMoved>();
let friendlies = ecs.read_storage::<Bystander>();
let combat_stats = ecs.read_storage::<CombatStats>();
let map = ecs.fetch::<Map>();
@ -279,6 +280,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
let mut wants_to_melee = ecs.write_storage::<WantsToMelee>();
let mut doors = ecs.write_storage::<Door>();
let names = ecs.read_storage::<Name>();
let mut swap_entities: Vec<(Entity, i32, i32)> = Vec::new();
for (entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() {
if pos.x + delta_x < 0
@ -291,10 +293,24 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y);
for potential_target in map.tile_content[destination_idx].iter() {
let target = combat_stats.get(*potential_target);
if let Some(_target) = target {
wants_to_melee.insert(entity, WantsToMelee { target: *potential_target }).expect("Add target failed.");
return true;
let friendly = friendlies.get(*potential_target);
if friendly.is_some() {
swap_entities.push((*potential_target, pos.x, pos.y));
pos.x = min(map.width - 1, max(0, pos.x + delta_x));
pos.y = min(map.height - 1, max(0, pos.y + delta_y));
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
viewshed.dirty = true;
let mut ppos = ecs.write_resource::<Point>();
ppos.x = pos.x;
ppos.y = pos.y;
} else {
let target = combat_stats.get(*potential_target);
if let Some(_target) = target {
wants_to_melee
.insert(entity, WantsToMelee { target: *potential_target })
.expect("Add target failed.");
return true;
}
}
let door = doors.get_mut(*potential_target);
if let Some(door) = door {
@ -307,6 +323,20 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
}
}
if swap_entities.len() > 0 {
for m in swap_entities.iter() {
let their_pos = positions.get_mut(m.0);
if let Some(name) = names.get(m.0) {
gamelog::Logger::new().append("You swap places with the").npc_name_n(&name.name).period().log();
}
if let Some(their_pos) = their_pos {
their_pos.x = m.1;
their_pos.y = m.2;
}
}
return true;
}
if map.blocked[destination_idx] {
gamelog::Logger::new().append("You can't move there.").log();
return false;

View file

@ -9,6 +9,8 @@ pub struct Mob {
pub flags: Option<Vec<String>>,
pub stats: MobStats,
pub vision_range: i32,
pub ai: String,
pub quips: Option<Vec<String>>,
}
#[derive(Deserialize, Debug)]

View file

@ -149,7 +149,11 @@ pub fn spawn_named_mob(
let mut eb = new_entity;
eb = spawn_position(pos, eb);
eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() });
eb = eb.with(Monster {});
match mob_template.ai.as_ref() {
"bystander" => eb = eb.with(Bystander {}),
"melee" => eb = eb.with(Monster {}),
_ => {}
}
let rolled_hp = roll_hit_dice(rng, 1, mob_template.stats.max_hp);
eb = eb.with(CombatStats {
max_hp: rolled_hp,
@ -172,6 +176,10 @@ pub fn spawn_named_mob(
}
}
if let Some(quips) = &mob_template.quips {
eb = eb.with(Quips { available: quips.clone() });
}
return Some(eb.build());
}
None
@ -207,6 +215,7 @@ pub fn spawn_named_prop(raws: &RawMaster, new_entity: EntityBuilder, key: &str,
"ENTRY_TRIGGER" => eb = eb.with(EntryTrigger {}),
"SINGLE_ACTIVATION" => eb = eb.with(SingleActivation {}),
"DOOR" => eb = eb.with(Door { open: false }),
"PROP" => eb = eb.with(Prop {}),
_ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())),
}
}

View file

@ -51,6 +51,7 @@ pub fn save_game(ecs: &mut World) {
Attributes,
BlocksTile,
BlocksVisibility,
Bystander,
CombatStats,
Confusion,
Consumable,
@ -76,8 +77,10 @@ pub fn save_game(ecs: &mut World) {
ParticleLifetime,
Player,
Position,
Prop,
ProvidesHealing,
ProvidesNutrition,
Quips,
Ranged,
Renderable,
SingleActivation,
@ -147,6 +150,7 @@ pub fn load_game(ecs: &mut World) {
Attributes,
BlocksTile,
BlocksVisibility,
Bystander,
CombatStats,
Confusion,
Consumable,
@ -172,8 +176,10 @@ pub fn load_game(ecs: &mut World) {
ParticleLifetime,
Player,
Position,
Prop,
ProvidesHealing,
ProvidesNutrition,
Quips,
Ranged,
Renderable,
SingleActivation,