mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'crabman/friendly-fire-aura' into 'master'
/aura command and friendly fire auras See merge request veloren/veloren!4391
This commit is contained in:
commit
5bd884df86
@ -54,6 +54,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Missing plugins are requested from the server and cached locally.
|
||||
- Support for adding spots in plugins.
|
||||
- Added real colours to LOD trees and rooftops, unique models for most tree kinds, and models for several buildings
|
||||
- `/aura` command
|
||||
- Friendly Fire and Forced PvP auras to desert city arenas
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -85,12 +85,14 @@ 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
|
||||
|
||||
command-player-info-unavailable = Cannot get player information for { $target }
|
||||
command-unimplemented-waypoint-spawn = Waypoint spawning is not implemented
|
||||
command-unimplemented-teleporter-spawn = Teleporter spawning is not implemented
|
||||
command-unimplemented-spawn-special = Spawning special entities is not implemented
|
||||
command-kit-inventory-unavailable = Could not get inventory
|
||||
command-inventory-cant-fit-item = Can't fit item to inventory
|
||||
# Emitted by /disconnect_all when you don't exist (?)
|
||||
|
@ -1,6 +1,10 @@
|
||||
use crate::{
|
||||
assets::{self, AssetCombined, Concatenate},
|
||||
comp::{self, buff::BuffKind, inventory::item::try_all_item_defs, AdminRole as Role, Skill},
|
||||
combat::GroupTarget,
|
||||
comp::{
|
||||
self, aura::AuraKindVariant, buff::BuffKind, inventory::item::try_all_item_defs,
|
||||
AdminRole as Role, Skill,
|
||||
},
|
||||
generation::try_all_entity_configs,
|
||||
npc, terrain,
|
||||
};
|
||||
@ -313,6 +317,7 @@ pub enum ServerChatCommand {
|
||||
AreaAdd,
|
||||
AreaList,
|
||||
AreaRemove,
|
||||
Aura,
|
||||
Ban,
|
||||
BattleMode,
|
||||
BattleModeForce,
|
||||
@ -431,6 +436,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", GroupTarget::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 +972,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 +1303,44 @@ 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",
|
||||
ForcePvP => "force_pvp"
|
||||
));
|
||||
|
||||
impl_from_to_str_cmd!(GroupTarget, (
|
||||
InGroup => "in_group",
|
||||
OutOfGroup => "out_of_group"
|
||||
));
|
||||
|
||||
/// Parse a series of command arguments into values, including collecting all
|
||||
/// trailing arguments.
|
||||
#[macro_export]
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
ability::Capability,
|
||||
aura::{AuraKindVariant, EnteredAuras},
|
||||
buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource},
|
||||
inventory::{
|
||||
item::{
|
||||
@ -89,8 +90,12 @@ pub struct TargetInfo<'a> {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AttackOptions {
|
||||
pub target_dodging: bool,
|
||||
pub may_harm: bool,
|
||||
/// Result of [`permit_pvp`]
|
||||
pub permit_pvp: bool,
|
||||
pub target_group: GroupTarget,
|
||||
/// When set to `true`, entities in the same group or pets & pet owners may
|
||||
/// hit eachother albeit the target_group being OutOfGroup
|
||||
pub allow_friendly_fire: bool,
|
||||
pub precision_mult: Option<f32>,
|
||||
}
|
||||
|
||||
@ -270,7 +275,8 @@ impl Attack {
|
||||
|
||||
let AttackOptions {
|
||||
target_dodging,
|
||||
may_harm,
|
||||
permit_pvp,
|
||||
allow_friendly_fire,
|
||||
target_group,
|
||||
precision_mult,
|
||||
} = options;
|
||||
@ -279,14 +285,14 @@ impl Attack {
|
||||
// "attack" has negative effects.
|
||||
//
|
||||
// so if target dodges this "attack" or we don't want to harm target,
|
||||
// it should avoid such "damage" or effect
|
||||
// it should avoid such "damage" or effect, unless friendly fire is enabled
|
||||
let avoid_damage = |attack_damage: &AttackDamage| {
|
||||
matches!(attack_damage.target, Some(GroupTarget::OutOfGroup))
|
||||
&& (target_dodging || !may_harm)
|
||||
(matches!(attack_damage.target, Some(GroupTarget::OutOfGroup)) && !allow_friendly_fire)
|
||||
&& (target_dodging || !permit_pvp)
|
||||
};
|
||||
let avoid_effect = |attack_effect: &AttackEffect| {
|
||||
matches!(attack_effect.target, Some(GroupTarget::OutOfGroup))
|
||||
&& (target_dodging || !may_harm)
|
||||
(matches!(attack_effect.target, Some(GroupTarget::OutOfGroup)) && !allow_friendly_fire)
|
||||
&& (target_dodging || !permit_pvp)
|
||||
};
|
||||
let precision_mult = attacker
|
||||
.and_then(|a| a.stats)
|
||||
@ -300,7 +306,7 @@ impl Attack {
|
||||
for damage in self
|
||||
.damages
|
||||
.iter()
|
||||
.filter(|d| d.target.map_or(true, |t| t == target_group))
|
||||
.filter(|d| allow_friendly_fire || d.target.map_or(true, |t| t == target_group))
|
||||
.filter(|d| !avoid_damage(d))
|
||||
{
|
||||
let damage_instance = damage.instance + damage_instance_offset;
|
||||
@ -594,7 +600,7 @@ impl Attack {
|
||||
.iter()
|
||||
.flat_map(|stats| stats.effects_on_attack.iter()),
|
||||
)
|
||||
.filter(|e| e.target.map_or(true, |t| t == target_group))
|
||||
.filter(|e| allow_friendly_fire || e.target.map_or(true, |t| t == target_group))
|
||||
.filter(|e| !avoid_effect(e))
|
||||
{
|
||||
let requirements_met = effect.requirements.iter().all(|req| match req {
|
||||
@ -778,6 +784,24 @@ impl Attack {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allow_friendly_fire(
|
||||
entered_auras: &ReadStorage<EnteredAuras>,
|
||||
attacker: EcsEntity,
|
||||
target: EcsEntity,
|
||||
) -> bool {
|
||||
entered_auras
|
||||
.get(attacker)
|
||||
.zip(entered_auras.get(target))
|
||||
.and_then(|(attacker, target)| {
|
||||
Some((
|
||||
attacker.auras.get(&AuraKindVariant::FriendlyFire)?,
|
||||
target.auras.get(&AuraKindVariant::FriendlyFire)?,
|
||||
))
|
||||
})
|
||||
// Only allow friendly fire if both entities are affectd by the same FriendlyFire aura
|
||||
.is_some_and(|(attacker, target)| attacker.intersection(target).next().is_some())
|
||||
}
|
||||
|
||||
/// Function that checks for unintentional PvP between players.
|
||||
///
|
||||
/// Returns `false` if attack will create unintentional conflict,
|
||||
@ -787,9 +811,10 @@ impl Attack {
|
||||
/// If both players have PvP mode enabled, interact with NPC and
|
||||
/// in any other case, this function will return `true`
|
||||
// TODO: add parameter for doing self-harm?
|
||||
pub fn may_harm(
|
||||
pub fn permit_pvp(
|
||||
alignments: &ReadStorage<Alignment>,
|
||||
players: &ReadStorage<Player>,
|
||||
entered_auras: &ReadStorage<EnteredAuras>,
|
||||
id_maps: &IdMaps,
|
||||
attacker: Option<EcsEntity>,
|
||||
target: EcsEntity,
|
||||
@ -815,17 +840,34 @@ pub fn may_harm(
|
||||
};
|
||||
|
||||
// "Dereference" to owner if this is a pet.
|
||||
let attacker = owner_if_pet(attacker);
|
||||
let target = owner_if_pet(target);
|
||||
let attacker_owner = owner_if_pet(attacker);
|
||||
let target_owner = owner_if_pet(target);
|
||||
|
||||
// Prevent owners from attacking their pets and vice versa
|
||||
if attacker == target {
|
||||
return false;
|
||||
// If both players are in the same ForcePvP aura, allow them to harm eachother
|
||||
if let (Some(attacker_auras), Some(target_auras)) = (
|
||||
entered_auras.get(attacker_owner),
|
||||
entered_auras.get(target_owner),
|
||||
) && attacker_auras
|
||||
.auras
|
||||
.get(&AuraKindVariant::ForcePvP)
|
||||
.zip(target_auras.auras.get(&AuraKindVariant::ForcePvP))
|
||||
// Only allow forced pvp if both entities are affectd by the same FriendlyFire aura
|
||||
.is_some_and(|(attacker, target)| attacker.intersection(target).next().is_some())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prevent PvP between pets, unless friendly fire is enabled
|
||||
//
|
||||
// This code is NOT intended to prevent pet <-> owner combat,
|
||||
// pets and their owners being in the same group should take care of that
|
||||
if attacker_owner == target_owner {
|
||||
return allow_friendly_fire(entered_auras, attacker, target);
|
||||
}
|
||||
|
||||
// Get player components
|
||||
let attacker_info = players.get(attacker);
|
||||
let target_info = players.get(target);
|
||||
let attacker_info = players.get(attacker_owner);
|
||||
let target_info = players.get(target_owner);
|
||||
|
||||
// Return `true` if not players.
|
||||
attacker_info
|
||||
|
@ -7,6 +7,7 @@ use crate::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slotmap::{new_key_type, SlotMap};
|
||||
use specs::{Component, DerefFlaggedStorage, VecStorage};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
new_key_type! { pub struct AuraKey; }
|
||||
|
||||
@ -21,12 +22,26 @@ pub enum AuraKind {
|
||||
category: BuffCategory,
|
||||
source: BuffSource,
|
||||
},
|
||||
/// Enables free-for-all friendly-fire. Includes group members, and pets.
|
||||
/// BattleMode checks still apply.
|
||||
FriendlyFire,
|
||||
/// Ignores the [`crate::comp::BattleMode`] of all entities affected by this
|
||||
/// aura, only player entities will be affected by this aura.
|
||||
ForcePvP,
|
||||
/* TODO: Implement other effects here. Things to think about
|
||||
* are terrain/sprite effects, collision and physics, and
|
||||
* environmental conditions like temperature and humidity
|
||||
* Multiple auras can be given to an entity. */
|
||||
}
|
||||
|
||||
/// Variants of [`AuraKind`] without data
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum AuraKindVariant {
|
||||
Buff,
|
||||
FriendlyFire,
|
||||
ForcePvP,
|
||||
}
|
||||
|
||||
/// Aura
|
||||
/// Applies a buff to entities in the radius if meeting
|
||||
/// conditions set forth in the aura system.
|
||||
@ -57,6 +72,8 @@ pub enum AuraChange {
|
||||
Add(Aura),
|
||||
/// Removes auras of these indices
|
||||
RemoveByKey(Vec<AuraKey>),
|
||||
EnterAura(Uid, AuraKey, AuraKindVariant),
|
||||
ExitAura(Uid, AuraKey, AuraKindVariant),
|
||||
}
|
||||
|
||||
/// Used by the aura system to filter entities when applying an effect.
|
||||
@ -91,6 +108,16 @@ impl From<(Option<GroupTarget>, Option<&Uid>)> for AuraTarget {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<AuraKindVariant> for AuraKind {
|
||||
fn as_ref(&self) -> &AuraKindVariant {
|
||||
match self {
|
||||
AuraKind::Buff { .. } => &AuraKindVariant::Buff,
|
||||
AuraKind::FriendlyFire => &AuraKindVariant::FriendlyFire,
|
||||
AuraKind::ForcePvP => &AuraKindVariant::ForcePvP,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuraData {
|
||||
pub duration: Option<Secs>,
|
||||
@ -168,6 +195,24 @@ impl AuraBuffConstructor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Auras affecting an entity
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
pub struct EnteredAuras {
|
||||
/// [`AuraKey`] is local to each [`Auras`] component, therefore we also
|
||||
/// store the [`Uid`] of the aura caster
|
||||
pub auras: HashMap<AuraKindVariant, HashSet<(Uid, AuraKey)>>,
|
||||
}
|
||||
|
||||
impl EnteredAuras {
|
||||
pub fn flatten(&self) -> impl Iterator<Item = (Uid, AuraKey)> + '_ {
|
||||
self.auras.values().flat_map(|i| i.iter().copied())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Auras {
|
||||
type Storage = DerefFlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
||||
impl Component for EnteredAuras {
|
||||
type Storage = DerefFlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ pub use self::{
|
||||
TradingBehavior,
|
||||
},
|
||||
anchor::Anchor,
|
||||
aura::{Aura, AuraChange, AuraKind, Auras},
|
||||
aura::{Aura, AuraChange, AuraKind, Auras, EnteredAuras},
|
||||
beam::Beam,
|
||||
body::{
|
||||
arthropod, biped_large, biped_small, bird_large, bird_medium, crustacean, dragon,
|
||||
|
@ -6,10 +6,9 @@ use crate::{
|
||||
agent::Sound,
|
||||
dialogue::Subject,
|
||||
invite::{InviteKind, InviteResponse},
|
||||
misc::PortalData,
|
||||
DisconnectReason, LootOwner, Ori, Pos, UnresolvedChatMsg, Vel,
|
||||
},
|
||||
generation::EntityInfo,
|
||||
generation::{EntityInfo, SpecialEntity},
|
||||
lottery::LootSpec,
|
||||
mounting::VolumePos,
|
||||
outcome::Outcome,
|
||||
@ -152,8 +151,10 @@ pub struct ChatEvent(pub UnresolvedChatMsg);
|
||||
pub struct CommandEvent(pub EcsEntity, pub String, pub Vec<String>);
|
||||
|
||||
// Entity Creation
|
||||
pub struct CreateWaypointEvent(pub Vec3<f32>);
|
||||
pub struct CreateTeleporterEvent(pub Vec3<f32>, pub PortalData);
|
||||
pub struct CreateSpecialEntityEvent {
|
||||
pub pos: Vec3<f32>,
|
||||
pub entity: SpecialEntity,
|
||||
}
|
||||
|
||||
pub struct CreateNpcEvent {
|
||||
pub pos: Pos,
|
||||
@ -305,6 +306,7 @@ pub struct ExitIngameEvent {
|
||||
pub entity: EcsEntity,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuraEvent {
|
||||
pub entity: EcsEntity,
|
||||
pub aura_change: comp::AuraChange,
|
||||
@ -497,8 +499,7 @@ pub fn register_event_busses(ecs: &mut World) {
|
||||
ecs.insert(EventBus::<ClientDisconnectWithoutPersistenceEvent>::default());
|
||||
ecs.insert(EventBus::<ChatEvent>::default());
|
||||
ecs.insert(EventBus::<CommandEvent>::default());
|
||||
ecs.insert(EventBus::<CreateWaypointEvent>::default());
|
||||
ecs.insert(EventBus::<CreateTeleporterEvent>::default());
|
||||
ecs.insert(EventBus::<CreateSpecialEntityEvent>::default());
|
||||
ecs.insert(EventBus::<CreateNpcEvent>::default());
|
||||
ecs.insert(EventBus::<CreateShipEvent>::default());
|
||||
ecs.insert(EventBus::<CreateItemDropEvent>::default());
|
||||
|
@ -177,10 +177,14 @@ pub fn try_all_entity_configs() -> Result<Vec<String>, Error> {
|
||||
Ok(configs.read().ids().map(|id| id.to_string()).collect())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SpecialEntity {
|
||||
Waypoint,
|
||||
Teleporter(PortalData),
|
||||
/// Totem with FriendlyFire and ForcePvP auras
|
||||
ArenaTotem {
|
||||
range: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -94,6 +94,7 @@ impl CharacterBehavior for Data {
|
||||
data.strength *=
|
||||
(self.static_data.combo_at_cast.max(1) as f32).sqrt();
|
||||
},
|
||||
AuraKind::FriendlyFire | AuraKind::ForcePvP => {},
|
||||
}
|
||||
output_events.emit_server(ComboChangeEvent {
|
||||
entity: data.entity,
|
||||
|
@ -243,6 +243,7 @@ impl State {
|
||||
ecs.register::<comp::ActiveAbilities>();
|
||||
ecs.register::<comp::Buffs>();
|
||||
ecs.register::<comp::Auras>();
|
||||
ecs.register::<comp::EnteredAuras>();
|
||||
ecs.register::<comp::Energy>();
|
||||
ecs.register::<comp::Combo>();
|
||||
ecs.register::<comp::Health>();
|
||||
|
@ -1,7 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use common::{
|
||||
combat,
|
||||
comp::{
|
||||
aura::{AuraChange, AuraKey, AuraKind, AuraTarget},
|
||||
aura::{AuraChange, AuraKey, AuraKind, AuraTarget, EnteredAuras},
|
||||
buff::{Buff, BuffCategory, BuffChange, BuffSource},
|
||||
group::Group,
|
||||
Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos, Stats,
|
||||
@ -38,6 +40,7 @@ pub struct ReadData<'a> {
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
auras: ReadStorage<'a, Auras>,
|
||||
entered_auras: ReadStorage<'a, EnteredAuras>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -51,6 +54,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
fn run(_job: &mut Job<Self>, read_data: Self::SystemData) {
|
||||
let mut emitters = read_data.events.get_emitters();
|
||||
let mut active_auras: HashSet<(Uid, Uid, AuraKey)> = HashSet::new();
|
||||
|
||||
// Iterate through all entities with an aura
|
||||
for (entity, pos, auras_comp, uid) in (
|
||||
@ -75,49 +79,55 @@ impl<'a> System<'a> for Sys {
|
||||
.0
|
||||
.in_circle_aabr(pos.0.xy(), aura.radius)
|
||||
.filter_map(|target| {
|
||||
read_data
|
||||
.positions
|
||||
.get(target)
|
||||
.and_then(|l| read_data.healths.get(target).map(|r| (l, r)))
|
||||
.and_then(|l| read_data.uids.get(target).map(|r| (l, r)))
|
||||
.map(|((target_pos, health), target_uid)| {
|
||||
(
|
||||
target,
|
||||
target_pos,
|
||||
health,
|
||||
target_uid,
|
||||
read_data.stats.get(target),
|
||||
)
|
||||
})
|
||||
read_data.positions.get(target).and_then(|target_pos| {
|
||||
Some((
|
||||
target,
|
||||
target_pos,
|
||||
read_data.healths.get(target)?,
|
||||
read_data.uids.get(target)?,
|
||||
read_data.entered_auras.get(target)?,
|
||||
read_data.stats.get(target),
|
||||
))
|
||||
})
|
||||
});
|
||||
target_iter.for_each(|(target, target_pos, health, target_uid, stats)| {
|
||||
let target_buffs = match read_data.buffs.get(target) {
|
||||
Some(buff) => buff,
|
||||
None => return,
|
||||
};
|
||||
target_iter.for_each(
|
||||
|(target, target_pos, health, target_uid, entered_auras, stats)| {
|
||||
let target_buffs = match read_data.buffs.get(target) {
|
||||
Some(buff) => buff,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Ensure entity is within the aura radius
|
||||
if target_pos.0.distance_squared(pos.0) < aura.radius.powi(2) {
|
||||
// Ensure the entity is in the group we want to target
|
||||
let same_group = |uid: Uid| {
|
||||
read_data
|
||||
.id_maps
|
||||
.uid_entity(uid)
|
||||
.and_then(|e| read_data.groups.get(e))
|
||||
.map_or(false, |owner_group| {
|
||||
Some(owner_group) == read_data.groups.get(target)
|
||||
// Ensure entity is within the aura radius
|
||||
if target_pos.0.distance_squared(pos.0) < aura.radius.powi(2) {
|
||||
// Ensure the entity is in the group we want to target
|
||||
let same_group = |uid: Uid| {
|
||||
read_data
|
||||
.id_maps
|
||||
.uid_entity(uid)
|
||||
.and_then(|e| read_data.groups.get(e))
|
||||
.map_or(false, |owner_group| {
|
||||
Some(owner_group) == read_data.groups.get(target)
|
||||
})
|
||||
|| *target_uid == uid
|
||||
};
|
||||
|
||||
let allow_friendly_fire = combat::allow_friendly_fire(
|
||||
&read_data.entered_auras,
|
||||
entity,
|
||||
target,
|
||||
);
|
||||
|
||||
if !(allow_friendly_fire && entity != target
|
||||
|| match aura.target {
|
||||
AuraTarget::GroupOf(uid) => same_group(uid),
|
||||
AuraTarget::NotGroupOf(uid) => !same_group(uid),
|
||||
AuraTarget::All => true,
|
||||
})
|
||||
|| *target_uid == uid
|
||||
};
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let is_target = match aura.target {
|
||||
AuraTarget::GroupOf(uid) => same_group(uid),
|
||||
AuraTarget::NotGroupOf(uid) => !same_group(uid),
|
||||
AuraTarget::All => true,
|
||||
};
|
||||
|
||||
if is_target {
|
||||
activate_aura(
|
||||
let did_activate = activate_aura(
|
||||
key,
|
||||
aura,
|
||||
*uid,
|
||||
@ -125,12 +135,31 @@ impl<'a> System<'a> for Sys {
|
||||
health,
|
||||
target_buffs,
|
||||
stats,
|
||||
allow_friendly_fire,
|
||||
&read_data,
|
||||
&mut emitters,
|
||||
);
|
||||
|
||||
if did_activate {
|
||||
if entered_auras
|
||||
.auras
|
||||
.get(aura.aura_kind.as_ref())
|
||||
.map_or(true, |auras| !auras.contains(&(*uid, key)))
|
||||
{
|
||||
emitters.emit(AuraEvent {
|
||||
entity: target,
|
||||
aura_change: AuraChange::EnterAura(
|
||||
*uid,
|
||||
key,
|
||||
*aura.aura_kind.as_ref(),
|
||||
),
|
||||
});
|
||||
}
|
||||
active_auras.insert((*uid, *target_uid, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
if !expired_auras.is_empty() {
|
||||
emitters.emit(AuraEvent {
|
||||
@ -139,6 +168,30 @@ impl<'a> System<'a> for Sys {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (entity, entered_auras, uid) in (
|
||||
&read_data.entities,
|
||||
&read_data.entered_auras,
|
||||
&read_data.uids,
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, active_auras, _)| !active_auras.auras.is_empty())
|
||||
{
|
||||
emitters.emit_many(
|
||||
entered_auras
|
||||
.auras
|
||||
.iter()
|
||||
.flat_map(|(variant, entered_auras)| {
|
||||
entered_auras.iter().zip(core::iter::repeat(*variant))
|
||||
})
|
||||
.filter_map(|((caster_uid, key), variant)| {
|
||||
(!active_auras.contains(&(*caster_uid, *uid, *key))).then_some(AuraEvent {
|
||||
entity,
|
||||
aura_change: AuraChange::ExitAura(*caster_uid, *key, variant),
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,9 +205,10 @@ fn activate_aura(
|
||||
health: &Health,
|
||||
target_buffs: &Buffs,
|
||||
stats: Option<&Stats>,
|
||||
allow_friendly_fire: bool,
|
||||
read_data: &ReadData,
|
||||
emitters: &mut impl EmitExt<BuffEvent>,
|
||||
) {
|
||||
) -> bool {
|
||||
let should_activate = match aura.aura_kind {
|
||||
AuraKind::Buff { kind, source, .. } => {
|
||||
let conditions_held = match kind {
|
||||
@ -187,26 +241,32 @@ fn activate_aura(
|
||||
//
|
||||
// We don't have this for now, but think about this
|
||||
// when we will add this.
|
||||
let may_harm = || {
|
||||
let permit_pvp = || {
|
||||
let owner = match source {
|
||||
BuffSource::Character { by } => read_data.id_maps.uid_entity(by),
|
||||
_ => None,
|
||||
};
|
||||
combat::may_harm(
|
||||
combat::permit_pvp(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.entered_auras,
|
||||
&read_data.id_maps,
|
||||
owner,
|
||||
target,
|
||||
)
|
||||
};
|
||||
|
||||
conditions_held && (kind.is_buff() || may_harm())
|
||||
conditions_held && (kind.is_buff() || allow_friendly_fire || permit_pvp())
|
||||
},
|
||||
AuraKind::FriendlyFire => true,
|
||||
AuraKind::ForcePvP => {
|
||||
// Only apply this aura to players
|
||||
read_data.players.contains(target)
|
||||
},
|
||||
};
|
||||
|
||||
if !should_activate {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: When more aura kinds (besides Buff) are
|
||||
@ -244,5 +304,9 @@ fn activate_aura(
|
||||
});
|
||||
}
|
||||
},
|
||||
// No implementation needed for these auras
|
||||
AuraKind::FriendlyFire | AuraKind::ForcePvP => {},
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ use common::{
|
||||
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
aura::EnteredAuras,
|
||||
Alignment, Beam, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
|
||||
Player, Pos, Scale, Stats,
|
||||
},
|
||||
@ -59,6 +60,7 @@ pub struct ReadData<'a> {
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
character_states: ReadStorage<'a, CharacterState>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
entered_auras: ReadStorage<'a, EnteredAuras>,
|
||||
outcomes: Read<'a, EventBus<Outcome>>,
|
||||
events: ReadAttackEvents<'a>,
|
||||
}
|
||||
@ -203,6 +205,12 @@ impl<'a> System<'a> for Sys {
|
||||
>= tgt_dist;
|
||||
|
||||
if hit {
|
||||
let allow_friendly_fire = combat::allow_friendly_fire(
|
||||
&read_data.entered_auras,
|
||||
entity,
|
||||
target,
|
||||
);
|
||||
|
||||
// See if entities are in the same group
|
||||
let same_group = group
|
||||
.map(|group_a| Some(group_a) == read_data.groups.get(target))
|
||||
@ -243,9 +251,10 @@ impl<'a> System<'a> for Sys {
|
||||
.and_then(|cs| cs.attack_immunities())
|
||||
.map_or(false, |i| i.beams);
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
let permit_pvp = combat::permit_pvp(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.entered_auras,
|
||||
&read_data.id_maps,
|
||||
Some(entity),
|
||||
target,
|
||||
@ -275,7 +284,8 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let attack_options = AttackOptions {
|
||||
target_dodging,
|
||||
may_harm,
|
||||
permit_pvp,
|
||||
allow_friendly_fire,
|
||||
target_group,
|
||||
precision_mult,
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ use common::{
|
||||
combat::{self, DamageContributor},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
aura::Auras,
|
||||
aura::{Auras, EnteredAuras},
|
||||
body::{object, Body},
|
||||
buff::{
|
||||
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffKey, BuffKind, BuffSource,
|
||||
@ -61,6 +61,7 @@ pub struct ReadData<'a> {
|
||||
msm: ReadExpect<'a, MaterialStatManifest>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
auras: ReadStorage<'a, Auras>,
|
||||
entered_auras: ReadStorage<'a, EnteredAuras>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
bodies: ReadStorage<'a, Body>,
|
||||
light_emitters: ReadStorage<'a, LightEmitter>,
|
||||
@ -162,9 +163,10 @@ impl<'a> System<'a> for Sys {
|
||||
if let Some((_, burning)) = buff_comp.iter_kind(BuffKind::Burning).next() {
|
||||
for t_entity in physics_state.touch_entities.keys().filter_map(|te_uid| {
|
||||
read_data.id_maps.uid_entity(*te_uid).filter(|te| {
|
||||
combat::may_harm(
|
||||
combat::permit_pvp(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.entered_auras,
|
||||
&read_data.id_maps,
|
||||
Some(entity),
|
||||
*te,
|
||||
|
@ -2,6 +2,7 @@ use common::{
|
||||
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
aura::EnteredAuras,
|
||||
melee::MultiTarget,
|
||||
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Melee,
|
||||
Ori, Player, Pos, Scale, Stats,
|
||||
@ -59,6 +60,7 @@ pub struct ReadData<'a> {
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
entered_auras: ReadStorage<'a, EnteredAuras>,
|
||||
events: ReadAttackEvents<'a>,
|
||||
}
|
||||
|
||||
@ -186,6 +188,8 @@ impl<'a> System<'a> for Sys {
|
||||
&& !is_blocked_by_wall(&read_data.terrain, attacker_cylinder, target_cylinder);
|
||||
|
||||
if hit {
|
||||
let allow_friendly_fire =
|
||||
combat::allow_friendly_fire(&read_data.entered_auras, attacker, target);
|
||||
// See if entities are in the same group
|
||||
let same_group = read_data
|
||||
.groups
|
||||
@ -227,9 +231,10 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
let permit_pvp = combat::permit_pvp(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.entered_auras,
|
||||
&read_data.id_maps,
|
||||
Some(attacker),
|
||||
target,
|
||||
@ -264,7 +269,8 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let attack_options = AttackOptions {
|
||||
target_dodging,
|
||||
may_harm,
|
||||
permit_pvp,
|
||||
allow_friendly_fire,
|
||||
target_group,
|
||||
precision_mult,
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ use common::{
|
||||
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
aura::EnteredAuras,
|
||||
projectile, Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health,
|
||||
Inventory, Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel,
|
||||
},
|
||||
@ -71,6 +72,7 @@ pub struct ReadData<'a> {
|
||||
character_states: ReadStorage<'a, CharacterState>,
|
||||
terrain: ReadExpect<'a, TerrainGrid>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
entered_auras: ReadStorage<'a, EnteredAuras>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling projectile effect triggers
|
||||
@ -138,7 +140,20 @@ impl<'a> System<'a> for Sys {
|
||||
GroupTarget::OutOfGroup
|
||||
};
|
||||
|
||||
if projectile.ignore_group && same_group {
|
||||
if projectile.ignore_group
|
||||
&& same_group
|
||||
&& projectile
|
||||
.owner
|
||||
.and_then(|owner| {
|
||||
read_data
|
||||
.id_maps
|
||||
.uid_entity(owner)
|
||||
.zip(read_data.id_maps.uid_entity(other))
|
||||
})
|
||||
.map_or(true, |(owner, other)| {
|
||||
!combat::allow_friendly_fire(&read_data.entered_auras, owner, other)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -354,10 +369,15 @@ fn dispatch_hit(
|
||||
});
|
||||
}
|
||||
|
||||
let allow_friendly_fire = owner.is_some_and(|owner| {
|
||||
combat::allow_friendly_fire(&read_data.entered_auras, owner, target)
|
||||
});
|
||||
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
let permit_pvp = combat::permit_pvp(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.entered_auras,
|
||||
&read_data.id_maps,
|
||||
owner,
|
||||
target,
|
||||
@ -447,7 +467,8 @@ fn dispatch_hit(
|
||||
|
||||
let attack_options = AttackOptions {
|
||||
target_dodging,
|
||||
may_harm,
|
||||
permit_pvp,
|
||||
allow_friendly_fire,
|
||||
target_group: projectile_target_info.target_group,
|
||||
precision_mult,
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ use common::{
|
||||
combat::{self, AttackOptions, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
aura::EnteredAuras,
|
||||
shockwave::ShockwaveDodgeable,
|
||||
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, Inventory, Ori,
|
||||
PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
|
||||
@ -62,6 +63,7 @@ pub struct ReadData<'a> {
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
character_states: ReadStorage<'a, CharacterState>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
entered_auras: ReadStorage<'a, EnteredAuras>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or
|
||||
@ -192,7 +194,14 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
// Check if it is a hit
|
||||
//
|
||||
// TODO: Should the owner entity really be filtered out here? Unlike other
|
||||
// attacks, explosions and shockwaves are rather "imprecise"
|
||||
// attacks with which one shoud be easily able to hit oneself.
|
||||
// Once we make shockwaves start out a little way out from the center, this can
|
||||
// be removed.
|
||||
let hit = entity != target
|
||||
&& shockwave_owner.map_or(true, |owner| owner != target)
|
||||
&& !health_b.is_dead
|
||||
&& (pos_b.0 - pos.0).magnitude() < frame_end_dist + rad_b
|
||||
// Collision shapes
|
||||
@ -208,6 +217,9 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
if hit {
|
||||
let allow_friendly_fire = shockwave_owner.is_some_and(|entity| {
|
||||
combat::allow_friendly_fire(&read_data.entered_auras, entity, target)
|
||||
});
|
||||
let dir = Dir::from_unnormalized(pos_b.0 - pos.0).unwrap_or(look_dir);
|
||||
|
||||
let attacker_info =
|
||||
@ -246,9 +258,10 @@ impl<'a> System<'a> for Sys {
|
||||
ShockwaveDodgeable::No => false,
|
||||
});
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
let permit_pvp = combat::permit_pvp(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.entered_auras,
|
||||
&read_data.id_maps,
|
||||
shockwave_owner,
|
||||
target,
|
||||
@ -257,7 +270,8 @@ impl<'a> System<'a> for Sys {
|
||||
let precision_mult = None;
|
||||
let attack_options = AttackOptions {
|
||||
target_dodging,
|
||||
may_harm,
|
||||
permit_pvp,
|
||||
allow_friendly_fire,
|
||||
target_group,
|
||||
precision_mult,
|
||||
};
|
||||
|
@ -1,7 +1,8 @@
|
||||
use common::{
|
||||
comp::{
|
||||
inventory::item::MaterialStatManifest, tool::AbilityMap, Auras, Buffs, CharacterActivity,
|
||||
CharacterState, Collider, Combo, Controller, Energy, Health, Ori, Pos, Stats, Vel,
|
||||
CharacterState, Collider, Combo, Controller, Energy, EnteredAuras, Health, Ori, Pos, Stats,
|
||||
Vel,
|
||||
},
|
||||
resources::{DeltaTime, GameMode, Time},
|
||||
shared_server_config::ServerConstants,
|
||||
@ -128,6 +129,7 @@ pub fn create_player(state: &mut State) -> Entity {
|
||||
.with(Buffs::default())
|
||||
.with(Combo::default())
|
||||
.with(Auras::default())
|
||||
.with(EnteredAuras::default())
|
||||
.with(Energy::new(body))
|
||||
.with(Health::new(body))
|
||||
.with(skill_set)
|
||||
|
@ -28,6 +28,7 @@ use common::{
|
||||
},
|
||||
comp::{
|
||||
self,
|
||||
aura::{AuraKindVariant, AuraTarget},
|
||||
buff::{Buff, BuffData, BuffKind, BuffSource, MiscBuffData},
|
||||
inventory::{
|
||||
item::{all_items_expect, tool::AbilityMap, MaterialStatManifest, Quality},
|
||||
@ -35,15 +36,16 @@ 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,
|
||||
event::{
|
||||
ClientDisconnectEvent, CreateNpcEvent, CreateWaypointEvent, EventBus, ExplosionEvent,
|
||||
ClientDisconnectEvent, CreateNpcEvent, CreateSpecialEntityEvent, EventBus, ExplosionEvent,
|
||||
GroupManipEvent, InitiateInviteEvent, TamePetEvent,
|
||||
},
|
||||
generation::{EntityConfig, EntityInfo},
|
||||
generation::{EntityConfig, EntityInfo, SpecialEntity},
|
||||
link::Is,
|
||||
mounting::{Rider, Volume, VolumeRider},
|
||||
npc::{self, get_npc_name},
|
||||
@ -55,7 +57,8 @@ use common::{
|
||||
tether::Tethered,
|
||||
uid::Uid,
|
||||
vol::ReadVol,
|
||||
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||
weather, Damage, DamageKind, DamageSource, Explosion, GroupTarget, LoadoutBuilder,
|
||||
RadiusEffect,
|
||||
};
|
||||
use common_net::{
|
||||
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
|
||||
@ -67,7 +70,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 +137,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,
|
||||
@ -666,11 +670,8 @@ fn handle_into_npc(
|
||||
TransformEntityError::EntityDead => {
|
||||
Content::localized_with_args("command-entity-dead", [("entity", "target")])
|
||||
},
|
||||
TransformEntityError::UnexpectedNpcWaypoint => {
|
||||
Content::localized("command-unimplemented-waypoint-spawn")
|
||||
},
|
||||
TransformEntityError::UnexpectedNpcTeleporter => {
|
||||
Content::localized("command-unimplemented-teleporter-spawn")
|
||||
TransformEntityError::UnexpectedSpecialEntity => {
|
||||
Content::localized("command-unimplemented-spawn-special")
|
||||
},
|
||||
TransformEntityError::LoadingCharacter => {
|
||||
Content::localized("command-transform-invalid-presence")
|
||||
@ -727,11 +728,8 @@ fn handle_make_npc(
|
||||
);
|
||||
|
||||
match SpawnEntityData::from_entity_info(entity_info) {
|
||||
SpawnEntityData::Waypoint(_) => {
|
||||
return Err(Content::localized("command-unimplemented-waypoint-spawn"));
|
||||
},
|
||||
SpawnEntityData::Teleporter(_, _) => {
|
||||
return Err(Content::localized("command-unimplemented-teleporter-spawn"));
|
||||
SpawnEntityData::Special(_, _) => {
|
||||
return Err(Content::localized("command-unimplemented-spawn-special"));
|
||||
},
|
||||
SpawnEntityData::Npc(data) => {
|
||||
let (npc_builder, _pos) = data.to_npc_builder();
|
||||
@ -1996,8 +1994,11 @@ fn handle_spawn_campfire(
|
||||
server
|
||||
.state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<CreateWaypointEvent>>()
|
||||
.emit_now(CreateWaypointEvent(pos.0));
|
||||
.read_resource::<EventBus<CreateSpecialEntityEvent>>()
|
||||
.emit_now(CreateSpecialEntityEvent {
|
||||
pos: pos.0,
|
||||
entity: SpecialEntity::Waypoint,
|
||||
});
|
||||
|
||||
server.notify_client(
|
||||
client,
|
||||
@ -3967,6 +3968,113 @@ 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, GroupTarget, AuraKindVariant, ..Vec<String>)
|
||||
else {
|
||||
return Err(Content::Plain(action.help_string()));
|
||||
};
|
||||
let new_entity = new_entity.unwrap_or(false);
|
||||
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::ForcePvP => AuraKind::ForcePvP,
|
||||
};
|
||||
let aura_target = server
|
||||
.state
|
||||
.read_component_copied::<Uid>(target)
|
||||
.map(|uid| match aura_target {
|
||||
Some(GroupTarget::InGroup) => AuraTarget::GroupOf(uid),
|
||||
Some(GroupTarget::OutOfGroup) => AuraTarget::NotGroupOf(uid),
|
||||
None => 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 +4330,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) {
|
||||
|
@ -13,9 +13,10 @@ use common::{
|
||||
},
|
||||
event::{
|
||||
CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent, CreateShipEvent,
|
||||
CreateTeleporterEvent, CreateWaypointEvent, EventBus, InitializeCharacterEvent,
|
||||
InitializeSpectatorEvent, ShockwaveEvent, ShootEvent, UpdateCharacterDataEvent,
|
||||
CreateSpecialEntityEvent, EventBus, InitializeCharacterEvent, InitializeSpectatorEvent,
|
||||
ShockwaveEvent, ShootEvent, UpdateCharacterDataEvent,
|
||||
},
|
||||
generation::SpecialEntity,
|
||||
mounting::{Mounting, Volume, VolumeMounting, VolumePos},
|
||||
outcome::Outcome,
|
||||
resources::{Secs, Time},
|
||||
@ -388,53 +389,76 @@ pub fn handle_shockwave(server: &mut Server, ev: ShockwaveEvent) {
|
||||
.build();
|
||||
}
|
||||
|
||||
pub fn handle_create_waypoint(server: &mut Server, ev: CreateWaypointEvent) {
|
||||
pub fn handle_create_special_entity(server: &mut Server, ev: CreateSpecialEntityEvent) {
|
||||
let time = server.state.get_time();
|
||||
server
|
||||
.state
|
||||
.create_object(Pos(ev.0), comp::object::Body::CampfireLit)
|
||||
.with(LightEmitter {
|
||||
col: Rgb::new(1.0, 0.3, 0.1),
|
||||
strength: 5.0,
|
||||
flicker: 1.0,
|
||||
animated: true,
|
||||
})
|
||||
.with(WaypointArea::default())
|
||||
.with(comp::Immovable)
|
||||
.with(comp::Auras::new(vec![
|
||||
Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::CampfireHeal,
|
||||
data: BuffData::new(0.02, Some(Secs(1.0))),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
5.0,
|
||||
None,
|
||||
AuraTarget::All,
|
||||
Time(time),
|
||||
),
|
||||
Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::Burning,
|
||||
data: BuffData::new(2.0, Some(Secs(10.0))),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
0.7,
|
||||
None,
|
||||
AuraTarget::All,
|
||||
Time(time),
|
||||
),
|
||||
]))
|
||||
.build();
|
||||
}
|
||||
|
||||
pub fn handle_create_teleporter(server: &mut Server, ev: CreateTeleporterEvent) {
|
||||
server
|
||||
.state
|
||||
.create_teleporter(comp::Pos(ev.0), ev.1)
|
||||
.build();
|
||||
match ev.entity {
|
||||
SpecialEntity::Waypoint => {
|
||||
server
|
||||
.state
|
||||
.create_object(Pos(ev.pos), comp::object::Body::CampfireLit)
|
||||
.with(LightEmitter {
|
||||
col: Rgb::new(1.0, 0.3, 0.1),
|
||||
strength: 5.0,
|
||||
flicker: 1.0,
|
||||
animated: true,
|
||||
})
|
||||
.with(WaypointArea::default())
|
||||
.with(comp::Immovable)
|
||||
.with(comp::EnteredAuras::default())
|
||||
.with(comp::Auras::new(vec![
|
||||
Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::CampfireHeal,
|
||||
data: BuffData::new(0.02, Some(Secs(1.0))),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
5.0,
|
||||
None,
|
||||
AuraTarget::All,
|
||||
Time(time),
|
||||
),
|
||||
Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::Burning,
|
||||
data: BuffData::new(2.0, Some(Secs(10.0))),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
0.7,
|
||||
None,
|
||||
AuraTarget::All,
|
||||
Time(time),
|
||||
),
|
||||
]))
|
||||
.build();
|
||||
},
|
||||
SpecialEntity::Teleporter(portal) => {
|
||||
server
|
||||
.state
|
||||
.create_teleporter(comp::Pos(ev.pos), portal)
|
||||
.build();
|
||||
},
|
||||
SpecialEntity::ArenaTotem { range } => {
|
||||
server
|
||||
.state
|
||||
.create_object(Pos(ev.pos), comp::object::Body::GnarlingTotemGreen)
|
||||
.with(comp::Immovable)
|
||||
.with(comp::EnteredAuras::default())
|
||||
.with(comp::Auras::new(vec![
|
||||
Aura::new(
|
||||
AuraKind::FriendlyFire,
|
||||
range,
|
||||
None,
|
||||
AuraTarget::All,
|
||||
Time(time),
|
||||
),
|
||||
Aura::new(AuraKind::ForcePvP, range, None, AuraTarget::All, Time(time)),
|
||||
]))
|
||||
.build();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_create_item_drop(server: &mut Server, ev: CreateItemDropEvent) {
|
||||
|
@ -18,7 +18,9 @@ use common::{
|
||||
combat,
|
||||
combat::{AttackSource, DamageContributor},
|
||||
comp::{
|
||||
self, aura, buff,
|
||||
self,
|
||||
aura::{self, EnteredAuras},
|
||||
buff,
|
||||
chat::{KillSource, KillType},
|
||||
inventory::item::{AbilityMap, MaterialStatManifest},
|
||||
item::flatten_counted_items,
|
||||
@ -945,6 +947,7 @@ impl ServerEvent for ExplosionEvent {
|
||||
ReadStorage<'a, comp::Combo>,
|
||||
ReadStorage<'a, Inventory>,
|
||||
ReadStorage<'a, Alignment>,
|
||||
ReadStorage<'a, EnteredAuras>,
|
||||
ReadStorage<'a, comp::Buffs>,
|
||||
ReadStorage<'a, comp::Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
@ -975,6 +978,7 @@ impl ServerEvent for ExplosionEvent {
|
||||
combos,
|
||||
inventories,
|
||||
alignments,
|
||||
entered_auras,
|
||||
buffs,
|
||||
stats,
|
||||
healths,
|
||||
@ -1274,17 +1278,27 @@ impl ServerEvent for ExplosionEvent {
|
||||
let target_dodging = char_state_b_maybe
|
||||
.and_then(|cs| cs.attack_immunities())
|
||||
.map_or(false, |i| i.explosions);
|
||||
let allow_friendly_fire =
|
||||
owner_entity.is_some_and(|owner_entity| {
|
||||
combat::allow_friendly_fire(
|
||||
&entered_auras,
|
||||
owner_entity,
|
||||
entity_b,
|
||||
)
|
||||
});
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
let permit_pvp = combat::permit_pvp(
|
||||
&alignments,
|
||||
&players,
|
||||
&entered_auras,
|
||||
&id_maps,
|
||||
owner_entity,
|
||||
entity_b,
|
||||
);
|
||||
let attack_options = combat::AttackOptions {
|
||||
target_dodging,
|
||||
may_harm,
|
||||
permit_pvp,
|
||||
allow_friendly_fire,
|
||||
target_group,
|
||||
precision_mult: None,
|
||||
};
|
||||
@ -1322,8 +1336,9 @@ impl ServerEvent for ExplosionEvent {
|
||||
1.0 - distance_squared / ev.explosion.radius.powi(2)
|
||||
};
|
||||
|
||||
// Player check only accounts for PvP/PvE flag, but bombs
|
||||
// are intented to do friendly fire.
|
||||
// Player check only accounts for PvP/PvE flag (unless in a friendly
|
||||
// fire aura), but bombs are intented to do
|
||||
// friendly fire.
|
||||
//
|
||||
// What exactly is friendly fire is subject to discussion.
|
||||
// As we probably want to minimize possibility of being dick
|
||||
@ -1331,10 +1346,11 @@ impl ServerEvent for ExplosionEvent {
|
||||
// you want to harm yourself.
|
||||
//
|
||||
// This can be changed later.
|
||||
let may_harm = || {
|
||||
combat::may_harm(
|
||||
let permit_pvp = || {
|
||||
combat::permit_pvp(
|
||||
&alignments,
|
||||
&players,
|
||||
&entered_auras,
|
||||
&id_maps,
|
||||
owner_entity,
|
||||
entity_b,
|
||||
@ -1345,7 +1361,7 @@ impl ServerEvent for ExplosionEvent {
|
||||
|
||||
if is_alive {
|
||||
effect.modify_strength(strength);
|
||||
if !effect.is_harm() || may_harm() {
|
||||
if !effect.is_harm() || permit_pvp() {
|
||||
emit_effect_events(
|
||||
&mut emitters,
|
||||
*time,
|
||||
@ -1504,11 +1520,16 @@ impl ServerEvent for BonkEvent {
|
||||
}
|
||||
|
||||
impl ServerEvent for AuraEvent {
|
||||
type SystemData<'a> = WriteStorage<'a, Auras>;
|
||||
type SystemData<'a> = (WriteStorage<'a, Auras>, WriteStorage<'a, EnteredAuras>);
|
||||
|
||||
fn handle(events: impl ExactSizeIterator<Item = Self>, mut auras: Self::SystemData<'_>) {
|
||||
fn handle(
|
||||
events: impl ExactSizeIterator<Item = Self>,
|
||||
(mut auras, mut entered_auras): Self::SystemData<'_>,
|
||||
) {
|
||||
for ev in events {
|
||||
if let Some(mut auras) = auras.get_mut(ev.entity) {
|
||||
if let (Some(mut auras), Some(mut entered_auras)) =
|
||||
(auras.get_mut(ev.entity), entered_auras.get_mut(ev.entity))
|
||||
{
|
||||
use aura::AuraChange;
|
||||
match ev.aura_change {
|
||||
AuraChange::Add(new_aura) => {
|
||||
@ -1519,6 +1540,24 @@ impl ServerEvent for AuraEvent {
|
||||
auras.remove(key);
|
||||
}
|
||||
},
|
||||
AuraChange::EnterAura(uid, key, variant) => {
|
||||
entered_auras
|
||||
.auras
|
||||
.entry(variant)
|
||||
.and_modify(|entered_auras| {
|
||||
entered_auras.insert((uid, key));
|
||||
})
|
||||
.or_insert_with(|| <_ as Into<_>>::into([(uid, key)]));
|
||||
},
|
||||
AuraChange::ExitAura(uid, key, variant) => {
|
||||
if let Some(entered_auras_variant) = entered_auras.auras.get_mut(&variant) {
|
||||
entered_auras_variant.remove(&(uid, key));
|
||||
|
||||
if entered_auras_variant.is_empty() {
|
||||
entered_auras.auras.remove(&variant);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2126,8 +2165,7 @@ pub fn handle_transform(
|
||||
#[derive(Debug)]
|
||||
pub enum TransformEntityError {
|
||||
EntityDead,
|
||||
UnexpectedNpcWaypoint,
|
||||
UnexpectedNpcTeleporter,
|
||||
UnexpectedSpecialEntity,
|
||||
LoadingCharacter,
|
||||
EntityIsPlayer,
|
||||
}
|
||||
@ -2269,11 +2307,8 @@ pub fn transform_entity(
|
||||
}
|
||||
}
|
||||
},
|
||||
SpawnEntityData::Waypoint(_) => {
|
||||
return Err(TransformEntityError::UnexpectedNpcWaypoint);
|
||||
},
|
||||
SpawnEntityData::Teleporter(_, _) => {
|
||||
return Err(TransformEntityError::UnexpectedNpcTeleporter);
|
||||
SpawnEntityData::Special(_, _) => {
|
||||
return Err(TransformEntityError::UnexpectedSpecialEntity);
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -14,8 +14,8 @@ use specs::{
|
||||
use self::{
|
||||
entity_creation::{
|
||||
handle_create_item_drop, handle_create_npc, handle_create_object, handle_create_ship,
|
||||
handle_create_teleporter, handle_create_waypoint, handle_initialize_character,
|
||||
handle_initialize_spectator, handle_loaded_character_data, handle_shockwave, handle_shoot,
|
||||
handle_create_special_entity, handle_initialize_character, handle_initialize_spectator,
|
||||
handle_loaded_character_data, handle_shockwave, handle_shoot,
|
||||
},
|
||||
entity_manipulation::{handle_delete, handle_transform},
|
||||
interaction::handle_tame_pet,
|
||||
@ -146,8 +146,7 @@ impl Server {
|
||||
self.handle_serial_events(handle_create_ship);
|
||||
self.handle_serial_events(handle_shoot);
|
||||
self.handle_serial_events(handle_shockwave);
|
||||
self.handle_serial_events(handle_create_waypoint);
|
||||
self.handle_serial_events(handle_create_teleporter);
|
||||
self.handle_serial_events(handle_create_special_entity);
|
||||
self.handle_serial_events(handle_create_item_drop);
|
||||
self.handle_serial_events(handle_create_object);
|
||||
self.handle_serial_events(handle_delete);
|
||||
|
@ -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 {
|
||||
|
@ -13,12 +13,10 @@ use crate::{
|
||||
use common::{
|
||||
calendar::Calendar,
|
||||
comp::{
|
||||
self, agent, biped_small, bird_medium, misc::PortalData, BehaviorCapability, ForceUpdate,
|
||||
Pos, Presence, Waypoint,
|
||||
},
|
||||
event::{
|
||||
CreateNpcEvent, CreateTeleporterEvent, CreateWaypointEvent, EmitExt, EventBus, NpcBuilder,
|
||||
self, agent, biped_small, bird_medium, BehaviorCapability, ForceUpdate, Pos, Presence,
|
||||
Waypoint,
|
||||
},
|
||||
event::{CreateNpcEvent, CreateSpecialEntityEvent, EmitExt, EventBus, NpcBuilder},
|
||||
event_emitters,
|
||||
generation::{EntityInfo, SpecialEntity},
|
||||
lottery::LootSpec,
|
||||
@ -58,8 +56,7 @@ type RtSimData<'a> = ();
|
||||
event_emitters! {
|
||||
struct Events[Emitters] {
|
||||
create_npc: CreateNpcEvent,
|
||||
create_waypoint: CreateWaypointEvent,
|
||||
create_teleporter: CreateTeleporterEvent,
|
||||
create_waypoint: CreateSpecialEntityEvent,
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,8 +204,8 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let data = SpawnEntityData::from_entity_info(entity);
|
||||
match data {
|
||||
SpawnEntityData::Waypoint(pos) => {
|
||||
emitters.emit(CreateWaypointEvent(pos));
|
||||
SpawnEntityData::Special(pos, entity) => {
|
||||
emitters.emit(CreateSpecialEntityEvent { pos, entity });
|
||||
},
|
||||
SpawnEntityData::Npc(data) => {
|
||||
let (npc_builder, pos) = data.to_npc_builder();
|
||||
@ -220,9 +217,6 @@ impl<'a> System<'a> for Sys {
|
||||
rider: None,
|
||||
});
|
||||
},
|
||||
SpawnEntityData::Teleporter(pos, teleporter) => {
|
||||
emitters.emit(CreateTeleporterEvent(pos, teleporter));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -423,8 +417,7 @@ pub struct NpcData {
|
||||
#[derive(Debug)]
|
||||
pub enum SpawnEntityData {
|
||||
Npc(NpcData),
|
||||
Waypoint(Vec3<f32>),
|
||||
Teleporter(Vec3<f32>, PortalData),
|
||||
Special(Vec3<f32>, SpecialEntity),
|
||||
}
|
||||
|
||||
impl SpawnEntityData {
|
||||
@ -454,10 +447,7 @@ impl SpawnEntityData {
|
||||
} = entity;
|
||||
|
||||
if let Some(special) = special_entity {
|
||||
return match special {
|
||||
SpecialEntity::Waypoint => Self::Waypoint(pos),
|
||||
SpecialEntity::Teleporter(teleporter) => Self::Teleporter(pos, teleporter),
|
||||
};
|
||||
return Self::Special(pos, special);
|
||||
}
|
||||
|
||||
let name = name.unwrap_or_else(|| "Unnamed".to_string());
|
||||
|
@ -26,7 +26,10 @@ use crate::{
|
||||
admin::draw_admin_commands_window, character_states::draw_char_state_group,
|
||||
experimental_shaders::draw_experimental_shaders_window, widgets::two_col_row,
|
||||
};
|
||||
use common::comp::{aura::AuraKind::Buff, Body, Fluid};
|
||||
use common::comp::{
|
||||
aura::AuraKind::{Buff, ForcePvP, FriendlyFire},
|
||||
Body, Fluid,
|
||||
};
|
||||
use egui_winit_platform::Platform;
|
||||
use std::time::Duration;
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
@ -711,7 +714,9 @@ fn selected_entity_window(
|
||||
ui.end_row();
|
||||
auras.auras.iter().for_each(|(_, v)| {
|
||||
ui.label(match v.aura_kind {
|
||||
Buff { kind, .. } => format!("Buff - {:?}", kind)
|
||||
Buff { kind, .. } => format!("Buff - {:?}", kind),
|
||||
FriendlyFire => "Friendly Fire".to_string(),
|
||||
ForcePvP => "ForcedPvP".to_string(),
|
||||
});
|
||||
ui.label(format!("{:1}", v.radius));
|
||||
ui.label(v.end_time.map_or("-".to_owned(), |x| format!("{:1}s", x.0 - time.0)));
|
||||
|
@ -1861,8 +1861,8 @@ impl ParticleMgr {
|
||||
Duration::from_secs(3),
|
||||
time,
|
||||
ParticleMode::Ice,
|
||||
pos.x + Vec3::unit_z() * z_start,
|
||||
pos.x + offset.with_z(z_end),
|
||||
pos + Vec3::unit_z() * z_start,
|
||||
pos + offset.with_z(z_end),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
@ -1234,9 +1234,14 @@ impl Structure for DesertCityArena {
|
||||
.fill(sandstone.clone());
|
||||
// campfires & repair benches
|
||||
painter.spawn(
|
||||
EntityInfo::at((spire_pos - 2).with_z(base - 1).map(|e| e as f32))
|
||||
EntityInfo::at((spire_pos - 2).with_z(base - 1).as_())
|
||||
.into_special(SpecialEntity::Waypoint),
|
||||
);
|
||||
painter.spawn(EntityInfo::at(center.with_z(base).as_()).into_special(
|
||||
SpecialEntity::ArenaTotem {
|
||||
range: length as f32,
|
||||
},
|
||||
));
|
||||
painter.sprite((spire_pos + 2).with_z(base - 1), SpriteKind::RepairBench);
|
||||
|
||||
// lamps
|
||||
|
Loading…
Reference in New Issue
Block a user