mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'juliancoffee/balance_update' into 'master'
Abilitiy tweaks and /make_npc command See merge request veloren/veloren!2769
This commit is contained in:
commit
5116c31f20
@ -2,7 +2,7 @@ ComboMelee(
|
||||
stage_data: [
|
||||
(
|
||||
stage: 1,
|
||||
base_damage: 90,
|
||||
base_damage: 110,
|
||||
base_poise_damage: 12,
|
||||
damage_increase: 10,
|
||||
poise_damage_increase: 0,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 3.0,
|
||||
swing_duration: 0.35,
|
||||
recover_duration: 1.2,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: true,
|
||||
damage_kind: Slashing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 1.0,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 1.0,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 3.0,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.7,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 4.0,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.5,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Piercing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 1.0,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 1.0,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 0.8,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 1.0,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 1.2,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 1.1,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 1.0,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 1.0,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 1.2,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 1.1,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 2.0,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.5,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: false,
|
||||
damage_kind: Crushing,
|
||||
|
@ -3,8 +3,8 @@ ChargedMelee(
|
||||
energy_drain: 300,
|
||||
initial_damage: 10,
|
||||
scaled_damage: 160,
|
||||
initial_poise_damage: 20,
|
||||
scaled_poise_damage: 60,
|
||||
initial_poise_damage: 5,
|
||||
scaled_poise_damage: 75,
|
||||
initial_knockback: 5.0,
|
||||
scaled_knockback: 20.0,
|
||||
range: 3.5,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 1.0,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.8,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: true,
|
||||
damage_kind: Piercing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 1.2,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.5,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: true,
|
||||
is_interruptible: true,
|
||||
damage_kind: Piercing,
|
||||
|
@ -14,6 +14,7 @@ DashMelee(
|
||||
charge_duration: 1.2,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.9,
|
||||
ori_modifier: 0.3,
|
||||
charge_through: false,
|
||||
is_interruptible: true,
|
||||
damage_kind: Piercing,
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
assets,
|
||||
comp::{self, buff::BuffKind, inventory::item::try_all_item_defs, AdminRole as Role, Skill},
|
||||
generation::try_all_entity_configs,
|
||||
npc, terrain,
|
||||
};
|
||||
use assets::AssetExt;
|
||||
@ -76,6 +77,7 @@ pub enum ChatCommand {
|
||||
Lantern,
|
||||
Light,
|
||||
MakeBlock,
|
||||
MakeNpc,
|
||||
MakeSprite,
|
||||
Motd,
|
||||
Object,
|
||||
@ -242,6 +244,15 @@ lazy_static! {
|
||||
items
|
||||
};
|
||||
|
||||
/// List of all entity configs. Useful for tab completing
|
||||
static ref ENTITY_CONFIGS: Vec<String> = {
|
||||
try_all_entity_configs()
|
||||
.unwrap_or_else(|e| {
|
||||
warn!(?e, "Failed to load entity configs");
|
||||
Vec::new()
|
||||
})
|
||||
};
|
||||
|
||||
static ref KITS: Vec<String> = {
|
||||
if let Ok(kits) = KitManifest::load(KIT_MANIFEST_PATH) {
|
||||
kits.read().0.keys().cloned().collect()
|
||||
@ -461,6 +472,14 @@ impl ChatCommand {
|
||||
"Make a block at your location with a color",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::MakeNpc => cmd(
|
||||
vec![
|
||||
Enum("entity_config", ENTITY_CONFIGS.clone(), Required),
|
||||
Integer("num", 1, Optional),
|
||||
],
|
||||
"Spawn entity from config near you",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::MakeSprite => cmd(
|
||||
vec![Enum("sprite", SPRITE_KINDS.clone(), Required)],
|
||||
"Make a sprite at your location",
|
||||
@ -521,8 +540,8 @@ impl ChatCommand {
|
||||
"Set the server description",
|
||||
Some(Admin),
|
||||
),
|
||||
// Uses Message because site names can contain spaces, which would be assumed to be
|
||||
// separators otherwise
|
||||
// Uses Message because site names can contain spaces,
|
||||
// which would be assumed to be separators otherwise
|
||||
ChatCommand::Site => cmd(
|
||||
vec![Message(Required)],
|
||||
"Teleport to a site",
|
||||
@ -634,6 +653,7 @@ impl ChatCommand {
|
||||
ChatCommand::Lantern => "lantern",
|
||||
ChatCommand::Light => "light",
|
||||
ChatCommand::MakeBlock => "make_block",
|
||||
ChatCommand::MakeNpc => "make_npc",
|
||||
ChatCommand::MakeSprite => "make_sprite",
|
||||
ChatCommand::Motd => "motd",
|
||||
ChatCommand::Object => "object",
|
||||
@ -788,7 +808,6 @@ pub enum ArgumentSpec {
|
||||
}
|
||||
|
||||
impl ArgumentSpec {
|
||||
#[allow(clippy::nonstandard_macro_braces)] //tmp as of false positive !?
|
||||
pub fn usage_string(&self) -> String {
|
||||
match self {
|
||||
ArgumentSpec::PlayerName(req) => {
|
||||
@ -836,9 +855,9 @@ impl ArgumentSpec {
|
||||
ArgumentSpec::SubCommand => "<[/]command> [args...]".to_string(),
|
||||
ArgumentSpec::Enum(label, _, req) => {
|
||||
if &Requirement::Required == req {
|
||||
format! {"<{}>", label}
|
||||
format!("<{}>", label)
|
||||
} else {
|
||||
format! {"[{}]", label}
|
||||
format!("[{}]", label)
|
||||
}
|
||||
},
|
||||
ArgumentSpec::Boolean(label, _, req) => {
|
||||
|
@ -119,6 +119,7 @@ pub enum CharacterAbility {
|
||||
charge_duration: f32,
|
||||
swing_duration: f32,
|
||||
recover_duration: f32,
|
||||
ori_modifier: f32,
|
||||
charge_through: bool,
|
||||
is_interruptible: bool,
|
||||
damage_kind: DamageKind,
|
||||
@ -497,6 +498,7 @@ impl CharacterAbility {
|
||||
charge_duration: _,
|
||||
ref mut swing_duration,
|
||||
ref mut recover_duration,
|
||||
ori_modifier: _,
|
||||
charge_through: _,
|
||||
is_interruptible: _,
|
||||
damage_kind: _,
|
||||
@ -1531,6 +1533,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
|
||||
charge_duration,
|
||||
swing_duration,
|
||||
recover_duration,
|
||||
ori_modifier,
|
||||
charge_through,
|
||||
is_interruptible,
|
||||
damage_kind,
|
||||
@ -1552,6 +1555,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
|
||||
charge_duration: Duration::from_secs_f32(*charge_duration),
|
||||
swing_duration: Duration::from_secs_f32(*swing_duration),
|
||||
recover_duration: Duration::from_secs_f32(*recover_duration),
|
||||
ori_modifier: *ori_modifier,
|
||||
is_interruptible: *is_interruptible,
|
||||
damage_effect: *damage_effect,
|
||||
ability_info,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
assets::{self, AssetExt},
|
||||
assets::{self, AssetExt, Error},
|
||||
comp::{
|
||||
self, agent, humanoid,
|
||||
inventory::loadout_builder::{ItemSpec, LoadoutBuilder},
|
||||
@ -140,11 +140,16 @@ impl EntityConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return all entity config specifiers
|
||||
pub fn try_all_entity_configs() -> Result<Vec<String>, Error> {
|
||||
let configs = assets::load_dir::<EntityConfig>("common.entity", true)?;
|
||||
Ok(configs.ids().map(|id| id.to_owned()).collect())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EntityInfo {
|
||||
pub pos: Vec3<f32>,
|
||||
pub is_waypoint: bool, // Edge case, overrides everything else
|
||||
pub is_giant: bool,
|
||||
pub has_agency: bool,
|
||||
pub alignment: Alignment,
|
||||
pub agent_mark: Option<agent::Mark>,
|
||||
@ -170,7 +175,6 @@ impl EntityInfo {
|
||||
Self {
|
||||
pos,
|
||||
is_waypoint: false,
|
||||
is_giant: false,
|
||||
has_agency: true,
|
||||
alignment: Alignment::Wild,
|
||||
agent_mark: None,
|
||||
@ -307,11 +311,6 @@ impl EntityInfo {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_giant(mut self) -> Self {
|
||||
self.is_giant = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_alignment(mut self, alignment: Alignment) -> Self {
|
||||
self.alignment = alignment;
|
||||
self
|
||||
@ -382,7 +381,7 @@ impl EntityInfo {
|
||||
|
||||
pub fn with_automatic_name(mut self) -> Self {
|
||||
let npc_names = NPC_NAMES.read();
|
||||
self.name = match &self.body {
|
||||
let name = match &self.body {
|
||||
Body::Humanoid(body) => Some(get_npc_name(&npc_names.humanoid, body.species)),
|
||||
Body::QuadrupedMedium(body) => {
|
||||
Some(get_npc_name(&npc_names.quadruped_medium, body.species))
|
||||
@ -400,14 +399,8 @@ impl EntityInfo {
|
||||
Body::Golem(body) => Some(get_npc_name(&npc_names.golem, body.species)),
|
||||
Body::BipedLarge(body) => Some(get_npc_name(&npc_names.biped_large, body.species)),
|
||||
_ => None,
|
||||
}
|
||||
.map(|s| {
|
||||
if self.is_giant {
|
||||
format!("Giant {}", s)
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
});
|
||||
};
|
||||
self.name = name.map(str::to_owned);
|
||||
self
|
||||
}
|
||||
|
||||
@ -554,10 +547,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_all_entity_assets() {
|
||||
// It just load everything that could
|
||||
let entity_configs = assets::load_dir::<EntityConfig>("common.entity", true)
|
||||
.expect("Failed to access entity directory");
|
||||
for config_asset in entity_configs.ids() {
|
||||
// Get list of entity configs, load everything, validate content.
|
||||
let entity_configs =
|
||||
try_all_entity_configs().expect("Failed to access entity configs directory");
|
||||
for config_asset in entity_configs {
|
||||
// print asset name so we don't need to find errors everywhere
|
||||
// it'll be ignored by default so you'll see it only in case of errors
|
||||
//
|
||||
@ -568,7 +561,7 @@ mod tests {
|
||||
// 2) Add try_from_asset() for LoadoutBuilder and
|
||||
// SkillSet builder which will return Result and we will happily
|
||||
// panic in validate_meta() with the name of config_asset
|
||||
println!("{}:", config_asset);
|
||||
println!("{}:", &config_asset);
|
||||
|
||||
let EntityConfig {
|
||||
hands,
|
||||
@ -577,12 +570,12 @@ mod tests {
|
||||
loot,
|
||||
meta,
|
||||
alignment: _alignment, // can't fail if serialized, it's a boring enum
|
||||
} = EntityConfig::from_asset_expect(config_asset);
|
||||
} = EntityConfig::from_asset_expect(&config_asset);
|
||||
|
||||
validate_hands(hands, config_asset);
|
||||
validate_body_and_name(body, name, config_asset);
|
||||
validate_loot(loot, config_asset);
|
||||
validate_meta(meta, config_asset);
|
||||
validate_hands(hands, &config_asset);
|
||||
validate_body_and_name(body, name, &config_asset);
|
||||
validate_loot(loot, &config_asset);
|
||||
validate_meta(meta, &config_asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ pub struct StaticData {
|
||||
pub swing_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// How fast can you turn during charge
|
||||
pub ori_modifier: f32,
|
||||
/// Whether the state can be interrupted by other abilities
|
||||
pub is_interruptible: bool,
|
||||
/// Adds an effect onto the main damage of the attack
|
||||
@ -107,13 +109,14 @@ impl CharacterBehavior for Data {
|
||||
/ self.static_data.charge_duration.as_secs_f32())
|
||||
.min(1.0);
|
||||
|
||||
handle_orientation(data, &mut update, 0.6);
|
||||
handle_orientation(data, &mut update, self.static_data.ori_modifier);
|
||||
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
|
||||
strength: self.static_data.forward_speed * charge_frac.sqrt(),
|
||||
});
|
||||
|
||||
// This logic basically just decides if a charge should end, and prevents the
|
||||
// character state spamming attacks while checking if it has hit something
|
||||
// This logic basically just decides if a charge should end,
|
||||
// and prevents the character state spamming attacks
|
||||
// while checking if it has hit something.
|
||||
if !self.exhausted {
|
||||
// Hit attempt
|
||||
let poise = AttackEffect::new(
|
||||
|
@ -1,11 +1,12 @@
|
||||
//! # Implementing new commands.
|
||||
//! To implement a new command, add an instance of `ChatCommand` to
|
||||
//! `CHAT_COMMANDS` and provide a handler function.
|
||||
//! To implement a new command provide a handler function
|
||||
//! in [do_command].
|
||||
|
||||
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},
|
||||
@ -68,6 +70,7 @@ impl ChatCommandExt for ChatCommand {
|
||||
}
|
||||
|
||||
type CmdResult<T> = Result<T, String>;
|
||||
|
||||
/// Handler function called when the command is executed.
|
||||
/// # Arguments
|
||||
/// * `&mut Server` - the `Server` instance executing the command.
|
||||
@ -140,6 +143,7 @@ fn do_command(
|
||||
ChatCommand::Lantern => handle_lantern,
|
||||
ChatCommand::Light => handle_light,
|
||||
ChatCommand::MakeBlock => handle_make_block,
|
||||
ChatCommand::MakeNpc => handle_make_npc,
|
||||
ChatCommand::MakeSprite => handle_make_sprite,
|
||||
ChatCommand::Motd => handle_motd,
|
||||
ChatCommand::Object => handle_object,
|
||||
@ -547,6 +551,93 @@ fn handle_make_block(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_make_npc(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
args: Vec<String>,
|
||||
action: &ChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
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(
|
||||
server: &mut Server,
|
||||
_client: EcsEntity,
|
||||
|
@ -10,10 +10,9 @@ use crate::{
|
||||
SpawnPoint, Tick,
|
||||
};
|
||||
use common::{
|
||||
comp::{self, agent, bird_medium, Alignment, BehaviorCapability, ForceUpdate, Pos, Waypoint},
|
||||
comp::{self, agent, bird_medium, BehaviorCapability, ForceUpdate, Pos, Waypoint},
|
||||
event::{EventBus, ServerEvent},
|
||||
generation::{get_npc_name, EntityInfo},
|
||||
npc::NPC_NAMES,
|
||||
generation::EntityInfo,
|
||||
resources::Time,
|
||||
terrain::TerrainGrid,
|
||||
LoadoutBuilder, SkillSetBuilder,
|
||||
@ -22,6 +21,7 @@ use common_ecs::{Job, Origin, Phase, System};
|
||||
use common_net::msg::{SerializedTerrainChunk, ServerGeneral};
|
||||
use common_state::TerrainChanges;
|
||||
use comp::Behavior;
|
||||
use rand::Rng;
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteExpect, WriteStorage};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
@ -199,145 +199,43 @@ impl<'a> System<'a> for Sys {
|
||||
"Chunk spawned entity that wasn't nearby",
|
||||
);
|
||||
|
||||
if entity.is_waypoint {
|
||||
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut body = entity.body;
|
||||
let name = entity.name.unwrap_or_else(|| "Unnamed".to_string());
|
||||
let alignment = entity.alignment;
|
||||
let mut stats = comp::Stats::new(name);
|
||||
|
||||
let mut scale = entity.scale;
|
||||
|
||||
// Replace stuff if it's a boss
|
||||
if entity.is_giant {
|
||||
if rand::random::<f32>() < 0.65 && entity.alignment != Alignment::Enemy {
|
||||
let body_new = comp::humanoid::Body::random();
|
||||
let npc_names = NPC_NAMES.read();
|
||||
|
||||
body = comp::Body::Humanoid(body_new);
|
||||
stats = comp::Stats::new(format!(
|
||||
"Gentle Giant {}",
|
||||
get_npc_name(&npc_names.humanoid, body_new.species)
|
||||
));
|
||||
}
|
||||
scale = 2.0 + rand::random::<f32>();
|
||||
}
|
||||
|
||||
let EntityInfo {
|
||||
skillset_asset,
|
||||
main_tool,
|
||||
second_tool,
|
||||
loadout_asset,
|
||||
make_loadout,
|
||||
trading_information: economy,
|
||||
..
|
||||
} = entity;
|
||||
|
||||
let skill_set = {
|
||||
let skillset_builder = SkillSetBuilder::default();
|
||||
if let Some(skillset_asset) = skillset_asset {
|
||||
skillset_builder.with_asset_expect(&skillset_asset).build()
|
||||
} else {
|
||||
skillset_builder.build()
|
||||
}
|
||||
};
|
||||
|
||||
let loadout = {
|
||||
let mut loadout_builder = LoadoutBuilder::empty();
|
||||
let rng = &mut rand::thread_rng();
|
||||
|
||||
// If main tool is passed, use it. Otherwise fallback to default tool
|
||||
if let Some(main_tool) = main_tool {
|
||||
loadout_builder = loadout_builder.active_mainhand(Some(main_tool));
|
||||
} else {
|
||||
loadout_builder = loadout_builder.with_default_maintool(&body);
|
||||
}
|
||||
|
||||
// If second tool is passed, use it as well
|
||||
if let Some(second_tool) = second_tool {
|
||||
loadout_builder = loadout_builder.active_offhand(Some(second_tool));
|
||||
}
|
||||
|
||||
// If there is config, apply it.
|
||||
// If not, use default equipement for this body.
|
||||
if let Some(asset) = loadout_asset {
|
||||
loadout_builder = loadout_builder.with_asset_expect(&asset, rng);
|
||||
} else {
|
||||
loadout_builder = loadout_builder.with_default_equipment(&body);
|
||||
}
|
||||
|
||||
// Evaluate lazy function for loadout creation
|
||||
if let Some(make_loadout) = make_loadout {
|
||||
loadout_builder =
|
||||
loadout_builder.with_creator(make_loadout, economy.as_ref());
|
||||
}
|
||||
loadout_builder.build()
|
||||
};
|
||||
|
||||
let health = Some(comp::Health::new(body, entity.level.unwrap_or(0)));
|
||||
let poise = comp::Poise::new(body);
|
||||
|
||||
let can_speak = match body {
|
||||
comp::Body::Humanoid(_) => true,
|
||||
comp::Body::BirdMedium(bird_medium) => match bird_medium.species {
|
||||
// Parrots like to have a word in this, too...
|
||||
bird_medium::Species::Parrot => alignment == comp::Alignment::Npc,
|
||||
_ => false,
|
||||
let rng = &mut rand::thread_rng();
|
||||
let data = NpcData::from_entity_info(entity, rng);
|
||||
match data {
|
||||
NpcData::Waypoint(pos) => {
|
||||
server_emitter.emit(ServerEvent::CreateWaypoint(pos));
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
let trade_for_site = if matches!(entity.agent_mark, Some(agent::Mark::Merchant)) {
|
||||
economy.map(|e| e.id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// TODO: This code sets an appropriate base_damage for the enemy. This doesn't
|
||||
// work because the damage is now saved in an ability
|
||||
/*
|
||||
if let Some(item::ItemKind::Tool(item::ToolData { base_damage, .. })) =
|
||||
&mut loadout.active_item.map(|i| i.item.kind)
|
||||
{
|
||||
*base_damage = stats.level.level() as u32 * 3;
|
||||
}
|
||||
*/
|
||||
server_emitter.emit(ServerEvent::CreateNpc {
|
||||
pos: Pos(entity.pos),
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
loadout,
|
||||
agent: if entity.has_agency {
|
||||
Some(
|
||||
comp::Agent::from_body(&body)
|
||||
.with_behavior(
|
||||
Behavior::default()
|
||||
.maybe_with_capabilities(
|
||||
can_speak.then(|| BehaviorCapability::SPEAK),
|
||||
)
|
||||
.with_trade_site(trade_for_site),
|
||||
)
|
||||
.with_patrol_origin(entity.pos)
|
||||
.with_no_flee(!matches!(
|
||||
entity.agent_mark,
|
||||
Some(agent::Mark::Guard)
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
NpcData::Data {
|
||||
pos,
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
loadout,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale,
|
||||
drop_item,
|
||||
} => {
|
||||
server_emitter.emit(ServerEvent::CreateNpc {
|
||||
pos,
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
loadout,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale,
|
||||
anchor: Some(comp::Anchor::Chunk(key)),
|
||||
drop_item,
|
||||
rtsim_entity: None,
|
||||
projectile: None,
|
||||
});
|
||||
},
|
||||
body,
|
||||
alignment,
|
||||
scale: comp::Scale(scale),
|
||||
anchor: Some(comp::Anchor::Chunk(key)),
|
||||
drop_item: entity.loot_drop,
|
||||
rtsim_entity: None,
|
||||
projectile: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a safezone if chunk contains the spawn position
|
||||
@ -442,6 +340,144 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convinient structure to use when you need to create new npc
|
||||
/// from EntityInfo
|
||||
// TODO: better name?
|
||||
pub enum NpcData {
|
||||
Data {
|
||||
pos: Pos,
|
||||
stats: comp::Stats,
|
||||
skill_set: comp::SkillSet,
|
||||
health: Option<comp::Health>,
|
||||
poise: comp::Poise,
|
||||
loadout: comp::inventory::loadout::Loadout,
|
||||
agent: Option<comp::Agent>,
|
||||
body: comp::Body,
|
||||
alignment: comp::Alignment,
|
||||
scale: comp::Scale,
|
||||
drop_item: Option<comp::Item>,
|
||||
},
|
||||
Waypoint(Vec3<f32>),
|
||||
}
|
||||
|
||||
impl NpcData {
|
||||
pub fn from_entity_info(entity: EntityInfo, rng: &mut impl Rng) -> Self {
|
||||
let EntityInfo {
|
||||
// flags
|
||||
is_waypoint,
|
||||
has_agency,
|
||||
agent_mark,
|
||||
alignment,
|
||||
// stats
|
||||
body,
|
||||
name,
|
||||
scale,
|
||||
pos,
|
||||
level,
|
||||
loot_drop,
|
||||
// tools and skills
|
||||
skillset_asset,
|
||||
main_tool,
|
||||
second_tool,
|
||||
loadout_asset,
|
||||
make_loadout,
|
||||
trading_information: economy,
|
||||
// unused
|
||||
pet: _, // TODO: I had no idea we have this.
|
||||
} = entity;
|
||||
|
||||
if is_waypoint {
|
||||
return Self::Waypoint(pos);
|
||||
}
|
||||
|
||||
let name = name.unwrap_or_else(|| "Unnamed".to_string());
|
||||
let stats = comp::Stats::new(name);
|
||||
|
||||
let skill_set = {
|
||||
let skillset_builder = SkillSetBuilder::default();
|
||||
if let Some(skillset_asset) = skillset_asset {
|
||||
skillset_builder.with_asset_expect(&skillset_asset).build()
|
||||
} else {
|
||||
skillset_builder.build()
|
||||
}
|
||||
};
|
||||
|
||||
let loadout = {
|
||||
let mut loadout_builder = LoadoutBuilder::empty();
|
||||
|
||||
// If main tool is passed, use it. Otherwise fallback to default tool
|
||||
if let Some(main_tool) = main_tool {
|
||||
loadout_builder = loadout_builder.active_mainhand(Some(main_tool));
|
||||
} else {
|
||||
loadout_builder = loadout_builder.with_default_maintool(&body);
|
||||
}
|
||||
|
||||
// If second tool is passed, use it as well
|
||||
if let Some(second_tool) = second_tool {
|
||||
loadout_builder = loadout_builder.active_offhand(Some(second_tool));
|
||||
}
|
||||
|
||||
// If there is config, apply it.
|
||||
// If not, use default equipement for this body.
|
||||
if let Some(asset) = loadout_asset {
|
||||
loadout_builder = loadout_builder.with_asset_expect(&asset, rng);
|
||||
} else {
|
||||
loadout_builder = loadout_builder.with_default_equipment(&body);
|
||||
}
|
||||
|
||||
// Evaluate lazy function for loadout creation
|
||||
if let Some(make_loadout) = make_loadout {
|
||||
loadout_builder = loadout_builder.with_creator(make_loadout, economy.as_ref());
|
||||
}
|
||||
loadout_builder.build()
|
||||
};
|
||||
|
||||
let health = Some(comp::Health::new(body, level.unwrap_or(0)));
|
||||
let poise = comp::Poise::new(body);
|
||||
|
||||
let can_speak = match body {
|
||||
comp::Body::Humanoid(_) => true,
|
||||
comp::Body::BirdMedium(bird_medium) => match bird_medium.species {
|
||||
// Parrots like to have a word in this, too...
|
||||
bird_medium::Species::Parrot => alignment == comp::Alignment::Npc,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let trade_for_site = if matches!(agent_mark, Some(agent::Mark::Merchant)) {
|
||||
economy.map(|e| e.id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let agent = has_agency.then(|| {
|
||||
comp::Agent::from_body(&body)
|
||||
.with_behavior(
|
||||
Behavior::default()
|
||||
.maybe_with_capabilities(can_speak.then(|| BehaviorCapability::SPEAK))
|
||||
.with_trade_site(trade_for_site),
|
||||
)
|
||||
.with_patrol_origin(pos)
|
||||
.with_no_flee(!matches!(agent_mark, Some(agent::Mark::Guard)))
|
||||
});
|
||||
|
||||
NpcData::Data {
|
||||
pos: Pos(pos),
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
loadout,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale: comp::Scale(scale),
|
||||
drop_item: loot_drop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunk_in_vd(
|
||||
player_pos: Vec3<f32>,
|
||||
chunk_pos: Vec2<i32>,
|
||||
|
Loading…
Reference in New Issue
Block a user