diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 2b9e1530e4..960044b230 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -6,6 +6,7 @@ use crate::{ settings::{ Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord, }, + sys::terrain::NpcData, wiring::{Logic, OutputFormula}, Server, SpawnPoint, StateExt, }; @@ -28,6 +29,7 @@ use common::{ depot, effect::Effect, event::{EventBus, ServerEvent}, + generation::EntityInfo, npc::{self, get_npc_name}, resources::{PlayerPhysicsSettings, TimeOfDay}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, @@ -550,13 +552,90 @@ fn handle_make_block( } fn handle_make_npc( - _server: &mut Server, - _client: EcsEntity, - _target: EcsEntity, - _args: Vec, - _action: &ChatCommand, + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + args: Vec, + action: &ChatCommand, ) -> CmdResult<()> { - Err("Not implemented".to_owned()) + let (entity_config, number) = parse_args!(args, String, i8); + + let entity_config = entity_config.ok_or_else(|| action.help_string())?; + let number = match number { + Some(i8::MIN..=0) => { + return Err("Number of entities should be at least 1".to_owned()); + }, + Some(50..=i8::MAX) => { + return Err("Number of entities should be less than 50".to_owned()); + }, + Some(number) => number, + None => 1, + }; + + let rng = &mut rand::thread_rng(); + for _ in 0..number { + let comp::Pos(pos) = position(server, target, "target")?; + let entity_info = EntityInfo::at(pos).with_asset_expect(&entity_config); + match NpcData::from_entity_info(entity_info, rng) { + NpcData::Waypoint(_) => { + return Err("Waypoint spawning is not implemented".to_owned()); + }, + NpcData::Data { + loadout, + pos, + stats, + skill_set, + poise, + health, + body, + agent, + alignment, + scale, + drop_item, + } => { + let inventory = Inventory::new_with_loadout(loadout); + + let mut entity_builder = server + .state + .create_npc(pos, stats, skill_set, health, poise, inventory, body) + .with(alignment) + .with(scale) + .with(comp::Vel(Vec3::new(0.0, 0.0, 0.0))) + .with(comp::MountState::Unmounted); + + if let Some(agent) = agent { + entity_builder = entity_builder.with(agent); + } + + if let Some(drop_item) = drop_item { + entity_builder = entity_builder.with(comp::ItemDrop(drop_item)); + } + + // 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. + use comp::Alignment; + 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(); + }, + }; + } + + server.notify_client( + client, + ServerGeneral::server_msg( + ChatType::CommandInfo, + format!("Spawned {} entities from config: {}", number, entity_config), + ), + ); + + Ok(()) } fn handle_make_sprite(