mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'juliancoffee/pvp_command' into 'master'
Implement /battlemode command and more See merge request veloren/veloren!2787
This commit is contained in:
commit
003b9f57ce
@ -47,6 +47,8 @@ pub enum ChatCommand {
|
||||
Alias,
|
||||
ApplyBuff,
|
||||
Ban,
|
||||
BattleMode,
|
||||
BattleModeForce,
|
||||
Build,
|
||||
BuildAreaAdd,
|
||||
BuildAreaList,
|
||||
@ -321,6 +323,28 @@ impl ChatCommand {
|
||||
true for overwrite to alter an existing ban..",
|
||||
Some(Moderator),
|
||||
),
|
||||
#[rustfmt::skip]
|
||||
ChatCommand::BattleMode => cmd(
|
||||
vec![Enum(
|
||||
"battle mode",
|
||||
vec!["pvp".to_owned(), "pve".to_owned()],
|
||||
Optional,
|
||||
)],
|
||||
"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,
|
||||
),
|
||||
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 +647,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",
|
||||
|
@ -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,15 +469,56 @@ impl Attack {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if we should allow negative effects from one player to another
|
||||
// FIXME: handle pets?
|
||||
// This code works only with players.
|
||||
// You still can kill someone's pet and
|
||||
// you still can be killed by someone's pet
|
||||
pub fn may_harm(attacker: Option<&Player>, target: Option<&Player>) -> bool {
|
||||
attacker
|
||||
.zip(target)
|
||||
.map_or(true, |(attacker, target)| attacker.may_harm(target))
|
||||
/// Function that checks for unintentional PvP between players.
|
||||
///
|
||||
/// 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"))]
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ pub struct PlayerPhysicsSettings {
|
||||
///
|
||||
/// 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,
|
||||
|
@ -4,7 +4,7 @@ use common::{
|
||||
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 +27,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,9 +175,9 @@ 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
|
||||
// We don't have this for now, but think about this
|
||||
// when we will add this.
|
||||
let may_harm = || {
|
||||
let owner = match source {
|
||||
@ -185,11 +186,13 @@ 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)
|
||||
})
|
||||
combat::may_harm(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
owner,
|
||||
target,
|
||||
)
|
||||
};
|
||||
|
||||
conditions_held && (kind.is_buff() || may_harm())
|
||||
|
@ -2,8 +2,8 @@ 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,
|
||||
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,13 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
beam_owner.and_then(|owner| read_data.players.get(owner)),
|
||||
read_data.players.get(target),
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
beam_owner,
|
||||
target,
|
||||
);
|
||||
let attack_options = AttackOptions {
|
||||
// No luck with dodging beams
|
||||
|
@ -2,13 +2,13 @@ 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,
|
||||
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,13 @@ impl<'a> System<'a> for Sys {
|
||||
char_state: read_data.char_states.get(target),
|
||||
};
|
||||
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
read_data.players.get(attacker),
|
||||
read_data.players.get(target),
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
Some(attacker),
|
||||
target,
|
||||
);
|
||||
|
||||
let attack_options = AttackOptions {
|
||||
|
@ -2,8 +2,8 @@ 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,
|
||||
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,13 @@ fn dispatch_hit(
|
||||
});
|
||||
}
|
||||
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
owner.and_then(|owner| read_data.players.get(owner)),
|
||||
read_data.players.get(target),
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
owner,
|
||||
target,
|
||||
);
|
||||
|
||||
let attack_options = AttackOptions {
|
||||
|
@ -2,8 +2,8 @@ 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,
|
||||
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,13 @@ impl<'a> System<'a> for Sys {
|
||||
char_state: read_data.character_states.get(target),
|
||||
};
|
||||
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
shockwave_owner.and_then(|owner| read_data.players.get(owner)),
|
||||
read_data.players.get(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
|
||||
|
@ -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;
|
||||
@ -31,7 +34,7 @@ use common::{
|
||||
event::{EventBus, ServerEvent},
|
||||
generation::EntityInfo,
|
||||
npc::{self, get_npc_name},
|
||||
resources::{PlayerPhysicsSettings, TimeOfDay},
|
||||
resources::{BattleMode, PlayerPhysicsSettings, Time, TimeOfDay},
|
||||
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
|
||||
uid::Uid,
|
||||
vol::RectVolSize,
|
||||
@ -52,7 +55,6 @@ use vek::*;
|
||||
use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement};
|
||||
use world::util::Sampler;
|
||||
|
||||
use crate::{client::Client, login_provider::LoginProvider, wiring};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
pub trait ChatCommandExt {
|
||||
@ -113,6 +115,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 +3098,145 @@ fn handle_ban(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_battlemode(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
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() {
|
||||
return Err("Command disabled in server settings".to_owned());
|
||||
}
|
||||
|
||||
#[cfg(feature = "worldgen")]
|
||||
let in_town = {
|
||||
// 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, |wpos, size: u32| {
|
||||
wpos / size as i32
|
||||
});
|
||||
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("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_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 {
|
||||
let msg = format!(
|
||||
"Cooldown period active. Try again in {:.0} seconds",
|
||||
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()),
|
||||
};
|
||||
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(
|
||||
client,
|
||||
ServerGeneral::server_msg(
|
||||
ChatType::CommandInfo,
|
||||
format!("New battle mode: {:?}", mode),
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
} else {
|
||||
let players = ecs.read_storage::<comp::Player>();
|
||||
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.");
|
||||
} 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, msg),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_battlemode_force(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
args: Vec<String>,
|
||||
action: &ChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
let ecs = server.state.ecs();
|
||||
let settings = ecs.read_resource::<Settings>();
|
||||
if !settings.battle_mode.allow_choosing() {
|
||||
return Err("Command disabled in server settings".to_owned());
|
||||
}
|
||||
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!("Set battle mode to: {:?}", mode),
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_unban(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
|
@ -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,13 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
char_state: char_state_b_maybe,
|
||||
};
|
||||
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
owner_entity.and_then(|owner| players.get(owner)),
|
||||
players.get(entity_b),
|
||||
alignments,
|
||||
players,
|
||||
uid_allocator,
|
||||
owner_entity,
|
||||
entity_b,
|
||||
);
|
||||
let attack_options = combat::AttackOptions {
|
||||
// cool guyz maybe don't look at explosions
|
||||
@ -968,6 +974,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(),
|
||||
@ -983,23 +991,18 @@ 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.
|
||||
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(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
|
||||
|
@ -1,7 +1,7 @@
|
||||
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,
|
||||
@ -201,13 +201,17 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
|
||||
Some(skill_set),
|
||||
Some(inventory),
|
||||
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),
|
||||
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>(),
|
||||
state.ecs().fetch_mut::<BattleModeBuffer>(),
|
||||
) {
|
||||
match presence.kind {
|
||||
PresenceKind::Character(char_id) => {
|
||||
@ -216,6 +220,12 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
|
||||
.read_storage::<common::comp::Waypoint>()
|
||||
.get(entity)
|
||||
.cloned();
|
||||
// Store last battle mode change
|
||||
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>();
|
||||
|
@ -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::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>,
|
||||
@ -196,8 +218,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 {
|
||||
|
@ -40,6 +40,28 @@ pub struct X509FilePair {
|
||||
pub key: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum ServerBattleMode {
|
||||
Global(BattleMode),
|
||||
PerPlayer { default: BattleMode },
|
||||
}
|
||||
|
||||
impl ServerBattleMode {
|
||||
pub fn allow_choosing(&self) -> bool {
|
||||
match self {
|
||||
ServerBattleMode::Global { .. } => false,
|
||||
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)]
|
||||
#[serde(default)]
|
||||
pub struct Settings {
|
||||
@ -49,7 +71,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 +101,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),
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
presence::{Presence, RepositionOnChunkLoad},
|
||||
settings::Settings,
|
||||
sys::sentinel::DeletedEntities,
|
||||
wiring, SpawnPoint,
|
||||
wiring, BattleModeBuffer, SpawnPoint,
|
||||
};
|
||||
use common::{
|
||||
character::CharacterId,
|
||||
@ -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,36 @@ 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 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 = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,12 @@ impl<'a> System<'a> for Sys {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let player = Player::new(username, read_data.settings.battle_mode, uuid);
|
||||
// NOTE: this is just default value.
|
||||
//
|
||||
// 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);
|
||||
|
||||
if !player.is_valid() {
|
||||
|
Loading…
Reference in New Issue
Block a user