mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'juliancoffee/handle_pvp' into 'master'
Handle PvP/PvE See merge request veloren/veloren!2686
This commit is contained in:
commit
f4e04ecefc
@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Healing sceptre crafting recipe
|
- Healing sceptre crafting recipe
|
||||||
- NPCs can now warn players before engaging in combat
|
- NPCs can now warn players before engaging in combat
|
||||||
- Custom error message when a supported graphics backend can not be found
|
- Custom error message when a supported graphics backend can not be found
|
||||||
|
- Add server setting with PvE/PvP switch
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ enum-iterator = "0.6"
|
|||||||
vek = { version = "=0.14.1", features = ["serde"] }
|
vek = { version = "=0.14.1", features = ["serde"] }
|
||||||
|
|
||||||
# Strum
|
# Strum
|
||||||
strum = "0.21"
|
strum = { version = "0.21", features = ["derive"] }
|
||||||
|
# TODO: remove this and rewrite every use of strum_macros to strum
|
||||||
strum_macros = "0.21"
|
strum_macros = "0.21"
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
|||||||
poise::PoiseChange,
|
poise::PoiseChange,
|
||||||
skills::SkillGroupKind,
|
skills::SkillGroupKind,
|
||||||
Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange,
|
Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange,
|
||||||
HealthSource, Inventory, Ori, SkillSet, Stats,
|
HealthSource, Inventory, Ori, Player, SkillSet, Stats,
|
||||||
},
|
},
|
||||||
event::ServerEvent,
|
event::ServerEvent,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
@ -71,6 +71,13 @@ pub struct TargetInfo<'a> {
|
|||||||
pub char_state: Option<&'a CharacterState>,
|
pub char_state: Option<&'a CharacterState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct AttackOptions {
|
||||||
|
pub target_dodging: bool,
|
||||||
|
pub may_harm: bool,
|
||||||
|
pub target_group: GroupTarget,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive
|
#[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive
|
||||||
pub struct Attack {
|
pub struct Attack {
|
||||||
@ -161,25 +168,44 @@ impl Attack {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn apply_attack(
|
pub fn apply_attack(
|
||||||
&self,
|
&self,
|
||||||
target_group: GroupTarget,
|
|
||||||
attacker: Option<AttackerInfo>,
|
attacker: Option<AttackerInfo>,
|
||||||
target: TargetInfo,
|
target: TargetInfo,
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
target_dodging: bool,
|
options: AttackOptions,
|
||||||
// Currently just modifies damage, maybe look into modifying strength of other effects?
|
// Currently strength_modifier just modifies damage,
|
||||||
|
// maybe look into modifying strength of other effects?
|
||||||
strength_modifier: f32,
|
strength_modifier: f32,
|
||||||
attack_source: AttackSource,
|
attack_source: AttackSource,
|
||||||
mut emit: impl FnMut(ServerEvent),
|
mut emit: impl FnMut(ServerEvent),
|
||||||
mut emit_outcome: impl FnMut(Outcome),
|
mut emit_outcome: impl FnMut(Outcome),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut is_applied = false;
|
let AttackOptions {
|
||||||
|
target_dodging,
|
||||||
|
may_harm,
|
||||||
|
target_group,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// target == OutOfGroup is basic heuristic that this
|
||||||
|
// "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
|
||||||
|
let avoid_damage = |attack_damage: &AttackDamage| {
|
||||||
|
matches!(attack_damage.target, Some(GroupTarget::OutOfGroup))
|
||||||
|
&& (target_dodging || !may_harm)
|
||||||
|
};
|
||||||
|
let avoid_effect = |attack_effect: &AttackEffect| {
|
||||||
|
matches!(attack_effect.target, Some(GroupTarget::OutOfGroup))
|
||||||
|
&& (target_dodging || !may_harm)
|
||||||
|
};
|
||||||
let is_crit = thread_rng().gen::<f32>() < self.crit_chance;
|
let is_crit = thread_rng().gen::<f32>() < self.crit_chance;
|
||||||
|
let mut is_applied = false;
|
||||||
let mut accumulated_damage = 0.0;
|
let mut accumulated_damage = 0.0;
|
||||||
for damage in self
|
for damage in self
|
||||||
.damages
|
.damages
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|d| d.target.map_or(true, |t| t == target_group))
|
.filter(|d| d.target.map_or(true, |t| t == target_group))
|
||||||
.filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging))
|
.filter(|d| !avoid_damage(d))
|
||||||
{
|
{
|
||||||
is_applied = true;
|
is_applied = true;
|
||||||
let damage_reduction = Attack::compute_damage_reduction(
|
let damage_reduction = Attack::compute_damage_reduction(
|
||||||
@ -294,7 +320,7 @@ impl Attack {
|
|||||||
.effects
|
.effects
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|e| e.target.map_or(true, |t| t == target_group))
|
.filter(|e| e.target.map_or(true, |t| t == target_group))
|
||||||
.filter(|e| !(matches!(e.target, Some(GroupTarget::OutOfGroup)) && target_dodging))
|
.filter(|e| !avoid_effect(e))
|
||||||
{
|
{
|
||||||
if effect.requirements.iter().all(|req| match req {
|
if effect.requirements.iter().all(|req| match req {
|
||||||
CombatRequirement::AnyDamage => accumulated_damage > 0.0 && target.health.is_some(),
|
CombatRequirement::AnyDamage => accumulated_damage > 0.0 && target.health.is_some(),
|
||||||
@ -430,6 +456,17 @@ impl Attack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if we should allow negative effects from one player to another
|
||||||
|
// FIXME: handle pets?
|
||||||
|
// This code works only with players.
|
||||||
|
// You still can kill someone's pet and
|
||||||
|
// you still can be killed by someone's pet
|
||||||
|
pub fn may_harm(attacker: Option<&Player>, target: Option<&Player>) -> bool {
|
||||||
|
attacker
|
||||||
|
.zip(target)
|
||||||
|
.map_or(true, |(attacker, target)| attacker.may_harm(target))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct AttackDamage {
|
pub struct AttackDamage {
|
||||||
|
@ -81,22 +81,22 @@ impl BuffKind {
|
|||||||
/// Checks if buff is buff or debuff
|
/// Checks if buff is buff or debuff
|
||||||
pub fn is_buff(self) -> bool {
|
pub fn is_buff(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
BuffKind::Regeneration => true,
|
BuffKind::Regeneration
|
||||||
BuffKind::Saturation => true,
|
| BuffKind::Saturation
|
||||||
BuffKind::Bleeding => false,
|
| BuffKind::Potion
|
||||||
BuffKind::Cursed => false,
|
| BuffKind::CampfireHeal
|
||||||
BuffKind::Potion => true,
|
| BuffKind::IncreaseMaxEnergy
|
||||||
BuffKind::CampfireHeal => true,
|
| BuffKind::IncreaseMaxHealth
|
||||||
BuffKind::IncreaseMaxEnergy => true,
|
| BuffKind::Invulnerability
|
||||||
BuffKind::IncreaseMaxHealth => true,
|
| BuffKind::ProtectingWard => true,
|
||||||
BuffKind::Invulnerability => true,
|
BuffKind::Bleeding
|
||||||
BuffKind::ProtectingWard => true,
|
| BuffKind::Cursed
|
||||||
BuffKind::Burning => false,
|
| BuffKind::Burning
|
||||||
BuffKind::Crippled => false,
|
| BuffKind::Crippled
|
||||||
BuffKind::Frenzied => true,
|
| BuffKind::Frenzied
|
||||||
BuffKind::Frozen => false,
|
| BuffKind::Frozen
|
||||||
BuffKind::Wet => false,
|
| BuffKind::Wet
|
||||||
BuffKind::Ensnared => false,
|
| BuffKind::Ensnared => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ use specs::{Component, DerefFlaggedStorage, NullStorage};
|
|||||||
use specs_idvs::IdvStorage;
|
use specs_idvs::IdvStorage;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::resources::BattleMode;
|
||||||
|
|
||||||
const MAX_ALIAS_LEN: usize = 32;
|
const MAX_ALIAS_LEN: usize = 32;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -17,11 +19,31 @@ pub enum DisconnectReason {
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub alias: String,
|
pub alias: String,
|
||||||
|
pub battle_mode: BattleMode,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BattleMode {
|
||||||
|
pub fn may_harm(self, other: Self) -> bool {
|
||||||
|
matches!((self, other), (BattleMode::PvP, BattleMode::PvP))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new(alias: String, uuid: Uuid) -> Self { Self { alias, uuid } }
|
pub fn new(alias: String, battle_mode: BattleMode, uuid: Uuid) -> Self {
|
||||||
|
Self {
|
||||||
|
alias,
|
||||||
|
battle_mode,
|
||||||
|
uuid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Currently we allow attacking only if both players are opt-in to PvP.
|
||||||
|
///
|
||||||
|
/// Simple as tea, if they don't want the tea, don't make them drink the
|
||||||
|
/// tea.
|
||||||
|
/// You can make tea for yourself though.
|
||||||
|
pub fn may_harm(&self, other: &Player) -> bool { self.battle_mode.may_harm(other.battle_mode) }
|
||||||
|
|
||||||
pub fn is_valid(&self) -> bool { Self::alias_validate(&self.alias).is_ok() }
|
pub fn is_valid(&self) -> bool { Self::alias_validate(&self.alias).is_ok() }
|
||||||
|
|
||||||
|
@ -28,6 +28,15 @@ impl Effect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_harm(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Effect::Health(c) => c.amount < 0,
|
||||||
|
Effect::PoiseChange(c) => c.amount < 0,
|
||||||
|
Effect::Damage(_) => true,
|
||||||
|
Effect::Buff(e) => !e.kind.is_buff(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn modify_strength(&mut self, modifier: f32) {
|
pub fn modify_strength(&mut self, modifier: f32) {
|
||||||
match self {
|
match self {
|
||||||
Effect::Health(change) => {
|
Effect::Health(change) => {
|
||||||
|
@ -74,3 +74,13 @@ impl PlayerPhysicsSetting {
|
|||||||
pub struct PlayerPhysicsSettings {
|
pub struct PlayerPhysicsSettings {
|
||||||
pub settings: hashbrown::HashMap<uuid::Uuid, PlayerPhysicsSetting>,
|
pub settings: hashbrown::HashMap<uuid::Uuid, PlayerPhysicsSetting>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describe how players interact with other players.
|
||||||
|
///
|
||||||
|
/// May be removed when we will discover better way
|
||||||
|
/// to handle duels and murders
|
||||||
|
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub enum BattleMode {
|
||||||
|
PvP,
|
||||||
|
PvE,
|
||||||
|
}
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
use common::{
|
use common::{
|
||||||
|
combat,
|
||||||
comp::{
|
comp::{
|
||||||
aura::{AuraChange, AuraKey, AuraKind, AuraTarget},
|
aura::{AuraChange, AuraKey, AuraKind, AuraTarget},
|
||||||
buff::{self, BuffCategory},
|
buff::{Buff, BuffCategory, BuffChange, BuffSource},
|
||||||
group::Group,
|
group::Group,
|
||||||
Auras, BuffKind, Buffs, CharacterState, Health, Pos,
|
Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{Emitter, EventBus, ServerEvent},
|
||||||
resources::DeltaTime,
|
resources::DeltaTime,
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use specs::{
|
use specs::{
|
||||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
|
||||||
World, WriteStorage,
|
ReadStorage, SystemData, World, WriteStorage,
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(SystemData)]
|
#[derive(SystemData)]
|
||||||
pub struct ReadData<'a> {
|
pub struct ReadData<'a> {
|
||||||
entities: Entities<'a>,
|
entities: Entities<'a>,
|
||||||
|
players: ReadStorage<'a, Player>,
|
||||||
dt: Read<'a, DeltaTime>,
|
dt: Read<'a, DeltaTime>,
|
||||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||||
uid_allocator: Read<'a, UidAllocator>,
|
uid_allocator: Read<'a, UidAllocator>,
|
||||||
@ -101,91 +103,36 @@ impl<'a> System<'a> for Sys {
|
|||||||
Some(buff) => buff,
|
Some(buff) => buff,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure entity is within the aura radius
|
// Ensure entity is within the aura radius
|
||||||
if target_pos.0.distance_squared(pos.0) < aura.radius.powi(2) {
|
if target_pos.0.distance_squared(pos.0) < aura.radius.powi(2) {
|
||||||
if let AuraTarget::GroupOf(uid) = aura.target {
|
// Ensure the entity is in the group we want to target
|
||||||
let same_group = read_data
|
let same_group = |uid: Uid| {
|
||||||
|
read_data
|
||||||
.uid_allocator
|
.uid_allocator
|
||||||
.retrieve_entity_internal(uid.into())
|
.retrieve_entity_internal(uid.into())
|
||||||
.and_then(|e| read_data.groups.get(e))
|
.and_then(|e| read_data.groups.get(e))
|
||||||
.map_or(false, |owner_group| {
|
.map_or(false, |owner_group| {
|
||||||
Some(owner_group) == read_data.groups.get(target)
|
Some(owner_group) == read_data.groups.get(target)
|
||||||
})
|
})
|
||||||
|| *target_uid == uid;
|
|| *target_uid == uid
|
||||||
|
};
|
||||||
|
|
||||||
if !same_group {
|
let is_target = match aura.target {
|
||||||
return;
|
AuraTarget::GroupOf(uid) => same_group(uid),
|
||||||
}
|
AuraTarget::NotGroupOf(uid) => !same_group(uid),
|
||||||
}
|
AuraTarget::All => true,
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: When more aura kinds (besides Buff) are
|
if is_target {
|
||||||
// implemented, match on them here
|
activate_aura(
|
||||||
match aura.aura_kind {
|
aura,
|
||||||
AuraKind::Buff {
|
target,
|
||||||
kind,
|
health,
|
||||||
data,
|
&mut target_buffs,
|
||||||
category,
|
&read_data,
|
||||||
source,
|
&mut server_emitter,
|
||||||
} => {
|
);
|
||||||
let apply_buff = match kind {
|
|
||||||
BuffKind::CampfireHeal => {
|
|
||||||
matches!(
|
|
||||||
read_data.char_states.get(target),
|
|
||||||
Some(CharacterState::Sit)
|
|
||||||
) && health.current() < health.maximum()
|
|
||||||
},
|
|
||||||
// Add other specific buff conditions here
|
|
||||||
_ => true,
|
|
||||||
};
|
|
||||||
if apply_buff {
|
|
||||||
// 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 {
|
|
||||||
use 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -201,3 +148,111 @@ impl<'a> System<'a> for Sys {
|
|||||||
buffs.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 => {
|
||||||
|
let target_state = read_data.char_states.get(target);
|
||||||
|
matches!(target_state, Some(CharacterState::Sit))
|
||||||
|
&& 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 intented mechanics.
|
||||||
|
//
|
||||||
|
// Not that we 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,
|
||||||
|
};
|
||||||
|
owner.map_or(true, |attacker| {
|
||||||
|
let attacker = read_data.players.get(attacker);
|
||||||
|
let target = read_data.players.get(target);
|
||||||
|
combat::may_harm(attacker, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
agent::{Sound, SoundKind},
|
agent::{Sound, SoundKind},
|
||||||
Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource,
|
Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource,
|
||||||
Inventory, Ori, Pos, Scale, Stats,
|
Inventory, Ori, Player, Pos, Scale, Stats,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
@ -26,6 +26,7 @@ use vek::*;
|
|||||||
#[derive(SystemData)]
|
#[derive(SystemData)]
|
||||||
pub struct ReadData<'a> {
|
pub struct ReadData<'a> {
|
||||||
entities: Entities<'a>,
|
entities: Entities<'a>,
|
||||||
|
players: ReadStorage<'a, Player>,
|
||||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||||
time: Read<'a, Time>,
|
time: Read<'a, Time>,
|
||||||
dt: Read<'a, DeltaTime>,
|
dt: Read<'a, DeltaTime>,
|
||||||
@ -212,12 +213,23 @@ impl<'a> System<'a> for Sys {
|
|||||||
char_state: read_data.character_states.get(target),
|
char_state: read_data.character_states.get(target),
|
||||||
};
|
};
|
||||||
|
|
||||||
beam_segment.properties.attack.apply_attack(
|
|
||||||
|
let may_harm = combat::may_harm(
|
||||||
|
beam_owner.and_then(|owner| read_data.players.get(owner)),
|
||||||
|
read_data.players.get(target),
|
||||||
|
);
|
||||||
|
let attack_options = AttackOptions {
|
||||||
|
// No luck with dodging beams
|
||||||
|
target_dodging: false,
|
||||||
|
may_harm,
|
||||||
target_group,
|
target_group,
|
||||||
|
};
|
||||||
|
|
||||||
|
beam_segment.properties.attack.apply_attack(
|
||||||
attacker_info,
|
attacker_info,
|
||||||
target_info,
|
target_info,
|
||||||
ori.look_dir(),
|
ori.look_dir(),
|
||||||
false,
|
attack_options,
|
||||||
1.0,
|
1.0,
|
||||||
AttackSource::Beam,
|
AttackSource::Beam,
|
||||||
|e| server_events.push(e),
|
|e| server_events.push(e),
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
agent::{Sound, SoundKind},
|
agent::{Sound, SoundKind},
|
||||||
Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale,
|
Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Player, Pos,
|
||||||
Stats,
|
Scale, Stats,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
@ -22,6 +22,7 @@ use vek::*;
|
|||||||
pub struct ReadData<'a> {
|
pub struct ReadData<'a> {
|
||||||
time: Read<'a, Time>,
|
time: Read<'a, Time>,
|
||||||
entities: Entities<'a>,
|
entities: Entities<'a>,
|
||||||
|
players: ReadStorage<'a, Player>,
|
||||||
uids: ReadStorage<'a, Uid>,
|
uids: ReadStorage<'a, Uid>,
|
||||||
positions: ReadStorage<'a, Pos>,
|
positions: ReadStorage<'a, Pos>,
|
||||||
orientations: ReadStorage<'a, Ori>,
|
orientations: ReadStorage<'a, Ori>,
|
||||||
@ -115,7 +116,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let rad_b = body_b.radius() * scale_b;
|
let rad_b = body_b.radius() * scale_b;
|
||||||
|
|
||||||
// Check if entity is dodging
|
// Check if entity is dodging
|
||||||
let is_dodge = read_data
|
let target_dodging = read_data
|
||||||
.char_states
|
.char_states
|
||||||
.get(target)
|
.get(target)
|
||||||
.map_or(false, |c_s| c_s.is_melee_dodge());
|
.map_or(false, |c_s| c_s.is_melee_dodge());
|
||||||
@ -161,12 +162,22 @@ impl<'a> System<'a> for Sys {
|
|||||||
char_state: read_data.char_states.get(target),
|
char_state: read_data.char_states.get(target),
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_applied = melee_attack.attack.apply_attack(
|
let may_harm = combat::may_harm(
|
||||||
|
read_data.players.get(attacker),
|
||||||
|
read_data.players.get(target),
|
||||||
|
);
|
||||||
|
|
||||||
|
let attack_options = AttackOptions {
|
||||||
|
target_dodging,
|
||||||
|
may_harm,
|
||||||
target_group,
|
target_group,
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_applied = melee_attack.attack.apply_attack(
|
||||||
attacker_info,
|
attacker_info,
|
||||||
target_info,
|
target_info,
|
||||||
dir,
|
dir,
|
||||||
is_dodge,
|
attack_options,
|
||||||
1.0,
|
1.0,
|
||||||
AttackSource::Melee,
|
AttackSource::Melee,
|
||||||
|e| server_emitter.emit(e),
|
|e| server_emitter.emit(e),
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
agent::{Sound, SoundKind},
|
agent::{Sound, SoundKind},
|
||||||
projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory,
|
projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory,
|
||||||
Ori, PhysicsState, Pos, Projectile, Stats, Vel,
|
Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{Emitter, EventBus, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
resources::{DeltaTime, Time},
|
resources::{DeltaTime, Time},
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
@ -15,8 +15,8 @@ use common::{
|
|||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use specs::{
|
use specs::{
|
||||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
|
||||||
World, Write, WriteStorage,
|
ReadStorage, SystemData, World, Write, WriteStorage,
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -25,6 +25,7 @@ use vek::*;
|
|||||||
pub struct ReadData<'a> {
|
pub struct ReadData<'a> {
|
||||||
time: Read<'a, Time>,
|
time: Read<'a, Time>,
|
||||||
entities: Entities<'a>,
|
entities: Entities<'a>,
|
||||||
|
players: ReadStorage<'a, Player>,
|
||||||
dt: Read<'a, DeltaTime>,
|
dt: Read<'a, DeltaTime>,
|
||||||
uid_allocator: Read<'a, UidAllocator>,
|
uid_allocator: Read<'a, UidAllocator>,
|
||||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||||
@ -114,94 +115,36 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let projectile = &mut *projectile;
|
let projectile = &mut *projectile;
|
||||||
|
|
||||||
|
let entity_of =
|
||||||
|
|uid: Uid| read_data.uid_allocator.retrieve_entity_internal(uid.into());
|
||||||
for effect in projectile.hit_entity.drain(..) {
|
for effect in projectile.hit_entity.drain(..) {
|
||||||
match effect {
|
let owner = projectile.owner.and_then(entity_of);
|
||||||
projectile::Effect::Attack(attack) => {
|
let projectile_info = ProjectileInfo {
|
||||||
if let Some(target) = read_data
|
entity,
|
||||||
.uid_allocator
|
effect,
|
||||||
.retrieve_entity_internal(other.into())
|
owner_uid: projectile.owner,
|
||||||
{
|
owner,
|
||||||
if let (Some(pos), Some(ori)) =
|
ori: orientations.get(entity),
|
||||||
(read_data.positions.get(target), orientations.get(entity))
|
pos,
|
||||||
{
|
};
|
||||||
let dir = ori.look_dir();
|
|
||||||
|
|
||||||
let owner_entity = projectile.owner.and_then(|u| {
|
let target = entity_of(other);
|
||||||
read_data.uid_allocator.retrieve_entity_internal(u.into())
|
let projectile_target_info = ProjectileTargetInfo {
|
||||||
});
|
uid: other,
|
||||||
|
entity: target,
|
||||||
|
target_group,
|
||||||
|
ori: target.and_then(|target| orientations.get(target)),
|
||||||
|
};
|
||||||
|
|
||||||
let attacker_info =
|
dispatch_hit(
|
||||||
owner_entity.zip(projectile.owner).map(|(entity, uid)| {
|
projectile_info,
|
||||||
AttackerInfo {
|
projectile_target_info,
|
||||||
entity,
|
&read_data,
|
||||||
uid,
|
&mut projectile_vanished,
|
||||||
energy: read_data.energies.get(entity),
|
&mut outcomes,
|
||||||
combo: read_data.combos.get(entity),
|
&mut server_emitter,
|
||||||
inventory: read_data.inventories.get(entity),
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let target_info = TargetInfo {
|
|
||||||
entity: target,
|
|
||||||
uid: other,
|
|
||||||
inventory: read_data.inventories.get(target),
|
|
||||||
stats: read_data.stats.get(target),
|
|
||||||
health: read_data.healths.get(target),
|
|
||||||
pos: pos.0,
|
|
||||||
ori: orientations.get(target),
|
|
||||||
char_state: read_data.character_states.get(target),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(&body) = read_data.bodies.get(entity) {
|
|
||||||
outcomes.push(Outcome::ProjectileHit {
|
|
||||||
pos: pos.0,
|
|
||||||
body,
|
|
||||||
vel: read_data
|
|
||||||
.velocities
|
|
||||||
.get(entity)
|
|
||||||
.map_or(Vec3::zero(), |v| v.0),
|
|
||||||
source: projectile.owner,
|
|
||||||
target: read_data.uids.get(target).copied(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
attack.apply_attack(
|
|
||||||
target_group,
|
|
||||||
attacker_info,
|
|
||||||
target_info,
|
|
||||||
dir,
|
|
||||||
false,
|
|
||||||
1.0,
|
|
||||||
AttackSource::Projectile,
|
|
||||||
|e| server_emitter.emit(e),
|
|
||||||
|o| outcomes.push(o),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
projectile::Effect::Explode(e) => {
|
|
||||||
server_emitter.emit(ServerEvent::Explosion {
|
|
||||||
pos: pos.0,
|
|
||||||
explosion: e,
|
|
||||||
owner: projectile.owner,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
projectile::Effect::Vanish => {
|
|
||||||
server_emitter.emit(ServerEvent::Destroy {
|
|
||||||
entity,
|
|
||||||
cause: HealthSource::World,
|
|
||||||
});
|
|
||||||
projectile_vanished = true;
|
|
||||||
},
|
|
||||||
projectile::Effect::Possess => {
|
|
||||||
if other != projectile.owner.unwrap() {
|
|
||||||
if let Some(owner) = projectile.owner {
|
|
||||||
server_emitter.emit(ServerEvent::Possess(owner, other));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if projectile_vanished {
|
if projectile_vanished {
|
||||||
@ -253,3 +196,141 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ProjectileInfo<'a> {
|
||||||
|
entity: EcsEntity,
|
||||||
|
effect: projectile::Effect,
|
||||||
|
owner_uid: Option<Uid>,
|
||||||
|
owner: Option<EcsEntity>,
|
||||||
|
ori: Option<&'a Ori>,
|
||||||
|
pos: &'a Pos,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProjectileTargetInfo<'a> {
|
||||||
|
uid: Uid,
|
||||||
|
entity: Option<EcsEntity>,
|
||||||
|
target_group: GroupTarget,
|
||||||
|
ori: Option<&'a Ori>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_hit(
|
||||||
|
projectile_info: ProjectileInfo,
|
||||||
|
projectile_target_info: ProjectileTargetInfo,
|
||||||
|
read_data: &ReadData,
|
||||||
|
projectile_vanished: &mut bool,
|
||||||
|
outcomes: &mut Vec<Outcome>,
|
||||||
|
server_emitter: &mut Emitter<ServerEvent>,
|
||||||
|
) {
|
||||||
|
match projectile_info.effect {
|
||||||
|
projectile::Effect::Attack(attack) => {
|
||||||
|
let target_uid = projectile_target_info.uid;
|
||||||
|
let target = if let Some(entity) = projectile_target_info.entity {
|
||||||
|
entity
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (target_pos, projectile_dir) = {
|
||||||
|
let target_pos = read_data.positions.get(target);
|
||||||
|
let projectile_ori = projectile_info.ori;
|
||||||
|
match target_pos.zip(projectile_ori) {
|
||||||
|
Some((tgt_pos, proj_ori)) => {
|
||||||
|
let Pos(tgt_pos) = tgt_pos;
|
||||||
|
(*tgt_pos, proj_ori.look_dir())
|
||||||
|
},
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let owner = projectile_info.owner;
|
||||||
|
let projectile_entity = projectile_info.entity;
|
||||||
|
|
||||||
|
let attacker_info =
|
||||||
|
owner
|
||||||
|
.zip(projectile_info.owner_uid)
|
||||||
|
.map(|(entity, uid)| AttackerInfo {
|
||||||
|
entity,
|
||||||
|
uid,
|
||||||
|
energy: read_data.energies.get(entity),
|
||||||
|
combo: read_data.combos.get(entity),
|
||||||
|
inventory: read_data.inventories.get(entity),
|
||||||
|
});
|
||||||
|
|
||||||
|
let target_info = TargetInfo {
|
||||||
|
entity: target,
|
||||||
|
uid: target_uid,
|
||||||
|
inventory: read_data.inventories.get(target),
|
||||||
|
stats: read_data.stats.get(target),
|
||||||
|
health: read_data.healths.get(target),
|
||||||
|
pos: target_pos,
|
||||||
|
ori: projectile_target_info.ori,
|
||||||
|
char_state: read_data.character_states.get(target),
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Is it possible to have projectile without body??
|
||||||
|
if let Some(&body) = read_data.bodies.get(projectile_entity) {
|
||||||
|
outcomes.push(Outcome::ProjectileHit {
|
||||||
|
pos: target_pos,
|
||||||
|
body,
|
||||||
|
vel: read_data
|
||||||
|
.velocities
|
||||||
|
.get(projectile_entity)
|
||||||
|
.map_or(Vec3::zero(), |v| v.0),
|
||||||
|
source: projectile_info.owner_uid,
|
||||||
|
target: read_data.uids.get(target).copied(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let may_harm = combat::may_harm(
|
||||||
|
owner.and_then(|owner| read_data.players.get(owner)),
|
||||||
|
read_data.players.get(target),
|
||||||
|
);
|
||||||
|
|
||||||
|
let attack_options = AttackOptions {
|
||||||
|
// They say witchers can dodge arrows,
|
||||||
|
// but we don't have witchers
|
||||||
|
target_dodging: false,
|
||||||
|
may_harm,
|
||||||
|
target_group: projectile_target_info.target_group,
|
||||||
|
};
|
||||||
|
|
||||||
|
attack.apply_attack(
|
||||||
|
attacker_info,
|
||||||
|
target_info,
|
||||||
|
projectile_dir,
|
||||||
|
attack_options,
|
||||||
|
1.0,
|
||||||
|
AttackSource::Projectile,
|
||||||
|
|e| server_emitter.emit(e),
|
||||||
|
|o| outcomes.push(o),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
projectile::Effect::Explode(e) => {
|
||||||
|
let Pos(pos) = *projectile_info.pos;
|
||||||
|
let owner_uid = projectile_info.owner_uid;
|
||||||
|
server_emitter.emit(ServerEvent::Explosion {
|
||||||
|
pos,
|
||||||
|
explosion: e,
|
||||||
|
owner: owner_uid,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
projectile::Effect::Vanish => {
|
||||||
|
let entity = projectile_info.entity;
|
||||||
|
server_emitter.emit(ServerEvent::Destroy {
|
||||||
|
entity,
|
||||||
|
cause: HealthSource::World,
|
||||||
|
});
|
||||||
|
*projectile_vanished = true;
|
||||||
|
},
|
||||||
|
projectile::Effect::Possess => {
|
||||||
|
let target_uid = projectile_target_info.uid;
|
||||||
|
let owner_uid = projectile_info.owner_uid;
|
||||||
|
if let Some(owner_uid) = owner_uid {
|
||||||
|
if target_uid != owner_uid {
|
||||||
|
server_emitter.emit(ServerEvent::Possess(owner_uid, target_uid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
projectile::Effect::Stick => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
agent::{Sound, SoundKind},
|
agent::{Sound, SoundKind},
|
||||||
Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori,
|
Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori,
|
||||||
PhysicsState, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
|
PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
@ -25,6 +25,7 @@ pub struct ReadData<'a> {
|
|||||||
entities: Entities<'a>,
|
entities: Entities<'a>,
|
||||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||||
time: Read<'a, Time>,
|
time: Read<'a, Time>,
|
||||||
|
players: ReadStorage<'a, Player>,
|
||||||
dt: Read<'a, DeltaTime>,
|
dt: Read<'a, DeltaTime>,
|
||||||
uid_allocator: Read<'a, UidAllocator>,
|
uid_allocator: Read<'a, UidAllocator>,
|
||||||
uids: ReadStorage<'a, Uid>,
|
uids: ReadStorage<'a, Uid>,
|
||||||
@ -208,12 +209,22 @@ impl<'a> System<'a> for Sys {
|
|||||||
char_state: read_data.character_states.get(target),
|
char_state: read_data.character_states.get(target),
|
||||||
};
|
};
|
||||||
|
|
||||||
shockwave.properties.attack.apply_attack(
|
let may_harm = combat::may_harm(
|
||||||
|
shockwave_owner.and_then(|owner| read_data.players.get(owner)),
|
||||||
|
read_data.players.get(target),
|
||||||
|
);
|
||||||
|
let attack_options = AttackOptions {
|
||||||
|
// Trying roll during earthquake isn't the best idea
|
||||||
|
target_dodging: false,
|
||||||
|
may_harm,
|
||||||
target_group,
|
target_group,
|
||||||
|
};
|
||||||
|
|
||||||
|
shockwave.properties.attack.apply_attack(
|
||||||
attacker_info,
|
attacker_info,
|
||||||
target_info,
|
target_info,
|
||||||
dir,
|
dir,
|
||||||
false,
|
attack_options,
|
||||||
1.0,
|
1.0,
|
||||||
AttackSource::Shockwave,
|
AttackSource::Shockwave,
|
||||||
|e| server_emitter.emit(e),
|
|e| server_emitter.emit(e),
|
||||||
|
@ -819,6 +819,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
let energies = &ecs.read_storage::<comp::Energy>();
|
let energies = &ecs.read_storage::<comp::Energy>();
|
||||||
let combos = &ecs.read_storage::<comp::Combo>();
|
let combos = &ecs.read_storage::<comp::Combo>();
|
||||||
let inventories = &ecs.read_storage::<comp::Inventory>();
|
let inventories = &ecs.read_storage::<comp::Inventory>();
|
||||||
|
let players = &ecs.read_storage::<comp::Player>();
|
||||||
for (
|
for (
|
||||||
entity_b,
|
entity_b,
|
||||||
pos_b,
|
pos_b,
|
||||||
@ -887,12 +888,23 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
char_state: char_state_b_maybe,
|
char_state: char_state_b_maybe,
|
||||||
};
|
};
|
||||||
|
|
||||||
attack.apply_attack(
|
let may_harm = combat::may_harm(
|
||||||
|
owner_entity.and_then(|owner| players.get(owner)),
|
||||||
|
players.get(entity_b),
|
||||||
|
);
|
||||||
|
let attack_options = combat::AttackOptions {
|
||||||
|
// cool guyz maybe don't look at explosions
|
||||||
|
// but they still got hurt, it's not Hollywood
|
||||||
|
target_dodging: false,
|
||||||
|
may_harm,
|
||||||
target_group,
|
target_group,
|
||||||
|
};
|
||||||
|
|
||||||
|
attack.apply_attack(
|
||||||
attacker_info,
|
attacker_info,
|
||||||
target_info,
|
target_info,
|
||||||
dir,
|
dir,
|
||||||
false,
|
attack_options,
|
||||||
strength,
|
strength,
|
||||||
combat::AttackSource::Explosion,
|
combat::AttackSource::Explosion,
|
||||||
|e| server_eventbus.emit_now(e),
|
|e| server_eventbus.emit_now(e),
|
||||||
@ -902,6 +914,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
RadiusEffect::Entity(mut effect) => {
|
RadiusEffect::Entity(mut effect) => {
|
||||||
|
let players = &ecs.read_storage::<comp::Player>();
|
||||||
for (entity_b, pos_b, body_b_maybe) in (
|
for (entity_b, pos_b, body_b_maybe) in (
|
||||||
&ecs.entities(),
|
&ecs.entities(),
|
||||||
&ecs.read_storage::<comp::Pos>(),
|
&ecs.read_storage::<comp::Pos>(),
|
||||||
@ -916,14 +929,35 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
1.0 - distance_squared / explosion.radius.powi(2)
|
1.0 - distance_squared / explosion.radius.powi(2)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Player check only accounts for PvP/PvE flag.
|
||||||
|
//
|
||||||
|
// But bombs are intented to do
|
||||||
|
// friendly fire.
|
||||||
|
//
|
||||||
|
// What exactly friendly fire is subject to discussion.
|
||||||
|
// As we probably want to minimize possibility of being dick
|
||||||
|
// even to your group members, the only exception is when
|
||||||
|
// you want to harm yourself.
|
||||||
|
//
|
||||||
|
// This can be changed later.
|
||||||
|
let may_harm = || {
|
||||||
|
owner_entity.map_or(false, |attacker| {
|
||||||
|
let attacker_player = players.get(attacker);
|
||||||
|
let target_player = players.get(entity_b);
|
||||||
|
combat::may_harm(attacker_player, target_player) || attacker == entity_b
|
||||||
|
})
|
||||||
|
};
|
||||||
if strength > 0.0 {
|
if strength > 0.0 {
|
||||||
let is_alive = ecs
|
let is_alive = ecs
|
||||||
.read_storage::<comp::Health>()
|
.read_storage::<comp::Health>()
|
||||||
.get(entity_b)
|
.get(entity_b)
|
||||||
.map_or(true, |h| !h.is_dead);
|
.map_or(true, |h| !h.is_dead);
|
||||||
|
|
||||||
if is_alive {
|
if is_alive {
|
||||||
effect.modify_strength(strength);
|
effect.modify_strength(strength);
|
||||||
server.state().apply_effect(entity_b, effect.clone(), owner);
|
if !effect.is_harm() || may_harm() {
|
||||||
|
server.state().apply_effect(entity_b, effect.clone(), owner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ pub use server_description::ServerDescription;
|
|||||||
pub use whitelist::{Whitelist, WhitelistInfo, WhitelistRecord};
|
pub use whitelist::{Whitelist, WhitelistInfo, WhitelistRecord};
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use common::resources::BattleMode;
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use portpicker::pick_unused_port;
|
use portpicker::pick_unused_port;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -48,7 +49,7 @@ pub struct Settings {
|
|||||||
pub quic_files: Option<X509FilePair>,
|
pub quic_files: Option<X509FilePair>,
|
||||||
pub max_players: usize,
|
pub max_players: usize,
|
||||||
pub world_seed: u32,
|
pub world_seed: u32,
|
||||||
//pub pvp_enabled: bool,
|
pub battle_mode: BattleMode,
|
||||||
pub server_name: String,
|
pub server_name: String,
|
||||||
pub start_time: f64,
|
pub start_time: f64,
|
||||||
/// When set to None, loads the default map file (if available); otherwise,
|
/// When set to None, loads the default map file (if available); otherwise,
|
||||||
@ -73,6 +74,7 @@ impl Default for Settings {
|
|||||||
world_seed: DEFAULT_WORLD_SEED,
|
world_seed: DEFAULT_WORLD_SEED,
|
||||||
server_name: "Veloren Alpha".into(),
|
server_name: "Veloren Alpha".into(),
|
||||||
max_players: 100,
|
max_players: 100,
|
||||||
|
battle_mode: BattleMode::PvP,
|
||||||
start_time: 9.0 * 3600.0,
|
start_time: 9.0 * 3600.0,
|
||||||
map_file: None,
|
map_file: None,
|
||||||
max_view_distance: Some(65),
|
max_view_distance: Some(65),
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
client::Client,
|
client::Client,
|
||||||
login_provider::{LoginProvider, PendingLogin},
|
login_provider::{LoginProvider, PendingLogin},
|
||||||
metrics::PlayerMetrics,
|
metrics::PlayerMetrics,
|
||||||
EditableSettings,
|
EditableSettings, Settings,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{Admin, Player, Stats},
|
comp::{Admin, Player, Stats},
|
||||||
@ -17,7 +17,8 @@ use common_net::msg::{
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use plugin_api::Health;
|
use plugin_api::Health;
|
||||||
use specs::{
|
use specs::{
|
||||||
storage::StorageEntry, Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage,
|
shred::ResourceId, storage::StorageEntry, Entities, Join, Read, ReadExpect, ReadStorage,
|
||||||
|
SystemData, World, WriteExpect, WriteStorage,
|
||||||
};
|
};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
@ -29,26 +30,32 @@ type ReadPlugin<'a> = Read<'a, PluginMgr>;
|
|||||||
#[cfg(not(feature = "plugins"))]
|
#[cfg(not(feature = "plugins"))]
|
||||||
type ReadPlugin<'a> = Option<Read<'a, ()>>;
|
type ReadPlugin<'a> = Option<Read<'a, ()>>;
|
||||||
|
|
||||||
|
#[derive(SystemData)]
|
||||||
|
pub struct ReadData<'a> {
|
||||||
|
entities: Entities<'a>,
|
||||||
|
stats: ReadStorage<'a, Stats>,
|
||||||
|
uids: ReadStorage<'a, Uid>,
|
||||||
|
clients: ReadStorage<'a, Client>,
|
||||||
|
server_event_bus: Read<'a, EventBus<ServerEvent>>,
|
||||||
|
player_metrics: ReadExpect<'a, PlayerMetrics>,
|
||||||
|
settings: ReadExpect<'a, Settings>,
|
||||||
|
editable_settings: ReadExpect<'a, EditableSettings>,
|
||||||
|
_healths: ReadStorage<'a, Health>, // used by plugin feature
|
||||||
|
_plugin_mgr: ReadPlugin<'a>, // used by plugin feature
|
||||||
|
_uid_allocator: Read<'a, UidAllocator>, // used by plugin feature
|
||||||
|
}
|
||||||
|
|
||||||
/// This system will handle new messages from clients
|
/// This system will handle new messages from clients
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
impl<'a> System<'a> for Sys {
|
impl<'a> System<'a> for Sys {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
Entities<'a>,
|
ReadData<'a>,
|
||||||
ReadExpect<'a, PlayerMetrics>,
|
|
||||||
ReadStorage<'a, Health>,
|
|
||||||
ReadStorage<'a, Uid>,
|
|
||||||
ReadStorage<'a, Client>,
|
|
||||||
WriteStorage<'a, Player>,
|
WriteStorage<'a, Player>,
|
||||||
WriteStorage<'a, PendingLogin>,
|
|
||||||
Read<'a, UidAllocator>,
|
|
||||||
ReadPlugin<'a>,
|
|
||||||
ReadStorage<'a, Stats>,
|
|
||||||
WriteExpect<'a, LoginProvider>,
|
|
||||||
WriteStorage<'a, Admin>,
|
WriteStorage<'a, Admin>,
|
||||||
ReadExpect<'a, EditableSettings>,
|
WriteStorage<'a, PendingLogin>,
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
WriteExpect<'a, LoginProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const NAME: &'static str = "msg::register";
|
const NAME: &'static str = "msg::register";
|
||||||
@ -58,24 +65,21 @@ impl<'a> System<'a> for Sys {
|
|||||||
fn run(
|
fn run(
|
||||||
_job: &mut Job<Self>,
|
_job: &mut Job<Self>,
|
||||||
(
|
(
|
||||||
entities,
|
read_data,
|
||||||
player_metrics,
|
|
||||||
_health_comp, // used by plugin feature
|
|
||||||
uids,
|
|
||||||
clients,
|
|
||||||
mut players,
|
mut players,
|
||||||
mut pending_logins,
|
|
||||||
_uid_allocator, // used by plugin feature
|
|
||||||
_plugin_mgr, // used by plugin feature
|
|
||||||
stats,
|
|
||||||
mut login_provider,
|
|
||||||
mut admins,
|
mut admins,
|
||||||
editable_settings,
|
mut pending_logins,
|
||||||
server_event_bus,
|
mut login_provider,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
|
let mut server_emitter = read_data.server_event_bus.emitter();
|
||||||
// Player list to send new players.
|
// Player list to send new players.
|
||||||
let player_list = (&uids, &players, stats.maybe(), admins.maybe())
|
let player_list = (
|
||||||
|
&read_data.uids,
|
||||||
|
&players,
|
||||||
|
read_data.stats.maybe(),
|
||||||
|
admins.maybe(),
|
||||||
|
)
|
||||||
.join()
|
.join()
|
||||||
.map(|(uid, player, stats, admin)| {
|
.map(|(uid, player, stats, admin)| {
|
||||||
(*uid, PlayerInfo {
|
(*uid, PlayerInfo {
|
||||||
@ -92,7 +96,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut new_players = Vec::new();
|
let mut new_players = Vec::new();
|
||||||
|
|
||||||
// defer auth lockup
|
// defer auth lockup
|
||||||
for (entity, client) in (&entities, &clients).join() {
|
for (entity, client) in (&read_data.entities, &read_data.clients).join() {
|
||||||
let _ = super::try_recv_all(client, 0, |_, msg: ClientRegister| {
|
let _ = super::try_recv_all(client, 0, |_, msg: ClientRegister| {
|
||||||
trace!(?msg.token_or_username, "defer auth lockup");
|
trace!(?msg.token_or_username, "defer auth lockup");
|
||||||
let pending = login_provider.verify(&msg.token_or_username);
|
let pending = login_provider.verify(&msg.token_or_username);
|
||||||
@ -103,15 +107,17 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let mut finished_pending = vec![];
|
let mut finished_pending = vec![];
|
||||||
let mut retries = vec![];
|
let mut retries = vec![];
|
||||||
for (entity, client, mut pending) in (&entities, &clients, &mut pending_logins).join() {
|
for (entity, client, mut pending) in
|
||||||
|
(&read_data.entities, &read_data.clients, &mut pending_logins).join()
|
||||||
|
{
|
||||||
if let Err(e) = || -> std::result::Result<(), crate::error::Error> {
|
if let Err(e) = || -> std::result::Result<(), crate::error::Error> {
|
||||||
#[cfg(feature = "plugins")]
|
#[cfg(feature = "plugins")]
|
||||||
let ecs_world = EcsWorld {
|
let ecs_world = EcsWorld {
|
||||||
entities: &entities,
|
entities: &read_data.entities,
|
||||||
health: (&_health_comp).into(),
|
health: (&read_data._healths).into(),
|
||||||
uid: (&uids).into(),
|
uid: (&read_data.uids).into(),
|
||||||
player: (&players).into(),
|
player: (&players).into(),
|
||||||
uid_allocator: &_uid_allocator,
|
uid_allocator: &read_data._uid_allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (username, uuid) = match login_provider.login(
|
let (username, uuid) = match login_provider.login(
|
||||||
@ -119,10 +125,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
#[cfg(feature = "plugins")]
|
#[cfg(feature = "plugins")]
|
||||||
&ecs_world,
|
&ecs_world,
|
||||||
#[cfg(feature = "plugins")]
|
#[cfg(feature = "plugins")]
|
||||||
&_plugin_mgr,
|
&read_data._plugin_mgr,
|
||||||
&*editable_settings.admins,
|
&*read_data.editable_settings.admins,
|
||||||
&*editable_settings.whitelist,
|
&*read_data.editable_settings.whitelist,
|
||||||
&*editable_settings.banlist,
|
&*read_data.editable_settings.banlist,
|
||||||
) {
|
) {
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
Some(r) => {
|
Some(r) => {
|
||||||
@ -130,7 +136,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
trace!(?r, "pending login returned");
|
trace!(?r, "pending login returned");
|
||||||
match r {
|
match r {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
server_event_bus.emit_now(ServerEvent::ClientDisconnect(
|
server_emitter.emit(ServerEvent::ClientDisconnect(
|
||||||
entity,
|
entity,
|
||||||
common::comp::DisconnectReason::Kicked,
|
common::comp::DisconnectReason::Kicked,
|
||||||
));
|
));
|
||||||
@ -143,12 +149,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Check if user is already logged-in
|
// Check if user is already logged-in
|
||||||
if let Some((old_entity, old_client, _)) = (&entities, &clients, &players)
|
if let Some((old_entity, old_client, _)) =
|
||||||
.join()
|
(&read_data.entities, &read_data.clients, &players)
|
||||||
.find(|(_, _, old_player)| old_player.uuid() == uuid)
|
.join()
|
||||||
|
.find(|(_, _, old_player)| old_player.uuid() == uuid)
|
||||||
{
|
{
|
||||||
// Remove old client
|
// Remove old client
|
||||||
server_event_bus.emit_now(ServerEvent::ClientDisconnect(
|
server_emitter.emit(ServerEvent::ClientDisconnect(
|
||||||
old_entity,
|
old_entity,
|
||||||
common::comp::DisconnectReason::NewerLogin,
|
common::comp::DisconnectReason::NewerLogin,
|
||||||
));
|
));
|
||||||
@ -166,8 +173,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let player = Player::new(username, uuid);
|
let player = Player::new(username, read_data.settings.battle_mode, uuid);
|
||||||
let admin = editable_settings.admins.get(&uuid);
|
let admin = read_data.editable_settings.admins.get(&uuid);
|
||||||
|
|
||||||
if !player.is_valid() {
|
if !player.is_valid() {
|
||||||
// Invalid player
|
// Invalid player
|
||||||
@ -178,7 +185,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
if let Ok(StorageEntry::Vacant(v)) = players.entry(entity) {
|
if let Ok(StorageEntry::Vacant(v)) = players.entry(entity) {
|
||||||
// Add Player component to this client, if the entity exists.
|
// Add Player component to this client, if the entity exists.
|
||||||
v.insert(player);
|
v.insert(player);
|
||||||
player_metrics.players_connected.inc();
|
read_data.player_metrics.players_connected.inc();
|
||||||
|
|
||||||
// Give the Admin component to the player if their name exists in
|
// Give the Admin component to the player if their name exists in
|
||||||
// admin list
|
// admin list
|
||||||
@ -214,22 +221,24 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
// Handle new players.
|
// Handle new players.
|
||||||
// Tell all clients to add them to the player list.
|
// Tell all clients to add them to the player list.
|
||||||
for entity in new_players {
|
let player_info = |entity| {
|
||||||
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
|
let player_info = read_data.uids.get(entity).zip(players.get(entity));
|
||||||
let mut lazy_msg = None;
|
player_info.map(|(u, p)| (entity, u, p))
|
||||||
for (_, client) in (&players, &clients).join() {
|
};
|
||||||
if lazy_msg.is_none() {
|
for (entity, uid, player) in new_players.into_iter().filter_map(player_info) {
|
||||||
lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate(
|
let mut lazy_msg = None;
|
||||||
PlayerListUpdate::Add(*uid, PlayerInfo {
|
for (_, client) in (&players, &read_data.clients).join() {
|
||||||
player_alias: player.alias.clone(),
|
if lazy_msg.is_none() {
|
||||||
is_online: true,
|
lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate(
|
||||||
is_moderator: admins.get(entity).is_some(),
|
PlayerListUpdate::Add(*uid, PlayerInfo {
|
||||||
character: None, // new players will be on character select.
|
player_alias: player.alias.clone(),
|
||||||
}),
|
is_online: true,
|
||||||
)));
|
is_moderator: admins.get(entity).is_some(),
|
||||||
}
|
character: None, // new players will be on character select.
|
||||||
lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
|
}),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user