1#[cfg(feature = "worldgen")]
2use crate::rtsim::RtSim;
3use crate::{
4 BattleModeBuffer, SpawnPoint,
5 automod::AutoMod,
6 chat::ChatExporter,
7 client::Client,
8 events::{self, shared::update_map_markers},
9 persistence::PersistedComponents,
10 pet::restore_pet,
11 presence::RepositionToFreeSpace,
12 settings::Settings,
13 sys::sentinel::DeletedEntities,
14 wiring,
15};
16use common::{
17 LoadoutBuilder, ViewDistances,
18 character::CharacterId,
19 comp::{
20 self, BASE_ABILITY_LIMIT, CapsulePrism, ChatType, Content, Group, Inventory, LootOwner,
21 Object, Player, Poise, Presence, PresenceKind, item::ItemKind, misc::PortalData, object,
22 },
23 interaction::Interaction,
24 link::{Is, Link, LinkHandle},
25 mounting::{Mounting, Rider, VolumeMounting, VolumeRider},
26 resources::{Secs, Time},
27 rtsim::{Actor, RtSimEntity},
28 tether::Tethered,
29 uid::{IdMaps, Uid},
30 util::Dir,
31};
32#[cfg(feature = "worldgen")]
33use common::{calendar::Calendar, resources::TimeOfDay, slowjob::SlowJobPool};
34use common_net::{
35 msg::{CharacterInfo, PlayerListUpdate, ServerGeneral},
36 sync::WorldSyncExt,
37};
38use common_state::State;
39use specs::{
40 Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt, WriteStorage,
41 storage::{GenericReadStorage, GenericWriteStorage},
42};
43use std::time::{Duration, Instant};
44use tracing::{error, trace, warn};
45use vek::*;
46
47pub trait StateExt {
48 fn create_npc(
50 &mut self,
51 pos: comp::Pos,
52 ori: comp::Ori,
53 stats: comp::Stats,
54 skill_set: comp::SkillSet,
55 health: Option<comp::Health>,
56 poise: Poise,
57 inventory: Inventory,
58 body: comp::Body,
59 scale: comp::Scale,
60 ) -> EcsEntityBuilder<'_>;
61 fn create_empty(&mut self, pos: comp::Pos) -> EcsEntityBuilder<'_>;
63 fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body)
65 -> EcsEntityBuilder<'_>;
66 fn create_item_drop(
69 &mut self,
70 pos: comp::Pos,
71 ori: comp::Ori,
72 vel: comp::Vel,
73 item: comp::PickupItem,
74 loot_owner: Option<LootOwner>,
75 ) -> Option<EcsEntity>;
76 fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
77 &mut self,
78 pos: comp::Pos,
79 ori: comp::Ori,
80 ship: comp::ship::Body,
81 make_collider: F,
82 ) -> EcsEntityBuilder<'_>;
83 fn create_projectile(
85 &mut self,
86 pos: comp::Pos,
87 vel: comp::Vel,
88 body: comp::Body,
89 projectile: comp::Projectile,
90 ) -> EcsEntityBuilder<'_>;
91 fn create_shockwave(
93 &mut self,
94 properties: comp::shockwave::Properties,
95 pos: comp::Pos,
96 ori: comp::Ori,
97 ) -> EcsEntityBuilder<'_>;
98 fn create_arcing(
99 &mut self,
100 arc: comp::ArcProperties,
101 target: Uid,
102 owner: Option<Uid>,
103 pos: comp::Pos,
104 ) -> EcsEntityBuilder<'_>;
105 fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder<'_>;
107 fn create_wiring(
108 &mut self,
109 pos: comp::Pos,
110 object: comp::object::Body,
111 wiring_element: wiring::WiringElement,
112 ) -> EcsEntityBuilder<'_>;
113 #[cfg(feature = "worldgen")]
118 fn create_persister(
119 &mut self,
120 pos: comp::Pos,
121 view_distance: u32,
122 world: &std::sync::Arc<world::World>,
123 index: &world::IndexOwned,
124 ) -> EcsEntityBuilder<'_>;
125 fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder<'_>;
129 fn initialize_character_data(
131 &mut self,
132 entity: EcsEntity,
133 character_id: CharacterId,
134 view_distances: ViewDistances,
135 );
136 fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances);
138 fn update_character_data(
141 &mut self,
142 entity: EcsEntity,
143 components: PersistedComponents,
144 ) -> Result<(), String>;
145 fn validate_chat_msg(
147 &self,
148 player: EcsEntity,
149 chat_type: &comp::ChatType<comp::Group>,
150 msg: &Content,
151 from_client: bool,
156 ) -> bool;
157 fn send_chat(&self, msg: comp::UnresolvedChatMsg, from_client: bool);
158 fn notify_players(&self, msg: ServerGeneral);
159 fn notify_in_game_clients(&self, msg: ServerGeneral);
160 fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error>;
163 fn maintain_links(&mut self);
165 fn delete_entity_recorded(
167 &mut self,
168 entity: EcsEntity,
169 ) -> Result<(), specs::error::WrongGeneration>;
170 fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor>;
172 fn position_mut<T>(
179 &mut self,
180 entity: EcsEntity,
181 dismount_volume: bool,
182 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
183 ) -> Result<T, Content>;
184
185 fn position_mut_reposition<T>(
197 &mut self,
198 entity: EcsEntity,
199 dismount_volume: bool,
200 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
201 needs_ground: bool,
202 modify_waypoints: bool,
203 ) -> Result<T, Content>;
204}
205
206impl StateExt for State {
207 fn create_npc(
208 &mut self,
209 pos: comp::Pos,
210 ori: comp::Ori,
211 stats: comp::Stats,
212 skill_set: comp::SkillSet,
213 health: Option<comp::Health>,
214 poise: Poise,
215 inventory: Inventory,
216 body: comp::Body,
217 scale: comp::Scale,
218 ) -> EcsEntityBuilder<'_> {
219 self.ecs_mut()
220 .create_entity_synced()
221 .with(pos)
222 .with(comp::Vel(Vec3::zero()))
223 .with(ori)
224 .with(comp::Mass(body.mass().0 * scale.0.powi(3)))
225 .with(body.density())
226 .with(body.collider())
227 .with(scale)
228 .with(comp::Controller::default())
229 .with(body)
230 .with(comp::Energy::new(body))
231 .with(stats)
232 .with(if body.is_humanoid() {
233 comp::ActiveAbilities::default_limited(BASE_ABILITY_LIMIT)
234 } else {
235 comp::ActiveAbilities::default()
236 })
237 .with(skill_set)
238 .maybe_with(health)
239 .with(poise)
240 .with(comp::Alignment::Npc)
241 .with(comp::CharacterState::default())
242 .with(comp::CharacterActivity::default())
243 .with(inventory)
244 .with(comp::Buffs::default())
245 .with(comp::Combo::default())
246 .with(comp::Auras::default())
247 .with(comp::EnteredAuras::default())
248 .with(comp::Stance::default())
249 .with(comp::projectile::ProjectileHitEntities::default())
250 .maybe_with(body.heads().map(comp::body::parts::Heads::new))
251 }
252
253 fn create_empty(&mut self, pos: comp::Pos) -> EcsEntityBuilder<'_> {
254 self.ecs_mut()
255 .create_entity_synced()
256 .with(pos)
257 .with(comp::Vel(Vec3::zero()))
258 .with(comp::Ori::default())
259 }
260
261 fn create_object(
262 &mut self,
263 pos: comp::Pos,
264 object: comp::object::Body,
265 ) -> EcsEntityBuilder<'_> {
266 let body = comp::Body::Object(object);
267 self.create_empty(pos)
268 .with(body.mass())
269 .with(body.density())
270 .with(body.collider())
271 .with(body)
272 }
273
274 fn create_item_drop(
275 &mut self,
276 pos: comp::Pos,
277 ori: comp::Ori,
278 vel: comp::Vel,
279 world_item: comp::PickupItem,
280 loot_owner: Option<LootOwner>,
281 ) -> Option<EcsEntity> {
282 {
284 use crate::sys::item::get_nearby_mergeable_items;
285
286 let positions = self.ecs().read_storage::<comp::Pos>();
287 let loot_owners = self.ecs().read_storage::<LootOwner>();
288 let mut items = self.ecs().write_storage::<comp::PickupItem>();
289 let entities = self.ecs().entities();
290 let spatial_grid = self.ecs().read_resource();
291
292 let nearby_items = get_nearby_mergeable_items(
293 &world_item,
294 &pos,
295 loot_owner.as_ref(),
296 (&entities, &items, &positions, &loot_owners, &spatial_grid),
297 );
298
299 if let Some((mergeable_item, _)) =
301 nearby_items.min_by_key(|(_, dist)| (dist * 1000.0) as i32)
302 {
303 items
304 .get_mut(mergeable_item)
305 .expect("we know that the item exists")
306 .try_merge(world_item)
307 .expect("`try_merge` should succeed because `can_merge` returned `true`");
308 return None;
309 }
310 }
311
312 let spawned_at = *self.ecs().read_resource::<Time>();
313
314 let item_body = comp::body::item::Body::from(world_item.item());
315 let body = comp::Body::Item(item_body);
316 let light_emitter = match &*world_item.item().kind() {
317 ItemKind::Lantern(lantern) => Some(comp::LightEmitter {
318 col: lantern.color(),
319 strength: lantern.strength(),
320 flicker: lantern.flicker(),
321 animated: true,
322 dir: lantern.dir,
323 }),
324 _ => None,
325 };
326 Some(
327 self.ecs_mut()
328 .create_entity_synced()
329 .with(world_item)
330 .with(pos)
331 .with(ori)
332 .with(vel)
333 .with(item_body.orientation(&mut rand::rng()))
334 .with(item_body.mass())
335 .with(item_body.density())
336 .with(body.collider())
337 .with(body)
338 .with(Object::DeleteAfter {
339 spawned_at,
340 timeout: Duration::from_secs(300),
342 })
343 .maybe_with(loot_owner)
344 .maybe_with(light_emitter)
345 .build(),
346 )
347 }
348
349 fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
350 &mut self,
351 pos: comp::Pos,
352 ori: comp::Ori,
353 ship: comp::ship::Body,
354 make_collider: F,
355 ) -> EcsEntityBuilder<'_> {
356 let body = comp::Body::Ship(ship);
357
358 self
359 .ecs_mut()
360 .create_entity_synced()
361 .with(pos)
362 .with(comp::Vel(Vec3::zero()))
363 .with(ori)
364 .with(body.mass())
365 .with(body.density())
366 .with(make_collider(ship))
367 .with(body)
368 .with(comp::Controller::default())
369 .with(Inventory::with_empty())
370 .with(comp::CharacterState::default())
371 .with(comp::CharacterActivity::default())
372 .with(comp::Energy::new(ship.into()))
376 .with(comp::Stats::new({
377 Content::Plain("Airship".to_string())
381 }, body))
382 .with(comp::SkillSet::default())
383 .with(comp::ActiveAbilities::default())
384 .with(comp::Combo::default())
385 }
386
387 fn create_projectile(
388 &mut self,
389 pos: comp::Pos,
390 vel: comp::Vel,
391 body: comp::Body,
392 projectile: comp::Projectile,
393 ) -> EcsEntityBuilder<'_> {
394 let mut projectile_base = self
395 .ecs_mut()
396 .create_entity_synced()
397 .with(pos)
398 .with(vel)
399 .with(comp::Ori::from_unnormalized_vec(vel.0).unwrap_or_default())
400 .with(body.mass())
401 .with(body.density());
402
403 if projectile.is_sticky {
404 projectile_base = projectile_base.with(comp::Sticky);
405 }
406 if projectile.is_point {
407 projectile_base = projectile_base.with(comp::Collider::Point);
408 } else {
409 projectile_base = projectile_base.with(body.collider());
410 }
411
412 projectile_base.with(projectile).with(body)
413 }
414
415 fn create_shockwave(
416 &mut self,
417 properties: comp::shockwave::Properties,
418 pos: comp::Pos,
419 ori: comp::Ori,
420 ) -> EcsEntityBuilder<'_> {
421 self.ecs_mut()
422 .create_entity_synced()
423 .with(pos)
424 .with(ori)
425 .with(comp::Shockwave {
426 properties,
427 creation: None,
428 })
429 .with(comp::ShockwaveHitEntities {
430 hit_entities: Vec::<Uid>::new(),
431 })
432 }
433
434 fn create_arcing(
435 &mut self,
436 arc: comp::ArcProperties,
437 target: Uid,
438 owner: Option<Uid>,
439 pos: comp::Pos,
440 ) -> EcsEntityBuilder<'_> {
441 let time = self.get_time();
442
443 self.ecs_mut()
444 .create_entity_synced()
445 .with(pos)
446 .with(comp::Arcing {
447 properties: arc,
448 last_arc_time: Time(time),
449 hit_entities: vec![target],
450 owner,
451 })
452 }
453
454 fn create_safezone(&mut self, range: Option<f32>, pos: comp::Pos) -> EcsEntityBuilder<'_> {
455 use comp::{
456 aura::{Aura, AuraKind, AuraTarget, Auras},
457 buff::{BuffCategory, BuffData, BuffKind, BuffSource},
458 };
459 let time = self.get_time();
460 self.ecs_mut()
462 .create_entity_synced()
463 .with(pos)
464 .with(Auras::new(vec![Aura::new(
465 AuraKind::Buff {
466 kind: BuffKind::Invulnerability,
467 data: BuffData::new(1.0, Some(Secs(1.0))),
468 category: BuffCategory::Natural,
469 source: BuffSource::World,
470 },
471 range.unwrap_or(100.0),
472 None,
473 AuraTarget::All,
474 Time(time),
475 )]))
476 }
477
478 fn create_wiring(
479 &mut self,
480 pos: comp::Pos,
481 object: comp::object::Body,
482 wiring_element: wiring::WiringElement,
483 ) -> EcsEntityBuilder<'_> {
484 self.ecs_mut()
485 .create_entity_synced()
486 .with(pos)
487 .with(comp::Vel(Vec3::zero()))
488 .with(comp::Ori::default())
489 .with({
490 let body: comp::Body = object.into();
491 body.collider()
492 })
493 .with(comp::Body::Object(object))
494 .with(comp::Mass(100.0))
495 .with(wiring_element)
497 .with(comp::LightEmitter {
498 col: Rgb::new(0.0, 0.0, 0.0),
499 strength: 2.0,
500 flicker: 1.0,
501 animated: true,
502 dir: None,
503 })
504 }
505
506 #[cfg(feature = "worldgen")]
511 fn create_persister(
512 &mut self,
513 pos: comp::Pos,
514 view_distance: u32,
515 world: &std::sync::Arc<world::World>,
516 index: &world::IndexOwned,
517 ) -> EcsEntityBuilder<'_> {
518 use common::{terrain::TerrainChunkSize, vol::RectVolSize};
519 use std::sync::Arc;
520 {
522 let ecs = self.ecs();
523 let slow_jobs = ecs.write_resource::<SlowJobPool>();
524 let rtsim = ecs.read_resource::<RtSim>();
525 let mut chunk_generator =
526 ecs.write_resource::<crate::chunk_generator::ChunkGenerator>();
527 let chunk_pos = self.terrain().pos_key(pos.0.map(|e| e as i32));
528 (-(view_distance as i32)..view_distance as i32 + 1)
529 .flat_map(|x| {
530 (-(view_distance as i32)..view_distance as i32 + 1).map(move |y| Vec2::new(x, y))
531 })
532 .map(|offset| offset + chunk_pos)
533 .filter(|chunk_key| {
536 pos.0.xy().map(|e| e as f64).distance(
537 chunk_key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
538 ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt())
539 * TerrainChunkSize::RECT_SIZE.x as f64
540 })
541 .for_each(|chunk_key| {
542 {
543 let time = (*ecs.read_resource::<TimeOfDay>(), (*ecs.read_resource::<Calendar>()).clone());
544 chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), &rtsim, index.clone(), time);
545 }
546 });
547 }
548
549 self.ecs_mut()
550 .create_entity_synced()
551 .with(pos)
552 .with(Presence::new(
553 ViewDistances {
554 terrain: view_distance,
555 entity: view_distance,
556 },
557 PresenceKind::Spectator,
558 ))
559 }
560
561 fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder<'_> {
562 self.create_object(pos, object::Body::Portal)
563 .with(comp::Immovable)
564 .with(comp::Object::from(portal))
565 }
566
567 fn initialize_character_data(
568 &mut self,
569 entity: EcsEntity,
570 character_id: CharacterId,
571 view_distances: ViewDistances,
572 ) {
573 let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
574
575 if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
576 self.write_component_ignore_entity_dead(entity, comp::Controller::default());
581 self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
582 self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
583 self.write_component_ignore_entity_dead(entity, comp::Ori::default());
584 self.write_component_ignore_entity_dead(
585 entity,
586 comp::Collider::CapsulePrism(CapsulePrism {
587 p0: Vec2::zero(),
588 p1: Vec2::zero(),
589 radius: 0.4,
590 z_min: 0.0,
591 z_max: 1.75,
592 }),
593 );
594 self.write_component_ignore_entity_dead(entity, comp::CharacterState::default());
595 self.write_component_ignore_entity_dead(entity, comp::CharacterActivity::default());
596 self.write_component_ignore_entity_dead(entity, comp::Alignment::Owned(player_uid));
597 self.write_component_ignore_entity_dead(entity, comp::Buffs::default());
598 self.write_component_ignore_entity_dead(entity, comp::Auras::default());
599 self.write_component_ignore_entity_dead(entity, comp::EnteredAuras::default());
600 self.write_component_ignore_entity_dead(entity, comp::Combo::default());
601 self.write_component_ignore_entity_dead(entity, comp::Stance::default());
602 self.write_component_ignore_entity_dead(
603 entity,
604 comp::projectile::ProjectileHitEntities::default(),
605 );
606
607 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
609
610 self.write_component_ignore_entity_dead(
611 entity,
612 Presence::new(view_distances, PresenceKind::LoadingCharacter(character_id)),
613 );
614
615 if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
617 client.send_fallible(ServerGeneral::CharacterSuccess);
618 }
619 }
620 }
621
622 fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances) {
623 let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
624
625 if self.read_component_copied::<Uid>(entity).is_some() {
626 self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
631
632 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
634
635 self.write_component_ignore_entity_dead(
636 entity,
637 Presence::new(view_distances, PresenceKind::Spectator),
638 );
639
640 if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
642 client.send_fallible(ServerGeneral::SpectatorSuccess(spawn_point));
643 }
644 }
645 }
646
647 fn update_character_data(
649 &mut self,
650 entity: EcsEntity,
651 components: PersistedComponents,
652 ) -> Result<(), String> {
653 let PersistedComponents {
654 body,
655 hardcore,
656 stats,
657 skill_set,
658 inventory,
659 waypoint,
660 pets,
661 active_abilities,
662 map_marker,
663 } = components;
664
665 if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
666 let result =
667 if let Some(presence) = self.ecs().write_storage::<Presence>().get_mut(entity) {
668 if let PresenceKind::LoadingCharacter(id) = presence.kind {
669 presence.kind = PresenceKind::Character(id);
670 self.ecs()
671 .write_resource::<IdMaps>()
672 .add_character(id, entity);
673 Ok(())
674 } else {
675 Err("PresenceKind is not LoadingCharacter")
676 }
677 } else {
678 Err("Presence component missing")
679 };
680 if let Err(err) = result {
681 let err = format!("Unexpected state when applying loaded character info: {err}");
682 error!("{err}");
683 return Err(err);
685 }
686
687 let name = stats.name.clone();
688 let gender = stats.original_body.humanoid_gender();
690
691 self.write_component_ignore_entity_dead(entity, body.collider());
696 self.write_component_ignore_entity_dead(entity, body);
697 self.write_component_ignore_entity_dead(entity, body.mass());
698 self.write_component_ignore_entity_dead(entity, body.density());
699 self.write_component_ignore_entity_dead(entity, comp::Health::new(body));
700 self.write_component_ignore_entity_dead(entity, comp::Energy::new(body));
701 self.write_component_ignore_entity_dead(entity, Poise::new(body));
702 self.write_component_ignore_entity_dead(entity, stats);
703 self.write_component_ignore_entity_dead(entity, active_abilities);
704 self.write_component_ignore_entity_dead(entity, skill_set);
705 self.write_component_ignore_entity_dead(entity, inventory);
706 self.write_component_ignore_entity_dead(
707 entity,
708 comp::InventoryUpdateBuffer::new(comp::InventoryUpdateEvent::Init),
709 );
710
711 if let Some(hardcore) = hardcore {
712 self.write_component_ignore_entity_dead(entity, hardcore);
713 }
714
715 if let Some(waypoint) = waypoint {
716 self.write_component_ignore_entity_dead(entity, RepositionToFreeSpace {
717 needs_ground: true,
718 modify_waypoints: true,
719 });
720 self.write_component_ignore_entity_dead(entity, waypoint);
721 self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos()));
722 self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
723 self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
727 }
728
729 if let Some(map_marker) = map_marker {
730 self.write_component_ignore_entity_dead(entity, map_marker);
731 }
732
733 let player_pos = self.ecs().read_storage::<comp::Pos>().get(entity).copied();
734 if let Some(player_pos) = player_pos {
735 trace!(
736 "Loading {} pets for player at pos {:?}",
737 pets.len(),
738 player_pos
739 );
740 let mut rng = rand::rng();
741
742 for (pet, body, stats) in pets {
743 let ori = comp::Ori::from(Dir::random_2d(&mut rng));
744 let pet_entity = self
745 .create_npc(
746 player_pos,
747 ori,
748 stats,
749 comp::SkillSet::default(),
750 Some(comp::Health::new(body)),
751 Poise::new(body),
752 Inventory::with_loadout(
753 LoadoutBuilder::from_default(&body).build(),
754 body,
755 ),
756 body,
757 comp::Scale(1.0),
758 )
759 .with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)))
760 .build();
761
762 restore_pet(self.ecs(), pet_entity, entity, pet);
763 }
764 } else {
765 error!("Player has no pos, cannot load {} pets", pets.len());
766 }
767
768 let settings = self.ecs().read_resource::<Settings>();
769 let mut char_battle_mode = settings.gameplay.battle_mode.default_mode();
770 let presences = self.ecs().read_storage::<Presence>();
771 let presence = presences.get(entity);
772 if let Some(Presence {
773 kind: PresenceKind::Character(char_id),
774 ..
775 }) = presence
776 {
777 let battlemode_buffer = self.ecs().fetch::<BattleModeBuffer>();
778 let mut players = self.ecs().write_storage::<comp::Player>();
779 if let Some(mut player_info) = players.get_mut(entity) {
780 if let Some((mode, change)) = battlemode_buffer.get(char_id) {
781 char_battle_mode = *mode;
782 player_info.last_battlemode_change = Some(*change);
783 } else {
784 player_info.last_battlemode_change = None;
794 }
795
796 player_info.battle_mode = char_battle_mode;
797 }
798 }
799
800 if self
801 .ecs()
802 .read_component::<Client>()
803 .get(entity)
804 .is_some_and(|client| client.client_type.emit_login_events())
805 {
806 self.notify_players(ServerGeneral::PlayerListUpdate(
808 PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
809 name,
810 gender,
811 battle_mode: char_battle_mode,
812 }),
813 ));
814 }
815 }
816
817 Ok(())
818 }
819
820 fn validate_chat_msg(
821 &self,
822 entity: EcsEntity,
823 chat_type: &comp::ChatType<comp::Group>,
824 msg: &Content,
825 from_client: bool,
826 ) -> bool {
827 let mut automod = self.ecs().write_resource::<AutoMod>();
828 let client = self.ecs().read_storage::<Client>();
829 let player = self.ecs().read_storage::<Player>();
830 let Some(client) = client.get(entity) else {
831 return true;
832 };
833 let Some(player) = player.get(entity) else {
834 return true;
835 };
836
837 let Some(msg) = msg.as_plain() else {
841 return !from_client;
842 };
843
844 match automod.validate_chat_msg(
845 player.uuid(),
846 self.ecs()
847 .read_storage::<comp::Admin>()
848 .get(entity)
849 .map(|a| a.0),
850 Instant::now(),
851 chat_type,
852 msg,
853 ) {
854 Ok(note) => {
855 if let Some(note) = note {
856 let _ = client.send(ServerGeneral::server_msg(
857 ChatType::CommandInfo,
858 Content::Plain(format!("{}", note)),
859 ));
860 }
861 true
862 },
863 Err(err) => {
864 let _ = client.send(ServerGeneral::server_msg(
865 ChatType::CommandError,
866 Content::Plain(format!("{}", err)),
867 ));
868 false
869 },
870 }
871 }
872
873 fn send_chat(&self, msg: comp::UnresolvedChatMsg, from_client: bool) {
876 let ecs = self.ecs();
877 let is_within =
878 |target, a: &comp::Pos, b: &comp::Pos| a.0.distance_squared(b.0) < target * target;
879
880 let group_manager = ecs.read_resource::<comp::group::GroupManager>();
881 let chat_exporter = ecs.read_resource::<ChatExporter>();
882
883 let group_info = msg.get_group().and_then(|g| group_manager.group_info(*g));
884
885 if let Some(exported_message) = ChatExporter::generate(&msg, ecs) {
886 chat_exporter.send(exported_message);
887 }
888
889 let resolved_msg = msg
890 .clone()
891 .map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
892
893 let id_maps = ecs.read_resource::<IdMaps>();
894 let entity_from_uid = |uid| id_maps.uid_entity(uid);
895
896 if msg.chat_type.uid().is_none_or(|sender| {
897 entity_from_uid(sender).is_some_and(|e| {
898 self.validate_chat_msg(e, &msg.chat_type, msg.content(), from_client)
899 })
900 }) {
901 match &msg.chat_type {
902 comp::ChatType::Offline(_)
903 | comp::ChatType::CommandInfo
904 | comp::ChatType::CommandError
905 | comp::ChatType::Meta
906 | comp::ChatType::World(_) => {
907 self.notify_players(ServerGeneral::ChatMsg(resolved_msg))
908 },
909 comp::ChatType::Online(u) => {
910 for (client, uid) in
911 (&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>()).join()
912 {
913 if uid != u {
914 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
915 }
916 }
917 },
918 &comp::ChatType::Tell(from, to) => {
919 let clients = ecs.read_storage::<Client>();
920 if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
921 from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
922 }
923 if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
924 to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
925 }
926 },
927 comp::ChatType::Kill(kill_source, uid) => {
928 let clients = ecs.read_storage::<Client>();
929 let clients_count = clients.count();
930 if clients_count
933 > ecs
934 .fetch::<Settings>()
935 .max_player_for_kill_broadcast
936 .unwrap_or_default()
937 {
938 let killed_entity = entity_from_uid(*uid);
940 let groups = ecs.read_storage::<Group>();
941 let killed_group = killed_entity.and_then(|e| groups.get(e));
942 if let Some(g) = &killed_group {
943 send_to_group(g, ecs, &resolved_msg);
944 }
945
946 let positions = ecs.read_storage::<comp::Pos>();
949 if let Some(died_player_pos) = killed_entity.and_then(|e| positions.get(e))
950 {
951 for (ent, client, pos) in
952 (&*ecs.entities(), &clients, &positions).join()
953 {
954 let client_group = groups.get(ent);
955 let is_different_group =
956 !(killed_group == client_group && client_group.is_some());
957 if is_within(comp::ChatMsg::SAY_DISTANCE, pos, died_player_pos)
958 && is_different_group
959 {
960 client.send_fallible(ServerGeneral::ChatMsg(
961 resolved_msg.clone(),
962 ));
963 }
964 }
965 }
966 } else {
967 self.notify_players(ServerGeneral::server_msg(
968 comp::ChatType::Kill(kill_source.clone(), *uid),
969 msg.into_content(),
970 ))
971 }
972 },
973 comp::ChatType::Say(uid) => {
974 let entity_opt = entity_from_uid(*uid);
975
976 let positions = ecs.read_storage::<comp::Pos>();
977 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
978 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
979 if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
980 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
981 }
982 }
983 }
984 },
985 comp::ChatType::Region(uid) => {
986 let entity_opt = entity_from_uid(*uid);
987
988 let positions = ecs.read_storage::<comp::Pos>();
989 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
990 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
991 if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
992 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
993 }
994 }
995 }
996 },
997 comp::ChatType::Npc(uid) => {
998 let entity_opt = entity_from_uid(*uid);
999
1000 let positions = ecs.read_storage::<comp::Pos>();
1001 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1002 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1003 if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
1004 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1005 }
1006 }
1007 }
1008 },
1009 comp::ChatType::NpcSay(uid) => {
1010 let entity_opt = entity_from_uid(*uid);
1011
1012 let positions = ecs.read_storage::<comp::Pos>();
1013 if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
1014 for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
1015 if is_within(comp::ChatMsg::NPC_SAY_DISTANCE, pos, speaker_pos) {
1016 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1017 }
1018 }
1019 }
1020 },
1021 &comp::ChatType::NpcTell(from, to) => {
1022 let clients = ecs.read_storage::<Client>();
1023 if let Some(from_client) = entity_from_uid(from).and_then(|e| clients.get(e)) {
1024 from_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1025 }
1026 if let Some(to_client) = entity_from_uid(to).and_then(|e| clients.get(e)) {
1027 to_client.send_fallible(ServerGeneral::ChatMsg(resolved_msg));
1028 }
1029 },
1030 comp::ChatType::FactionMeta(s) | comp::ChatType::Faction(_, s) => {
1031 for (client, faction) in (
1032 &ecs.read_storage::<Client>(),
1033 &ecs.read_storage::<comp::Faction>(),
1034 )
1035 .join()
1036 {
1037 if s == &faction.0 {
1038 client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
1039 }
1040 }
1041 },
1042 comp::ChatType::Group(from, g) => {
1043 if group_info.is_none() {
1044 let reply = comp::ChatType::CommandError
1051 .into_msg(Content::localized("command-message-group-missing"));
1052
1053 let clients = ecs.read_storage::<Client>();
1054 if let Some(client) =
1055 entity_from_uid(*from).and_then(|entity| clients.get(entity))
1056 {
1057 client.send_fallible(ServerGeneral::ChatMsg(reply));
1058 }
1059 } else {
1060 send_to_group(g, ecs, &resolved_msg);
1061 }
1062 },
1063 comp::ChatType::GroupMeta(g) => {
1064 send_to_group(g, ecs, &resolved_msg);
1065 },
1066 }
1067 }
1068 }
1069
1070 fn notify_players(&self, msg: ServerGeneral) {
1072 let mut msg = Some(msg);
1073 let mut lazy_msg = None;
1074 for (client, _) in (
1075 &self.ecs().read_storage::<Client>(),
1076 &self.ecs().read_storage::<comp::Player>(),
1077 )
1078 .join()
1079 {
1080 if let Some(msg) = msg.take() {
1081 lazy_msg = Some(client.prepare(msg));
1082 }
1083 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1084 }
1085 }
1086
1087 fn notify_in_game_clients(&self, msg: ServerGeneral) {
1089 let mut msg = Some(msg);
1090 let mut lazy_msg = None;
1091 for (client, _) in (
1092 &mut self.ecs().write_storage::<Client>(),
1093 &self.ecs().read_storage::<Presence>(),
1094 )
1095 .join()
1096 {
1097 if let Some(msg) = msg.take() {
1098 lazy_msg = Some(client.prepare(msg));
1099 }
1100 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
1101 }
1102 }
1103
1104 fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error> {
1105 let linker = LinkHandle::from_link(link);
1106
1107 L::create(&linker, &mut self.ecs().system_data())?;
1108
1109 self.ecs_mut()
1110 .entry::<Vec<LinkHandle<L>>>()
1111 .or_insert_with(Vec::new)
1112 .push(linker);
1113
1114 Ok(())
1115 }
1116
1117 fn maintain_links(&mut self) {
1118 fn maintain_link<L: Link>(state: &State) {
1119 if let Some(mut handles) = state.ecs().try_fetch_mut::<Vec<LinkHandle<L>>>() {
1120 let mut persist_data = None;
1121 handles.retain(|link| {
1122 if L::persist(
1123 link,
1124 persist_data.get_or_insert_with(|| state.ecs().system_data()),
1125 ) {
1126 true
1127 } else {
1128 persist_data.take();
1131 L::delete(link, &mut state.ecs().system_data());
1132 false
1133 }
1134 });
1135 }
1136 }
1137
1138 maintain_link::<Mounting>(self);
1139 maintain_link::<VolumeMounting>(self);
1140 maintain_link::<Tethered>(self);
1141 maintain_link::<Interaction>(self);
1142 }
1143
1144 fn delete_entity_recorded(
1145 &mut self,
1146 entity: EcsEntity,
1147 ) -> Result<(), specs::error::WrongGeneration> {
1148 {
1154 let clients = self.ecs().read_storage::<Client>();
1155 let uids = self.ecs().read_storage::<Uid>();
1156 let mut group_manager = self.ecs().write_resource::<comp::group::GroupManager>();
1157 let map_markers = self.ecs().read_storage::<comp::MapMarker>();
1158 group_manager.entity_deleted(
1159 entity,
1160 &mut self.ecs().write_storage(),
1161 &self.ecs().read_storage(),
1162 &uids,
1163 &self.ecs().entities(),
1164 &mut |entity, group_change| {
1165 clients
1166 .get(entity)
1167 .and_then(|c| {
1168 group_change
1169 .try_map_ref(|e| uids.get(*e).copied())
1170 .map(|g| (g, c))
1171 })
1172 .map(|(g, c)| {
1173 update_map_markers(&map_markers, &uids, c, &group_change);
1174 c.send_fallible(ServerGeneral::GroupUpdate(g));
1175 });
1176 },
1177 );
1178 }
1179
1180 events::shared::cancel_trades_for(self, entity);
1182
1183 let maybe_uid = self.read_component_copied::<Uid>(entity);
1186 let (maybe_character, sync_me) = self
1187 .read_storage::<Presence>()
1188 .get(entity)
1189 .map(|p| (p.kind.character_id(), p.kind.sync_me()))
1190 .unzip();
1191 let maybe_rtsim = self.read_component_copied::<RtSimEntity>(entity);
1192
1193 self.mut_resource::<IdMaps>().remove_entity(
1194 Some(entity),
1195 maybe_uid,
1196 maybe_character.flatten(),
1197 maybe_rtsim,
1198 );
1199
1200 delete_entity_common(self, entity, maybe_uid, sync_me.unwrap_or(true))
1201 }
1202
1203 fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor> {
1204 if let Some(rtsim_entity) = self
1205 .ecs()
1206 .read_storage::<RtSimEntity>()
1207 .get(entity)
1208 .copied()
1209 {
1210 Some(Actor::Npc(rtsim_entity))
1211 } else if let Some(PresenceKind::Character(character)) = self
1212 .ecs()
1213 .read_storage::<Presence>()
1214 .get(entity)
1215 .map(|p| p.kind)
1216 {
1217 Some(Actor::Character(character))
1218 } else {
1219 None
1220 }
1221 }
1222
1223 fn position_mut<T>(
1224 &mut self,
1225 entity: EcsEntity,
1226 dismount_volume: bool,
1227 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1228 ) -> Result<T, Content> {
1229 let ecs = self.ecs_mut();
1230 position_mut(
1231 entity,
1232 dismount_volume,
1233 f,
1234 &ecs.read_resource(),
1235 &mut ecs.write_storage(),
1236 ecs.write_storage(),
1237 ecs.write_storage(),
1238 ecs.read_storage(),
1239 ecs.read_storage(),
1240 ecs.read_storage(),
1241 )
1242 }
1243
1244 fn position_mut_reposition<T>(
1245 &mut self,
1246 entity: EcsEntity,
1247 dismount_volume: bool,
1248 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1249 needs_ground: bool,
1250 modify_waypoints: bool,
1251 ) -> Result<T, Content> {
1252 self.position_mut(entity, dismount_volume, f).inspect(|_| {
1253 self.write_component_ignore_entity_dead(entity, RepositionToFreeSpace {
1254 needs_ground,
1255 modify_waypoints,
1256 });
1257 })
1258 }
1259}
1260
1261pub fn position_mut<T>(
1262 entity: EcsEntity,
1263 dismount_volume: bool,
1264 f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
1265 id_maps: &IdMaps,
1266 is_volume_riders: &mut WriteStorage<Is<VolumeRider>>,
1267 mut positions: impl GenericWriteStorage<Component = comp::Pos>,
1268 mut force_updates: impl GenericWriteStorage<Component = comp::ForceUpdate>,
1269 is_riders: impl GenericReadStorage<Component = Is<Rider>>,
1270 presences: impl GenericReadStorage<Component = Presence>,
1271 clients: impl GenericReadStorage<Component = Client>,
1272) -> Result<T, Content> {
1273 if dismount_volume {
1274 is_volume_riders.remove(entity);
1275 }
1276
1277 let entity = is_riders
1278 .get(entity)
1279 .and_then(|is_rider| id_maps.uid_entity(is_rider.mount))
1280 .map(Ok)
1281 .or_else(|| {
1282 is_volume_riders.get(entity).and_then(|volume_rider| {
1283 Some(match volume_rider.pos.kind {
1284 common::mounting::Volume::Terrain => {
1285 Err(Content::Plain("Tried to move the world.".to_string()))
1286 },
1287 common::mounting::Volume::Entity(uid) => Ok(id_maps.uid_entity(uid)?),
1288 })
1289 })
1290 })
1291 .unwrap_or(Ok(entity))?;
1292
1293 let mut maybe_pos = None;
1294
1295 let res = positions
1296 .get_mut(entity)
1297 .map(|pos| {
1298 let res = f(pos);
1299 maybe_pos = Some(pos.0);
1300 res
1301 })
1302 .ok_or(Content::localized_with_args(
1303 "command-position-unavailable",
1304 [("target", "entity")],
1305 ));
1306
1307 if let Some(pos) = maybe_pos {
1308 if presences
1309 .get(entity)
1310 .map(|presence| presence.kind == PresenceKind::Spectator)
1311 .unwrap_or(false)
1312 {
1313 clients.get(entity).map(|client| {
1314 client.send_fallible(ServerGeneral::SpectatePosition(pos));
1315 });
1316 } else {
1317 force_updates
1318 .get_mut(entity)
1319 .map(|force_update| force_update.update());
1320 }
1321 }
1322
1323 res
1324}
1325
1326fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {
1327 for (client, group) in (&ecs.read_storage::<Client>(), &ecs.read_storage::<Group>()).join() {
1328 if g == group {
1329 client.send_fallible(ServerGeneral::ChatMsg(msg.clone()));
1330 }
1331 }
1332}
1333
1334pub(crate) fn delete_entity_common(
1337 state: &mut State,
1338 entity: EcsEntity,
1339 maybe_uid: Option<Uid>,
1340 sync_me: bool,
1341) -> Result<(), specs::error::WrongGeneration> {
1342 let maybe_pos = state.read_component_copied::<comp::Pos>(entity);
1343
1344 let result = state.ecs_mut().delete_entity(entity);
1346
1347 if result.is_ok() {
1348 let region_map = state.mut_resource::<common::region::RegionMap>();
1349 let region_key = region_map.entity_deleted(entity);
1350 if let Some(uid) = maybe_uid {
1358 if let Some(region_key) = region_key {
1359 state
1360 .mut_resource::<DeletedEntities>()
1361 .record_deleted_entity(uid, region_key);
1362 } else if sync_me && let Some(pos) = maybe_pos {
1365 warn!(
1368 ?uid,
1369 ?pos,
1370 "Failed to find region containing entity during entity deletion, assuming it \
1371 wasn't sent to any clients and so deletion doesn't need to be recorded for \
1372 sync purposes"
1373 );
1374 }
1375 } else {
1376 error!("Deleting entity without Uid component");
1380 }
1381 }
1382 result
1383}