veloren_server/sys/
item.rs

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; // Start by checking an item 10 times every second
13
14#[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        // Contains items that have been checked for merge, or that were merged into
45        // another one
46        let mut merged = HashMap::new();
47        // Contains merges that will be performed (from, into)
48        let mut merges = Vec::new();
49        // Delete events are emitted when this is dropped
50        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            // Do not process items that are already being merged
56            if merged.contains_key(&entity) {
57                continue;
58            }
59
60            // Exponentially back of the frequency at which items are checked for merge
61            if program_time.0 < item.next_merge_check().0 {
62                continue;
63            }
64
65            // We do not want to allow merging this item if it isn't already being
66            // merged into another
67            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                // Prevent merging an item multiple times, we cannot
76                // do this in the above filter since we mutate `merged` below
77                if merged.contains_key(&source_entity) {
78                    continue;
79                }
80
81                // Do not merge items multiple times
82                merged.insert(source_entity, false);
83                // Defer the merge
84                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                // We re-insert the item, should be unreachable since we already checked whether
98                // the items were mergeable in the above loop
99                items
100                    .insert(source, item)
101                    .expect("PickupItem was removed from this entity earlier");
102            } else {
103                // If the merging was successfull, we remove the old item entity from the ECS
104                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        // We do not actually need write access here, but currently all callers of this function
127        // have a WriteStorage<Item> in scope which we cannot *downcast* into a ReadStorage
128        &'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    // Get nearby items
135    spatial_grid
136        .0
137        .in_circle_aabr(pos.0.xy(), MAX_ITEM_MERGE_DIST)
138        // Filter out any unrelated entities
139        .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 by "mergeability"
153        .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}