veloren_server/
pet.rs

1use crate::{client::Client, events::shared::update_map_markers};
2use common::{
3    comp::{
4        self, Agent, Alignment, Behavior, BehaviorCapability, Pet, TradingBehavior, anchor::Anchor,
5        group::GroupManager,
6    },
7    uid::Uid,
8};
9use common_net::msg::ServerGeneral;
10use specs::{Entity, Join, WorldExt};
11use tracing::{error, warn};
12
13/// Restores a pet retrieved from the database on login, assigning it to its
14/// owner
15pub fn restore_pet(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet: Pet) {
16    tame_pet_internal(ecs, pet_entity, owner, Some(pet));
17}
18
19/// Tames a pet, adding to the owner's group and setting its alignment
20pub fn tame_pet(ecs: &specs::World, pet_entity: Entity, owner: Entity) {
21    tame_pet_internal(ecs, pet_entity, owner, None);
22}
23
24fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet: Option<Pet>) {
25    let uids = ecs.read_storage::<Uid>();
26    let (owner_uid, pet_uid) = match (uids.get(owner), uids.get(pet_entity)) {
27        (Some(uid_owner), Some(uid_pet)) => (*uid_owner, *uid_pet),
28        _ => return,
29    };
30    let mut alignments = ecs.write_storage::<Alignment>();
31    let Some(owner_alignment) = alignments.get(owner).copied() else {
32        error!("Owner of a pet must have an Alignment");
33        return;
34    };
35
36    if let Some(Alignment::Owned(existing_owner_uid)) = alignments.get(pet_entity) {
37        if *existing_owner_uid != owner_uid {
38            warn!("Disallowing taming of pet already owned by another entity");
39            return;
40        }
41    }
42
43    if let Alignment::Owned(owner_alignment_uid) = owner_alignment {
44        if owner_alignment_uid != owner_uid {
45            error!("Pets cannot be owners of pets");
46            return;
47        }
48    }
49
50    if (
51        &ecs.entities(),
52        &alignments,
53        ecs.read_storage::<Pet>().mask(),
54    )
55        .join()
56        .any(|(_, alignment, _)| matches!(alignment, Alignment::Owned(uid) if *uid == pet_uid))
57    {
58        error!("Cannot tame entity which owns pets");
59        return;
60    }
61
62    let _ = alignments.insert(pet_entity, common::comp::Alignment::Owned(owner_uid));
63
64    // Anchor the pet to the player to prevent it de-spawning
65    // when its chunk is unloaded if its owner is still logged
66    // in
67    let _ = ecs
68        .write_storage()
69        .insert(pet_entity, Anchor::Entity(owner));
70
71    let _ = ecs
72        .write_storage()
73        .insert(pet_entity, pet.unwrap_or_default());
74
75    // Create an agent for this entity using its body
76    if let Some(body) = ecs.read_storage().get(pet_entity) {
77        // Pets can trade with their owner
78        let mut agent = Agent::from_body(body).with_behavior(
79            Behavior::default().maybe_with_capabilities(Some(BehaviorCapability::TRADE)),
80        );
81        agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
82        // Pets shouldn't wander too far from their owner
83        agent.psyche.idle_wander_factor = 0.25;
84        agent.psyche.aggro_range_multiplier = 0.25;
85        agent.patrol_origin = None;
86        let _ = ecs.write_storage().insert(pet_entity, agent);
87    }
88
89    // Add to group system
90    let clients = ecs.read_storage::<Client>();
91    let mut group_manager = ecs.write_resource::<GroupManager>();
92    let map_markers = ecs.read_storage::<comp::MapMarker>();
93
94    drop(alignments);
95    group_manager.new_pet(
96        pet_entity,
97        owner,
98        &mut ecs.write_storage(),
99        &ecs.entities(),
100        &ecs.read_storage(),
101        &uids,
102        &mut |entity, group_change| {
103            clients
104                .get(entity)
105                .and_then(|c| {
106                    group_change
107                        .try_map_ref(|e| uids.get(*e).copied())
108                        .map(|g| (g, c))
109                })
110                .map(|(g, c)| {
111                    // Might be unneccessary, but maybe pets can somehow have map
112                    // markers in the future
113                    update_map_markers(&map_markers, &uids, c, &group_change);
114                    c.send_fallible(ServerGeneral::GroupUpdate(g));
115                });
116        },
117    );
118}