generated from hheik/bevy-template
Added weight slowdown for workers and unit tests for inventory & slowdown
parent
d9319bfd5c
commit
f422d0afe4
|
|
@ -230,6 +230,12 @@ dependencies = [
|
|||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_float_eq"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d2119f741b79fe9907f5396d19bffcb46568cfcc315e78677d731972ac7085"
|
||||
|
||||
[[package]]
|
||||
name = "assert_type_match"
|
||||
version = "0.1.1"
|
||||
|
|
@ -2419,6 +2425,7 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
|||
name = "glorbs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_float_eq",
|
||||
"bevy",
|
||||
"bevy-inspector-egui",
|
||||
"bevy_mod_debugdump",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ bevy_rapier2d = "0.28.0"
|
|||
fastrand = "2.3.0"
|
||||
num-traits = "0.2.19"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_float_eq = "1.1.4"
|
||||
|
||||
# Enable a small amount of optimization in debug mode
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 509 B |
Binary file not shown.
|
|
@ -1,7 +1,12 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Component, Reflect)]
|
||||
use crate::util::Kilograms;
|
||||
|
||||
use super::item::Inventory;
|
||||
|
||||
#[derive(Clone, Debug, Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(Transform)]
|
||||
pub struct Mover {
|
||||
pub speed: f32,
|
||||
}
|
||||
|
|
@ -11,3 +16,47 @@ impl Default for Mover {
|
|||
Self { speed: 100.0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(Inventory)]
|
||||
pub struct WeightSlowdown {
|
||||
/// At what weight should speed be halved.
|
||||
///
|
||||
/// Should NOT be zero!
|
||||
pub halfpoint: Kilograms,
|
||||
}
|
||||
|
||||
impl Default for WeightSlowdown {
|
||||
fn default() -> Self {
|
||||
Self { halfpoint: 50.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightSlowdown {
|
||||
pub fn multiplier(&self, weight: Kilograms) -> f32 {
|
||||
1.0 / (weight.max(0.0) / self.halfpoint + 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::WeightSlowdown;
|
||||
use assert_float_eq::assert_f32_near;
|
||||
|
||||
#[test]
|
||||
fn slowdown_calculates_correctly() {
|
||||
let slowdown = WeightSlowdown { halfpoint: 10.0 };
|
||||
assert_f32_near!(slowdown.multiplier(0.0), 1.0);
|
||||
assert_f32_near!(slowdown.multiplier(5.0), 2.0 / 3.0);
|
||||
assert_f32_near!(slowdown.multiplier(10.0), 1.0 / 2.0);
|
||||
assert_f32_near!(slowdown.multiplier(20.0), 1.0 / 3.0);
|
||||
assert_f32_near!(slowdown.multiplier(100.0), 1.0 / 11.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_weight_acts_as_zero_weight() {
|
||||
let slowdown = WeightSlowdown { halfpoint: 10.0 };
|
||||
assert_f32_near!(slowdown.multiplier(-10.0), 1.0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
272
src/game/item.rs
272
src/game/item.rs
|
|
@ -7,25 +7,19 @@ use crate::util::Kilograms;
|
|||
#[require(Sprite)]
|
||||
pub enum Item {
|
||||
Wood,
|
||||
Stone,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
// TODO: implement
|
||||
fn stack_max_size(&self) -> Option<u32> {
|
||||
match self {
|
||||
_ => Some(100),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
fn weight(&self) -> Kilograms {
|
||||
match self {
|
||||
Self::Wood => 1.0, // Paper birch, height 30cm, diameter 18cm ~= 6 kg, chopped in 6 pieces ~= 1kg
|
||||
Self::Stone => 10.0, // Just a 10kg piece of rock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Reflect)]
|
||||
#[derive(Copy, Clone, Debug, Reflect, PartialEq, Eq)]
|
||||
pub struct ItemStack {
|
||||
pub item: Item,
|
||||
pub count: u32,
|
||||
|
|
@ -46,7 +40,7 @@ pub struct ItemSource {
|
|||
pub gather_limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||
#[derive(Clone, Debug, Default, Component, Reflect, PartialEq, Eq)]
|
||||
#[reflect(Component)]
|
||||
pub struct Inventory {
|
||||
pub capacity: Option<usize>,
|
||||
|
|
@ -55,7 +49,7 @@ pub struct Inventory {
|
|||
pub items: Vec<ItemStack>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum InventoryError {
|
||||
IndexNotFound {
|
||||
index: usize,
|
||||
|
|
@ -67,7 +61,7 @@ pub enum InventoryError {
|
|||
/// Result for trying to insert an item to inventory. Should be used and discarded immediately, as
|
||||
/// it might not be valid after mutating inventory. In future, It could contain a hash value for
|
||||
/// the valid inventory state that it aplies to!
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum InsertResult {
|
||||
/// Combine to an existing stack. Contains (`index`, `new_count`)
|
||||
Combine { index: usize, new_count: u32 },
|
||||
|
|
@ -75,7 +69,7 @@ pub enum InsertResult {
|
|||
Push { stack: ItemStack },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum RemoveResult {
|
||||
/// The stack will get depleted, so it can be removed. Contains the `index` of empty stack.
|
||||
Empty { index: usize },
|
||||
|
|
@ -228,6 +222,8 @@ pub fn update_item_sprite(
|
|||
for (mut sprite, item) in query.iter_mut() {
|
||||
sprite.image = assets.load(match item {
|
||||
Item::Wood => "sprites/wood.png",
|
||||
Item::Stone => "sprites/stone.png",
|
||||
// _ => "sprites/missing.png",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -238,3 +234,253 @@ pub struct SpawnItem {
|
|||
pub to: Vec2,
|
||||
pub velocity: Vec2,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use assert_float_eq::assert_f32_near;
|
||||
|
||||
#[test]
|
||||
fn inventory_weight_calculation() {
|
||||
let wood_weight = Item::Wood.weight();
|
||||
let stone_weight = Item::Stone.weight();
|
||||
let inventory = Inventory {
|
||||
items: vec![
|
||||
ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 1,
|
||||
},
|
||||
ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 5,
|
||||
},
|
||||
ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 0,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
assert_f32_near!(inventory.weight(), wood_weight + stone_weight * 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_push_result_ok() {
|
||||
let inventory = Inventory {
|
||||
items: vec![ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 1,
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
let stack = ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 2,
|
||||
};
|
||||
assert_eq!(
|
||||
inventory.try_insert(stack),
|
||||
Some(InsertResult::Push { stack })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_combine_result_ok() {
|
||||
let inventory = Inventory {
|
||||
items: vec![
|
||||
ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 1,
|
||||
},
|
||||
ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 5,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let stack = ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 1,
|
||||
};
|
||||
assert_eq!(
|
||||
inventory.try_insert(stack),
|
||||
Some(InsertResult::Combine {
|
||||
index: 1,
|
||||
new_count: 6
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_empty_result() {
|
||||
let inventory = Inventory {
|
||||
items: vec![
|
||||
ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 1,
|
||||
},
|
||||
ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 5,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
inventory.try_remove(ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 5
|
||||
}),
|
||||
Some(RemoveResult::Empty { index: 1 })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_partial_remove_result() {
|
||||
let inventory = Inventory {
|
||||
items: vec![
|
||||
ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 1,
|
||||
},
|
||||
ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 5,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
inventory.try_remove(ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 1
|
||||
}),
|
||||
Some(RemoveResult::Partial {
|
||||
index: 1,
|
||||
new_count: 4
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_invalid_transfer_results() {
|
||||
let inventory = Inventory {
|
||||
items: vec![ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 1,
|
||||
}],
|
||||
capacity: Some(1),
|
||||
};
|
||||
let wood_stack = ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 5,
|
||||
};
|
||||
assert_eq!(inventory.try_insert(wood_stack), None);
|
||||
assert_eq!(inventory.try_remove(wood_stack), None);
|
||||
|
||||
let inventory = Inventory {
|
||||
items: vec![ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 1,
|
||||
}],
|
||||
capacity: None,
|
||||
};
|
||||
assert_eq!(inventory.try_remove(wood_stack), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_insert_applies_ok() {
|
||||
let stack_a = ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 1,
|
||||
};
|
||||
let stack_b = ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 2,
|
||||
};
|
||||
let mut inventory = Inventory {
|
||||
items: vec![stack_a],
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
inventory.apply_insert(InsertResult::Push { stack: stack_b }),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
inventory,
|
||||
Inventory {
|
||||
items: vec![stack_a, stack_b],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
inventory.apply_insert(InsertResult::Combine {
|
||||
index: 1,
|
||||
new_count: 4
|
||||
}),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
inventory,
|
||||
Inventory {
|
||||
items: vec![
|
||||
stack_a,
|
||||
ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 4
|
||||
}
|
||||
],
|
||||
capacity: None
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_remove_applies_ok() {
|
||||
let stack_a = ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 5,
|
||||
};
|
||||
let stack_b = ItemStack {
|
||||
item: Item::Wood,
|
||||
count: 2,
|
||||
};
|
||||
let mut inventory = Inventory {
|
||||
items: vec![stack_a, stack_b],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
inventory.apply_remove(RemoveResult::Partial {
|
||||
index: 0,
|
||||
new_count: 2
|
||||
}),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
inventory,
|
||||
Inventory {
|
||||
items: vec![
|
||||
ItemStack {
|
||||
item: Item::Stone,
|
||||
count: 2
|
||||
},
|
||||
stack_b
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
inventory.apply_remove(RemoveResult::Empty { index: 0 }),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
inventory,
|
||||
Inventory {
|
||||
items: vec![stack_b],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use bevy::{prelude::*, sprite::Anchor};
|
||||
use bevy_rapier2d::prelude::*;
|
||||
use creature::Mover;
|
||||
use creature::{Mover, WeightSlowdown};
|
||||
use item::{Inventory, Item, ItemSource, ItemStack, Stockpile};
|
||||
use work::{WorkDuration, Worker};
|
||||
|
||||
|
|
@ -18,6 +18,7 @@ use crate::util::SpriteLoader;
|
|||
SpriteLoader(|| SpriteLoader::from("sprites/glorb.png")),
|
||||
Worker,
|
||||
Inventory(|| Inventory::with_capacity(1)),
|
||||
WeightSlowdown(|| WeightSlowdown { halfpoint: 50.0 }),
|
||||
RigidBody(|| RigidBody::KinematicPositionBased),
|
||||
Mover,
|
||||
)]
|
||||
|
|
@ -32,7 +33,7 @@ pub struct Glorb;
|
|||
..default()
|
||||
}),
|
||||
SpriteLoader(|| SpriteLoader::from("sprites/tree.png")),
|
||||
ItemSource(|| ItemSource { drops: vec![ItemStack { item: Item::Wood, count: 1 }], gather_limit: None }),
|
||||
ItemSource(|| ItemSource { drops: vec![ItemStack { item: Item::Wood, count: 50 }], gather_limit: Some(1) }),
|
||||
WorkDuration(|| WorkDuration(5.0))
|
||||
)]
|
||||
pub struct Tree;
|
||||
|
|
@ -46,7 +47,7 @@ pub struct Tree;
|
|||
..default()
|
||||
}),
|
||||
SpriteLoader(|| SpriteLoader::from("sprites/box.png")),
|
||||
Inventory(|| Inventory::with_capacity(25)),
|
||||
Inventory,
|
||||
Stockpile,
|
||||
)]
|
||||
pub struct Chest;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
game::{creature::Mover, item::Inventory, work::Worker},
|
||||
util::{inverse_lerp, lerp},
|
||||
use crate::game::{
|
||||
creature::{Mover, WeightSlowdown},
|
||||
item::Inventory,
|
||||
work::Worker,
|
||||
};
|
||||
|
||||
pub fn worker_movement(
|
||||
mut worker_query: Query<(Entity, &Mover, &Worker, &mut Transform, Option<&Inventory>)>,
|
||||
mut worker_query: Query<(Entity, &Mover, &Worker, &mut Transform)>,
|
||||
slowdown_query: Query<(&Inventory, &WeightSlowdown)>,
|
||||
global_query: Query<&GlobalTransform>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for (entity, mover, worker, mut transform, inventory) in worker_query.iter_mut() {
|
||||
for (entity, mover, worker, mut transform) in worker_query.iter_mut() {
|
||||
let Some(task) = worker.task.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -20,16 +22,12 @@ pub fn worker_movement(
|
|||
let diff = (to.translation() - from.translation()).xy();
|
||||
let dist = diff.length().max(0.);
|
||||
let dir = diff.normalize_or_zero();
|
||||
let movement = dir.extend(0.)
|
||||
* mover.speed
|
||||
* inventory.map_or(1.0, |inventory| {
|
||||
lerp(
|
||||
1.0,
|
||||
0.2,
|
||||
inverse_lerp(0.0, 10.0, inventory.weight()).min(1.0),
|
||||
)
|
||||
})
|
||||
* time.delta_secs();
|
||||
let weight_mult = slowdown_query
|
||||
.get(entity)
|
||||
.map_or(1.0, |(inventory, slowdown)| {
|
||||
slowdown.multiplier(inventory.weight())
|
||||
});
|
||||
let movement = dir.extend(0.) * mover.speed * weight_mult * time.delta_secs();
|
||||
transform.translation += movement.clamp_length_max(dist);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ pub fn setup_2d(mut commands: Commands) {
|
|||
commands.spawn((Transform::from_xyz(200.0, 0.0, 0.0), prefab::Tree));
|
||||
|
||||
// commands.spawn((Name::from("Wood"), Item::Wood));
|
||||
commands.spawn((Name::from("Stone"), Item::Stone));
|
||||
|
||||
commands.spawn((Transform::from_xyz(-200.0, -150.0, 0.0), prefab::Chest));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue