From 6b0d016418274ccc8e4068c43946e3074583bb6d Mon Sep 17 00:00:00 2001 From: juliancoffee 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, uid_allocator: &UidAllocator) -> Option { + 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>, 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, explosion: Explosion, o let energies = &ecs.read_storage::(); let combos = &ecs.read_storage::(); let inventories = &ecs.read_storage::(); + let alignments = &ecs.read_storage::(); + let uid_allocator = &ecs.read_resource::(); let players = &ecs.read_storage::(); for ( entity_b, @@ -942,9 +944,16 @@ pub fn handle_explosion(server: &Server, pos: Vec3, 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, explosion: Explosion, o } }, RadiusEffect::Entity(mut effect) => { + let alignments = &ecs.read_storage::(); + let uid_allocator = &ecs.read_resource::(); let players = &ecs.read_storage::(); for (entity_b, pos_b, body_b_maybe) in ( &ecs.entities(), @@ -994,12 +1005,17 @@ pub fn handle_explosion(server: &Server, pos: Vec3, 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 f01309dfc216fcae7b90ddf7f934a8df24c5299e Mon Sep 17 00:00:00 2001 From: juliancoffee 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, 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 2e79c61123e0f7e16b896216c413b75566516329 Mon Sep 17 00:00:00 2001 From: juliancoffee 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, + _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::(); + 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, + action: &ChatCommand, +) -> CmdResult<()> { + let ecs = &server.state.ecs(); + let mut players = ecs.write_storage::(); + 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 338e81de102678333c989d1ac433a7ab2d38f058 Mon Sep 17 00:00:00 2001 From: juliancoffee 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, + players: &ReadStorage, + uid_allocator: &UidAllocator, + attacker: Option, + 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, uid_allocator: &UidAllocator) -> Option { - 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, 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, 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 3fd573f1ec2fb2af46997023acbc1b7564c6645e Mon Sep 17 00:00:00 2001 From: juliancoffee 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, _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::(); + 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::(); let player = players @@ -3129,27 +3158,35 @@ fn handle_battlemode_force( args: Vec, action: &ChatCommand, ) -> CmdResult<()> { - let ecs = &server.state.ecs(); - let mut players = ecs.write_storage::(); 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::(); + 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, 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 68a4b269d2eaebf10409509cce0c8a205a28fca4 Mon Sep 17 00:00:00 2001 From: juliancoffee 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::(); + 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::(); 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::(); 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::(); + 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::(); 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 1838d151f515efa244cf0cdb206405ecdc5d345a Mon Sep 17 00:00:00 2001 From: juliancoffee 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::(); - 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 85e8c50d358b8030b4a5697c3278a555303eafa4 Mon Sep 17 00:00:00 2001 From: juliancoffee 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::(); 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::(); @@ -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 fbe745fe1e00acac086c4e26c3d572c77ad98e19 Mon Sep 17 00:00:00 2001 From: juliancoffee 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