Skip to content

Commit 614c0f6

Browse files
authored
feat: porters second iteration (#94)
1 parent 1909727 commit 614c0f6

File tree

5 files changed

+169
-157
lines changed

5 files changed

+169
-157
lines changed

src/gameplay/people/pathfinding.rs

Lines changed: 91 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,128 @@
1-
use std::collections::VecDeque;
2-
3-
use bevy::{
4-
platform::collections::{HashMap, HashSet},
5-
prelude::*,
6-
};
1+
use bevy::prelude::*;
2+
use rand::seq::IndexedRandom;
73

84
use crate::gameplay::{
95
FactorySystems,
10-
people::porting::{PorterArrival, PorterLost, Porting},
11-
recipe::{
12-
assets::Recipe,
13-
select::{RecipeChanged, SelectedRecipe},
14-
},
6+
people::porting::{PorterArrival, Porting},
7+
random::Seed,
8+
recipe::{assets::Recipe, select::SelectedRecipe},
159
world::{
16-
construction::{Constructions, StructureConstructed},
17-
demolition::Demolished,
18-
tilemap::coord::Coord,
10+
construction::Constructions,
11+
tilemap::{CARDINALS, coord::Coord},
1912
},
2013
};
2114

15+
pub const ARRIVAL_THRESHOLD: f32 = 8.0;
16+
2217
pub(super) fn plugin(app: &mut App) {
23-
app.add_systems(
24-
FixedUpdate,
25-
pathfind.in_set(FactorySystems::Logistics).run_if(
26-
on_message::<RecipeChanged>
27-
.or(on_message::<StructureConstructed>.or(on_message::<Demolished>)),
28-
),
29-
);
18+
app.add_message::<PathfindingTargetReached>();
3019

3120
app.add_systems(
3221
FixedUpdate,
33-
walk_along_path.in_set(FactorySystems::Logistics),
22+
(move_towards_target, calculate_next_target)
23+
.chain()
24+
.in_set(FactorySystems::Logistics),
3425
);
3526
}
3627

3728
#[derive(Component, Reflect, Default)]
3829
#[reflect(Component)]
39-
pub struct Pathable {
40-
pub walkable: bool,
41-
}
30+
pub struct Walkable;
4231

43-
impl Pathable {
44-
pub fn walkable() -> Self {
45-
Self { walkable: true }
46-
}
47-
}
32+
#[derive(Message)]
33+
struct PathfindingTargetReached(Entity);
4834

49-
fn pathfind(
50-
structures: Query<(Entity, &SelectedRecipe)>,
51-
recipes: Res<Assets<Recipe>>,
52-
pathable_query: Query<&Pathable>,
53-
coordinates: Query<&Coord>,
54-
mut commands: Commands,
55-
constructions: Res<Constructions>,
35+
fn move_towards_target(
36+
porters: Query<(Entity, &mut Transform, &mut Sprite, &Porting)>,
37+
tiles: Query<&Transform, Without<Porting>>,
38+
mut target_reached: MessageWriter<PathfindingTargetReached>,
39+
time: Res<Time>,
5640
) {
57-
for (structure, selected_recipe) in structures {
58-
let Some(recipe) = recipes.get(&selected_recipe.0) else {
41+
for (porter, mut transform, mut sprite, porting) in porters {
42+
let Ok(target_transform) = tiles.get(porting.target) else {
5943
continue;
6044
};
6145

62-
let mut queue = VecDeque::new();
63-
let mut visited = HashSet::new();
64-
let mut parent = HashMap::new();
65-
let mut solutions = VecDeque::new();
66-
67-
queue.push_back(structure);
68-
visited.insert(structure);
69-
70-
while let Some(current) = queue.pop_front() {
71-
let coord = coordinates.get(current).unwrap();
72-
73-
let neighbors: Vec<IVec2> = [IVec2::X, IVec2::NEG_X, IVec2::Y, IVec2::NEG_Y]
74-
.into_iter()
75-
.map(|c| c + coord.0)
76-
.collect();
77-
78-
for neighbor_coord in neighbors {
79-
let Some(neighbor) = constructions.get(&neighbor_coord) else {
80-
continue;
81-
};
82-
83-
if visited.contains(neighbor) {
84-
continue;
85-
}
86-
87-
visited.insert(*neighbor);
88-
parent.insert(neighbor, current);
89-
90-
if let Ok(pathable) = pathable_query.get(*neighbor)
91-
&& pathable.walkable
92-
{
93-
queue.push_back(*neighbor);
94-
}
95-
96-
let Ok((_, other_selected_recipe)) = structures.get(*neighbor) else {
97-
continue;
98-
};
99-
100-
let Some(other_recipe) = recipes.get(&other_selected_recipe.0) else {
101-
continue;
102-
};
103-
104-
let is_goal = recipe
105-
.output
106-
.iter()
107-
.any(|output| other_recipe.input.contains_key(output.0));
108-
109-
if is_goal {
110-
let mut path = Vec::new();
111-
let mut cur = *neighbor;
112-
while cur != structure {
113-
path.push(cur);
114-
cur = *parent.get(&cur).unwrap();
115-
}
116-
solutions.push_back((*neighbor, path));
117-
}
118-
}
119-
}
46+
sprite.flip_x = target_transform.translation.x < transform.translation.x;
47+
48+
transform.translation = transform.translation.move_towards(
49+
target_transform.translation,
50+
porting.speed * time.delta_secs(),
51+
);
12052

121-
commands.entity(structure).insert(PorterPaths(solutions));
53+
if transform
54+
.translation
55+
.xy()
56+
.distance(target_transform.translation.xy())
57+
<= ARRIVAL_THRESHOLD
58+
{
59+
target_reached.write(PathfindingTargetReached(porter));
60+
}
12261
}
12362
}
12463

125-
#[derive(Component, Reflect)]
126-
#[reflect(Component)]
127-
pub struct PorterPaths(pub VecDeque<(Entity, Vec<Entity>)>);
128-
129-
fn walk_along_path(
130-
porters: Query<(Entity, &mut Transform, &mut Porting, &mut Sprite)>,
131-
transforms: Query<&Transform, Without<Porting>>,
132-
time: Res<Time>,
133-
mut porter_arrivals: MessageWriter<PorterArrival>,
134-
mut porter_losses: MessageWriter<PorterLost>,
64+
fn calculate_next_target(
65+
mut targets_reached: MessageReader<PathfindingTargetReached>,
66+
mut porters: Query<&mut Porting>,
67+
coords: Query<&Coord>,
68+
walkables: Query<(), With<Walkable>>,
69+
constructions: Res<Constructions>,
70+
structures: Query<&SelectedRecipe>,
71+
recipes: Res<Assets<Recipe>>,
72+
mut porter_arrived: MessageWriter<PorterArrival>,
73+
mut seed: ResMut<Seed>,
13574
) {
136-
const SPEED: f32 = 64.0;
137-
const ARRIVAL_THRESHHOLD: f32 = 16.0;
138-
139-
for (entity, mut transform, mut porting, mut sprite) in porters {
140-
let Some(goal) = porting.path.last() else {
75+
for PathfindingTargetReached(porter) in targets_reached.read() {
76+
let Ok(mut porting) = porters.get_mut(*porter) else {
14177
continue;
14278
};
14379

144-
let Ok(goal_transform) = transforms.get(*goal) else {
145-
porter_losses.write(PorterLost(entity));
146-
continue;
147-
};
80+
let target = porting.target;
14881

149-
sprite.flip_x = goal_transform.translation.x < transform.translation.x;
82+
porting.visited.insert(target);
83+
porting.path.push(target);
15084

151-
transform.translation = transform
152-
.translation
153-
.move_towards(goal_transform.translation, SPEED * time.delta_secs());
85+
let Ok(coord) = coords.get(target) else {
86+
continue;
87+
};
15488

155-
if transform.translation.distance(goal_transform.translation) <= ARRIVAL_THRESHHOLD {
156-
porting.path.pop();
89+
let neighbors: Vec<Entity> = CARDINALS
90+
.into_iter()
91+
.map(|c| c + coord.0)
92+
.filter_map(|c| constructions.get(&c).cloned())
93+
.collect();
94+
95+
if let Some(structure) = neighbors.iter().find(|&&entity| {
96+
let Ok(selected_recipe) = structures.get(entity) else {
97+
return false;
98+
};
99+
100+
let Some(recipe) = recipes.get(&selected_recipe.0) else {
101+
return false;
102+
};
103+
104+
recipe.input.contains_key(&porting.item)
105+
}) {
106+
porter_arrived.write(PorterArrival {
107+
porter: *porter,
108+
destination: *structure,
109+
});
110+
}
157111

158-
if porting.path.is_empty() {
159-
porter_arrivals.write(PorterArrival(entity));
160-
}
112+
let paths: Vec<Entity> = neighbors
113+
.iter()
114+
.cloned()
115+
.filter(|e| {
116+
walkables.contains(*e) && (porting.backtracking || !porting.visited.contains(e))
117+
})
118+
.collect();
119+
120+
if let Some(t) = paths.choose(&mut seed) {
121+
porting.target = *t;
122+
porting.backtracking = false;
123+
} else if let Some(t) = porting.path.pop() {
124+
porting.target = t;
125+
porting.backtracking = true;
161126
}
162127
}
163128
}

0 commit comments

Comments
 (0)