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