From 47098554652ca9b5a536134320f22a625cb5f7a3 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 27 Aug 2021 15:52:52 +0300 Subject: [PATCH 01/14] Don't forget about pets in PvP checks --- common/src/combat.rs | 9 +++---- common/src/comp/agent.rs | 13 ++++++++-- common/systems/src/aura.rs | 24 ++++++++++++------ common/systems/src/beam.rs | 21 ++++++++++++---- common/systems/src/melee.rs | 24 +++++++++++++----- common/systems/src/projectile.rs | 21 ++++++++++++---- common/systems/src/shockwave.rs | 22 ++++++++++++---- server/src/events/entity_manipulation.rs | 32 ++++++++++++++++++------ 8 files changed, 123 insertions(+), 43 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index e8fb6270da..bc73dc91bb 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -469,11 +469,10 @@ 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 +/// Says if we should allow negative effects from one player to another +/// +/// NOTE: this function doesn't handle pets or friendly-fire, you will need to +/// figure it out on call-side. pub fn may_harm(attacker: Option<&Player>, target: Option<&Player>) -> bool { attacker .zip(target) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 72cd5bc641..3760b03544 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -3,10 +3,10 @@ use crate::{ path::Chaser, rtsim::RtSimController, trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult}, - uid::Uid, + uid::{Uid, UidAllocator}, }; use serde::Deserialize; -use specs::{Component, Entity as EcsEntity}; +use specs::{saveload::MarkerAllocator, Component, Entity as EcsEntity}; use specs_idvs::IdvStorage; use std::{collections::VecDeque, fmt}; use strum::IntoEnumIterator; @@ -34,6 +34,15 @@ pub enum Alignment { Passive, } +// Helper function to get owner +pub fn owner_of(alignment: Option<Alignment>, uid_allocator: &UidAllocator) -> Option<EcsEntity> { + if let Some(Alignment::Owned(uid)) = alignment { + uid_allocator.retrieve_entity_internal(uid.into()) + } else { + None + } +} + #[derive(Copy, Clone, Debug, PartialEq)] pub enum Mark { Merchant, diff --git a/common/systems/src/aura.rs b/common/systems/src/aura.rs index f9201b3d8d..31264e647a 100644 --- a/common/systems/src/aura.rs +++ b/common/systems/src/aura.rs @@ -1,10 +1,11 @@ use common::{ combat, comp::{ + agent::owner_of, aura::{AuraChange, AuraKey, AuraKind, AuraTarget}, buff::{Buff, BuffCategory, BuffChange, BuffSource}, group::Group, - Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos, + Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos, }, event::{Emitter, EventBus, ServerEvent}, resources::DeltaTime, @@ -27,6 +28,7 @@ pub struct ReadData<'a> { cached_spatial_grid: Read<'a, common::CachedSpatialGrid>, positions: ReadStorage<'a, Pos>, char_states: ReadStorage<'a, CharacterState>, + alignments: ReadStorage<'a, Alignment>, healths: ReadStorage<'a, Health>, groups: ReadStorage<'a, Group>, uids: ReadStorage<'a, Uid>, @@ -174,7 +176,7 @@ fn activate_aura( // 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. + // even if it's intented mechanic. // // Not that we have this for now, but think about this // when we will add this. @@ -185,11 +187,19 @@ fn activate_aura( }, _ => 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) - }) + let owner_if_pet = |entity| { + // Return owner entity if pet, + // or just return entity back otherwise + owner_of( + read_data.alignments.get(entity).copied(), + &read_data.uid_allocator, + ) + .unwrap_or(entity) + }; + combat::may_harm( + owner.and_then(|owner| read_data.players.get(owner_if_pet(owner))), + read_data.players.get(owner_if_pet(target)), + ) }; conditions_held && (kind.is_buff() || may_harm()) diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index 86854bd814..8b2403c3a4 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -1,9 +1,9 @@ use common::{ combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ - agent::{Sound, SoundKind}, - Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, - Inventory, Ori, Player, Pos, Scale, Stats, + agent::{owner_of, Sound, SoundKind}, + Alignment, Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, + HealthSource, Inventory, Ori, Player, Pos, Scale, Stats, }, event::{EventBus, ServerEvent}, outcome::Outcome, @@ -36,6 +36,7 @@ pub struct ReadData<'a> { uids: ReadStorage<'a, Uid>, positions: ReadStorage<'a, Pos>, orientations: ReadStorage<'a, Ori>, + alignments: ReadStorage<'a, Alignment>, scales: ReadStorage<'a, Scale>, bodies: ReadStorage<'a, Body>, healths: ReadStorage<'a, Health>, @@ -214,9 +215,19 @@ impl<'a> System<'a> for Sys { }; + // PvP check + let owner_if_pet = |entity| { + // Return owner entity if pet, + // or just return entity back otherwise + owner_of( + read_data.alignments.get(entity).copied(), + &read_data.uid_allocator, + ) + .unwrap_or(entity) + }; let may_harm = combat::may_harm( - beam_owner.and_then(|owner| read_data.players.get(owner)), - read_data.players.get(target), + beam_owner.and_then(|owner| read_data.players.get(owner_if_pet(owner))), + read_data.players.get(owner_if_pet(target)), ); let attack_options = AttackOptions { // No luck with dodging beams diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index 433f6b48c5..005efad509 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -1,14 +1,14 @@ use common::{ combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ - agent::{Sound, SoundKind}, - Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Player, Pos, - Scale, Stats, + agent::{owner_of, Sound, SoundKind}, + Alignment, Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, + Player, Pos, Scale, Stats, }, event::{EventBus, ServerEvent}, outcome::Outcome, resources::Time, - uid::Uid, + uid::{Uid, UidAllocator}, util::Dir, GroupTarget, }; @@ -21,11 +21,13 @@ use vek::*; #[derive(SystemData)] pub struct ReadData<'a> { time: Read<'a, Time>, + uid_allocator: Read<'a, UidAllocator>, entities: Entities<'a>, players: ReadStorage<'a, Player>, uids: ReadStorage<'a, Uid>, positions: ReadStorage<'a, Pos>, orientations: ReadStorage<'a, Ori>, + alignments: ReadStorage<'a, Alignment>, scales: ReadStorage<'a, Scale>, bodies: ReadStorage<'a, Body>, healths: ReadStorage<'a, Health>, @@ -162,9 +164,19 @@ impl<'a> System<'a> for Sys { char_state: read_data.char_states.get(target), }; + // PvP check + let owner_if_pet = |entity| { + // Return owner entity if pet, + // or just return entity back otherwise + owner_of( + read_data.alignments.get(entity).copied(), + &read_data.uid_allocator, + ) + .unwrap_or(entity) + }; let may_harm = combat::may_harm( - read_data.players.get(attacker), - read_data.players.get(target), + read_data.players.get(owner_if_pet(attacker)), + read_data.players.get(owner_if_pet(target)), ); let attack_options = AttackOptions { diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index 44f7fe66cd..f27ac95466 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -1,9 +1,9 @@ use common::{ combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ - agent::{Sound, SoundKind}, - projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, - Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel, + agent::{owner_of, Sound, SoundKind}, + projectile, Alignment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, + Inventory, Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel, }, event::{Emitter, EventBus, ServerEvent}, outcome::Outcome, @@ -31,6 +31,7 @@ pub struct ReadData<'a> { server_bus: Read<'a, EventBus<ServerEvent>>, uids: ReadStorage<'a, Uid>, positions: ReadStorage<'a, Pos>, + alignments: ReadStorage<'a, Alignment>, physics_states: ReadStorage<'a, PhysicsState>, velocities: ReadStorage<'a, Vel>, inventories: ReadStorage<'a, Inventory>, @@ -299,9 +300,19 @@ fn dispatch_hit( }); } + // PvP check + let owner_if_pet = |entity| { + // Return owner entity if pet, + // or just return entity back otherwise + owner_of( + read_data.alignments.get(entity).copied(), + &read_data.uid_allocator, + ) + .unwrap_or(entity) + }; let may_harm = combat::may_harm( - owner.and_then(|owner| read_data.players.get(owner)), - read_data.players.get(target), + owner.and_then(|owner| read_data.players.get(owner_if_pet(owner))), + read_data.players.get(owner_if_pet(target)), ); let attack_options = AttackOptions { diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index 86ad74eca7..75fd5db924 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -1,9 +1,9 @@ use common::{ combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ - agent::{Sound, SoundKind}, - Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, - PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats, + agent::{owner_of, Sound, SoundKind}, + Alignment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, + Ori, PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats, }, event::{EventBus, ServerEvent}, outcome::Outcome, @@ -31,6 +31,7 @@ pub struct ReadData<'a> { uids: ReadStorage<'a, Uid>, positions: ReadStorage<'a, Pos>, orientations: ReadStorage<'a, Ori>, + alignments: ReadStorage<'a, Alignment>, scales: ReadStorage<'a, Scale>, bodies: ReadStorage<'a, Body>, healths: ReadStorage<'a, Health>, @@ -209,9 +210,20 @@ impl<'a> System<'a> for Sys { char_state: read_data.character_states.get(target), }; + // PvP check + let owner_if_pet = |entity| { + // Return owner entity if pet, + // or just return entity back otherwise + owner_of( + read_data.alignments.get(entity).copied(), + &read_data.uid_allocator, + ) + .unwrap_or(entity) + }; let may_harm = combat::may_harm( - shockwave_owner.and_then(|owner| read_data.players.get(owner)), - read_data.players.get(target), + shockwave_owner + .and_then(|owner| read_data.players.get(owner_if_pet(owner))), + read_data.players.get(owner_if_pet(target)), ); let attack_options = AttackOptions { // Trying roll during earthquake isn't the best idea diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 56978c7380..5d81428093 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1,7 +1,7 @@ use crate::{ client::Client, comp::{ - agent::{Agent, AgentEvent, Sound, SoundKind}, + agent::{owner_of, Agent, AgentEvent, Sound, SoundKind}, biped_large, bird_large, quadruped_low, quadruped_medium, quadruped_small, skills::SkillGroupKind, theropod, BuffKind, BuffSource, PhysicsState, @@ -873,6 +873,8 @@ 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 alignments = &ecs.read_storage::<Alignment>(); + let uid_allocator = &ecs.read_resource::<UidAllocator>(); let players = &ecs.read_storage::<comp::Player>(); for ( entity_b, @@ -942,9 +944,16 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o char_state: char_state_b_maybe, }; + // PvP check + let owner_if_pet = |entity| { + // Return owner entity if pet, + // or just return entity back otherwise + owner_of(alignments.get(entity).copied(), uid_allocator) + .unwrap_or(entity) + }; let may_harm = combat::may_harm( - owner_entity.and_then(|owner| players.get(owner)), - players.get(entity_b), + owner_entity.and_then(|owner| players.get(owner_if_pet(owner))), + players.get(owner_if_pet(entity_b)), ); let attack_options = combat::AttackOptions { // cool guyz maybe don't look at explosions @@ -968,6 +977,8 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o } }, RadiusEffect::Entity(mut effect) => { + let alignments = &ecs.read_storage::<Alignment>(); + let uid_allocator = &ecs.read_resource::<UidAllocator>(); let players = &ecs.read_storage::<comp::Player>(); for (entity_b, pos_b, body_b_maybe) in ( &ecs.entities(), @@ -994,12 +1005,17 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o // you want to harm yourself. // // This can be changed later. + // PvP check + let owner_if_pet = |entity| { + // Return owner entity if pet, + // or just return entity back otherwise + owner_of(alignments.get(entity).copied(), uid_allocator).unwrap_or(entity) + }; 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 - }) + combat::may_harm( + owner_entity.and_then(|owner| players.get(owner_if_pet(owner))), + players.get(owner_if_pet(entity_b)), + ) || owner_entity.map_or(true, |entity_a| entity_a == entity_b) }; if strength > 0.0 { let is_alive = ecs From e5e3349fb6978920740d5c5041cc96fb1e688e9c Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 27 Aug 2021 17:08:18 +0300 Subject: [PATCH 02/14] Add PerPlayer server flag for BattleMode --- server/src/settings.rs | 10 ++++++++-- server/src/sys/msg/register.rs | 10 +++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/server/src/settings.rs b/server/src/settings.rs index 5c17adb5e1..7e45e3d668 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -40,6 +40,12 @@ pub struct X509FilePair { pub key: PathBuf, } +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +pub enum ServerBattleMode { + Global(BattleMode), + PerPlayer { default: BattleMode }, +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct Settings { @@ -49,7 +55,7 @@ pub struct Settings { pub quic_files: Option<X509FilePair>, pub max_players: usize, pub world_seed: u32, - pub battle_mode: BattleMode, + pub battle_mode: ServerBattleMode, pub server_name: String, pub start_time: f64, /// When set to None, loads the default map file (if available); otherwise, @@ -79,7 +85,7 @@ impl Default for Settings { world_seed: DEFAULT_WORLD_SEED, server_name: "Veloren Alpha".into(), max_players: 100, - battle_mode: BattleMode::PvP, + battle_mode: ServerBattleMode::Global(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 816640be05..a2a5b3cbf9 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -2,6 +2,7 @@ use crate::{ client::Client, login_provider::{LoginProvider, PendingLogin}, metrics::PlayerMetrics, + settings::ServerBattleMode, EditableSettings, Settings, }; use common::{ @@ -173,7 +174,14 @@ impl<'a> System<'a> for Sys { return Ok(()); } - let player = Player::new(username, read_data.settings.battle_mode, uuid); + let battle_mode = match read_data.settings.battle_mode { + ServerBattleMode::Global(mode) => mode, + // FIXME: + // Should this use just default battle_mode + // or should we take it from last change? + ServerBattleMode::PerPlayer { default: mode } => mode, + }; + let player = Player::new(username, battle_mode, uuid); let admin = read_data.editable_settings.admins.get(&uuid); if !player.is_valid() { From 1cedaa44176c1d2e757cbb9f8910dd66a9ad6781 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 27 Aug 2021 19:25:05 +0300 Subject: [PATCH 03/14] Implement /battlemode_force command + add placeholder for /battlemode command (currently can only show your battle mode) --- common/src/cmd.rs | 23 ++++++++++++++++++ server/src/cmd.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 7c1a2d6ebf..fc3352c6af 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -47,6 +47,8 @@ pub enum ChatCommand { Alias, ApplyBuff, Ban, + BattleMode, + BattleModeForce, Build, BuildAreaAdd, BuildAreaList, @@ -321,6 +323,25 @@ impl ChatCommand { true for overwrite to alter an existing ban..", Some(Moderator), ), + ChatCommand::BattleMode => cmd( + vec![Enum( + "battle mode", + vec!["pvp".to_owned(), "pve".to_owned()], + Optional, + )], + "Set your battle mode to pvp/pve.\n\ + If called without arguments will show current battle mode.", + None, + ), + ChatCommand::BattleModeForce => cmd( + vec![Enum( + "battle mode", + vec!["pvp".to_owned(), "pve".to_owned()], + Required, + )], + "Change your battle mode flag without any checks", + Some(Admin), + ), ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", None), ChatCommand::BuildAreaAdd => cmd( vec![ @@ -623,6 +644,8 @@ impl ChatCommand { ChatCommand::Alias => "alias", ChatCommand::ApplyBuff => "buff", ChatCommand::Ban => "ban", + ChatCommand::BattleMode => "battlemode", + ChatCommand::BattleModeForce => "battlemode_force", ChatCommand::Build => "build", ChatCommand::BuildAreaAdd => "build_area_add", ChatCommand::BuildAreaList => "build_area_list", diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 960044b230..6e71021f01 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -31,7 +31,7 @@ use common::{ event::{EventBus, ServerEvent}, generation::EntityInfo, npc::{self, get_npc_name}, - resources::{PlayerPhysicsSettings, TimeOfDay}, + resources::{PlayerPhysicsSettings, TimeOfDay, BattleMode}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, uid::Uid, vol::RectVolSize, @@ -113,6 +113,8 @@ fn do_command( ChatCommand::Alias => handle_alias, ChatCommand::ApplyBuff => handle_apply_buff, ChatCommand::Ban => handle_ban, + ChatCommand::BattleMode => handle_battlemode, + ChatCommand::BattleModeForce => handle_battlemode_force, ChatCommand::Build => handle_build, ChatCommand::BuildAreaAdd => handle_build_area_add, ChatCommand::BuildAreaList => handle_build_area_list, @@ -3094,6 +3096,62 @@ fn handle_ban( } } +fn handle_battlemode( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + args: Vec<String>, + _action: &ChatCommand, +) -> CmdResult<()> { + let ecs = &server.state.ecs(); + if let Some(mode) = parse_args!(args, String) { + Err("Seting mode isn't implemented".to_owned()) + } else { + let players = ecs.read_storage::<comp::Player>(); + let player = players + .get(target) + .ok_or_else(|| "Cannot get player component for target".to_string())?; + server.notify_client( + client, + ServerGeneral::server_msg( + ChatType::CommandInfo, + format!("Battle mode is {:?}", player.battle_mode), + ), + ); + Ok(()) + } +} + +fn handle_battlemode_force( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + args: Vec<String>, + action: &ChatCommand, +) -> CmdResult<()> { + let ecs = &server.state.ecs(); + let mut players = ecs.write_storage::<comp::Player>(); + let mode = parse_args!(args, String).ok_or_else(|| action.help_string())?; + let mode = match mode.as_str() { + "pvp" => BattleMode::PvP, + "pve" => BattleMode::PvE, + _ => return Err("Available modes: pvp, pve".to_owned()), + }; + if let Some(ref mut player) = players.get_mut(target) { + player.battle_mode = mode; + server.notify_client( + client, + ServerGeneral::server_msg( + ChatType::CommandInfo, + format!("Set battle_mode to {:?}", player.battle_mode), + ), + ); + Ok(()) + } else { + Err("Cannot get player component for target".to_owned()) + } +} + fn handle_unban( server: &mut Server, client: EcsEntity, From 7be1c4d14a26ba75266a4db69d41a92888543e46 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 27 Aug 2021 21:49:58 +0300 Subject: [PATCH 04/14] Deduplicate pvp-checks --- common/src/cmd.rs | 1 + common/src/combat.rs | 64 ++++++++++++++++++++---- common/src/comp/agent.rs | 13 +---- common/systems/src/aura.rs | 19 +++---- common/systems/src/beam.rs | 18 +++---- common/systems/src/melee.rs | 18 +++---- common/systems/src/projectile.rs | 18 +++---- common/systems/src/shockwave.rs | 19 +++---- server/src/cmd.rs | 2 +- server/src/events/entity_manipulation.rs | 36 ++++++------- 10 files changed, 101 insertions(+), 107 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index fc3352c6af..b1a6890b74 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -323,6 +323,7 @@ impl ChatCommand { true for overwrite to alter an existing ban..", Some(Moderator), ), + #[rustfmt::skip] ChatCommand::BattleMode => cmd( vec![Enum( "battle mode", diff --git a/common/src/combat.rs b/common/src/combat.rs index bc73dc91bb..f2d02722b7 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -12,13 +12,13 @@ use crate::{ }, poise::PoiseChange, skills::SkillGroupKind, - Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange, - HealthSource, Inventory, Ori, Player, Poise, SkillSet, Stats, + Alignment, Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, + HealthChange, HealthSource, Inventory, Ori, Player, Poise, SkillSet, Stats, }, event::ServerEvent, outcome::Outcome, states::utils::StageSection, - uid::Uid, + uid::{Uid, UidAllocator}, util::Dir, }; @@ -28,7 +28,7 @@ use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; #[cfg(not(target_arch = "wasm32"))] -use specs::Entity as EcsEntity; +use specs::{saveload::MarkerAllocator, Entity as EcsEntity, ReadStorage}; #[cfg(not(target_arch = "wasm32"))] use std::{ops::MulAssign, time::Duration}; #[cfg(not(target_arch = "wasm32"))] use vek::*; @@ -469,14 +469,56 @@ impl Attack { } } -/// Says if we should allow negative effects from one player to another +/// Function that checks for unintentional PvP between players. /// -/// NOTE: this function doesn't handle pets or friendly-fire, you will need to -/// figure it out on call-side. -pub fn may_harm(attacker: Option<&Player>, target: Option<&Player>) -> bool { - attacker - .zip(target) - .map_or(true, |(attacker, target)| attacker.may_harm(target)) +/// Returns `false` if attack will create unintentional conflict, +/// e.g. if player with PvE mode will harm pets of other players +/// or other players will do the same to such player. +/// +/// If both players have PvP mode enabled, interact with NPC and +/// in any other case, this function will return `true` +// TODO: add parameter for doing self-harm? +pub fn may_harm( + alignments: &ReadStorage<Alignment>, + players: &ReadStorage<Player>, + uid_allocator: &UidAllocator, + attacker: Option<EcsEntity>, + target: EcsEntity, +) -> bool { + // Return owner entity if pet, + // or just return entity back otherwise + let owner_if_pet = |entity| { + let alignment = alignments.get(entity).copied(); + if let Some(Alignment::Owned(uid)) = alignment { + // return original entity + // if can't get owner + uid_allocator + .retrieve_entity_internal(uid.into()) + .unwrap_or(entity) + } else { + entity + } + }; + + // Just return ok if attacker is unknown, it's probably + // environment or command. + let attacker = match attacker { + Some(attacker) => attacker, + None => return true, + }; + + // "Dereference" to owner if this is a pet. + let attacker = owner_if_pet(attacker); + let target = owner_if_pet(target); + + // Get player components + let attacker_info = players.get(attacker); + let target_info = players.get(target); + + // Return `true` if not players. + attacker_info + .zip(target_info) + .map_or(true, |(a, t)| a.may_harm(t)) } #[cfg(not(target_arch = "wasm32"))] diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 3760b03544..72cd5bc641 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -3,10 +3,10 @@ use crate::{ path::Chaser, rtsim::RtSimController, trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult}, - uid::{Uid, UidAllocator}, + uid::Uid, }; use serde::Deserialize; -use specs::{saveload::MarkerAllocator, Component, Entity as EcsEntity}; +use specs::{Component, Entity as EcsEntity}; use specs_idvs::IdvStorage; use std::{collections::VecDeque, fmt}; use strum::IntoEnumIterator; @@ -34,15 +34,6 @@ pub enum Alignment { Passive, } -// Helper function to get owner -pub fn owner_of(alignment: Option<Alignment>, uid_allocator: &UidAllocator) -> Option<EcsEntity> { - if let Some(Alignment::Owned(uid)) = alignment { - uid_allocator.retrieve_entity_internal(uid.into()) - } else { - None - } -} - #[derive(Copy, Clone, Debug, PartialEq)] pub enum Mark { Merchant, diff --git a/common/systems/src/aura.rs b/common/systems/src/aura.rs index 31264e647a..f316e6fde7 100644 --- a/common/systems/src/aura.rs +++ b/common/systems/src/aura.rs @@ -1,7 +1,6 @@ use common::{ combat, comp::{ - agent::owner_of, aura::{AuraChange, AuraKey, AuraKind, AuraTarget}, buff::{Buff, BuffCategory, BuffChange, BuffSource}, group::Group, @@ -178,7 +177,7 @@ fn activate_aura( // Which means that you can't apply debuffs on you and your group // even if it's intented mechanic. // - // Not that we have this for now, but think about this + // We don't have this for now, but think about this // when we will add this. let may_harm = || { let owner = match source { @@ -187,18 +186,12 @@ fn activate_aura( }, _ => None, }; - let owner_if_pet = |entity| { - // Return owner entity if pet, - // or just return entity back otherwise - owner_of( - read_data.alignments.get(entity).copied(), - &read_data.uid_allocator, - ) - .unwrap_or(entity) - }; combat::may_harm( - owner.and_then(|owner| read_data.players.get(owner_if_pet(owner))), - read_data.players.get(owner_if_pet(target)), + &read_data.alignments, + &read_data.players, + &read_data.uid_allocator, + owner, + target, ) }; diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index 8b2403c3a4..06182c3953 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -1,7 +1,7 @@ use common::{ combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ - agent::{owner_of, Sound, SoundKind}, + agent::{Sound, SoundKind}, Alignment, Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, Player, Pos, Scale, Stats, }, @@ -216,18 +216,12 @@ impl<'a> System<'a> for Sys { // PvP check - let owner_if_pet = |entity| { - // Return owner entity if pet, - // or just return entity back otherwise - owner_of( - read_data.alignments.get(entity).copied(), - &read_data.uid_allocator, - ) - .unwrap_or(entity) - }; let may_harm = combat::may_harm( - beam_owner.and_then(|owner| read_data.players.get(owner_if_pet(owner))), - read_data.players.get(owner_if_pet(target)), + &read_data.alignments, + &read_data.players, + &read_data.uid_allocator, + beam_owner, + target, ); let attack_options = AttackOptions { // No luck with dodging beams diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index 005efad509..5067b79b53 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -1,7 +1,7 @@ use common::{ combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ - agent::{owner_of, Sound, SoundKind}, + agent::{Sound, SoundKind}, Alignment, Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Player, Pos, Scale, Stats, }, @@ -165,18 +165,12 @@ impl<'a> System<'a> for Sys { }; // PvP check - let owner_if_pet = |entity| { - // Return owner entity if pet, - // or just return entity back otherwise - owner_of( - read_data.alignments.get(entity).copied(), - &read_data.uid_allocator, - ) - .unwrap_or(entity) - }; let may_harm = combat::may_harm( - read_data.players.get(owner_if_pet(attacker)), - read_data.players.get(owner_if_pet(target)), + &read_data.alignments, + &read_data.players, + &read_data.uid_allocator, + Some(attacker), + target, ); let attack_options = AttackOptions { diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index f27ac95466..83bb98e102 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -1,7 +1,7 @@ use common::{ combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ - agent::{owner_of, Sound, SoundKind}, + agent::{Sound, SoundKind}, projectile, Alignment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, Player, Pos, Projectile, Stats, Vel, }, @@ -301,18 +301,12 @@ fn dispatch_hit( } // PvP check - let owner_if_pet = |entity| { - // Return owner entity if pet, - // or just return entity back otherwise - owner_of( - read_data.alignments.get(entity).copied(), - &read_data.uid_allocator, - ) - .unwrap_or(entity) - }; let may_harm = combat::may_harm( - owner.and_then(|owner| read_data.players.get(owner_if_pet(owner))), - read_data.players.get(owner_if_pet(target)), + &read_data.alignments, + &read_data.players, + &read_data.uid_allocator, + owner, + target, ); let attack_options = AttackOptions { diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index 75fd5db924..f44b965d9d 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -1,7 +1,7 @@ use common::{ combat::{self, AttackOptions, AttackSource, AttackerInfo, TargetInfo}, comp::{ - agent::{owner_of, Sound, SoundKind}, + agent::{Sound, SoundKind}, Alignment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, Player, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats, }, @@ -211,19 +211,12 @@ impl<'a> System<'a> for Sys { }; // PvP check - let owner_if_pet = |entity| { - // Return owner entity if pet, - // or just return entity back otherwise - owner_of( - read_data.alignments.get(entity).copied(), - &read_data.uid_allocator, - ) - .unwrap_or(entity) - }; let may_harm = combat::may_harm( - shockwave_owner - .and_then(|owner| read_data.players.get(owner_if_pet(owner))), - read_data.players.get(owner_if_pet(target)), + &read_data.alignments, + &read_data.players, + &read_data.uid_allocator, + shockwave_owner, + target, ); let attack_options = AttackOptions { // Trying roll during earthquake isn't the best idea diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 6e71021f01..9bdae67c03 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -31,7 +31,7 @@ use common::{ event::{EventBus, ServerEvent}, generation::EntityInfo, npc::{self, get_npc_name}, - resources::{PlayerPhysicsSettings, TimeOfDay, BattleMode}, + resources::{BattleMode, PlayerPhysicsSettings, TimeOfDay}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, uid::Uid, vol::RectVolSize, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 5d81428093..42a69a54fc 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1,7 +1,7 @@ use crate::{ client::Client, comp::{ - agent::{owner_of, Agent, AgentEvent, Sound, SoundKind}, + agent::{Agent, AgentEvent, Sound, SoundKind}, biped_large, bird_large, quadruped_low, quadruped_medium, quadruped_small, skills::SkillGroupKind, theropod, BuffKind, BuffSource, PhysicsState, @@ -945,15 +945,12 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o }; // PvP check - let owner_if_pet = |entity| { - // Return owner entity if pet, - // or just return entity back otherwise - owner_of(alignments.get(entity).copied(), uid_allocator) - .unwrap_or(entity) - }; let may_harm = combat::may_harm( - owner_entity.and_then(|owner| players.get(owner_if_pet(owner))), - players.get(owner_if_pet(entity_b)), + alignments, + players, + uid_allocator, + owner_entity, + entity_b, ); let attack_options = combat::AttackOptions { // cool guyz maybe don't look at explosions @@ -994,27 +991,22 @@ 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. + // Player check only accounts for PvP/PvE flag, but bombs + // are intented to do friendly fire. // - // But bombs are intented to do - // friendly fire. - // - // What exactly friendly fire is subject to discussion. + // What exactly is 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. - // PvP check - let owner_if_pet = |entity| { - // Return owner entity if pet, - // or just return entity back otherwise - owner_of(alignments.get(entity).copied(), uid_allocator).unwrap_or(entity) - }; let may_harm = || { combat::may_harm( - owner_entity.and_then(|owner| players.get(owner_if_pet(owner))), - players.get(owner_if_pet(entity_b)), + alignments, + players, + uid_allocator, + owner_entity, + entity_b, ) || owner_entity.map_or(true, |entity_a| entity_a == entity_b) }; if strength > 0.0 { From d43f34aea87e20e7f79d1b312f82590574870af6 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 28 Aug 2021 00:08:48 +0300 Subject: [PATCH 05/14] Allow changing battle_mode only in towns --- server/src/cmd.rs | 75 ++++++++++++++++++------ server/src/events/entity_manipulation.rs | 9 +-- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 9bdae67c03..db62540b17 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -50,9 +50,10 @@ use specs::{storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt} use std::str::FromStr; use vek::*; use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement}; -use world::util::Sampler; +use world::{site::SiteKind, util::Sampler}; use crate::{client::Client, login_provider::LoginProvider, wiring}; +use std::ops::DerefMut; use tracing::{error, info, warn}; pub trait ChatCommandExt { @@ -3103,9 +3104,37 @@ fn handle_battlemode( args: Vec<String>, _action: &ChatCommand, ) -> CmdResult<()> { - let ecs = &server.state.ecs(); + let ecs = server.state.ecs(); if let Some(mode) = parse_args!(args, String) { - Err("Seting mode isn't implemented".to_owned()) + let world = &server.world; + let index = &server.index; + let sim = world.sim(); + let pos = position(server, target, "target")?; + let chunk_pos = Vec2::from(pos.0).map(|x: f32| x as i32); + let chunk = sim + .get(chunk_pos) + .ok_or_else(|| "Cannot get current chunk for target")?; + let site_ids = &chunk.sites; + let mut in_town = false; + for site_id in site_ids.iter() { + let site = index.sites.get(*site_id); + if matches!(site.kind, SiteKind::Settlement(_)) { + in_town = true; + break; + } + } + + if !in_town { + return Err("You can change battle_mode only in town".to_owned()); + } + + let mut players = ecs.write_storage::<comp::Player>(); + let mut player = players + .get_mut(target) + .ok_or_else(|| "Cannot get player component for target".to_owned())?; + // FIXME: handle cooldown before merge here! + // + set_battlemode(&mode, player.deref_mut(), server, client) } else { let players = ecs.read_storage::<comp::Player>(); let player = players @@ -3129,27 +3158,35 @@ fn handle_battlemode_force( args: Vec<String>, action: &ChatCommand, ) -> CmdResult<()> { - let ecs = &server.state.ecs(); - let mut players = ecs.write_storage::<comp::Player>(); let mode = parse_args!(args, String).ok_or_else(|| action.help_string())?; - let mode = match mode.as_str() { + let ecs = server.state.ecs(); + let mut players = ecs.write_storage::<comp::Player>(); + let mut player = players + .get_mut(target) + .ok_or_else(|| "Cannot get player component for target".to_owned())?; + set_battlemode(&mode, player.deref_mut(), server, client) +} + +fn set_battlemode( + mode: &str, + player_info: &mut comp::Player, + server: &Server, + client: EcsEntity, +) -> CmdResult<()> { + let mode = match mode { "pvp" => BattleMode::PvP, "pve" => BattleMode::PvE, _ => return Err("Available modes: pvp, pve".to_owned()), }; - if let Some(ref mut player) = players.get_mut(target) { - player.battle_mode = mode; - server.notify_client( - client, - ServerGeneral::server_msg( - ChatType::CommandInfo, - format!("Set battle_mode to {:?}", player.battle_mode), - ), - ); - Ok(()) - } else { - Err("Cannot get player component for target".to_owned()) - } + player_info.battle_mode = mode; + server.notify_client( + client, + ServerGeneral::server_msg( + ChatType::CommandInfo, + format!("Set battle_mode to {:?}", player_info.battle_mode), + ), + ); + Ok(()) } fn handle_unban( diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 42a69a54fc..f25657913d 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1001,13 +1001,8 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o // // This can be changed later. let may_harm = || { - combat::may_harm( - alignments, - players, - uid_allocator, - owner_entity, - entity_b, - ) || owner_entity.map_or(true, |entity_a| entity_a == entity_b) + combat::may_harm(alignments, players, uid_allocator, owner_entity, entity_b) + || owner_entity.map_or(true, |entity_a| entity_a == entity_b) }; if strength > 0.0 { let is_alive = ecs From deacec94d7c6e6c04df05be6c60306c03a6c7f8a Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 28 Aug 2021 01:42:42 +0300 Subject: [PATCH 06/14] Allow changing mode only with enabled settings - send warning in force mode --- server/src/cmd.rs | 28 ++++++++++++++++++++++------ server/src/settings.rs | 9 +++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index db62540b17..832289ec9f 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -3,12 +3,15 @@ //! in [do_command]. use crate::{ + client::Client, + login_provider::LoginProvider, settings::{ Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord, }, sys::terrain::NpcData, + wiring, wiring::{Logic, OutputFormula}, - Server, SpawnPoint, StateExt, + Server, Settings, SpawnPoint, StateExt, }; use assets::AssetExt; use authc::Uuid; @@ -52,7 +55,6 @@ use vek::*; use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement}; use world::{site::SiteKind, util::Sampler}; -use crate::{client::Client, login_provider::LoginProvider, wiring}; use std::ops::DerefMut; use tracing::{error, info, warn}; @@ -3105,6 +3107,10 @@ fn handle_battlemode( _action: &ChatCommand, ) -> CmdResult<()> { let ecs = server.state.ecs(); + let settings = ecs.read_resource::<Settings>(); + if !settings.battle_mode.allow_choosing() { + return Err("Toggling battlemode is disabled.".to_owned()); + } if let Some(mode) = parse_args!(args, String) { let world = &server.world; let index = &server.index; @@ -3113,7 +3119,7 @@ fn handle_battlemode( let chunk_pos = Vec2::from(pos.0).map(|x: f32| x as i32); let chunk = sim .get(chunk_pos) - .ok_or_else(|| "Cannot get current chunk for target")?; + .ok_or("Cannot get current chunk for target")?; let site_ids = &chunk.sites; let mut in_town = false; for site_id in site_ids.iter() { @@ -3131,7 +3137,7 @@ fn handle_battlemode( let mut players = ecs.write_storage::<comp::Player>(); let mut player = players .get_mut(target) - .ok_or_else(|| "Cannot get player component for target".to_owned())?; + .ok_or("Cannot get player component for target")?; // FIXME: handle cooldown before merge here! // set_battlemode(&mode, player.deref_mut(), server, client) @@ -3139,7 +3145,7 @@ fn handle_battlemode( let players = ecs.read_storage::<comp::Player>(); let player = players .get(target) - .ok_or_else(|| "Cannot get player component for target".to_string())?; + .ok_or("Cannot get player component for target")?; server.notify_client( client, ServerGeneral::server_msg( @@ -3160,10 +3166,20 @@ fn handle_battlemode_force( ) -> CmdResult<()> { let mode = parse_args!(args, String).ok_or_else(|| action.help_string())?; let ecs = server.state.ecs(); + let settings = ecs.read_resource::<Settings>(); + if !settings.battle_mode.allow_choosing() { + server.notify_client( + client, + ServerGeneral::server_msg( + ChatType::CommandInfo, + "Warning! Forcing battle_mode while not enabled in settings!".to_owned(), + ), + ); + } let mut players = ecs.write_storage::<comp::Player>(); let mut player = players .get_mut(target) - .ok_or_else(|| "Cannot get player component for target".to_owned())?; + .ok_or("Cannot get player component for target")?; set_battlemode(&mode, player.deref_mut(), server, client) } diff --git a/server/src/settings.rs b/server/src/settings.rs index 7e45e3d668..f2c7004d29 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -46,6 +46,15 @@ pub enum ServerBattleMode { PerPlayer { default: BattleMode }, } +impl ServerBattleMode { + pub fn allow_choosing(&self) -> bool { + match self { + ServerBattleMode::Global { .. } => false, + ServerBattleMode::PerPlayer { .. } => true, + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct Settings { From 6c1095884aa9d2846c2d2f7901cdde0277d5c361 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 28 Aug 2021 13:36:33 +0300 Subject: [PATCH 07/14] Make it compile without feature(worldgen) + move settings check to toggling mode segment --- server/src/cmd.rs | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 832289ec9f..5d95344c40 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -3108,28 +3108,36 @@ fn handle_battlemode( ) -> CmdResult<()> { let ecs = server.state.ecs(); let settings = ecs.read_resource::<Settings>(); - if !settings.battle_mode.allow_choosing() { - return Err("Toggling battlemode is disabled.".to_owned()); - } if let Some(mode) = parse_args!(args, String) { - let world = &server.world; - let index = &server.index; - let sim = world.sim(); - let pos = position(server, target, "target")?; - let chunk_pos = Vec2::from(pos.0).map(|x: f32| x as i32); - let chunk = sim - .get(chunk_pos) - .ok_or("Cannot get current chunk for target")?; - let site_ids = &chunk.sites; - let mut in_town = false; - for site_id in site_ids.iter() { - let site = index.sites.get(*site_id); - if matches!(site.kind, SiteKind::Settlement(_)) { - in_town = true; - break; - } + if !settings.battle_mode.allow_choosing() { + return Err("Toggling battlemode is disabled.".to_owned()); } + #[cfg(feature = "worldgen")] + let in_town = { + let world = &server.world; + let index = &server.index; + let sim = world.sim(); + let pos = position(server, target, "target")?; + let chunk_pos = Vec2::from(pos.0).map(|x: f32| x as i32); + let chunk = sim + .get(chunk_pos) + .ok_or("Cannot get current chunk for target")?; + let site_ids = &chunk.sites; + let mut in_town = false; + for site_id in site_ids.iter() { + let site = index.sites.get(*site_id); + if matches!(site.kind, SiteKind::Settlement(_)) { + in_town = true; + break; + } + } + in_town + }; + // just skip this check, if worldgen is disabled + #[cfg(not(feature = "worldgen"))] + let in_town = true; + if !in_town { return Err("You can change battle_mode only in town".to_owned()); } From b96de1adf7f15c78c916f9a3625268ce141f1f80 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 28 Aug 2021 16:32:18 +0300 Subject: [PATCH 08/14] Fix town detection --- server/src/cmd.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 5d95344c40..6b3a821136 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -3110,7 +3110,7 @@ fn handle_battlemode( let settings = ecs.read_resource::<Settings>(); if let Some(mode) = parse_args!(args, String) { if !settings.battle_mode.allow_choosing() { - return Err("Toggling battlemode is disabled.".to_owned()); + return Err("Command disabled in server settings".to_owned()); } #[cfg(feature = "worldgen")] @@ -3118,11 +3118,19 @@ fn handle_battlemode( let world = &server.world; let index = &server.index; let sim = world.sim(); + // get chunk position let pos = position(server, target, "target")?; - let chunk_pos = Vec2::from(pos.0).map(|x: f32| x as i32); + let wpos = pos.0.xy().map(|x| x as i32); + let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |pos, size: u32| { + pos / size as i32 + }); let chunk = sim .get(chunk_pos) .ok_or("Cannot get current chunk for target")?; + // search for towns in chunk + // + // NOTE: this code finds town even if it is far from it. + // Does it count plant fields? let site_ids = &chunk.sites; let mut in_town = false; for site_id in site_ids.iter() { @@ -3139,7 +3147,7 @@ fn handle_battlemode( let in_town = true; if !in_town { - return Err("You can change battle_mode only in town".to_owned()); + return Err("Too far from town".to_owned()); } let mut players = ecs.write_storage::<comp::Player>(); @@ -3158,7 +3166,7 @@ fn handle_battlemode( client, ServerGeneral::server_msg( ChatType::CommandInfo, - format!("Battle mode is {:?}", player.battle_mode), + format!("Current battle mode: {:?}", player.battle_mode), ), ); Ok(()) @@ -3180,7 +3188,7 @@ fn handle_battlemode_force( client, ServerGeneral::server_msg( ChatType::CommandInfo, - "Warning! Forcing battle_mode while not enabled in settings!".to_owned(), + "Warning! Forcing battle mode while not enabled in settings!".to_owned(), ), ); } @@ -3207,7 +3215,7 @@ fn set_battlemode( client, ServerGeneral::server_msg( ChatType::CommandInfo, - format!("Set battle_mode to {:?}", player_info.battle_mode), + format!("New battle mode: {:?}", player_info.battle_mode), ), ); Ok(()) From df066f364acc88b73b080f547b2868412c6fff27 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Tue, 31 Aug 2021 18:55:28 +0300 Subject: [PATCH 09/14] Cooldowns - Add last_battlemode_change to Player component - check on last_battlemode_change in handle_battlemode - set last_battlemode_change after setting battlemode - still are not persisted in any way --- common/src/comp/player.rs | 11 ++++- server/src/cmd.rs | 75 +++++++++++++++++++--------------- server/src/sys/msg/register.rs | 9 ++-- 3 files changed, 57 insertions(+), 38 deletions(-) diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index 76db8ddc55..8eecf6b90d 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -3,7 +3,7 @@ use specs::{Component, DerefFlaggedStorage, NullStorage}; use specs_idvs::IdvStorage; use uuid::Uuid; -use crate::resources::BattleMode; +use crate::resources::{BattleMode, Time}; const MAX_ALIAS_LEN: usize = 32; @@ -20,6 +20,7 @@ pub enum DisconnectReason { pub struct Player { pub alias: String, pub battle_mode: BattleMode, + pub last_battlemode_change: Option<Time>, uuid: Uuid, } @@ -30,10 +31,16 @@ impl BattleMode { } impl Player { - pub fn new(alias: String, battle_mode: BattleMode, uuid: Uuid) -> Self { + pub fn new( + alias: String, + battle_mode: BattleMode, + uuid: Uuid, + last_battlemode_change: Option<Time>, + ) -> Self { Self { alias, battle_mode, + last_battlemode_change, uuid, } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 6b3a821136..c51cfcc9f8 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -34,7 +34,7 @@ use common::{ event::{EventBus, ServerEvent}, generation::EntityInfo, npc::{self, get_npc_name}, - resources::{BattleMode, PlayerPhysicsSettings, TimeOfDay}, + resources::{BattleMode, PlayerPhysicsSettings, Time, TimeOfDay}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, uid::Uid, vol::RectVolSize, @@ -55,7 +55,6 @@ use vek::*; use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement}; use world::{site::SiteKind, util::Sampler}; -use std::ops::DerefMut; use tracing::{error, info, warn}; pub trait ChatCommandExt { @@ -3128,12 +3127,11 @@ fn handle_battlemode( .get(chunk_pos) .ok_or("Cannot get current chunk for target")?; // search for towns in chunk - // - // NOTE: this code finds town even if it is far from it. - // Does it count plant fields? let site_ids = &chunk.sites; let mut in_town = false; for site_id in site_ids.iter() { + // NOTE: this code finds town even if it is far from actual + // houses in settlement. Is it because of plant fields? let site = index.sites.get(*site_id); if matches!(site.kind, SiteKind::Settlement(_)) { in_town = true; @@ -3151,12 +3149,40 @@ fn handle_battlemode( } let mut players = ecs.write_storage::<comp::Player>(); - let mut player = players + let mut player_info = players .get_mut(target) .ok_or("Cannot get player component for target")?; - // FIXME: handle cooldown before merge here! - // - set_battlemode(&mode, player.deref_mut(), server, client) + let time = ecs.read_resource::<Time>(); + if let Some(Time(last_change)) = player_info.last_battlemode_change { + const COOLDOWN: f64 = 60.0 * 5.0; + + let Time(time) = *time; + let elapsed = time - last_change; + if elapsed < COOLDOWN { + #[rustfmt::skip] + let msg = format!( + "You can switch battlemode only once in {:.0} seconds. \ + Last change was {:.0} seconds before", + COOLDOWN, elapsed, + ); + return Err(msg); + } + } + let mode = match mode.as_str() { + "pvp" => BattleMode::PvP, + "pve" => BattleMode::PvE, + _ => return Err("Available modes: pvp, pve".to_owned()), + }; + player_info.battle_mode = mode; + player_info.last_battlemode_change = Some(*time); + server.notify_client( + client, + ServerGeneral::server_msg( + ChatType::CommandInfo, + format!("New battle mode: {:?}", mode), + ), + ); + Ok(()) } else { let players = ecs.read_storage::<comp::Player>(); let player = players @@ -3180,42 +3206,27 @@ fn handle_battlemode_force( args: Vec<String>, action: &ChatCommand, ) -> CmdResult<()> { - let mode = parse_args!(args, String).ok_or_else(|| action.help_string())?; let ecs = server.state.ecs(); let settings = ecs.read_resource::<Settings>(); if !settings.battle_mode.allow_choosing() { - server.notify_client( - client, - ServerGeneral::server_msg( - ChatType::CommandInfo, - "Warning! Forcing battle mode while not enabled in settings!".to_owned(), - ), - ); + return Err("Command disabled in server settings".to_owned()); } - let mut players = ecs.write_storage::<comp::Player>(); - let mut player = players - .get_mut(target) - .ok_or("Cannot get player component for target")?; - set_battlemode(&mode, player.deref_mut(), server, client) -} - -fn set_battlemode( - mode: &str, - player_info: &mut comp::Player, - server: &Server, - client: EcsEntity, -) -> CmdResult<()> { - let mode = match mode { + let mode = parse_args!(args, String).ok_or_else(|| action.help_string())?; + let mode = match mode.as_str() { "pvp" => BattleMode::PvP, "pve" => BattleMode::PvE, _ => return Err("Available modes: pvp, pve".to_owned()), }; + let mut players = ecs.write_storage::<comp::Player>(); + let mut player_info = players + .get_mut(target) + .ok_or("Cannot get player component for target")?; player_info.battle_mode = mode; server.notify_client( client, ServerGeneral::server_msg( ChatType::CommandInfo, - format!("New battle mode: {:?}", player_info.battle_mode), + format!("Set battle mode to: {:?}", mode), ), ); Ok(()) diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index a2a5b3cbf9..a32100a00e 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -174,14 +174,15 @@ impl<'a> System<'a> for Sys { return Ok(()); } + // FIXME: + // Take last battle_mode and last battle_mode change + // from in-memory persistence let battle_mode = match read_data.settings.battle_mode { ServerBattleMode::Global(mode) => mode, - // FIXME: - // Should this use just default battle_mode - // or should we take it from last change? ServerBattleMode::PerPlayer { default: mode } => mode, }; - let player = Player::new(username, battle_mode, uuid); + let player = Player::new(username, battle_mode, uuid, None); + let admin = read_data.editable_settings.admins.get(&uuid); if !player.is_valid() { From 91539a3b67403a31c1817dc9d71c63ca80ea66f9 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Fri, 3 Sep 2021 23:49:13 +0300 Subject: [PATCH 10/14] Add in-memory persistence for battlemode changes --- common/src/resources.rs | 18 ++++++++++++++++++ server/src/events/player.rs | 10 ++++++++++ server/src/lib.rs | 4 +++- server/src/state_ext.rs | 23 ++++++++++++++++++++--- server/src/sys/msg/register.rs | 7 ++++--- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/common/src/resources.rs b/common/src/resources.rs index 2b08b6407b..688562c081 100644 --- a/common/src/resources.rs +++ b/common/src/resources.rs @@ -1,5 +1,7 @@ +use crate::character::CharacterId; #[cfg(not(target_arch = "wasm32"))] use crate::comp::Pos; +use hashbrown::HashMap; use serde::{Deserialize, Serialize}; #[cfg(not(target_arch = "wasm32"))] use specs::Entity; @@ -75,6 +77,22 @@ pub struct PlayerPhysicsSettings { pub settings: hashbrown::HashMap<uuid::Uuid, PlayerPhysicsSetting>, } +/// Store of BattleMode cooldowns for players while they go offline +#[derive(Clone, Default, Debug)] +pub struct BattleModeBuffer { + map: HashMap<CharacterId, (BattleMode, Time)>, +} + +impl BattleModeBuffer { + pub fn push(&mut self, char_id: CharacterId, save: (BattleMode, Time)) { + self.map.insert(char_id, save); + } + + pub fn pop(&mut self, char_id: &CharacterId) -> Option<(BattleMode, Time)> { + self.map.remove(char_id) + } +} + /// Describe how players interact with other players. /// /// May be removed when we will discover better way diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 667dd5fe39..517ec11819 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -6,6 +6,7 @@ use crate::{ use common::{ comp, comp::{group, pet::is_tameable}, + resources::BattleModeBuffer, uid::{Uid, UidAllocator}, }; use common_base::span; @@ -201,12 +202,14 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { Some(skill_set), Some(inventory), Some(player_uid), + Some(player_info), mut character_updater, ) = ( state.read_storage::<Presence>().get(entity), state.read_storage::<comp::SkillSet>().get(entity), state.read_storage::<comp::Inventory>().get(entity), state.read_storage::<Uid>().get(entity), + state.read_storage::<comp::Player>().get(entity), state.ecs().fetch_mut::<CharacterUpdater>(), ) { match presence.kind { @@ -216,6 +219,13 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { .read_storage::<common::comp::Waypoint>() .get(entity) .cloned(); + // Store last battle mode change + let mut battlemode_buffer = state.ecs().fetch_mut::<BattleModeBuffer>(); + if let Some(change) = player_info.last_battlemode_change { + let mode = player_info.battle_mode; + let save = (mode, change); + battlemode_buffer.push(char_id, save); + } // Get player's pets let alignments = state.ecs().read_storage::<comp::Alignment>(); diff --git a/server/src/lib.rs b/server/src/lib.rs index 97ecf22a93..227a9b4379 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -69,7 +69,7 @@ use common::{ comp::{item::MaterialStatManifest, CharacterAbility}, event::{EventBus, ServerEvent}, recipe::default_recipe_book, - resources::TimeOfDay, + resources::{BattleModeBuffer, TimeOfDay}, rtsim::RtSimEntity, slowjob::SlowJobPool, terrain::{TerrainChunk, TerrainChunkSize}, @@ -196,8 +196,10 @@ impl Server { let ecs_system_metrics = EcsSystemMetrics::new(®istry).unwrap(); let tick_metrics = TickMetrics::new(®istry).unwrap(); let physics_metrics = PhysicsMetrics::new(®istry).unwrap(); + let battlemode_buffer = BattleModeBuffer::default(); let mut state = State::server(); + state.ecs_mut().insert(battlemode_buffer); state.ecs_mut().insert(settings.clone()); state.ecs_mut().insert(editable_settings); state.ecs_mut().insert(DataDir { diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 9d91e90357..08ff295c36 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -16,7 +16,7 @@ use common::{ Group, Inventory, Poise, }, effect::Effect, - resources::TimeOfDay, + resources::{BattleModeBuffer, TimeOfDay}, slowjob::SlowJobPool, uid::{Uid, UidAllocator}, }; @@ -496,8 +496,8 @@ impl StateExt for State { }), )); - // NOTE: By fetching the player_uid, we validated that the entity exists, and we - // call nothing that can delete it in any of the subsequent + // NOTE: By fetching the player_uid, we validated that the entity exists, + // and we call nothing that can delete it in any of the subsequent // commands, so we can assume that all of these calls succeed, // justifying ignoring the result of insertion. self.write_component_ignore_entity_dead(entity, comp::Collider::Box { @@ -567,6 +567,23 @@ impl StateExt for State { } else { warn!("Player has no pos, cannot load {} pets", pets.len()); } + + let presences = self.ecs().read_storage::<Presence>(); + let presence = presences.get(entity); + if let Some(Presence { + kind: PresenceKind::Character(char_id), + .. + }) = presence + { + let mut battlemode_buffer = self.ecs().fetch_mut::<BattleModeBuffer>(); + if let Some((mode, change)) = battlemode_buffer.pop(char_id) { + let mut players = self.ecs().write_storage::<comp::Player>(); + if let Some(mut player_info) = players.get_mut(entity) { + player_info.battle_mode = mode; + player_info.last_battlemode_change = Some(change); + } + } + } } } diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index a32100a00e..682b8405a0 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -174,9 +174,10 @@ impl<'a> System<'a> for Sys { return Ok(()); } - // FIXME: - // Take last battle_mode and last battle_mode change - // from in-memory persistence + // NOTE: this is just default value. + // + // It will be overwritten in ServerExt::update_character_data + // from stored last battlemode change if such exists. let battle_mode = match read_data.settings.battle_mode { ServerBattleMode::Global(mode) => mode, ServerBattleMode::PerPlayer { default: mode } => mode, From c87905305d0a836d5928041fe1aa73d4e3038ba6 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 4 Sep 2021 00:30:31 +0300 Subject: [PATCH 11/14] Improve UX of /battlemode --- server/src/cmd.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index c51cfcc9f8..66a0586a86 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -3105,7 +3105,11 @@ fn handle_battlemode( args: Vec<String>, _action: &ChatCommand, ) -> CmdResult<()> { + // TODO: discuss time + const COOLDOWN: f64 = 60.0 * 5.0; + let ecs = server.state.ecs(); + let time = ecs.read_resource::<Time>(); let settings = ecs.read_resource::<Settings>(); if let Some(mode) = parse_args!(args, String) { if !settings.battle_mode.allow_choosing() { @@ -3152,10 +3156,7 @@ fn handle_battlemode( let mut player_info = players .get_mut(target) .ok_or("Cannot get player component for target")?; - let time = ecs.read_resource::<Time>(); if let Some(Time(last_change)) = player_info.last_battlemode_change { - const COOLDOWN: f64 = 60.0 * 5.0; - let Time(time) = *time; let elapsed = time - last_change; if elapsed < COOLDOWN { @@ -3173,6 +3174,9 @@ fn handle_battlemode( "pve" => BattleMode::PvE, _ => return Err("Available modes: pvp, pve".to_owned()), }; + if player_info.battle_mode == mode { + return Err("Attempted to set the same battlemode".to_owned()); + } player_info.battle_mode = mode; player_info.last_battlemode_change = Some(*time); server.notify_client( @@ -3188,12 +3192,23 @@ fn handle_battlemode( let player = players .get(target) .ok_or("Cannot get player component for target")?; + let mut msg = format!("Current battle mode: {:?}.", player.battle_mode); + if settings.battle_mode.allow_choosing() { + msg.push_str(" Possible to change."); + } else { + msg.push_str(" Global."); + } + if let Some(change) = player.last_battlemode_change { + let Time(time) = *time; + let Time(change) = change; + let elapsed = time - change; + let next = COOLDOWN - elapsed; + let notice = format!(" Next change will be available in: {:.0} seconds", next); + msg.push_str(¬ice); + } server.notify_client( client, - ServerGeneral::server_msg( - ChatType::CommandInfo, - format!("Current battle mode: {:?}", player.battle_mode), - ), + ServerGeneral::server_msg(ChatType::CommandInfo, msg), ); Ok(()) } From 182d3c4815f9b69a5d56cdc09b82603834c01919 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 4 Sep 2021 01:40:02 +0300 Subject: [PATCH 12/14] Workaround of bug with global Player component * Set default value of battle_mode and last_battlemode_change explicitly if isn't found in battlemode_buffer --- common/src/resources.rs | 20 +------------------- server/src/events/player.rs | 6 +++--- server/src/lib.rs | 24 +++++++++++++++++++++++- server/src/settings.rs | 7 +++++++ server/src/state_ext.rs | 25 +++++++++++++++++++------ server/src/sys/msg/register.rs | 9 ++------- 6 files changed, 55 insertions(+), 36 deletions(-) diff --git a/common/src/resources.rs b/common/src/resources.rs index 688562c081..fee169e388 100644 --- a/common/src/resources.rs +++ b/common/src/resources.rs @@ -1,7 +1,5 @@ -use crate::character::CharacterId; #[cfg(not(target_arch = "wasm32"))] use crate::comp::Pos; -use hashbrown::HashMap; use serde::{Deserialize, Serialize}; #[cfg(not(target_arch = "wasm32"))] use specs::Entity; @@ -77,27 +75,11 @@ pub struct PlayerPhysicsSettings { pub settings: hashbrown::HashMap<uuid::Uuid, PlayerPhysicsSetting>, } -/// Store of BattleMode cooldowns for players while they go offline -#[derive(Clone, Default, Debug)] -pub struct BattleModeBuffer { - map: HashMap<CharacterId, (BattleMode, Time)>, -} - -impl BattleModeBuffer { - pub fn push(&mut self, char_id: CharacterId, save: (BattleMode, Time)) { - self.map.insert(char_id, save); - } - - pub fn pop(&mut self, char_id: &CharacterId) -> Option<(BattleMode, Time)> { - self.map.remove(char_id) - } -} - /// 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)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Deserialize, Serialize)] pub enum BattleMode { PvP, PvE, diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 517ec11819..0bd57edab1 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -1,12 +1,11 @@ use super::Event; use crate::{ client::Client, metrics::PlayerMetrics, persistence::character_updater::CharacterUpdater, - presence::Presence, state_ext::StateExt, Server, + presence::Presence, state_ext::StateExt, BattleModeBuffer, Server, }; use common::{ comp, comp::{group, pet::is_tameable}, - resources::BattleModeBuffer, uid::{Uid, UidAllocator}, }; use common_base::span; @@ -204,6 +203,7 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { Some(player_uid), Some(player_info), mut character_updater, + mut battlemode_buffer, ) = ( state.read_storage::<Presence>().get(entity), state.read_storage::<comp::SkillSet>().get(entity), @@ -211,6 +211,7 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { state.read_storage::<Uid>().get(entity), state.read_storage::<comp::Player>().get(entity), state.ecs().fetch_mut::<CharacterUpdater>(), + state.ecs().fetch_mut::<BattleModeBuffer>(), ) { match presence.kind { PresenceKind::Character(char_id) => { @@ -220,7 +221,6 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { .get(entity) .cloned(); // Store last battle mode change - let mut battlemode_buffer = state.ecs().fetch_mut::<BattleModeBuffer>(); if let Some(change) = player_info.last_battlemode_change { let mode = player_info.battle_mode; let save = (mode, change); diff --git a/server/src/lib.rs b/server/src/lib.rs index 227a9b4379..736f2e7392 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -64,12 +64,13 @@ use crate::{ use common::grid::Grid; use common::{ assets::AssetExt, + character::CharacterId, cmd::ChatCommand, comp, comp::{item::MaterialStatManifest, CharacterAbility}, event::{EventBus, ServerEvent}, recipe::default_recipe_book, - resources::{BattleModeBuffer, TimeOfDay}, + resources::{BattleMode, Time, TimeOfDay}, rtsim::RtSimEntity, slowjob::SlowJobPool, terrain::{TerrainChunk, TerrainChunkSize}, @@ -109,6 +110,7 @@ use crate::{ persistence::{DatabaseSettings, SqlLogMode}, sys::terrain, }; +use hashbrown::HashMap; use std::sync::RwLock; #[cfg(feature = "plugins")] @@ -152,6 +154,26 @@ enum DisconnectType { #[derive(Copy, Clone)] pub struct TickStart(Instant); +/// Store of BattleMode cooldowns for players while they go offline +#[derive(Clone, Default, Debug)] +pub struct BattleModeBuffer { + map: HashMap<CharacterId, (BattleMode, Time)>, +} + +impl BattleModeBuffer { + pub fn push(&mut self, char_id: CharacterId, save: (BattleMode, Time)) { + self.map.insert(char_id, save); + } + + pub fn get(&self, char_id: &CharacterId) -> Option<&(BattleMode, Time)> { + self.map.get(char_id) + } + + pub fn pop(&mut self, char_id: &CharacterId) -> Option<(BattleMode, Time)> { + self.map.remove(char_id) + } +} + pub struct Server { state: State, world: Arc<World>, diff --git a/server/src/settings.rs b/server/src/settings.rs index f2c7004d29..d3cd0ace6c 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -53,6 +53,13 @@ impl ServerBattleMode { ServerBattleMode::PerPlayer { .. } => true, } } + + pub fn default_mode(&self) -> BattleMode { + match self { + ServerBattleMode::Global(mode) => *mode, + ServerBattleMode::PerPlayer { default: mode } => *mode, + } + } } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 08ff295c36..d51463ae7e 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -5,7 +5,7 @@ use crate::{ presence::{Presence, RepositionOnChunkLoad}, settings::Settings, sys::sentinel::DeletedEntities, - wiring, SpawnPoint, + wiring, BattleModeBuffer, SpawnPoint, }; use common::{ character::CharacterId, @@ -16,7 +16,7 @@ use common::{ Group, Inventory, Poise, }, effect::Effect, - resources::{BattleModeBuffer, TimeOfDay}, + resources::TimeOfDay, slowjob::SlowJobPool, uid::{Uid, UidAllocator}, }; @@ -575,12 +575,25 @@ impl StateExt for State { .. }) = presence { - let mut battlemode_buffer = self.ecs().fetch_mut::<BattleModeBuffer>(); - if let Some((mode, change)) = battlemode_buffer.pop(char_id) { - let mut players = self.ecs().write_storage::<comp::Player>(); + let battlemode_buffer = self.ecs().fetch::<BattleModeBuffer>(); + let mut players = self.ecs().write_storage::<comp::Player>(); + if let Some((mode, change)) = battlemode_buffer.get(char_id) { + if let Some(mut player_info) = players.get_mut(entity) { + player_info.battle_mode = *mode; + player_info.last_battlemode_change = Some(*change); + } + } else { + // FIXME: + // ??? + // + // This probably shouldn't exist, + // but without this code, character gets battle_mode from + // another character on this account. + let settings = self.ecs().read_resource::<Settings>(); + let mode = settings.battle_mode.default_mode(); if let Some(mut player_info) = players.get_mut(entity) { player_info.battle_mode = mode; - player_info.last_battlemode_change = Some(change); + player_info.last_battlemode_change = None; } } } diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 682b8405a0..e5e0f5ea57 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -2,7 +2,6 @@ use crate::{ client::Client, login_provider::{LoginProvider, PendingLogin}, metrics::PlayerMetrics, - settings::ServerBattleMode, EditableSettings, Settings, }; use common::{ @@ -176,12 +175,8 @@ impl<'a> System<'a> for Sys { // NOTE: this is just default value. // - // It will be overwritten in ServerExt::update_character_data - // from stored last battlemode change if such exists. - let battle_mode = match read_data.settings.battle_mode { - ServerBattleMode::Global(mode) => mode, - ServerBattleMode::PerPlayer { default: mode } => mode, - }; + // It will be overwritten in ServerExt::update_character_data. + let battle_mode = read_data.settings.battle_mode.default_mode(); let player = Player::new(username, battle_mode, uuid, None); let admin = read_data.editable_settings.admins.get(&uuid); From 134699e1db568cadc5dd79725f4128e183fcb70d Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 4 Sep 2021 20:38:53 +0300 Subject: [PATCH 13/14] Adress review: - explanation of what pvp/pve means in /battlemode help - check for radius from town instead of town in chunks (because it count plant fields, which is kinda meh) - better error displaying --- common/src/cmd.rs | 2 +- server/src/cmd.rs | 56 +++++++++++++++++++---------------------------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index b1a6890b74..caab209624 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -330,7 +330,7 @@ impl ChatCommand { vec!["pvp".to_owned(), "pve".to_owned()], Optional, )], - "Set your battle mode to pvp/pve.\n\ + "Set your battle mode to pvp (player vs player) or pve (player vs environment).\n\ If called without arguments will show current battle mode.", None, ), diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 66a0586a86..160b06dfc0 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -53,7 +53,7 @@ use specs::{storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt} use std::str::FromStr; use vek::*; use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement}; -use world::{site::SiteKind, util::Sampler}; +use world::util::Sampler; use tracing::{error, info, warn}; @@ -3118,53 +3118,42 @@ fn handle_battlemode( #[cfg(feature = "worldgen")] let in_town = { - let world = &server.world; - let index = &server.index; - let sim = world.sim(); // get chunk position let pos = position(server, target, "target")?; let wpos = pos.0.xy().map(|x| x as i32); - let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |pos, size: u32| { - pos / size as i32 + let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |wpos, size: u32| { + wpos / size as i32 }); - let chunk = sim - .get(chunk_pos) - .ok_or("Cannot get current chunk for target")?; - // search for towns in chunk - let site_ids = &chunk.sites; - let mut in_town = false; - for site_id in site_ids.iter() { - // NOTE: this code finds town even if it is far from actual - // houses in settlement. Is it because of plant fields? - let site = index.sites.get(*site_id); - if matches!(site.kind, SiteKind::Settlement(_)) { - in_town = true; - break; - } - } - in_town + server.world.civs().sites().any(|site| { + // empirical + const RADIUS: f32 = 9.0; + let delta = site + .center + .map(|x| x as f32) + .distance(chunk_pos.map(|x| x as f32)); + delta < RADIUS + }) }; // just skip this check, if worldgen is disabled #[cfg(not(feature = "worldgen"))] let in_town = true; if !in_town { - return Err("Too far from town".to_owned()); + return Err("You need to be in town to change battle mode!".to_owned()); } let mut players = ecs.write_storage::<comp::Player>(); - let mut player_info = players - .get_mut(target) - .ok_or("Cannot get player component for target")?; + let mut player_info = players.get_mut(target).ok_or_else(|| { + error!("Can't get player component for player"); + "Error!" + })?; if let Some(Time(last_change)) = player_info.last_battlemode_change { let Time(time) = *time; let elapsed = time - last_change; if elapsed < COOLDOWN { - #[rustfmt::skip] let msg = format!( - "You can switch battlemode only once in {:.0} seconds. \ - Last change was {:.0} seconds before", - COOLDOWN, elapsed, + "Cooldown period active. Try again in {} second", + COOLDOWN - elapsed, ); return Err(msg); } @@ -3189,9 +3178,10 @@ fn handle_battlemode( Ok(()) } else { let players = ecs.read_storage::<comp::Player>(); - let player = players - .get(target) - .ok_or("Cannot get player component for target")?; + let player = players.get(target).ok_or_else(|| { + error!("Can't get player component for player"); + "Error!" + })?; let mut msg = format!("Current battle mode: {:?}.", player.battle_mode); if settings.battle_mode.allow_choosing() { msg.push_str(" Possible to change."); From 69f18c8fe660d02af4463df9bc4a9ac13c77a4b0 Mon Sep 17 00:00:00 2001 From: juliancoffee <lightdarkdaughter@gmail.com> Date: Sat, 4 Sep 2021 20:56:55 +0300 Subject: [PATCH 14/14] Better formatting --- common/src/cmd.rs | 4 +++- server/src/cmd.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index caab209624..614d86bb72 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -330,7 +330,9 @@ impl ChatCommand { vec!["pvp".to_owned(), "pve".to_owned()], Optional, )], - "Set your battle mode to pvp (player vs player) or pve (player vs environment).\n\ + "Set your battle mode to:\n\ + * pvp (player vs player)\n\ + * pve (player vs environment).\n\ If called without arguments will show current battle mode.", None, ), diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 160b06dfc0..e17ef01967 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -3152,7 +3152,7 @@ fn handle_battlemode( let elapsed = time - last_change; if elapsed < COOLDOWN { let msg = format!( - "Cooldown period active. Try again in {} second", + "Cooldown period active. Try again in {:.0} seconds", COOLDOWN - elapsed, ); return Err(msg);