data driven items

This commit is contained in:
Tyrel Souza 2021-08-31 23:14:41 -04:00
parent cb97acba42
commit 488e73ea46
9 changed files with 279 additions and 123 deletions

27
Cargo.lock generated
View File

@ -60,6 +60,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.5.2" version = "0.5.2"
@ -556,6 +562,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bracket-lib", "bracket-lib",
"legion", "legion",
"ron",
"serde",
] ]
[[package]] [[package]]
@ -1432,6 +1440,17 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ron"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a9d94cee22c8a4b5e2a1c7f9a20fdc315668ee8a75835949bb40d7456934634"
dependencies = [
"base64",
"bitflags",
"serde",
]
[[package]] [[package]]
name = "rusttype" name = "rusttype"
version = "0.9.2" version = "0.9.2"
@ -1480,18 +1499,18 @@ checksum = "a0eddf2e8f50ced781f288c19f18621fa72a3779e3cb58dbf23b07469b0abeb4"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.129" version = "1.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.129" version = "1.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -8,3 +8,5 @@ edition = "2018"
[dependencies] [dependencies]
bracket-lib = "~0.8.1" bracket-lib = "~0.8.1"
legion = "~0.3.1" legion = "~0.3.1"
serde = { version = "=1.0.115"}
ron = "=0.6.1"

64
resources/template.ron Normal file
View File

@ -0,0 +1,64 @@
Templates(
entities: [
Template(
entity_type: Item,
name: "Healing Potion",
glyph: '!',
levels : [0,1,2],
provides: Some([("Healing", 6)]),
frequency: 2
),
Template(
entity_type: Item,
name: "Dungoen Map",
glyph: '{',
levels : [0, 1, 2],
provides: Some([("MagicMap", 0)]),
frequency: 1
),
Template(
entity_type: Enemy,
name: "Goblin",
glyph: 'g',
levels : [0, 1, 2],
hp: Some(1),
frequency: 3,
base_damage: Some(1)
),
Template(
entity_type: Enemy,
name: "Orc",
glyph: 'o',
levels : [0, 1, 2],
hp: Some(2),
frequency: 2,
base_damage: Some(1)
),
Template(
entity_type: Enemy,
name: "Ogre",
glyph: 'O',
levels : [1, 2],
hp: Some(5),
frequency: 1,
base_damage: Some(2)
),
Template(
entity_type: Enemy,
name: "Ettin",
glyph: 'E',
levels : [2],
hp: Some(10),
frequency: 2,
base_damage: Some(3)
),
Template(
entity_type: Item,
name : "Rusty Sword",
glyph: '/',
levels : [0,1,2],
frequency: 2,
base_damage: Some(2)
)
]
)

View File

@ -87,3 +87,9 @@ pub struct ActivateItem {
pub used_by: Entity, pub used_by: Entity,
pub item: Entity, pub item: Entity,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Damage(pub i32);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Weapon;

View File

@ -79,12 +79,12 @@ impl State {
let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start); let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start);
map_builder.map.tiles[exit_idx] = TileType::Exit; map_builder.map.tiles[exit_idx] = TileType::Exit;
} }
spawn_level(
map_builder &mut self.ecs,
.monster_spawns &mut rng,
.iter() map_level as usize,
.for_each(|pos| spawn_entity(&mut self.ecs, &mut rng, *pos)); &map_builder.monster_spawns,
);
self.resources.insert(map_builder.map); self.resources.insert(map_builder.map);
self.resources.insert(Camera::new(map_builder.player_start)); self.resources.insert(Camera::new(map_builder.player_start));
self.resources.insert(TurnState::AwaitingInput); self.resources.insert(TurnState::AwaitingInput);
@ -101,10 +101,7 @@ impl State {
let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start); let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start);
map_builder.map.tiles[exit_idx] = TileType::Exit; map_builder.map.tiles[exit_idx] = TileType::Exit;
map_builder spawn_level(&mut ecs, &mut rng, 0, &map_builder.monster_spawns);
.monster_spawns
.iter()
.for_each(|pos| spawn_entity(&mut ecs, &mut rng, *pos));
resources.insert(map_builder.map); resources.insert(map_builder.map);
resources.insert(Camera::new(map_builder.player_start)); resources.insert(Camera::new(map_builder.player_start));
resources.insert(TurnState::AwaitingInput); resources.insert(TurnState::AwaitingInput);
@ -153,10 +150,7 @@ impl State {
//spawn_amulet_of_yala(&mut self.ecs, map_builder.amulet_start); //spawn_amulet_of_yala(&mut self.ecs, map_builder.amulet_start);
let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start); let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start);
map_builder.map.tiles[exit_idx] = TileType::Exit; map_builder.map.tiles[exit_idx] = TileType::Exit;
map_builder spawn_level(&mut self.ecs, &mut rng, 0, &map_builder.monster_spawns);
.monster_spawns
.iter()
.for_each(|pos| spawn_entity(&mut self.ecs, &mut rng, *pos));
self.resources.insert(map_builder.map); self.resources.insert(map_builder.map);
self.resources.insert(Camera::new(map_builder.player_start)); self.resources.insert(Camera::new(map_builder.player_start));
self.resources.insert(TurnState::AwaitingInput); self.resources.insert(TurnState::AwaitingInput);

View File

@ -1,93 +0,0 @@
pub use crate::prelude::*;
pub fn spawn_player(ecs: &mut World, pos: Point) {
ecs.push((
Player { map_level: 0 },
pos,
Render {
color: ColorPair::new(WHITE, BLACK),
glyph: to_cp437('@'),
},
Health {
current: 10,
max: 10,
},
FieldOfView::new(8),
));
}
pub fn spawn_monster(ecs: &mut World, rng: &mut RandomNumberGenerator, pos: Point) {
let (hp, name, glyph) = match rng.roll_dice(1, 10) {
1..=8 => goblin(),
_ => orc(),
};
ecs.push((
Enemy,
pos,
Render {
color: ColorPair::new(WHITE, BLACK),
glyph,
},
ChasingPlayer {},
Health {
current: hp,
max: hp,
},
Name(name),
FieldOfView::new(6),
));
}
fn goblin() -> (i32, String, FontCharType) {
(1, "Goblin".to_string(), to_cp437('g'))
}
fn orc() -> (i32, String, FontCharType) {
(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()),
));
}
pub fn spawn_healing_potion(ecs: &mut World, pos: Point) {
ecs.push((
Item,
pos,
Render {
color: ColorPair::new(WHITE, BLACK),
glyph: to_cp437('!'),
},
Name("Healing Potion".to_string()),
ProvidesHealing { amount: 6 },
));
}
pub fn spawn_magic_mapper(ecs: &mut World, pos: Point) {
ecs.push((
Item,
pos,
Render {
color: ColorPair::new(WHITE, BLACK),
glyph: to_cp437('{'),
},
Name("Magic Map".to_string()),
ProvidesDungeonMap {},
));
}
pub fn spawn_entity(ecs: &mut World, rng: &mut RandomNumberGenerator, pos: Point) {
let roll = rng.roll_dice(1, 6);
match roll {
1 => spawn_healing_potion(ecs, pos),
2 => spawn_magic_mapper(ecs, pos),
_ => spawn_monster(ecs, rng, pos),
}
}

44
src/spawners/mod.rs Normal file
View File

@ -0,0 +1,44 @@
mod template;
use template::*;
pub use crate::prelude::*;
pub fn spawn_level(
ecs: &mut World,
rng: &mut RandomNumberGenerator,
level: usize,
spawn_points: &[Point],
) {
let template = Templates::load();
template.spawn_entities(ecs, rng, level, spawn_points);
}
pub fn spawn_player(ecs: &mut World, pos: Point) {
ecs.push((
Player { map_level: 0 },
pos,
Render {
color: ColorPair::new(WHITE, BLACK),
glyph: to_cp437('@'),
},
Health {
current: 10,
max: 10,
},
FieldOfView::new(8),
Damage(1),
));
}
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()),
));
}

107
src/spawners/template.rs Normal file
View File

@ -0,0 +1,107 @@
pub use crate::prelude::*;
use legion::systems::CommandBuffer;
use ron::de::from_reader;
use serde::Deserialize;
use std::collections::HashSet;
use std::fs::File;
#[derive(Clone, Deserialize, Debug)]
pub struct Template {
pub entity_type: EntityType,
pub levels: HashSet<usize>,
pub frequency: i32,
pub name: String,
pub glyph: char,
pub provides: Option<Vec<(String, i32)>>,
pub hp: Option<i32>,
pub base_damage: Option<i32>,
}
#[derive(Clone, Deserialize, Debug, PartialEq)]
pub enum EntityType {
Enemy,
Item,
}
#[derive(Clone, Deserialize, Debug)]
pub struct Templates {
pub entities: Vec<Template>,
}
impl Templates {
pub fn load() -> Self {
let file = File::open("resources/template.ron").expect("Failed to open template file");
from_reader(file).expect("Unable to load templates")
}
pub fn spawn_entities(
&self,
ecs: &mut World,
rng: &mut RandomNumberGenerator,
level: usize,
spawn_points: &[Point],
) {
let mut available_entities = Vec::new();
self.entities
.iter()
.filter(|e| e.levels.contains(&level))
.for_each(|t| {
for _ in 0..t.frequency {
available_entities.push(t);
}
});
let mut commands = CommandBuffer::new(ecs);
spawn_points.iter().for_each(|pt| {
if let Some(entity) = rng.random_slice_entry(&available_entities) {
self.spawn_entity(pt, entity, &mut commands);
}
});
commands.flush(ecs);
}
fn spawn_entity(
&self,
pt: &Point,
template: &Template,
commands: &mut legion::systems::CommandBuffer,
) {
let entity = commands.push((
pt.clone(),
Render {
color: ColorPair::new(WHITE, BLACK),
glyph: to_cp437(template.glyph),
},
Name(template.name.clone()),
));
match template.entity_type {
EntityType::Item => commands.add_component(entity, Item {}),
EntityType::Enemy => {
commands.add_component(entity, Enemy {});
commands.add_component(entity, FieldOfView::new(6));
commands.add_component(entity, ChasingPlayer {});
commands.add_component(
entity,
Health {
current: template.hp.unwrap(),
max: template.hp.unwrap(),
},
);
}
}
if let Some(effects) = &template.provides {
effects
.iter()
.for_each(|(provides, n)| match provides.as_str() {
"Healing" => commands.add_component(entity, ProvidesHealing { amount: *n }),
"MagicMap" => commands.add_component(entity, ProvidesDungeonMap {}),
_ => {
println!("We don't know how to provide: {}", provides)
}
});
}
if let Some(damage) = &template.base_damage {
commands.add_component(entity, Damage(*damage));
if template.entity_type == EntityType::Item {
commands.add_component(entity, Weapon {});
}
}
}
}

View File

@ -4,29 +4,42 @@ use crate::prelude::*;
#[read_component(WantsToAttack)] #[read_component(WantsToAttack)]
#[read_component(Player)] #[read_component(Player)]
#[write_component(Health)] #[write_component(Health)]
#[read_component(Damage)]
#[read_component(Carried)]
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();
let victims: Vec<(Entity, Entity)> = attackers let victims: Vec<(Entity, Entity, Entity)> = attackers
.iter(ecs) .iter(ecs)
.map(|(entity, attack)| (*entity, attack.victim)) .map(|(entity, attack)| (*entity, attack.attacker, attack.victim))
.collect(); .collect();
victims.iter().for_each(|(message, victim)| { victims.iter().for_each(|(message, attacker, victim)| {
let is_player = ecs let is_player = ecs
.entry_ref(*victim) .entry_ref(*victim)
.unwrap() .unwrap()
.get_component::<Player>() .get_component::<Player>()
.is_ok(); .is_ok();
if let Ok(mut health) = ecs let base_damage = if let Ok(v) = ecs.entry_ref(*attacker) {
.entry_mut(*victim) if let Ok(dmg) = v.get_component::<Damage>() {
.unwrap() dmg.0
.get_component_mut::<Health>() } else {
{ 0
println!("Health before attack: {}", health.current); }
health.current -= 1; } else {
0
};
let weapon_damage: i32 = <(&Carried, &Damage)>::query()
.iter(ecs)
.filter(|(carried, _)| carried.0 == *attacker)
.map(|(_, dmg)| dmg.0)
.sum();
let final_damage = base_damage + weapon_damage;
if let Ok(mut health) = ecs.entry_mut(*victim).unwrap().get_component::<Health>() {
health.current -= final_damage;
if health.current < 1 && !is_player { if health.current < 1 && !is_player {
commands.remove(*victim); commands.remove(*victim);
} }
println!("Health after attack: {}", health.current);
} }
commands.remove(*message); commands.remove(*message);
}); });