mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
/aura command
This commit is contained in:
parent
af4f147fda
commit
8dd7e3e0d5
@ -85,6 +85,9 @@ command-message-group-missing = You are using group chat but do not belong to a
|
||||
/region to change chat.
|
||||
command-tell-request = { $sender } wants to talk to you.
|
||||
command-transform-invalid-presence = Cannot transform in the current presence
|
||||
command-aura-invalid-buff-parameters = Invalid buff parameters for aura
|
||||
command-aura-spawn = Spawned new aura attached to entity
|
||||
command-aura-spawn-new-entity = Spawned new aura
|
||||
|
||||
# Unreachable/untestable but added for consistency
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
use crate::{
|
||||
assets::{self, AssetCombined, Concatenate},
|
||||
comp::{self, buff::BuffKind, inventory::item::try_all_item_defs, AdminRole as Role, Skill},
|
||||
comp::{
|
||||
self,
|
||||
aura::{AuraKindVariant, SimpleAuraTarget},
|
||||
buff::BuffKind,
|
||||
inventory::item::try_all_item_defs,
|
||||
AdminRole as Role, Skill,
|
||||
},
|
||||
generation::try_all_entity_configs,
|
||||
npc, terrain,
|
||||
};
|
||||
@ -313,6 +319,7 @@ pub enum ServerChatCommand {
|
||||
AreaAdd,
|
||||
AreaList,
|
||||
AreaRemove,
|
||||
Aura,
|
||||
Ban,
|
||||
BattleMode,
|
||||
BattleModeForce,
|
||||
@ -431,6 +438,18 @@ impl ServerChatCommand {
|
||||
"Change your alias",
|
||||
Some(Moderator),
|
||||
),
|
||||
ServerChatCommand::Aura => cmd(
|
||||
vec![
|
||||
Float("aura_radius", 10.0, Required),
|
||||
Float("aura_duration", 10.0, Optional),
|
||||
Boolean("new_entity", "true".to_string(), Optional),
|
||||
Enum("aura_target", SimpleAuraTarget::all_options(), Optional),
|
||||
Enum("aura_kind", AuraKindVariant::all_options(), Required),
|
||||
Any("aura spec", Optional),
|
||||
],
|
||||
"Create an aura",
|
||||
Some(Admin),
|
||||
),
|
||||
ServerChatCommand::Buff => cmd(
|
||||
vec![
|
||||
Enum("buff", BUFFS.clone(), Required),
|
||||
@ -955,6 +974,7 @@ impl ServerChatCommand {
|
||||
ServerChatCommand::AreaAdd => "area_add",
|
||||
ServerChatCommand::AreaList => "area_list",
|
||||
ServerChatCommand::AreaRemove => "area_remove",
|
||||
ServerChatCommand::Aura => "aura",
|
||||
ServerChatCommand::Ban => "ban",
|
||||
ServerChatCommand::BattleMode => "battlemode",
|
||||
ServerChatCommand::BattleModeForce => "battlemode_force",
|
||||
@ -1285,6 +1305,45 @@ impl ArgumentSpec {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CommandEnumArg: FromStr {
|
||||
fn all_options() -> Vec<String>;
|
||||
}
|
||||
|
||||
macro_rules! impl_from_to_str_cmd {
|
||||
($enum:ident, ($($attribute:ident => $str:expr),*)) => {
|
||||
impl std::str::FromStr for $enum {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
$(
|
||||
$str => Ok($enum::$attribute),
|
||||
)*
|
||||
s => Err(format!("Invalid variant: {s}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::cmd::CommandEnumArg for $enum {
|
||||
fn all_options() -> Vec<String> {
|
||||
vec![$($str.to_string()),*]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_to_str_cmd!(AuraKindVariant, (
|
||||
Buff => "buff",
|
||||
FriendlyFire => "friendly_fire",
|
||||
IgnorePvE => "ingore_pve"
|
||||
));
|
||||
|
||||
impl_from_to_str_cmd!(SimpleAuraTarget, (
|
||||
Group => "group",
|
||||
OutOfGroup => "out_of_group",
|
||||
All => "all"
|
||||
));
|
||||
|
||||
/// Parse a series of command arguments into values, including collecting all
|
||||
/// trailing arguments.
|
||||
#[macro_export]
|
||||
|
@ -28,6 +28,7 @@ use common::{
|
||||
},
|
||||
comp::{
|
||||
self,
|
||||
aura::{AuraKindVariant, AuraTarget, SimpleAuraTarget},
|
||||
buff::{Buff, BuffData, BuffKind, BuffSource, MiscBuffData},
|
||||
inventory::{
|
||||
item::{all_items_expect, tool::AbilityMap, MaterialStatManifest, Quality},
|
||||
@ -35,7 +36,8 @@ use common::{
|
||||
},
|
||||
invite::InviteKind,
|
||||
misc::PortalData,
|
||||
AdminRole, ChatType, Content, Inventory, Item, LightEmitter, WaypointArea,
|
||||
AdminRole, Aura, AuraKind, BuffCategory, ChatType, Content, Inventory, Item, LightEmitter,
|
||||
WaypointArea,
|
||||
},
|
||||
depot,
|
||||
effect::Effect,
|
||||
@ -67,7 +69,7 @@ use hashbrown::{HashMap, HashSet};
|
||||
use humantime::Duration as HumanDuration;
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{storage::StorageEntry, Builder, Entity as EcsEntity, Join, LendJoin, WorldExt};
|
||||
use std::{fmt::Write, ops::DerefMut, str::FromStr, sync::Arc};
|
||||
use std::{fmt::Write, ops::DerefMut, str::FromStr, sync::Arc, time::Duration};
|
||||
use vek::*;
|
||||
use wiring::{Circuit, Wire, WireNode, WiringAction, WiringActionEffect, WiringElement};
|
||||
use world::util::{Sampler, LOCALITY};
|
||||
@ -134,6 +136,7 @@ fn do_command(
|
||||
ServerChatCommand::AreaAdd => handle_area_add,
|
||||
ServerChatCommand::AreaList => handle_area_list,
|
||||
ServerChatCommand::AreaRemove => handle_area_remove,
|
||||
ServerChatCommand::Aura => handle_aura,
|
||||
ServerChatCommand::Ban => handle_ban,
|
||||
ServerChatCommand::BattleMode => handle_battlemode,
|
||||
ServerChatCommand::BattleModeForce => handle_battlemode_force,
|
||||
@ -3967,6 +3970,116 @@ fn handle_ban(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_aura(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
let target_uid = uid(server, target, "target")?;
|
||||
|
||||
let (Some(aura_radius), aura_duration, new_entity, aura_target, Some(aura_kind_variant), spec) =
|
||||
parse_cmd_args!(args, f32, f32, bool, SimpleAuraTarget, AuraKindVariant, ..Vec<String>)
|
||||
else {
|
||||
return Err(Content::Plain(action.help_string()));
|
||||
};
|
||||
let (new_entity, aura_target) = (
|
||||
new_entity.unwrap_or(false),
|
||||
aura_target.unwrap_or(SimpleAuraTarget::OutOfGroup),
|
||||
);
|
||||
let aura_kind = match aura_kind_variant {
|
||||
AuraKindVariant::Buff => {
|
||||
let (Some(buff), strength, duration, misc_data_spec) =
|
||||
parse_cmd_args!(spec, String, f32, f64, String)
|
||||
else {
|
||||
return Err(Content::localized("command-aura-invalid-buff-parameters"));
|
||||
};
|
||||
let buffkind = parse_buffkind(&buff).ok_or_else(|| {
|
||||
Content::localized_with_args("command-buff-unknown", [("buff", buff.clone())])
|
||||
})?;
|
||||
let buffdata = build_buff(
|
||||
buffkind,
|
||||
strength.unwrap_or(1.0),
|
||||
duration.unwrap_or(10.0),
|
||||
(!buffkind.is_simple())
|
||||
.then(|| {
|
||||
misc_data_spec.ok_or_else(|| {
|
||||
Content::localized_with_args("command-buff-data", [(
|
||||
"buff",
|
||||
buff.clone(),
|
||||
)])
|
||||
})
|
||||
})
|
||||
.transpose()?,
|
||||
)?;
|
||||
|
||||
AuraKind::Buff {
|
||||
kind: buffkind,
|
||||
data: buffdata,
|
||||
category: BuffCategory::Natural,
|
||||
source: if new_entity {
|
||||
BuffSource::World
|
||||
} else {
|
||||
BuffSource::Character { by: target_uid }
|
||||
},
|
||||
}
|
||||
},
|
||||
AuraKindVariant::FriendlyFire => AuraKind::FriendlyFire,
|
||||
AuraKindVariant::IgnorePvE => AuraKind::ForcePvP,
|
||||
};
|
||||
let aura_target = server
|
||||
.state
|
||||
.read_component_copied::<Uid>(target)
|
||||
.map(|uid| match aura_target {
|
||||
SimpleAuraTarget::Group => AuraTarget::GroupOf(uid),
|
||||
SimpleAuraTarget::OutOfGroup => AuraTarget::NotGroupOf(uid),
|
||||
SimpleAuraTarget::All => AuraTarget::All,
|
||||
})
|
||||
.unwrap_or(AuraTarget::All);
|
||||
|
||||
let time = Time(server.state.get_time());
|
||||
let aura = Aura::new(
|
||||
aura_kind,
|
||||
aura_radius,
|
||||
aura_duration.map(|duration| Secs(duration as f64)),
|
||||
aura_target,
|
||||
time,
|
||||
);
|
||||
|
||||
if new_entity {
|
||||
let pos = position(server, target, "target")?;
|
||||
server
|
||||
.state
|
||||
.create_empty(pos)
|
||||
.with(comp::Auras::new(vec![aura]))
|
||||
.maybe_with(aura_duration.map(|duration| comp::Object::DeleteAfter {
|
||||
spawned_at: time,
|
||||
timeout: Duration::from_secs_f32(duration),
|
||||
}))
|
||||
.build();
|
||||
} else {
|
||||
let mut auras = server.state.ecs().write_storage::<comp::Auras>();
|
||||
if let Some(mut auras) = auras.get_mut(target) {
|
||||
auras.insert(aura);
|
||||
}
|
||||
}
|
||||
|
||||
server.notify_client(
|
||||
client,
|
||||
ServerGeneral::server_msg(
|
||||
ChatType::CommandInfo,
|
||||
Content::localized(if new_entity {
|
||||
"command-aura-spawn-new-entity"
|
||||
} else {
|
||||
"command-aura-spawn"
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_battlemode(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
@ -4222,81 +4335,89 @@ fn handle_buff(
|
||||
let buffkind = parse_buffkind(&buff).ok_or_else(|| {
|
||||
Content::localized_with_args("command-buff-unknown", [("buff", buff.clone())])
|
||||
})?;
|
||||
let buffdata = build_buff(
|
||||
buffkind,
|
||||
strength,
|
||||
duration.unwrap_or(10.0),
|
||||
(!buffkind.is_simple())
|
||||
.then(|| {
|
||||
misc_data_spec.ok_or_else(|| {
|
||||
Content::localized_with_args("command-buff-data", [("buff", buff.clone())])
|
||||
})
|
||||
})
|
||||
.transpose()?,
|
||||
)?;
|
||||
|
||||
if buffkind.is_simple() {
|
||||
let duration = duration.unwrap_or(10.0);
|
||||
let buffdata = BuffData::new(strength, Some(Secs(duration)));
|
||||
cast_buff(buffkind, buffdata, server, target);
|
||||
Ok(())
|
||||
} else {
|
||||
// default duration is longer for complex buffs
|
||||
let duration = duration.unwrap_or(20.0);
|
||||
let spec = misc_data_spec.ok_or_else(|| {
|
||||
Content::localized_with_args("command-buff-data", [("buff", buff.clone())])
|
||||
})?;
|
||||
cast_buff_complex(buffkind, server, target, spec, strength, duration)
|
||||
}
|
||||
cast_buff(buffkind, buffdata, server, target);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn cast_buff_complex(
|
||||
buffkind: BuffKind,
|
||||
server: &mut Server,
|
||||
target: EcsEntity,
|
||||
spec: String,
|
||||
fn build_buff(
|
||||
buff_kind: BuffKind,
|
||||
strength: f32,
|
||||
duration: f64,
|
||||
) -> CmdResult<()> {
|
||||
// explicit match to remember that this function exists
|
||||
let misc_data = match buffkind {
|
||||
BuffKind::Polymorphed => {
|
||||
let Ok(npc::NpcBody(_id, mut body)) = spec.parse() else {
|
||||
return Err(Content::localized_with_args("command-buff-body-unknown", [
|
||||
("spec", spec.clone()),
|
||||
]));
|
||||
};
|
||||
MiscBuffData::Body(body())
|
||||
},
|
||||
BuffKind::Regeneration
|
||||
| BuffKind::Saturation
|
||||
| BuffKind::Potion
|
||||
| BuffKind::Agility
|
||||
| BuffKind::CampfireHeal
|
||||
| BuffKind::Frenzied
|
||||
| BuffKind::EnergyRegen
|
||||
| BuffKind::IncreaseMaxEnergy
|
||||
| BuffKind::IncreaseMaxHealth
|
||||
| BuffKind::Invulnerability
|
||||
| BuffKind::ProtectingWard
|
||||
| BuffKind::Hastened
|
||||
| BuffKind::Fortitude
|
||||
| BuffKind::Reckless
|
||||
| BuffKind::Flame
|
||||
| BuffKind::Frigid
|
||||
| BuffKind::Lifesteal
|
||||
| BuffKind::ImminentCritical
|
||||
| BuffKind::Fury
|
||||
| BuffKind::Sunderer
|
||||
| BuffKind::Defiance
|
||||
| BuffKind::Bloodfeast
|
||||
| BuffKind::Berserk
|
||||
| BuffKind::Bleeding
|
||||
| BuffKind::Cursed
|
||||
| BuffKind::Burning
|
||||
| BuffKind::Crippled
|
||||
| BuffKind::Frozen
|
||||
| BuffKind::Wet
|
||||
| BuffKind::Ensnared
|
||||
| BuffKind::Poisoned
|
||||
| BuffKind::Parried
|
||||
| BuffKind::PotionSickness
|
||||
| BuffKind::Heatstroke => unreachable!("is_simple() above"),
|
||||
};
|
||||
spec: Option<String>,
|
||||
) -> CmdResult<BuffData> {
|
||||
if buff_kind.is_simple() {
|
||||
Ok(BuffData::new(strength, Some(Secs(duration))))
|
||||
} else {
|
||||
let spec = spec.expect("spec must be passed to build_buff if buff_kind is not simple");
|
||||
|
||||
let buffdata = BuffData::new(strength, Some(Secs(duration))).with_misc_data(misc_data);
|
||||
// Explicit match to remember that this function exists
|
||||
let misc_data = match buff_kind {
|
||||
BuffKind::Polymorphed => {
|
||||
let Ok(npc::NpcBody(_id, mut body)) = spec.parse() else {
|
||||
return Err(Content::localized_with_args("command-buff-body-unknown", [
|
||||
("spec", spec.clone()),
|
||||
]));
|
||||
};
|
||||
MiscBuffData::Body(body())
|
||||
},
|
||||
BuffKind::Regeneration
|
||||
| BuffKind::Saturation
|
||||
| BuffKind::Potion
|
||||
| BuffKind::Agility
|
||||
| BuffKind::CampfireHeal
|
||||
| BuffKind::Frenzied
|
||||
| BuffKind::EnergyRegen
|
||||
| BuffKind::IncreaseMaxEnergy
|
||||
| BuffKind::IncreaseMaxHealth
|
||||
| BuffKind::Invulnerability
|
||||
| BuffKind::ProtectingWard
|
||||
| BuffKind::Hastened
|
||||
| BuffKind::Fortitude
|
||||
| BuffKind::Reckless
|
||||
| BuffKind::Flame
|
||||
| BuffKind::Frigid
|
||||
| BuffKind::Lifesteal
|
||||
| BuffKind::ImminentCritical
|
||||
| BuffKind::Fury
|
||||
| BuffKind::Sunderer
|
||||
| BuffKind::Defiance
|
||||
| BuffKind::Bloodfeast
|
||||
| BuffKind::Berserk
|
||||
| BuffKind::Bleeding
|
||||
| BuffKind::Cursed
|
||||
| BuffKind::Burning
|
||||
| BuffKind::Crippled
|
||||
| BuffKind::Frozen
|
||||
| BuffKind::Wet
|
||||
| BuffKind::Ensnared
|
||||
| BuffKind::Poisoned
|
||||
| BuffKind::Parried
|
||||
| BuffKind::PotionSickness
|
||||
| BuffKind::Heatstroke => {
|
||||
if buff_kind.is_simple() {
|
||||
unreachable!("is_simple() above")
|
||||
} else {
|
||||
panic!("Buff Kind {buff_kind:?} is complex but has no defined spec parser")
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
cast_buff(buffkind, buffdata, server, target);
|
||||
Ok(())
|
||||
Ok(BuffData::new(strength, Some(Secs(duration))).with_misc_data(misc_data))
|
||||
}
|
||||
}
|
||||
|
||||
fn cast_buff(buffkind: BuffKind, data: BuffData, server: &mut Server, target: EcsEntity) {
|
||||
|
@ -63,6 +63,8 @@ pub trait StateExt {
|
||||
inventory: Inventory,
|
||||
body: comp::Body,
|
||||
) -> EcsEntityBuilder;
|
||||
/// Create an entity with only a position
|
||||
fn create_empty(&mut self, pos: comp::Pos) -> EcsEntityBuilder;
|
||||
/// Build a static object entity
|
||||
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder;
|
||||
/// Create an item drop or merge the item with an existing drop, if a
|
||||
@ -314,16 +316,21 @@ impl StateExt for State {
|
||||
.with(comp::Buffs::default())
|
||||
.with(comp::Combo::default())
|
||||
.with(comp::Auras::default())
|
||||
.with(comp::EnteredAuras::default())
|
||||
.with(comp::Stance::default())
|
||||
}
|
||||
|
||||
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder {
|
||||
let body = comp::Body::Object(object);
|
||||
fn create_empty(&mut self, pos: comp::Pos) -> EcsEntityBuilder {
|
||||
self.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(pos)
|
||||
.with(comp::Vel(Vec3::zero()))
|
||||
.with(comp::Ori::default())
|
||||
}
|
||||
|
||||
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder {
|
||||
let body = comp::Body::Object(object);
|
||||
self.create_empty(pos)
|
||||
.with(body.mass())
|
||||
.with(body.density())
|
||||
.with(body.collider())
|
||||
@ -628,6 +635,7 @@ impl StateExt for State {
|
||||
self.write_component_ignore_entity_dead(entity, comp::Alignment::Owned(player_uid));
|
||||
self.write_component_ignore_entity_dead(entity, comp::Buffs::default());
|
||||
self.write_component_ignore_entity_dead(entity, comp::Auras::default());
|
||||
self.write_component_ignore_entity_dead(entity, comp::EnteredAuras::default());
|
||||
self.write_component_ignore_entity_dead(entity, comp::Combo::default());
|
||||
self.write_component_ignore_entity_dead(entity, comp::Stance::default());
|
||||
|
||||
|
@ -68,15 +68,15 @@ impl<'a> System<'a> for Sys {
|
||||
&entities,
|
||||
&positions,
|
||||
&velocities,
|
||||
&physics_states,
|
||||
physics_states.maybe(),
|
||||
&objects,
|
||||
&bodies,
|
||||
bodies.maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
match object {
|
||||
Object::Bomb { owner } => {
|
||||
if physics.on_surface().is_some() {
|
||||
if physics.is_some_and(|physics| physics.on_surface().is_some()) {
|
||||
emitters.emit(DeleteEvent(entity));
|
||||
emitters.emit(ExplosionEvent {
|
||||
pos: pos.0,
|
||||
@ -213,7 +213,9 @@ impl<'a> System<'a> for Sys {
|
||||
})
|
||||
});
|
||||
|
||||
if (*body == Body::Object(object::Body::PortalActive)) != is_active {
|
||||
if body.is_some_and(|body| {
|
||||
(*body == Body::Object(object::Body::PortalActive)) != is_active
|
||||
}) {
|
||||
emitters.emit(ChangeBodyEvent {
|
||||
entity,
|
||||
new_body: Body::Object(if is_active {
|
||||
|
Loading…
Reference in New Issue
Block a user