misc post party changes

This commit is contained in:
crabman 2024-03-31 03:13:15 +01:00
parent 302e2e92b0
commit 10c47128ce
No known key found for this signature in database
6 changed files with 240 additions and 80 deletions

View File

@ -88,6 +88,9 @@ command-transform-invalid-presence = Cannot transform in the current presence
command-aura-invalid-buff-parameters = Invalid buff parameters for aura
command-aura-spawn = Spawned new aura attached to entity
command-aura-spawn-new-entity = Spawned new aura
command-reloaded-chunks = Reloaded { $reloaded } chunks
command-server-no-experimental-terrain-persistence = Server was compiled without terrain persistence enabled
command-experimental-terrain-persistence-disabled = Experimental terrain persistence is disabled
# Unreachable/untestable but added for consistency

View File

@ -325,6 +325,7 @@ pub enum ServerChatCommand {
Buff,
Build,
Campfire,
ClearPersistedTerrain,
CreateLocation,
DebugColumn,
DebugWays,
@ -532,6 +533,11 @@ impl ServerChatCommand {
Some(Admin),
),
ServerChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Some(Admin)),
ServerChatCommand::ClearPersistedTerrain => cmd(
vec![Integer("radius", 6, Required)],
"Clears nearby persisted terrain",
Some(Admin),
),
ServerChatCommand::DebugColumn => cmd(
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
"Prints some debug information about a column",
@ -632,9 +638,11 @@ impl ServerChatCommand {
Some(Moderator),
),
ServerChatCommand::Kill => cmd(vec![], "Kill yourself", None),
ServerChatCommand::KillNpcs => {
cmd(vec![Flag("--also-pets")], "Kill the NPCs", Some(Admin))
},
ServerChatCommand::KillNpcs => cmd(
vec![Float("radius", 100.0, Optional), Flag("--also-pets")],
"Kill the NPCs",
Some(Admin),
),
ServerChatCommand::Kit => cmd(
vec![Enum("kit_name", KITS.to_vec(), Required)],
"Place a set of items into your inventory.",
@ -715,8 +723,8 @@ impl ServerChatCommand {
Some(Admin),
),
ServerChatCommand::ReloadChunks => cmd(
vec![],
"Reloads all chunks loaded on the server",
vec![Integer("radius", 6, Optional)],
"Reloads chunks loaded on the server",
Some(Admin),
),
ServerChatCommand::RemoveLights => cmd(
@ -980,6 +988,7 @@ impl ServerChatCommand {
ServerChatCommand::Buff => "buff",
ServerChatCommand::Build => "build",
ServerChatCommand::Campfire => "campfire",
ServerChatCommand::ClearPersistedTerrain => "clear_persisted_terrain",
ServerChatCommand::DebugColumn => "debug_column",
ServerChatCommand::DebugWays => "debug_ways",
ServerChatCommand::DisconnectAllPlayers => "disconnect_all_players",

View File

@ -540,12 +540,15 @@ impl State {
}
/// Removes every chunk of the terrain.
pub fn clear_terrain(&mut self) {
pub fn clear_terrain(&mut self) -> usize {
let removed_chunks = &mut self.ecs.write_resource::<TerrainChanges>().removed_chunks;
self.terrain_mut().drain().for_each(|(key, _)| {
removed_chunks.insert(key);
});
self.terrain_mut()
.drain()
.map(|(key, _)| {
removed_chunks.insert(key);
})
.count()
}
/// Insert the provided chunk into this state's terrain.
@ -570,7 +573,7 @@ impl State {
/// Remove the chunk with the given key from this state's terrain, if it
/// exists.
pub fn remove_chunk(&mut self, key: Vec2<i32>) {
pub fn remove_chunk(&mut self, key: Vec2<i32>) -> bool {
if self
.ecs
.write_resource::<TerrainGrid>()
@ -581,6 +584,10 @@ impl State {
.write_resource::<TerrainChanges>()
.removed_chunks
.insert(key);
true
} else {
false
}
}

View File

@ -53,12 +53,13 @@ use common::{
parse_cmd_args,
resources::{BattleMode, PlayerPhysicsSettings, ProgramTime, Secs, Time, TimeOfDay, TimeScale},
rtsim::{Actor, Role},
spiral::Spiral2d,
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
tether::Tethered,
uid::Uid,
vol::ReadVol,
weather, Damage, DamageKind, DamageSource, Explosion, GroupTarget, LoadoutBuilder,
RadiusEffect,
weather, CachedSpatialGrid, Damage, DamageKind, DamageSource, Explosion, GroupTarget,
LoadoutBuilder, RadiusEffect,
};
use common_net::{
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
@ -145,6 +146,7 @@ fn do_command(
ServerChatCommand::Buff => handle_buff,
ServerChatCommand::Build => handle_build,
ServerChatCommand::Campfire => handle_spawn_campfire,
ServerChatCommand::ClearPersistedTerrain => handle_clear_persisted_terrain,
ServerChatCommand::DebugColumn => handle_debug_column,
ServerChatCommand::DebugWays => handle_debug_ways,
ServerChatCommand::DisconnectAllPlayers => handle_disconnect_all_players,
@ -2010,6 +2012,56 @@ fn handle_spawn_campfire(
Ok(())
}
#[cfg(feature = "persistent_world")]
fn handle_clear_persisted_terrain(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
action: &ServerChatCommand,
) -> CmdResult<()> {
let Some(radius) = parse_cmd_args!(args, i32) else {
return Err(Content::Plain(action.help_string()));
};
let radius = radius.min(64);
let pos = position(server, target, "target")?;
let chunk_key = server.state.terrain().pos_key(pos.0.as_());
let mut terrain_persistence2 = server
.state
.ecs()
.try_fetch_mut::<crate::terrain_persistence::TerrainPersistence>();
if let Some(ref mut terrain_persistence) = terrain_persistence2 {
for offset in Spiral2d::with_radius(radius) {
let chunk_key = chunk_key + offset;
terrain_persistence.clear_chunk(chunk_key);
}
drop(terrain_persistence2);
reload_chunks_inner(server, pos.0, Some(radius));
Ok(())
} else {
Err(Content::localized(
"command-experimental-terrain-persistence-disabled",
))
}
}
#[cfg(not(feature = "persistent_world"))]
fn handle_clear_persisted_terrain(
_server: &mut Server,
_client: EcsEntity,
_target: EcsEntity,
_args: Vec<String>,
_action: &ServerChatCommand,
) -> CmdResult<()> {
Err(Content::localized(
"command-server-no-experimental-terrain-persistence",
))
}
fn handle_safezone(
server: &mut Server,
client: EcsEntity,
@ -2413,16 +2465,21 @@ fn parse_alignment(owner: Uid, alignment: &str) -> CmdResult<Alignment> {
fn handle_kill_npcs(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
target: EcsEntity,
args: Vec<String>,
_action: &ServerChatCommand,
) -> CmdResult<()> {
let kill_pets = if let Some(kill_option) = parse_cmd_args!(args, String) {
let (radius, options) = parse_cmd_args!(args, f32, String);
let kill_pets = if let Some(kill_option) = options {
kill_option.contains("--also-pets")
} else {
false
};
let position = radius
.map(|_| position(server, target, "target"))
.transpose()?;
let to_kill = {
let ecs = server.state.ecs();
let entities = ecs.entities();
@ -2432,40 +2489,78 @@ fn handle_kill_npcs(
let alignments = ecs.read_storage::<Alignment>();
let rtsim_entities = ecs.read_storage::<common::rtsim::RtSimEntity>();
let mut rtsim = ecs.write_resource::<crate::rtsim::RtSim>();
let spatial_grid;
(
&entities,
&healths,
!&players,
alignments.maybe(),
&positions,
)
.join()
.filter_map(|(entity, _health, (), alignment, pos)| {
let should_kill = kill_pets
|| if let Some(Alignment::Owned(owned)) = alignment {
ecs.entity_from_uid(*owned)
.map_or(true, |owner| !players.contains(owner))
} else {
true
};
let mut iter_a;
let mut iter_b;
if should_kill {
if let Some(rtsim_entity) = rtsim_entities.get(entity).copied() {
rtsim.hook_rtsim_actor_death(
&ecs.read_resource::<Arc<world::World>>(),
ecs.read_resource::<world::IndexOwned>().as_index_ref(),
Actor::Npc(rtsim_entity.0),
Some(pos.0),
None,
);
}
Some(entity)
let iter: &mut dyn Iterator<
Item = (
EcsEntity,
&comp::Health,
(),
Option<&comp::Alignment>,
&comp::Pos,
),
> = if let (Some(radius), Some(position)) = (radius, position) {
spatial_grid = ecs.read_resource::<CachedSpatialGrid>();
iter_a = spatial_grid
.0
.in_circle_aabr(position.0.xy(), radius)
.filter_map(|entity| {
(
&entities,
&healths,
!&players,
alignments.maybe(),
&positions,
)
.lend_join()
.get(entity, &entities)
})
.filter(move |(_, _, _, _, pos)| {
pos.0.distance_squared(position.0) <= radius.powi(2)
});
&mut iter_a as _
} else {
iter_b = (
&entities,
&healths,
!&players,
alignments.maybe(),
&positions,
)
.join();
&mut iter_b as _
};
iter.filter_map(|(entity, _health, (), alignment, pos)| {
let should_kill = kill_pets
|| if let Some(Alignment::Owned(owned)) = alignment {
ecs.entity_from_uid(*owned)
.map_or(true, |owner| !players.contains(owner))
} else {
None
true
};
if should_kill {
if let Some(rtsim_entity) = rtsim_entities.get(entity).copied() {
rtsim.hook_rtsim_actor_death(
&ecs.read_resource::<Arc<world::World>>(),
ecs.read_resource::<world::IndexOwned>().as_index_ref(),
Actor::Npc(rtsim_entity.0),
Some(pos.0),
None,
);
}
})
.collect::<Vec<_>>()
Some(entity)
} else {
None
}
})
.collect::<Vec<_>>()
};
let count = to_kill.len();
for entity in to_kill {
@ -3605,20 +3700,60 @@ fn parse_skill_tree(skill_tree: &str) -> CmdResult<comp::skillset::SkillGroupKin
}
}
fn reload_chunks_inner(server: &mut Server, pos: Vec3<f32>, radius: Option<i32>) -> usize {
let mut removed = 0;
if let Some(radius) = radius {
let chunk_key = server.state.terrain().pos_key(pos.as_());
for key_offset in Spiral2d::with_radius(radius) {
let chunk_key = chunk_key + key_offset;
#[cfg(feature = "persistent_world")]
server
.state
.ecs()
.try_fetch_mut::<crate::terrain_persistence::TerrainPersistence>()
.map(|mut terrain_persistence| terrain_persistence.unload_chunk(chunk_key));
if server.state.remove_chunk(chunk_key) {
removed += 1;
}
}
} else {
#[cfg(feature = "persistent_world")]
server
.state
.ecs()
.try_fetch_mut::<crate::terrain_persistence::TerrainPersistence>()
.map(|mut terrain_persistence| terrain_persistence.unload_all());
removed = server.state.clear_terrain();
}
removed
}
fn handle_reload_chunks(
server: &mut Server,
_client: EcsEntity,
_target: EcsEntity,
_args: Vec<String>,
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
_action: &ServerChatCommand,
) -> CmdResult<()> {
#[cfg(feature = "persistent_world")]
server
.state
.ecs()
.try_fetch_mut::<crate::terrain_persistence::TerrainPersistence>()
.map(|mut terrain_persistence| terrain_persistence.unload_all());
server.state.clear_terrain();
let radius = parse_cmd_args!(args, i32);
let pos = position(server, target, "target")?.0;
let removed = reload_chunks_inner(server, pos, radius.map(|radius| radius.min(64)));
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
Content::localized_with_args("command-reloaded-chunks", [(
"reloaded",
removed.to_string(),
)]),
),
);
Ok(())
}

View File

@ -2267,6 +2267,7 @@ pub fn transform_entity(
set_or_remove_component(server, entity, Some(skill_set))?;
set_or_remove_component(server, entity, Some(poise))?;
set_or_remove_component(server, entity, health)?;
set_or_remove_component(server, entity, Some(comp::Energy::new(body)))?;
set_or_remove_component(server, entity, Some(body))?;
set_or_remove_component(server, entity, Some(body.mass()))?;
set_or_remove_component(server, entity, Some(body.density()))?;

View File

@ -15,7 +15,7 @@ use std::{
use tracing::{debug, error, info, warn};
use vek::*;
const MAX_BLOCK_CACHE: usize = 5_000_000;
const MAX_BLOCK_CACHE: usize = 64_000_000;
pub struct TerrainPersistence {
path: PathBuf,
@ -157,11 +157,8 @@ impl TerrainPersistence {
pub fn unload_chunk(&mut self, key: Vec2<i32>) {
if let Some(LoadedChunk { chunk, modified }) = self.chunks.remove(&key) {
match (self.cached_chunks.peek(&key), modified) {
(Some(_), false) => {},
_ => {
self.cached_chunks.insert(key, chunk.clone());
},
if modified || self.cached_chunks.peek(&key).is_none() {
self.cached_chunks.insert(key, chunk.clone());
}
// Prevent any uneccesarry IO when nothing in this chunk has changed
@ -169,22 +166,38 @@ impl TerrainPersistence {
return;
}
let bytes = match bincode::serialize::<version::Current>(&chunk.prepare_raw()) {
Err(err) => {
error!("Failed to serialize chunk data: {:?}", err);
return;
},
Ok(bytes) => bytes,
};
if chunk.blocks.is_empty() {
let path = self.path_for(key);
let atomic_file =
AtomicFile::new(self.path_for(key), OverwriteBehavior::AllowOverwrite);
if let Err(err) = atomic_file.write(|file| file.write_all(&bytes)) {
error!("Failed to write chunk data to file: {:?}", err);
if let Err(error) = std::fs::remove_file(&path) {
error!(?error, ?path, "Failed to remove file for empty chunk");
}
} else {
let bytes = match bincode::serialize::<version::Current>(&chunk.prepare_raw()) {
Err(err) => {
error!("Failed to serialize chunk data: {:?}", err);
return;
},
Ok(bytes) => bytes,
};
let atomic_file =
AtomicFile::new(self.path_for(key), OverwriteBehavior::AllowOverwrite);
if let Err(err) = atomic_file.write(|file| file.write_all(&bytes)) {
error!("Failed to write chunk data to file: {:?}", err);
}
}
}
}
pub fn clear_chunk(&mut self, chunk: Vec2<i32>) {
self.cached_chunks.remove(&chunk);
self.chunks.insert(chunk, LoadedChunk {
chunk: Chunk::default(),
modified: true,
});
}
pub fn unload_all(&mut self) {
for key in self.chunks.keys().copied().collect::<Vec<_>>() {
self.unload_chunk(key);
@ -202,10 +215,6 @@ impl TerrainPersistence {
.insert(pos - key * TerrainChunk::RECT_SIZE.map(|e| e as i32), block);
if old_block != Some(block) {
loaded_chunk.modified = true;
if old_block.is_none() {
self.cached_chunks.limiter_mut().add_block();
}
}
}
}
@ -251,7 +260,7 @@ impl Limiter<Vec2<i32>, Chunk> for ByBlockLimiter {
type KeyToInsert<'a> = Vec2<i32>;
type LinkType = u32;
fn is_over_the_limit(&self, _length: usize) -> bool { false }
fn is_over_the_limit(&self, _length: usize) -> bool { self.counted_blocks > self.block_limit }
fn on_insert(
&mut self,
@ -306,10 +315,6 @@ impl ByBlockLimiter {
counted_blocks: 0,
}
}
/// This function should only be used when it is guaranteed that a block has
/// been added
fn add_block(&mut self) { self.counted_blocks += 1; }
}
/// # Adding a new chunk format version