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