mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
allow pet definitions in entity configurations
This commit is contained in:
parent
285791817d
commit
40fed95760
@ -7,5 +7,7 @@
|
||||
inventory: (
|
||||
loadout: FromBody,
|
||||
),
|
||||
// Added for testing
|
||||
pets: ["common.entity.wild.peaceful.crab"],
|
||||
meta: [],
|
||||
)
|
||||
)
|
||||
|
@ -32,7 +32,7 @@ impl Component for Object {
|
||||
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PortalData {
|
||||
pub target: Vec3<f32>,
|
||||
pub requires_no_aggro: bool,
|
||||
|
@ -59,6 +59,7 @@ pub struct NpcBuilder {
|
||||
pub scale: comp::Scale,
|
||||
pub anchor: Option<comp::Anchor>,
|
||||
pub loot: LootSpec<String>,
|
||||
pub pets: Vec<NpcBuilder>,
|
||||
pub rtsim_entity: Option<RtSimEntity>,
|
||||
pub projectile: Option<comp::Projectile>,
|
||||
}
|
||||
@ -79,6 +80,7 @@ impl NpcBuilder {
|
||||
loot: LootSpec::Nothing,
|
||||
rtsim_entity: None,
|
||||
projectile: None,
|
||||
pets: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,6 +133,11 @@ impl NpcBuilder {
|
||||
self.loot = loot;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_pets(mut self, pets: Vec<NpcBuilder>) -> Self {
|
||||
self.pets = pets;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientConnectedEvent {
|
||||
|
@ -136,6 +136,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)
|
||||
#[serde(default)]
|
||||
pub pets: Vec<String>,
|
||||
|
||||
/// Meta Info for optional fields
|
||||
/// Possible fields:
|
||||
/// SkillSetAsset(String) with asset_specifier for skillset
|
||||
@ -204,8 +208,7 @@ pub struct EntityInfo {
|
||||
// Skills
|
||||
pub skillset_asset: Option<String>,
|
||||
|
||||
// Not implemented
|
||||
pub pet: Option<Box<EntityInfo>>,
|
||||
pub pets: Vec<EntityInfo>,
|
||||
|
||||
// Economy
|
||||
// we can't use DHashMap, do we want to move that into common?
|
||||
@ -236,7 +239,7 @@ impl EntityInfo {
|
||||
loadout: LoadoutBuilder::empty(),
|
||||
make_loadout: None,
|
||||
skillset_asset: None,
|
||||
pet: None,
|
||||
pets: Vec::new(),
|
||||
trading_information: None,
|
||||
special_entity: None,
|
||||
}
|
||||
@ -279,6 +282,7 @@ impl EntityInfo {
|
||||
inventory,
|
||||
loot,
|
||||
meta,
|
||||
pets,
|
||||
} = config;
|
||||
|
||||
match body {
|
||||
@ -316,6 +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();
|
||||
|
||||
// Prefer the new configuration, if possible
|
||||
let AgentConfig {
|
||||
has_agency,
|
||||
@ -675,6 +691,18 @@ 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}"))
|
||||
}) {
|
||||
if !pet.pets.is_empty() {
|
||||
panic!("Pets must not be owners of pets: {config_asset}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_entity_assets() {
|
||||
// Get list of entity configs, load everything, validate content.
|
||||
@ -689,6 +717,7 @@ mod tests {
|
||||
loot,
|
||||
meta,
|
||||
alignment: _, // can't fail if serialized, it's a boring enum
|
||||
pets,
|
||||
} = EntityConfig::from_asset_expect_owned(&config_asset);
|
||||
|
||||
validate_body(&body, &config_asset);
|
||||
@ -698,6 +727,7 @@ mod tests {
|
||||
// misc
|
||||
validate_loot(loot, &config_asset);
|
||||
validate_meta(meta, &config_asset);
|
||||
validate_pets(pets, &config_asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -744,6 +744,8 @@ fn handle_make_npc(
|
||||
agent,
|
||||
alignment,
|
||||
scale,
|
||||
// TODO
|
||||
pets: _,
|
||||
loot,
|
||||
} => {
|
||||
// Spread about spawned npcs
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
client::Client, events::player::handle_exit_ingame, persistence::PersistedComponents,
|
||||
presence::RepositionOnChunkLoad, sys, CharacterUpdater, Server, StateExt,
|
||||
pet::tame_pet, presence::RepositionOnChunkLoad, sys, CharacterUpdater, Server, StateExt,
|
||||
};
|
||||
use common::{
|
||||
comp::{
|
||||
@ -227,6 +227,17 @@ pub fn handle_create_npc(server: &mut Server, mut ev: CreateNpcEvent) -> EcsEnti
|
||||
.expect("We just created these entities");
|
||||
}
|
||||
|
||||
for pet in ev.npc.pets {
|
||||
let pet_entity = handle_create_npc(server, CreateNpcEvent {
|
||||
pos: ev.pos,
|
||||
ori: Ori::default(),
|
||||
npc: pet,
|
||||
rider: None,
|
||||
});
|
||||
|
||||
tame_pet(server.state.ecs(), pet_entity, new_entity);
|
||||
}
|
||||
|
||||
new_entity
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ use crate::{
|
||||
BuffKind, BuffSource, PhysicsState,
|
||||
},
|
||||
error,
|
||||
events::entity_creation::handle_create_npc,
|
||||
pet::tame_pet,
|
||||
rtsim::RtSim,
|
||||
state_ext::StateExt,
|
||||
sys::terrain::{NpcData, SAFE_ZONE_RADIUS},
|
||||
@ -28,12 +30,12 @@ use common::{
|
||||
consts::TELEPORTER_RADIUS,
|
||||
event::{
|
||||
AuraEvent, BonkEvent, BuffEvent, ChangeAbilityEvent, ChangeBodyEvent, ChangeStanceEvent,
|
||||
ChatEvent, ComboChangeEvent, CreateItemDropEvent, CreateObjectEvent, DeleteEvent,
|
||||
DestroyEvent, EmitExt, Emitter, EnergyChangeEvent, EntityAttackedHookEvent, EventBus,
|
||||
ExplosionEvent, HealthChangeEvent, KnockbackEvent, LandOnGroundEvent, MakeAdminEvent,
|
||||
ParryHookEvent, PoiseChangeEvent, RemoveLightEmitterEvent, RespawnEvent, SoundEvent,
|
||||
StartTeleportingEvent, TeleportToEvent, TeleportToPositionEvent, TransformEvent,
|
||||
UpdateMapMarkerEvent,
|
||||
ChatEvent, ComboChangeEvent, CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent,
|
||||
DeleteEvent, DestroyEvent, EmitExt, Emitter, EnergyChangeEvent, EntityAttackedHookEvent,
|
||||
EventBus, ExplosionEvent, HealthChangeEvent, KnockbackEvent, LandOnGroundEvent,
|
||||
MakeAdminEvent, ParryHookEvent, PoiseChangeEvent, RemoveLightEmitterEvent, RespawnEvent,
|
||||
SoundEvent, StartTeleportingEvent, TeleportToEvent, TeleportToPositionEvent,
|
||||
TransformEvent, UpdateMapMarkerEvent,
|
||||
},
|
||||
event_emitters,
|
||||
generation::EntityInfo,
|
||||
@ -2154,6 +2156,7 @@ pub fn transform_entity(
|
||||
loot,
|
||||
alignment: _,
|
||||
pos: _,
|
||||
pets,
|
||||
} => {
|
||||
fn set_or_remove_component<C: specs::Component>(
|
||||
server: &mut Server,
|
||||
@ -2247,6 +2250,26 @@ pub fn transform_entity(
|
||||
set_or_remove_component(server, entity, agent)?;
|
||||
set_or_remove_component(server, entity, loot.to_items().map(comp::ItemDrops))?;
|
||||
}
|
||||
|
||||
// Spawn pets
|
||||
let position = server
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Pos>()
|
||||
.get(entity)
|
||||
.copied();
|
||||
if let Some(pos) = position {
|
||||
for (pet, _pos) in pets.into_iter().filter_map(|pet| pet.to_npc_builder().ok()) {
|
||||
let pet_entity = handle_create_npc(server, CreateNpcEvent {
|
||||
pos,
|
||||
ori: comp::Ori::default(),
|
||||
npc: pet,
|
||||
rider: None,
|
||||
});
|
||||
|
||||
tame_pet(server.state.ecs(), pet_entity, entity);
|
||||
}
|
||||
}
|
||||
},
|
||||
NpcData::Waypoint(_) => {
|
||||
return Err(TransformEntityError::UnexpectedNpcWaypoint);
|
||||
|
@ -37,6 +37,14 @@ fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet:
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = ecs
|
||||
.write_storage()
|
||||
.insert(pet_entity, common::comp::Alignment::Owned(owner_uid));
|
||||
|
@ -4,7 +4,7 @@ use super::*;
|
||||
use crate::sys::terrain::NpcData;
|
||||
use common::{
|
||||
calendar::Calendar,
|
||||
comp::{self, Agent, Body, Presence, PresenceKind},
|
||||
comp::{self, Body, Presence, PresenceKind},
|
||||
event::{CreateNpcEvent, CreateShipEvent, DeleteEvent, EventBus, NpcBuilder},
|
||||
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
||||
resources::{DeltaTime, Time, TimeOfDay},
|
||||
@ -339,41 +339,19 @@ impl<'a> System<'a> for Sys {
|
||||
Some(&calendar_data),
|
||||
);
|
||||
|
||||
create_npc_emitter.emit(match NpcData::from_entity_info(entity_info) {
|
||||
NpcData::Data {
|
||||
pos,
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
inventory,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale,
|
||||
loot,
|
||||
} => CreateNpcEvent {
|
||||
pos,
|
||||
ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))),
|
||||
npc: NpcBuilder::new(stats, body, alignment)
|
||||
.with_skill_set(skill_set)
|
||||
.with_health(health)
|
||||
.with_poise(poise)
|
||||
.with_inventory(inventory)
|
||||
.with_agent(agent.map(|agent| Agent {
|
||||
rtsim_outbox: Some(Default::default()),
|
||||
..agent
|
||||
}))
|
||||
.with_scale(scale)
|
||||
.with_loot(loot)
|
||||
.with_rtsim(RtSimEntity(id)),
|
||||
rider: steering,
|
||||
},
|
||||
// EntityConfig can't represent Waypoints at all
|
||||
// as of now, and if someone will try to spawn
|
||||
// rtsim waypoint it is definitely error.
|
||||
NpcData::Waypoint(_) => unimplemented!(),
|
||||
NpcData::Teleporter(_, _) => unimplemented!(),
|
||||
let (mut npc_builder, pos) = NpcData::from_entity_info(entity_info)
|
||||
.to_npc_builder()
|
||||
.expect("NpcData must be valid");
|
||||
|
||||
if let Some(agent) = &mut npc_builder.agent {
|
||||
agent.rtsim_outbox = Some(Default::default());
|
||||
}
|
||||
|
||||
create_npc_emitter.emit(CreateNpcEvent {
|
||||
pos,
|
||||
ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))),
|
||||
npc: npc_builder.with_rtsim(RtSimEntity(id)),
|
||||
rider: steering,
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -400,37 +378,17 @@ impl<'a> System<'a> for Sys {
|
||||
Some(&calendar_data),
|
||||
);
|
||||
|
||||
Some(match NpcData::from_entity_info(entity_info) {
|
||||
NpcData::Data {
|
||||
pos: _,
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
inventory,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale,
|
||||
loot,
|
||||
} => NpcBuilder::new(stats, body, alignment)
|
||||
.with_skill_set(skill_set)
|
||||
.with_health(health)
|
||||
.with_poise(poise)
|
||||
.with_inventory(inventory)
|
||||
.with_agent(agent.map(|agent| Agent {
|
||||
rtsim_outbox: Some(Default::default()),
|
||||
..agent
|
||||
}))
|
||||
.with_scale(scale)
|
||||
.with_loot(loot)
|
||||
.with_rtsim(RtSimEntity(npc_id)),
|
||||
// EntityConfig can't represent Waypoints at all
|
||||
// as of now, and if someone will try to spawn
|
||||
// rtsim waypoint it is definitely error.
|
||||
NpcData::Waypoint(_) => unimplemented!(),
|
||||
NpcData::Teleporter(_, _) => unimplemented!(),
|
||||
})
|
||||
let mut npc_builder = NpcData::from_entity_info(entity_info)
|
||||
.to_npc_builder()
|
||||
.expect("NpcData must be valid")
|
||||
.0
|
||||
.with_rtsim(RtSimEntity(npc_id));
|
||||
|
||||
if let Some(agent) = &mut npc_builder.agent {
|
||||
agent.rtsim_outbox = Some(Default::default());
|
||||
}
|
||||
|
||||
Some(npc_builder)
|
||||
} else {
|
||||
error!("Npc is loaded but vehicle is unloaded");
|
||||
None
|
||||
|
@ -209,31 +209,16 @@ impl<'a> System<'a> for Sys {
|
||||
NpcData::Waypoint(pos) => {
|
||||
emitters.emit(CreateWaypointEvent(pos));
|
||||
},
|
||||
NpcData::Data {
|
||||
pos,
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
inventory,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale,
|
||||
loot,
|
||||
} => {
|
||||
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");
|
||||
|
||||
emitters.emit(CreateNpcEvent {
|
||||
pos,
|
||||
ori: comp::Ori::from(Dir::random_2d(&mut rng)),
|
||||
npc: 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_anchor(comp::Anchor::Chunk(key))
|
||||
.with_loot(loot),
|
||||
npc: npc_builder.with_anchor(comp::Anchor::Chunk(key)),
|
||||
rider: None,
|
||||
});
|
||||
},
|
||||
@ -420,6 +405,7 @@ impl<'a> System<'a> for Sys {
|
||||
// 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,
|
||||
@ -433,6 +419,7 @@ pub enum NpcData {
|
||||
alignment: comp::Alignment,
|
||||
scale: comp::Scale,
|
||||
loot: LootSpec<String>,
|
||||
pets: Vec<NpcData>,
|
||||
},
|
||||
Waypoint(Vec3<f32>),
|
||||
Teleporter(Vec3<f32>, PortalData),
|
||||
@ -462,7 +449,7 @@ impl NpcData {
|
||||
make_loadout,
|
||||
trading_information: economy,
|
||||
// unused
|
||||
pet: _, // TODO: I had no idea we have this.
|
||||
pets,
|
||||
} = entity;
|
||||
|
||||
if let Some(special) = special_entity {
|
||||
@ -574,6 +561,44 @@ impl NpcData {
|
||||
alignment,
|
||||
scale: comp::Scale(scale),
|
||||
loot,
|
||||
pets: pets.into_iter().map(NpcData::from_entity_info).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn to_npc_builder(self) -> Result<(NpcBuilder, comp::Pos), 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_data| pet_data.to_npc_builder().map(|(npc, _pos)| npc))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
pos,
|
||||
)),
|
||||
NpcData::Waypoint(_) => Err(self),
|
||||
NpcData::Teleporter(_, _) => Err(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user