1use crate::{
2 CharacterUpdater, Server, StateExt, client::Client, events::player::handle_exit_ingame,
3 persistence::PersistedComponents, pet::tame_pet, presence::RepositionOnChunkLoad, sys,
4};
5use common::{
6 comp::{
7 self, Alignment, BehaviorCapability, ItemDrops, LightEmitter, Ori, Pos, TradingBehavior,
8 Vel, WaypointArea,
9 aura::{Aura, AuraKind, AuraTarget},
10 buff::{BuffCategory, BuffData, BuffKind, BuffSource},
11 ship::figuredata::VOXEL_COLLIDER_MANIFEST,
12 },
13 event::{
14 CreateAuraEntityEvent, CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent,
15 CreateShipEvent, CreateSpecialEntityEvent, EventBus, InitializeCharacterEvent,
16 InitializeSpectatorEvent, NpcBuilder, ShockwaveEvent, ShootEvent, UpdateCharacterDataEvent,
17 },
18 generation::SpecialEntity,
19 mounting::{Mounting, Volume, VolumeMounting, VolumePos},
20 outcome::Outcome,
21 resources::{Secs, Time},
22 uid::{IdMaps, Uid},
23 vol::IntoFullVolIterator,
24};
25use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
26use specs::{Builder, Entity as EcsEntity, WorldExt};
27use std::time::Duration;
28use vek::{Rgb, Vec3};
29
30use super::group_manip::update_map_markers;
31
32pub fn handle_initialize_character(server: &mut Server, ev: InitializeCharacterEvent) {
33 let updater = server.state.ecs().fetch::<CharacterUpdater>();
34 let pending_database_action = updater.has_pending_database_action(ev.character_id);
35 drop(updater);
36
37 if !pending_database_action {
38 let clamped_vds = ev
39 .requested_view_distances
40 .clamp(server.settings().max_view_distance);
41 server
42 .state
43 .initialize_character_data(ev.entity, ev.character_id, clamped_vds);
44 if ev.requested_view_distances.terrain != clamped_vds.terrain {
46 server.notify_client(
47 ev.entity,
48 ServerGeneral::SetViewDistance(clamped_vds.terrain),
49 );
50 }
51 } else {
52 handle_exit_ingame(server, ev.entity, true);
56 }
57}
58
59pub fn handle_initialize_spectator(server: &mut Server, ev: InitializeSpectatorEvent) {
60 let clamped_vds = ev.1.clamp(server.settings().max_view_distance);
61 server.state.initialize_spectator_data(ev.0, clamped_vds);
62 if ev.1.terrain != clamped_vds.terrain {
64 server.notify_client(ev.0, ServerGeneral::SetViewDistance(clamped_vds.terrain));
65 }
66 sys::subscription::initialize_region_subscription(server.state.ecs(), ev.0);
67}
68
69pub fn handle_loaded_character_data(server: &mut Server, ev: UpdateCharacterDataEvent) {
70 let loaded_components = PersistedComponents {
71 body: ev.components.0,
72 hardcore: ev.components.1,
73 stats: ev.components.2,
74 skill_set: ev.components.3,
75 inventory: ev.components.4,
76 waypoint: ev.components.5,
77 pets: ev.components.6,
78 active_abilities: ev.components.7,
79 map_marker: ev.components.8,
80 };
81 if let Some(marker) = loaded_components.map_marker {
82 server.notify_client(
83 ev.entity,
84 ServerGeneral::MapMarker(comp::MapMarkerUpdate::Owned(comp::MapMarkerChange::Update(
85 marker.0,
86 ))),
87 );
88 }
89
90 let result_msg = if let Err(err) = server
91 .state
92 .update_character_data(ev.entity, loaded_components)
93 {
94 handle_exit_ingame(server, ev.entity, false); ServerGeneral::CharacterDataLoadResult(Err(err))
96 } else {
97 sys::subscription::initialize_region_subscription(server.state.ecs(), ev.entity);
98 ServerGeneral::CharacterDataLoadResult(Ok(ev.metadata))
100 };
101 server.notify_client(ev.entity, result_msg);
102}
103
104pub fn handle_create_npc(server: &mut Server, ev: CreateNpcEvent) -> EcsEntity {
105 let NpcBuilder {
107 stats,
108 skill_set,
109 health,
110 poise,
111 inventory,
112 body,
113 mut agent,
114 alignment,
115 scale,
116 anchor,
117 loot,
118 pets,
119 rtsim_entity,
120 projectile,
121 heads,
122 death_effects,
123 } = ev.npc;
124 let entity = server
125 .state
126 .create_npc(
127 ev.pos, ev.ori, stats, skill_set, health, poise, inventory, body,
128 )
129 .with(scale)
130 .maybe_with(heads)
131 .maybe_with(death_effects);
132
133 if let Some(agent) = &mut agent {
134 if let Alignment::Owned(_) = &alignment {
135 agent.behavior.allow(BehaviorCapability::TRADE);
136 agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
137 }
138 }
139
140 let entity = entity.with(alignment);
141
142 let entity = if let Some(agent) = agent {
143 entity.with(agent)
144 } else {
145 entity
146 };
147
148 let entity = if let Some(drop_items) = loot.to_items() {
149 entity.with(ItemDrops(drop_items))
150 } else {
151 entity
152 };
153
154 let entity = if let Some(home_chunk) = anchor {
155 entity.with(home_chunk)
156 } else {
157 entity
158 };
159
160 let entity = if let Some(rtsim_entity) = rtsim_entity {
162 entity.with(rtsim_entity).with(RepositionOnChunkLoad {
163 needs_ground: false,
164 })
165 } else {
166 entity
167 };
168
169 let entity = if let Some(projectile) = projectile {
170 entity.with(projectile)
171 } else {
172 entity
173 };
174
175 let new_entity = entity.build();
176
177 if let Some(rtsim_entity) = rtsim_entity {
178 server
179 .state()
180 .ecs()
181 .write_resource::<IdMaps>()
182 .add_rtsim(rtsim_entity, new_entity);
183 }
184
185 if let comp::Alignment::Owned(owner_uid) = alignment {
187 let state = server.state();
188 let uids = state.ecs().read_storage::<Uid>();
189 let clients = state.ecs().read_storage::<Client>();
190 let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
191 if let Some(owner) = state.ecs().entity_from_uid(owner_uid) {
192 let map_markers = state.ecs().read_storage::<comp::MapMarker>();
193 group_manager.new_pet(
194 new_entity,
195 owner,
196 &mut state.ecs().write_storage(),
197 &state.ecs().entities(),
198 &state.ecs().read_storage(),
199 &uids,
200 &mut |entity, group_change| {
201 clients
202 .get(entity)
203 .and_then(|c| {
204 group_change
205 .try_map_ref(|e| uids.get(*e).copied())
206 .map(|g| (g, c))
207 })
208 .map(|(g, c)| {
209 update_map_markers(&map_markers, &uids, c, &group_change);
212 c.send_fallible(ServerGeneral::GroupUpdate(g));
213 });
214 },
215 );
216 }
217 } else if let Some(group) = alignment.group() {
218 let _ = server.state.ecs().write_storage().insert(new_entity, group);
219 }
220
221 if let Some(rider) = ev.rider {
222 let rider_entity = handle_create_npc(server, CreateNpcEvent {
223 pos: ev.pos,
224 ori: Ori::default(),
225 npc: rider,
226 rider: None,
227 });
228 let uids = server.state().ecs().read_storage::<Uid>();
229 let link = Mounting {
230 mount: *uids.get(new_entity).expect("We just created this entity"),
231 rider: *uids.get(rider_entity).expect("We just created this entity"),
232 };
233 drop(uids);
234 server
235 .state
236 .link(link)
237 .expect("We just created these entities");
238 }
239
240 for (pet, offset) in pets {
241 let pet_entity = handle_create_npc(server, CreateNpcEvent {
242 pos: comp::Pos(ev.pos.0 + offset),
243 ori: Ori::from_unnormalized_vec(offset).unwrap_or_default(),
244 npc: pet,
245 rider: None,
246 });
247
248 tame_pet(server.state.ecs(), pet_entity, new_entity);
249 }
250
251 new_entity
252}
253
254pub fn handle_create_ship(server: &mut Server, ev: CreateShipEvent) {
255 let collider = ev.ship.make_collider();
256 let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
257
258 let (mut steering, mut _seats) = {
261 let mut steering = Vec::new();
262 let mut seats = Vec::new();
263
264 for (pos, block) in collider
265 .get_vol(&voxel_colliders_manifest)
266 .iter()
267 .flat_map(|voxel_collider| voxel_collider.volume().full_vol_iter())
268 {
269 match (block.is_controller(), block.is_mountable()) {
270 (true, true) => steering.push((pos, *block)),
271 (false, true) => seats.push((pos, *block)),
272 _ => {},
273 }
274 }
275 (steering.into_iter(), seats.into_iter())
276 };
277
278 let mut entity = server
279 .state
280 .create_ship(ev.pos, ev.ori, ev.ship, |_| collider);
281 if let Some(rtsim_vehicle) = ev.rtsim_entity {
291 entity = entity.with(rtsim_vehicle);
292 }
293 let entity = entity.build();
294
295 if let Some(driver) = ev.driver {
296 let npc_entity = handle_create_npc(server, CreateNpcEvent {
297 pos: ev.pos,
298 ori: ev.ori,
299 npc: driver,
300 rider: None,
301 });
302
303 let uids = server.state.ecs().read_storage::<Uid>();
304 let (rider_uid, mount_uid) = uids
305 .get(npc_entity)
306 .copied()
307 .zip(uids.get(entity).copied())
308 .expect("Couldn't get Uid from newly created ship and npc");
309 drop(uids);
310
311 if let Some((steering_pos, steering_block)) = steering.next() {
312 server
313 .state
314 .link(VolumeMounting {
315 pos: VolumePos {
316 kind: Volume::Entity(mount_uid),
317 pos: steering_pos,
318 },
319 block: steering_block,
320 rider: rider_uid,
321 })
322 .expect("Failed to link driver to ship");
323 } else {
324 server
325 .state
326 .link(Mounting {
327 mount: mount_uid,
328 rider: rider_uid,
329 })
330 .expect("Failed to link driver to ship");
331 }
332 }
333
334 }
366
367pub fn handle_shoot(server: &mut Server, ev: ShootEvent) {
368 let state = server.state_mut();
369
370 let pos = ev.pos.0;
371
372 let vel = *ev.dir * ev.speed
373 + state
374 .ecs()
375 .read_storage::<Vel>()
376 .get(ev.entity)
377 .map_or(Vec3::zero(), |v| v.0);
378
379 state
381 .ecs()
382 .read_resource::<EventBus<Outcome>>()
383 .emit_now(Outcome::ProjectileShot {
384 pos,
385 body: ev.body,
386 vel,
387 });
388
389 let mut builder = state.create_projectile(Pos(pos), Vel(vel), ev.body, ev.projectile);
390 if let Some(light) = ev.light {
391 builder = builder.with(light)
392 }
393 if let Some(object) = ev.object {
394 builder = builder.with(object)
395 }
396
397 builder.build();
398}
399
400pub fn handle_shockwave(server: &mut Server, ev: ShockwaveEvent) {
401 let state = server.state_mut();
402 state
403 .create_shockwave(ev.properties, ev.pos, ev.ori)
404 .build();
405}
406
407pub fn handle_create_special_entity(server: &mut Server, ev: CreateSpecialEntityEvent) {
408 let time = server.state.get_time();
409
410 match ev.entity {
411 SpecialEntity::Waypoint => {
412 server
413 .state
414 .create_object(Pos(ev.pos), comp::object::Body::CampfireLit)
415 .with(LightEmitter {
416 col: Rgb::new(1.0, 0.3, 0.1),
417 strength: 5.0,
418 flicker: 1.0,
419 animated: true,
420 })
421 .with(WaypointArea::default())
422 .with(comp::Immovable)
423 .with(comp::EnteredAuras::default())
424 .with(comp::Auras::new(vec![
425 Aura::new(
426 AuraKind::Buff {
427 kind: BuffKind::CampfireHeal,
428 data: BuffData::new(0.02, Some(Secs(1.0))),
429 category: BuffCategory::Natural,
430 source: BuffSource::World,
431 },
432 5.0,
433 None,
434 AuraTarget::All,
435 Time(time),
436 ),
437 Aura::new(
438 AuraKind::Buff {
439 kind: BuffKind::Burning,
440 data: BuffData::new(2.0, Some(Secs(10.0))),
441 category: BuffCategory::Natural,
442 source: BuffSource::World,
443 },
444 0.7,
445 None,
446 AuraTarget::All,
447 Time(time),
448 ),
449 ]))
450 .build();
451 },
452 SpecialEntity::Teleporter(portal) => {
453 server
454 .state
455 .create_teleporter(comp::Pos(ev.pos), portal)
456 .build();
457 },
458 SpecialEntity::ArenaTotem { range } => {
459 server
460 .state
461 .create_object(Pos(ev.pos), comp::object::Body::GnarlingTotemGreen)
462 .with(comp::Immovable)
463 .with(comp::EnteredAuras::default())
464 .with(comp::Auras::new(vec![
465 Aura::new(
466 AuraKind::FriendlyFire,
467 range,
468 None,
469 AuraTarget::All,
470 Time(time),
471 ),
472 Aura::new(AuraKind::ForcePvP, range, None, AuraTarget::All, Time(time)),
473 ]))
474 .build();
475 },
476 }
477}
478
479pub fn handle_create_item_drop(server: &mut Server, ev: CreateItemDropEvent) {
480 server
481 .state
482 .create_item_drop(ev.pos, ev.ori, ev.vel, ev.item, ev.loot_owner);
483}
484
485pub fn handle_create_object(
486 server: &mut Server,
487 CreateObjectEvent {
488 pos,
489 vel,
490 body,
491 object,
492 item,
493 light_emitter,
494 stats,
495 }: CreateObjectEvent,
496) {
497 server
498 .state
499 .create_object(pos, body)
500 .with(vel)
501 .maybe_with(object)
502 .maybe_with(item)
503 .maybe_with(light_emitter)
504 .maybe_with(stats)
505 .build();
506}
507
508pub fn handle_create_aura_entity(server: &mut Server, ev: CreateAuraEntityEvent) {
509 let time = *server.state.ecs().read_resource::<Time>();
510 let mut entity = server
511 .state
512 .ecs_mut()
513 .create_entity_synced()
514 .with(ev.pos)
515 .with(comp::Vel(Vec3::zero()))
516 .with(comp::Ori::default())
517 .with(ev.auras)
518 .with(comp::Alignment::Owned(ev.creator_uid));
519
520 if let Some(dur) = ev.duration {
522 let object = comp::Object::DeleteAfter {
523 spawned_at: time,
524 timeout: Duration::from_secs_f64(dur.0),
525 };
526 entity = entity.with(object);
527 }
528 entity.build();
529}