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