1use std::collections::HashMap;
2
3use common::{
4 CachedSpatialGrid, comp,
5 event::{DeleteEvent, EventBus},
6 resources::ProgramTime,
7};
8use common_ecs::{Origin, Phase, System};
9use specs::{Entities, Entity, Join, LendJoin, Read, ReadStorage, WriteStorage};
10
11const MAX_ITEM_MERGE_DIST: f32 = 2.0;
12const CHECKS_PER_SECOND: f64 = 10.0; #[derive(Default)]
15pub struct Sys;
16
17impl<'a> System<'a> for Sys {
18 type SystemData = (
19 Entities<'a>,
20 WriteStorage<'a, comp::PickupItem>,
21 ReadStorage<'a, comp::Pos>,
22 ReadStorage<'a, comp::LootOwner>,
23 Read<'a, CachedSpatialGrid>,
24 Read<'a, ProgramTime>,
25 Read<'a, EventBus<DeleteEvent>>,
26 );
27
28 const NAME: &'static str = "item";
29 const ORIGIN: Origin = Origin::Server;
30 const PHASE: Phase = Phase::Create;
31
32 fn run(
33 _job: &mut common_ecs::Job<Self>,
34 (
35 entities,
36 mut items,
37 positions,
38 loot_owners,
39 spatial_grid,
40 program_time,
41 delete_bus,
42 ): Self::SystemData,
43 ) {
44 let mut merged = HashMap::new();
47 let mut merges = Vec::new();
49 let mut delete_emitter = delete_bus.emitter();
51
52 for (entity, item, pos, loot_owner) in
53 (&entities, &items, &positions, loot_owners.maybe()).join()
54 {
55 if merged.contains_key(&entity) {
57 continue;
58 }
59
60 if program_time.0 < item.next_merge_check().0 {
62 continue;
63 }
64
65 merged.insert(entity, true);
68
69 for (source_entity, _) in get_nearby_mergeable_items(
70 item,
71 pos,
72 loot_owner,
73 (&entities, &items, &positions, &loot_owners, &spatial_grid),
74 ) {
75 if merged.contains_key(&source_entity) {
78 continue;
79 }
80
81 merged.insert(source_entity, false);
83 merges.push((source_entity, entity));
85 }
86 }
87
88 for (source, target) in merges {
89 let source_item = items
90 .remove(source)
91 .expect("We know this entity must have an item.");
92 let mut target_item = items
93 .get_mut(target)
94 .expect("We know this entity must have an item.");
95
96 if let Err(item) = target_item.try_merge(source_item) {
97 items
100 .insert(source, item)
101 .expect("PickupItem was removed from this entity earlier");
102 } else {
103 delete_emitter.emit(DeleteEvent(source));
105 }
106 }
107
108 for updated in merged
109 .into_iter()
110 .filter_map(|(entity, is_merge_parent)| is_merge_parent.then_some(entity))
111 {
112 if let Some(mut item) = items.get_mut(updated) {
113 item.next_merge_check_mut().0 +=
114 (program_time.0 - item.created().0).max(1.0 / CHECKS_PER_SECOND);
115 }
116 }
117 }
118}
119
120pub fn get_nearby_mergeable_items<'a>(
121 item: &'a comp::PickupItem,
122 pos: &'a comp::Pos,
123 loot_owner: Option<&'a comp::LootOwner>,
124 (entities, items, positions, loot_owners, spatial_grid): (
125 &'a Entities<'a>,
126 &'a WriteStorage<'a, comp::PickupItem>,
129 &'a ReadStorage<'a, comp::Pos>,
130 &'a ReadStorage<'a, comp::LootOwner>,
131 &'a CachedSpatialGrid,
132 ),
133) -> impl Iterator<Item = (Entity, f32)> + 'a {
134 spatial_grid
136 .0
137 .in_circle_aabr(pos.0.xy(), MAX_ITEM_MERGE_DIST)
138 .flat_map(move |entity| {
140 (entities, items, positions, loot_owners.maybe())
141 .lend_join()
142 .get(entity, entities)
143 .and_then(|(entity, item, other_position, loot_owner)| {
144 let distance_sqrd = other_position.0.distance_squared(pos.0);
145 if distance_sqrd < MAX_ITEM_MERGE_DIST.powi(2) {
146 Some((entity, item, distance_sqrd, loot_owner))
147 } else {
148 None
149 }
150 })
151 })
152 .filter_map(move |(entity, other_item, distance, other_loot_owner)| {
154 (other_loot_owner.map(|owner| owner.owner()) == loot_owner.map(|owner| owner.owner())
155 && item.can_merge(other_item)).then_some((entity, distance))
156 })
157}