Merge branch 'juliancoffee/pvp_command' into 'master'

Implement /battlemode command and more

See merge request veloren/veloren!2787
This commit is contained in:
Joshua Barretto 2021-09-04 18:37:01 +00:00
commit 003b9f57ce
16 changed files with 399 additions and 64 deletions

View File

@ -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",

View File

@ -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"))]

View File

@ -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,
}
}

View File

@ -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,

View File

@ -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())

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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(&notice);
}
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,

View File

@ -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

View File

@ -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>();

View File

@ -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(&registry).unwrap();
let tick_metrics = TickMetrics::new(&registry).unwrap();
let physics_metrics = PhysicsMetrics::new(&registry).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 {

View File

@ -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),

View File

@ -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;
}
}
}
}
}

View File

@ -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() {