mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
I don't know how much I put in this commit, but it's some stuff
This commit is contained in:
parent
02ead906a3
commit
ca65700aea
@ -3,32 +3,38 @@ use specs::{Component, Entity as EcsEntity};
|
|||||||
use specs_idvs::IDVStorage;
|
use specs_idvs::IDVStorage;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Agent {
|
pub enum Alignment {
|
||||||
Wanderer(Vec2<f32>),
|
Wild,
|
||||||
Pet {
|
Enemy,
|
||||||
target: EcsEntity,
|
Npc,
|
||||||
chaser: Chaser,
|
}
|
||||||
},
|
|
||||||
Enemy {
|
impl Alignment {
|
||||||
bearing: Vec2<f32>,
|
pub fn hostile_towards(self, other: Alignment) -> bool {
|
||||||
target: Option<EcsEntity>,
|
match (self, other) {
|
||||||
},
|
(Alignment::Wild, Alignment::Npc) => true,
|
||||||
|
_ => self != other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Alignment {
|
||||||
|
type Storage = IDVStorage<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Agent {
|
||||||
|
pub chaser: Chaser,
|
||||||
|
pub target: Option<EcsEntity>,
|
||||||
|
pub owner: Option<EcsEntity>,
|
||||||
|
pub patrol_origin: Option<Vec3<f32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Agent {
|
impl Agent {
|
||||||
pub fn enemy() -> Self {
|
pub fn with_pet(mut self, owner: EcsEntity) -> Self {
|
||||||
Agent::Enemy {
|
self.owner = Some(owner);
|
||||||
bearing: Vec2::zero(),
|
self
|
||||||
target: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pet(target: EcsEntity) -> Self {
|
|
||||||
Agent::Pet {
|
|
||||||
target,
|
|
||||||
chaser: Chaser::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ mod visual;
|
|||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use admin::Admin;
|
pub use admin::Admin;
|
||||||
pub use agent::Agent;
|
pub use agent::{Agent, Alignment};
|
||||||
pub use body::{
|
pub use body::{
|
||||||
biped_large, bird_medium, bird_small, critter, dragon, fish_medium, fish_small, humanoid,
|
biped_large, bird_medium, bird_small, critter, dragon, fish_medium, fish_small, humanoid,
|
||||||
object, quadruped_medium, quadruped_small, Body,
|
object, quadruped_medium, quadruped_small, Body,
|
||||||
|
@ -102,6 +102,7 @@ pub enum ServerEvent {
|
|||||||
stats: comp::Stats,
|
stats: comp::Stats,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
agent: comp::Agent,
|
agent: comp::Agent,
|
||||||
|
alignment: comp::Alignment,
|
||||||
scale: comp::Scale,
|
scale: comp::Scale,
|
||||||
},
|
},
|
||||||
ClientDisconnect(EcsEntity),
|
ClientDisconnect(EcsEntity),
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
terrain::Block,
|
terrain::Block,
|
||||||
vol::{BaseVol, ReadVol},
|
vol::{BaseVol, ReadVol},
|
||||||
};
|
};
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -89,13 +90,19 @@ pub struct Chaser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Chaser {
|
impl Chaser {
|
||||||
pub fn chase<V>(&mut self, vol: &V, pos: Vec3<f32>, tgt: Vec3<f32>) -> Option<Vec3<f32>>
|
pub fn chase<V>(
|
||||||
|
&mut self,
|
||||||
|
vol: &V,
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
tgt: Vec3<f32>,
|
||||||
|
min_dist: f32,
|
||||||
|
) -> Option<Vec3<f32>>
|
||||||
where
|
where
|
||||||
V: BaseVol<Vox = Block> + ReadVol,
|
V: BaseVol<Vox = Block> + ReadVol,
|
||||||
{
|
{
|
||||||
let pos_to_tgt = pos.distance(tgt);
|
let pos_to_tgt = pos.distance(tgt);
|
||||||
|
|
||||||
if pos_to_tgt < 4.0 {
|
if pos_to_tgt < min_dist {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +111,7 @@ impl Chaser {
|
|||||||
if end_to_tgt > pos_to_tgt * 0.3 + 5.0 {
|
if end_to_tgt > pos_to_tgt * 0.3 + 5.0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
if rand::random::<f32>() < 0.005 {
|
if thread_rng().gen::<f32>() < 0.005 {
|
||||||
// TODO: Only re-calculate route when we're stuck
|
// TODO: Only re-calculate route when we're stuck
|
||||||
self.route = Route::default();
|
self.route = Route::default();
|
||||||
}
|
}
|
||||||
@ -205,12 +212,7 @@ where
|
|||||||
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
||||||
|
|
||||||
let mut new_astar = match astar.take() {
|
let mut new_astar = match astar.take() {
|
||||||
None => {
|
None => Astar::new(20_000, start, heuristic.clone()),
|
||||||
let max_iters = ((Vec2::<f32>::from(startf).distance(Vec2::from(endf)) * 2.0 + 25.0)
|
|
||||||
.powf(2.0) as usize)
|
|
||||||
.min(25_000);
|
|
||||||
Astar::new(max_iters, start, heuristic.clone())
|
|
||||||
}
|
|
||||||
Some(astar) => astar,
|
Some(astar) => astar,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,6 +141,7 @@ impl State {
|
|||||||
ecs.register::<comp::Last<comp::Ori>>();
|
ecs.register::<comp::Last<comp::Ori>>();
|
||||||
ecs.register::<comp::Last<comp::CharacterState>>();
|
ecs.register::<comp::Last<comp::CharacterState>>();
|
||||||
ecs.register::<comp::Agent>();
|
ecs.register::<comp::Agent>();
|
||||||
|
ecs.register::<comp::Alignment>();
|
||||||
ecs.register::<comp::ForceUpdate>();
|
ecs.register::<comp::ForceUpdate>();
|
||||||
ecs.register::<comp::InventoryUpdate>();
|
ecs.register::<comp::InventoryUpdate>();
|
||||||
ecs.register::<comp::Admin>();
|
ecs.register::<comp::Admin>();
|
||||||
|
@ -1,20 +1,29 @@
|
|||||||
use crate::comp::{
|
|
||||||
Agent, CharacterState, Controller, MountState, MovementState::Glide, Pos, Stats,
|
|
||||||
};
|
|
||||||
use crate::terrain::TerrainGrid;
|
use crate::terrain::TerrainGrid;
|
||||||
use rand::{seq::SliceRandom, thread_rng};
|
use crate::{
|
||||||
use specs::{Entities, Join, ReadExpect, ReadStorage, System, WriteStorage};
|
comp::{
|
||||||
|
self, Agent, Alignment, CharacterState, Controller, MountState, MovementState::Glide, Pos,
|
||||||
|
Stats,
|
||||||
|
},
|
||||||
|
sync::{Uid, UidAllocator},
|
||||||
|
};
|
||||||
|
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||||
|
use specs::{
|
||||||
|
saveload::{Marker, MarkerAllocator},
|
||||||
|
Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
|
||||||
|
};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
/// This system will allow NPCs to modify their controller
|
/// This system will allow NPCs to modify their controller
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
impl<'a> System<'a> for Sys {
|
impl<'a> System<'a> for Sys {
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
|
Read<'a, UidAllocator>,
|
||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
ReadStorage<'a, Stats>,
|
ReadStorage<'a, Stats>,
|
||||||
ReadStorage<'a, CharacterState>,
|
ReadStorage<'a, CharacterState>,
|
||||||
ReadExpect<'a, TerrainGrid>,
|
ReadExpect<'a, TerrainGrid>,
|
||||||
|
ReadStorage<'a, Alignment>,
|
||||||
WriteStorage<'a, Agent>,
|
WriteStorage<'a, Agent>,
|
||||||
WriteStorage<'a, Controller>,
|
WriteStorage<'a, Controller>,
|
||||||
ReadStorage<'a, MountState>,
|
ReadStorage<'a, MountState>,
|
||||||
@ -23,19 +32,22 @@ impl<'a> System<'a> for Sys {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
(
|
(
|
||||||
|
uid_allocator,
|
||||||
entities,
|
entities,
|
||||||
positions,
|
positions,
|
||||||
stats,
|
stats,
|
||||||
character_states,
|
character_states,
|
||||||
terrain,
|
terrain,
|
||||||
|
alignments,
|
||||||
mut agents,
|
mut agents,
|
||||||
mut controllers,
|
mut controllers,
|
||||||
mount_states,
|
mount_states,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
for (entity, pos, agent, controller, mount_state) in (
|
for (entity, pos, alignment, agent, controller, mount_state) in (
|
||||||
&entities,
|
&entities,
|
||||||
&positions,
|
&positions,
|
||||||
|
alignments.maybe(),
|
||||||
&mut agents,
|
&mut agents,
|
||||||
&mut controllers,
|
&mut controllers,
|
||||||
mount_states.maybe(),
|
mount_states.maybe(),
|
||||||
@ -60,6 +72,99 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let mut inputs = &mut controller.inputs;
|
let mut inputs = &mut controller.inputs;
|
||||||
|
|
||||||
|
const PET_DIST: f32 = 12.0;
|
||||||
|
const PATROL_DIST: f32 = 48.0;
|
||||||
|
const SIGHT_DIST: f32 = 18.0;
|
||||||
|
const MIN_ATTACK_DIST: f32 = 3.25;
|
||||||
|
|
||||||
|
let mut chase_tgt = None;
|
||||||
|
let mut choose_target = false;
|
||||||
|
|
||||||
|
if let Some(target) = agent.target {
|
||||||
|
// Chase / attack target
|
||||||
|
if let (Some(tgt_pos), stats) = (positions.get(target), stats.get(target)) {
|
||||||
|
if stats.map(|s| s.is_dead).unwrap_or(false) {
|
||||||
|
// Don't target dead entities
|
||||||
|
choose_target = true;
|
||||||
|
} else if pos.0.distance(tgt_pos.0) < SIGHT_DIST {
|
||||||
|
chase_tgt = Some((tgt_pos.0, 1.0, true))
|
||||||
|
} else {
|
||||||
|
// Lose sight of enemies
|
||||||
|
choose_target = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
choose_target = true;
|
||||||
|
}
|
||||||
|
} else if let Some(owner) = agent.owner {
|
||||||
|
if let Some(tgt_pos) = positions.get(owner) {
|
||||||
|
if pos.0.distance(tgt_pos.0) > PET_DIST || agent.target.is_none() {
|
||||||
|
// Follow owner
|
||||||
|
chase_tgt = Some((tgt_pos.0, 6.0, false));
|
||||||
|
} else {
|
||||||
|
choose_target = thread_rng().gen::<f32>() < 0.02;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
agent.owner = None;
|
||||||
|
}
|
||||||
|
} else if let Some(patrol_origin) = agent.patrol_origin {
|
||||||
|
if pos.0.distance(patrol_origin) < PATROL_DIST {
|
||||||
|
// Return to patrol origin
|
||||||
|
chase_tgt = Some((patrol_origin, 64.0, false));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
choose_target = thread_rng().gen::<f32>() < 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attack a target that's attacking us
|
||||||
|
if let Some(stats) = stats.get(entity) {
|
||||||
|
match stats.health.last_change.1.cause {
|
||||||
|
comp::HealthSource::Attack { by } => {
|
||||||
|
if agent.target.is_none() {
|
||||||
|
agent.target = uid_allocator.retrieve_entity_internal(by.id());
|
||||||
|
} else if thread_rng().gen::<f32>() < 0.005 {
|
||||||
|
agent.target = uid_allocator.retrieve_entity_internal(by.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if choose_target {
|
||||||
|
// Search for new targets
|
||||||
|
let entities = (&entities, &positions, &stats, alignments.maybe())
|
||||||
|
.join()
|
||||||
|
.filter(|(e, e_pos, e_stats, e_alignment)| {
|
||||||
|
(e_pos.0 - pos.0).magnitude() < SIGHT_DIST
|
||||||
|
&& *e != entity
|
||||||
|
&& !e_stats.is_dead
|
||||||
|
&& alignment
|
||||||
|
.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b)))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.map(|(e, _, _, _)| e)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
agent.target = (&entities).choose(&mut thread_rng()).cloned();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chase target
|
||||||
|
if let Some((tgt_pos, min_dist, aggressive)) = chase_tgt {
|
||||||
|
if let Some(bearing) = agent.chaser.chase(&*terrain, pos.0, tgt_pos, min_dist) {
|
||||||
|
inputs.move_dir = Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
|
||||||
|
inputs.jump.set_state(bearing.z > 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos.0.distance(tgt_pos) < MIN_ATTACK_DIST && aggressive {
|
||||||
|
inputs.look_dir = tgt_pos - pos.0;
|
||||||
|
inputs.move_dir = Vec2::from(tgt_pos - pos.0)
|
||||||
|
.try_normalized()
|
||||||
|
.unwrap_or(Vec2::zero())
|
||||||
|
* 0.01;
|
||||||
|
inputs.primary.set_state(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
match agent {
|
match agent {
|
||||||
Agent::Wanderer(bearing) => {
|
Agent::Wanderer(bearing) => {
|
||||||
*bearing += Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5)
|
*bearing += Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5)
|
||||||
@ -74,7 +179,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
Agent::Pet { target, chaser } => {
|
Agent::Pet { target, chaser } => {
|
||||||
// Run towards target.
|
// Run towards target.
|
||||||
if let Some(tgt_pos) = positions.get(*target) {
|
if let Some(tgt_pos) = positions.get(*target) {
|
||||||
if let Some(bearing) = chaser.chase(&*terrain, pos.0, tgt_pos.0) {
|
if let Some(bearing) = chaser.chase(&*terrain, pos.0, tgt_pos.0, 5.0) {
|
||||||
inputs.move_dir =
|
inputs.move_dir =
|
||||||
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
|
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
|
||||||
inputs.jump.set_state(bearing.z > 1.0);
|
inputs.jump.set_state(bearing.z > 1.0);
|
||||||
@ -160,6 +265,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
||||||
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
||||||
|
@ -458,13 +458,19 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat
|
|||||||
fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
||||||
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
|
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
|
||||||
(Some(opt_align), Some(id), opt_amount) => {
|
(Some(opt_align), Some(id), opt_amount) => {
|
||||||
if let Some(agent) = alignment_to_agent(&opt_align, entity) {
|
if let Some(alignment) = parse_alignment(&opt_align) {
|
||||||
let amount = opt_amount
|
let amount = opt_amount
|
||||||
.and_then(|a| a.parse().ok())
|
.and_then(|a| a.parse().ok())
|
||||||
.filter(|x| *x > 0)
|
.filter(|x| *x > 0)
|
||||||
.unwrap_or(1)
|
.unwrap_or(1)
|
||||||
.min(10);
|
.min(10);
|
||||||
|
|
||||||
|
let agent = if let comp::Alignment::Npc = alignment {
|
||||||
|
comp::Agent::default().with_pet(entity)
|
||||||
|
} else {
|
||||||
|
comp::Agent::default()
|
||||||
|
};
|
||||||
|
|
||||||
match server.state.read_component_cloned::<comp::Pos>(entity) {
|
match server.state.read_component_cloned::<comp::Pos>(entity) {
|
||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
for _ in 0..amount {
|
for _ in 0..amount {
|
||||||
@ -486,6 +492,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
|
|||||||
.with(comp::Vel(vel))
|
.with(comp::Vel(vel))
|
||||||
.with(comp::MountState::Unmounted)
|
.with(comp::MountState::Unmounted)
|
||||||
.with(agent.clone())
|
.with(agent.clone())
|
||||||
|
.with(alignment)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) {
|
if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) {
|
||||||
@ -576,11 +583,11 @@ fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alignment_to_agent(alignment: &str, target: EcsEntity) -> Option<comp::Agent> {
|
fn parse_alignment(alignment: &str) -> Option<comp::Alignment> {
|
||||||
match alignment {
|
match alignment {
|
||||||
"hostile" => Some(comp::Agent::enemy()),
|
"wild" => Some(comp::Alignment::Wild),
|
||||||
"friendly" => Some(comp::Agent::pet(target)),
|
"enemy" => Some(comp::Alignment::Enemy),
|
||||||
// passive?
|
"npc" => Some(comp::Alignment::Npc),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,6 +133,7 @@ impl Server {
|
|||||||
|
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
let world = World::generate(settings.world_seed);
|
let world = World::generate(settings.world_seed);
|
||||||
|
#[cfg(not(feature = "worldgen"))]
|
||||||
let map = vec![0];
|
let map = vec![0];
|
||||||
|
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
@ -299,6 +300,7 @@ impl Server {
|
|||||||
state.write_component(entity, comp::Ori(Vec3::unit_y()));
|
state.write_component(entity, comp::Ori(Vec3::unit_y()));
|
||||||
state.write_component(entity, comp::Gravity(1.0));
|
state.write_component(entity, comp::Gravity(1.0));
|
||||||
state.write_component(entity, comp::CharacterState::default());
|
state.write_component(entity, comp::CharacterState::default());
|
||||||
|
state.write_component(entity, comp::Alignment::Npc);
|
||||||
state.write_component(entity, comp::Inventory::default());
|
state.write_component(entity, comp::Inventory::default());
|
||||||
state.write_component(entity, comp::InventoryUpdate);
|
state.write_component(entity, comp::InventoryUpdate);
|
||||||
// Make sure physics are accepted.
|
// Make sure physics are accepted.
|
||||||
@ -834,12 +836,14 @@ impl Server {
|
|||||||
stats,
|
stats,
|
||||||
body,
|
body,
|
||||||
agent,
|
agent,
|
||||||
|
alignment,
|
||||||
scale,
|
scale,
|
||||||
} => {
|
} => {
|
||||||
state
|
state
|
||||||
.create_npc(pos, stats, body)
|
.create_npc(pos, stats, body)
|
||||||
.with(agent)
|
.with(agent)
|
||||||
.with(scale)
|
.with(scale)
|
||||||
|
.with(alignment)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1228,6 +1232,7 @@ impl StateExt for State {
|
|||||||
.with(comp::Controller::default())
|
.with(comp::Controller::default())
|
||||||
.with(body)
|
.with(body)
|
||||||
.with(stats)
|
.with(stats)
|
||||||
|
.with(comp::Alignment::Npc)
|
||||||
.with(comp::Energy::new(500))
|
.with(comp::Energy::new(500))
|
||||||
.with(comp::Gravity(1.0))
|
.with(comp::Gravity(1.0))
|
||||||
.with(comp::CharacterState::default())
|
.with(comp::CharacterState::default())
|
||||||
|
@ -173,7 +173,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
pos: Pos(npc.pos),
|
pos: Pos(npc.pos),
|
||||||
stats,
|
stats,
|
||||||
body,
|
body,
|
||||||
agent: comp::Agent::enemy(),
|
alignment: comp::Alignment::Enemy,
|
||||||
|
agent: comp::Agent::default(),
|
||||||
scale: comp::Scale(scale),
|
scale: comp::Scale(scale),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user