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