example item
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
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,
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn new(max_weight: u32, max_items: Option<u32>) -> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::entities::item::decorations::ItemDecorations;
|
||||
use crate::entities::item::material::Material;
|
||||
use crate::entities::item::quality::Quality;
|
||||
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
|
||||
}
|
||||
|
||||
impl Default for Item {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
wear: 0,
|
||||
quality: Quality::default(),
|
||||
stain_time: 0,
|
||||
temperature: 20, // Room temperature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
// Calculate weight based on materials and decorations
|
||||
pub fn calculate_weight(
|
||||
&self,
|
||||
item_type: &ItemType,
|
||||
base_material: &Material,
|
||||
decorations: Option<&ItemDecorations>,
|
||||
) -> u32 {
|
||||
// Base weight from primary material
|
||||
let base_volume = item_type.base_volume();
|
||||
let base_weight = base_material.density() * base_volume;
|
||||
|
||||
// Additional weight from decorations
|
||||
let decoration_weight = decorations
|
||||
.map(|d| d.total_decoration_weight())
|
||||
.unwrap_or(0.0);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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,
|
||||
pub item_type: ItemType,
|
||||
pub base_material: Material,
|
||||
pub decorations: ItemDecorations,
|
||||
pub transform: Transform,
|
||||
pub visibility: Visibility,
|
||||
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>,
|
||||
}
|
||||
|
||||
impl ItemName {
|
||||
pub fn new(base_name: String) -> Self {
|
||||
Self {
|
||||
base_name,
|
||||
custom_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Generate full display name including materials and decorations
|
||||
pub fn display_name(
|
||||
&self,
|
||||
item: &Item,
|
||||
base_material: &Material,
|
||||
decorations: Option<&ItemDecorations>,
|
||||
) -> 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())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
decoration_parts.push(format!(
|
||||
"encrusted with {}{}",
|
||||
quality_prefix,
|
||||
gem.material.name()
|
||||
));
|
||||
}
|
||||
|
||||
// Trim materials
|
||||
for trim in &decorations.trim_materials {
|
||||
let quality_prefix = if trim.quality != Quality::Ordinary {
|
||||
format!("{} ", trim.quality.display_name())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let trim_name = if quality_prefix.is_empty() {
|
||||
trim.material.name()
|
||||
} else {
|
||||
format!("{} {}", quality_prefix, trim.material.name())
|
||||
};
|
||||
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())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
decoration_parts.push(format!(
|
||||
"engraved with {}images of {}",
|
||||
quality_prefix, engraving.subject
|
||||
));
|
||||
}
|
||||
|
||||
if !decoration_parts.is_empty() {
|
||||
result = format!("{}, {}", result, decoration_parts.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
// Custom name
|
||||
if let Some(custom) = &self.custom_name {
|
||||
result = format!("\"{}\" {}", custom, result);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::entities::item::material::Material;
|
||||
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>,
|
||||
}
|
||||
|
||||
impl Default for ItemDecorations {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gem_encrustings: Vec::new(),
|
||||
trim_materials: Vec::new(),
|
||||
engravings: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemDecorations {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
// Calculate total additional weight from decorations
|
||||
pub fn total_decoration_weight(&self) -> f32 {
|
||||
let gem_weight: f32 = self
|
||||
.gem_encrustings
|
||||
.iter()
|
||||
.map(|gem| gem.material.density() * gem.size)
|
||||
.sum();
|
||||
|
||||
let trim_weight: f32 = self
|
||||
.trim_materials
|
||||
.iter()
|
||||
.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
|
||||
.iter()
|
||||
.map(|gem| gem.material.base_value() as f32 * gem.size * gem.quality.value_multiplier())
|
||||
.sum();
|
||||
|
||||
let trim_value: f32 = self
|
||||
.trim_materials
|
||||
.iter()
|
||||
.map(|trim| {
|
||||
trim.material.base_value() as f32 * trim.amount * trim.quality.value_multiplier()
|
||||
})
|
||||
.sum();
|
||||
|
||||
let engraving_value: f32 = self
|
||||
.engravings
|
||||
.iter()
|
||||
.map(|engraving| engraving.base_value() * engraving.quality.value_multiplier())
|
||||
.sum();
|
||||
|
||||
(gem_value + trim_value + engraving_value) as u32
|
||||
}
|
||||
|
||||
pub fn add_gem_encrusting(&mut self, material: Material, size: f32, quality: Quality) {
|
||||
if let Material::Gem(_) = material {
|
||||
self.gem_encrustings.push(GemEncrusting {
|
||||
material,
|
||||
size,
|
||||
quality,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_trim(&mut self, material: Material, amount: f32, quality: Quality) {
|
||||
self.trim_materials.push(TrimMaterial {
|
||||
material,
|
||||
amount,
|
||||
quality,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_engraving(&mut self, subject: String, quality: Quality) {
|
||||
self.engravings.push(Engraving { subject, quality });
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Engraving {
|
||||
pub subject: String, // What the engraving depicts
|
||||
pub quality: Quality, // Quality of the engraving
|
||||
}
|
||||
|
||||
impl Engraving {
|
||||
pub fn base_value(&self) -> f32 {
|
||||
// Base value for engravings (they're pure craftsmanship)
|
||||
20.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Material {
|
||||
NA,
|
||||
Wood(WoodType),
|
||||
Stone(StoneType),
|
||||
Metal(MetalType),
|
||||
Cloth(ClothType),
|
||||
Leather(LeatherType),
|
||||
Bone,
|
||||
Shell,
|
||||
Glass,
|
||||
Ceramic,
|
||||
Gem(GemType),
|
||||
Wax,
|
||||
}
|
||||
|
||||
impl Material {
|
||||
// Weight per unit volume (arbitrary units - adjust as needed)
|
||||
pub fn density(&self) -> f32 {
|
||||
match self {
|
||||
Material::NA => 1.0,
|
||||
Material::Wood(wood) => match wood {
|
||||
WoodType::Oak => 0.7,
|
||||
WoodType::Pine => 0.5,
|
||||
WoodType::Birch => 0.6,
|
||||
WoodType::Ironwood => 1.2,
|
||||
},
|
||||
Material::Stone(stone) => match stone {
|
||||
StoneType::Granite => 2.7,
|
||||
StoneType::Marble => 2.6,
|
||||
StoneType::Sandstone => 2.2,
|
||||
StoneType::Obsidian => 2.4,
|
||||
},
|
||||
Material::Metal(metal) => match metal {
|
||||
MetalType::Iron => 7.9,
|
||||
MetalType::Steel => 7.8,
|
||||
MetalType::Copper => 8.9,
|
||||
MetalType::Silver => 10.5,
|
||||
MetalType::Gold => 19.3,
|
||||
MetalType::Platinum => 21.5,
|
||||
},
|
||||
Material::Cloth(_) => 0.3,
|
||||
Material::Leather(_) => 0.8,
|
||||
Material::Bone => 1.8,
|
||||
Material::Shell => 2.0,
|
||||
Material::Glass => 2.5,
|
||||
Material::Ceramic => 2.3,
|
||||
Material::Gem(gem) => match gem {
|
||||
GemType::Diamond => 3.5,
|
||||
GemType::Ruby => 4.0,
|
||||
GemType::Emerald => 2.7,
|
||||
GemType::Sapphire => 4.0,
|
||||
},
|
||||
Material::Wax => 0.1,
|
||||
}
|
||||
}
|
||||
|
||||
// Base value per unit (before quality modifiers)
|
||||
pub fn base_value(&self) -> u32 {
|
||||
match self {
|
||||
Material::NA => 1,
|
||||
Material::Wood(_) => 1,
|
||||
Material::Stone(_) => 2,
|
||||
Material::Metal(metal) => match metal {
|
||||
MetalType::Iron => 5,
|
||||
MetalType::Steel => 8,
|
||||
MetalType::Copper => 3,
|
||||
MetalType::Silver => 15,
|
||||
MetalType::Gold => 30,
|
||||
MetalType::Platinum => 40,
|
||||
},
|
||||
Material::Cloth(_) => 2,
|
||||
Material::Leather(_) => 3,
|
||||
Material::Bone => 1,
|
||||
Material::Shell => 2,
|
||||
Material::Glass => 4,
|
||||
Material::Ceramic => 3,
|
||||
Material::Gem(gem) => match gem {
|
||||
GemType::Diamond => 200,
|
||||
GemType::Ruby => 150,
|
||||
GemType::Emerald => 100,
|
||||
GemType::Sapphire => 120,
|
||||
},
|
||||
Material::Wax => 3,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
Material::NA => "N/A".to_string(),
|
||||
Material::Wood(wood) => format!("{:?}", wood).to_lowercase(),
|
||||
Material::Stone(stone) => format!("{:?}", stone).to_lowercase(),
|
||||
Material::Metal(metal) => format!("{:?}", metal).to_lowercase(),
|
||||
Material::Cloth(cloth) => format!("{:?}", cloth).to_lowercase(),
|
||||
Material::Leather(leather) => {
|
||||
format!("{} leather", format!("{:?}", leather).to_lowercase())
|
||||
}
|
||||
Material::Bone => "bone".to_string(),
|
||||
Material::Shell => "shell".to_string(),
|
||||
Material::Glass => "glass".to_string(),
|
||||
Material::Ceramic => "ceramic".to_string(),
|
||||
Material::Gem(gem) => format!("{:?}", gem).to_lowercase(),
|
||||
Material::Wax => "wax".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum WoodType {
|
||||
Oak,
|
||||
Pine,
|
||||
Birch,
|
||||
Ironwood,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum StoneType {
|
||||
Granite,
|
||||
Marble,
|
||||
Sandstone,
|
||||
Obsidian,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum MetalType {
|
||||
Iron,
|
||||
Steel,
|
||||
Copper,
|
||||
Silver,
|
||||
Gold,
|
||||
Platinum,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ClothType {
|
||||
Cotton,
|
||||
Silk,
|
||||
Wool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum LeatherType {
|
||||
Cow,
|
||||
Pig,
|
||||
Dragon,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum GemType {
|
||||
Diamond,
|
||||
Ruby,
|
||||
Emerald,
|
||||
Sapphire,
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
pub mod container;
|
||||
pub mod core;
|
||||
pub mod decorations;
|
||||
pub mod material;
|
||||
pub mod perishable;
|
||||
pub mod prefabs;
|
||||
pub mod quality;
|
||||
pub mod systems;
|
||||
pub mod types;
|
||||
|
||||
pub use container::*;
|
||||
pub use core::*;
|
||||
pub use decorations::*;
|
||||
pub use material::*;
|
||||
pub use perishable::*;
|
||||
pub use prefabs::*;
|
||||
pub use quality::*;
|
||||
pub use systems::*;
|
||||
pub use types::*;
|
||||
@@ -0,0 +1,51 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::entities::item::material::Material;
|
||||
use crate::entities::item::types::ItemType;
|
||||
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
|
||||
}
|
||||
|
||||
pub fn decay_system(
|
||||
time: Res<Time>,
|
||||
mut perishable_items: Query<(Entity, &mut Perishable, &Item, &ItemType, &Material)>,
|
||||
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())
|
||||
.insert(new_material.clone())
|
||||
.remove::<Perishable>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::entities::item::{Item, ItemBundle, ItemDecorations, ItemType, Material, Quality};
|
||||
use crate::world::VisibleGameEntity;
|
||||
|
||||
// Enum for misc item prefabs
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum MiscPrefab {
|
||||
RawMeat,
|
||||
Coin,
|
||||
}
|
||||
|
||||
// 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.
|
||||
pub fn spawn_prefab(
|
||||
commands: &mut Commands,
|
||||
asset_server: &Res<AssetServer>,
|
||||
prefab: MiscPrefab,
|
||||
position: Vec3,
|
||||
) -> Entity {
|
||||
match prefab {
|
||||
MiscPrefab::RawMeat => {
|
||||
let meat = commands
|
||||
.spawn(ItemBundle {
|
||||
item: Item {
|
||||
quality: Quality::Ordinary,
|
||||
..Item::default()
|
||||
},
|
||||
item_type: ItemType::Food(crate::entities::item::FoodType::Meat),
|
||||
base_material: Material::NA,
|
||||
decorations: ItemDecorations::new(),
|
||||
transform: Transform::from_translation(position - Vec3::new(0.0, 0.0, 0.1)),
|
||||
visibility: Visibility::Visible,
|
||||
sprite: Sprite {
|
||||
image: asset_server.load("raw_meat.png"),
|
||||
..Default::default()
|
||||
},
|
||||
})
|
||||
.id();
|
||||
commands.entity(meat).insert(VisibleGameEntity).id()
|
||||
}
|
||||
MiscPrefab::Coin => {
|
||||
let coin = commands
|
||||
.spawn(ItemBundle {
|
||||
item: Item {
|
||||
quality: Quality::Ordinary,
|
||||
..Item::default()
|
||||
},
|
||||
item_type: ItemType::Coin,
|
||||
base_material: Material::Metal(crate::entities::item::MetalType::Copper),
|
||||
decorations: ItemDecorations::new(),
|
||||
transform: Transform::from_translation(position - Vec3::new(0.0, 0.0, 0.1)),
|
||||
visibility: Visibility::Visible,
|
||||
sprite: Sprite {
|
||||
image: asset_server.load("coin.png"),
|
||||
..Default::default()
|
||||
},
|
||||
})
|
||||
.id();
|
||||
commands.entity(coin).insert(VisibleGameEntity).id()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod misc_prefabs;
|
||||
|
||||
pub use misc_prefabs::*;
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod misc; // miscellaneous items (raw meat, coins, etc.)
|
||||
|
||||
pub use misc::*; // re-export misc prefabs for easier access
|
||||
@@ -0,0 +1,46 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Quality {
|
||||
// Standard qualities
|
||||
Ordinary = 0,
|
||||
WellCrafted = 1,
|
||||
FinelyCrafted = 2,
|
||||
Superior = 3,
|
||||
Exceptional = 4,
|
||||
Masterwork = 5,
|
||||
// Special artifact quality
|
||||
Artifact = 10,
|
||||
}
|
||||
|
||||
impl Quality {
|
||||
pub fn value_multiplier(&self) -> f32 {
|
||||
match self {
|
||||
Quality::Ordinary => 1.0,
|
||||
Quality::WellCrafted => 1.2,
|
||||
Quality::FinelyCrafted => 1.5,
|
||||
Quality::Superior => 2.0,
|
||||
Quality::Exceptional => 3.0,
|
||||
Quality::Masterwork => 5.0,
|
||||
Quality::Artifact => 10.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
Quality::Ordinary => "",
|
||||
Quality::WellCrafted => "well-crafted",
|
||||
Quality::FinelyCrafted => "finely-crafted",
|
||||
Quality::Superior => "superior",
|
||||
Quality::Exceptional => "exceptional",
|
||||
Quality::Masterwork => "masterwork",
|
||||
Quality::Artifact => "artifact",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Quality {
|
||||
fn default() -> Self {
|
||||
Quality::Ordinary
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::entities::item::decorations::ItemDecorations;
|
||||
use crate::entities::item::material::{GemType, Material, MetalType};
|
||||
use crate::entities::item::quality::Quality;
|
||||
use crate::entities::item::types::ItemType;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a decorated item
|
||||
pub fn create_decorated_item(
|
||||
commands: &mut Commands,
|
||||
item_type: ItemType,
|
||||
base_material: Material,
|
||||
quality: Quality,
|
||||
position: Vec3,
|
||||
sprite: Sprite,
|
||||
) -> Entity {
|
||||
let mut decorations = ItemDecorations::new();
|
||||
|
||||
// Example: Add a ruby encrusting to valuable items
|
||||
if quality >= Quality::Superior {
|
||||
decorations.add_gem_encrusting(
|
||||
Material::Gem(GemType::Ruby),
|
||||
0.5, // Small gem
|
||||
Quality::WellCrafted,
|
||||
);
|
||||
}
|
||||
|
||||
// Example: Add silver trim to masterwork items
|
||||
if quality >= Quality::Masterwork {
|
||||
decorations.add_trim(
|
||||
Material::Metal(MetalType::Silver),
|
||||
0.2, // Small amount of trim
|
||||
quality,
|
||||
);
|
||||
}
|
||||
|
||||
// Example: Add engraving to exceptional+ items
|
||||
if quality >= Quality::Exceptional {
|
||||
decorations.add_engraving("a majestic dorf holding a pickaxe".to_string(), quality);
|
||||
}
|
||||
|
||||
commands
|
||||
.spawn(ItemBundle {
|
||||
item: Item {
|
||||
quality,
|
||||
..Item::default()
|
||||
},
|
||||
item_type: item_type.clone(),
|
||||
base_material,
|
||||
decorations,
|
||||
transform: Transform::from_translation(position),
|
||||
visibility: Visibility::Visible,
|
||||
sprite: sprite,
|
||||
})
|
||||
.insert(ItemName::new(item_type.name()))
|
||||
.id()
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
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,
|
||||
Decoration,
|
||||
}
|
||||
|
||||
impl ItemType {
|
||||
// Base volume for this item type (used for weight calculation)
|
||||
pub fn base_volume(&self) -> f32 {
|
||||
match self {
|
||||
ItemType::Weapon(weapon) => match weapon {
|
||||
WeaponType::Sword => 2.0,
|
||||
WeaponType::Axe => 1.5,
|
||||
WeaponType::Hammer => 3.0,
|
||||
WeaponType::Spear => 1.8,
|
||||
WeaponType::Bow => 0.8,
|
||||
WeaponType::Crossbow => 2.5,
|
||||
WeaponType::Dagger => 0.3,
|
||||
},
|
||||
ItemType::Armor(armor) => match armor {
|
||||
ArmorType::Helmet => 1.0,
|
||||
ArmorType::Chestplate => 4.0,
|
||||
ArmorType::Leggings => 2.5,
|
||||
ArmorType::Boots => 0.8,
|
||||
ArmorType::Gloves => 0.4,
|
||||
ArmorType::Shield => 2.0,
|
||||
ArmorType::Cloak => 0.5,
|
||||
ArmorType::Shirt => 0.3,
|
||||
ArmorType::Pants => 0.4,
|
||||
},
|
||||
ItemType::Tool(tool) => match tool {
|
||||
ToolType::Pickaxe => 2.0,
|
||||
ToolType::Shovel => 1.5,
|
||||
ToolType::Axe => 1.75,
|
||||
ToolType::Hammer => 1.0,
|
||||
ToolType::Anvil => 50.0, // Heavy!
|
||||
ToolType::Furnace => 100.0, // Very heavy!
|
||||
},
|
||||
ItemType::Food(_) => 0.2,
|
||||
ItemType::Drink(_) => 0.3,
|
||||
ItemType::Medicine => 0.1,
|
||||
ItemType::RawMaterial => 0.5,
|
||||
ItemType::Gem => 0.1,
|
||||
ItemType::Coin => 0.01,
|
||||
ItemType::Container => 1.0,
|
||||
ItemType::Book => 0.5,
|
||||
ItemType::Toy => 0.2,
|
||||
ItemType::Decoration => 0.3,
|
||||
}
|
||||
}
|
||||
|
||||
// Base value for this item type
|
||||
pub fn base_value(&self) -> u32 {
|
||||
match self {
|
||||
ItemType::Weapon(_) => 50,
|
||||
ItemType::Armor(_) => 30,
|
||||
ItemType::Tool(_) => 25,
|
||||
ItemType::Food(_) => 2,
|
||||
ItemType::Drink(_) => 3,
|
||||
ItemType::Medicine => 10,
|
||||
ItemType::RawMaterial => 1,
|
||||
ItemType::Gem => 100,
|
||||
ItemType::Coin => 1,
|
||||
ItemType::Container => 15,
|
||||
ItemType::Book => 20,
|
||||
ItemType::Toy => 5,
|
||||
ItemType::Decoration => 10,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
ItemType::Weapon(weapon) => format!("{:?}", weapon).to_lowercase(),
|
||||
ItemType::Armor(armor) => format!("{:?}", armor).to_lowercase(),
|
||||
ItemType::Tool(tool) => format!("{:?}", tool).to_lowercase(),
|
||||
ItemType::Food(food) => format!("{:?}", food).to_lowercase(),
|
||||
ItemType::Drink(drink) => format!("{:?}", drink).to_lowercase(),
|
||||
ItemType::Medicine => "medicine".to_string(),
|
||||
ItemType::RawMaterial => "raw material".to_string(),
|
||||
ItemType::Gem => "gem".to_string(),
|
||||
ItemType::Coin => "coin".to_string(),
|
||||
ItemType::Container => "container".to_string(),
|
||||
ItemType::Book => "book".to_string(),
|
||||
ItemType::Toy => "toy".to_string(),
|
||||
ItemType::Decoration => "decoration".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum WeaponType {
|
||||
Sword,
|
||||
Axe,
|
||||
Hammer,
|
||||
Spear,
|
||||
Bow,
|
||||
Crossbow,
|
||||
Dagger,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ArmorType {
|
||||
Helmet,
|
||||
Chestplate,
|
||||
Leggings,
|
||||
Boots,
|
||||
Gloves,
|
||||
Shield,
|
||||
Cloak,
|
||||
Shirt,
|
||||
Pants,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ToolType {
|
||||
Pickaxe,
|
||||
Shovel,
|
||||
Axe,
|
||||
Hammer,
|
||||
Anvil,
|
||||
Furnace,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum FoodType {
|
||||
Meat,
|
||||
Vegetable,
|
||||
Fruit,
|
||||
Grain,
|
||||
Prepared, // cooked meals
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum DrinkType {
|
||||
Water,
|
||||
Juice,
|
||||
Ale,
|
||||
Wine,
|
||||
Spirits,
|
||||
}
|
||||
@@ -1,17 +1,24 @@
|
||||
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::shared_components::Ambulatory;
|
||||
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
|
||||
}
|
||||
|
||||
impl Pig {
|
||||
@@ -31,19 +38,24 @@ impl Pig {
|
||||
},
|
||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
||||
visibility: Visibility::Hidden,
|
||||
drop_timer: PigDropTimer(Timer::from_seconds(5.0, TimerMode::Repeating)), // NEW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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>,
|
||||
mut rng_q: Query<&mut Entropy<WyRand>, With<Global>>,
|
||||
) {
|
||||
if let Ok(mut rng) = rng_q.single_mut() {
|
||||
// Spawn a handful of pigs
|
||||
for _ in 0..5 {
|
||||
let cit = commands
|
||||
let pig = commands
|
||||
.spawn(Pig::new(
|
||||
&asset_server,
|
||||
Vec3::new(
|
||||
@@ -53,7 +65,29 @@ pub fn spawn_pigs(
|
||||
) * TILE_SIZE,
|
||||
))
|
||||
.id();
|
||||
commands.entity(cit).insert(VisibleGameEntity);
|
||||
commands.entity(pig).insert(VisibleGameEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)>,
|
||||
) {
|
||||
for (mut timer, transform) in &mut pigs {
|
||||
timer.tick(time.delta());
|
||||
|
||||
if timer.just_finished() {
|
||||
// Example: pig drops raw meat
|
||||
spawn_prefab(
|
||||
&mut commands,
|
||||
&asset_server,
|
||||
MiscPrefab::RawMeat,
|
||||
transform.translation,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn spawn_rabbits(
|
||||
if let Ok(mut rng) = rng_q.single_mut() {
|
||||
// Spawn a handful of rabbits
|
||||
for _ in 0..15 {
|
||||
let cit = commands
|
||||
let rab = commands
|
||||
.spawn(Rabbit::new(
|
||||
&asset_server,
|
||||
Vec3::new(
|
||||
@@ -53,7 +53,7 @@ pub fn spawn_rabbits(
|
||||
) * TILE_SIZE,
|
||||
))
|
||||
.id();
|
||||
commands.entity(cit).insert(VisibleGameEntity);
|
||||
commands.entity(rab).insert(VisibleGameEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod item;
|
||||
pub mod livestock;
|
||||
pub mod sentient;
|
||||
pub mod shared_components;
|
||||
|
||||
Reference in New Issue
Block a user