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