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
|
||||
- NPCs can now warn players before engaging in combat
|
||||
- Custom error message when a supported graphics backend can not be found
|
||||
- Add server setting with PvE/PvP switch
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -26,7 +26,8 @@ enum-iterator = "0.6"
|
||||
vek = { version = "=0.14.1", features = ["serde"] }
|
||||
|
||||
# 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"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||
poise::PoiseChange,
|
||||
skills::SkillGroupKind,
|
||||
Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange,
|
||||
HealthSource, Inventory, Ori, SkillSet, Stats,
|
||||
HealthSource, Inventory, Ori, Player, SkillSet, Stats,
|
||||
},
|
||||
event::ServerEvent,
|
||||
outcome::Outcome,
|
||||
@ -71,6 +71,13 @@ pub struct TargetInfo<'a> {
|
||||
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"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive
|
||||
pub struct Attack {
|
||||
@ -161,25 +168,44 @@ impl Attack {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn apply_attack(
|
||||
&self,
|
||||
target_group: GroupTarget,
|
||||
attacker: Option<AttackerInfo>,
|
||||
target: TargetInfo,
|
||||
dir: Dir,
|
||||
target_dodging: bool,
|
||||
// Currently just modifies damage, maybe look into modifying strength of other effects?
|
||||
options: AttackOptions,
|
||||
// Currently strength_modifier just modifies damage,
|
||||
// maybe look into modifying strength of other effects?
|
||||
strength_modifier: f32,
|
||||
attack_source: AttackSource,
|
||||
mut emit: impl FnMut(ServerEvent),
|
||||
mut emit_outcome: impl FnMut(Outcome),
|
||||
) -> 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 mut is_applied = false;
|
||||
let mut accumulated_damage = 0.0;
|
||||
for damage in self
|
||||
.damages
|
||||
.iter()
|
||||
.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;
|
||||
let damage_reduction = Attack::compute_damage_reduction(
|
||||
@ -294,7 +320,7 @@ impl Attack {
|
||||
.effects
|
||||
.iter()
|
||||
.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 {
|
||||
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"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AttackDamage {
|
||||
|
@ -81,22 +81,22 @@ impl BuffKind {
|
||||
/// Checks if buff is buff or debuff
|
||||
pub fn is_buff(self) -> bool {
|
||||
match self {
|
||||
BuffKind::Regeneration => true,
|
||||
BuffKind::Saturation => true,
|
||||
BuffKind::Bleeding => false,
|
||||
BuffKind::Cursed => false,
|
||||
BuffKind::Potion => true,
|
||||
BuffKind::CampfireHeal => true,
|
||||
BuffKind::IncreaseMaxEnergy => true,
|
||||
BuffKind::IncreaseMaxHealth => true,
|
||||
BuffKind::Invulnerability => true,
|
||||
BuffKind::ProtectingWard => true,
|
||||
BuffKind::Burning => false,
|
||||
BuffKind::Crippled => false,
|
||||
BuffKind::Frenzied => true,
|
||||
BuffKind::Frozen => false,
|
||||
BuffKind::Wet => false,
|
||||
BuffKind::Ensnared => false,
|
||||
BuffKind::Regeneration
|
||||
| BuffKind::Saturation
|
||||
| BuffKind::Potion
|
||||
| BuffKind::CampfireHeal
|
||||
| BuffKind::IncreaseMaxEnergy
|
||||
| BuffKind::IncreaseMaxHealth
|
||||
| BuffKind::Invulnerability
|
||||
| BuffKind::ProtectingWard => true,
|
||||
BuffKind::Bleeding
|
||||
| BuffKind::Cursed
|
||||
| BuffKind::Burning
|
||||
| BuffKind::Crippled
|
||||
| BuffKind::Frenzied
|
||||
| BuffKind::Frozen
|
||||
| BuffKind::Wet
|
||||
| BuffKind::Ensnared => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ use specs::{Component, DerefFlaggedStorage, NullStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::resources::BattleMode;
|
||||
|
||||
const MAX_ALIAS_LEN: usize = 32;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -17,11 +19,31 @@ pub enum DisconnectReason {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Player {
|
||||
pub alias: String,
|
||||
pub battle_mode: BattleMode,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl BattleMode {
|
||||
pub fn may_harm(self, other: Self) -> bool {
|
||||
matches!((self, other), (BattleMode::PvP, BattleMode::PvP))
|
||||
}
|
||||
}
|
||||
|
||||
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() }
|
||||
|
||||
|
@ -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) {
|
||||
match self {
|
||||
Effect::Health(change) => {
|
||||
|
@ -74,3 +74,13 @@ impl PlayerPhysicsSetting {
|
||||
pub struct PlayerPhysicsSettings {
|
||||
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::{
|
||||
combat,
|
||||
comp::{
|
||||
aura::{AuraChange, AuraKey, AuraKind, AuraTarget},
|
||||
buff::{self, BuffCategory},
|
||||
buff::{Buff, BuffCategory, BuffChange, BuffSource},
|
||||
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,
|
||||
uid::{Uid, UidAllocator},
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
||||
World, WriteStorage,
|
||||
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>,
|
||||
@ -101,91 +103,36 @@ impl<'a> System<'a> for Sys {
|
||||
Some(buff) => buff,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Ensure entity is within the aura radius
|
||||
if target_pos.0.distance_squared(pos.0) < aura.radius.powi(2) {
|
||||
if let AuraTarget::GroupOf(uid) = aura.target {
|
||||
let same_group = read_data
|
||||
// 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;
|
||||
|
||||
if !same_group {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: When more aura kinds (besides Buff) are
|
||||
// implemented, match on them here
|
||||
match aura.aura_kind {
|
||||
AuraKind::Buff {
|
||||
kind,
|
||||
data,
|
||||
category,
|
||||
source,
|
||||
} => {
|
||||
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,
|
||||
|| *target_uid == uid
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -201,3 +148,111 @@ impl<'a> System<'a> for Sys {
|
||||
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::{
|
||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
||||
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource,
|
||||
Inventory, Ori, Pos, Scale, Stats,
|
||||
Inventory, Ori, Player, Pos, Scale, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
outcome::Outcome,
|
||||
@ -26,6 +26,7 @@ use vek::*;
|
||||
#[derive(SystemData)]
|
||||
pub struct ReadData<'a> {
|
||||
entities: Entities<'a>,
|
||||
players: ReadStorage<'a, Player>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
time: Read<'a, Time>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
@ -212,12 +213,23 @@ impl<'a> System<'a> for Sys {
|
||||
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,
|
||||
};
|
||||
|
||||
beam_segment.properties.attack.apply_attack(
|
||||
attacker_info,
|
||||
target_info,
|
||||
ori.look_dir(),
|
||||
false,
|
||||
attack_options,
|
||||
1.0,
|
||||
AttackSource::Beam,
|
||||
|e| server_events.push(e),
|
||||
|
@ -1,9 +1,9 @@
|
||||
use common::{
|
||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
||||
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale,
|
||||
Stats,
|
||||
Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Player, Pos,
|
||||
Scale, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
outcome::Outcome,
|
||||
@ -22,6 +22,7 @@ use vek::*;
|
||||
pub struct ReadData<'a> {
|
||||
time: Read<'a, Time>,
|
||||
entities: Entities<'a>,
|
||||
players: ReadStorage<'a, Player>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
orientations: ReadStorage<'a, Ori>,
|
||||
@ -115,7 +116,7 @@ impl<'a> System<'a> for Sys {
|
||||
let rad_b = body_b.radius() * scale_b;
|
||||
|
||||
// Check if entity is dodging
|
||||
let is_dodge = read_data
|
||||
let target_dodging = read_data
|
||||
.char_states
|
||||
.get(target)
|
||||
.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),
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
let is_applied = melee_attack.attack.apply_attack(
|
||||
attacker_info,
|
||||
target_info,
|
||||
dir,
|
||||
is_dodge,
|
||||
attack_options,
|
||||
1.0,
|
||||
AttackSource::Melee,
|
||||
|e| server_emitter.emit(e),
|
||||
|
@ -1,11 +1,11 @@
|
||||
use common::{
|
||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
||||
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
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,
|
||||
resources::{DeltaTime, Time},
|
||||
uid::{Uid, UidAllocator},
|
||||
@ -15,8 +15,8 @@ use common::{
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
||||
World, Write, WriteStorage,
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
|
||||
ReadStorage, SystemData, World, Write, WriteStorage,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
@ -25,6 +25,7 @@ use vek::*;
|
||||
pub struct ReadData<'a> {
|
||||
time: Read<'a, Time>,
|
||||
entities: Entities<'a>,
|
||||
players: ReadStorage<'a, Player>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
@ -114,95 +115,37 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
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(..) {
|
||||
match effect {
|
||||
projectile::Effect::Attack(attack) => {
|
||||
if let Some(target) = read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal(other.into())
|
||||
{
|
||||
if let (Some(pos), Some(ori)) =
|
||||
(read_data.positions.get(target), orientations.get(entity))
|
||||
{
|
||||
let dir = ori.look_dir();
|
||||
|
||||
let owner_entity = projectile.owner.and_then(|u| {
|
||||
read_data.uid_allocator.retrieve_entity_internal(u.into())
|
||||
});
|
||||
|
||||
let attacker_info =
|
||||
owner_entity.zip(projectile.owner).map(|(entity, uid)| {
|
||||
AttackerInfo {
|
||||
let owner = projectile.owner.and_then(entity_of);
|
||||
let projectile_info = ProjectileInfo {
|
||||
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: 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),
|
||||
effect,
|
||||
owner_uid: projectile.owner,
|
||||
owner,
|
||||
ori: orientations.get(entity),
|
||||
pos,
|
||||
};
|
||||
|
||||
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(
|
||||
let target = entity_of(other);
|
||||
let projectile_target_info = ProjectileTargetInfo {
|
||||
uid: other,
|
||||
entity: target,
|
||||
target_group,
|
||||
attacker_info,
|
||||
target_info,
|
||||
dir,
|
||||
false,
|
||||
1.0,
|
||||
AttackSource::Projectile,
|
||||
|e| server_emitter.emit(e),
|
||||
|o| outcomes.push(o),
|
||||
ori: target.and_then(|target| orientations.get(target)),
|
||||
};
|
||||
|
||||
dispatch_hit(
|
||||
projectile_info,
|
||||
projectile_target_info,
|
||||
&read_data,
|
||||
&mut projectile_vanished,
|
||||
&mut outcomes,
|
||||
&mut server_emitter,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
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 {
|
||||
continue 'projectile_loop;
|
||||
@ -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::{
|
||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
||||
combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
agent::{Sound, SoundKind},
|
||||
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},
|
||||
outcome::Outcome,
|
||||
@ -25,6 +25,7 @@ pub struct ReadData<'a> {
|
||||
entities: Entities<'a>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
time: Read<'a, Time>,
|
||||
players: ReadStorage<'a, Player>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
@ -208,12 +209,22 @@ impl<'a> System<'a> for Sys {
|
||||
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,
|
||||
};
|
||||
|
||||
shockwave.properties.attack.apply_attack(
|
||||
attacker_info,
|
||||
target_info,
|
||||
dir,
|
||||
false,
|
||||
attack_options,
|
||||
1.0,
|
||||
AttackSource::Shockwave,
|
||||
|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 combos = &ecs.read_storage::<comp::Combo>();
|
||||
let inventories = &ecs.read_storage::<comp::Inventory>();
|
||||
let players = &ecs.read_storage::<comp::Player>();
|
||||
for (
|
||||
entity_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,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
attack.apply_attack(
|
||||
attacker_info,
|
||||
target_info,
|
||||
dir,
|
||||
false,
|
||||
attack_options,
|
||||
strength,
|
||||
combat::AttackSource::Explosion,
|
||||
|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) => {
|
||||
let players = &ecs.read_storage::<comp::Player>();
|
||||
for (entity_b, pos_b, body_b_maybe) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<comp::Pos>(),
|
||||
@ -916,17 +929,38 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
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 {
|
||||
let is_alive = ecs
|
||||
.read_storage::<comp::Health>()
|
||||
.get(entity_b)
|
||||
.map_or(true, |h| !h.is_dead);
|
||||
|
||||
if is_alive {
|
||||
effect.modify_strength(strength);
|
||||
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};
|
||||
|
||||
use chrono::Utc;
|
||||
use common::resources::BattleMode;
|
||||
use core::time::Duration;
|
||||
use portpicker::pick_unused_port;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -48,7 +49,7 @@ pub struct Settings {
|
||||
pub quic_files: Option<X509FilePair>,
|
||||
pub max_players: usize,
|
||||
pub world_seed: u32,
|
||||
//pub pvp_enabled: bool,
|
||||
pub battle_mode: BattleMode,
|
||||
pub server_name: String,
|
||||
pub start_time: f64,
|
||||
/// 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,
|
||||
server_name: "Veloren Alpha".into(),
|
||||
max_players: 100,
|
||||
battle_mode: BattleMode::PvP,
|
||||
start_time: 9.0 * 3600.0,
|
||||
map_file: None,
|
||||
max_view_distance: Some(65),
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
client::Client,
|
||||
login_provider::{LoginProvider, PendingLogin},
|
||||
metrics::PlayerMetrics,
|
||||
EditableSettings,
|
||||
EditableSettings, Settings,
|
||||
};
|
||||
use common::{
|
||||
comp::{Admin, Player, Stats},
|
||||
@ -17,7 +17,8 @@ use common_net::msg::{
|
||||
use hashbrown::HashMap;
|
||||
use plugin_api::Health;
|
||||
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;
|
||||
|
||||
@ -29,26 +30,32 @@ type ReadPlugin<'a> = Read<'a, PluginMgr>;
|
||||
#[cfg(not(feature = "plugins"))]
|
||||
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
|
||||
#[derive(Default)]
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
#[allow(clippy::type_complexity)]
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
ReadExpect<'a, PlayerMetrics>,
|
||||
ReadStorage<'a, Health>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Client>,
|
||||
ReadData<'a>,
|
||||
WriteStorage<'a, Player>,
|
||||
WriteStorage<'a, PendingLogin>,
|
||||
Read<'a, UidAllocator>,
|
||||
ReadPlugin<'a>,
|
||||
ReadStorage<'a, Stats>,
|
||||
WriteExpect<'a, LoginProvider>,
|
||||
WriteStorage<'a, Admin>,
|
||||
ReadExpect<'a, EditableSettings>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
WriteStorage<'a, PendingLogin>,
|
||||
WriteExpect<'a, LoginProvider>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "msg::register";
|
||||
@ -58,24 +65,21 @@ impl<'a> System<'a> for Sys {
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(
|
||||
entities,
|
||||
player_metrics,
|
||||
_health_comp, // used by plugin feature
|
||||
uids,
|
||||
clients,
|
||||
read_data,
|
||||
mut players,
|
||||
mut pending_logins,
|
||||
_uid_allocator, // used by plugin feature
|
||||
_plugin_mgr, // used by plugin feature
|
||||
stats,
|
||||
mut login_provider,
|
||||
mut admins,
|
||||
editable_settings,
|
||||
server_event_bus,
|
||||
mut pending_logins,
|
||||
mut login_provider,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = read_data.server_event_bus.emitter();
|
||||
// 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()
|
||||
.map(|(uid, player, stats, admin)| {
|
||||
(*uid, PlayerInfo {
|
||||
@ -92,7 +96,7 @@ impl<'a> System<'a> for Sys {
|
||||
let mut new_players = Vec::new();
|
||||
|
||||
// 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| {
|
||||
trace!(?msg.token_or_username, "defer auth lockup");
|
||||
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 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> {
|
||||
#[cfg(feature = "plugins")]
|
||||
let ecs_world = EcsWorld {
|
||||
entities: &entities,
|
||||
health: (&_health_comp).into(),
|
||||
uid: (&uids).into(),
|
||||
entities: &read_data.entities,
|
||||
health: (&read_data._healths).into(),
|
||||
uid: (&read_data.uids).into(),
|
||||
player: (&players).into(),
|
||||
uid_allocator: &_uid_allocator,
|
||||
uid_allocator: &read_data._uid_allocator,
|
||||
};
|
||||
|
||||
let (username, uuid) = match login_provider.login(
|
||||
@ -119,10 +125,10 @@ impl<'a> System<'a> for Sys {
|
||||
#[cfg(feature = "plugins")]
|
||||
&ecs_world,
|
||||
#[cfg(feature = "plugins")]
|
||||
&_plugin_mgr,
|
||||
&*editable_settings.admins,
|
||||
&*editable_settings.whitelist,
|
||||
&*editable_settings.banlist,
|
||||
&read_data._plugin_mgr,
|
||||
&*read_data.editable_settings.admins,
|
||||
&*read_data.editable_settings.whitelist,
|
||||
&*read_data.editable_settings.banlist,
|
||||
) {
|
||||
None => return Ok(()),
|
||||
Some(r) => {
|
||||
@ -130,7 +136,7 @@ impl<'a> System<'a> for Sys {
|
||||
trace!(?r, "pending login returned");
|
||||
match r {
|
||||
Err(e) => {
|
||||
server_event_bus.emit_now(ServerEvent::ClientDisconnect(
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(
|
||||
entity,
|
||||
common::comp::DisconnectReason::Kicked,
|
||||
));
|
||||
@ -143,12 +149,13 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
// Check if user is already logged-in
|
||||
if let Some((old_entity, old_client, _)) = (&entities, &clients, &players)
|
||||
if let Some((old_entity, old_client, _)) =
|
||||
(&read_data.entities, &read_data.clients, &players)
|
||||
.join()
|
||||
.find(|(_, _, old_player)| old_player.uuid() == uuid)
|
||||
{
|
||||
// Remove old client
|
||||
server_event_bus.emit_now(ServerEvent::ClientDisconnect(
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(
|
||||
old_entity,
|
||||
common::comp::DisconnectReason::NewerLogin,
|
||||
));
|
||||
@ -166,8 +173,8 @@ impl<'a> System<'a> for Sys {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let player = Player::new(username, uuid);
|
||||
let admin = editable_settings.admins.get(&uuid);
|
||||
let player = Player::new(username, read_data.settings.battle_mode, uuid);
|
||||
let admin = read_data.editable_settings.admins.get(&uuid);
|
||||
|
||||
if !player.is_valid() {
|
||||
// Invalid player
|
||||
@ -178,7 +185,7 @@ impl<'a> System<'a> for Sys {
|
||||
if let Ok(StorageEntry::Vacant(v)) = players.entry(entity) {
|
||||
// Add Player component to this client, if the entity exists.
|
||||
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
|
||||
// admin list
|
||||
@ -214,10 +221,13 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Handle new players.
|
||||
// Tell all clients to add them to the player list.
|
||||
for entity in new_players {
|
||||
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
|
||||
let player_info = |entity| {
|
||||
let player_info = read_data.uids.get(entity).zip(players.get(entity));
|
||||
player_info.map(|(u, p)| (entity, u, p))
|
||||
};
|
||||
for (entity, uid, player) in new_players.into_iter().filter_map(player_info) {
|
||||
let mut lazy_msg = None;
|
||||
for (_, client) in (&players, &clients).join() {
|
||||
for (_, client) in (&players, &read_data.clients).join() {
|
||||
if lazy_msg.is_none() {
|
||||
lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::Add(*uid, PlayerInfo {
|
||||
@ -233,4 +243,3 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user