veloren_server/events/
entity_creation.rs

1use crate::{
2    CharacterUpdater, Server, StateExt, client::Client, events::player::handle_exit_ingame,
3    persistence::PersistedComponents, pet::tame_pet, presence::RepositionOnChunkLoad, sys,
4};
5use common::{
6    comp::{
7        self, Alignment, BehaviorCapability, ItemDrops, LightEmitter, Ori, Pos, TradingBehavior,
8        Vel, WaypointArea,
9        aura::{Aura, AuraKind, AuraTarget},
10        buff::{BuffCategory, BuffData, BuffKind, BuffSource},
11        ship::figuredata::VOXEL_COLLIDER_MANIFEST,
12    },
13    event::{
14        CreateAuraEntityEvent, CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent,
15        CreateShipEvent, CreateSpecialEntityEvent, EventBus, InitializeCharacterEvent,
16        InitializeSpectatorEvent, NpcBuilder, ShockwaveEvent, ShootEvent, UpdateCharacterDataEvent,
17    },
18    generation::SpecialEntity,
19    mounting::{Mounting, Volume, VolumeMounting, VolumePos},
20    outcome::Outcome,
21    resources::{Secs, Time},
22    uid::{IdMaps, Uid},
23    vol::IntoFullVolIterator,
24};
25use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
26use specs::{Builder, Entity as EcsEntity, WorldExt};
27use std::time::Duration;
28use vek::{Rgb, Vec3};
29
30use super::group_manip::update_map_markers;
31
32pub fn handle_initialize_character(server: &mut Server, ev: InitializeCharacterEvent) {
33    let updater = server.state.ecs().fetch::<CharacterUpdater>();
34    let pending_database_action = updater.has_pending_database_action(ev.character_id);
35    drop(updater);
36
37    if !pending_database_action {
38        let clamped_vds = ev
39            .requested_view_distances
40            .clamp(server.settings().max_view_distance);
41        server
42            .state
43            .initialize_character_data(ev.entity, ev.character_id, clamped_vds);
44        // Correct client if its requested VD is too high.
45        if ev.requested_view_distances.terrain != clamped_vds.terrain {
46            server.notify_client(
47                ev.entity,
48                ServerGeneral::SetViewDistance(clamped_vds.terrain),
49            );
50        }
51    } else {
52        // A character delete or update was somehow initiated after the login commenced,
53        // so kick the client out of "ingame" without saving any data and abort
54        // the character loading process.
55        handle_exit_ingame(server, ev.entity, true);
56    }
57}
58
59pub fn handle_initialize_spectator(server: &mut Server, ev: InitializeSpectatorEvent) {
60    let clamped_vds = ev.1.clamp(server.settings().max_view_distance);
61    server.state.initialize_spectator_data(ev.0, clamped_vds);
62    // Correct client if its requested VD is too high.
63    if ev.1.terrain != clamped_vds.terrain {
64        server.notify_client(ev.0, ServerGeneral::SetViewDistance(clamped_vds.terrain));
65    }
66    sys::subscription::initialize_region_subscription(server.state.ecs(), ev.0);
67}
68
69pub fn handle_loaded_character_data(server: &mut Server, ev: UpdateCharacterDataEvent) {
70    let loaded_components = PersistedComponents {
71        body: ev.components.0,
72        hardcore: ev.components.1,
73        stats: ev.components.2,
74        skill_set: ev.components.3,
75        inventory: ev.components.4,
76        waypoint: ev.components.5,
77        pets: ev.components.6,
78        active_abilities: ev.components.7,
79        map_marker: ev.components.8,
80    };
81    if let Some(marker) = loaded_components.map_marker {
82        server.notify_client(
83            ev.entity,
84            ServerGeneral::MapMarker(comp::MapMarkerUpdate::Owned(comp::MapMarkerChange::Update(
85                marker.0,
86            ))),
87        );
88    }
89
90    let result_msg = if let Err(err) = server
91        .state
92        .update_character_data(ev.entity, loaded_components)
93    {
94        handle_exit_ingame(server, ev.entity, false); // remove client from in-game state
95        ServerGeneral::CharacterDataLoadResult(Err(err))
96    } else {
97        sys::subscription::initialize_region_subscription(server.state.ecs(), ev.entity);
98        // We notify the client with the metadata result from the operation.
99        ServerGeneral::CharacterDataLoadResult(Ok(ev.metadata))
100    };
101    server.notify_client(ev.entity, result_msg);
102}
103
104pub fn handle_create_npc(server: &mut Server, ev: CreateNpcEvent) -> EcsEntity {
105    // Destruct the builder to ensure all fields are exhaustive
106    let NpcBuilder {
107        stats,
108        skill_set,
109        health,
110        poise,
111        inventory,
112        body,
113        mut agent,
114        alignment,
115        scale,
116        anchor,
117        loot,
118        pets,
119        rtsim_entity,
120        projectile,
121        heads,
122        death_effects,
123    } = ev.npc;
124    let entity = server
125        .state
126        .create_npc(
127            ev.pos, ev.ori, stats, skill_set, health, poise, inventory, body,
128        )
129        .with(scale)
130        .maybe_with(heads)
131        .maybe_with(death_effects);
132
133    if let Some(agent) = &mut agent {
134        if let Alignment::Owned(_) = &alignment {
135            agent.behavior.allow(BehaviorCapability::TRADE);
136            agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
137        }
138    }
139
140    let entity = entity.with(alignment);
141
142    let entity = if let Some(agent) = agent {
143        entity.with(agent)
144    } else {
145        entity
146    };
147
148    let entity = if let Some(drop_items) = loot.to_items() {
149        entity.with(ItemDrops(drop_items))
150    } else {
151        entity
152    };
153
154    let entity = if let Some(home_chunk) = anchor {
155        entity.with(home_chunk)
156    } else {
157        entity
158    };
159
160    // Rtsim entity added to IdMaps below.
161    let entity = if let Some(rtsim_entity) = rtsim_entity {
162        entity.with(rtsim_entity).with(RepositionOnChunkLoad {
163            needs_ground: false,
164        })
165    } else {
166        entity
167    };
168
169    let entity = if let Some(projectile) = projectile {
170        entity.with(projectile)
171    } else {
172        entity
173    };
174
175    let new_entity = entity.build();
176
177    if let Some(rtsim_entity) = rtsim_entity {
178        server
179            .state()
180            .ecs()
181            .write_resource::<IdMaps>()
182            .add_rtsim(rtsim_entity, new_entity);
183    }
184
185    // Add to group system if a pet
186    if let comp::Alignment::Owned(owner_uid) = alignment {
187        let state = server.state();
188        let uids = state.ecs().read_storage::<Uid>();
189        let clients = state.ecs().read_storage::<Client>();
190        let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
191        if let Some(owner) = state.ecs().entity_from_uid(owner_uid) {
192            let map_markers = state.ecs().read_storage::<comp::MapMarker>();
193            group_manager.new_pet(
194                new_entity,
195                owner,
196                &mut state.ecs().write_storage(),
197                &state.ecs().entities(),
198                &state.ecs().read_storage(),
199                &uids,
200                &mut |entity, group_change| {
201                    clients
202                        .get(entity)
203                        .and_then(|c| {
204                            group_change
205                                .try_map_ref(|e| uids.get(*e).copied())
206                                .map(|g| (g, c))
207                        })
208                        .map(|(g, c)| {
209                            // Might be unnecessary, but maybe pets can somehow have map
210                            // markers in the future
211                            update_map_markers(&map_markers, &uids, c, &group_change);
212                            c.send_fallible(ServerGeneral::GroupUpdate(g));
213                        });
214                },
215            );
216        }
217    } else if let Some(group) = alignment.group() {
218        let _ = server.state.ecs().write_storage().insert(new_entity, group);
219    }
220
221    if let Some(rider) = ev.rider {
222        let rider_entity = handle_create_npc(server, CreateNpcEvent {
223            pos: ev.pos,
224            ori: Ori::default(),
225            npc: rider,
226            rider: None,
227        });
228        let uids = server.state().ecs().read_storage::<Uid>();
229        let link = Mounting {
230            mount: *uids.get(new_entity).expect("We just created this entity"),
231            rider: *uids.get(rider_entity).expect("We just created this entity"),
232        };
233        drop(uids);
234        server
235            .state
236            .link(link)
237            .expect("We just created these entities");
238    }
239
240    for (pet, offset) in pets {
241        let pet_entity = handle_create_npc(server, CreateNpcEvent {
242            pos: comp::Pos(ev.pos.0 + offset),
243            ori: Ori::from_unnormalized_vec(offset).unwrap_or_default(),
244            npc: pet,
245            rider: None,
246        });
247
248        tame_pet(server.state.ecs(), pet_entity, new_entity);
249    }
250
251    new_entity
252}
253
254pub fn handle_create_ship(server: &mut Server, ev: CreateShipEvent) {
255    let collider = ev.ship.make_collider();
256    let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
257
258    // TODO: Find better solution for this, maybe something like a serverside block
259    // of interests.
260    let (mut steering, mut _seats) = {
261        let mut steering = Vec::new();
262        let mut seats = Vec::new();
263
264        for (pos, block) in collider
265            .get_vol(&voxel_colliders_manifest)
266            .iter()
267            .flat_map(|voxel_collider| voxel_collider.volume().full_vol_iter())
268        {
269            match (block.is_controller(), block.is_mountable()) {
270                (true, true) => steering.push((pos, *block)),
271                (false, true) => seats.push((pos, *block)),
272                _ => {},
273            }
274        }
275        (steering.into_iter(), seats.into_iter())
276    };
277
278    let mut entity = server
279        .state
280        .create_ship(ev.pos, ev.ori, ev.ship, |_| collider);
281    /*
282    if let Some(mut agent) = agent {
283        let (kp, ki, kd) = pid_coefficients(&Body::Ship(ship));
284        fn pure_z(sp: Vec3<f32>, pv: Vec3<f32>) -> f32 { (sp - pv).z }
285        agent =
286            agent.with_position_pid_controller(PidController::new(kp, ki, kd, pos.0, 0.0, pure_z));
287        entity = entity.with(agent);
288    }
289    */
290    if let Some(rtsim_vehicle) = ev.rtsim_entity {
291        entity = entity.with(rtsim_vehicle);
292    }
293    let entity = entity.build();
294
295    if let Some(driver) = ev.driver {
296        let npc_entity = handle_create_npc(server, CreateNpcEvent {
297            pos: ev.pos,
298            ori: ev.ori,
299            npc: driver,
300            rider: None,
301        });
302
303        let uids = server.state.ecs().read_storage::<Uid>();
304        let (rider_uid, mount_uid) = uids
305            .get(npc_entity)
306            .copied()
307            .zip(uids.get(entity).copied())
308            .expect("Couldn't get Uid from newly created ship and npc");
309        drop(uids);
310
311        if let Some((steering_pos, steering_block)) = steering.next() {
312            server
313                .state
314                .link(VolumeMounting {
315                    pos: VolumePos {
316                        kind: Volume::Entity(mount_uid),
317                        pos: steering_pos,
318                    },
319                    block: steering_block,
320                    rider: rider_uid,
321                })
322                .expect("Failed to link driver to ship");
323        } else {
324            server
325                .state
326                .link(Mounting {
327                    mount: mount_uid,
328                    rider: rider_uid,
329                })
330                .expect("Failed to link driver to ship");
331        }
332    }
333
334    /*
335    for passenger in ev.passengers {
336        let npc_entity = handle_create_npc(server, CreateNpcEvent {
337            pos: Pos(ev.pos.0 + Vec3::unit_z() * 5.0),
338            ori: ev.ori,
339            npc: passenger,
340            rider: None,
341        });
342        if let Some((rider_pos, rider_block)) = seats.next() {
343            let uids = server.state.ecs().read_storage::<Uid>();
344            let (rider_uid, mount_uid) = uids
345                .get(npc_entity)
346                .copied()
347                .zip(uids.get(entity).copied())
348                .expect("Couldn't get Uid from newly created ship and npc");
349            drop(uids);
350
351            server
352                .state
353                .link(VolumeMounting {
354                    pos: VolumePos {
355                        kind: Volume::Entity(mount_uid),
356                        pos: rider_pos,
357                    },
358                    block: rider_block,
359                    rider: rider_uid,
360                })
361                .expect("Failed to link passanger to ship");
362        }
363    }
364    */
365}
366
367pub fn handle_shoot(server: &mut Server, ev: ShootEvent) {
368    let state = server.state_mut();
369
370    let pos = ev.pos.0;
371
372    let vel = *ev.dir * ev.speed
373        + state
374            .ecs()
375            .read_storage::<Vel>()
376            .get(ev.entity)
377            .map_or(Vec3::zero(), |v| v.0);
378
379    // Add an outcome
380    state
381        .ecs()
382        .read_resource::<EventBus<Outcome>>()
383        .emit_now(Outcome::ProjectileShot {
384            pos,
385            body: ev.body,
386            vel,
387        });
388
389    let mut builder = state.create_projectile(Pos(pos), Vel(vel), ev.body, ev.projectile);
390    if let Some(light) = ev.light {
391        builder = builder.with(light)
392    }
393    if let Some(object) = ev.object {
394        builder = builder.with(object)
395    }
396
397    builder.build();
398}
399
400pub fn handle_shockwave(server: &mut Server, ev: ShockwaveEvent) {
401    let state = server.state_mut();
402    state
403        .create_shockwave(ev.properties, ev.pos, ev.ori)
404        .build();
405}
406
407pub fn handle_create_special_entity(server: &mut Server, ev: CreateSpecialEntityEvent) {
408    let time = server.state.get_time();
409
410    match ev.entity {
411        SpecialEntity::Waypoint => {
412            server
413                .state
414                .create_object(Pos(ev.pos), comp::object::Body::CampfireLit)
415                .with(LightEmitter {
416                    col: Rgb::new(1.0, 0.3, 0.1),
417                    strength: 5.0,
418                    flicker: 1.0,
419                    animated: true,
420                })
421                .with(WaypointArea::default())
422                .with(comp::Immovable)
423                .with(comp::EnteredAuras::default())
424                .with(comp::Auras::new(vec![
425                    Aura::new(
426                        AuraKind::Buff {
427                            kind: BuffKind::CampfireHeal,
428                            data: BuffData::new(0.02, Some(Secs(1.0))),
429                            category: BuffCategory::Natural,
430                            source: BuffSource::World,
431                        },
432                        5.0,
433                        None,
434                        AuraTarget::All,
435                        Time(time),
436                    ),
437                    Aura::new(
438                        AuraKind::Buff {
439                            kind: BuffKind::Burning,
440                            data: BuffData::new(2.0, Some(Secs(10.0))),
441                            category: BuffCategory::Natural,
442                            source: BuffSource::World,
443                        },
444                        0.7,
445                        None,
446                        AuraTarget::All,
447                        Time(time),
448                    ),
449                ]))
450                .build();
451        },
452        SpecialEntity::Teleporter(portal) => {
453            server
454                .state
455                .create_teleporter(comp::Pos(ev.pos), portal)
456                .build();
457        },
458        SpecialEntity::ArenaTotem { range } => {
459            server
460                .state
461                .create_object(Pos(ev.pos), comp::object::Body::GnarlingTotemGreen)
462                .with(comp::Immovable)
463                .with(comp::EnteredAuras::default())
464                .with(comp::Auras::new(vec![
465                    Aura::new(
466                        AuraKind::FriendlyFire,
467                        range,
468                        None,
469                        AuraTarget::All,
470                        Time(time),
471                    ),
472                    Aura::new(AuraKind::ForcePvP, range, None, AuraTarget::All, Time(time)),
473                ]))
474                .build();
475        },
476    }
477}
478
479pub fn handle_create_item_drop(server: &mut Server, ev: CreateItemDropEvent) {
480    server
481        .state
482        .create_item_drop(ev.pos, ev.ori, ev.vel, ev.item, ev.loot_owner);
483}
484
485pub fn handle_create_object(
486    server: &mut Server,
487    CreateObjectEvent {
488        pos,
489        vel,
490        body,
491        object,
492        item,
493        light_emitter,
494        stats,
495    }: CreateObjectEvent,
496) {
497    server
498        .state
499        .create_object(pos, body)
500        .with(vel)
501        .maybe_with(object)
502        .maybe_with(item)
503        .maybe_with(light_emitter)
504        .maybe_with(stats)
505        .build();
506}
507
508pub fn handle_create_aura_entity(server: &mut Server, ev: CreateAuraEntityEvent) {
509    let time = *server.state.ecs().read_resource::<Time>();
510    let mut entity = server
511        .state
512        .ecs_mut()
513        .create_entity_synced()
514        .with(ev.pos)
515        .with(comp::Vel(Vec3::zero()))
516        .with(comp::Ori::default())
517        .with(ev.auras)
518        .with(comp::Alignment::Owned(ev.creator_uid));
519
520    // If a duration is specified, create a projectile component for the entity
521    if let Some(dur) = ev.duration {
522        let object = comp::Object::DeleteAfter {
523            spawned_at: time,
524            timeout: Duration::from_secs_f64(dur.0),
525        };
526        entity = entity.with(object);
527    }
528    entity.build();
529}