generated from hheik/bevy-template
Working gather-pickup-store work system
parent
bf50c19ba0
commit
d9319bfd5c
|
|
@ -2424,6 +2424,7 @@ dependencies = [
|
|||
"bevy_mod_debugdump",
|
||||
"bevy_prototype_lyon",
|
||||
"bevy_rapier2d",
|
||||
"fastrand",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ bevy-inspector-egui = "0.28.1"
|
|||
bevy_mod_debugdump = "0.12.1"
|
||||
bevy_prototype_lyon = "0.13.0"
|
||||
bevy_rapier2d = "0.28.0"
|
||||
fastrand = "2.3.0"
|
||||
num-traits = "0.2.19"
|
||||
|
||||
# Enable a small amount of optimization in debug mode
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Plugin for DebugPlugin {
|
|||
|
||||
app.add_plugins((
|
||||
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>()
|
||||
|
|
|
|||
|
|
@ -26,14 +26,22 @@ pub fn init(app: &mut App) {
|
|||
.register_type::<creature::Mover>()
|
||||
.register_type::<prefab::Glorb>()
|
||||
.register_type::<prefab::Tree>()
|
||||
.add_event::<work::OnTaskFinish>()
|
||||
.add_event::<item::SpawnItem>()
|
||||
.add_systems(Startup, systems::setup_2d)
|
||||
.add_systems(
|
||||
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(Update, (systems::draw_job_targets).in_set(debug::DebugSet));
|
||||
|
|
|
|||
149
src/game/item.rs
149
src/game/item.rs
|
|
@ -1,5 +1,7 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::util::Kilograms;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(Sprite)]
|
||||
|
|
@ -7,44 +9,78 @@ pub enum Item {
|
|||
Wood,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Reflect)]
|
||||
pub struct ItemStack {
|
||||
pub item: Item,
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
impl Default for ItemStack {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
item: Item::Wood,
|
||||
count: 1,
|
||||
}
|
||||
impl From<Item> for ItemStack {
|
||||
fn from(item: Item) -> Self {
|
||||
Self { item, count: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct ItemSource(pub ItemStack);
|
||||
pub struct ItemSource {
|
||||
/// 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)]
|
||||
#[reflect(Component)]
|
||||
pub struct Inventory {
|
||||
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 enum InsertResult {
|
||||
/// Combine to an existing stack. Contains (`index`, `new_count`)
|
||||
Combine(usize, u32),
|
||||
/// No existing stackable stacks, push a new one.
|
||||
Push(ItemStack),
|
||||
#[derive(Clone, Debug)]
|
||||
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)]
|
||||
pub enum InsertResult {
|
||||
/// Combine to an existing stack. Contains (`index`, `new_count`)
|
||||
Combine { index: usize, new_count: u32 },
|
||||
/// No existing stackable stacks, push a new one.
|
||||
Push { stack: ItemStack },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RemoveResult {
|
||||
/// The stack will get depleted, so it can be removed. Contains the `index` of empty stack.
|
||||
Empty(usize),
|
||||
Empty { index: usize },
|
||||
/// The stack will get only partially depleted. Contains (`index`, `new_count`)
|
||||
Partial(usize, u32),
|
||||
Partial { index: usize, new_count: u32 },
|
||||
}
|
||||
|
||||
impl Inventory {
|
||||
|
|
@ -55,26 +91,30 @@ impl Inventory {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.items.iter().all(|stack| stack.count == 0)
|
||||
pub fn weight(&self) -> Kilograms {
|
||||
self.items
|
||||
.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
|
||||
.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, stack)| stack.item == item_stack.item)
|
||||
{
|
||||
Some((index, stack)) => {
|
||||
Some(InsertResult::Combine(index, stack.count + item_stack.count))
|
||||
}
|
||||
Some((index, stack)) => Some(InsertResult::Combine {
|
||||
index,
|
||||
new_count: stack.count + item_stack.count,
|
||||
}),
|
||||
None => {
|
||||
if self
|
||||
.capacity
|
||||
.map_or(true, |capacity| self.items.len() < capacity)
|
||||
{
|
||||
Some(InsertResult::Push(*item_stack))
|
||||
Some(InsertResult::Push { stack: item_stack })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -82,7 +122,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
|
||||
.items
|
||||
.iter()
|
||||
|
|
@ -92,9 +132,9 @@ impl Inventory {
|
|||
Some((index, stack)) => match stack.count.checked_sub(item_stack.count) {
|
||||
Some(new_count) => {
|
||||
if new_count == 0 {
|
||||
Some(RemoveResult::Empty(index))
|
||||
Some(RemoveResult::Empty { index })
|
||||
} else {
|
||||
Some(RemoveResult::Partial(index, new_count))
|
||||
Some(RemoveResult::Partial { index, new_count })
|
||||
}
|
||||
}
|
||||
// Not enough items
|
||||
|
|
@ -108,7 +148,7 @@ impl Inventory {
|
|||
pub fn try_transfer(
|
||||
&self,
|
||||
to: &Self,
|
||||
item_stack: &ItemStack,
|
||||
item_stack: ItemStack,
|
||||
) -> Option<(RemoveResult, InsertResult)> {
|
||||
match (self.try_remove(item_stack), to.try_insert(item_stack)) {
|
||||
(Some(remove_result), Some(insert_result)) => Some((remove_result, insert_result)),
|
||||
|
|
@ -116,6 +156,58 @@ 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) {
|
||||
if let Some(capacity) = self.capacity {
|
||||
self.items.truncate(capacity);
|
||||
|
|
@ -139,3 +231,10 @@ pub fn update_item_sprite(
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, Debug)]
|
||||
pub struct SpawnItem {
|
||||
pub item: Item,
|
||||
pub to: Vec2,
|
||||
pub velocity: Vec2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ pub struct Glorb;
|
|||
..default()
|
||||
}),
|
||||
SpriteLoader(|| SpriteLoader::from("sprites/tree.png")),
|
||||
ItemSource(|| ItemSource(ItemStack { item: Item::Wood, count: 1 })),
|
||||
ItemSource(|| ItemSource { drops: vec![ItemStack { item: Item::Wood, count: 1 }], gather_limit: None }),
|
||||
WorkDuration(|| WorkDuration(5.0))
|
||||
)]
|
||||
pub struct Tree;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
mod creature;
|
||||
mod demo;
|
||||
mod item;
|
||||
mod worker;
|
||||
|
||||
pub use creature::*;
|
||||
pub use demo::*;
|
||||
pub use item::*;
|
||||
pub use worker::*;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,35 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::game::{creature::Mover, work::Worker};
|
||||
use crate::{
|
||||
game::{creature::Mover, item::Inventory, work::Worker},
|
||||
util::{inverse_lerp, lerp},
|
||||
};
|
||||
|
||||
pub fn worker_movement(
|
||||
mut worker_query: Query<(Entity, &Mover, &Worker, &mut Transform)>,
|
||||
mut worker_query: Query<(Entity, &Mover, &Worker, &mut Transform, Option<&Inventory>)>,
|
||||
global_query: Query<&GlobalTransform>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for (entity, mover, worker, mut transform) in worker_query.iter_mut() {
|
||||
let Some(task) = worker.0 else { continue };
|
||||
for (entity, mover, worker, mut transform, inventory) 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 movement = dir.extend(0.) * mover.speed * time.delta_secs();
|
||||
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();
|
||||
transform.translation += movement.clamp_length_max(dist);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
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,44 +1,104 @@
|
|||
use bevy::{color::palettes::css, prelude::*};
|
||||
use bevy::{color::palettes::css, prelude::*, utils::HashMap};
|
||||
|
||||
use crate::{
|
||||
debug::{ColoredShape, DebugDraw, Shape},
|
||||
game::{
|
||||
item::{Inventory, ItemSource, Stockpile},
|
||||
work::{Task, WorkType, Worker},
|
||||
item::{Inventory, Item, ItemSource, ItemStack, SpawnItem, Stockpile},
|
||||
work::{OnTaskFinish, Task, WorkDuration, WorkType, Worker},
|
||||
},
|
||||
util::random_vec2,
|
||||
};
|
||||
|
||||
pub fn work_select(
|
||||
mut worker_query: Query<(&mut Worker, &Inventory, &GlobalTransform)>,
|
||||
workers: Query<(Entity, &Inventory, &GlobalTransform), With<Worker>>,
|
||||
mut worker_query: Query<&mut Worker>,
|
||||
pickup_query: Query<(Entity, &Item, &GlobalTransform)>,
|
||||
store_query: Query<(Entity, &Inventory, &GlobalTransform), With<Stockpile>>,
|
||||
gather_query: Query<(Entity, &ItemSource, &GlobalTransform)>,
|
||||
gather_query: Query<(Entity, &GlobalTransform), With<ItemSource>>,
|
||||
) {
|
||||
for (mut worker, worker_inventory, worker_transform) in worker_query.iter_mut() {
|
||||
// What tasks are already targeting given entity
|
||||
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
|
||||
if worker.0.is_some() {
|
||||
if worker.task.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 worker_stack in worker_inventory.items.iter() {
|
||||
if let Some((_, _)) =
|
||||
worker_inventory.try_transfer(stockpile_inventory, worker_stack)
|
||||
if worker_inventory
|
||||
.try_transfer(stockpile_inventory, *worker_stack)
|
||||
.is_some()
|
||||
{
|
||||
let stockpile_dist_squared = worker_transform
|
||||
let dist_squared = worker_transform
|
||||
.translation()
|
||||
.distance_squared(stockpile_transform.translation());
|
||||
if task_by_distance.is_none_or(|(_, closest_task_dist)| {
|
||||
stockpile_dist_squared < closest_task_dist
|
||||
}) {
|
||||
if task_by_distance
|
||||
.as_ref()
|
||||
.is_none_or(|(_, closest_task_dist)| dist_squared < *closest_task_dist)
|
||||
{
|
||||
task_by_distance = Some((
|
||||
Task {
|
||||
target: stockpile_entity,
|
||||
work_type: WorkType::Store(*worker_stack),
|
||||
progress: 0.0,
|
||||
},
|
||||
stockpile_dist_squared,
|
||||
dist_squared,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -46,17 +106,18 @@ pub fn work_select(
|
|||
}
|
||||
|
||||
if let Some((task, _)) = task_by_distance {
|
||||
worker.0 = Some(task);
|
||||
worker.task = Some(task.clone());
|
||||
add_to_task_map(&mut target_tasks_map, task);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (item_source_entity, item_source, item_source_transform) in gather_query.iter() {
|
||||
if worker_inventory.try_insert(&item_source.0).is_some() {
|
||||
let source_dist_squared = worker_transform
|
||||
for (item_source_entity, item_source_transform) in gather_query.iter() {
|
||||
let dist_squared = worker_transform
|
||||
.translation()
|
||||
.distance_squared(item_source_transform.translation());
|
||||
if task_by_distance
|
||||
.is_none_or(|(_, closest_task_dist)| source_dist_squared < closest_task_dist)
|
||||
.as_ref()
|
||||
.is_none_or(|(_, closest_task_dist)| dist_squared < *closest_task_dist)
|
||||
{
|
||||
task_by_distance = Some((
|
||||
Task {
|
||||
|
|
@ -64,13 +125,155 @@ pub fn work_select(
|
|||
work_type: WorkType::Gather,
|
||||
progress: 0.0,
|
||||
},
|
||||
source_dist_squared,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
worker.0 = task_by_distance.map(|(task, _)| task);
|
||||
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;
|
||||
}
|
||||
}
|
||||
(worker_global, target_global) => {
|
||||
warn!("Either worker or target doesn't have a GlobalTransform component! Stopping the task. worker: {worker_global:?} target: {target_global:?}");
|
||||
worker.task = None;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,44 +284,39 @@ pub fn draw_job_targets(
|
|||
global_query: Query<&GlobalTransform>,
|
||||
) {
|
||||
for (worker_entity, worker) in worker_query.iter() {
|
||||
let worker_global = global_query.get(worker_entity).unwrap();
|
||||
let colored_shapes = match worker.0 {
|
||||
Some(task) => match task.work_type {
|
||||
WorkType::Gather => vec![
|
||||
let worker_global = match global_query.get(worker_entity) {
|
||||
Ok(global) => global,
|
||||
Err(_) => continue,
|
||||
};
|
||||
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 {
|
||||
shape: Shape::Polygon {
|
||||
center: worker_global.translation().xy(),
|
||||
sides: 3,
|
||||
radius: 16.,
|
||||
},
|
||||
color: css::GREEN,
|
||||
color,
|
||||
},
|
||||
ColoredShape {
|
||||
shape: Shape::Line {
|
||||
from: worker_global.translation().xy(),
|
||||
to: global_query.get(task.target).unwrap().translation().xy(),
|
||||
to: target_global.translation().xy(),
|
||||
},
|
||||
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,
|
||||
},
|
||||
],
|
||||
color,
|
||||
},
|
||||
]
|
||||
}
|
||||
None => vec![ColoredShape {
|
||||
shape: Shape::Polygon {
|
||||
center: worker_global.translation().xy(),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::util::Seconds;
|
|||
use super::item::ItemStack;
|
||||
|
||||
/// Total time it takes to finish a task
|
||||
#[derive(Copy, Clone, Debug, Default, Component, Reflect)]
|
||||
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct WorkDuration(pub Seconds);
|
||||
|
||||
|
|
@ -20,13 +20,14 @@ impl WorkDuration {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Reflect)]
|
||||
#[derive(Clone, Debug, Reflect)]
|
||||
pub enum WorkType {
|
||||
Pickup,
|
||||
Gather,
|
||||
Store(ItemStack),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Reflect)]
|
||||
#[derive(Clone, Debug, Reflect)]
|
||||
pub struct Task {
|
||||
pub progress: Seconds,
|
||||
pub work_type: WorkType,
|
||||
|
|
@ -36,12 +37,25 @@ pub struct Task {
|
|||
impl Task {
|
||||
pub fn add_progress(&mut self, work_duration: &WorkDuration, progress: Seconds) -> u32 {
|
||||
match work_duration.safe_duration() {
|
||||
Some(duration) => (self.progress + progress).div_euclid(duration).max(0.0) as u32,
|
||||
Some(duration) => {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Component, Reflect)]
|
||||
#[derive(Event, Clone, Debug)]
|
||||
pub struct OnTaskFinish {
|
||||
pub worker: Entity,
|
||||
pub task: Task,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct Worker(pub Option<Task>);
|
||||
pub struct Worker {
|
||||
pub task: Option<Task>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_mod_debugdump::{render_graph, render_graph_dot};
|
||||
use core::{fmt, ops};
|
||||
use std::{fs, path::Path};
|
||||
use std::{f32::consts::PI, fs, path::Path};
|
||||
|
||||
mod basis;
|
||||
mod plugin;
|
||||
|
|
@ -153,6 +153,10 @@ pub fn loop_value(from: f32, to: f32, value: f32) -> f32 {
|
|||
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) {
|
||||
// TODO: Figure out how to list schedules under the new interned ScheduleLabel system
|
||||
println!("Writing render graph");
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
pub type Seconds = f32;
|
||||
pub type Kilograms = f32;
|
||||
|
|
|
|||
Loading…
Reference in New Issue