mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
263 lines
9.8 KiB
Rust
263 lines
9.8 KiB
Rust
use common::{
|
|
combat,
|
|
comp::{
|
|
aura::{AuraChange, AuraKey, AuraKind, AuraTarget},
|
|
buff::{Buff, BuffCategory, BuffChange, BuffSource},
|
|
group::Group,
|
|
Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos,
|
|
},
|
|
event::{Emitter, EventBus, ServerEvent},
|
|
resources::DeltaTime,
|
|
uid::{Uid, UidAllocator},
|
|
};
|
|
use common_ecs::{Job, Origin, Phase, System};
|
|
use specs::{
|
|
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
|
|
ReadStorage, SystemData, World, WriteStorage,
|
|
};
|
|
use std::time::Duration;
|
|
|
|
#[derive(SystemData)]
|
|
pub struct ReadData<'a> {
|
|
entities: Entities<'a>,
|
|
players: ReadStorage<'a, Player>,
|
|
dt: Read<'a, DeltaTime>,
|
|
server_bus: Read<'a, EventBus<ServerEvent>>,
|
|
uid_allocator: Read<'a, UidAllocator>,
|
|
cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
|
|
positions: ReadStorage<'a, Pos>,
|
|
char_states: ReadStorage<'a, CharacterState>,
|
|
alignments: ReadStorage<'a, Alignment>,
|
|
healths: ReadStorage<'a, Health>,
|
|
groups: ReadStorage<'a, Group>,
|
|
uids: ReadStorage<'a, Uid>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct Sys;
|
|
impl<'a> System<'a> for Sys {
|
|
type SystemData = (
|
|
ReadData<'a>,
|
|
WriteStorage<'a, Auras>,
|
|
WriteStorage<'a, Buffs>,
|
|
);
|
|
|
|
const NAME: &'static str = "aura";
|
|
const ORIGIN: Origin = Origin::Common;
|
|
const PHASE: Phase = Phase::Create;
|
|
|
|
fn run(_job: &mut Job<Self>, (read_data, mut auras, mut buffs): Self::SystemData) {
|
|
let mut server_emitter = read_data.server_bus.emitter();
|
|
let dt = read_data.dt.0;
|
|
|
|
auras.set_event_emission(false);
|
|
buffs.set_event_emission(false);
|
|
|
|
// Iterate through all buffs, on any buffs that are from an aura, sets the check
|
|
// for whether the buff recently set by aura to false
|
|
for (_, mut buffs_comp) in (&read_data.entities, &mut buffs).join() {
|
|
for (_, buff) in buffs_comp.buffs.iter_mut() {
|
|
if let Some(cat_id) = buff
|
|
.cat_ids
|
|
.iter_mut()
|
|
.find(|cat_id| matches!(cat_id, BuffCategory::FromAura(true)))
|
|
{
|
|
*cat_id = BuffCategory::FromAura(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Iterate through all entities with an aura
|
|
for (entity, pos, mut auras_comp) in
|
|
(&read_data.entities, &read_data.positions, &mut auras).join()
|
|
{
|
|
let mut expired_auras = Vec::<AuraKey>::new();
|
|
// Iterate through the auras attached to this entity
|
|
for (key, aura) in auras_comp.auras.iter_mut() {
|
|
// Tick the aura and subtract dt from it
|
|
if let Some(remaining_time) = &mut aura.duration {
|
|
if let Some(new_duration) =
|
|
remaining_time.checked_sub(Duration::from_secs_f32(dt))
|
|
{
|
|
*remaining_time = new_duration;
|
|
} else {
|
|
*remaining_time = Duration::default();
|
|
expired_auras.push(key);
|
|
}
|
|
}
|
|
let target_iter = read_data
|
|
.cached_spatial_grid
|
|
.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)
|
|
})
|
|
});
|
|
target_iter.for_each(|(target, target_pos, health, target_uid)| {
|
|
let mut target_buffs = match buffs.get_mut(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
|
|
.uid_allocator
|
|
.retrieve_entity_internal(uid.into())
|
|
.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 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(
|
|
aura,
|
|
target,
|
|
health,
|
|
&mut target_buffs,
|
|
&read_data,
|
|
&mut server_emitter,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if !expired_auras.is_empty() {
|
|
server_emitter.emit(ServerEvent::Aura {
|
|
entity,
|
|
aura_change: AuraChange::RemoveByKey(expired_auras),
|
|
});
|
|
}
|
|
}
|
|
auras.set_event_emission(true);
|
|
buffs.set_event_emission(true);
|
|
}
|
|
}
|
|
|
|
#[warn(clippy::pedantic)]
|
|
//#[warn(clippy::nursery)]
|
|
fn activate_aura(
|
|
aura: &Aura,
|
|
target: EcsEntity,
|
|
health: &Health,
|
|
target_buffs: &mut Buffs,
|
|
read_data: &ReadData,
|
|
server_emitter: &mut Emitter<ServerEvent>,
|
|
) {
|
|
let should_activate = match aura.aura_kind {
|
|
AuraKind::Buff { kind, source, .. } => {
|
|
let conditions_held = match kind {
|
|
BuffKind::CampfireHeal => read_data
|
|
.char_states
|
|
.get(target)
|
|
.map_or(false, |target_state| {
|
|
target_state.is_sitting() && health.current() < health.maximum()
|
|
}),
|
|
// Add other specific buff conditions here
|
|
_ => true,
|
|
};
|
|
|
|
// TODO: this check will disable friendly fire with PvE switch.
|
|
//
|
|
// Which means that you can't apply debuffs on you and your group
|
|
// even if it's intended mechanic.
|
|
//
|
|
// We don't have this for now, but think about this
|
|
// when we will add this.
|
|
let may_harm = || {
|
|
let owner = match source {
|
|
BuffSource::Character { by } => {
|
|
read_data.uid_allocator.retrieve_entity_internal(by.into())
|
|
},
|
|
_ => None,
|
|
};
|
|
combat::may_harm(
|
|
&read_data.alignments,
|
|
&read_data.players,
|
|
&read_data.uid_allocator,
|
|
owner,
|
|
target,
|
|
)
|
|
};
|
|
|
|
conditions_held && (kind.is_buff() || may_harm())
|
|
},
|
|
};
|
|
|
|
if !should_activate {
|
|
return;
|
|
}
|
|
|
|
// TODO: When more aura kinds (besides Buff) are
|
|
// implemented, match on them here
|
|
match aura.aura_kind {
|
|
AuraKind::Buff {
|
|
kind,
|
|
data,
|
|
category,
|
|
source,
|
|
} => {
|
|
// Checks that target is not already receiving a buff
|
|
// from an aura, where the buff is of the same kind,
|
|
// and is of at least the same strength
|
|
// and of at least the same duration.
|
|
// If no such buff is present, adds the buff.
|
|
let emit_buff = !target_buffs.buffs.iter().any(|(_, buff)| {
|
|
buff.cat_ids
|
|
.iter()
|
|
.any(|cat_id| matches!(cat_id, BuffCategory::FromAura(_)))
|
|
&& buff.kind == kind
|
|
&& buff.data.strength >= data.strength
|
|
&& buff.time.map_or(true, |dur| {
|
|
data.duration.map_or(false, |dur_2| dur >= dur_2)
|
|
})
|
|
});
|
|
if emit_buff {
|
|
server_emitter.emit(ServerEvent::Buff {
|
|
entity: target,
|
|
buff_change: BuffChange::Add(Buff::new(
|
|
kind,
|
|
data,
|
|
vec![category, BuffCategory::FromAura(true)],
|
|
source,
|
|
)),
|
|
});
|
|
}
|
|
// Finds all buffs on target that are from an aura, are of
|
|
// the same buff kind, and are of at most the same strength.
|
|
// For any such buffs, marks it as recently applied.
|
|
for (_, buff) in target_buffs.buffs.iter_mut().filter(|(_, buff)| {
|
|
buff.cat_ids
|
|
.iter()
|
|
.any(|cat_id| matches!(cat_id, BuffCategory::FromAura(_)))
|
|
&& buff.kind == kind
|
|
&& buff.data.strength <= data.strength
|
|
}) {
|
|
if let Some(cat_id) = buff
|
|
.cat_ids
|
|
.iter_mut()
|
|
.find(|cat_id| matches!(cat_id, BuffCategory::FromAura(false)))
|
|
{
|
|
*cat_id = BuffCategory::FromAura(true);
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|