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