allow entity -> chunk anchor chains

This commit is contained in:
crabman 2024-02-28 23:25:36 +00:00
parent 40fed95760
commit 504e45ebdb
No known key found for this signature in database
8 changed files with 66 additions and 96 deletions

View File

@ -8,6 +8,6 @@
loadout: FromBody,
),
// Added for testing
pets: ["common.entity.wild.peaceful.crab"],
pets: [("common.entity.wild.peaceful.crab", 5, 10)],
meta: [],
)

View File

@ -59,7 +59,7 @@ pub struct NpcBuilder {
pub scale: comp::Scale,
pub anchor: Option<comp::Anchor>,
pub loot: LootSpec<String>,
pub pets: Vec<NpcBuilder>,
pub pets: Vec<(NpcBuilder, Vec3<f32>)>,
pub rtsim_entity: Option<RtSimEntity>,
pub projectile: Option<comp::Projectile>,
}
@ -134,7 +134,7 @@ impl NpcBuilder {
self
}
pub fn with_pets(mut self, pets: Vec<NpcBuilder>) -> Self {
pub fn with_pets(mut self, pets: Vec<(NpcBuilder, Vec3<f32>)>) -> Self {
self.pets = pets;
self
}

View File

@ -138,7 +138,7 @@ pub struct EntityConfig {
/// Pets to spawn with this entity (specified as a list of asset paths)
#[serde(default)]
pub pets: Vec<String>,
pub pets: Vec<(String, usize, usize)>,
/// Meta Info for optional fields
/// Possible fields:
@ -320,17 +320,18 @@ impl EntityInfo {
// NOTE: set loadout after body, as it's used with default equipement
self = self.with_inventory(inventory, config_asset, loadout_rng, time);
self.pets = pets
.into_iter()
.map(|pet_config| {
EntityInfo::at(self.pos).with_entity_config(
EntityConfig::load_expect_cloned(&pet_config),
config_asset,
loadout_rng,
time,
)
})
.collect();
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,
)
}));
}
// Prefer the new configuration, if possible
let AgentConfig {
@ -692,10 +693,11 @@ mod tests {
}
#[cfg(test)]
fn validate_pets(pets: Vec<String>, config_asset: &str) {
for pet in pets.into_iter().map(|pet_asset| {
EntityConfig::load_cloned(&pet_asset)
.expect(&format!("Pet asset path invalid, in {config_asset}"))
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}")
})
}) {
if !pet.pets.is_empty() {
panic!("Pets must not be owners of pets: {config_asset}");

View File

@ -40,8 +40,8 @@ use common::{
depot,
effect::Effect,
event::{
ClientDisconnectEvent, CreateWaypointEvent, EventBus, ExplosionEvent, GroupManipEvent,
InitiateInviteEvent, TamePetEvent,
ClientDisconnectEvent, CreateNpcEvent, CreateWaypointEvent, EventBus, ExplosionEvent,
GroupManipEvent, InitiateInviteEvent, TamePetEvent,
},
generation::{EntityConfig, EntityInfo},
link::Is,
@ -733,63 +733,21 @@ fn handle_make_npc(
NpcData::Teleporter(_, _) => {
return Err(Content::localized("command-unimplemented-teleporter-spawn"));
},
NpcData::Data {
inventory,
pos,
stats,
skill_set,
poise,
health,
body,
agent,
alignment,
scale,
// TODO
pets: _,
loot,
} => {
// Spread about spawned npcs
let vel = Vec3::new(
thread_rng().gen_range(-2.0..3.0),
thread_rng().gen_range(-2.0..3.0),
10.0,
);
data @ NpcData::Data { .. } => {
let (npc_builder, _pos) = data
.to_npc_builder()
.expect("We know this NpcData is valid");
let mut entity_builder = server
server
.state
.create_npc(
pos,
comp::Ori::default(),
stats,
skill_set,
health,
poise,
inventory,
body,
)
.with(alignment)
.with(scale)
.with(comp::Vel(vel));
if let Some(agent) = agent {
entity_builder = entity_builder.with(agent);
}
if let Some(drop_items) = loot.to_items() {
entity_builder = entity_builder.with(comp::ItemDrops(drop_items));
}
// Some would say it's a hack, some would say it's incomplete
// simulation. But this is what we do to avoid PvP between npc.
let npc_group = match alignment {
Alignment::Enemy => Some(comp::group::ENEMY),
Alignment::Npc | Alignment::Tame => Some(comp::group::NPC),
Alignment::Wild | Alignment::Passive | Alignment::Owned(_) => None,
};
if let Some(group) = npc_group {
entity_builder = entity_builder.with(group);
}
entity_builder.build();
.ecs()
.read_resource::<EventBus<CreateNpcEvent>>()
.emit_now(CreateNpcEvent {
pos: comp::Pos(pos),
ori: comp::Ori::default(),
npc: npc_builder,
rider: None,
});
},
};
}

View File

@ -227,10 +227,10 @@ pub fn handle_create_npc(server: &mut Server, mut ev: CreateNpcEvent) -> EcsEnti
.expect("We just created these entities");
}
for pet in ev.npc.pets {
for (pet, offset) in ev.npc.pets {
let pet_entity = handle_create_npc(server, CreateNpcEvent {
pos: ev.pos,
ori: Ori::default(),
pos: comp::Pos(ev.pos.0 + offset),
ori: Ori::from_unnormalized_vec(offset).unwrap_or_default(),
npc: pet,
rider: None,
});

View File

@ -2252,17 +2252,14 @@ pub fn transform_entity(
}
// Spawn pets
let position = server
.state
.ecs()
.read_storage::<comp::Pos>()
.get(entity)
.copied();
let position = server.state.read_component_copied::<comp::Pos>(entity);
if let Some(pos) = position {
for (pet, _pos) in pets.into_iter().filter_map(|pet| pet.to_npc_builder().ok()) {
for (pet, offset) in pets.into_iter().filter_map(|(pet, offset)| {
pet.to_npc_builder().map(|(pet, _)| (pet, offset)).ok()
}) {
let pet_entity = handle_create_npc(server, CreateNpcEvent {
pos,
ori: comp::Ori::default(),
pos: comp::Pos(pos.0 + offset),
ori: comp::Ori::from_unnormalized_vec(offset).unwrap_or_default(),
npc: pet,
rider: None,
});

View File

@ -849,7 +849,8 @@ impl Server {
Anchor::Entity(anchor_entity) => Some(*anchor_entity),
_ => None,
})
.filter(|anchor_entity| anchors.get(*anchor_entity).is_some())
// We allow Anchor::Entity(_) -> Anchor::Chunk(_) chains
.filter(|anchor_entity| matches!(anchors.get(*anchor_entity), Some(Anchor::Entity(_))))
.collect();
drop(anchors);

View File

@ -39,7 +39,7 @@ use specs::{
storage::GenericReadStorage, Entities, Entity, Join, LendJoin, ParJoin, Read, ReadExpect,
ReadStorage, Write, WriteExpect, WriteStorage,
};
use std::sync::Arc;
use std::{f32::consts::TAU, sync::Arc};
use vek::*;
#[cfg(feature = "persistent_world")]
@ -210,7 +210,6 @@ impl<'a> System<'a> for Sys {
emitters.emit(CreateWaypointEvent(pos));
},
data @ NpcData::Data { .. } => {
// TODO: Investigate anchor chains with pets
let (npc_builder, pos) = data
.to_npc_builder()
.expect("This NpcData is known to be valid");
@ -419,7 +418,7 @@ pub enum NpcData {
alignment: comp::Alignment,
scale: comp::Scale,
loot: LootSpec<String>,
pets: Vec<NpcData>,
pets: Vec<(NpcData, Vec3<f32>)>,
},
Waypoint(Vec3<f32>),
Teleporter(Vec3<f32>, PortalData),
@ -561,7 +560,21 @@ impl NpcData {
alignment,
scale: comp::Scale(scale),
loot,
pets: pets.into_iter().map(NpcData::from_entity_info).collect(),
pets: {
let pet_count = pets.len() as f32;
pets.into_iter()
.enumerate()
.map(|(i, pet)| {
(
NpcData::from_entity_info(pet),
Vec2::one()
.rotated_z(TAU * (i as f32 / pet_count))
.with_z(0.0)
* ((pet_count * 3.0) / TAU),
)
})
.collect()
},
}
}
@ -592,13 +605,12 @@ impl NpcData {
.with_loot(loot)
.with_pets(
pets.into_iter()
.map(|pet_data| pet_data.to_npc_builder().map(|(npc, _pos)| npc))
.map(|(pet, offset)| pet.to_npc_builder().map(|(pet, _)| (pet, offset)))
.collect::<Result<Vec<_>, _>>()?,
),
pos,
)),
NpcData::Waypoint(_) => Err(self),
NpcData::Teleporter(_, _) => Err(self),
NpcData::Waypoint(_) | NpcData::Teleporter(_, _) => Err(self),
}
}
}