item flashing

This commit is contained in:
2025-09-14 18:48:50 +01:00
parent 8a5884c6d5
commit 4c4c985be3
13 changed files with 137 additions and 84 deletions
-5
View File
@@ -2,15 +2,10 @@ use bevy::prelude::*;
#[derive(Component, Debug)] #[derive(Component, Debug)]
pub struct Container { pub struct Container {
// Maximum weight this container can hold
pub max_weight: u32, pub max_weight: u32,
// Maximum number of items (None = unlimited)
pub max_items: Option<u32>, pub max_items: Option<u32>,
// Current weight of contents
pub current_weight: u32, pub current_weight: u32,
// List of item entities contained
pub contents: Vec<Entity>, // TODO - move away from Entity pub contents: Vec<Entity>, // TODO - move away from Entity
// Whether the container is currently open/accessible
pub is_open: bool, pub is_open: bool,
} }
+3 -32
View File
@@ -8,14 +8,10 @@ use crate::entities::item::types::ItemType;
#[derive(Component, Debug)] #[derive(Component, Debug)]
#[require(Transform, Visibility)] #[require(Transform, Visibility)]
pub struct Item { pub struct Item {
// How worn/damaged the item is (0-255, where 255 is completely destroyed)
pub wear: u8, pub wear: u8,
// The quality of craftsmanship for the base item
pub quality: Quality, pub quality: Quality,
// Time until stains fade (in game ticks/turns),
pub stain_time: u16, pub stain_time: u16,
// Temperature of the item (for hot/cold items) pub temperature: i16, // Can be negative for cold items
pub temperature: i16, // Can be negative for very cold items
} }
impl Default for Item { impl Default for Item {
@@ -52,27 +48,19 @@ impl Item {
let total_weight = base_weight + decoration_weight; let total_weight = base_weight + decoration_weight;
// Convert to u32 (multiply by 1000 to get grams if density was in kg/unit) // Convert to u32
(total_weight * 1000.0) as u32 (total_weight) as u32
} }
// Calculate total value considering all factors
pub fn calculate_value( pub fn calculate_value(
&self, &self,
item_type: &ItemType, item_type: &ItemType,
base_material: &Material, base_material: &Material,
decorations: Option<&ItemDecorations>, decorations: Option<&ItemDecorations>,
) -> u32 { ) -> u32 {
// Base value from item type and material
let base_value = item_type.base_value() + base_material.base_value(); let base_value = item_type.base_value() + base_material.base_value();
// Apply quality multiplier to base
let quality_modified_value = base_value as f32 * self.quality.value_multiplier(); let quality_modified_value = base_value as f32 * self.quality.value_multiplier();
// Additional value from decorations
let decoration_value = decorations.map(|d| d.total_decoration_value()).unwrap_or(0); let decoration_value = decorations.map(|d| d.total_decoration_value()).unwrap_or(0);
// Reduce value based on wear
let wear_multiplier = (255 - self.wear) as f32 / 255.0; let wear_multiplier = (255 - self.wear) as f32 / 255.0;
let total_value = (quality_modified_value + decoration_value as f32) * wear_multiplier; let total_value = (quality_modified_value + decoration_value as f32) * wear_multiplier;
@@ -80,33 +68,27 @@ impl Item {
total_value as u32 total_value as u32
} }
// Check if the item is destroyed due to wear
pub fn is_destroyed(&self) -> bool { pub fn is_destroyed(&self) -> bool {
self.wear >= 255 self.wear >= 255
} }
// Apply wear to the item
pub fn add_wear(&mut self, wear_amount: u8) { pub fn add_wear(&mut self, wear_amount: u8) {
self.wear = self.wear.saturating_add(wear_amount); self.wear = self.wear.saturating_add(wear_amount);
} }
// Check if item has any stains
pub fn has_stains(&self) -> bool { pub fn has_stains(&self) -> bool {
self.stain_time > 0 self.stain_time > 0
} }
// Reduce stain time (call this periodically to fade stains)
pub fn reduce_stain_time(&mut self, amount: u16) { pub fn reduce_stain_time(&mut self, amount: u16) {
self.stain_time = self.stain_time.saturating_sub(amount); self.stain_time = self.stain_time.saturating_sub(amount);
} }
// Add staining to the item
pub fn add_stain(&mut self, duration: u16) { pub fn add_stain(&mut self, duration: u16) {
self.stain_time = self.stain_time.saturating_add(duration); self.stain_time = self.stain_time.saturating_add(duration);
} }
} }
// Bundle for creating item entities
#[derive(Bundle)] #[derive(Bundle)]
pub struct ItemBundle { pub struct ItemBundle {
pub item: Item, pub item: Item,
@@ -118,12 +100,9 @@ pub struct ItemBundle {
pub sprite: Sprite, pub sprite: Sprite,
} }
// Component for items that have names (can be generated or custom)
#[derive(Component, Debug, Clone)] #[derive(Component, Debug, Clone)]
pub struct ItemName { pub struct ItemName {
// The base name of the item type
pub base_name: String, pub base_name: String,
// Custom name given by player or generated
pub custom_name: Option<String>, pub custom_name: Option<String>,
} }
@@ -144,25 +123,20 @@ impl ItemName {
) -> String { ) -> String {
let mut parts = Vec::new(); let mut parts = Vec::new();
// Quality prefix
let quality_name = item.quality.display_name(); let quality_name = item.quality.display_name();
if !quality_name.is_empty() { if !quality_name.is_empty() {
parts.push(quality_name.to_string()); parts.push(quality_name.to_string());
} }
// Base material
parts.push(base_material.name()); parts.push(base_material.name());
// Item name
parts.push(self.base_name.clone()); parts.push(self.base_name.clone());
let mut result = parts.join(" "); let mut result = parts.join(" ");
// Add decorations to the description
if let Some(decorations) = decorations { if let Some(decorations) = decorations {
let mut decoration_parts = Vec::new(); let mut decoration_parts = Vec::new();
// Gem encrustings
for gem in &decorations.gem_encrustings { for gem in &decorations.gem_encrustings {
let quality_prefix = if gem.quality != Quality::Ordinary { let quality_prefix = if gem.quality != Quality::Ordinary {
format!("{} ", gem.quality.display_name()) format!("{} ", gem.quality.display_name())
@@ -176,7 +150,6 @@ impl ItemName {
)); ));
} }
// Trim materials
for trim in &decorations.trim_materials { for trim in &decorations.trim_materials {
let quality_prefix = if trim.quality != Quality::Ordinary { let quality_prefix = if trim.quality != Quality::Ordinary {
format!("{} ", trim.quality.display_name()) format!("{} ", trim.quality.display_name())
@@ -191,7 +164,6 @@ impl ItemName {
decoration_parts.push(format!("with {} trim", trim_name)); decoration_parts.push(format!("with {} trim", trim_name));
} }
// Engravings
for engraving in &decorations.engravings { for engraving in &decorations.engravings {
let quality_prefix = if engraving.quality != Quality::Ordinary { let quality_prefix = if engraving.quality != Quality::Ordinary {
format!("{} ", engraving.quality.display_name()) format!("{} ", engraving.quality.display_name())
@@ -209,7 +181,6 @@ impl ItemName {
} }
} }
// Custom name
if let Some(custom) = &self.custom_name { if let Some(custom) = &self.custom_name {
result = format!("\"{}\" {}", custom, result); result = format!("\"{}\" {}", custom, result);
} }
+7 -13
View File
@@ -5,11 +5,8 @@ use crate::entities::item::quality::Quality;
#[derive(Component, Debug, Clone)] #[derive(Component, Debug, Clone)]
pub struct ItemDecorations { pub struct ItemDecorations {
// Gems encrusted on the item
pub gem_encrustings: Vec<GemEncrusting>, pub gem_encrustings: Vec<GemEncrusting>,
// Metal trim/bands
pub trim_materials: Vec<TrimMaterial>, pub trim_materials: Vec<TrimMaterial>,
// Engravings on the item
pub engravings: Vec<Engraving>, pub engravings: Vec<Engraving>,
} }
@@ -28,7 +25,6 @@ impl ItemDecorations {
Self::default() Self::default()
} }
// Calculate total additional weight from decorations
pub fn total_decoration_weight(&self) -> f32 { pub fn total_decoration_weight(&self) -> f32 {
let gem_weight: f32 = self let gem_weight: f32 = self
.gem_encrustings .gem_encrustings
@@ -42,11 +38,9 @@ impl ItemDecorations {
.map(|trim| trim.material.density() * trim.amount) .map(|trim| trim.material.density() * trim.amount)
.sum(); .sum();
// Engravings don't add weight, they remove material
gem_weight + trim_weight gem_weight + trim_weight
} }
// Calculate total additional value from decorations
pub fn total_decoration_value(&self) -> u32 { pub fn total_decoration_value(&self) -> u32 {
let gem_value: f32 = self let gem_value: f32 = self
.gem_encrustings .gem_encrustings
@@ -97,26 +91,26 @@ impl ItemDecorations {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GemEncrusting { pub struct GemEncrusting {
pub material: Material, // Should be a gem, but using material to gain access to its wider functions pub material: Material, // Should be a gem, but using material to gain access to its wider functions
pub size: f32, // Size of the gem (affects weight and value) pub size: f32,
pub quality: Quality, // Quality of the gem cut pub quality: Quality,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TrimMaterial { pub struct TrimMaterial {
pub material: Material, // Usually metal for bands/trim pub material: Material, // Usually metal for bands/trim
pub amount: f32, // Amount of material used pub amount: f32,
pub quality: Quality, // Quality of the trim work pub quality: Quality,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Engraving { pub struct Engraving {
pub subject: String, // What the engraving depicts pub subject: String,
pub quality: Quality, // Quality of the engraving pub quality: Quality,
} }
impl Engraving { impl Engraving {
pub fn base_value(&self) -> f32 { pub fn base_value(&self) -> f32 {
// Base value for engravings (they're pure craftsmanship) // Base value for engravings (they're pure craftsmanship so a static value is fine)
20.0 20.0
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@ pub enum Material {
} }
impl Material { impl Material {
// Weight per unit volume (arbitrary units - adjust as needed) // Weight per unit volume (arbitrary units - TODO: Adjust later)
pub fn density(&self) -> f32 { pub fn density(&self) -> f32 {
match self { match self {
Material::NA => 1.0, Material::NA => 1.0,
+1 -8
View File
@@ -6,18 +6,15 @@ use crate::entities::item::Item;
#[derive(Component, Debug)] #[derive(Component, Debug)]
pub struct Perishable { pub struct Perishable {
// How much the item has decayed (0-255)
pub decay: u8, pub decay: u8,
// How fast this item decays per tick
pub decay_rate: u8, pub decay_rate: u8,
// What happens when fully decayed
pub decay_result: DecayResult, pub decay_result: DecayResult,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum DecayResult { pub enum DecayResult {
Disappear, Disappear,
Transform(ItemType, Material), // Transform into another item type with different material Transform(ItemType, Material), // Transform into another item type with different material (fish -> rotten fish)
} }
pub fn decay_system( pub fn decay_system(
@@ -26,18 +23,14 @@ pub fn decay_system(
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, mut perishable, item, item_type, base_material) in perishable_items.iter_mut() { for (entity, mut perishable, item, item_type, base_material) in perishable_items.iter_mut() {
// Simple decay - you might want to make this more sophisticated
if time.elapsed_secs_f64() as u32 % 60 == 0 { if time.elapsed_secs_f64() as u32 % 60 == 0 {
// Decay every 60 seconds
perishable.decay = perishable.decay.saturating_add(perishable.decay_rate); perishable.decay = perishable.decay.saturating_add(perishable.decay_rate);
if perishable.decay >= 255 { if perishable.decay >= 255 {
match &perishable.decay_result { match &perishable.decay_result {
DecayResult::Disappear => { DecayResult::Disappear => {
commands.entity(entity).despawn(); commands.entity(entity).despawn();
} }
DecayResult::Transform(new_item_type, new_material) => { DecayResult::Transform(new_item_type, new_material) => {
// Transform the item
commands commands
.entity(entity) .entity(entity)
.insert(new_item_type.clone()) .insert(new_item_type.clone())
+22 -2
View File
@@ -1,9 +1,9 @@
use bevy::prelude::*; use bevy::prelude::*;
use crate::entities::item::{Item, ItemBundle, ItemDecorations, ItemType, Material, Quality}; use crate::entities::item::{Item, ItemBundle, ItemDecorations, ItemType, Material, Quality};
use crate::world::tiles::tilemap;
use crate::world::VisibleGameEntity; use crate::world::VisibleGameEntity;
// Enum for misc item prefabs
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum MiscPrefab { pub enum MiscPrefab {
RawMeat, RawMeat,
@@ -12,11 +12,13 @@ pub enum MiscPrefab {
// Spawns a misc prefab at a given position. // Spawns a misc prefab at a given position.
// These are all likely temporary to speed up development but will be replaced by semi-custom items everywhere. // These are all likely temporary to speed up development but will be replaced by semi-custom items everywhere.
// As well as acting as demonstration of how to spawn items for the future.
pub fn spawn_prefab( pub fn spawn_prefab(
commands: &mut Commands, commands: &mut Commands,
asset_server: &Res<AssetServer>, asset_server: &Res<AssetServer>,
prefab: MiscPrefab, prefab: MiscPrefab,
position: Vec3, position: Vec3,
tilemap: &mut ResMut<tilemap::TileMap>,
) -> Entity { ) -> Entity {
match prefab { match prefab {
MiscPrefab::RawMeat => { MiscPrefab::RawMeat => {
@@ -37,7 +39,16 @@ pub fn spawn_prefab(
}, },
}) })
.id(); .id();
commands.entity(meat).insert(VisibleGameEntity).id() let mut items = tilemap
.item_tiles
.get(&position.as_ivec3())
.unwrap_or(&Vec::new())
.clone();
items.push(meat.index());
tilemap
.item_tiles
.insert(position.as_ivec3(), items.clone());
return commands.entity(meat).insert(VisibleGameEntity).id();
} }
MiscPrefab::Coin => { MiscPrefab::Coin => {
let coin = commands let coin = commands
@@ -57,6 +68,15 @@ pub fn spawn_prefab(
}, },
}) })
.id(); .id();
let mut items = tilemap
.item_tiles
.get(&position.as_ivec3())
.unwrap_or(&Vec::new())
.clone();
items.push(coin.index());
tilemap
.item_tiles
.insert(position.as_ivec3(), items.clone());
commands.entity(coin).insert(VisibleGameEntity).id() commands.entity(coin).insert(VisibleGameEntity).id()
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ pub enum Quality {
Superior = 3, Superior = 3,
Exceptional = 4, Exceptional = 4,
Masterwork = 5, Masterwork = 5,
// Special artifact quality // Special artifact quality (created through moments of genius or madness)
Artifact = 10, Artifact = 10,
} }
+1 -3
View File
@@ -8,9 +8,7 @@ use crate::entities::item::Item;
use crate::entities::item::ItemBundle; use crate::entities::item::ItemBundle;
use crate::entities::item::ItemName; use crate::entities::item::ItemName;
// System to reduce stain time over time
pub fn stain_fade_system(time: Res<Time>, mut items: Query<&mut Item>) { pub fn stain_fade_system(time: Res<Time>, mut items: Query<&mut Item>) {
// Fade stains every 10 seconds
if time.elapsed_secs_f64() as u32 % 10 == 0 { if time.elapsed_secs_f64() as u32 % 10 == 0 {
for mut item in items.iter_mut() { for mut item in items.iter_mut() {
item.reduce_stain_time(1); item.reduce_stain_time(1);
@@ -18,7 +16,7 @@ pub fn stain_fade_system(time: Res<Time>, mut items: Query<&mut Item>) {
} }
} }
// Helper function to create a decorated item // Helper function to create a decorated item (DEMO ONLY)
pub fn create_decorated_item( pub fn create_decorated_item(
commands: &mut Commands, commands: &mut Commands,
item_type: ItemType, item_type: ItemType,
-7
View File
@@ -2,22 +2,15 @@ use bevy::prelude::*;
#[derive(Component, Debug, Clone, PartialEq, Eq, Hash)] #[derive(Component, Debug, Clone, PartialEq, Eq, Hash)]
pub enum ItemType { pub enum ItemType {
// Weapons
Weapon(WeaponType), Weapon(WeaponType),
// Armor/Clothing
Armor(ArmorType), Armor(ArmorType),
// Tools
Tool(ToolType), Tool(ToolType),
// Consumables
Food(FoodType), Food(FoodType),
Drink(DrinkType), Drink(DrinkType),
Medicine, Medicine,
// Crafting materials
RawMaterial, RawMaterial,
// Trade goods
Gem, Gem,
Coin, Coin,
// Misc
Container, Container,
Book, Book,
Toy, Toy,
+5 -10
View File
@@ -1,24 +1,20 @@
use crate::constants::TILE_SIZE; use crate::constants::TILE_SIZE;
use crate::constants::*; use crate::constants::*;
use crate::entities::item::{ use crate::entities::item::{spawn_prefab, MiscPrefab};
create_decorated_item, spawn_prefab, FoodType, Item, ItemBundle, ItemDecorations, ItemType,
Material, MiscPrefab, Quality, WoodType,
};
use crate::entities::shared_components::Ambulatory; use crate::entities::shared_components::Ambulatory;
use crate::world::tiles::tilemap;
use crate::world::VisibleGameEntity; use crate::world::VisibleGameEntity;
use bevy::ecs::spawn;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_rand::prelude::*; use bevy_rand::prelude::*;
use rand::Rng; use rand::Rng;
// Pig bundle
#[derive(Bundle)] #[derive(Bundle)]
pub struct Pig { pub struct Pig {
ambulatory: Ambulatory, ambulatory: Ambulatory,
sprite: Sprite, sprite: Sprite,
transform: Transform, transform: Transform,
visibility: Visibility, visibility: Visibility,
drop_timer: PigDropTimer, // NEW drop_timer: PigDropTimer,
} }
impl Pig { impl Pig {
@@ -43,11 +39,9 @@ impl Pig {
} }
} }
// Timer component for pigs dropping items
#[derive(Component, Deref, DerefMut)] #[derive(Component, Deref, DerefMut)]
pub struct PigDropTimer(pub Timer); pub struct PigDropTimer(pub Timer);
// Spawn a handful of pigs
pub fn spawn_pigs( pub fn spawn_pigs(
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
@@ -70,12 +64,12 @@ pub fn spawn_pigs(
} }
} }
// System: make pigs drop items every 5 seconds
pub fn pig_drop_system( pub fn pig_drop_system(
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
time: Res<Time>, time: Res<Time>,
mut pigs: Query<(&mut PigDropTimer, &Transform)>, mut pigs: Query<(&mut PigDropTimer, &Transform)>,
mut tilemap: ResMut<tilemap::TileMap>,
) { ) {
for (mut timer, transform) in &mut pigs { for (mut timer, transform) in &mut pigs {
timer.tick(time.delta()); timer.tick(time.delta());
@@ -87,6 +81,7 @@ pub fn pig_drop_system(
&asset_server, &asset_server,
MiscPrefab::RawMeat, MiscPrefab::RawMeat,
transform.translation, transform.translation,
&mut tilemap,
); );
} }
} }
-1
View File
@@ -41,7 +41,6 @@ pub fn spawn_rabbits(
mut rng_q: Query<&mut Entropy<WyRand>, With<Global>>, mut rng_q: Query<&mut Entropy<WyRand>, With<Global>>,
) { ) {
if let Ok(mut rng) = rng_q.single_mut() { if let Ok(mut rng) = rng_q.single_mut() {
// Spawn a handful of rabbits
for _ in 0..15 { for _ in 0..15 {
let rab = commands let rab = commands
.spawn(Rabbit::new( .spawn(Rabbit::new(
+6 -1
View File
@@ -1,7 +1,10 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_rand::prelude::*; use bevy_rand::prelude::*;
use crate::entities::livestock::pig::pig_drop_system; use crate::{
entities::livestock::pig::pig_drop_system,
world::tiles::{item_tile_management_system, ItemRotationTimer},
};
mod camera; mod camera;
mod constants; mod constants;
@@ -56,5 +59,7 @@ fn main() {
), ),
) )
.add_systems(Update, pig_drop_system) .add_systems(Update, pig_drop_system)
.insert_resource(ItemRotationTimer::default())
.add_systems(Update, item_tile_management_system)
.run(); .run();
} }
+90
View File
@@ -1,8 +1,98 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_platform::collections::hash_map::HashMap; use bevy_platform::collections::hash_map::HashMap;
use crate::{entities::item::Item, game};
#[derive(Resource, Default, Clone)] #[derive(Resource, Default, Clone)]
pub struct TileMap { pub struct TileMap {
pub floor_tiles: HashMap<IVec3, (i32, bool, bool, bool, i32, [u32; 8])>, //id, canStandIn, canStandOn, visiblyTransparent, astar_weight, visible_range pub floor_tiles: HashMap<IVec3, (i32, bool, bool, bool, i32, [u32; 8])>, //id, canStandIn, canStandOn, visiblyTransparent, astar_weight, visible_range
pub fixture_tiles: HashMap<IVec3, (i32, bool, bool, [u32; 8])>, // id, canStandIn, canStandOn, visible_range pub fixture_tiles: HashMap<IVec3, (i32, bool, bool, [u32; 8])>, // id, canStandIn, canStandOn, visible_range
pub item_tiles: HashMap<IVec3, Vec<u32>>, // Entity.id's of items on this tile
}
#[derive(Resource)]
pub struct ItemRotationTimer {
timer: Timer,
current_indices: HashMap<IVec3, usize>,
}
impl Default for ItemRotationTimer {
fn default() -> Self {
Self {
timer: Timer::from_seconds(0.4f32, TimerMode::Repeating),
current_indices: HashMap::default(),
}
}
}
pub fn item_tile_management_system(
time: Res<Time>,
mut tilemap: ResMut<TileMap>,
mut rotation_timer: ResMut<ItemRotationTimer>,
z_index: Res<game::ZIndex>,
mut visibility_query: Query<&mut Visibility, With<Item>>,
) {
println!(
"{} items accross {} tiles",
visibility_query.iter().count(),
tilemap.item_tiles.len()
);
rotation_timer.timer.tick(time.delta());
let mut tiles_to_remove = Vec::new();
let mut tiles_with_items = Vec::new();
for (position, items) in tilemap.item_tiles.iter() {
if items.is_empty() {
tiles_to_remove.push(*position);
} else {
tiles_with_items.push((*position, items.clone()));
}
}
for position in tiles_to_remove {
tilemap.item_tiles.remove(&position);
rotation_timer.current_indices.remove(&position);
}
if rotation_timer.timer.just_finished() {
for (position, items) in tiles_with_items {
if position.z > z_index.0 as i32 {
continue;
}
if items.len() <= 1 {
if let Some(&entity_id) = items.first() {
let entity = Entity::from_raw(entity_id);
if let Ok(mut visibility) = visibility_query.get_mut(entity) {
*visibility = Visibility::Visible;
}
}
continue;
}
let current_index = rotation_timer
.current_indices
.get(&position)
.copied()
.unwrap_or(0);
if let Some(&current_entity_id) = items.get(current_index) {
let current_entity = Entity::from_raw(current_entity_id);
if let Ok(mut visibility) = visibility_query.get_mut(current_entity) {
*visibility = Visibility::Hidden;
}
}
let next_index = (current_index + 1) % items.len();
if let Some(&next_entity_id) = items.get(next_index) {
let next_entity = Entity::from_raw(next_entity_id);
if let Ok(mut visibility) = visibility_query.get_mut(next_entity) {
*visibility = Visibility::Visible;
}
}
rotation_timer.current_indices.insert(position, next_index);
}
}
} }