veloren_server/sys/
teleporter.rs1use common::{
2 CachedSpatialGrid,
3 comp::{Agent, Alignment, CharacterState, Object, Pos, Teleporting},
4 consts::TELEPORTER_RADIUS,
5 event::{EventBus, TeleportToPositionEvent},
6 outcome::Outcome,
7 resources::Time,
8 uid::Uid,
9};
10use common_ecs::{Origin, Phase, System};
11use specs::{Entities, Join, LendJoin, Read, ReadStorage, WriteStorage};
12use vek::Vec3;
13
14const MAX_AGGRO_DIST: f32 = 200.; const PET_TELEPORT_RADIUS: f32 = 20.;
16
17#[derive(Default)]
18pub struct Sys;
19
20fn in_portal_range(player_pos: Vec3<f32>, portal_pos: Vec3<f32>) -> bool {
21 player_pos.distance_squared(portal_pos) <= TELEPORTER_RADIUS.powi(2)
22}
23
24impl<'a> System<'a> for Sys {
25 type SystemData = (
26 Entities<'a>,
27 ReadStorage<'a, Pos>,
28 ReadStorage<'a, Uid>,
29 ReadStorage<'a, Alignment>,
30 ReadStorage<'a, Agent>,
31 ReadStorage<'a, Object>,
32 WriteStorage<'a, Teleporting>,
33 ReadStorage<'a, CharacterState>,
34 Read<'a, CachedSpatialGrid>,
35 Read<'a, Time>,
36 Read<'a, EventBus<TeleportToPositionEvent>>,
37 Read<'a, EventBus<Outcome>>,
38 );
39
40 const NAME: &'static str = "teleporter";
41 const ORIGIN: Origin = Origin::Server;
42 const PHASE: Phase = Phase::Create;
43
44 fn run(
45 _job: &mut common_ecs::Job<Self>,
46 (
47 entities,
48 positions,
49 uids,
50 alignments,
51 agent,
52 objects,
53 mut teleporting,
54 character_states,
55 spatial_grid,
56 time,
57 teleport_to_position_events,
58 outcome_bus,
59 ): Self::SystemData,
60 ) {
61 let mut teleport_to_position_emitter = teleport_to_position_events.emitter();
62 let mut outcome_emitter = outcome_bus.emitter();
63 let check_aggro = |entity, pos: Vec3<f32>| {
64 spatial_grid
65 .0
66 .in_circle_aabr(pos.xy(), MAX_AGGRO_DIST)
67 .any(|agent_entity| {
68 agent.get(agent_entity).is_some_and(|agent| {
69 agent.target.is_some_and(|agent_target| {
70 agent_target.target == entity && agent_target.aggro_on
71 })
72 })
73 })
74 };
75
76 let mut cancel_teleporting = Vec::new();
77
78 for (entity, uid, position, teleporting, character_state) in (
79 &entities,
80 &uids,
81 &positions,
82 &teleporting,
83 &character_states,
84 )
85 .join()
86 {
87 let portal_pos = positions.get(teleporting.portal);
88 let Some(Object::Portal {
89 target,
90 requires_no_aggro,
91 ..
92 }) = objects.get(teleporting.portal)
93 else {
94 cancel_teleporting.push(entity);
95 continue;
96 };
97
98 if portal_pos.is_none_or(|portal_pos| !in_portal_range(position.0, portal_pos.0))
99 || (*requires_no_aggro && check_aggro(entity, position.0))
100 || !matches!(
101 character_state,
102 CharacterState::Idle(_)
103 | CharacterState::Wielding(_)
104 | CharacterState::Sit
105 | CharacterState::Dance
106 )
107 {
108 cancel_teleporting.push(entity);
109 } else if teleporting.end_time.0 <= time.0 {
110 let nearby = spatial_grid
112 .0
113 .in_circle_aabr(position.0.xy(), PET_TELEPORT_RADIUS)
114 .filter_map(|entity| {
115 (&entities, &positions, &alignments)
116 .lend_join()
117 .get(entity, &entities)
118 })
119 .filter_map(|(nearby_entity, entity_position, alignment)| {
120 (matches!(alignment, Alignment::Owned(entity_uid) if entity_uid == uid)
121 && entity_position.0.distance_squared(position.0)
122 <= PET_TELEPORT_RADIUS.powi(2)
123 || entity == nearby_entity)
125 .then_some(nearby_entity)
126 });
127
128 for entity in nearby {
129 cancel_teleporting.push(entity);
130 teleport_to_position_emitter.emit(TeleportToPositionEvent {
131 entity,
132 position: *target,
133 });
134 outcome_emitter.emit(Outcome::TeleportedByPortal { pos: *target });
135 }
136 }
137 }
138
139 for entity in cancel_teleporting {
140 let _ = teleporting.remove(entity);
141 }
142 }
143}