From 7d4a8cbfa4b99257fd0fcc480c7c5c2216ff21c3 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Mon, 26 Jul 2021 16:52:43 +0300 Subject: [PATCH 01/13] Add battle_mode server setting and player flag --- common/src/combat.rs | 38 ++++++-- common/src/comp/player.rs | 27 +++++- common/src/effect.rs | 9 ++ common/src/resources.rs | 10 +++ common/systems/src/beam.rs | 29 +++++- common/systems/src/melee.rs | 39 ++++++-- common/systems/src/projectile.rs | 29 +++++- common/systems/src/shockwave.rs | 29 +++++- server/src/events/entity_manipulation.rs | 41 ++++++++- server/src/settings.rs | 4 +- server/src/sys/msg/register.rs | 109 +++++++++++++---------- 11 files changed, 288 insertions(+), 76 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index 1bd42c2389..a7c923624b 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -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 target_group: GroupTarget, + pub avoid_harm: bool, +} + #[cfg(not(target_arch = "wasm32"))] #[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive pub struct Attack { @@ -158,28 +165,47 @@ impl Attack { 1.0 - (1.0 - damage_reduction) * (1.0 - block_reduction) } + #[allow(clippy::too_many_arguments)] pub fn apply_attack( &self, - target_group: GroupTarget, attacker: Option, 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, + target_group, + avoid_harm, + } = 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 || avoid_harm) + }; + let avoid_effect = |attack_effect: &AttackEffect| { + matches!(attack_effect.target, Some(GroupTarget::OutOfGroup)) + && (target_dodging || avoid_harm) + }; let is_crit = thread_rng().gen::() < 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(), diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index 7a34664569..a9f7ce59bd 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -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,34 @@ pub enum DisconnectReason { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Player { pub alias: String, + pub battle_mode: BattleMode, uuid: Uuid, } 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. + pub fn allow_harm(&self, other: &Player) -> bool { + // TODO: discuss if we want to keep self-harm + matches!( + (self.battle_mode, other.battle_mode), + (BattleMode::PvP, BattleMode::PvP) + ) + } + + /// Inverse of `allow_harm`. Read its doc to learn more. + pub fn disallow_harm(&self, other: &Player) -> bool { + !self.allow_harm(other) + } pub fn is_valid(&self) -> bool { Self::alias_validate(&self.alias).is_ok() } diff --git a/common/src/effect.rs b/common/src/effect.rs index aca9cf0ca8..a1b6b599e3 100644 --- a/common/src/effect.rs +++ b/common/src/effect.rs @@ -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) => { diff --git a/common/src/resources.rs b/common/src/resources.rs index e6d798ce84..ee57b3242a 100644 --- a/common/src/resources.rs +++ b/common/src/resources.rs @@ -74,3 +74,13 @@ impl PlayerPhysicsSetting { pub struct PlayerPhysicsSettings { pub settings: hashbrown::HashMap, } + +/// Describe how players interact with other players. +/// +/// Probably will be removed when we will discover better way +/// to handle duels and murders +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +pub enum BattleMode { + PvP, + PvE, +} diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index dae48525d2..0a3a86e93a 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -1,9 +1,9 @@ use common::{ - combat::{AttackSource, AttackerInfo, TargetInfo}, + combat::{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>, time: Read<'a, Time>, dt: Read<'a, DeltaTime>, @@ -212,12 +213,32 @@ impl<'a> System<'a> for Sys { char_state: read_data.character_states.get(target), }; - beam_segment.properties.attack.apply_attack( + // No luck with dodging beams + let is_dodge = false; + let avoid_harm = { + let players = &read_data.players; + beam_owner.map_or(false, |attacker| { + if let (Some(attacker), Some(target)) = + (players.get(attacker), players.get(target)) + { + attacker.disallow_harm(target) + } else { + false + } + }) + }; + + let attack_options = AttackOptions { + target_dodging: is_dodge, target_group, + avoid_harm, + }; + + 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), diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index 20ac8b54a9..5844b3711d 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -1,9 +1,9 @@ use common::{ - combat::{AttackSource, AttackerInfo, TargetInfo}, + combat::{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>, @@ -161,12 +162,40 @@ impl<'a> System<'a> for Sys { char_state: read_data.char_states.get(target), }; - let is_applied = melee_attack.attack.apply_attack( + let avoid_harm = { + let players = &read_data.players; + if let (Some(attacker), Some(target)) = + (players.get(attacker), players.get(target)) + { + attacker.disallow_harm(target) + } else { + false + } + }; + + // FIXME: printf debugging, this shouldn't go to master + if let Some(attacker) = read_data.players.get(attacker) { + println!("attacker battle_mode: {:?}", attacker.battle_mode); + } else { + println!("attacker special casing") + } + if let Some(target) = read_data.players.get(target) { + println!("target battle_mode: {:?}", target.battle_mode); + } else { + println!("target special casing") + } + + let attack_options = AttackOptions { + target_dodging: is_dodge, target_group, + avoid_harm, + }; + + 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), diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index e9d7a5a3e8..73dbbea93e 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -1,9 +1,9 @@ use common::{ - combat::{AttackSource, AttackerInfo, TargetInfo}, + combat::{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}, outcome::Outcome, @@ -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>, @@ -152,6 +153,22 @@ impl<'a> System<'a> for Sys { char_state: read_data.character_states.get(target), }; + // They say witchers can dodge arrows, + // but we don't have witchers + let is_dodge = false; + let avoid_harm = { + let players = &read_data.players; + projectile_owner.map_or(false, |attacker| { + if let (Some(attacker), Some(target)) = + (players.get(attacker), players.get(target)) + { + attacker.disallow_harm(target) + } else { + false + } + }) + }; + if let Some(&body) = read_data.bodies.get(entity) { outcomes.push(Outcome::ProjectileHit { pos: pos.0, @@ -165,12 +182,16 @@ impl<'a> System<'a> for Sys { }); } - attack.apply_attack( + let attack_options = AttackOptions { + target_dodging: is_dodge, target_group, + avoid_harm, + }; + attack.apply_attack( attacker_info, target_info, dir, - false, + attack_options, 1.0, AttackSource::Projectile, |e| server_emitter.emit(e), diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index 2997821811..fbfe31756f 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -1,9 +1,9 @@ use common::{ - combat::{AttackSource, AttackerInfo, TargetInfo}, + combat::{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>, time: Read<'a, Time>, + players: ReadStorage<'a, Player>, dt: Read<'a, DeltaTime>, uid_allocator: Read<'a, UidAllocator>, uids: ReadStorage<'a, Uid>, @@ -208,12 +209,32 @@ impl<'a> System<'a> for Sys { char_state: read_data.character_states.get(target), }; - shockwave.properties.attack.apply_attack( + // Trying roll during earthquake isn't the best idea + let is_dodge = false; + let avoid_harm = { + let players = &read_data.players; + shockwave_owner.map_or(false, |attacker| { + if let (Some(attacker), Some(target)) = + (players.get(attacker), players.get(target)) + { + attacker.disallow_harm(target) + } else { + false + } + }) + }; + + let attack_options = AttackOptions { + target_dodging: is_dodge, target_group, + avoid_harm, + }; + + shockwave.properties.attack.apply_attack( attacker_info, target_info, dir, - false, + attack_options, 1.0, AttackSource::Shockwave, |e| server_emitter.emit(e), diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 345984e2a2..da92740aba 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -819,6 +819,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o let energies = &ecs.read_storage::(); let combos = &ecs.read_storage::(); let inventories = &ecs.read_storage::(); + let players = &ecs.read_storage::(); for ( entity_b, pos_b, @@ -887,12 +888,31 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o char_state: char_state_b_maybe, }; - attack.apply_attack( + let avoid_harm = { + owner_entity.map_or(false, |attacker| { + if let (Some(attacker), Some(target)) = + (players.get(attacker), players.get(entity_b)) + { + attacker.disallow_harm(target) + } else { + false + } + }) + }; + + 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, target_group, + avoid_harm, + }; + + attack.apply_attack( attacker_info, target_info, dir, - false, + attack_options, strength, combat::AttackSource::Explosion, |e| server_eventbus.emit_now(e), @@ -902,6 +922,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o } }, RadiusEffect::Entity(mut effect) => { + let players = &ecs.read_storage::(); for (entity_b, pos_b, body_b_maybe) in ( &ecs.entities(), &ecs.read_storage::(), @@ -921,9 +942,23 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o .read_storage::() .get(entity_b) .map_or(true, |h| !h.is_dead); + if is_alive { + let avoid_harm = { + owner_entity.map_or(false, |attacker| { + if let (Some(attacker), Some(target)) = + (players.get(attacker), players.get(entity_b)) + { + attacker.disallow_harm(target) + } else { + false + } + }) + }; effect.modify_strength(strength); - server.state().apply_effect(entity_b, effect.clone(), owner); + if !(effect.is_harm() && avoid_harm) { + server.state().apply_effect(entity_b, effect.clone(), owner); + } } } } diff --git a/server/src/settings.rs b/server/src/settings.rs index ce66969277..db41af5a5d 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -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, 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), diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 8d3015d946..00923eb490 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -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>; +#[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>, + _health_comp: 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>, + ReadData<'a>, ReadExpect<'a, PlayerMetrics>, - ReadStorage<'a, Health>, - ReadStorage<'a, Uid>, - ReadStorage<'a, Client>, - WriteStorage<'a, Player>, - WriteStorage<'a, PendingLogin>, - Read<'a, UidAllocator>, - ReadPlugin<'a>, - ReadStorage<'a, Stats>, - WriteExpect<'a, LoginProvider>, - WriteStorage<'a, Admin>, + ReadExpect<'a, Settings>, ReadExpect<'a, EditableSettings>, - Read<'a, EventBus>, + WriteStorage<'a, Player>, + WriteStorage<'a, Admin>, + WriteStorage<'a, PendingLogin>, + WriteExpect<'a, LoginProvider>, ); const NAME: &'static str = "msg::register"; @@ -58,24 +65,23 @@ impl<'a> System<'a> for Sys { fn run( _job: &mut Job, ( - entities, + read_data, player_metrics, - _health_comp, // used by plugin feature - uids, - clients, - mut players, - mut pending_logins, - _uid_allocator, // used by plugin feature - _plugin_mgr, // used by plugin feature - stats, - mut login_provider, - mut admins, + settings, editable_settings, - server_event_bus, + mut players, + mut admins, + mut pending_logins, + mut login_provider, ): Self::SystemData, ) { // 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 +98,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 +109,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._health_comp).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,7 +127,7 @@ impl<'a> System<'a> for Sys { #[cfg(feature = "plugins")] &ecs_world, #[cfg(feature = "plugins")] - &_plugin_mgr, + &read_data._plugin_mgr, &*editable_settings.admins, &*editable_settings.whitelist, &*editable_settings.banlist, @@ -130,10 +138,12 @@ impl<'a> System<'a> for Sys { trace!(?r, "pending login returned"); match r { Err(e) => { - server_event_bus.emit_now(ServerEvent::ClientDisconnect( - entity, - common::comp::DisconnectReason::Kicked, - )); + read_data + .server_event_bus + .emit_now(ServerEvent::ClientDisconnect( + entity, + common::comp::DisconnectReason::Kicked, + )); client.send(ServerRegisterAnswer::Err(e))?; return Ok(()); }, @@ -143,15 +153,18 @@ impl<'a> System<'a> for Sys { }; // Check if user is already logged-in - if let Some((old_entity, old_client, _)) = (&entities, &clients, &players) - .join() - .find(|(_, _, old_player)| old_player.uuid() == uuid) + 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( - old_entity, - common::comp::DisconnectReason::NewerLogin, - )); + read_data + .server_event_bus + .emit_now(ServerEvent::ClientDisconnect( + old_entity, + common::comp::DisconnectReason::NewerLogin, + )); let _ = old_client.send(ServerGeneral::Disconnect(DisconnectReason::Kicked( String::from("You have logged in from another location."), ))); @@ -166,7 +179,7 @@ impl<'a> System<'a> for Sys { return Ok(()); } - let player = Player::new(username, uuid); + let player = Player::new(username, settings.battle_mode, uuid); let admin = editable_settings.admins.get(&uuid); if !player.is_valid() { @@ -215,9 +228,9 @@ 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)) { + if let (Some(uid), Some(player)) = (read_data.uids.get(entity), players.get(entity)) { 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 { From c7fdb640ac8e10d468ce2c873414efff66c67f15 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Wed, 28 Jul 2021 00:57:48 +0300 Subject: [PATCH 02/13] Cleaning --- common/src/combat.rs | 23 +++++++++++++++--- common/src/comp/player.rs | 7 +++--- common/systems/src/beam.rs | 19 +++------------ common/systems/src/melee.rs | 30 +++-------------------- common/systems/src/projectile.rs | 19 +++------------ common/systems/src/shockwave.rs | 19 +++------------ server/src/events/entity_manipulation.rs | 31 ++++++------------------ 7 files changed, 45 insertions(+), 103 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index a7c923624b..237ddf46a4 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -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, @@ -53,6 +53,7 @@ pub enum AttackSource { #[derive(Copy, Clone)] pub struct AttackerInfo<'a> { pub entity: EcsEntity, + pub player: Option<&'a Player>, pub uid: Uid, pub energy: Option<&'a Energy>, pub combo: Option<&'a Combo>, @@ -62,6 +63,7 @@ pub struct AttackerInfo<'a> { #[cfg(not(target_arch = "wasm32"))] pub struct TargetInfo<'a> { pub entity: EcsEntity, + pub player: Option<&'a Player>, pub uid: Uid, pub inventory: Option<&'a Inventory>, pub stats: Option<&'a Stats>, @@ -75,7 +77,6 @@ pub struct TargetInfo<'a> { pub struct AttackOptions { pub target_dodging: bool, pub target_group: GroupTarget, - pub avoid_harm: bool, } #[cfg(not(target_arch = "wasm32"))] @@ -165,7 +166,6 @@ impl Attack { 1.0 - (1.0 - damage_reduction) * (1.0 - block_reduction) } - #[allow(clippy::too_many_arguments)] pub fn apply_attack( &self, @@ -183,8 +183,11 @@ impl Attack { let AttackOptions { target_dodging, target_group, - avoid_harm, } = options; + + let avoid_harm = + attacker.map_or(false, |attacker| avoid_harm(attacker.player, target.player)); + // target == OutOfGroup is basic heuristic that this // "attack" has negative effects. // @@ -456,6 +459,18 @@ impl Attack { } } +/// Checks if we should avoid 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 avoid_harm(attacker: Option<&Player>, target: Option<&Player>) -> bool { + if let (Some(attacker), Some(target)) = (attacker, target) { + return attacker.disallow_harm(target); + } + false +} + #[cfg(not(target_arch = "wasm32"))] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AttackDamage { diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index a9f7ce59bd..f3ab03a081 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -34,7 +34,8 @@ impl Player { /// 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. + /// Simple as tea, if they don't want the tea, don't make them drink the + /// tea. pub fn allow_harm(&self, other: &Player) -> bool { // TODO: discuss if we want to keep self-harm matches!( @@ -44,9 +45,7 @@ impl Player { } /// Inverse of `allow_harm`. Read its doc to learn more. - pub fn disallow_harm(&self, other: &Player) -> bool { - !self.allow_harm(other) - } + pub fn disallow_harm(&self, other: &Player) -> bool { !self.allow_harm(other) } pub fn is_valid(&self) -> bool { Self::alias_validate(&self.alias).is_ok() } diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index 0a3a86e93a..b22bca06f8 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -196,6 +196,7 @@ impl<'a> System<'a> for Sys { .zip(beam_segment.owner) .map(|(entity, uid)| AttackerInfo { entity, + player: read_data.players.get(entity), uid, energy: read_data.energies.get(entity), combo: read_data.combos.get(entity), @@ -204,6 +205,7 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, + player: read_data.players.get(target), uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), @@ -214,24 +216,11 @@ impl<'a> System<'a> for Sys { }; // No luck with dodging beams - let is_dodge = false; - let avoid_harm = { - let players = &read_data.players; - beam_owner.map_or(false, |attacker| { - if let (Some(attacker), Some(target)) = - (players.get(attacker), players.get(target)) - { - attacker.disallow_harm(target) - } else { - false - } - }) - }; + let target_dodging = false; let attack_options = AttackOptions { - target_dodging: is_dodge, + target_dodging, target_group, - avoid_harm, }; beam_segment.properties.attack.apply_attack( diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index 5844b3711d..d7134396ff 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -116,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()); @@ -145,6 +145,7 @@ impl<'a> System<'a> for Sys { let attacker_info = Some(AttackerInfo { entity: attacker, + player: read_data.players.get(attacker), uid: *uid, energy: read_data.energies.get(attacker), combo: read_data.combos.get(attacker), @@ -153,6 +154,7 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, + player: read_data.players.get(target), uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), @@ -162,33 +164,9 @@ impl<'a> System<'a> for Sys { char_state: read_data.char_states.get(target), }; - let avoid_harm = { - let players = &read_data.players; - if let (Some(attacker), Some(target)) = - (players.get(attacker), players.get(target)) - { - attacker.disallow_harm(target) - } else { - false - } - }; - - // FIXME: printf debugging, this shouldn't go to master - if let Some(attacker) = read_data.players.get(attacker) { - println!("attacker battle_mode: {:?}", attacker.battle_mode); - } else { - println!("attacker special casing") - } - if let Some(target) = read_data.players.get(target) { - println!("target battle_mode: {:?}", target.battle_mode); - } else { - println!("target special casing") - } - let attack_options = AttackOptions { - target_dodging: is_dodge, + target_dodging, target_group, - avoid_harm, }; let is_applied = melee_attack.attack.apply_attack( diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index 73dbbea93e..e19430d328 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -135,6 +135,7 @@ impl<'a> System<'a> for Sys { owner_entity.zip(projectile.owner).map(|(entity, uid)| { AttackerInfo { entity, + player: read_data.players.get(entity), uid, energy: read_data.energies.get(entity), combo: read_data.combos.get(entity), @@ -144,6 +145,7 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, + player: read_data.players.get(target), uid: other, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), @@ -155,19 +157,7 @@ impl<'a> System<'a> for Sys { // They say witchers can dodge arrows, // but we don't have witchers - let is_dodge = false; - let avoid_harm = { - let players = &read_data.players; - projectile_owner.map_or(false, |attacker| { - if let (Some(attacker), Some(target)) = - (players.get(attacker), players.get(target)) - { - attacker.disallow_harm(target) - } else { - false - } - }) - }; + let target_dodging = false; if let Some(&body) = read_data.bodies.get(entity) { outcomes.push(Outcome::ProjectileHit { @@ -183,9 +173,8 @@ impl<'a> System<'a> for Sys { } let attack_options = AttackOptions { - target_dodging: is_dodge, + target_dodging, target_group, - avoid_harm, }; attack.apply_attack( attacker_info, diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index fbfe31756f..19f49f7f1c 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -192,6 +192,7 @@ impl<'a> System<'a> for Sys { .zip(shockwave.owner) .map(|(entity, uid)| AttackerInfo { entity, + player: read_data.players.get(entity), uid, energy: read_data.energies.get(entity), combo: read_data.combos.get(entity), @@ -200,6 +201,7 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, + player: read_data.players.get(target), uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), @@ -210,24 +212,11 @@ impl<'a> System<'a> for Sys { }; // Trying roll during earthquake isn't the best idea - let is_dodge = false; - let avoid_harm = { - let players = &read_data.players; - shockwave_owner.map_or(false, |attacker| { - if let (Some(attacker), Some(target)) = - (players.get(attacker), players.get(target)) - { - attacker.disallow_harm(target) - } else { - false - } - }) - }; + let target_dodging = false; let attack_options = AttackOptions { - target_dodging: is_dodge, + target_dodging, target_group, - avoid_harm, }; shockwave.properties.attack.apply_attack( diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index da92740aba..aab9831833 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -871,6 +871,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o .zip(owner) .map(|(entity, uid)| combat::AttackerInfo { entity, + player: players.get(entity), uid, energy: energies.get(entity), combo: combos.get(entity), @@ -879,6 +880,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o let target_info = combat::TargetInfo { entity: entity_b, + player: players.get(entity_b), uid: *uid_b, inventory: inventories.get(entity_b), stats: stats_b_maybe, @@ -888,24 +890,11 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o char_state: char_state_b_maybe, }; - let avoid_harm = { - owner_entity.map_or(false, |attacker| { - if let (Some(attacker), Some(target)) = - (players.get(attacker), players.get(entity_b)) - { - attacker.disallow_harm(target) - } else { - false - } - }) - }; - 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, target_group, - avoid_harm, }; attack.apply_attack( @@ -944,17 +933,11 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o .map_or(true, |h| !h.is_dead); if is_alive { - let avoid_harm = { - owner_entity.map_or(false, |attacker| { - if let (Some(attacker), Some(target)) = - (players.get(attacker), players.get(entity_b)) - { - attacker.disallow_harm(target) - } else { - false - } - }) - }; + let avoid_harm = owner_entity.map_or(false, |attacker| { + let attacker = players.get(attacker); + let target = players.get(entity_b); + combat::avoid_harm(attacker, target) + }); effect.modify_strength(strength); if !(effect.is_harm() && avoid_harm) { server.state().apply_effect(entity_b, effect.clone(), owner); From 61416faec3a4c6a098877c1582b86f3c56420512 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Wed, 28 Jul 2021 13:38:53 +0300 Subject: [PATCH 03/13] Refactor Aura system, add avoid_harm flag + fix bug where you cast OutOfGroup on yourself and your group --- common/src/comp/buff.rs | 32 +++--- common/systems/src/aura.rs | 215 +++++++++++++++++++++++-------------- 2 files changed, 152 insertions(+), 95 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index a8640f6a10..e87d301a1a 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -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, } } diff --git a/common/systems/src/aura.rs b/common/systems/src/aura.rs index da19ac5762..38873a9632 100644 --- a/common/systems/src/aura.rs +++ b/common/systems/src/aura.rs @@ -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>, uid_allocator: Read<'a, UidAllocator>, @@ -101,92 +103,43 @@ 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; + || *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, - }; - 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); - } - } + match aura.target { + AuraTarget::GroupOf(uid) => { + if !same_group(uid) { + return; } }, + AuraTarget::NotGroupOf(uid) => { + if same_group(uid) { + return; + } + }, + AuraTarget::All => {}, } + + activate_aura( + aura, + target, + health, + &mut target_buffs, + &read_data, + &mut server_emitter, + ); } }); } @@ -201,3 +154,107 @@ 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, +) { + let should_activate = |aura: &Aura| { + match aura.aura_kind { + AuraKind::Buff { kind, source, .. } => { + let owner = match source { + BuffSource::Character { by } => { + read_data.uid_allocator.retrieve_entity_internal(by.into()) + }, + _ => None, + }; + let avoid_harm = owner.map_or(false, |attacker| { + let attacker = read_data.players.get(attacker); + let target = read_data.players.get(target); + combat::avoid_harm(attacker, target) + }); + 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, + }; + + if conditions_held { + if kind.is_buff() { true } else { !avoid_harm } + } else { + false + } + }, + } + }; + + // TODO: When more aura kinds (besides Buff) are + // implemented, match on them here + match aura.aura_kind { + AuraKind::Buff { + kind, + data, + category, + source, + } => { + if !should_activate(aura) { + return; + } + // 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); + } + } + }, + } +} From 229e7b9ceca24b76235c1ed98ff99d13e972e8d4 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Wed, 28 Jul 2021 14:36:36 +0300 Subject: [PATCH 04/13] Add CHANGELOG note about battle_mode setting --- CHANGELOG.md | 1 + common/src/resources.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9712d6266d..0f39c3f7e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/common/src/resources.rs b/common/src/resources.rs index ee57b3242a..2b08b6407b 100644 --- a/common/src/resources.rs +++ b/common/src/resources.rs @@ -77,7 +77,7 @@ pub struct PlayerPhysicsSettings { /// Describe how players interact with other players. /// -/// Probably will be removed when we will discover better way +/// May be removed when we will discover better way /// to handle duels and murders #[derive(Copy, Clone, Debug, Deserialize, Serialize)] pub enum BattleMode { From 9300353c98143216d7e78fbaaa90b22eefd54e84 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 29 Jul 2021 20:11:39 +0300 Subject: [PATCH 05/13] Use emitter.emit() in server/sys/msg/register --- server/src/sys/msg/register.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 00923eb490..9f2e22f3b4 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -75,6 +75,7 @@ impl<'a> System<'a> for Sys { mut login_provider, ): Self::SystemData, ) { + let mut server_emitter = read_data.server_event_bus.emitter(); // Player list to send new players. let player_list = ( &read_data.uids, @@ -138,12 +139,10 @@ impl<'a> System<'a> for Sys { trace!(?r, "pending login returned"); match r { Err(e) => { - read_data - .server_event_bus - .emit_now(ServerEvent::ClientDisconnect( - entity, - common::comp::DisconnectReason::Kicked, - )); + server_emitter.emit(ServerEvent::ClientDisconnect( + entity, + common::comp::DisconnectReason::Kicked, + )); client.send(ServerRegisterAnswer::Err(e))?; return Ok(()); }, @@ -159,12 +158,10 @@ impl<'a> System<'a> for Sys { .find(|(_, _, old_player)| old_player.uuid() == uuid) { // Remove old client - read_data - .server_event_bus - .emit_now(ServerEvent::ClientDisconnect( - old_entity, - common::comp::DisconnectReason::NewerLogin, - )); + server_emitter.emit(ServerEvent::ClientDisconnect( + old_entity, + common::comp::DisconnectReason::NewerLogin, + )); let _ = old_client.send(ServerGeneral::Disconnect(DisconnectReason::Kicked( String::from("You have logged in from another location."), ))); From aaf2df19d482bd2707aae911a24dc9b11f55fb49 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 29 Jul 2021 21:17:19 +0300 Subject: [PATCH 06/13] Make Veloren compile again --- common/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index 98b2d0f288..c332b5f9eb 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -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] From 7413e7fd113d737321689595161dc6e8eece578d Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 29 Jul 2021 22:04:40 +0300 Subject: [PATCH 07/13] Prettify aura system --- common/systems/src/aura.rs | 96 +++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 53 deletions(-) diff --git a/common/systems/src/aura.rs b/common/systems/src/aura.rs index 38873a9632..96638d4067 100644 --- a/common/systems/src/aura.rs +++ b/common/systems/src/aura.rs @@ -118,28 +118,22 @@ impl<'a> System<'a> for Sys { || *target_uid == uid }; - match aura.target { - AuraTarget::GroupOf(uid) => { - if !same_group(uid) { - return; - } - }, - AuraTarget::NotGroupOf(uid) => { - if same_group(uid) { - return; - } - }, - AuraTarget::All => {}, - } + let is_target = match aura.target { + AuraTarget::GroupOf(uid) => same_group(uid), + AuraTarget::NotGroupOf(uid) => !same_group(uid), + AuraTarget::All => true, + }; - activate_aura( - aura, - target, - health, - &mut target_buffs, - &read_data, - &mut server_emitter, - ); + if is_target { + activate_aura( + aura, + target, + health, + &mut target_buffs, + &read_data, + &mut server_emitter, + ); + } } }); } @@ -165,39 +159,39 @@ fn activate_aura( read_data: &ReadData, server_emitter: &mut Emitter, ) { - let should_activate = |aura: &Aura| { - match aura.aura_kind { - AuraKind::Buff { kind, source, .. } => { + 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, + }; + let avoid_harm = || { let owner = match source { BuffSource::Character { by } => { read_data.uid_allocator.retrieve_entity_internal(by.into()) }, _ => None, }; - let avoid_harm = owner.map_or(false, |attacker| { + owner.map_or(false, |attacker| { let attacker = read_data.players.get(attacker); let target = read_data.players.get(target); combat::avoid_harm(attacker, target) - }); - 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, - }; + }) + }; - if conditions_held { - if kind.is_buff() { true } else { !avoid_harm } - } else { - false - } - }, - } + conditions_held && (kind.is_buff() || !avoid_harm()) + }, }; + if !should_activate { + return; + } + // TODO: When more aura kinds (besides Buff) are // implemented, match on them here match aura.aura_kind { @@ -207,14 +201,11 @@ fn activate_aura( category, source, } => { - if !should_activate(aura) { - return; - } - // 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 + // 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() @@ -237,9 +228,8 @@ fn activate_aura( }); } // 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 + // 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() From 497a94cd7a4161401f599442b4c100afb0c9f876 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Fri, 30 Jul 2021 14:34:05 +0300 Subject: [PATCH 08/13] Post review refactoring * inline `target_dodging` into struct declaration as they are named anyway * move `avoid_harm` check out of `Attack::apply_attack` so we don't need to pass whole Player component. * another cosmetic things --- common/src/combat.rs | 9 +++------ common/src/comp/player.rs | 11 +++++++---- common/systems/src/aura.rs | 2 +- common/systems/src/beam.rs | 14 ++++++++------ common/systems/src/melee.rs | 10 +++++++--- common/systems/src/projectile.rs | 19 +++++++++++-------- common/systems/src/shockwave.rs | 15 ++++++++------- server/src/events/entity_manipulation.rs | 21 +++++++++++++-------- server/src/sys/msg/register.rs | 6 +++--- 9 files changed, 61 insertions(+), 46 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index 237ddf46a4..a3a66c81be 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -53,7 +53,6 @@ pub enum AttackSource { #[derive(Copy, Clone)] pub struct AttackerInfo<'a> { pub entity: EcsEntity, - pub player: Option<&'a Player>, pub uid: Uid, pub energy: Option<&'a Energy>, pub combo: Option<&'a Combo>, @@ -63,7 +62,6 @@ pub struct AttackerInfo<'a> { #[cfg(not(target_arch = "wasm32"))] pub struct TargetInfo<'a> { pub entity: EcsEntity, - pub player: Option<&'a Player>, pub uid: Uid, pub inventory: Option<&'a Inventory>, pub stats: Option<&'a Stats>, @@ -76,6 +74,7 @@ pub struct TargetInfo<'a> { #[derive(Clone, Copy)] pub struct AttackOptions { pub target_dodging: bool, + pub avoid_harm: bool, pub target_group: GroupTarget, } @@ -182,12 +181,10 @@ impl Attack { ) -> bool { let AttackOptions { target_dodging, + avoid_harm, target_group, } = options; - let avoid_harm = - attacker.map_or(false, |attacker| avoid_harm(attacker.player, target.player)); - // target == OutOfGroup is basic heuristic that this // "attack" has negative effects. // @@ -464,7 +461,7 @@ impl Attack { // 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 avoid_harm(attacker: Option<&Player>, target: Option<&Player>) -> bool { +pub fn avoid_player_harm(attacker: Option<&Player>, target: Option<&Player>) -> bool { if let (Some(attacker), Some(target)) = (attacker, target) { return attacker.disallow_harm(target); } diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index f3ab03a081..c2666ef11c 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -23,6 +23,12 @@ pub struct Player { uuid: Uuid, } +impl BattleMode { + pub fn allow_harm(self, other: Self) -> bool { + matches!((self, other), (BattleMode::PvP, BattleMode::PvP)) + } +} + impl Player { pub fn new(alias: String, battle_mode: BattleMode, uuid: Uuid) -> Self { Self { @@ -38,10 +44,7 @@ impl Player { /// tea. pub fn allow_harm(&self, other: &Player) -> bool { // TODO: discuss if we want to keep self-harm - matches!( - (self.battle_mode, other.battle_mode), - (BattleMode::PvP, BattleMode::PvP) - ) + self.battle_mode.allow_harm(other.battle_mode) } /// Inverse of `allow_harm`. Read its doc to learn more. diff --git a/common/systems/src/aura.rs b/common/systems/src/aura.rs index 96638d4067..1d9f2cb3a0 100644 --- a/common/systems/src/aura.rs +++ b/common/systems/src/aura.rs @@ -180,7 +180,7 @@ fn activate_aura( owner.map_or(false, |attacker| { let attacker = read_data.players.get(attacker); let target = read_data.players.get(target); - combat::avoid_harm(attacker, target) + combat::avoid_player_harm(attacker, target) }) }; diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index b22bca06f8..959c61cd7f 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -1,5 +1,5 @@ use common::{ - combat::{AttackOptions, AttackSource, AttackerInfo, TargetInfo}, + combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ agent::{Sound, SoundKind}, Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, @@ -196,7 +196,6 @@ impl<'a> System<'a> for Sys { .zip(beam_segment.owner) .map(|(entity, uid)| AttackerInfo { entity, - player: read_data.players.get(entity), uid, energy: read_data.energies.get(entity), combo: read_data.combos.get(entity), @@ -205,7 +204,6 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, - player: read_data.players.get(target), uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), @@ -215,11 +213,15 @@ impl<'a> System<'a> for Sys { char_state: read_data.character_states.get(target), }; - // No luck with dodging beams - let target_dodging = false; + let avoid_harm = combat::avoid_player_harm( + beam_owner.and_then(|owner| read_data.players.get(owner)), + read_data.players.get(target), + ); let attack_options = AttackOptions { - target_dodging, + // No luck with dodging beams + target_dodging: false, + avoid_harm, target_group, }; diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index d7134396ff..aefdee9db1 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -1,5 +1,5 @@ use common::{ - combat::{AttackOptions, AttackSource, AttackerInfo, TargetInfo}, + combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ agent::{Sound, SoundKind}, Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Player, Pos, @@ -145,7 +145,6 @@ impl<'a> System<'a> for Sys { let attacker_info = Some(AttackerInfo { entity: attacker, - player: read_data.players.get(attacker), uid: *uid, energy: read_data.energies.get(attacker), combo: read_data.combos.get(attacker), @@ -154,7 +153,6 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, - player: read_data.players.get(target), uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), @@ -164,8 +162,14 @@ impl<'a> System<'a> for Sys { char_state: read_data.char_states.get(target), }; + let avoid_harm = combat::avoid_player_harm( + read_data.players.get(attacker), + read_data.players.get(target), + ); + let attack_options = AttackOptions { target_dodging, + avoid_harm, target_group, }; diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index e19430d328..a183306bbd 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -1,5 +1,5 @@ use common::{ - combat::{AttackOptions, AttackSource, AttackerInfo, TargetInfo}, + combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ agent::{Sound, SoundKind}, projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, @@ -115,6 +115,7 @@ impl<'a> System<'a> for Sys { } let projectile = &mut *projectile; + // FIXME: this code is highway to hell, resolve this for effect in projectile.hit_entity.drain(..) { match effect { projectile::Effect::Attack(attack) => { @@ -135,7 +136,6 @@ impl<'a> System<'a> for Sys { owner_entity.zip(projectile.owner).map(|(entity, uid)| { AttackerInfo { entity, - player: read_data.players.get(entity), uid, energy: read_data.energies.get(entity), combo: read_data.combos.get(entity), @@ -145,7 +145,6 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, - player: read_data.players.get(target), uid: other, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), @@ -155,10 +154,6 @@ impl<'a> System<'a> for Sys { char_state: read_data.character_states.get(target), }; - // They say witchers can dodge arrows, - // but we don't have witchers - let target_dodging = false; - if let Some(&body) = read_data.bodies.get(entity) { outcomes.push(Outcome::ProjectileHit { pos: pos.0, @@ -172,8 +167,16 @@ impl<'a> System<'a> for Sys { }); } + let avoid_harm = combat::avoid_player_harm( + owner_entity.and_then(|owner| read_data.players.get(owner)), + read_data.players.get(target), + ); + let attack_options = AttackOptions { - target_dodging, + // They say witchers can dodge arrows, + // but we don't have witchers + target_dodging: false, + avoid_harm, target_group, }; attack.apply_attack( diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index 19f49f7f1c..6e6a66bd65 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -1,5 +1,5 @@ use common::{ - combat::{AttackOptions, AttackSource, AttackerInfo, TargetInfo}, + combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ agent::{Sound, SoundKind}, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, @@ -192,7 +192,6 @@ impl<'a> System<'a> for Sys { .zip(shockwave.owner) .map(|(entity, uid)| AttackerInfo { entity, - player: read_data.players.get(entity), uid, energy: read_data.energies.get(entity), combo: read_data.combos.get(entity), @@ -201,7 +200,6 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, - player: read_data.players.get(target), uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), @@ -211,11 +209,14 @@ impl<'a> System<'a> for Sys { char_state: read_data.character_states.get(target), }; - // Trying roll during earthquake isn't the best idea - let target_dodging = false; - + let avoid_harm = combat::avoid_player_harm( + shockwave_owner.and_then(|owner| read_data.players.get(owner)), + read_data.players.get(target), + ); let attack_options = AttackOptions { - target_dodging, + // Trying roll during earthquake isn't the best idea + target_dodging: false, + avoid_harm, target_group, }; diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index aab9831833..ea10d993ad 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -871,7 +871,6 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o .zip(owner) .map(|(entity, uid)| combat::AttackerInfo { entity, - player: players.get(entity), uid, energy: energies.get(entity), combo: combos.get(entity), @@ -880,7 +879,6 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o let target_info = combat::TargetInfo { entity: entity_b, - player: players.get(entity_b), uid: *uid_b, inventory: inventories.get(entity_b), stats: stats_b_maybe, @@ -890,10 +888,15 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o char_state: char_state_b_maybe, }; + let avoid_harm = combat::avoid_player_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, + avoid_harm, target_group, }; @@ -926,6 +929,13 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o 1.0 - distance_squared / explosion.radius.powi(2) }; + let avoid_harm = || { + owner_entity.map_or(false, |attacker| { + let attacker = players.get(attacker); + let target = players.get(entity_b); + combat::avoid_player_harm(attacker, target) + }) + }; if strength > 0.0 { let is_alive = ecs .read_storage::() @@ -933,13 +943,8 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o .map_or(true, |h| !h.is_dead); if is_alive { - let avoid_harm = owner_entity.map_or(false, |attacker| { - let attacker = players.get(attacker); - let target = players.get(entity_b); - combat::avoid_harm(attacker, target) - }); effect.modify_strength(strength); - if !(effect.is_harm() && avoid_harm) { + if !(effect.is_harm() && avoid_harm()) { server.state().apply_effect(entity_b, effect.clone(), owner); } } diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 9f2e22f3b4..f67d4c35c3 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -37,8 +37,8 @@ pub struct ReadData<'a> { uids: ReadStorage<'a, Uid>, clients: ReadStorage<'a, Client>, server_event_bus: Read<'a, EventBus>, - _health_comp: ReadStorage<'a, Health>, // used by plugin feature - _plugin_mgr: ReadPlugin<'a>, // used by plugin feature + _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 } @@ -117,7 +117,7 @@ impl<'a> System<'a> for Sys { #[cfg(feature = "plugins")] let ecs_world = EcsWorld { entities: &read_data.entities, - health: (&read_data._health_comp).into(), + health: (&read_data._healths).into(), uid: (&read_data.uids).into(), player: (&players).into(), uid_allocator: &read_data._uid_allocator, From 1efc3188ef20f10d2922628df37840ebc4442099 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Fri, 30 Jul 2021 19:41:12 +0300 Subject: [PATCH 09/13] Refactor projectile system --- common/systems/src/projectile.rs | 268 +++++++++++++++++++------------ 1 file changed, 168 insertions(+), 100 deletions(-) diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index a183306bbd..941e6f6254 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -5,7 +5,7 @@ use common::{ projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, 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::*; @@ -115,106 +115,36 @@ impl<'a> System<'a> for Sys { } let projectile = &mut *projectile; - // FIXME: this code is highway to hell, resolve this + + 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 = projectile.owner.and_then(entity_of); + let projectile_info = ProjectileInfo { + entity, + effect, + owner_uid: projectile.owner, + owner, + ori: orientations.get(entity), + pos, + }; - let owner_entity = projectile.owner.and_then(|u| { - read_data.uid_allocator.retrieve_entity_internal(u.into()) - }); + let target = entity_of(other); + let projectile_target_info = ProjectileTargetInfo { + uid: other, + entity: target, + target_group, + ori: target.and_then(|target| orientations.get(target)), + }; - let attacker_info = - owner_entity.zip(projectile.owner).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: 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(), - }); - } - - let avoid_harm = combat::avoid_player_harm( - owner_entity.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, - avoid_harm, - target_group, - }; - attack.apply_attack( - attacker_info, - target_info, - dir, - attack_options, - 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)); - } - } - }, - _ => {}, - } + dispatch_hit( + projectile_info, + projectile_target_info, + &read_data, + &mut projectile_vanished, + &mut outcomes, + &mut server_emitter, + ); } if projectile_vanished { @@ -266,3 +196,141 @@ impl<'a> System<'a> for Sys { } } } + +struct ProjectileInfo<'a> { + entity: EcsEntity, + effect: projectile::Effect, + owner_uid: Option, + owner: Option, + ori: Option<&'a Ori>, + pos: &'a Pos, +} + +struct ProjectileTargetInfo<'a> { + uid: Uid, + entity: Option, + 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, + server_emitter: &mut Emitter, +) { + 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 avoid_harm = combat::avoid_player_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, + avoid_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 => {}, + } +} From d9e2d501cde0b103d72f40545d93e9522447522b Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Fri, 30 Jul 2021 23:27:25 +0300 Subject: [PATCH 10/13] Prettify server/sys/msg/register.rs --- server/src/sys/msg/register.rs | 54 ++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index f67d4c35c3..bb6a0b5d81 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -37,6 +37,9 @@ pub struct ReadData<'a> { uids: ReadStorage<'a, Uid>, clients: ReadStorage<'a, Client>, server_event_bus: Read<'a, EventBus>, + 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 @@ -49,9 +52,6 @@ impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] type SystemData = ( ReadData<'a>, - ReadExpect<'a, PlayerMetrics>, - ReadExpect<'a, Settings>, - ReadExpect<'a, EditableSettings>, WriteStorage<'a, Player>, WriteStorage<'a, Admin>, WriteStorage<'a, PendingLogin>, @@ -66,9 +66,6 @@ impl<'a> System<'a> for Sys { _job: &mut Job, ( read_data, - player_metrics, - settings, - editable_settings, mut players, mut admins, mut pending_logins, @@ -129,9 +126,9 @@ impl<'a> System<'a> for Sys { &ecs_world, #[cfg(feature = "plugins")] &read_data._plugin_mgr, - &*editable_settings.admins, - &*editable_settings.whitelist, - &*editable_settings.banlist, + &*read_data.editable_settings.admins, + &*read_data.editable_settings.whitelist, + &*read_data.editable_settings.banlist, ) { None => return Ok(()), Some(r) => { @@ -176,8 +173,8 @@ impl<'a> System<'a> for Sys { return Ok(()); } - let player = Player::new(username, settings.battle_mode, 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 @@ -188,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 @@ -225,21 +222,26 @@ 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)) = (read_data.uids.get(entity), players.get(entity)) { - let mut lazy_msg = None; - for (_, client) in (&players, &read_data.clients).join() { - if lazy_msg.is_none() { - lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate( - PlayerListUpdate::Add(*uid, PlayerInfo { - 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)); + let player_info = read_data.uids.get(entity).zip(players.get(entity)); + let (uid, player) = if let Some((uid, player)) = player_info { + (uid, player) + } else { + continue; + }; + + let mut lazy_msg = None; + for (_, client) in (&players, &read_data.clients).join() { + if lazy_msg.is_none() { + lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate( + PlayerListUpdate::Add(*uid, PlayerInfo { + 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)); } } } From 5e6007db0365ea84711804e7b7817f1b6f58ebae Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 31 Jul 2021 01:32:29 +0300 Subject: [PATCH 11/13] Return self-harm --- common/src/comp/player.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index c2666ef11c..183e2dc21d 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -42,9 +42,9 @@ impl Player { /// /// 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 allow_harm(&self, other: &Player) -> bool { - // TODO: discuss if we want to keep self-harm - self.battle_mode.allow_harm(other.battle_mode) + self.battle_mode.allow_harm(other.battle_mode) || self.uuid == other.uuid } /// Inverse of `allow_harm`. Read its doc to learn more. From 44916383c6f6b5992da0a299898e13739ceb57d2 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 31 Jul 2021 12:01:13 +0300 Subject: [PATCH 12/13] continue -> filter_map to handle new players --- server/src/sys/msg/register.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index bb6a0b5d81..816640be05 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -221,14 +221,11 @@ impl<'a> System<'a> for Sys { // Handle new players. // Tell all clients to add them to the player list. - for entity in new_players { + let player_info = |entity| { let player_info = read_data.uids.get(entity).zip(players.get(entity)); - let (uid, player) = if let Some((uid, player)) = player_info { - (uid, player) - } else { - continue; - }; - + 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, &read_data.clients).join() { if lazy_msg.is_none() { From 4766450258d74971baa730b0b838b8a8f1703436 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 31 Jul 2021 20:53:09 +0300 Subject: [PATCH 13/13] Avoid negative bounds in harm checks * disallow_harm -> allow_harm to avoid negative reasoning since it mostly requires double negation in code * allow_harm -> may_harm to specify side-effect free --- common/src/combat.rs | 19 +++++++++--------- common/src/comp/player.rs | 9 ++------- common/systems/src/aura.rs | 16 +++++++++++---- common/systems/src/beam.rs | 4 ++-- common/systems/src/melee.rs | 4 ++-- common/systems/src/projectile.rs | 4 ++-- common/systems/src/shockwave.rs | 4 ++-- server/src/events/entity_manipulation.rs | 25 +++++++++++++++++------- 8 files changed, 49 insertions(+), 36 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index a3a66c81be..56f8b12dfa 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -74,7 +74,7 @@ pub struct TargetInfo<'a> { #[derive(Clone, Copy)] pub struct AttackOptions { pub target_dodging: bool, - pub avoid_harm: bool, + pub may_harm: bool, pub target_group: GroupTarget, } @@ -181,7 +181,7 @@ impl Attack { ) -> bool { let AttackOptions { target_dodging, - avoid_harm, + may_harm, target_group, } = options; @@ -192,11 +192,11 @@ impl Attack { // it should avoid such "damage" or effect let avoid_damage = |attack_damage: &AttackDamage| { matches!(attack_damage.target, Some(GroupTarget::OutOfGroup)) - && (target_dodging || avoid_harm) + && (target_dodging || !may_harm) }; let avoid_effect = |attack_effect: &AttackEffect| { matches!(attack_effect.target, Some(GroupTarget::OutOfGroup)) - && (target_dodging || avoid_harm) + && (target_dodging || !may_harm) }; let is_crit = thread_rng().gen::() < self.crit_chance; let mut is_applied = false; @@ -456,16 +456,15 @@ impl Attack { } } -/// Checks if we should avoid negative effects from one player to another +/// 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 avoid_player_harm(attacker: Option<&Player>, target: Option<&Player>) -> bool { - if let (Some(attacker), Some(target)) = (attacker, target) { - return attacker.disallow_harm(target); - } - false +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"))] diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index 183e2dc21d..76db8ddc55 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -24,7 +24,7 @@ pub struct Player { } impl BattleMode { - pub fn allow_harm(self, other: Self) -> bool { + pub fn may_harm(self, other: Self) -> bool { matches!((self, other), (BattleMode::PvP, BattleMode::PvP)) } } @@ -43,12 +43,7 @@ impl Player { /// 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 allow_harm(&self, other: &Player) -> bool { - self.battle_mode.allow_harm(other.battle_mode) || self.uuid == other.uuid - } - - /// Inverse of `allow_harm`. Read its doc to learn more. - pub fn disallow_harm(&self, other: &Player) -> bool { !self.allow_harm(other) } + 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() } diff --git a/common/systems/src/aura.rs b/common/systems/src/aura.rs index 1d9f2cb3a0..f9201b3d8d 100644 --- a/common/systems/src/aura.rs +++ b/common/systems/src/aura.rs @@ -170,21 +170,29 @@ fn activate_aura( // Add other specific buff conditions here _ => true, }; - let avoid_harm = || { + + // 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(false, |attacker| { + owner.map_or(true, |attacker| { let attacker = read_data.players.get(attacker); let target = read_data.players.get(target); - combat::avoid_player_harm(attacker, target) + combat::may_harm(attacker, target) }) }; - conditions_held && (kind.is_buff() || !avoid_harm()) + conditions_held && (kind.is_buff() || may_harm()) }, }; diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index 959c61cd7f..86854bd814 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -214,14 +214,14 @@ impl<'a> System<'a> for Sys { }; - let avoid_harm = combat::avoid_player_harm( + 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, - avoid_harm, + may_harm, target_group, }; diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index aefdee9db1..433f6b48c5 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -162,14 +162,14 @@ impl<'a> System<'a> for Sys { char_state: read_data.char_states.get(target), }; - let avoid_harm = combat::avoid_player_harm( + let may_harm = combat::may_harm( read_data.players.get(attacker), read_data.players.get(target), ); let attack_options = AttackOptions { target_dodging, - avoid_harm, + may_harm, target_group, }; diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index 941e6f6254..4b48cf0d4d 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -281,7 +281,7 @@ fn dispatch_hit( }); } - let avoid_harm = combat::avoid_player_harm( + let may_harm = combat::may_harm( owner.and_then(|owner| read_data.players.get(owner)), read_data.players.get(target), ); @@ -290,7 +290,7 @@ fn dispatch_hit( // They say witchers can dodge arrows, // but we don't have witchers target_dodging: false, - avoid_harm, + may_harm, target_group: projectile_target_info.target_group, }; diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index 6e6a66bd65..86ad74eca7 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -209,14 +209,14 @@ impl<'a> System<'a> for Sys { char_state: read_data.character_states.get(target), }; - let avoid_harm = combat::avoid_player_harm( + 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, - avoid_harm, + may_harm, target_group, }; diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index ea10d993ad..c9e4fbff3f 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -888,7 +888,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o char_state: char_state_b_maybe, }; - let avoid_harm = combat::avoid_player_harm( + let may_harm = combat::may_harm( owner_entity.and_then(|owner| players.get(owner)), players.get(entity_b), ); @@ -896,7 +896,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o // cool guyz maybe don't look at explosions // but they still got hurt, it's not Hollywood target_dodging: false, - avoid_harm, + may_harm, target_group, }; @@ -929,11 +929,22 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o 1.0 - distance_squared / explosion.radius.powi(2) }; - let avoid_harm = || { + // 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 = players.get(attacker); - let target = players.get(entity_b); - combat::avoid_player_harm(attacker, target) + 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 { @@ -944,7 +955,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o if is_alive { effect.modify_strength(strength); - if !(effect.is_harm() && avoid_harm()) { + if !effect.is_harm() || may_harm() { server.state().apply_effect(entity_b, effect.clone(), owner); } }