From 220b00c64c8e6432866c860ee344fb39db9ca7aa Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Thu, 13 Jan 2022 10:14:13 -0500 Subject: [PATCH] Add initiative penalties to items --- raws/spawns.json | 217 ++++++++++++++++++++------ src/ai.rs | 2 + src/ai/encumbrance_system.rs | 89 +++++++++++ src/components.rs | 22 ++- src/components/{simple.rs => tags.rs} | 8 +- src/inventory_system.rs | 26 ++- src/main.rs | 4 + src/raws/item_structs.rs | 3 + src/raws/rawmaster.rs | 18 +-- src/saveload_system.rs | 2 + src/spawner.rs | 13 +- 11 files changed, 334 insertions(+), 70 deletions(-) create mode 100644 src/ai/encumbrance_system.rs rename src/components/{simple.rs => tags.rs} (88%) diff --git a/raws/spawns.json b/raws/spawns.json index 84e47b4..118fae1 100644 --- a/raws/spawns.json +++ b/raws/spawns.json @@ -219,7 +219,10 @@ "effects": { "provides_healing": "8" } - } + }, + "weight_lbs": 0.5, + "base_value": 50.0, + "vendor_category": "alchemy" }, { "name": "Magic Missile Scroll", @@ -234,7 +237,10 @@ "ranged": "6", "damage": "20" } - } + }, + "weight_lbs": 0.5, + "base_value": 50.0, + "vendor_category": "alchemy" }, { "name": "Fireball Scroll", @@ -250,7 +256,10 @@ "damage": "20", "area_of_effect": "3" } - } + }, + "weight_lbs": 0.5, + "base_value": 100.0, + "vendor_category": "alchemy" }, { "name": "Confusion Scroll", @@ -263,10 +272,12 @@ "consumable": { "effects": { "ranged": "6", - "damage": "20", "confusion": "4" } - } + }, + "weight_lbs": 0.5, + "base_value": 75.0, + "vendor_category": "alchemy" }, { "name": "Magic Mapping Scroll", @@ -280,7 +291,10 @@ "effects": { "magic_mapping": "" } - } + }, + "weight_lbs": 0.5, + "base_value": 50.0, + "vendor_category": "alchemy" }, { "name": "Rations", @@ -294,7 +308,10 @@ "effects": { "food": "" } - } + }, + "weight_lbs": 2.0, + "base_value": 0.5, + "vendor_category": "food" }, { "name": "Meat", @@ -308,7 +325,10 @@ "effects": { "food": "" } - } + }, + "weight_lbs": 2.0, + "base_value": 0.5, + "vendor_category": "food" }, { "name": "Hide", @@ -317,7 +337,9 @@ "fg": "#A52A2A", "bg": "#000000", "order": 2 - } + }, + "weight_lbs": 2.0, + "base_value": 5.0 }, { "name": "Dried Sausage", @@ -331,7 +353,9 @@ "effects": { "food": "" } - } + }, + "weight_lbs": 2.0, + "base_value": 0.5 }, { "name": "Beer", @@ -345,7 +369,10 @@ "effects": { "provides_healing": "4" } - } + }, + "weight_lbs": 2.0, + "base_value": 0.5, + "vendor_category": "food" }, { "name": "Rusty Longsword", @@ -360,7 +387,11 @@ "attribute": "Might", "base_damage": "1d8-1", "hit_bonus": -1 - } + }, + "weight_lbs": 3.0, + "base_value": 10.0, + "initiative_penalty": 2, + "vendor_category": "junk" }, { "name": "Dagger", @@ -375,7 +406,11 @@ "attribute": "Quickness", "base_damage": "1d4", "hit_bonus": 0 - } + }, + "weight_lbs": 1.0, + "base_value": 2.0, + "initiative_penalty": 0, + "vendor_category": "weapon" }, { "name": "Shortsword", @@ -390,7 +425,11 @@ "attribute": "Might", "base_damage": "1d6", "hit_bonus": 0 - } + }, + "weight_lbs": 2.0, + "base_value": 10.0, + "initiative_penalty": 1, + "vendor_category": "weapon" }, { "name": "Longsword", @@ -405,7 +444,11 @@ "attribute": "Might", "base_damage": "1d8", "hit_bonus": 0 - } + }, + "weight_lbs": 3.0, + "base_value": 15.0, + "initiative_penalty": 2, + "vendor_category": "weapon" }, { "name": "Battleaxe", @@ -418,9 +461,13 @@ "weapon": { "range": "melee", "attribute": "Might", - "base_damage": "1d8+1", + "base_damage": "1d8", "hit_bonus": 0 - } + }, + "weight_lbs": 4.0, + "base_value": 10.0, + "initiative_penalty": 2, + "vendor_category": "weapon" }, { "name": "Shield", @@ -433,7 +480,11 @@ "wearable": { "slot": "Shield", "armor_class": 1.0 - } + }, + "weight_lbs": 5.0, + "base_value": 3.0, + "initiative_penalty": 0.5, + "vendor_category": "armor" }, { "name": "Tower Shield", @@ -446,7 +497,11 @@ "wearable": { "slot": "Shield", "armor_class": 2.0 - } + }, + "weight_lbs": 45.0, + "base_value": 30.0, + "initiative_penalty": 1.0, + "vendor_category": "armor" }, { "name": "Stained Tunic", @@ -459,7 +514,11 @@ "wearable": { "slot": "Torso", "armor_class": 0.1 - } + }, + "weight_lbs": 1.0, + "base_value": 1.0, + "initiative_penalty": 0.1, + "vendor_category": "junk" }, { "name": "Torn Trousers", @@ -472,7 +531,11 @@ "wearable": { "slot": "Legs", "armor_class": 0.1 - } + }, + "weight_lbs": 1.0, + "base_value": 1.0, + "initiative_penalty": 0.1, + "vendor_category": "junk" }, { "name": "Old Boots", @@ -485,7 +548,11 @@ "wearable": { "slot": "Feet", "armor_class": 0.1 - } + }, + "weight_lbs": 1.0, + "base_value": 1.0, + "initiative_penalty": 0.1, + "vendor_category": "junk" }, { "name": "Cudgel", @@ -500,7 +567,11 @@ "attribute": "Quickness", "base_damage": "1d4", "hit_bonus": 0 - } + }, + "weight_lbs": 2.0, + "base_value": 0.1, + "initiative_penalty": 2.0, + "vendor_category": "junk" }, { "name": "Cloth Tunic", @@ -513,7 +584,11 @@ "wearable": { "slot": "Torso", "armor_class": 0.1 - } + }, + "weight_lbs": 1.0, + "base_value": 1.0, + "initiative_penalty": 0.1, + "vendor_category": "clothes" }, { "name": "Cloth Pants", @@ -526,7 +601,11 @@ "wearable": { "slot": "Legs", "armor_class": 0.1 - } + }, + "weight_lbs": 1.0, + "base_value": 1.0, + "initiative_penalty": 0.1, + "vendor_category": "clothes" }, { "name": "Slippers", @@ -539,7 +618,11 @@ "wearable": { "slot": "Feet", "armor_class": 0.1 - } + }, + "weight_lbs": 1.0, + "base_value": 1.0, + "initiative_penalty": 0.1, + "vendor_category": "clothes" }, { "name": "Leather Armor", @@ -552,7 +635,11 @@ "wearable": { "slot": "Torso", "armor_class": 1.0 - } + }, + "weight_lbs": 15.0, + "base_value": 10.0, + "initiative_penalty": 0.5, + "vendor_category": "clothes" }, { "name": "Leather Boots", @@ -565,7 +652,11 @@ "wearable": { "slot": "Feet", "armor_class": 0.2 - } + }, + "weight_lbs": 2.0, + "base_value": 5.0, + "initiative_penalty": 0.25, + "vendor_category": "clothes" } ], "mobs": [ @@ -592,7 +683,11 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "2d6", + "vendor": [ + "food" + ] }, { "name": "Shady Salesman", @@ -612,7 +707,11 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "2d6", + "vendor": [ + "junk" + ] }, { "name": "Patron", @@ -637,7 +736,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "1d4" }, { "name": "Priest", @@ -657,7 +757,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "2d6" }, { "name": "Parishioner", @@ -682,7 +783,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "1d4" }, { "name": "Blacksmith", @@ -702,7 +804,12 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "2d6", + "vendor": [ + "armor", + "weapon" + ] }, { "name": "Clothier", @@ -722,7 +829,11 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "2d6", + "vendor": [ + "clothes" + ] }, { "name": "Alchemist", @@ -742,7 +853,11 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "2d6", + "vendor": [ + "alchemy" + ] }, { "name": "Mom", @@ -768,7 +883,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "2d6" }, { "name": "Peasant", @@ -791,7 +907,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "1d2" }, { "name": "Dock Worker", @@ -816,7 +933,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "1d2" }, { "name": "Fisher", @@ -841,7 +959,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "1d2" }, { "name": "Wannabe Pirate", @@ -866,7 +985,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "2d6" }, { "name": "Drunk", @@ -891,7 +1011,8 @@ "Cloth Pants", "Slippers" ], - "faction": "Townsfolk" + "faction": "Townsfolk", + "gold": "1d2" }, { "name": "Rat", @@ -1046,7 +1167,8 @@ "range": 6, "color": "#FFFF55" }, - "faction": "Bandits" + "faction": "Bandits", + "gold": "1d6" }, { "name": "Orc", @@ -1060,7 +1182,8 @@ "vision_range": 8, "movement": "static", "attributes": {}, - "faction": "Cave Goblins" + "faction": "Cave Goblins", + "gold": "1d8" }, { "name": "Goblin", @@ -1074,7 +1197,8 @@ "vision_range": 8, "movement": "static", "attributes": {}, - "faction": "Cave Goblins" + "faction": "Cave Goblins", + "gold": "1d6" }, { "name": "Kobold", @@ -1088,7 +1212,8 @@ "vision_range": 4, "movement": "static", "attributes": {}, - "faction": "Cave Goblins" + "faction": "Cave Goblins", + "gold": "1d4" }, { "name": "Bat", diff --git a/src/ai.rs b/src/ai.rs index 164827a..9a93bed 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -2,6 +2,7 @@ mod adjacent_ai_system; mod approach_ai_system; mod chase_ai_system; mod default_move_system; +mod encumbrance_system; mod flee_ai_system; mod initiative_system; mod quipping; @@ -12,6 +13,7 @@ pub use adjacent_ai_system::AdjacentAI; pub use approach_ai_system::ApproachAI; pub use chase_ai_system::ChaseAI; pub use default_move_system::DefaultMoveAI; +pub use encumbrance_system::EncumbranceSystem; pub use flee_ai_system::FleeAI; pub use initiative_system::InitiativeSystem; pub use quipping::QuipSystem; diff --git a/src/ai/encumbrance_system.rs b/src/ai/encumbrance_system.rs new file mode 100644 index 0000000..2974ebe --- /dev/null +++ b/src/ai/encumbrance_system.rs @@ -0,0 +1,89 @@ +use std::collections::HashMap; + +use ::specs::prelude::*; + +use crate::components::{Attributes, EquipmentChanged, Equipped, InBackpack, Item, Pools}; +use crate::game_log::GameLog; + +pub struct EncumbranceSystem {} + +impl<'a> System<'a> for EncumbranceSystem { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteStorage<'a, EquipmentChanged>, + Entities<'a>, + ReadStorage<'a, Item>, + ReadStorage<'a, InBackpack>, + ReadStorage<'a, Equipped>, + WriteStorage<'a, Pools>, + ReadStorage<'a, Attributes>, + ReadExpect<'a, Entity>, + WriteExpect<'a, GameLog>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + mut equip_dirty, + entities, + items, + backpacks, + wielded, + mut pools, + attributes, + player, + mut gamelog, + ) = data; + + if equip_dirty.is_empty() { + return; + } + + // Build the map of who needs updating + let mut to_update: HashMap = HashMap::new(); // (weight, initiative) + for (entity, _dirty) in (&entities, &equip_dirty).join() { + to_update.insert(entity, (0., 0.)); + } + + // Remove all dirty statements + equip_dirty.clear(); + + // Total up equipped items + for (item, equipped) in (&items, &wielded).join() { + if to_update.contains_key(&equipped.owner) { + let totals = to_update.get_mut(&equipped.owner).unwrap(); + totals.0 += item.weight_lbs; + totals.1 += item.initiative_penalty; + } + } + + // Total up carried items + for (item, carried) in (&items, &backpacks).join() { + if to_update.contains_key(&carried.owner) { + let totals = to_update.get_mut(&carried.owner).unwrap(); + totals.0 += item.weight_lbs; + totals.1 += item.initiative_penalty; + } + } + + // Apply the data to Pools + for (entity, (weight, initiative)) in to_update.iter() { + if let Some(pool) = pools.get_mut(*entity) { + pool.total_weight = *weight; + pool.total_initiative_penalty = *initiative; + + if let Some(attr) = attributes.get(*entity) { + let carry_capacity_lbs = (attr.might.base + attr.might.modifiers) * 15; + if pool.total_weight as i32 > carry_capacity_lbs { + // Overburdened + pool.total_initiative_penalty += 4.0; + if *entity == *player { + gamelog.append( + "You are overburdened, and suffering an initiative penalty.", + ); + } + } + } + } + } + } +} diff --git a/src/components.rs b/src/components.rs index 32ac0fe..e0472c9 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,5 +1,5 @@ mod enums; -mod simple; +mod tags; use std::collections::HashMap; @@ -10,7 +10,7 @@ use ::specs::prelude::*; use ::specs::saveload::{ConvertSaveload, Marker}; use ::specs_derive::*; pub use enums::*; -pub use simple::*; +pub use tags::*; use crate::gamesystem::attr_bonus; @@ -81,6 +81,13 @@ pub struct WantsToMelee { pub target: Entity, } +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Item { + pub initiative_penalty: f32, + pub weight_lbs: f32, + pub base_value: f32, +} + #[derive(Component, Debug, ConvertSaveload, Clone)] pub struct SufferDamage { pub amount: Vec<(i32, bool)>, @@ -250,12 +257,23 @@ pub struct Pool { pub current: i32, } +impl Pool { + pub fn new(default_value: i32) -> Pool { + Pool { + max: default_value, + current: default_value, + } + } +} + #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Pools { pub hit_points: Pool, pub mana: Pool, pub xp: i32, pub level: i32, + pub total_weight: f32, + pub total_initiative_penalty: f32, } #[derive(Serialize, Deserialize, Clone)] diff --git a/src/components/simple.rs b/src/components/tags.rs similarity index 88% rename from src/components/simple.rs rename to src/components/tags.rs index ea58651..b7b6281 100644 --- a/src/components/simple.rs +++ b/src/components/tags.rs @@ -1,3 +1,5 @@ +///! Simple Components that are mainly used for tagging behavior. None of these have +/// any properties use ::serde::{Deserialize, Serialize}; use ::specs::prelude::*; use ::specs_derive::*; @@ -5,9 +7,6 @@ use ::specs_derive::*; #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Player {} -#[derive(Component, Debug, Serialize, Deserialize, Clone)] -pub struct Item {} - #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Consumable {} @@ -37,3 +36,6 @@ pub struct BlocksVisibility {} #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct MyTurn {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct EquipmentChanged {} diff --git a/src/inventory_system.rs b/src/inventory_system.rs index 6680e4f..2141604 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -17,11 +17,19 @@ impl<'a> System<'a> for ItemCollectionSystem { WriteStorage<'a, Position>, ReadStorage<'a, Name>, WriteStorage<'a, InBackpack>, + WriteStorage<'a, EquipmentChanged>, ); fn run(&mut self, data: Self::SystemData) { - let (player_entity, mut gamelog, mut wants_pickup, mut positions, names, mut backpack) = - data; + let ( + player_entity, + mut gamelog, + mut wants_pickup, + mut positions, + names, + mut backpack, + mut dirty, + ) = data; for pickup in wants_pickup.join() { positions.remove(pickup.item); @@ -33,6 +41,9 @@ impl<'a> System<'a> for ItemCollectionSystem { }, ) .expect("Failed to add item to backpack"); + dirty + .insert(pickup.collected_by, EquipmentChanged {}) + .expect("Unable to insert equipment change"); if pickup.collected_by == *player_entity { gamelog.append(format!( @@ -73,6 +84,7 @@ impl<'a> System<'a> for ItemUseSystem { WriteStorage<'a, HungerClock>, ReadStorage<'a, MagicMapper>, WriteExpect<'a, RunState>, + WriteStorage<'a, EquipmentChanged>, ); #[allow(clippy::cognitive_complexity)] @@ -100,9 +112,13 @@ impl<'a> System<'a> for ItemUseSystem { mut hunger_clocks, magic_mapper, mut runstate, + mut dirty, ) = data; for (entity, useitem) in (&entities, &wants_use).join() { + dirty + .insert(entity, EquipmentChanged {}) + .expect("Unable to insert equipment change"); let mut used_item = true; // Targeting @@ -364,6 +380,7 @@ impl<'a> System<'a> for ItemDropSystem { ReadStorage<'a, Name>, WriteStorage<'a, Position>, WriteStorage<'a, InBackpack>, + WriteStorage<'a, EquipmentChanged>, ); fn run(&mut self, data: Self::SystemData) { @@ -375,6 +392,7 @@ impl<'a> System<'a> for ItemDropSystem { names, mut positions, mut backpack, + mut dirty, ) = data; for (entity, to_drop) in (&entities, &wants_drop).join() { @@ -397,6 +415,10 @@ impl<'a> System<'a> for ItemDropSystem { backpack.remove(to_drop.item); + dirty + .insert(entity, EquipmentChanged {}) + .expect("Unable to insert equipment change"); + if entity == *player_entity { gamelog.append(format!( "You drop the {}.", diff --git a/src/main.rs b/src/main.rs index 5a0b3c8..682a2ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,6 +113,9 @@ impl State { 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); @@ -475,6 +478,7 @@ fn main() -> ::rltk::BError { EntryTrigger, Equippable, Equipped, + EquipmentChanged, Faction, Hidden, HungerClock, diff --git a/src/raws/item_structs.rs b/src/raws/item_structs.rs index 478887b..ef1d0b4 100644 --- a/src/raws/item_structs.rs +++ b/src/raws/item_structs.rs @@ -9,6 +9,9 @@ pub struct Item { pub consumable: Option, pub weapon: Option, pub wearable: Option, + pub initiative_penalty: Option, + pub weight_lbs: Option, + pub base_value: Option, } #[derive(Deserialize, Debug)] diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 582c0b2..d039dc4 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -234,7 +234,11 @@ pub fn spawn_named_item( eb = eb.with(Name::from(&item_template.name)); - eb = eb.with(Item {}); + eb = eb.with(Item { + initiative_penalty: item_template.initiative_penalty.unwrap_or(0.), + weight_lbs: item_template.weight_lbs.unwrap_or(0.), + base_value: item_template.base_value.unwrap_or(0.), + }); if let Some(consumable) = &item_template.consumable { eb = eb.with(Consumable {}); @@ -396,14 +400,10 @@ pub fn spawn_named_mob( let pools = Pools { level: mob_level, xp: 0, - hit_points: Pool { - current: mob_hp, - max: mob_hp, - }, - mana: Pool { - current: mob_mana, - max: mob_mana, - }, + hit_points: Pool::new(mob_hp), + mana: Pool::new(mob_mana), + total_weight: 0., + total_initiative_penalty: 0., }; eb = eb.with(pools); diff --git a/src/saveload_system.rs b/src/saveload_system.rs index aab6034..6254cb5 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -74,6 +74,7 @@ pub fn save_game(ecs: &mut World) { EntryTrigger, Equippable, Equipped, + EquipmentChanged, Faction, Hidden, HungerClock, @@ -181,6 +182,7 @@ pub fn load_game(ecs: &mut World) { EntryTrigger, Equippable, Equipped, + EquipmentChanged, Faction, Hidden, HungerClock, diff --git a/src/spawner.rs b/src/spawner.rs index 061e4c9..3def57e 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -39,16 +39,12 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { }) .with(Skills::new(1)) .with(Pools { - hit_points: Pool { - current: player_hp_at_level(11, 1), - max: player_hp_at_level(11, 1), - }, - mana: Pool { - current: mana_at_level(11, 1), - max: mana_at_level(11, 1), - }, + hit_points: Pool::new(player_hp_at_level(11, 1)), + mana: Pool::new(mana_at_level(11, 1)), xp: 0, level: 1, + total_weight: 0., + total_initiative_penalty: 0., }) .with(LightSource { color: RGB::from_f32(1.0, 1.0, 0.5), @@ -56,6 +52,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { }) .with(Initiative { current: 0 }) .with(Faction::from("Player")) + .with(EquipmentChanged {}) .marked::>() .build();