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)]
pub struct Container {
// Maximum weight this container can hold
pub max_weight: u32,
// Maximum number of items (None = unlimited)
pub max_items: Option<u32>,
// Current weight of contents
pub current_weight: u32,
// List of item entities contained
pub contents: Vec<Entity>, // TODO - move away from Entity
// Whether the container is currently open/accessible
pub is_open: bool,
}
+3 -32
View File
@@ -8,14 +8,10 @@ use crate::entities::item::types::ItemType;
#[derive(Component, Debug)]
#[require(Transform, Visibility)]
pub struct Item {
// How worn/damaged the item is (0-255, where 255 is completely destroyed)
pub wear: u8,
// The quality of craftsmanship for the base item
pub quality: Quality,
// Time until stains fade (in game ticks/turns),
pub stain_time: u16,
// Temperature of the item (for hot/cold items)
pub temperature: i16, // Can be negative for very cold items
pub temperature: i16, // Can be negative for cold items
}
impl Default for Item {
@@ -52,27 +48,19 @@ impl Item {
let total_weight = base_weight + decoration_weight;
// Convert to u32 (multiply by 1000 to get grams if density was in kg/unit)
(total_weight * 1000.0) as u32
// Convert to u32
(total_weight) as u32
}
// Calculate total value considering all factors
pub fn calculate_value(
&self,
item_type: &ItemType,
base_material: &Material,
decorations: Option<&ItemDecorations>,
) -> u32 {
// Base value from item type and material
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();
// Additional value from decorations
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 total_value = (quality_modified_value + decoration_value as f32) * wear_multiplier;
@@ -80,33 +68,27 @@ impl Item {
total_value as u32
}
// Check if the item is destroyed due to wear
pub fn is_destroyed(&self) -> bool {
self.wear >= 255
}
// Apply wear to the item
pub fn add_wear(&mut self, wear_amount: u8) {
self.wear = self.wear.saturating_add(wear_amount);
}
// Check if item has any stains
pub fn has_stains(&self) -> bool {
self.stain_time > 0
}
// Reduce stain time (call this periodically to fade stains)
pub fn reduce_stain_time(&mut self, amount: u16) {
self.stain_time = self.stain_time.saturating_sub(amount);
}
// Add staining to the item
pub fn add_stain(&mut self, duration: u16) {
self.stain_time = self.stain_time.saturating_add(duration);
}
}
// Bundle for creating item entities
#[derive(Bundle)]
pub struct ItemBundle {
pub item: Item,
@@ -118,12 +100,9 @@ pub struct ItemBundle {
pub sprite: Sprite,
}
// Component for items that have names (can be generated or custom)
#[derive(Component, Debug, Clone)]
pub struct ItemName {
// The base name of the item type
pub base_name: String,
// Custom name given by player or generated
pub custom_name: Option<String>,
}
@@ -144,25 +123,20 @@ impl ItemName {
) -> String {
let mut parts = Vec::new();
// Quality prefix
let quality_name = item.quality.display_name();
if !quality_name.is_empty() {
parts.push(quality_name.to_string());
}
// Base material
parts.push(base_material.name());
// Item name
parts.push(self.base_name.clone());
let mut result = parts.join(" ");
// Add decorations to the description
if let Some(decorations) = decorations {
let mut decoration_parts = Vec::new();
// Gem encrustings
for gem in &decorations.gem_encrustings {
let quality_prefix = if gem.quality != Quality::Ordinary {
format!("{} ", gem.quality.display_name())
@@ -176,7 +150,6 @@ impl ItemName {
));
}
// Trim materials
for trim in &decorations.trim_materials {
let quality_prefix = if trim.quality != Quality::Ordinary {
format!("{} ", trim.quality.display_name())
@@ -191,7 +164,6 @@ impl ItemName {
decoration_parts.push(format!("with {} trim", trim_name));
}
// Engravings
for engraving in &decorations.engravings {
let quality_prefix = if engraving.quality != Quality::Ordinary {
format!("{} ", engraving.quality.display_name())
@@ -209,7 +181,6 @@ impl ItemName {
}
}
// Custom name
if let Some(custom) = &self.custom_name {
result = format!("\"{}\" {}", custom, result);
}
+7 -13
View File
@@ -5,11 +5,8 @@ use crate::entities::item::quality::Quality;
#[derive(Component, Debug, Clone)]
pub struct ItemDecorations {
// Gems encrusted on the item
pub gem_encrustings: Vec<GemEncrusting>,
// Metal trim/bands
pub trim_materials: Vec<TrimMaterial>,
// Engravings on the item
pub engravings: Vec<Engraving>,
}
@@ -28,7 +25,6 @@ impl ItemDecorations {
Self::default()
}
// Calculate total additional weight from decorations
pub fn total_decoration_weight(&self) -> f32 {
let gem_weight: f32 = self
.gem_encrustings
@@ -42,11 +38,9 @@ impl ItemDecorations {
.map(|trim| trim.material.density() * trim.amount)
.sum();
// Engravings don't add weight, they remove material
gem_weight + trim_weight
}
// Calculate total additional value from decorations
pub fn total_decoration_value(&self) -> u32 {
let gem_value: f32 = self
.gem_encrustings
@@ -97,26 +91,26 @@ impl ItemDecorations {
#[derive(Debug, Clone)]
pub struct GemEncrusting {
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 quality: Quality, // Quality of the gem cut
pub size: f32,
pub quality: Quality,
}
#[derive(Debug, Clone)]
pub struct TrimMaterial {
pub material: Material, // Usually metal for bands/trim
pub amount: f32, // Amount of material used
pub quality: Quality, // Quality of the trim work
pub amount: f32,
pub quality: Quality,
}
#[derive(Debug, Clone)]
pub struct Engraving {
pub subject: String, // What the engraving depicts
pub quality: Quality, // Quality of the engraving
pub subject: String,
pub quality: Quality,
}
impl Engraving {
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
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ pub enum 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 {
match self {
Material::NA => 1.0,
+1 -8
View File
@@ -6,18 +6,15 @@ use crate::entities::item::Item;
#[derive(Component, Debug)]
pub struct Perishable {
// How much the item has decayed (0-255)
pub decay: u8,
// How fast this item decays per tick
pub decay_rate: u8,
// What happens when fully decayed
pub decay_result: DecayResult,
}
#[derive(Debug, Clone)]
pub enum DecayResult {
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(
@@ -26,18 +23,14 @@ pub fn decay_system(
mut commands: Commands,
) {
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 {
// Decay every 60 seconds
perishable.decay = perishable.decay.saturating_add(perishable.decay_rate);
if perishable.decay >= 255 {
match &perishable.decay_result {
DecayResult::Disappear => {
commands.entity(entity).despawn();
}
DecayResult::Transform(new_item_type, new_material) => {
// Transform the item
commands
.entity(entity)
.insert(new_item_type.clone())
+22 -2
View File
@@ -1,9 +1,9 @@
use bevy::prelude::*;
use crate::entities::item::{Item, ItemBundle, ItemDecorations, ItemType, Material, Quality};
use crate::world::tiles::tilemap;
use crate::world::VisibleGameEntity;
// Enum for misc item prefabs
#[derive(Debug, Clone, Copy)]
pub enum MiscPrefab {
RawMeat,
@@ -12,11 +12,13 @@ pub enum MiscPrefab {
// 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.
// As well as acting as demonstration of how to spawn items for the future.
pub fn spawn_prefab(
commands: &mut Commands,
asset_server: &Res<AssetServer>,
prefab: MiscPrefab,
position: Vec3,
tilemap: &mut ResMut<tilemap::TileMap>,
) -> Entity {
match prefab {
MiscPrefab::RawMeat => {
@@ -37,7 +39,16 @@ pub fn spawn_prefab(
},
})
.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 => {
let coin = commands
@@ -57,6 +68,15 @@ pub fn spawn_prefab(
},
})
.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()
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ pub enum Quality {
Superior = 3,
Exceptional = 4,
Masterwork = 5,
// Special artifact quality
// Special artifact quality (created through moments of genius or madness)
Artifact = 10,
}
+1 -3
View File
@@ -8,9 +8,7 @@ use crate::entities::item::Item;
use crate::entities::item::ItemBundle;
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>) {
// Fade stains every 10 seconds
if time.elapsed_secs_f64() as u32 % 10 == 0 {
for mut item in items.iter_mut() {
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(
commands: &mut Commands,
item_type: ItemType,
-7
View File
@@ -2,22 +2,15 @@ use bevy::prelude::*;
#[derive(Component, Debug, Clone, PartialEq, Eq, Hash)]
pub enum ItemType {
// Weapons
Weapon(WeaponType),
// Armor/Clothing
Armor(ArmorType),
// Tools
Tool(ToolType),
// Consumables
Food(FoodType),
Drink(DrinkType),
Medicine,
// Crafting materials
RawMaterial,
// Trade goods
Gem,
Coin,
// Misc
Container,
Book,
Toy,
+5 -10
View File
@@ -1,24 +1,20 @@
use crate::constants::TILE_SIZE;
use crate::constants::*;
use crate::entities::item::{
create_decorated_item, spawn_prefab, FoodType, Item, ItemBundle, ItemDecorations, ItemType,
Material, MiscPrefab, Quality, WoodType,
};
use crate::entities::item::{spawn_prefab, MiscPrefab};
use crate::entities::shared_components::Ambulatory;
use crate::world::tiles::tilemap;
use crate::world::VisibleGameEntity;
use bevy::ecs::spawn;
use bevy::prelude::*;
use bevy_rand::prelude::*;
use rand::Rng;
// Pig bundle
#[derive(Bundle)]
pub struct Pig {
ambulatory: Ambulatory,
sprite: Sprite,
transform: Transform,
visibility: Visibility,
drop_timer: PigDropTimer, // NEW
drop_timer: PigDropTimer,
}
impl Pig {
@@ -43,11 +39,9 @@ impl Pig {
}
}
// Timer component for pigs dropping items
#[derive(Component, Deref, DerefMut)]
pub struct PigDropTimer(pub Timer);
// Spawn a handful of pigs
pub fn spawn_pigs(
mut commands: Commands,
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(
mut commands: Commands,
asset_server: Res<AssetServer>,
time: Res<Time>,
mut pigs: Query<(&mut PigDropTimer, &Transform)>,
mut tilemap: ResMut<tilemap::TileMap>,
) {
for (mut timer, transform) in &mut pigs {
timer.tick(time.delta());
@@ -87,6 +81,7 @@ pub fn pig_drop_system(
&asset_server,
MiscPrefab::RawMeat,
transform.translation,
&mut tilemap,
);
}
}
-1
View File
@@ -41,7 +41,6 @@ pub fn spawn_rabbits(
mut rng_q: Query<&mut Entropy<WyRand>, With<Global>>,
) {
if let Ok(mut rng) = rng_q.single_mut() {
// Spawn a handful of rabbits
for _ in 0..15 {
let rab = commands
.spawn(Rabbit::new(
+6 -1
View File
@@ -1,7 +1,10 @@
use bevy::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 constants;
@@ -56,5 +59,7 @@ fn main() {
),
)
.add_systems(Update, pig_drop_system)
.insert_resource(ItemRotationTimer::default())
.add_systems(Update, item_tile_management_system)
.run();
}
+90
View File
@@ -1,8 +1,98 @@
use bevy::prelude::*;
use bevy_platform::collections::hash_map::HashMap;
use crate::{entities::item::Item, game};
#[derive(Resource, Default, Clone)]
pub struct TileMap {
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 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);
}
}
}