Add clothing to player and NPCs
This commit is contained in:
parent
d96d1ce003
commit
5b227115db
280
raws/spawns.json
280
raws/spawns.json
@ -184,6 +184,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Dried Sausage",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "%",
|
||||||
|
"fg": "#00FF00",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"consumable": {
|
||||||
|
"effects": {
|
||||||
|
"food": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Beer",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "!",
|
||||||
|
"fg": "#FF00FF",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"consumable": {
|
||||||
|
"effects": {
|
||||||
|
"provides_healing": "4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rusty Longsword",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "/",
|
||||||
|
"fg": "#BB77BB",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"weapon": {
|
||||||
|
"range": "melee",
|
||||||
|
"attribute": "Might",
|
||||||
|
"base_damage": "1d8-1",
|
||||||
|
"hit_bonus": -1
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Dagger",
|
"name": "Dagger",
|
||||||
"renderable": {
|
"renderable": {
|
||||||
@ -194,7 +237,9 @@
|
|||||||
},
|
},
|
||||||
"weapon": {
|
"weapon": {
|
||||||
"range": "melee",
|
"range": "melee",
|
||||||
"power_bonus": 2
|
"attribute": "Quickness",
|
||||||
|
"base_damage": "1d4",
|
||||||
|
"hit_bonus": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -207,7 +252,9 @@
|
|||||||
},
|
},
|
||||||
"weapon": {
|
"weapon": {
|
||||||
"range": "melee",
|
"range": "melee",
|
||||||
"power_bonus": 4
|
"attribute": "Might",
|
||||||
|
"base_damage": "1d8",
|
||||||
|
"hit_bonus": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -220,7 +267,9 @@
|
|||||||
},
|
},
|
||||||
"weapon": {
|
"weapon": {
|
||||||
"range": "melee",
|
"range": "melee",
|
||||||
"power_bonus": 5
|
"attribute": "Might",
|
||||||
|
"base_damage": "1d8+1",
|
||||||
|
"hit_bonus": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -231,8 +280,9 @@
|
|||||||
"bg": "#000000",
|
"bg": "#000000",
|
||||||
"order": 2
|
"order": 2
|
||||||
},
|
},
|
||||||
"shield": {
|
"wearable": {
|
||||||
"defense_bonus": 1
|
"slot": "Shield",
|
||||||
|
"armor_class": 1.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -243,8 +293,102 @@
|
|||||||
"bg": "#000000",
|
"bg": "#000000",
|
||||||
"order": 2
|
"order": 2
|
||||||
},
|
},
|
||||||
"shield": {
|
"wearable": {
|
||||||
"defense_bonus": 3
|
"slot": "Shield",
|
||||||
|
"armor_class": 2.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stained Tunic",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "[",
|
||||||
|
"fg": "#00FF00",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"wearable": {
|
||||||
|
"slot": "Torso",
|
||||||
|
"armor_class": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Torn Trousers",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "[",
|
||||||
|
"fg": "#00FFFF",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"wearable": {
|
||||||
|
"slot": "Legs",
|
||||||
|
"armor_class": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Old Boots",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "[",
|
||||||
|
"fg": "#FF9999",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"wearable": {
|
||||||
|
"slot": "Legs",
|
||||||
|
"armor_class": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cudgel",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "/",
|
||||||
|
"fg": "#A52A2A",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"weapon": {
|
||||||
|
"range": "melee",
|
||||||
|
"attribute": "Quickness",
|
||||||
|
"base_damage": "1d4",
|
||||||
|
"hit_bonus": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cloth Tunic",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "[",
|
||||||
|
"fg": "#00FF00",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"wearable": {
|
||||||
|
"slot": "Torso",
|
||||||
|
"armor_class": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cloth Pants",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "[",
|
||||||
|
"fg": "#00FFFF",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"wearable": {
|
||||||
|
"slot": "Legs",
|
||||||
|
"armor_class": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Slippers",
|
||||||
|
"renderable": {
|
||||||
|
"glyph": "[",
|
||||||
|
"fg": "#FF9999",
|
||||||
|
"bg": "#000000",
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
"wearable": {
|
||||||
|
"slot": "Legs",
|
||||||
|
"armor_class": 0.1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -265,7 +409,13 @@
|
|||||||
},
|
},
|
||||||
"skills": {
|
"skills": {
|
||||||
"Melee": 2
|
"Melee": 2
|
||||||
}
|
},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Shady Salesman",
|
"name": "Shady Salesman",
|
||||||
@ -278,7 +428,13 @@
|
|||||||
"blocks_tile": true,
|
"blocks_tile": true,
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "vendor",
|
"ai": "vendor",
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Patron",
|
"name": "Patron",
|
||||||
@ -296,7 +452,13 @@
|
|||||||
"Oh my, I drank too much.",
|
"Oh my, I drank too much.",
|
||||||
"Still saving the world, eh?"
|
"Still saving the world, eh?"
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Priest",
|
"name": "Priest",
|
||||||
@ -309,7 +471,13 @@
|
|||||||
"blocks_tile": true,
|
"blocks_tile": true,
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "bystander",
|
"ai": "bystander",
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Parishioner",
|
"name": "Parishioner",
|
||||||
@ -327,7 +495,13 @@
|
|||||||
"I hear there's going to be a good sermon on tea",
|
"I hear there's going to be a good sermon on tea",
|
||||||
"Want some cake?"
|
"Want some cake?"
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Blacksmith",
|
"name": "Blacksmith",
|
||||||
@ -340,7 +514,13 @@
|
|||||||
"blocks_tile": true,
|
"blocks_tile": true,
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "vendor",
|
"ai": "vendor",
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Clothier",
|
"name": "Clothier",
|
||||||
@ -353,7 +533,13 @@
|
|||||||
"blocks_tile": true,
|
"blocks_tile": true,
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "vendor",
|
"ai": "vendor",
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Alchemist",
|
"name": "Alchemist",
|
||||||
@ -366,7 +552,13 @@
|
|||||||
"blocks_tile": true,
|
"blocks_tile": true,
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "vendor",
|
"ai": "vendor",
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Mom",
|
"name": "Mom",
|
||||||
@ -385,7 +577,13 @@
|
|||||||
"Be careful in the dungeon!",
|
"Be careful in the dungeon!",
|
||||||
"Your father would be so proud, were he here."
|
"Your father would be so proud, were he here."
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Peasant",
|
"name": "Peasant",
|
||||||
@ -401,7 +599,13 @@
|
|||||||
"quips": [
|
"quips": [
|
||||||
"Why are you in my house?"
|
"Why are you in my house?"
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Dock Worker",
|
"name": "Dock Worker",
|
||||||
@ -419,7 +623,13 @@
|
|||||||
"Nice weather",
|
"Nice weather",
|
||||||
"Hello"
|
"Hello"
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Fisher",
|
"name": "Fisher",
|
||||||
@ -437,7 +647,13 @@
|
|||||||
"I caught something, but it wasn't a fish!",
|
"I caught something, but it wasn't a fish!",
|
||||||
"Looks like rain"
|
"Looks like rain"
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Wannabe Pirate",
|
"name": "Wannabe Pirate",
|
||||||
@ -455,7 +671,13 @@
|
|||||||
"Grog!",
|
"Grog!",
|
||||||
"Booze!"
|
"Booze!"
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Drunk",
|
"name": "Drunk",
|
||||||
@ -473,7 +695,13 @@
|
|||||||
"Need... more... booze!",
|
"Need... more... booze!",
|
||||||
"Spare a copper?"
|
"Spare a copper?"
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {},
|
||||||
|
"equipped": [
|
||||||
|
"Cudgel",
|
||||||
|
"Cloth Tunic",
|
||||||
|
"Cloth Pants",
|
||||||
|
"Slippers"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Rat",
|
"name": "Rat",
|
||||||
@ -493,6 +721,16 @@
|
|||||||
"skills": {
|
"skills": {
|
||||||
"Melee": -1,
|
"Melee": -1,
|
||||||
"Defense": -1
|
"Defense": -1
|
||||||
|
},
|
||||||
|
"natural": {
|
||||||
|
"armor_class": 11,
|
||||||
|
"attacks": [
|
||||||
|
{
|
||||||
|
"name": "bite",
|
||||||
|
"hit_bonus": 0,
|
||||||
|
"damage": "1d4"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -165,6 +165,11 @@ pub struct WantsToRemoveItem {
|
|||||||
pub enum EquipmentSlot {
|
pub enum EquipmentSlot {
|
||||||
Melee,
|
Melee,
|
||||||
Shield,
|
Shield,
|
||||||
|
Head,
|
||||||
|
Torso,
|
||||||
|
Legs,
|
||||||
|
Feet,
|
||||||
|
Hands,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
@ -194,8 +199,9 @@ pub struct MeleeWeapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
pub struct DefenseBonus {
|
pub struct Wearable {
|
||||||
pub defense: i32,
|
pub armor_class: f32,
|
||||||
|
pub slot: EquipmentSlot,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
|
@ -519,7 +519,7 @@ fn main() -> ::rltk::BError {
|
|||||||
Equippable,
|
Equippable,
|
||||||
Equipped,
|
Equipped,
|
||||||
MeleeWeapon,
|
MeleeWeapon,
|
||||||
DefenseBonus,
|
Wearable,
|
||||||
WantsToRemoveItem,
|
WantsToRemoveItem,
|
||||||
ParticleLifetime,
|
ParticleLifetime,
|
||||||
HungerClock,
|
HungerClock,
|
||||||
|
@ -2,12 +2,13 @@ use ::rltk::{RandomNumberGenerator, RGB};
|
|||||||
use ::specs::prelude::*;
|
use ::specs::prelude::*;
|
||||||
|
|
||||||
use crate::components::{
|
use crate::components::{
|
||||||
Attributes, HungerClock, HungerState, Name, Pools, Skill, Skills, SufferDamage, WantsToMelee,
|
Attributes, Equipped, HungerClock, HungerState, MeleeWeapon, Name, Pools, Skill, Skills,
|
||||||
|
SufferDamage, WantsToMelee, Wearable,
|
||||||
};
|
};
|
||||||
use crate::game_log::GameLog;
|
use crate::game_log::GameLog;
|
||||||
use crate::gamesystem::skill_bonus;
|
use crate::gamesystem::skill_bonus;
|
||||||
use crate::particle_system::ParticleBuilder;
|
use crate::particle_system::ParticleBuilder;
|
||||||
use crate::Position;
|
use crate::{EquipmentSlot, Position, WeaponAttribute};
|
||||||
|
|
||||||
pub struct MeleeCombatSystem {}
|
pub struct MeleeCombatSystem {}
|
||||||
|
|
||||||
@ -26,6 +27,9 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||||||
ReadStorage<'a, HungerClock>,
|
ReadStorage<'a, HungerClock>,
|
||||||
ReadStorage<'a, Pools>,
|
ReadStorage<'a, Pools>,
|
||||||
WriteExpect<'a, RandomNumberGenerator>,
|
WriteExpect<'a, RandomNumberGenerator>,
|
||||||
|
ReadStorage<'a, Equipped>,
|
||||||
|
ReadStorage<'a, MeleeWeapon>,
|
||||||
|
ReadStorage<'a, Wearable>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
@ -42,6 +46,9 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||||||
hunger_clock,
|
hunger_clock,
|
||||||
pools,
|
pools,
|
||||||
mut rng,
|
mut rng,
|
||||||
|
equipped_items,
|
||||||
|
meleeweapons,
|
||||||
|
wearables,
|
||||||
) = data;
|
) = data;
|
||||||
|
|
||||||
for (entity, wants_melee, name, attacker_attributes, attacker_skills, attacker_pools) in (
|
for (entity, wants_melee, name, attacker_attributes, attacker_skills, attacker_pools) in (
|
||||||
@ -61,10 +68,28 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||||||
if attacker_pools.hit_points.current > 0 && target_pools.hit_points.current > 0 {
|
if attacker_pools.hit_points.current > 0 && target_pools.hit_points.current > 0 {
|
||||||
let target_name = names.get(wants_melee.target).unwrap();
|
let target_name = names.get(wants_melee.target).unwrap();
|
||||||
|
|
||||||
|
let mut weapon_info = MeleeWeapon {
|
||||||
|
attribute: WeaponAttribute::Might,
|
||||||
|
hit_bonus: 0,
|
||||||
|
damage_n_dice: 1,
|
||||||
|
damage_die_type: 4,
|
||||||
|
damage_bonus: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (wielded, melee) in (&equipped_items, &meleeweapons).join() {
|
||||||
|
if wielded.owner == entity && wielded.slot == EquipmentSlot::Melee {
|
||||||
|
weapon_info = melee.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let natural_roll = rng.roll_dice(1, 20);
|
let natural_roll = rng.roll_dice(1, 20);
|
||||||
let attribute_hit_bonus = attacker_attributes.might.bonus;
|
let attribute_hit_bonus = if weapon_info.attribute == WeaponAttribute::Might {
|
||||||
|
attacker_attributes.might.bonus
|
||||||
|
} else {
|
||||||
|
attacker_attributes.quickness.bonus
|
||||||
|
};
|
||||||
let skill_hit_bonus = skill_bonus(Skill::Melee, &*attacker_skills);
|
let skill_hit_bonus = skill_bonus(Skill::Melee, &*attacker_skills);
|
||||||
let weapon_hit_bonus = 0; // TODO: Once weapons support this
|
let weapon_hit_bonus = weapon_info.hit_bonus;
|
||||||
let mut status_hit_bonus = 0;
|
let mut status_hit_bonus = 0;
|
||||||
if let Some(hc) = hunger_clock.get(entity) {
|
if let Some(hc) = hunger_clock.get(entity) {
|
||||||
// Well-Fed grants +1
|
// Well-Fed grants +1
|
||||||
@ -78,10 +103,21 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||||||
+ weapon_hit_bonus
|
+ weapon_hit_bonus
|
||||||
+ status_hit_bonus;
|
+ status_hit_bonus;
|
||||||
|
|
||||||
|
let mut armor_item_bonus_f = 0.0;
|
||||||
|
for (wielded, armor) in (&equipped_items, &wearables).join() {
|
||||||
|
if wielded.owner == wants_melee.target {
|
||||||
|
armor_item_bonus_f += armor.armor_class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let base_armor_class = match natural.get(wants_melee.target) {
|
||||||
|
// None => 10,
|
||||||
|
// Some(nat) = nat.armor_class.unwrap_or(10);
|
||||||
|
// };
|
||||||
let base_armor_class = 10;
|
let base_armor_class = 10;
|
||||||
let armor_quickness_bonus = target_attributes.quickness.bonus;
|
let armor_quickness_bonus = target_attributes.quickness.bonus;
|
||||||
let armor_skill_bonus = skill_bonus(Skill::Defense, &*target_skills);
|
let armor_skill_bonus = skill_bonus(Skill::Defense, &*target_skills);
|
||||||
let armor_item_bonus = 0; // TODO: Once armor supports this
|
let armor_item_bonus = armor_item_bonus_f as i32;
|
||||||
let armor_class =
|
let armor_class =
|
||||||
base_armor_class + armor_quickness_bonus + armor_skill_bonus + armor_item_bonus;
|
base_armor_class + armor_quickness_bonus + armor_skill_bonus + armor_item_bonus;
|
||||||
|
|
||||||
@ -90,7 +126,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||||||
let base_damage = rng.roll_dice(1, 4);
|
let base_damage = rng.roll_dice(1, 4);
|
||||||
let attr_damage_bonus = attacker_attributes.might.bonus;
|
let attr_damage_bonus = attacker_attributes.might.bonus;
|
||||||
let skill_damage_bonus = skill_bonus(Skill::Melee, &*attacker_skills);
|
let skill_damage_bonus = skill_bonus(Skill::Melee, &*attacker_skills);
|
||||||
let weapon_damage_bonus = 0;
|
let weapon_damage_bonus = weapon_info.damage_bonus;
|
||||||
|
|
||||||
let damage = i32::max(
|
let damage = i32::max(
|
||||||
0,
|
0,
|
||||||
|
@ -8,7 +8,7 @@ pub struct Item {
|
|||||||
pub renderable: Option<Renderable>,
|
pub renderable: Option<Renderable>,
|
||||||
pub consumable: Option<Consumable>,
|
pub consumable: Option<Consumable>,
|
||||||
pub weapon: Option<Weapon>,
|
pub weapon: Option<Weapon>,
|
||||||
pub shield: Option<Shield>,
|
pub wearable: Option<Wearable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@ -33,6 +33,7 @@ pub struct Weapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct Shield {
|
pub struct Wearable {
|
||||||
pub defense_bonus: i32,
|
pub armor_class: f32,
|
||||||
|
pub slot: String,
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ pub struct Mob {
|
|||||||
pub level: Option<i32>,
|
pub level: Option<i32>,
|
||||||
pub hp: Option<i32>,
|
pub hp: Option<i32>,
|
||||||
pub mana: Option<i32>,
|
pub mana: Option<i32>,
|
||||||
|
pub equipped: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
|
|||||||
|
|
||||||
use ::regex::Regex;
|
use ::regex::Regex;
|
||||||
use ::specs::prelude::*;
|
use ::specs::prelude::*;
|
||||||
|
use ::specs::saveload::{MarkedBuilder, SimpleMarker};
|
||||||
|
|
||||||
use crate::components::*;
|
use crate::components::*;
|
||||||
use crate::gamesystem::{mana_at_level, npc_hp};
|
use crate::gamesystem::{mana_at_level, npc_hp};
|
||||||
@ -16,7 +17,7 @@ pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) {
|
|||||||
let mut die_type = 4;
|
let mut die_type = 4;
|
||||||
let mut die_bonus = 0;
|
let mut die_bonus = 0;
|
||||||
|
|
||||||
for cap in DIC_RE.captures_iter(dice) {
|
for cap in DICE_RE.captures_iter(dice) {
|
||||||
if let Some(group) = cap.get(1) {
|
if let Some(group) = cap.get(1) {
|
||||||
n_dice = group.as_str().parse::<i32>().expect("Not a digit");
|
n_dice = group.as_str().parse::<i32>().expect("Not a digit");
|
||||||
}
|
}
|
||||||
@ -33,6 +34,8 @@ pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) {
|
|||||||
|
|
||||||
pub enum SpawnType {
|
pub enum SpawnType {
|
||||||
AtPosition { x: i32, y: i32 },
|
AtPosition { x: i32, y: i32 },
|
||||||
|
Equipped { by: Entity },
|
||||||
|
Carried { by: Entity },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawMaster {
|
pub struct RawMaster {
|
||||||
@ -103,17 +106,40 @@ impl RawMaster {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_position(pos: SpawnType, new_entity: EntityBuilder) -> EntityBuilder {
|
fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot {
|
||||||
let mut eb = new_entity;
|
if !raws.item_index.contains_key(tag) {
|
||||||
|
panic!("Trying to equip an unknown item: {}", tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
let item_index = raws.item_index[tag];
|
||||||
|
let item = &raws.raws.items[item_index];
|
||||||
|
|
||||||
|
if item.weapon.is_some() {
|
||||||
|
return EquipmentSlot::Melee;
|
||||||
|
} else if let Some(wearable) = &item.wearable {
|
||||||
|
return string_to_slot(&wearable.slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("Trying to equip {}, but it has not slot tag", tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_position<'a>(
|
||||||
|
pos: SpawnType,
|
||||||
|
new_entity: EntityBuilder<'a>,
|
||||||
|
tag: &str,
|
||||||
|
raws: &RawMaster,
|
||||||
|
) -> EntityBuilder<'a> {
|
||||||
|
let eb = new_entity;
|
||||||
|
|
||||||
// Spawn in the specified location
|
// Spawn in the specified location
|
||||||
match pos {
|
match pos {
|
||||||
SpawnType::AtPosition { x, y } => {
|
SpawnType::AtPosition { x, y } => eb.with(Position { x, y }),
|
||||||
eb = eb.with(Position { x, y });
|
SpawnType::Carried { by } => eb.with(InBackpack { owner: by }),
|
||||||
|
SpawnType::Equipped { by } => {
|
||||||
|
let slot = find_slot_for_equippable_item(tag, raws);
|
||||||
|
eb.with(Equipped { owner: by, slot })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eb
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_renderable_component(
|
fn get_renderable_component(
|
||||||
@ -127,19 +153,36 @@ fn get_renderable_component(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_to_slot(slot: &str) -> EquipmentSlot {
|
||||||
|
match slot {
|
||||||
|
"Shield" => EquipmentSlot::Shield,
|
||||||
|
"Head" => EquipmentSlot::Head,
|
||||||
|
"Torso" => EquipmentSlot::Torso,
|
||||||
|
"Legs" => EquipmentSlot::Legs,
|
||||||
|
"Feet" => EquipmentSlot::Feet,
|
||||||
|
"Hands" => EquipmentSlot::Hands,
|
||||||
|
"Melee" => EquipmentSlot::Melee,
|
||||||
|
_ => {
|
||||||
|
rltk::console::log(format!("Warning: unknown equipment slot type [{}]", slot));
|
||||||
|
|
||||||
|
EquipmentSlot::Melee
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn_named_item(
|
pub fn spawn_named_item(
|
||||||
raws: &RawMaster,
|
raws: &RawMaster,
|
||||||
new_entity: EntityBuilder,
|
ecs: &mut World,
|
||||||
key: &str,
|
key: &str,
|
||||||
pos: SpawnType,
|
pos: SpawnType,
|
||||||
) -> Option<Entity> {
|
) -> Option<Entity> {
|
||||||
if raws.item_index.contains_key(key) {
|
if raws.item_index.contains_key(key) {
|
||||||
let item_template = &raws.raws.items[raws.item_index[key]];
|
let item_template = &raws.raws.items[raws.item_index[key]];
|
||||||
|
|
||||||
let mut eb = new_entity;
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
||||||
|
|
||||||
// Spawn in the specified location
|
// Spawn in the specified location
|
||||||
eb = spawn_position(pos, eb);
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
|
|
||||||
// Renderable
|
// Renderable
|
||||||
if let Some(renderable) = &item_template.renderable {
|
if let Some(renderable) = &item_template.renderable {
|
||||||
@ -196,17 +239,29 @@ pub fn spawn_named_item(
|
|||||||
eb = eb.with(Equippable {
|
eb = eb.with(Equippable {
|
||||||
slot: EquipmentSlot::Melee,
|
slot: EquipmentSlot::Melee,
|
||||||
});
|
});
|
||||||
eb = eb.with(MeleePowerBonus {
|
let (n_dice, die_type, bonus) = parse_dice_string(&weapon.base_damage);
|
||||||
power: weapon.power_bonus,
|
let mut wpn = MeleeWeapon {
|
||||||
});
|
attribute: WeaponAttribute::Might,
|
||||||
|
damage_n_dice: n_dice,
|
||||||
|
damage_die_type: die_type,
|
||||||
|
damage_bonus: bonus,
|
||||||
|
hit_bonus: weapon.hit_bonus,
|
||||||
|
};
|
||||||
|
|
||||||
|
wpn.attribute = match weapon.attribute.as_str() {
|
||||||
|
"Quickness" => WeaponAttribute::Quickness,
|
||||||
|
_ => WeaponAttribute::Might,
|
||||||
|
};
|
||||||
|
|
||||||
|
eb = eb.with(wpn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(shield) = &item_template.shield {
|
if let Some(wearable) = &item_template.wearable {
|
||||||
eb = eb.with(Equippable {
|
let slot = string_to_slot(&wearable.slot);
|
||||||
slot: EquipmentSlot::Shield,
|
eb = eb.with(Equippable { slot });
|
||||||
});
|
eb = eb.with(Wearable {
|
||||||
eb = eb.with(DefenseBonus {
|
slot,
|
||||||
defense: shield.defense_bonus,
|
armor_class: wearable.armor_class,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,17 +273,17 @@ pub fn spawn_named_item(
|
|||||||
|
|
||||||
pub fn spawn_named_mob(
|
pub fn spawn_named_mob(
|
||||||
raws: &RawMaster,
|
raws: &RawMaster,
|
||||||
new_entity: EntityBuilder,
|
ecs: &mut World,
|
||||||
key: &str,
|
key: &str,
|
||||||
pos: SpawnType,
|
pos: SpawnType,
|
||||||
) -> Option<Entity> {
|
) -> Option<Entity> {
|
||||||
if raws.mob_index.contains_key(key) {
|
if raws.mob_index.contains_key(key) {
|
||||||
let mob_template = &raws.raws.mobs[raws.mob_index[key]];
|
let mob_template = &raws.raws.mobs[raws.mob_index[key]];
|
||||||
|
|
||||||
let mut eb = new_entity;
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
||||||
|
|
||||||
// Spawn in the specified location
|
// Spawn in the specified location
|
||||||
eb = spawn_position(pos, eb);
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
|
|
||||||
// Renderable
|
// Renderable
|
||||||
if let Some(renderable) = &mob_template.renderable {
|
if let Some(renderable) = &mob_template.renderable {
|
||||||
@ -251,6 +306,10 @@ pub fn spawn_named_mob(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mob_template.blocks_tile {
|
||||||
|
eb = eb.with(BlocksTile {});
|
||||||
|
}
|
||||||
|
|
||||||
let mut mob_fitness = 11;
|
let mut mob_fitness = 11;
|
||||||
let mut mob_int = 11;
|
let mut mob_int = 11;
|
||||||
let mut attr = Attributes {
|
let mut attr = Attributes {
|
||||||
@ -318,17 +377,22 @@ pub fn spawn_named_mob(
|
|||||||
}
|
}
|
||||||
eb = eb.with(skills);
|
eb = eb.with(skills);
|
||||||
|
|
||||||
if mob_template.blocks_tile {
|
|
||||||
eb = eb.with(BlocksTile {});
|
|
||||||
}
|
|
||||||
|
|
||||||
eb = eb.with(Viewshed {
|
eb = eb.with(Viewshed {
|
||||||
visible_tiles: Vec::new(),
|
visible_tiles: Vec::new(),
|
||||||
range: mob_template.vision_range,
|
range: mob_template.vision_range,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Some(eb.build());
|
let new_mob = eb.build();
|
||||||
|
|
||||||
|
// Are they weilding anything?
|
||||||
|
if let Some(wielding) = &mob_template.equipped {
|
||||||
|
for tag in wielding.iter() {
|
||||||
|
spawn_named_entity(raws, ecs, tag, SpawnType::Equipped { by: new_mob });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(new_mob);
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -336,17 +400,17 @@ pub fn spawn_named_mob(
|
|||||||
|
|
||||||
pub fn spawn_named_prop(
|
pub fn spawn_named_prop(
|
||||||
raws: &RawMaster,
|
raws: &RawMaster,
|
||||||
new_entity: EntityBuilder,
|
ecs: &mut World,
|
||||||
key: &str,
|
key: &str,
|
||||||
pos: SpawnType,
|
pos: SpawnType,
|
||||||
) -> Option<Entity> {
|
) -> Option<Entity> {
|
||||||
if raws.prop_index.contains_key(key) {
|
if raws.prop_index.contains_key(key) {
|
||||||
let prop_template = &raws.raws.props[raws.prop_index[key]];
|
let prop_template = &raws.raws.props[raws.prop_index[key]];
|
||||||
|
|
||||||
let mut eb = new_entity;
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
||||||
|
|
||||||
// Spawn in the specified location
|
// Spawn in the specified location
|
||||||
eb = spawn_position(pos, eb);
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
|
|
||||||
// Renderable
|
// Renderable
|
||||||
if let Some(renderable) = &prop_template.renderable {
|
if let Some(renderable) = &prop_template.renderable {
|
||||||
@ -396,16 +460,16 @@ pub fn spawn_named_prop(
|
|||||||
|
|
||||||
pub fn spawn_named_entity(
|
pub fn spawn_named_entity(
|
||||||
raws: &RawMaster,
|
raws: &RawMaster,
|
||||||
new_entity: EntityBuilder,
|
ecs: &mut World,
|
||||||
key: &str,
|
key: &str,
|
||||||
pos: SpawnType,
|
pos: SpawnType,
|
||||||
) -> Option<Entity> {
|
) -> Option<Entity> {
|
||||||
if raws.item_index.contains_key(key) {
|
if raws.item_index.contains_key(key) {
|
||||||
return spawn_named_item(raws, new_entity, key, pos);
|
return spawn_named_item(raws, ecs, key, pos);
|
||||||
} else if raws.mob_index.contains_key(key) {
|
} else if raws.mob_index.contains_key(key) {
|
||||||
return spawn_named_mob(raws, new_entity, key, pos);
|
return spawn_named_mob(raws, ecs, key, pos);
|
||||||
} else if raws.prop_index.contains_key(key) {
|
} else if raws.prop_index.contains_key(key) {
|
||||||
return spawn_named_prop(raws, new_entity, key, pos);
|
return spawn_named_prop(raws, ecs, key, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
@ -76,7 +76,7 @@ pub fn save_game(ecs: &mut World) {
|
|||||||
Equippable,
|
Equippable,
|
||||||
Equipped,
|
Equipped,
|
||||||
MeleeWeapon,
|
MeleeWeapon,
|
||||||
DefenseBonus,
|
Wearable,
|
||||||
WantsToRemoveItem,
|
WantsToRemoveItem,
|
||||||
ParticleLifetime,
|
ParticleLifetime,
|
||||||
HungerClock,
|
HungerClock,
|
||||||
@ -172,7 +172,7 @@ pub fn load_game(ecs: &mut World) {
|
|||||||
Equippable,
|
Equippable,
|
||||||
Equipped,
|
Equipped,
|
||||||
MeleeWeapon,
|
MeleeWeapon,
|
||||||
DefenseBonus,
|
Wearable,
|
||||||
WantsToRemoveItem,
|
WantsToRemoveItem,
|
||||||
ParticleLifetime,
|
ParticleLifetime,
|
||||||
HungerClock,
|
HungerClock,
|
||||||
|
@ -12,7 +12,8 @@ use crate::{Map, Rect, TileType};
|
|||||||
|
|
||||||
/// Spawns the player and returns their entity object
|
/// Spawns the player and returns their entity object
|
||||||
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
||||||
ecs.create_entity()
|
let player = ecs
|
||||||
|
.create_entity()
|
||||||
.with(Position {
|
.with(Position {
|
||||||
x: player_x,
|
x: player_x,
|
||||||
y: player_y,
|
y: player_y,
|
||||||
@ -50,7 +51,47 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
|||||||
level: 1,
|
level: 1,
|
||||||
})
|
})
|
||||||
.marked::<SimpleMarker<SerializeMe>>()
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
.build()
|
.build();
|
||||||
|
|
||||||
|
// Starting equipment
|
||||||
|
spawn_named_entity(
|
||||||
|
&RAWS.lock().unwrap(),
|
||||||
|
ecs,
|
||||||
|
"Rusty Longsword",
|
||||||
|
SpawnType::Equipped { by: player },
|
||||||
|
);
|
||||||
|
spawn_named_entity(
|
||||||
|
&RAWS.lock().unwrap(),
|
||||||
|
ecs,
|
||||||
|
"Dried Sausage",
|
||||||
|
SpawnType::Carried { by: player },
|
||||||
|
);
|
||||||
|
spawn_named_entity(
|
||||||
|
&RAWS.lock().unwrap(),
|
||||||
|
ecs,
|
||||||
|
"Beer",
|
||||||
|
SpawnType::Carried { by: player },
|
||||||
|
);
|
||||||
|
spawn_named_entity(
|
||||||
|
&RAWS.lock().unwrap(),
|
||||||
|
ecs,
|
||||||
|
"Stained Tunic",
|
||||||
|
SpawnType::Equipped { by: player },
|
||||||
|
);
|
||||||
|
spawn_named_entity(
|
||||||
|
&RAWS.lock().unwrap(),
|
||||||
|
ecs,
|
||||||
|
"Torn Trousers",
|
||||||
|
SpawnType::Equipped { by: player },
|
||||||
|
);
|
||||||
|
spawn_named_entity(
|
||||||
|
&RAWS.lock().unwrap(),
|
||||||
|
ecs,
|
||||||
|
"Old Boots",
|
||||||
|
SpawnType::Equipped { by: player },
|
||||||
|
);
|
||||||
|
|
||||||
|
player
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_MONSTERS: i32 = 4;
|
const MAX_MONSTERS: i32 = 4;
|
||||||
@ -136,7 +177,7 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
|
|||||||
|
|
||||||
let item_result = spawn_named_entity(
|
let item_result = spawn_named_entity(
|
||||||
&RAWS.lock().unwrap(),
|
&RAWS.lock().unwrap(),
|
||||||
ecs.create_entity(),
|
ecs,
|
||||||
spawn.1,
|
spawn.1,
|
||||||
SpawnType::AtPosition { x, y },
|
SpawnType::AtPosition { x, y },
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user