From 2f9360c8bc047ab61c3283ce5a74ba85c8585640 Mon Sep 17 00:00:00 2001 From: popertots Date: Sat, 21 Mar 2026 21:27:26 +0000 Subject: [PATCH] inv update --- src/entities/item/container.rs | 49 ------ src/entities/item/inventory/constants.rs | 31 ++++ src/entities/item/inventory/container.rs | 143 +++++++++++++++ src/entities/item/inventory/mod.rs | 11 ++ src/entities/item/inventory/ops.rs | 103 +++++++++++ src/entities/item/inventory/personal.rs | 174 +++++++++++++++++++ src/entities/item/inventory/systems.rs | 79 +++++++++ src/entities/item/mod.rs | 7 +- src/entities/livestock/pig.rs | 7 +- src/entities/livestock/rabbit.rs | 7 +- src/entities/sentient/dorf.rs | 7 +- src/entities/shared_components/ambulatory.rs | 2 + src/entities/shared_systems/pathfinding.rs | 5 +- 13 files changed, 567 insertions(+), 58 deletions(-) delete mode 100644 src/entities/item/container.rs create mode 100644 src/entities/item/inventory/constants.rs create mode 100644 src/entities/item/inventory/container.rs create mode 100644 src/entities/item/inventory/mod.rs create mode 100644 src/entities/item/inventory/ops.rs create mode 100644 src/entities/item/inventory/personal.rs create mode 100644 src/entities/item/inventory/systems.rs diff --git a/src/entities/item/container.rs b/src/entities/item/container.rs deleted file mode 100644 index 4c653f8..0000000 --- a/src/entities/item/container.rs +++ /dev/null @@ -1,49 +0,0 @@ -use bevy::prelude::*; - -#[derive(Component, Debug)] -pub struct Container { - pub max_weight: u32, - pub max_items: Option, - pub current_weight: u32, - pub contents: Vec, // TODO - move away from Entity - pub is_open: bool, -} - -impl Container { - pub fn new(max_weight: u32, max_items: Option) -> Self { - Self { - max_weight, - max_items, - current_weight: 0, - contents: Vec::new(), - is_open: true, - } - } - - pub fn can_fit(&self, weight: u32) -> bool { - self.current_weight + weight <= self.max_weight - && self - .max_items - .map_or(true, |max| self.contents.len() < max as usize) - } - - pub fn add_item(&mut self, item_entity: Entity, weight: u32) -> bool { - if self.can_fit(weight) { - self.contents.push(item_entity); - self.current_weight += weight; - true - } else { - false - } - } - - pub fn remove_item(&mut self, item_entity: Entity, weight: u32) -> bool { - if let Some(pos) = self.contents.iter().position(|&e| e == item_entity) { - self.contents.remove(pos); - self.current_weight = self.current_weight.saturating_sub(weight); - true - } else { - false - } - } -} diff --git a/src/entities/item/inventory/constants.rs b/src/entities/item/inventory/constants.rs new file mode 100644 index 0000000..56613fa --- /dev/null +++ b/src/entities/item/inventory/constants.rs @@ -0,0 +1,31 @@ +/// Baseline carry weight for an entity with strength 0 (kg, arbitrary units). +/// Actual max = BASE_CARRY_WEIGHT + strength * STRENGTH_CARRY_MULTIPLIER. +pub const BASE_CARRY_WEIGHT: u32 = 20; + +/// Carry weight added per point of strength. +pub const STRENGTH_CARRY_MULTIPLIER: u32 = 5; + +/// Default strength value for a newly spawned entity. +/// Tune per entity type by setting PersonalInventory.strength at spawn. +pub const DEFAULT_STRENGTH: u8 = 5; + +/// walk_speed multiplier applied when carry weight exceeds max carry weight. +/// 0.5 = half speed when over-encumbered (Skyrim-style). +pub const ENCUMBERED_SPEED_MULTIPLIER: f32 = 0.5; + +/// Item size values. Every item has a size: u8. +/// Containers use max_item_size: u8 to gate what they accept. +pub const SIZE_TINY: u8 = 1; // coin, seed, ring, dice +pub const SIZE_SMALL: u8 = 2; // apple, knife, pouch, small book +pub const SIZE_MEDIUM: u8 = 4; // sword, boot, helmet, loaf of bread +pub const SIZE_LARGE: u8 = 6; // shield, backpack (empty), large tool +pub const SIZE_HUGE: u8 = 8; // chest, anvil, large crate + +/// Personal inventory slot counts per life stage, applied at spawn. +/// Baby: max(0, 1) unless species adult_slots == 1, then 0. +/// Child: floor(adult_slots / 2), minimum 0. +/// Adult: species-defined value. +/// See PersonalInventory::slots_for_age. +pub const DORF_ADULT_SLOTS: u8 = 8; +pub const RABBIT_ADULT_SLOTS: u8 = 1; +pub const PIG_ADULT_SLOTS: u8 = 0; // pigs carry nothing diff --git a/src/entities/item/inventory/container.rs b/src/entities/item/inventory/container.rs new file mode 100644 index 0000000..7677597 --- /dev/null +++ b/src/entities/item/inventory/container.rs @@ -0,0 +1,143 @@ +//! Container — a carryable or world-placed storage object. +//! +//! # Design +//! Containers enforce count and size limits. Weight is enforced at the carrier +//! (PersonalInventory) level, not here — a container does not know who carries it. +//! +//! # carry_weight_bonus +//! When a Container is directly held in a PersonalInventory slot (not nested inside +//! another container), its carry_weight_bonus is added to the carrier's effective +//! max weight. Only direct-hold containers contribute — nested containers do not. +//! The encumbrance system queries for this during weight recalculation. +//! +//! # Nesting +//! Containers can be placed inside other containers (contents: Vec may +//! contain entities that themselves have Container components). Nesting depth is +//! physically bounded by size — a coin purse (SIZE_SMALL) cannot hold a chest (SIZE_HUGE). +//! +//! # TODO +//! contents currently stores Entity. Once a stable non-generational ID scheme +//! exists, migrate to that. Tracked in container.rs TODO comment. + +use bevy::prelude::*; +use smallvec::SmallVec; + +use super::ops::AddResult; +use crate::entities::item::inventory::ops::can_add; + +#[derive(Component, Debug)] +pub struct Container { + /// Maximum number of items this container can hold. + pub max_count: u32, + /// Maximum size of a single item this container accepts. + /// Prevents putting a chest inside a coin purse. + pub max_item_size: u8, + /// This container's own size as an item (for fitting into other containers). + pub size: u8, + /// Bonus carry weight (kg) added to the direct carrier when this container + /// is held in a PersonalInventory slot. Does NOT stack when nested. + pub carry_weight_bonus: u32, + /// Whether this container can be opened/closed. Closed containers reject adds. + pub is_open: bool, + /// Items held. May include entities that themselves have Container components. + /// TODO: migrate away from Entity when stable ID scheme exists. + pub contents: SmallVec<[Entity; 8]>, + /// Cached total weight of contents (updated on add/remove, not per-tick). + pub current_weight: u32, +} + +impl Container { + /// Construct a new container. + /// + /// # Parameters + /// - `max_count`: item slot limit + /// - `max_item_size`: largest item size accepted (use SIZE_* constants) + /// - `size`: this container's own size as an item + /// - `carry_weight_bonus`: kg added to direct carrier's max weight + pub fn new(max_count: u32, max_item_size: u8, size: u8, carry_weight_bonus: u32) -> Self { + Self { + max_count, + max_item_size, + size, + carry_weight_bonus, + is_open: true, + contents: SmallVec::new(), + current_weight: 0, + } + } + + /// Attempt to add an item. + /// + /// `carrier_current_weight` and `carrier_max_weight` are the top-level + /// PersonalInventory's values — weight is enforced at the carrier level. + /// Pass u32::MAX for both if this container is a world object (chest on floor) + /// with no carrier weight limit. + pub fn try_add( + &mut self, + item_entity: Entity, + item_weight: u32, + item_size: u8, + carrier_current_weight: u32, + carrier_max_weight: u32, + ) -> AddResult { + let result = can_add( + self.is_open, + self.contents.len(), + self.max_count as usize, + item_size, + self.max_item_size, + carrier_current_weight, + carrier_max_weight, + item_weight, + ); + if result.is_ok() { + self.contents.push(item_entity); + self.current_weight = self.current_weight.saturating_add(item_weight); + } + result + } + + pub fn remove(&mut self, item_entity: Entity, item_weight: u32) -> bool { + if let Some(pos) = self.contents.iter().position(|&e| e == item_entity) { + self.contents.swap_remove(pos); + self.current_weight = self.current_weight.saturating_sub(item_weight); + true + } else { + false + } + } + + pub fn contains(&self, item_entity: Entity) -> bool { + self.contents.contains(&item_entity) + } + + pub fn count(&self) -> usize { + self.contents.len() + } + + pub fn is_full(&self) -> bool { + self.contents.len() >= self.max_count as usize + } +} + +/// Common container presets. Use these rather than constructing manually +/// to keep sizes and bonuses consistent across the codebase. +impl Container { + /// Small coin purse — coins, seeds, dice, rings only. + pub fn coin_purse() -> Self { + use crate::entities::item::inventory::constants::*; + Self::new(20, SIZE_TINY, SIZE_SMALL, 0) + } + + /// Leather backpack — most items fit, carried on back. + pub fn backpack() -> Self { + use crate::entities::item::inventory::constants::*; + Self::new(12, SIZE_LARGE, SIZE_LARGE, 20) + } + + /// Wooden chest — large capacity, large items fit, significant weight bonus. + pub fn wooden_chest() -> Self { + use crate::entities::item::inventory::constants::*; + Self::new(40, SIZE_HUGE, SIZE_HUGE, 40) + } +} diff --git a/src/entities/item/inventory/mod.rs b/src/entities/item/inventory/mod.rs new file mode 100644 index 0000000..2431bd6 --- /dev/null +++ b/src/entities/item/inventory/mod.rs @@ -0,0 +1,11 @@ +pub mod constants; +pub mod container; +pub mod ops; +pub mod personal; +pub mod systems; + +pub use constants::*; +pub use container::Container; +pub use ops::{best_slot, can_add, AddResult, SwapOp}; +pub use personal::{LifeStage, PersonalInventory}; +pub use systems::{update_encumbrance, InventoryChangedEvent}; diff --git a/src/entities/item/inventory/ops.rs b/src/entities/item/inventory/ops.rs new file mode 100644 index 0000000..51d10b9 --- /dev/null +++ b/src/entities/item/inventory/ops.rs @@ -0,0 +1,103 @@ +use bevy::prelude::*; + +/// Result of an attempt to add an item to an inventory slot. +/// The entity/player sees only the variant name — no internal details exposed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AddResult { + /// Item added successfully. + Ok, + /// Total weight would exceed the carrier's max. Item not added. + TooHeavy, + /// No free slots remain. Item not added. + TooMany, + /// Item's size exceeds this container's max_item_size. Item not added. + TooBig, + /// Container is closed. Item not added. + Closed, +} + +impl AddResult { + pub fn is_ok(self) -> bool { + self == AddResult::Ok + } + + /// Human-readable reason for UI/log use. Returns None on Ok. + pub fn reason(self) -> Option<&'static str> { + match self { + AddResult::Ok => None, + AddResult::TooHeavy => Some("too heavy to carry"), + AddResult::TooMany => Some("not enough space"), + AddResult::TooBig => Some("item too large to fit"), + AddResult::Closed => Some("container is closed"), + } + } +} + +/// Check whether an item can be added without modifying any state. +/// Used by both PersonalInventory and Container before committing. +/// +/// `carrier_current_weight` — total weight currently carried by the top-level carrier. +/// `carrier_max_weight` — max weight the carrier can hold. +/// Both are passed in so this fn has no ECS access and is testable in isolation. +#[inline] +pub fn can_add( + is_open: bool, + current_count: usize, + max_count: usize, + item_size: u8, + max_item_size: u8, + carrier_current_weight: u32, + carrier_max_weight: u32, + item_weight: u32, +) -> AddResult { + if !is_open { + return AddResult::Closed; + } + if item_size > max_item_size { + return AddResult::TooBig; + } + if current_count >= max_count { + return AddResult::TooMany; + } + if carrier_current_weight + item_weight > carrier_max_weight { + return AddResult::TooHeavy; + } + AddResult::Ok +} + +/// Represents a single item movement needed during reorganisation. +/// Used by the future reorganise task — defined now so the interface is stable. +/// +/// The task system executes these one at a time with a per-swap delay based on +/// the entity's intelligence stat (smarter entity = shorter delay per swap). +#[derive(Debug, Clone)] +pub struct SwapOp { + /// Entity being moved. + pub item: Entity, + /// Source container/inventory entity (None = ground). + pub from: Option, + /// Destination container/inventory entity (None = ground/drop). + pub to: Option, +} + +/// Find the best destination entity (container or personal inventory) for an item +/// given a list of candidate containers sorted by preference. +/// +/// "Best" = smallest max_item_size that still fits the item, i.e. most specific +/// container. A coin goes into the coin purse before the backpack. +/// +/// Returns None if no candidate can accept the item. +/// +/// # Parameters +/// - `candidates`: slice of (container_entity, max_item_size, current_count, +/// max_count, is_open) tuples, already filtered for weight. +/// - `item_size`: size of the item to place. +pub fn best_slot(candidates: &[(Entity, u8, usize, usize, bool)], item_size: u8) -> Option { + candidates + .iter() + .filter(|(_, max_size, count, max_count, open)| { + *open && item_size <= *max_size && count < max_count + }) + .min_by_key(|(_, max_size, _, _, _)| *max_size) + .map(|(entity, _, _, _, _)| *entity) +} diff --git a/src/entities/item/inventory/personal.rs b/src/entities/item/inventory/personal.rs new file mode 100644 index 0000000..b2dbc91 --- /dev/null +++ b/src/entities/item/inventory/personal.rs @@ -0,0 +1,174 @@ +//! PersonalInventory — the inventory of a living entity. +//! +//! # Design +//! Every entity that can carry items gets a PersonalInventory. Slot count is +//! species- and age-defined. Weight limit is strength-based. +//! +//! # Weight limit +//! max_carry_weight = BASE_CARRY_WEIGHT + strength * STRENGTH_CARRY_MULTIPLIER +//! + sum(carry_weight_bonus for each Container directly in slots) +//! +//! The bonus from held containers is re-evaluated whenever the slot contents +//! change. It is NOT recursive — containers inside containers do not contribute +//! their bonus to the carrier. +//! +//! # Encumbrance +//! When current_weight > max_carry_weight, the entity is encumbered. +//! The update_encumbrance system sets Ambulatory.walk_speed to +//! base_walk_speed * ENCUMBERED_SPEED_MULTIPLIER when over limit. +//! +//! # Item placement +//! Items are always placed in the best available slot at time of pickup — +//! the smallest container whose max_item_size >= item.size with a free slot. +//! No automatic reorganisation occurs. Reorganisation is an explicit task. +//! +//! # Life stage slot counts +//! Adult: species value (e.g. DORF_ADULT_SLOTS = 8) +//! Child: floor(adult / 2), minimum 0 +//! Baby: 1 if adult >= 2, else 0 +//! See slots_for_age(). + +use bevy::prelude::*; +use smallvec::SmallVec; + +use super::constants::*; +use super::ops::{can_add, AddResult}; + +/// Life stage of a living entity. Determines slot count at spawn. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LifeStage { + Baby, + Child, + Adult, +} + +impl LifeStage { + /// Compute slot count for this life stage given the species adult slot count. + /// + /// Baby: 1 if adult_slots >= 2, else 0 + /// Child: floor(adult_slots / 2), minimum 0 + /// Adult: adult_slots + pub fn slots_for_age(self, adult_slots: u8) -> u8 { + match self { + LifeStage::Adult => adult_slots, + LifeStage::Child => adult_slots / 2, // integer floor + LifeStage::Baby => { + if adult_slots >= 2 { + 1 + } else { + 0 + } + } + } + } +} + +#[derive(Component, Debug)] +pub struct PersonalInventory { + /// Items directly held (body slots). May include Container entities. + /// SmallVec<[Entity; 8]> — zero heap allocation for typical dorf (8 slots). + pub slots: SmallVec<[Entity; 8]>, + + /// Maximum number of directly-held items (hands, pockets, body slots). + pub max_slots: u8, + + /// Strength stat (0–255). Determines carry weight capacity. + /// Set at spawn per species/individual. Increase when stats system arrives. + pub strength: u8, + + /// Cached total weight of all directly-held items + their contents. + /// Updated on add/remove. NOT recomputed per tick. + pub current_weight: u32, + + /// Cached carry weight bonus from Container items in direct slots. + /// Updated whenever slots change. NOT recursive. + pub held_container_bonus: u32, + + /// Whether this entity is currently over their weight limit. + /// Set by update_encumbrance system. Read by Ambulatory system. + pub is_encumbered: bool, +} + +impl PersonalInventory { + /// Construct for a specific life stage and species. + pub fn new(adult_slots: u8, stage: LifeStage, strength: u8) -> Self { + Self { + slots: SmallVec::new(), + max_slots: stage.slots_for_age(adult_slots), + strength, + current_weight: 0, + held_container_bonus: 0, + is_encumbered: false, + } + } + + /// Maximum carry weight based on strength and held container bonuses. + pub fn max_carry_weight(&self) -> u32 { + BASE_CARRY_WEIGHT + + self.strength as u32 * STRENGTH_CARRY_MULTIPLIER + + self.held_container_bonus + } + + /// Attempt to add an item directly to a personal slot. + /// Use this when no container is available or preferred. + pub fn try_add_direct( + &mut self, + item_entity: Entity, + item_weight: u32, + item_size: u8, + ) -> AddResult { + let result = can_add( + true, + self.slots.len(), + self.max_slots as usize, + item_size, + u8::MAX, + self.current_weight, + self.max_carry_weight(), + item_weight, + ); + if result.is_ok() { + self.slots.push(item_entity); + self.current_weight = self.current_weight.saturating_add(item_weight); + } + result + } + + /// Remove an item from direct slots. Does NOT recurse into containers. + /// Returns true if found and removed. + pub fn remove_direct(&mut self, item_entity: Entity, item_weight: u32) -> bool { + if let Some(pos) = self.slots.iter().position(|&e| e == item_entity) { + self.slots.swap_remove(pos); + self.current_weight = self.current_weight.saturating_sub(item_weight); + true + } else { + false + } + } + + /// Whether the entity can accept an item at all (ignoring containers). + /// Quick check before attempting full placement. + pub fn can_accept(&self, item_weight: u32) -> bool { + self.slots.len() < self.max_slots as usize + && self.current_weight + item_weight <= self.max_carry_weight() + } + + pub fn is_full(&self) -> bool { + self.slots.len() >= self.max_slots as usize + } + + pub fn slot_count(&self) -> usize { + self.slots.len() + } + + /// Rebuild held_container_bonus from current direct slots. + /// Call after any slot change that might add/remove a Container. + /// Caller is responsible for triggering this — not automatic. + /// + /// Requires a query to check which slot entities have Container components. + /// Called by the encumbrance system, not inline in add/remove, to avoid + /// needing World/query access at the point of inventory mutation. + pub fn rebuild_container_bonus(&mut self, bonus: u32) { + self.held_container_bonus = bonus; + } +} diff --git a/src/entities/item/inventory/systems.rs b/src/entities/item/inventory/systems.rs new file mode 100644 index 0000000..b9275db --- /dev/null +++ b/src/entities/item/inventory/systems.rs @@ -0,0 +1,79 @@ +//! Encumbrance system — updates walk_speed based on carry weight. +//! +//! # Event-driven design +//! Rather than iterating all carriers every tick, encumbrance recalculates only +//! for entities that fired an InventoryChangedEvent. At 5000 entities with rare +//! inventory changes, this reduces per-tick work from O(entities) to O(changes). +//! +//! Any code that mutates a PersonalInventory or a Container held by an entity +//! MUST fire InventoryChangedEvent { carrier } after the mutation. The carrier +//! is always the top-level entity with PersonalInventory — not a nested container. +//! +//! # base_walk_speed +//! Ambulatory gains base_walk_speed: f32 — the unmodified speed. Encumbrance +//! writes walk_speed = base_walk_speed * ENCUMBERED_SPEED_MULTIPLIER. +//! Restoring writes walk_speed = base_walk_speed. base_walk_speed is never +//! modified by encumbrance. + +use bevy::prelude::*; +use rustc_hash::FxHashSet; + +use crate::entities::item::inventory::constants::ENCUMBERED_SPEED_MULTIPLIER; +use crate::entities::item::inventory::personal::PersonalInventory; +use crate::entities::item::Container; +use crate::entities::shared_components::Ambulatory; + +/// Fire this event whenever a PersonalInventory or a directly-held Container +/// changes contents or weight. Always use the top-level carrier entity. +/// +/// Multiple events for the same carrier in one frame are deduplicated — +/// only one encumbrance recalculation occurs per carrier per frame. +#[derive(Message)] +pub struct InventoryChangedEvent { + pub carrier: Entity, +} + +/// Recalculate encumbrance only for carriers that had inventory changes this frame. +/// +/// Deduplicates multiple events for the same entity via FxHashSet before querying. +/// Cost: O(unique_changed_carriers) instead of O(all_carriers). +pub fn update_encumbrance( + mut events: MessageReader, + mut carriers: Query<(&mut PersonalInventory, &mut Ambulatory)>, + containers: Query<&Container>, +) { + // Deduplicate — multiple adds in one frame should only trigger one recalc + let changed: FxHashSet = events.read().map(|e| e.carrier).collect(); + if changed.is_empty() { + return; + } + + for carrier_entity in changed { + let Ok((mut inventory, mut ambulatory)) = carriers.get_mut(carrier_entity) else { + continue; + }; + + // Rebuild container bonus from direct slots only (not recursive) + let bonus: u32 = inventory + .slots + .iter() + .filter_map(|&e| containers.get(e).ok()) + .map(|c| c.carry_weight_bonus) + .sum(); + inventory.rebuild_container_bonus(bonus); + + let was_encumbered = inventory.is_encumbered; + let now_encumbered = inventory.current_weight > inventory.max_carry_weight(); + inventory.is_encumbered = now_encumbered; + + if now_encumbered != was_encumbered { + ambulatory.walk_speed = if now_encumbered { + ambulatory.base_walk_speed * ENCUMBERED_SPEED_MULTIPLIER + } else { + ambulatory.base_walk_speed + }; + // TODO: additional encumbrance debuffs (combat penalty, stamina drain, + // job speed modifier) when gameplay systems exist + } + } +} diff --git a/src/entities/item/mod.rs b/src/entities/item/mod.rs index fc909c7..551c89c 100644 --- a/src/entities/item/mod.rs +++ b/src/entities/item/mod.rs @@ -1,8 +1,8 @@ pub mod constants; -pub mod container; pub mod core; pub mod decorations; pub mod drop_table; +pub mod inventory; pub mod material; pub mod perishable; pub mod prefabs; @@ -10,10 +10,13 @@ pub mod quality; pub mod systems; pub mod types; -pub use container::*; pub use core::*; pub use decorations::*; pub use drop_table::*; +pub use inventory::{ + best_slot, can_add, update_encumbrance, AddResult, Container, InventoryChangedEvent, LifeStage, + PersonalInventory, SwapOp, +}; pub use material::*; pub use perishable::*; pub use prefabs::*; diff --git a/src/entities/livestock/pig.rs b/src/entities/livestock/pig.rs index 548c7d2..b3696c5 100644 --- a/src/entities/livestock/pig.rs +++ b/src/entities/livestock/pig.rs @@ -18,10 +18,13 @@ pub struct Pig { impl Pig { pub fn new(asset_server: &Res, position: Vec3) -> Self { + let walk_speed = 25.; + let run_speed = 6.; Pig { ambulatory: Ambulatory { - walk_speed: 25., - run_speed: 6., + walk_speed, + base_walk_speed: walk_speed, + run_speed, target: None, current_path: None, path_index: 0, diff --git a/src/entities/livestock/rabbit.rs b/src/entities/livestock/rabbit.rs index 6854209..1a6a97c 100644 --- a/src/entities/livestock/rabbit.rs +++ b/src/entities/livestock/rabbit.rs @@ -20,10 +20,13 @@ pub struct Rabbit { impl Rabbit { pub fn new(asset_server: &Res, position: Vec3) -> Self { + let walk_speed = 1.; + let run_speed = 6.; Rabbit { ambulatory: Ambulatory { - walk_speed: 1., - run_speed: 6., + walk_speed, + base_walk_speed: walk_speed, + run_speed, target: None, current_path: None, path_index: 0, diff --git a/src/entities/sentient/dorf.rs b/src/entities/sentient/dorf.rs index 8d9ba88..f2ecd64 100644 --- a/src/entities/sentient/dorf.rs +++ b/src/entities/sentient/dorf.rs @@ -18,10 +18,13 @@ pub struct Dorf { impl Dorf { pub fn new(asset_server: &Res, position: Vec3) -> Self { + let walk_speed = 5.; + let run_speed = 6.; Dorf { ambulatory: Ambulatory { - walk_speed: 5., - run_speed: 6., + walk_speed, + base_walk_speed: walk_speed, + run_speed, target: None, current_path: None, path_index: 0, diff --git a/src/entities/shared_components/ambulatory.rs b/src/entities/shared_components/ambulatory.rs index d4097d3..10c7ea4 100644 --- a/src/entities/shared_components/ambulatory.rs +++ b/src/entities/shared_components/ambulatory.rs @@ -4,6 +4,7 @@ use bevy::tasks::Task; #[derive(Component)] pub struct Ambulatory { pub walk_speed: f32, + pub base_walk_speed: f32, pub run_speed: f32, pub current_path: Option>, pub path_index: usize, @@ -18,6 +19,7 @@ impl Default for Ambulatory { fn default() -> Self { Self { walk_speed: 1.0, + base_walk_speed: 1.0, run_speed: 2.0, current_path: None, path_index: 0, diff --git a/src/entities/shared_systems/pathfinding.rs b/src/entities/shared_systems/pathfinding.rs index 4ed2029..2a0453b 100644 --- a/src/entities/shared_systems/pathfinding.rs +++ b/src/entities/shared_systems/pathfinding.rs @@ -63,6 +63,7 @@ use crate::constants::{ ITILE_SIZE, PATHFINDER_HIERARCHICAL_THRESHOLD_CHUNKS, PATHFINDER_MAX_NODES, PATHFINDER_PROVISIONAL_NODE_LIMIT, PIXEL_RATIO, TILE_SIZE, }; +use crate::entities::item::inventory::{update_encumbrance, InventoryChangedEvent}; use crate::entities::shared_systems::constants::{ CONVOY_DOT_THRESHOLD, ES_DIRECTION_THRESHOLD, HEAD_ON_DOT_THRESHOLD, OCCUPANCY_CROWD_THRESHOLD, PATHFINDER_DIRTY_LOOKAHEAD, PATHFINDER_VALIDATE_STEPS, PATHFINDER_VALIDATION_COOLDOWN, @@ -218,7 +219,8 @@ pub struct PathfindingPlugin; impl Plugin for PathfindingPlugin { fn build(&self, app: &mut App) { - app.insert_resource(PathfindingBenchmark::new(100)) + app.add_message::() + .insert_resource(PathfindingBenchmark::new(100)) .insert_resource(crate::entities::shared_components::CompletedPaths::default()) .insert_resource(crate::entities::shared_components::PathRequestCounter::default()) .insert_resource(PathRequestQueue::default()) @@ -228,6 +230,7 @@ impl Plugin for PathfindingPlugin { FixedUpdate, ( rebuild_tile_occupancy, + update_encumbrance, collect_pathfinding_dirty_chunks, invalidate_paths_on_tile_change, prepare_paths,