mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
address review comments
This commit is contained in:
parent
51152f47fa
commit
d58f7f0931
@ -14,7 +14,7 @@
|
||||
]), None)),
|
||||
)),
|
||||
),
|
||||
pets: [("common.entity.dungeon.cultist.hound", 4, 5)],
|
||||
pets: [("common.entity.dungeon.cultist.hound", ( start: 4, end: 5 ))],
|
||||
meta: [
|
||||
SkillSetAsset("common.skillset.preset.rank5.fullskill"),
|
||||
],
|
||||
|
@ -14,7 +14,7 @@ use std::{collections::VecDeque, fmt};
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use vek::*;
|
||||
|
||||
use super::{dialogue::Subject, Pos};
|
||||
use super::{dialogue::Subject, Group, Pos};
|
||||
|
||||
pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
|
||||
pub const TRADE_INTERACTION_TIME: f32 = 300.0;
|
||||
@ -112,6 +112,18 @@ impl Alignment {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Default group for this alignment
|
||||
// NOTE: This is a HACK
|
||||
pub fn group(&self) -> Option<Group> {
|
||||
match self {
|
||||
Alignment::Wild => None,
|
||||
Alignment::Passive => None,
|
||||
Alignment::Enemy => Some(super::group::ENEMY),
|
||||
Alignment::Npc | Alignment::Tame => Some(super::group::NPC),
|
||||
Alignment::Owned(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Alignment {
|
||||
|
@ -3,6 +3,7 @@ use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slab::Slab;
|
||||
use specs::{storage::GenericReadStorage, Component, DerefFlaggedStorage, Join, LendJoin};
|
||||
use std::iter;
|
||||
use tracing::{error, warn};
|
||||
|
||||
// Primitive group system
|
||||
@ -309,19 +310,6 @@ impl GroupManager {
|
||||
return;
|
||||
}
|
||||
self.remove_from_group(member, groups, alignments, uids, entities, notifier, false);
|
||||
|
||||
// Set NPC back to their group
|
||||
if let Some(alignment) = alignments.get(member) {
|
||||
match alignment {
|
||||
Alignment::Npc => {
|
||||
let _ = groups.insert(member, NPC);
|
||||
},
|
||||
Alignment::Enemy => {
|
||||
let _ = groups.insert(member, ENEMY);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entity_deleted(
|
||||
@ -367,7 +355,7 @@ impl GroupManager {
|
||||
.join()
|
||||
.filter(|(e, _, g, _)| **g == group && !(to_be_deleted && *e == member))
|
||||
.fold(
|
||||
HashMap::<Uid, (Option<specs::Entity>, Vec<specs::Entity>)>::new(),
|
||||
HashMap::<Uid, (Option<specs::Entity>, Option<Alignment>, Vec<specs::Entity>)>::new(),
|
||||
|mut acc, (e, uid, _, alignment)| {
|
||||
if let Some(owner) = alignment.and_then(|a| match a {
|
||||
Alignment::Owned(owner) if uid != owner => Some(owner),
|
||||
@ -375,10 +363,14 @@ impl GroupManager {
|
||||
}) {
|
||||
// A pet
|
||||
// Assumes owner will be in the group
|
||||
acc.entry(*owner).or_default().1.push(e);
|
||||
acc.entry(*owner).or_default().2.push(e);
|
||||
} else {
|
||||
// Not a pet
|
||||
acc.entry(*uid).or_default().0 = Some(e);
|
||||
//
|
||||
// Entry may already exist from inserting pets
|
||||
let entry = acc.entry(*uid).or_default();
|
||||
entry.0 = Some(e);
|
||||
entry.1 = alignment.copied();
|
||||
}
|
||||
|
||||
acc
|
||||
@ -386,9 +378,15 @@ impl GroupManager {
|
||||
)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.for_each(|(owner, pets)| {
|
||||
.for_each(|(owner, alignment, pets)| {
|
||||
if let Some(owner) = owner {
|
||||
if !pets.is_empty() {
|
||||
if let Some(special_group) = alignment.and_then(|a| a.group()) {
|
||||
// Set NPC and pets back to their special alignment based group
|
||||
for entity in iter::once(owner).chain(pets) {
|
||||
groups.insert(entity, special_group)
|
||||
.expect("entity from join above so it must exist");
|
||||
}
|
||||
} else if !pets.is_empty() {
|
||||
let mut members =
|
||||
pets.iter().map(|e| (*e, Role::Pet)).collect::<Vec<_>>();
|
||||
members.push((owner, Role::Member));
|
||||
@ -415,8 +413,8 @@ impl GroupManager {
|
||||
});
|
||||
}
|
||||
});
|
||||
// Not leader
|
||||
} else {
|
||||
// Not leader
|
||||
let leaving_member_uid = if let Some(uid) = uids.get(member) {
|
||||
*uid
|
||||
} else {
|
||||
@ -426,8 +424,24 @@ impl GroupManager {
|
||||
|
||||
let leaving_pets = pets(member, leaving_member_uid, alignments, entities);
|
||||
|
||||
// If pets and not about to be deleted form new group
|
||||
if !leaving_pets.is_empty() && !to_be_deleted {
|
||||
// Handle updates for the leaving group member and their pets.
|
||||
if let Some(special_group) = alignments.get(member).and_then(|a| a.group())
|
||||
&& !to_be_deleted
|
||||
{
|
||||
// Set NPC and pets back to their the special alignment based group
|
||||
for entity in iter::once(member).chain(leaving_pets.iter().copied()) {
|
||||
groups
|
||||
.insert(entity, special_group)
|
||||
.expect("entity from join above so it must exist");
|
||||
}
|
||||
// Form new group if these conditions are met:
|
||||
// * Alignment not for a special npc group (handled above)
|
||||
// * The entity has pets.
|
||||
// * The entity isn't about to be deleted.
|
||||
} else if !leaving_pets.is_empty()
|
||||
&& !to_be_deleted
|
||||
&& alignments.get(member).and_then(Alignment::group).is_none()
|
||||
{
|
||||
let new_group = self.create_group(member, 1);
|
||||
|
||||
notifier(member, ChangeNotification::NewGroup {
|
||||
@ -435,7 +449,7 @@ impl GroupManager {
|
||||
members: leaving_pets
|
||||
.iter()
|
||||
.map(|p| (*p, Role::Pet))
|
||||
.chain(std::iter::once((member, Role::Member)))
|
||||
.chain(iter::once((member, Role::Member)))
|
||||
.collect(),
|
||||
});
|
||||
|
||||
@ -451,6 +465,8 @@ impl GroupManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Handle updates for the rest of the group, potentially disbanding it if there
|
||||
// is now only one member left.
|
||||
if let Some(info) = self.group_info_mut(group) {
|
||||
// If not pet, decrement number of members
|
||||
if !matches!(alignments.get(member), Some(Alignment::Owned(owner)) if uids.get(member).map_or(true, |uid| uid != owner))
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use crate::{
|
||||
assets::{self, AssetExt, Error},
|
||||
calendar::Calendar,
|
||||
@ -15,6 +17,7 @@ use crate::{
|
||||
};
|
||||
use enum_map::EnumMap;
|
||||
use serde::Deserialize;
|
||||
use tracing::error;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||
@ -136,9 +139,10 @@ pub struct EntityConfig {
|
||||
/// Check docs for `InventorySpec` struct in this file.
|
||||
pub inventory: InventorySpec,
|
||||
|
||||
/// Pets to spawn with this entity (specified as a list of asset paths)
|
||||
/// Pets to spawn with this entity (specified as a list of asset paths).
|
||||
/// The range represents how many pets will be spawned.
|
||||
#[serde(default)]
|
||||
pub pets: Vec<(String, usize, usize)>,
|
||||
pub pets: Vec<(String, RangeInclusive<usize>)>,
|
||||
|
||||
/// Meta Info for optional fields
|
||||
/// Possible fields:
|
||||
@ -320,18 +324,25 @@ impl EntityInfo {
|
||||
// NOTE: set loadout after body, as it's used with default equipement
|
||||
self = self.with_inventory(inventory, config_asset, loadout_rng, time);
|
||||
|
||||
for (pet_asset, start, end) in pets {
|
||||
let config = EntityConfig::load_expect_cloned(&pet_asset);
|
||||
self.pets
|
||||
.extend((0..loadout_rng.gen_range(start..=end)).map(|_| {
|
||||
EntityInfo::at(self.pos).with_entity_config(
|
||||
config.clone(),
|
||||
config_asset,
|
||||
loadout_rng,
|
||||
time,
|
||||
)
|
||||
}));
|
||||
let mut pet_infos: Vec<EntityInfo> = Vec::new();
|
||||
for (pet_asset, amount) in pets {
|
||||
let config = EntityConfig::load_expect(&pet_asset).read();
|
||||
let (start, mut end) = amount.into_inner();
|
||||
if start > end {
|
||||
error!("Invalid range for pet count start: {start}, end: {end}");
|
||||
end = start;
|
||||
}
|
||||
|
||||
pet_infos.extend((0..loadout_rng.gen_range(start..=end)).map(|_| {
|
||||
EntityInfo::at(self.pos).with_entity_config(
|
||||
config.clone(),
|
||||
config_asset,
|
||||
loadout_rng,
|
||||
time,
|
||||
)
|
||||
}));
|
||||
}
|
||||
self.pets = pet_infos;
|
||||
|
||||
// Prefer the new configuration, if possible
|
||||
let AgentConfig {
|
||||
@ -693,12 +704,22 @@ mod tests {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_pets(pets: Vec<(String, usize, usize)>, config_asset: &str) {
|
||||
for pet in pets.into_iter().map(|(pet_asset, _, _)| {
|
||||
EntityConfig::load_cloned(&pet_asset).unwrap_or_else(|_| {
|
||||
panic!("Pet asset path invalid: \"{pet_asset}\", in {config_asset}")
|
||||
})
|
||||
fn validate_pets(pets: Vec<(String, RangeInclusive<usize>)>, config_asset: &str) {
|
||||
for (pet, amount) in pets.into_iter().map(|(pet_asset, amount)| {
|
||||
(
|
||||
EntityConfig::load_cloned(&pet_asset).unwrap_or_else(|_| {
|
||||
panic!("Pet asset path invalid: \"{pet_asset}\", in {config_asset}")
|
||||
}),
|
||||
amount,
|
||||
)
|
||||
}) {
|
||||
assert!(
|
||||
amount.end() >= amount.start(),
|
||||
"Invalid pet spawn range ({}..={}), in {}",
|
||||
amount.start(),
|
||||
amount.end(),
|
||||
config_asset
|
||||
);
|
||||
if !pet.pets.is_empty() {
|
||||
panic!("Pets must not be owners of pets: {config_asset}");
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
server_description::ServerDescription, Ban, BanAction, BanInfo, EditableSetting,
|
||||
SettingError, WhitelistInfo, WhitelistRecord,
|
||||
},
|
||||
sys::terrain::NpcData,
|
||||
sys::terrain::SpawnEntityData,
|
||||
weather::WeatherJob,
|
||||
wiring,
|
||||
wiring::OutputFormula,
|
||||
@ -726,17 +726,15 @@ fn handle_make_npc(
|
||||
None,
|
||||
);
|
||||
|
||||
match NpcData::from_entity_info(entity_info) {
|
||||
NpcData::Waypoint(_) => {
|
||||
match SpawnEntityData::from_entity_info(entity_info) {
|
||||
SpawnEntityData::Waypoint(_) => {
|
||||
return Err(Content::localized("command-unimplemented-waypoint-spawn"));
|
||||
},
|
||||
NpcData::Teleporter(_, _) => {
|
||||
SpawnEntityData::Teleporter(_, _) => {
|
||||
return Err(Content::localized("command-unimplemented-teleporter-spawn"));
|
||||
},
|
||||
data @ NpcData::Data { .. } => {
|
||||
let (npc_builder, _pos) = data
|
||||
.to_npc_builder()
|
||||
.expect("We know this NpcData is valid");
|
||||
SpawnEntityData::Npc(data) => {
|
||||
let (npc_builder, _pos) = data.to_npc_builder();
|
||||
|
||||
server
|
||||
.state
|
||||
@ -1735,13 +1733,7 @@ fn handle_spawn(
|
||||
owner_entity: target,
|
||||
pet_entity: new_entity,
|
||||
});
|
||||
} else if let Some(group) = match alignment {
|
||||
Alignment::Wild => None,
|
||||
Alignment::Passive => None,
|
||||
Alignment::Enemy => Some(comp::group::ENEMY),
|
||||
Alignment::Npc | Alignment::Tame => Some(comp::group::NPC),
|
||||
comp::Alignment::Owned(_) => unreachable!(),
|
||||
} {
|
||||
} else if let Some(group) = alignment.group() {
|
||||
insert_or_replace_component(server, new_entity, group, "new entity")?;
|
||||
}
|
||||
|
||||
|
@ -198,13 +198,7 @@ pub fn handle_create_npc(server: &mut Server, mut ev: CreateNpcEvent) -> EcsEnti
|
||||
},
|
||||
);
|
||||
}
|
||||
} else if let Some(group) = match ev.npc.alignment {
|
||||
Alignment::Wild => None,
|
||||
Alignment::Passive => None,
|
||||
Alignment::Enemy => Some(comp::group::ENEMY),
|
||||
Alignment::Npc | Alignment::Tame => Some(comp::group::NPC),
|
||||
comp::Alignment::Owned(_) => unreachable!(),
|
||||
} {
|
||||
} else if let Some(group) = ev.npc.alignment.group() {
|
||||
let _ = server.state.ecs().write_storage().insert(new_entity, group);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ use crate::{
|
||||
pet::tame_pet,
|
||||
rtsim::RtSim,
|
||||
state_ext::StateExt,
|
||||
sys::terrain::{NpcData, SAFE_ZONE_RADIUS},
|
||||
sys::terrain::{NpcData, SpawnEntityData, SAFE_ZONE_RADIUS},
|
||||
Server, Settings, SpawnPoint,
|
||||
};
|
||||
use common::{
|
||||
@ -2143,8 +2143,8 @@ pub fn transform_entity(
|
||||
.read_storage::<comp::Player>()
|
||||
.contains(entity);
|
||||
|
||||
match NpcData::from_entity_info(entity_info) {
|
||||
NpcData::Data {
|
||||
match SpawnEntityData::from_entity_info(entity_info) {
|
||||
SpawnEntityData::Npc(NpcData {
|
||||
inventory,
|
||||
stats,
|
||||
skill_set,
|
||||
@ -2157,7 +2157,7 @@ pub fn transform_entity(
|
||||
alignment: _,
|
||||
pos: _,
|
||||
pets,
|
||||
} => {
|
||||
}) => {
|
||||
fn set_or_remove_component<C: specs::Component>(
|
||||
server: &mut Server,
|
||||
entity: EcsEntity,
|
||||
@ -2254,9 +2254,10 @@ pub fn transform_entity(
|
||||
// Spawn pets
|
||||
let position = server.state.read_component_copied::<comp::Pos>(entity);
|
||||
if let Some(pos) = position {
|
||||
for (pet, offset) in pets.into_iter().filter_map(|(pet, offset)| {
|
||||
pet.to_npc_builder().map(|(pet, _)| (pet, offset)).ok()
|
||||
}) {
|
||||
for (pet, offset) in pets
|
||||
.into_iter()
|
||||
.map(|(pet, offset)| (pet.to_npc_builder().0, offset))
|
||||
{
|
||||
let pet_entity = handle_create_npc(server, CreateNpcEvent {
|
||||
pos: comp::Pos(pos.0 + offset),
|
||||
ori: comp::Ori::from_unnormalized_vec(offset).unwrap_or_default(),
|
||||
@ -2268,10 +2269,10 @@ pub fn transform_entity(
|
||||
}
|
||||
}
|
||||
},
|
||||
NpcData::Waypoint(_) => {
|
||||
SpawnEntityData::Waypoint(_) => {
|
||||
return Err(TransformEntityError::UnexpectedNpcWaypoint);
|
||||
},
|
||||
NpcData::Teleporter(_, _) => {
|
||||
SpawnEntityData::Teleporter(_, _) => {
|
||||
return Err(TransformEntityError::UnexpectedNpcTeleporter);
|
||||
},
|
||||
}
|
||||
|
@ -105,6 +105,7 @@ pub struct InventoryManipData<'a> {
|
||||
orientations: ReadStorage<'a, comp::Ori>,
|
||||
controllers: ReadStorage<'a, comp::Controller>,
|
||||
agents: ReadStorage<'a, comp::Agent>,
|
||||
pets: ReadStorage<'a, comp::Pet>,
|
||||
velocities: ReadStorage<'a, comp::Vel>,
|
||||
}
|
||||
|
||||
@ -520,9 +521,9 @@ impl ServerEvent for InventoryManipEvent {
|
||||
const MAX_PETS: usize = 3;
|
||||
let reinsert = if let Some(pos) = data.positions.get(entity)
|
||||
{
|
||||
if (&data.alignments, &data.agents)
|
||||
if (&data.alignments, &data.agents, data.pets.mask())
|
||||
.join()
|
||||
.filter(|(alignment, _)| {
|
||||
.filter(|(alignment, _, _)| {
|
||||
alignment == &&comp::Alignment::Owned(*uid)
|
||||
})
|
||||
.count()
|
||||
|
@ -838,7 +838,16 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent anchor entity chains which are not currently supported
|
||||
// Prevent anchor entity chains which are not currently supported due to:
|
||||
// * potential cycles?
|
||||
// * unloading a chain could occur across an unbounded number of ticks with the
|
||||
// current implementation.
|
||||
// * in particular, we want to be able to unload all entities in a
|
||||
// limited number of ticks when a database error occurs and kicks all
|
||||
// players (not quiet sure on exact time frame, since it already
|
||||
// takes a tick after unloading all chunks for entities to despawn?),
|
||||
// see this thread and the discussion linked from there:
|
||||
// https://gitlab.com/veloren/veloren/-/merge_requests/2668#note_634913847
|
||||
let anchors = self.state.ecs().read_storage::<Anchor>();
|
||||
let anchored_anchor_entities: Vec<Entity> = (
|
||||
&self.state.ecs().entities(),
|
||||
@ -849,8 +858,13 @@ impl Server {
|
||||
Anchor::Entity(anchor_entity) => Some(*anchor_entity),
|
||||
_ => None,
|
||||
})
|
||||
// We allow Anchor::Entity(_) -> Anchor::Chunk(_) chains
|
||||
.filter(|anchor_entity| matches!(anchors.get(*anchor_entity), Some(Anchor::Entity(_))))
|
||||
// We allow Anchor::Entity(_) -> Anchor::Chunk(_) connections, since they can't chain further.
|
||||
//
|
||||
// NOTE: The entity with `Anchor::Entity` will unload one tick after the entity with `Anchor::Chunk`.
|
||||
.filter(|anchor_entity| match anchors.get(*anchor_entity) {
|
||||
Some(Anchor::Entity(_)) => true,
|
||||
Some(Anchor::Chunk(_)) | None => false
|
||||
})
|
||||
.collect();
|
||||
drop(anchors);
|
||||
|
||||
|
@ -7,8 +7,8 @@ use common::{
|
||||
uid::Uid,
|
||||
};
|
||||
use common_net::msg::ServerGeneral;
|
||||
use specs::{Entity, WorldExt};
|
||||
use tracing::warn;
|
||||
use specs::{Entity, Join, WorldExt};
|
||||
use tracing::{error, warn};
|
||||
|
||||
/// Restores a pet retrieved from the database on login, assigning it to its
|
||||
/// owner
|
||||
@ -23,31 +23,43 @@ pub fn tame_pet(ecs: &specs::World, pet_entity: Entity, owner: Entity) {
|
||||
|
||||
fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet: Option<Pet>) {
|
||||
let uids = ecs.read_storage::<Uid>();
|
||||
let owner_uid = match uids.get(owner) {
|
||||
Some(uid) => *uid,
|
||||
None => return,
|
||||
let (owner_uid, pet_uid) = match (uids.get(owner), uids.get(pet_entity)) {
|
||||
(Some(uid_owner), Some(uid_pet)) => (*uid_owner, *uid_pet),
|
||||
_ => return,
|
||||
};
|
||||
let mut alignments = ecs.write_storage::<Alignment>();
|
||||
let Some(owner_alignment) = alignments.get(owner).copied() else {
|
||||
error!("Owner of a pet must have an Alignment");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(Alignment::Owned(existing_owner_uid)) =
|
||||
ecs.read_storage::<Alignment>().get(pet_entity)
|
||||
{
|
||||
if let Some(Alignment::Owned(existing_owner_uid)) = alignments.get(pet_entity) {
|
||||
if *existing_owner_uid != owner_uid {
|
||||
warn!("Disallowing taming of pet already owned by another entity");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Alignment::Owned(owner_alignment_uid)) = ecs.read_storage::<Alignment>().get(owner)
|
||||
{
|
||||
if *owner_alignment_uid != owner_uid {
|
||||
warn!("Pets cannot be owners of pets");
|
||||
if let Alignment::Owned(owner_alignment_uid) = owner_alignment {
|
||||
if owner_alignment_uid != owner_uid {
|
||||
error!("Pets cannot be owners of pets");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = ecs
|
||||
.write_storage()
|
||||
.insert(pet_entity, common::comp::Alignment::Owned(owner_uid));
|
||||
if (
|
||||
&ecs.entities(),
|
||||
&alignments,
|
||||
ecs.read_storage::<Pet>().mask(),
|
||||
)
|
||||
.join()
|
||||
.any(|(_, alignment, _)| matches!(alignment, Alignment::Owned(uid) if *uid == pet_uid))
|
||||
{
|
||||
error!("Cannot tame entity which owns pets");
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = alignments.insert(pet_entity, common::comp::Alignment::Owned(owner_uid));
|
||||
|
||||
// Anchor the pet to the player to prevent it de-spawning
|
||||
// when its chunk is unloaded if its owner is still logged
|
||||
@ -78,6 +90,8 @@ fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet:
|
||||
let clients = ecs.read_storage::<Client>();
|
||||
let mut group_manager = ecs.write_resource::<GroupManager>();
|
||||
let map_markers = ecs.read_storage::<comp::MapMarker>();
|
||||
|
||||
drop(alignments);
|
||||
group_manager.new_pet(
|
||||
pet_entity,
|
||||
owner,
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![allow(dead_code)] // TODO: Remove this when rtsim is fleshed out
|
||||
|
||||
use super::*;
|
||||
use crate::sys::terrain::NpcData;
|
||||
use crate::sys::terrain::SpawnEntityData;
|
||||
use common::{
|
||||
calendar::Calendar,
|
||||
comp::{self, Body, Presence, PresenceKind},
|
||||
@ -339,9 +339,10 @@ impl<'a> System<'a> for Sys {
|
||||
Some(&calendar_data),
|
||||
);
|
||||
|
||||
let (mut npc_builder, pos) = NpcData::from_entity_info(entity_info)
|
||||
.to_npc_builder()
|
||||
.expect("NpcData must be valid");
|
||||
let (mut npc_builder, pos) = SpawnEntityData::from_entity_info(entity_info)
|
||||
.into_npc_data_inner()
|
||||
.expect("Entity loaded from assets cannot be special")
|
||||
.to_npc_builder();
|
||||
|
||||
if let Some(agent) = &mut npc_builder.agent {
|
||||
agent.rtsim_outbox = Some(Default::default());
|
||||
@ -378,9 +379,13 @@ impl<'a> System<'a> for Sys {
|
||||
Some(&calendar_data),
|
||||
);
|
||||
|
||||
let mut npc_builder = NpcData::from_entity_info(entity_info)
|
||||
let mut npc_builder = SpawnEntityData::from_entity_info(entity_info)
|
||||
.into_npc_data_inner()
|
||||
// EntityConfig can't represent Waypoints at all
|
||||
// as of now, and if someone will try to spawn
|
||||
// rtsim waypoint it is definitely error.
|
||||
.expect("Entity loaded from assets cannot be special")
|
||||
.to_npc_builder()
|
||||
.expect("NpcData must be valid")
|
||||
.0
|
||||
.with_rtsim(RtSimEntity(npc_id));
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
use crate::test_world::{IndexOwned, World};
|
||||
#[cfg(feature = "persistent_world")]
|
||||
use crate::TerrainPersistence;
|
||||
use tracing::error;
|
||||
#[cfg(feature = "worldgen")]
|
||||
use world::{IndexOwned, World};
|
||||
|
||||
@ -204,15 +205,13 @@ impl<'a> System<'a> for Sys {
|
||||
"Chunk spawned entity that wasn't nearby",
|
||||
);
|
||||
|
||||
let data = NpcData::from_entity_info(entity);
|
||||
let data = SpawnEntityData::from_entity_info(entity);
|
||||
match data {
|
||||
NpcData::Waypoint(pos) => {
|
||||
SpawnEntityData::Waypoint(pos) => {
|
||||
emitters.emit(CreateWaypointEvent(pos));
|
||||
},
|
||||
data @ NpcData::Data { .. } => {
|
||||
let (npc_builder, pos) = data
|
||||
.to_npc_builder()
|
||||
.expect("This NpcData is known to be valid");
|
||||
SpawnEntityData::Npc(data) => {
|
||||
let (npc_builder, pos) = data.to_npc_builder();
|
||||
|
||||
emitters.emit(CreateNpcEvent {
|
||||
pos,
|
||||
@ -221,7 +220,7 @@ impl<'a> System<'a> for Sys {
|
||||
rider: None,
|
||||
});
|
||||
},
|
||||
NpcData::Teleporter(pos, teleporter) => {
|
||||
SpawnEntityData::Teleporter(pos, teleporter) => {
|
||||
emitters.emit(CreateTeleporterEvent(pos, teleporter));
|
||||
},
|
||||
}
|
||||
@ -399,32 +398,36 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: better name
|
||||
#[derive(Debug)]
|
||||
pub struct NpcData {
|
||||
pub pos: Pos,
|
||||
pub stats: comp::Stats,
|
||||
pub skill_set: comp::SkillSet,
|
||||
pub health: Option<comp::Health>,
|
||||
pub poise: comp::Poise,
|
||||
pub inventory: comp::inventory::Inventory,
|
||||
pub agent: Option<comp::Agent>,
|
||||
pub body: comp::Body,
|
||||
pub alignment: comp::Alignment,
|
||||
pub scale: comp::Scale,
|
||||
pub loot: LootSpec<String>,
|
||||
pub pets: Vec<(NpcData, Vec3<f32>)>,
|
||||
}
|
||||
|
||||
/// Convinient structure to use when you need to create new npc
|
||||
/// from EntityInfo
|
||||
// TODO: better name?
|
||||
// TODO: if this is send around network, optimize the large_enum_variant
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum NpcData {
|
||||
Data {
|
||||
pos: Pos,
|
||||
stats: comp::Stats,
|
||||
skill_set: comp::SkillSet,
|
||||
health: Option<comp::Health>,
|
||||
poise: comp::Poise,
|
||||
inventory: comp::inventory::Inventory,
|
||||
agent: Option<comp::Agent>,
|
||||
body: comp::Body,
|
||||
alignment: comp::Alignment,
|
||||
scale: comp::Scale,
|
||||
loot: LootSpec<String>,
|
||||
pets: Vec<(NpcData, Vec3<f32>)>,
|
||||
},
|
||||
pub enum SpawnEntityData {
|
||||
Npc(NpcData),
|
||||
Waypoint(Vec3<f32>),
|
||||
Teleporter(Vec3<f32>, PortalData),
|
||||
}
|
||||
|
||||
impl NpcData {
|
||||
impl SpawnEntityData {
|
||||
pub fn from_entity_info(entity: EntityInfo) -> Self {
|
||||
let EntityInfo {
|
||||
// flags
|
||||
@ -447,7 +450,6 @@ impl NpcData {
|
||||
inventory: items,
|
||||
make_loadout,
|
||||
trading_information: economy,
|
||||
// unused
|
||||
pets,
|
||||
} = entity;
|
||||
|
||||
@ -548,7 +550,7 @@ impl NpcData {
|
||||
agent
|
||||
};
|
||||
|
||||
NpcData::Data {
|
||||
SpawnEntityData::Npc(NpcData {
|
||||
pos: Pos(pos),
|
||||
stats,
|
||||
skill_set,
|
||||
@ -564,57 +566,69 @@ impl NpcData {
|
||||
let pet_count = pets.len() as f32;
|
||||
pets.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, pet)| {
|
||||
(
|
||||
NpcData::from_entity_info(pet),
|
||||
.flat_map(|(i, pet)| {
|
||||
Some((
|
||||
SpawnEntityData::from_entity_info(pet)
|
||||
.into_npc_data_inner()
|
||||
.inspect_err(|data| {
|
||||
error!("Pets must be SpawnEntityData::Npc, but found: {data:?}")
|
||||
})
|
||||
.ok()?,
|
||||
Vec2::one()
|
||||
.rotated_z(TAU * (i as f32 / pet_count))
|
||||
.with_z(0.0)
|
||||
* ((pet_count * 3.0) / TAU),
|
||||
)
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn to_npc_builder(self) -> Result<(NpcBuilder, comp::Pos), Self> {
|
||||
pub fn into_npc_data_inner(self) -> Result<NpcData, Self> {
|
||||
match self {
|
||||
NpcData::Data {
|
||||
pos,
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
inventory,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale,
|
||||
loot,
|
||||
pets,
|
||||
} => Ok((
|
||||
NpcBuilder::new(stats, body, alignment)
|
||||
.with_skill_set(skill_set)
|
||||
.with_health(health)
|
||||
.with_poise(poise)
|
||||
.with_inventory(inventory)
|
||||
.with_agent(agent)
|
||||
.with_scale(scale)
|
||||
.with_loot(loot)
|
||||
.with_pets(
|
||||
pets.into_iter()
|
||||
.map(|(pet, offset)| pet.to_npc_builder().map(|(pet, _)| (pet, offset)))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
pos,
|
||||
)),
|
||||
NpcData::Waypoint(_) | NpcData::Teleporter(_, _) => Err(self),
|
||||
SpawnEntityData::Npc(inner) => Ok(inner),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NpcData {
|
||||
pub fn to_npc_builder(self) -> (NpcBuilder, comp::Pos) {
|
||||
let NpcData {
|
||||
pos,
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
inventory,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale,
|
||||
loot,
|
||||
pets,
|
||||
} = self;
|
||||
|
||||
(
|
||||
NpcBuilder::new(stats, body, alignment)
|
||||
.with_skill_set(skill_set)
|
||||
.with_health(health)
|
||||
.with_poise(poise)
|
||||
.with_inventory(inventory)
|
||||
.with_agent(agent)
|
||||
.with_scale(scale)
|
||||
.with_loot(loot)
|
||||
.with_pets(
|
||||
pets.into_iter()
|
||||
.map(|(pet, offset)| (pet.to_npc_builder().0, offset))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
pos,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_to_loaded_vd(vd: u32, max_view_distance: u32) -> i32 {
|
||||
// Hardcoded max VD to prevent stupid view distances from creating overflows.
|
||||
// This must be a value ≤
|
||||
|
Loading…
Reference in New Issue
Block a user