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