generated from hheik/bevy-template
Compare commits
No commits in common. "master" and "feature/debug_draw" have entirely different histories.
master
...
feature/de
|
|
@ -230,12 +230,6 @@ dependencies = [
|
||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "assert_float_eq"
|
|
||||||
version = "1.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "10d2119f741b79fe9907f5396d19bffcb46568cfcc315e78677d731972ac7085"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assert_type_match"
|
name = "assert_type_match"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -2425,13 +2419,11 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||||
name = "glorbs"
|
name = "glorbs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_float_eq",
|
|
||||||
"bevy",
|
"bevy",
|
||||||
"bevy-inspector-egui",
|
"bevy-inspector-egui",
|
||||||
"bevy_mod_debugdump",
|
"bevy_mod_debugdump",
|
||||||
"bevy_prototype_lyon",
|
"bevy_prototype_lyon",
|
||||||
"bevy_rapier2d",
|
"bevy_rapier2d",
|
||||||
"fastrand",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,8 @@ bevy-inspector-egui = "0.28.1"
|
||||||
bevy_mod_debugdump = "0.12.1"
|
bevy_mod_debugdump = "0.12.1"
|
||||||
bevy_prototype_lyon = "0.13.0"
|
bevy_prototype_lyon = "0.13.0"
|
||||||
bevy_rapier2d = "0.28.0"
|
bevy_rapier2d = "0.28.0"
|
||||||
fastrand = "2.3.0"
|
|
||||||
num-traits = "0.2.19"
|
num-traits = "0.2.19"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
assert_float_eq = "1.1.4"
|
|
||||||
|
|
||||||
# Enable a small amount of optimization in debug mode
|
# Enable a small amount of optimization in debug mode
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 509 B |
Binary file not shown.
|
|
@ -8,18 +8,15 @@ pub struct DebugSet;
|
||||||
|
|
||||||
impl Plugin for DebugPlugin {
|
impl Plugin for DebugPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.configure_sets(First, DebugSet.run_if(is_debug_enabled))
|
app.configure_sets(Last, DebugSet.run_if(is_debug_enabled))
|
||||||
.configure_sets(PreUpdate, DebugSet.run_if(is_debug_enabled))
|
.configure_sets(PostUpdate, DebugSet.run_if(is_debug_enabled));
|
||||||
.configure_sets(Update, DebugSet.run_if(is_debug_enabled))
|
|
||||||
.configure_sets(PostUpdate, DebugSet.run_if(is_debug_enabled))
|
|
||||||
.configure_sets(Last, DebugSet.run_if(is_debug_enabled));
|
|
||||||
|
|
||||||
app.insert_resource(DebugMode::off())
|
app.insert_resource(DebugMode::off())
|
||||||
.insert_resource(DebugDraw::default());
|
.insert_resource(DebugDraw::default());
|
||||||
|
|
||||||
app.add_plugins((
|
app.add_plugins((
|
||||||
bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled),
|
bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled),
|
||||||
// bevy_rapier2d::prelude::RapierDebugRenderPlugin::default(),
|
bevy_rapier2d::prelude::RapierDebugRenderPlugin::default(),
|
||||||
));
|
));
|
||||||
|
|
||||||
app.register_type::<DebugCanvas>()
|
app.register_type::<DebugCanvas>()
|
||||||
|
|
|
||||||
19
src/game.rs
19
src/game.rs
|
|
@ -3,7 +3,6 @@ use bevy::prelude::*;
|
||||||
use bevy_prototype_lyon::prelude::*;
|
use bevy_prototype_lyon::prelude::*;
|
||||||
use bevy_rapier2d::prelude::*;
|
use bevy_rapier2d::prelude::*;
|
||||||
|
|
||||||
mod creature;
|
|
||||||
mod item;
|
mod item;
|
||||||
mod prefab;
|
mod prefab;
|
||||||
mod systems;
|
mod systems;
|
||||||
|
|
@ -23,26 +22,10 @@ pub fn init(app: &mut App) {
|
||||||
.register_type::<item::Inventory>()
|
.register_type::<item::Inventory>()
|
||||||
.register_type::<work::WorkDuration>()
|
.register_type::<work::WorkDuration>()
|
||||||
.register_type::<work::Worker>()
|
.register_type::<work::Worker>()
|
||||||
.register_type::<creature::Mover>()
|
|
||||||
.register_type::<prefab::Glorb>()
|
.register_type::<prefab::Glorb>()
|
||||||
.register_type::<prefab::Tree>()
|
.register_type::<prefab::Tree>()
|
||||||
.add_event::<work::OnTaskFinish>()
|
|
||||||
.add_event::<item::SpawnItem>()
|
|
||||||
.add_systems(Startup, systems::setup_2d)
|
.add_systems(Startup, systems::setup_2d)
|
||||||
.add_systems(
|
.add_systems(Update, (systems::demo_2d, systems::work_select))
|
||||||
Update,
|
|
||||||
(
|
|
||||||
(
|
|
||||||
systems::demo_2d,
|
|
||||||
systems::work_select,
|
|
||||||
systems::worker_movement,
|
|
||||||
systems::spawn_items,
|
|
||||||
),
|
|
||||||
(systems::do_work,),
|
|
||||||
(systems::apply_task_result,),
|
|
||||||
)
|
|
||||||
.chain(),
|
|
||||||
)
|
|
||||||
.add_systems(PostUpdate, item::update_item_sprite)
|
.add_systems(PostUpdate, item::update_item_sprite)
|
||||||
.add_systems(Update, (systems::draw_job_targets).in_set(debug::DebugSet));
|
.add_systems(Update, (systems::draw_job_targets).in_set(debug::DebugSet));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
use crate::util::Kilograms;
|
|
||||||
|
|
||||||
use super::item::Inventory;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Component, Reflect)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
#[require(Transform)]
|
|
||||||
pub struct Mover {
|
|
||||||
pub speed: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Mover {
|
|
||||||
fn default() -> Self {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
393
src/game/item.rs
393
src/game/item.rs
|
|
@ -1,80 +1,50 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::util::Kilograms;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Component, Reflect)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Component, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
#[require(Sprite)]
|
#[require(Sprite)]
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
Wood,
|
Wood,
|
||||||
Stone,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
#[derive(Copy, Clone, Debug, Reflect)]
|
||||||
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, PartialEq, Eq)]
|
|
||||||
pub struct ItemStack {
|
pub struct ItemStack {
|
||||||
pub item: Item,
|
pub item: Item,
|
||||||
pub count: u32,
|
pub count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Item> for ItemStack {
|
impl Default for ItemStack {
|
||||||
fn from(item: Item) -> Self {
|
fn default() -> Self {
|
||||||
Self { item, count: 1 }
|
Self {
|
||||||
|
item: Item::Wood,
|
||||||
|
count: 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Component, Reflect)]
|
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct ItemSource {
|
pub struct ItemSource(pub ItemStack);
|
||||||
/// What items are spawned when gathered
|
|
||||||
pub drops: Vec<ItemStack>,
|
|
||||||
/// How many times this entity can be gathered before it despawns
|
|
||||||
pub gather_limit: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Component, Reflect, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct Inventory {
|
pub struct Inventory {
|
||||||
pub capacity: Option<usize>,
|
pub capacity: Option<usize>,
|
||||||
// TODO: Create a "Reserved" item stack type, that indicates that someone has reserved a slot
|
|
||||||
// for some new item type in the inventory
|
|
||||||
pub items: Vec<ItemStack>,
|
pub items: Vec<ItemStack>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum InventoryError {
|
|
||||||
IndexNotFound {
|
|
||||||
index: usize,
|
|
||||||
inventory: Inventory,
|
|
||||||
info: Option<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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, PartialEq, Eq)]
|
|
||||||
pub enum InsertResult {
|
pub enum InsertResult {
|
||||||
/// Combine to an existing stack. Contains (`index`, `new_count`)
|
/// Combine to an existing stack. Contains (`index`, `new_count`)
|
||||||
Combine { index: usize, new_count: u32 },
|
Combine(usize, u32),
|
||||||
/// No existing stackable stacks, push a new one.
|
/// No existing stackable stacks, push a new one.
|
||||||
Push { stack: ItemStack },
|
Push(ItemStack),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub enum RemoveResult {
|
pub enum RemoveResult {
|
||||||
/// The stack will get depleted, so it can be removed. Contains the `index` of empty stack.
|
/// The stack will get depleted, so it can be removed. Contains the `index` of empty stack.
|
||||||
Empty { index: usize },
|
Empty(usize),
|
||||||
/// The stack will get only partially depleted. Contains (`index`, `new_count`)
|
/// The stack will get only partially depleted. Contains (`index`, `new_count`)
|
||||||
Partial { index: usize, new_count: u32 },
|
Partial(usize, u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inventory {
|
impl Inventory {
|
||||||
|
|
@ -85,30 +55,26 @@ impl Inventory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weight(&self) -> Kilograms {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.items
|
self.items.iter().all(|stack| stack.count == 0)
|
||||||
.iter()
|
|
||||||
.map(|stack| stack.item.weight() * stack.count as f32)
|
|
||||||
.sum()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_insert(&self, item_stack: ItemStack) -> Option<InsertResult> {
|
pub fn try_insert(&self, item_stack: &ItemStack) -> Option<InsertResult> {
|
||||||
match self
|
match self
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_, stack)| stack.item == item_stack.item)
|
.find(|(_, stack)| stack.item == item_stack.item)
|
||||||
{
|
{
|
||||||
Some((index, stack)) => Some(InsertResult::Combine {
|
Some((index, stack)) => {
|
||||||
index,
|
Some(InsertResult::Combine(index, stack.count + item_stack.count))
|
||||||
new_count: stack.count + item_stack.count,
|
}
|
||||||
}),
|
|
||||||
None => {
|
None => {
|
||||||
if self
|
if self
|
||||||
.capacity
|
.capacity
|
||||||
.map_or(true, |capacity| self.items.len() < capacity)
|
.map_or(true, |capacity| self.items.len() < capacity)
|
||||||
{
|
{
|
||||||
Some(InsertResult::Push { stack: item_stack })
|
Some(InsertResult::Push(*item_stack))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +82,7 @@ impl Inventory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_remove(&self, item_stack: ItemStack) -> Option<RemoveResult> {
|
pub fn try_remove(&self, item_stack: &ItemStack) -> Option<RemoveResult> {
|
||||||
match self
|
match self
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -126,9 +92,9 @@ impl Inventory {
|
||||||
Some((index, stack)) => match stack.count.checked_sub(item_stack.count) {
|
Some((index, stack)) => match stack.count.checked_sub(item_stack.count) {
|
||||||
Some(new_count) => {
|
Some(new_count) => {
|
||||||
if new_count == 0 {
|
if new_count == 0 {
|
||||||
Some(RemoveResult::Empty { index })
|
Some(RemoveResult::Empty(index))
|
||||||
} else {
|
} else {
|
||||||
Some(RemoveResult::Partial { index, new_count })
|
Some(RemoveResult::Partial(index, new_count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not enough items
|
// Not enough items
|
||||||
|
|
@ -142,7 +108,7 @@ impl Inventory {
|
||||||
pub fn try_transfer(
|
pub fn try_transfer(
|
||||||
&self,
|
&self,
|
||||||
to: &Self,
|
to: &Self,
|
||||||
item_stack: ItemStack,
|
item_stack: &ItemStack,
|
||||||
) -> Option<(RemoveResult, InsertResult)> {
|
) -> Option<(RemoveResult, InsertResult)> {
|
||||||
match (self.try_remove(item_stack), to.try_insert(item_stack)) {
|
match (self.try_remove(item_stack), to.try_insert(item_stack)) {
|
||||||
(Some(remove_result), Some(insert_result)) => Some((remove_result, insert_result)),
|
(Some(remove_result), Some(insert_result)) => Some((remove_result, insert_result)),
|
||||||
|
|
@ -150,58 +116,6 @@ impl Inventory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_remove(&mut self, remove_result: RemoveResult) -> Result<(), InventoryError> {
|
|
||||||
match remove_result {
|
|
||||||
RemoveResult::Empty { index } => {
|
|
||||||
if index >= self.items.len() {
|
|
||||||
Err(InventoryError::IndexNotFound {
|
|
||||||
index,
|
|
||||||
inventory: self.clone(),
|
|
||||||
info: Some("RemoveResult::Empty".to_string()),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.items.remove(index);
|
|
||||||
self.sanitize();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RemoveResult::Partial { index, new_count } => match self.items.get_mut(index) {
|
|
||||||
Some(stack) => {
|
|
||||||
stack.count = new_count;
|
|
||||||
self.sanitize();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
None => Err(InventoryError::IndexNotFound {
|
|
||||||
index,
|
|
||||||
inventory: self.clone(),
|
|
||||||
info: Some("RemoveResult::Partial".to_string()),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_insert(&mut self, insert_result: InsertResult) -> Result<(), InventoryError> {
|
|
||||||
match insert_result {
|
|
||||||
InsertResult::Push { stack } => {
|
|
||||||
self.items.push(stack);
|
|
||||||
self.sanitize();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
InsertResult::Combine { index, new_count } => match self.items.get_mut(index) {
|
|
||||||
Some(stack) => {
|
|
||||||
stack.count = new_count;
|
|
||||||
self.sanitize();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
None => Err(InventoryError::IndexNotFound {
|
|
||||||
index,
|
|
||||||
inventory: self.clone(),
|
|
||||||
info: Some("InsertResult::Combine".to_string()),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sanitize(&mut self) {
|
pub fn sanitize(&mut self) {
|
||||||
if let Some(capacity) = self.capacity {
|
if let Some(capacity) = self.capacity {
|
||||||
self.items.truncate(capacity);
|
self.items.truncate(capacity);
|
||||||
|
|
@ -222,265 +136,6 @@ pub fn update_item_sprite(
|
||||||
for (mut sprite, item) in query.iter_mut() {
|
for (mut sprite, item) in query.iter_mut() {
|
||||||
sprite.image = assets.load(match item {
|
sprite.image = assets.load(match item {
|
||||||
Item::Wood => "sprites/wood.png",
|
Item::Wood => "sprites/wood.png",
|
||||||
Item::Stone => "sprites/stone.png",
|
|
||||||
// _ => "sprites/missing.png",
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Event, Clone, Debug)]
|
|
||||||
pub struct SpawnItem {
|
|
||||||
pub item: Item,
|
|
||||||
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,5 @@
|
||||||
use bevy::{prelude::*, sprite::Anchor};
|
use bevy::{prelude::*, sprite::Anchor};
|
||||||
use bevy_rapier2d::prelude::*;
|
use bevy_rapier2d::prelude::*;
|
||||||
use creature::{Mover, WeightSlowdown};
|
|
||||||
use item::{Inventory, Item, ItemSource, ItemStack, Stockpile};
|
use item::{Inventory, Item, ItemSource, ItemStack, Stockpile};
|
||||||
use work::{WorkDuration, Worker};
|
use work::{WorkDuration, Worker};
|
||||||
|
|
||||||
|
|
@ -18,9 +17,8 @@ use crate::util::SpriteLoader;
|
||||||
SpriteLoader(|| SpriteLoader::from("sprites/glorb.png")),
|
SpriteLoader(|| SpriteLoader::from("sprites/glorb.png")),
|
||||||
Worker,
|
Worker,
|
||||||
Inventory(|| Inventory::with_capacity(1)),
|
Inventory(|| Inventory::with_capacity(1)),
|
||||||
WeightSlowdown(|| WeightSlowdown { halfpoint: 50.0 }),
|
Velocity,
|
||||||
RigidBody(|| RigidBody::KinematicPositionBased),
|
RigidBody(|| RigidBody::KinematicVelocityBased),
|
||||||
Mover,
|
|
||||||
)]
|
)]
|
||||||
pub struct Glorb;
|
pub struct Glorb;
|
||||||
|
|
||||||
|
|
@ -33,7 +31,7 @@ pub struct Glorb;
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
SpriteLoader(|| SpriteLoader::from("sprites/tree.png")),
|
SpriteLoader(|| SpriteLoader::from("sprites/tree.png")),
|
||||||
ItemSource(|| ItemSource { drops: vec![ItemStack { item: Item::Wood, count: 50 }], gather_limit: Some(1) }),
|
ItemSource(|| ItemSource(ItemStack { item: Item::Wood, count: 1 })),
|
||||||
WorkDuration(|| WorkDuration(5.0))
|
WorkDuration(|| WorkDuration(5.0))
|
||||||
)]
|
)]
|
||||||
pub struct Tree;
|
pub struct Tree;
|
||||||
|
|
@ -47,7 +45,7 @@ pub struct Tree;
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
SpriteLoader(|| SpriteLoader::from("sprites/box.png")),
|
SpriteLoader(|| SpriteLoader::from("sprites/box.png")),
|
||||||
Inventory,
|
Inventory(|| Inventory::with_capacity(25)),
|
||||||
Stockpile,
|
Stockpile,
|
||||||
)]
|
)]
|
||||||
pub struct Chest;
|
pub struct Chest;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
mod creature;
|
|
||||||
mod demo;
|
mod demo;
|
||||||
mod item;
|
|
||||||
mod worker;
|
mod worker;
|
||||||
|
|
||||||
pub use creature::*;
|
|
||||||
pub use demo::*;
|
pub use demo::*;
|
||||||
pub use item::*;
|
|
||||||
pub use worker::*;
|
pub use worker::*;
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
use crate::game::{
|
|
||||||
creature::{Mover, WeightSlowdown},
|
|
||||||
item::Inventory,
|
|
||||||
work::Worker,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn worker_movement(
|
|
||||||
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) in worker_query.iter_mut() {
|
|
||||||
let Some(task) = worker.task.as_ref() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let (Ok(from), Ok(to)) = (global_query.get(entity), global_query.get(task.target)) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let diff = (to.translation() - from.translation()).xy();
|
|
||||||
let dist = diff.length().max(0.);
|
|
||||||
let dir = diff.normalize_or_zero();
|
|
||||||
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,7 +29,6 @@ pub fn setup_2d(mut commands: Commands) {
|
||||||
commands.spawn((Transform::from_xyz(200.0, 0.0, 0.0), prefab::Tree));
|
commands.spawn((Transform::from_xyz(200.0, 0.0, 0.0), prefab::Tree));
|
||||||
|
|
||||||
// commands.spawn((Name::from("Wood"), Item::Wood));
|
// 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));
|
commands.spawn((Transform::from_xyz(-200.0, -150.0, 0.0), prefab::Chest));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_rapier2d::prelude::{Damping, RigidBody, Velocity};
|
|
||||||
|
|
||||||
use crate::game::{item::SpawnItem, work::WorkDuration};
|
|
||||||
|
|
||||||
pub fn spawn_items(mut spawn_item_events: EventReader<SpawnItem>, mut commands: Commands) {
|
|
||||||
for event in spawn_item_events.read() {
|
|
||||||
commands.spawn((
|
|
||||||
Name::new(format!("{:?}", event.item)),
|
|
||||||
event.item,
|
|
||||||
WorkDuration(0.1),
|
|
||||||
Transform {
|
|
||||||
translation: event.to.extend(1.0),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
RigidBody::Dynamic,
|
|
||||||
Damping {
|
|
||||||
linear_damping: 4.0,
|
|
||||||
angular_damping: 1.0,
|
|
||||||
},
|
|
||||||
Velocity {
|
|
||||||
linvel: event.velocity,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,104 +1,44 @@
|
||||||
use bevy::{color::palettes::css, prelude::*, utils::HashMap};
|
use bevy::{color::palettes::css, prelude::*};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
debug::{ColoredShape, DebugDraw, Shape},
|
debug::{ColoredShape, DebugDraw, Shape},
|
||||||
game::{
|
game::{
|
||||||
item::{Inventory, Item, ItemSource, ItemStack, SpawnItem, Stockpile},
|
item::{Inventory, ItemSource, Stockpile},
|
||||||
work::{OnTaskFinish, Task, WorkDuration, WorkType, Worker},
|
work::{Task, WorkType, Worker},
|
||||||
},
|
},
|
||||||
util::random_vec2,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn work_select(
|
pub fn work_select(
|
||||||
workers: Query<(Entity, &Inventory, &GlobalTransform), With<Worker>>,
|
mut worker_query: Query<(&mut Worker, &Inventory, &GlobalTransform)>,
|
||||||
mut worker_query: Query<&mut Worker>,
|
|
||||||
pickup_query: Query<(Entity, &Item, &GlobalTransform)>,
|
|
||||||
store_query: Query<(Entity, &Inventory, &GlobalTransform), With<Stockpile>>,
|
store_query: Query<(Entity, &Inventory, &GlobalTransform), With<Stockpile>>,
|
||||||
gather_query: Query<(Entity, &GlobalTransform), With<ItemSource>>,
|
gather_query: Query<(Entity, &ItemSource, &GlobalTransform)>,
|
||||||
) {
|
) {
|
||||||
// What tasks are already targeting given entity
|
for (mut worker, worker_inventory, worker_transform) in worker_query.iter_mut() {
|
||||||
let mut target_tasks_map: HashMap<Entity, Vec<Task>> = HashMap::new();
|
|
||||||
|
|
||||||
fn add_to_task_map(map: &mut HashMap<Entity, Vec<Task>>, task: Task) {
|
|
||||||
match map.get_mut(&task.target) {
|
|
||||||
Some(tasks) => {
|
|
||||||
tasks.push(task);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
map.insert(task.target, vec![task]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for task in worker_query.iter().filter_map(|worker| worker.task.clone()) {
|
|
||||||
add_to_task_map(&mut target_tasks_map, task);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (worker_entity, worker_inventory, worker_transform) in workers.iter() {
|
|
||||||
let mut worker = worker_query.get_mut(worker_entity).unwrap();
|
|
||||||
|
|
||||||
// Skip if worker already has a job
|
// Skip if worker already has a job
|
||||||
if worker.task.is_some() {
|
if worker.0.is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut task_by_distance: Option<(Task, f32)> = None;
|
let mut task_by_distance: Option<(Task, f32)> = None;
|
||||||
|
|
||||||
for (task_entity, item, item_transform) in pickup_query.iter() {
|
|
||||||
// Skip items that are already targeted by any task
|
|
||||||
if target_tasks_map.contains_key(&task_entity) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Check if the item fits into workers inventory
|
|
||||||
if worker_inventory
|
|
||||||
.try_insert(ItemStack::from(*item))
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
let dist_squared = worker_transform
|
|
||||||
.translation()
|
|
||||||
.distance_squared(item_transform.translation());
|
|
||||||
if task_by_distance
|
|
||||||
.as_ref()
|
|
||||||
.is_none_or(|(_, closest_task_dist)| dist_squared < *closest_task_dist)
|
|
||||||
{
|
|
||||||
task_by_distance = Some((
|
|
||||||
Task {
|
|
||||||
target: task_entity,
|
|
||||||
work_type: WorkType::Pickup,
|
|
||||||
progress: 0.0,
|
|
||||||
},
|
|
||||||
dist_squared,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((task, _)) = task_by_distance {
|
|
||||||
worker.task = Some(task.clone());
|
|
||||||
add_to_task_map(&mut target_tasks_map, task);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (stockpile_entity, stockpile_inventory, stockpile_transform) in store_query.iter() {
|
for (stockpile_entity, stockpile_inventory, stockpile_transform) in store_query.iter() {
|
||||||
for worker_stack in worker_inventory.items.iter() {
|
for worker_stack in worker_inventory.items.iter() {
|
||||||
if worker_inventory
|
if let Some((_, _)) =
|
||||||
.try_transfer(stockpile_inventory, *worker_stack)
|
worker_inventory.try_transfer(stockpile_inventory, worker_stack)
|
||||||
.is_some()
|
|
||||||
{
|
{
|
||||||
let dist_squared = worker_transform
|
let stockpile_dist_squared = worker_transform
|
||||||
.translation()
|
.translation()
|
||||||
.distance_squared(stockpile_transform.translation());
|
.distance_squared(stockpile_transform.translation());
|
||||||
if task_by_distance
|
if task_by_distance.is_none_or(|(_, closest_task_dist)| {
|
||||||
.as_ref()
|
stockpile_dist_squared < closest_task_dist
|
||||||
.is_none_or(|(_, closest_task_dist)| dist_squared < *closest_task_dist)
|
}) {
|
||||||
{
|
|
||||||
task_by_distance = Some((
|
task_by_distance = Some((
|
||||||
Task {
|
Task {
|
||||||
target: stockpile_entity,
|
target: stockpile_entity,
|
||||||
work_type: WorkType::Store(*worker_stack),
|
work_type: WorkType::Store(*worker_stack),
|
||||||
progress: 0.0,
|
progress: 0.0,
|
||||||
},
|
},
|
||||||
dist_squared,
|
stockpile_dist_squared,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,174 +46,31 @@ pub fn work_select(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((task, _)) = task_by_distance {
|
if let Some((task, _)) = task_by_distance {
|
||||||
worker.task = Some(task.clone());
|
worker.0 = Some(task);
|
||||||
add_to_task_map(&mut target_tasks_map, task);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (item_source_entity, item_source_transform) in gather_query.iter() {
|
for (item_source_entity, item_source, item_source_transform) in gather_query.iter() {
|
||||||
let dist_squared = worker_transform
|
if worker_inventory.try_insert(&item_source.0).is_some() {
|
||||||
.translation()
|
let source_dist_squared = worker_transform
|
||||||
.distance_squared(item_source_transform.translation());
|
.translation()
|
||||||
if task_by_distance
|
.distance_squared(item_source_transform.translation());
|
||||||
.as_ref()
|
if task_by_distance
|
||||||
.is_none_or(|(_, closest_task_dist)| dist_squared < *closest_task_dist)
|
.is_none_or(|(_, closest_task_dist)| source_dist_squared < closest_task_dist)
|
||||||
{
|
|
||||||
task_by_distance = Some((
|
|
||||||
Task {
|
|
||||||
target: item_source_entity,
|
|
||||||
work_type: WorkType::Gather,
|
|
||||||
progress: 0.0,
|
|
||||||
},
|
|
||||||
dist_squared,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// worker.task = task_by_distance.map(|(task, _)| task.clone());
|
|
||||||
if let Some((task, _)) = task_by_distance {
|
|
||||||
worker.task = Some(task.clone());
|
|
||||||
add_to_task_map(&mut target_tasks_map, task);
|
|
||||||
} else {
|
|
||||||
worker.task = None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn do_work(
|
|
||||||
mut worker_query: Query<(Entity, &mut Worker)>,
|
|
||||||
mut on_task_finish: EventWriter<OnTaskFinish>,
|
|
||||||
global_query: Query<&GlobalTransform>,
|
|
||||||
duration_query: Query<&WorkDuration>,
|
|
||||||
time: Res<Time>,
|
|
||||||
) {
|
|
||||||
for (entity, mut worker) in worker_query.iter_mut() {
|
|
||||||
// Skip if worker already has a job
|
|
||||||
let task = match worker.task.as_mut() {
|
|
||||||
Some(task) => task,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
const DISTANCE_THRESHOLD_SQUARED: f32 = 32.0 * 32.0;
|
|
||||||
match (global_query.get(entity), global_query.get(task.target)) {
|
|
||||||
(Ok(a), Ok(b)) => {
|
|
||||||
if a.translation().xy().distance_squared(b.translation().xy())
|
|
||||||
> DISTANCE_THRESHOLD_SQUARED
|
|
||||||
{
|
{
|
||||||
continue;
|
task_by_distance = Some((
|
||||||
}
|
Task {
|
||||||
}
|
target: item_source_entity,
|
||||||
(worker_global, target_global) => {
|
work_type: WorkType::Gather,
|
||||||
warn!("Either worker or target doesn't have a GlobalTransform component! Stopping the task. worker: {worker_global:?} target: {target_global:?}");
|
progress: 0.0,
|
||||||
worker.task = None;
|
},
|
||||||
continue;
|
source_dist_squared,
|
||||||
}
|
))
|
||||||
}
|
|
||||||
|
|
||||||
let finished_tasks = match duration_query.get(task.target) {
|
|
||||||
Ok(duration) => task.add_progress(duration, time.delta_secs()),
|
|
||||||
Err(_) => 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
if finished_tasks == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let task = worker.task.take().unwrap();
|
|
||||||
for _ in 0..finished_tasks {
|
|
||||||
debug!("{entity} task done: {task:?}");
|
|
||||||
on_task_finish.send(OnTaskFinish {
|
|
||||||
task: task.clone(),
|
|
||||||
worker: entity,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_task_result(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut spawn_item: EventWriter<SpawnItem>,
|
|
||||||
mut on_task_finish: EventReader<OnTaskFinish>,
|
|
||||||
mut inventory_query: Query<&mut Inventory>,
|
|
||||||
item_query: Query<(Entity, &Item)>,
|
|
||||||
gather_query: Query<(&ItemSource, &GlobalTransform)>,
|
|
||||||
) {
|
|
||||||
for OnTaskFinish { worker, task } in on_task_finish.read() {
|
|
||||||
let target = task.target;
|
|
||||||
match task.work_type {
|
|
||||||
WorkType::Pickup => {
|
|
||||||
let mut inventory = match inventory_query.get_mut(*worker) {
|
|
||||||
Ok(inventory) => inventory,
|
|
||||||
Err(err) => {
|
|
||||||
error!("Pickup: can't get Inventory component from {worker}: {err:?}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let (item_entity, item) = match item_query.get(target) {
|
|
||||||
Ok(item) => item,
|
|
||||||
Err(err) => {
|
|
||||||
error!("Pickup: can't get Item component from {target}: {err:?}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match inventory.try_insert(ItemStack::from(*item)) {
|
|
||||||
Some(insert) => {
|
|
||||||
inventory
|
|
||||||
.apply_insert(insert)
|
|
||||||
.expect("Worker picks up an item");
|
|
||||||
commands
|
|
||||||
.get_entity(item_entity)
|
|
||||||
.expect("Getting item entity to despawn it")
|
|
||||||
.despawn_recursive();
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Not fatal, something could have filled the inventory on the way.
|
|
||||||
warn!("Worker {worker} could not pickup {item:?} possibly due to full inventory");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WorkType::Gather => {
|
|
||||||
let (item_source, source_transform) = match gather_query.get(target) {
|
|
||||||
Ok(item_source) => item_source,
|
|
||||||
Err(err) => {
|
|
||||||
error!("Gather: can't get ItemSource component from {target}: {err:?}",);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for stack in item_source.drops.iter() {
|
|
||||||
for _ in 0..stack.count {
|
|
||||||
spawn_item.send(SpawnItem {
|
|
||||||
item: stack.item,
|
|
||||||
to: source_transform.translation().xy(),
|
|
||||||
velocity: random_vec2(250.0, 250.0),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WorkType::Store(stack) => {
|
|
||||||
let [mut worker_inventory, mut stockpile_inventory] =
|
|
||||||
match inventory_query.get_many_mut([*worker, target]) {
|
|
||||||
Ok(inventory) => inventory,
|
|
||||||
Err(err) => {
|
|
||||||
error!("Store: can't get Inventory components: {err:?}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match worker_inventory.try_transfer(&stockpile_inventory, stack) {
|
|
||||||
Some((remove, insert)) => {
|
|
||||||
worker_inventory
|
|
||||||
.apply_remove(remove)
|
|
||||||
.expect("Worker stores an item");
|
|
||||||
stockpile_inventory
|
|
||||||
.apply_insert(insert)
|
|
||||||
.expect("Stockpile takes an item");
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
warn!("Worker {worker} could not store {stack:?} into {target}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
worker.0 = task_by_distance.map(|(task, _)| task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,39 +81,44 @@ pub fn draw_job_targets(
|
||||||
global_query: Query<&GlobalTransform>,
|
global_query: Query<&GlobalTransform>,
|
||||||
) {
|
) {
|
||||||
for (worker_entity, worker) in worker_query.iter() {
|
for (worker_entity, worker) in worker_query.iter() {
|
||||||
let worker_global = match global_query.get(worker_entity) {
|
let worker_global = global_query.get(worker_entity).unwrap();
|
||||||
Ok(global) => global,
|
let colored_shapes = match worker.0 {
|
||||||
Err(_) => continue,
|
Some(task) => match task.work_type {
|
||||||
};
|
WorkType::Gather => vec![
|
||||||
let colored_shapes = match worker.task.as_ref() {
|
|
||||||
Some(task) => {
|
|
||||||
let target_global = match global_query.get(task.target) {
|
|
||||||
Ok(global) => global,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
let color = match task.work_type {
|
|
||||||
WorkType::Pickup => css::AQUA,
|
|
||||||
WorkType::Gather => css::GREEN,
|
|
||||||
WorkType::Store(_) => css::YELLOW,
|
|
||||||
};
|
|
||||||
vec![
|
|
||||||
ColoredShape {
|
ColoredShape {
|
||||||
shape: Shape::Polygon {
|
shape: Shape::Polygon {
|
||||||
center: worker_global.translation().xy(),
|
center: worker_global.translation().xy(),
|
||||||
sides: 3,
|
sides: 3,
|
||||||
radius: 16.,
|
radius: 16.,
|
||||||
},
|
},
|
||||||
color,
|
color: css::GREEN,
|
||||||
},
|
},
|
||||||
ColoredShape {
|
ColoredShape {
|
||||||
shape: Shape::Line {
|
shape: Shape::Line {
|
||||||
from: worker_global.translation().xy(),
|
from: worker_global.translation().xy(),
|
||||||
to: target_global.translation().xy(),
|
to: global_query.get(task.target).unwrap().translation().xy(),
|
||||||
},
|
},
|
||||||
color,
|
color: css::GREEN,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
}
|
WorkType::Store(_) => vec![
|
||||||
|
ColoredShape {
|
||||||
|
shape: Shape::Polygon {
|
||||||
|
center: worker_global.translation().xy(),
|
||||||
|
sides: 3,
|
||||||
|
radius: 16.,
|
||||||
|
},
|
||||||
|
color: css::YELLOW,
|
||||||
|
},
|
||||||
|
ColoredShape {
|
||||||
|
shape: Shape::Line {
|
||||||
|
from: worker_global.translation().xy(),
|
||||||
|
to: global_query.get(task.target).unwrap().translation().xy(),
|
||||||
|
},
|
||||||
|
color: css::YELLOW,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
None => vec![ColoredShape {
|
None => vec![ColoredShape {
|
||||||
shape: Shape::Polygon {
|
shape: Shape::Polygon {
|
||||||
center: worker_global.translation().xy(),
|
center: worker_global.translation().xy(),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::util::Seconds;
|
||||||
use super::item::ItemStack;
|
use super::item::ItemStack;
|
||||||
|
|
||||||
/// Total time it takes to finish a task
|
/// Total time it takes to finish a task
|
||||||
#[derive(Clone, Debug, Default, Component, Reflect)]
|
#[derive(Copy, Clone, Debug, Default, Component, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct WorkDuration(pub Seconds);
|
pub struct WorkDuration(pub Seconds);
|
||||||
|
|
||||||
|
|
@ -20,14 +20,13 @@ impl WorkDuration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Reflect)]
|
#[derive(Copy, Clone, Debug, Reflect)]
|
||||||
pub enum WorkType {
|
pub enum WorkType {
|
||||||
Pickup,
|
|
||||||
Gather,
|
Gather,
|
||||||
Store(ItemStack),
|
Store(ItemStack),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Reflect)]
|
#[derive(Copy, Clone, Debug, Reflect)]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
pub progress: Seconds,
|
pub progress: Seconds,
|
||||||
pub work_type: WorkType,
|
pub work_type: WorkType,
|
||||||
|
|
@ -37,25 +36,12 @@ pub struct Task {
|
||||||
impl Task {
|
impl Task {
|
||||||
pub fn add_progress(&mut self, work_duration: &WorkDuration, progress: Seconds) -> u32 {
|
pub fn add_progress(&mut self, work_duration: &WorkDuration, progress: Seconds) -> u32 {
|
||||||
match work_duration.safe_duration() {
|
match work_duration.safe_duration() {
|
||||||
Some(duration) => {
|
Some(duration) => (self.progress + progress).div_euclid(duration).max(0.0) as u32,
|
||||||
self.progress += progress;
|
|
||||||
let times_completed = self.progress.div_euclid(duration).max(0.0) as u32;
|
|
||||||
self.progress = self.progress.rem_euclid(duration);
|
|
||||||
times_completed
|
|
||||||
}
|
|
||||||
None => 1,
|
None => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Event, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, Default, Component, Reflect)]
|
||||||
pub struct OnTaskFinish {
|
|
||||||
pub worker: Entity,
|
|
||||||
pub task: Task,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Component, Reflect)]
|
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct Worker {
|
pub struct Worker(pub Option<Task>);
|
||||||
pub task: Option<Task>,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_mod_debugdump::{render_graph, render_graph_dot};
|
use bevy_mod_debugdump::{render_graph, render_graph_dot};
|
||||||
use core::{fmt, ops};
|
use core::{fmt, ops};
|
||||||
use std::{f32::consts::PI, fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
mod basis;
|
mod basis;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
|
|
@ -153,10 +153,6 @@ pub fn loop_value(from: f32, to: f32, value: f32) -> f32 {
|
||||||
value - inverse_lerp(from, to, value).floor() * range
|
value - inverse_lerp(from, to, value).floor() * range
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_vec2(min_length: f32, max_length: f32) -> Vec2 {
|
|
||||||
Vec2::from_angle(fastrand::f32() * PI * 2.0) * lerp(min_length, max_length, fastrand::f32())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_app_graphs(app: &mut App) {
|
pub fn create_app_graphs(app: &mut App) {
|
||||||
// TODO: Figure out how to list schedules under the new interned ScheduleLabel system
|
// TODO: Figure out how to list schedules under the new interned ScheduleLabel system
|
||||||
println!("Writing render graph");
|
println!("Writing render graph");
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
pub type Seconds = f32;
|
pub type Seconds = f32;
|
||||||
pub type Kilograms = f32;
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue