Support cuts through world in world sync. Closes #192.

This commit is contained in:
IQuant 2024-10-14 21:33:02 +03:00
parent 5e186ef8e5
commit 75dd2bdef2
7 changed files with 162 additions and 31 deletions

View file

@ -556,6 +556,19 @@ impl NetManager {
}
}
Some("reset_world") => state.world.reset(),
Some("cut_through_world") => {
let x: Option<i32> = msg.next().and_then(|s| s.parse().ok());
let y_min: Option<i32> = msg.next().and_then(|s| s.parse().ok());
let y_max: Option<i32> = msg.next().and_then(|s| s.parse().ok());
let radius: Option<i32> = msg.next().and_then(|s| s.parse().ok());
let (Some(x), Some(y_min), Some(y_max), Some(radius)) = (x, y_min, y_max, radius)
else {
error!("Missing arguments in cut_through_world message");
return;
};
state.world.cut_through_world(x, y_min, y_max, radius);
}
Some("flush") => self.peer.flush(),
key => {
error!("Unknown msg from mod: {:?}", key)

View file

@ -1,10 +1,13 @@
use std::{env, mem};
use std::{env, f32::consts::PI, mem, time::Instant};
use bitcode::{Decode, Encode};
use rustc_hash::{FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
use tracing::{debug, info, warn};
use world_model::{ChunkCoord, ChunkData, ChunkDelta, WorldModel};
use world_model::{
chunk::{Chunk, Pixel},
ChunkCoord, ChunkData, ChunkDelta, WorldModel, CHUNK_SIZE,
};
pub use world_model::encoding::NoitaWorldUpdate;
@ -334,7 +337,13 @@ impl WorldManager {
}
pub(crate) fn update(&mut self) {
fn should_kill(my_pos: (i32, i32), cam_pos: (i32, i32), chx: i32, chy: i32, is_notplayer: bool) -> bool {
fn should_kill(
my_pos: (i32, i32),
cam_pos: (i32, i32),
chx: i32,
chy: i32,
is_notplayer: bool,
) -> bool {
let (x, y) = my_pos;
let (cx, cy) = cam_pos;
if (x - cx).abs() > 2 || (y - cy).abs() > 2 {
@ -370,12 +379,24 @@ impl WorldManager {
}
// This state doesn't have much to do.
ChunkState::WaitingForAuthority => {
if should_kill(self.my_pos, self.cam_pos, chunk.0, chunk.1, self.is_notplayer) {
if should_kill(
self.my_pos,
self.cam_pos,
chunk.0,
chunk.1,
self.is_notplayer,
) {
*state = ChunkState::UnloadPending;
}
}
ChunkState::Listening { authority, .. } => {
if should_kill(self.my_pos, self.cam_pos, chunk.0, chunk.1, self.is_notplayer) {
if should_kill(
self.my_pos,
self.cam_pos,
chunk.0,
chunk.1,
self.is_notplayer,
) {
debug!("Unloading [listening] chunk {chunk:?}");
emit_queue.push((
Destination::Peer(*authority),
@ -385,7 +406,13 @@ impl WorldManager {
}
}
ChunkState::Authority { new_authority, .. } => {
if should_kill(self.my_pos, self.cam_pos, chunk.0, chunk.1, self.is_notplayer) {
if should_kill(
self.my_pos,
self.cam_pos,
chunk.0,
chunk.1,
self.is_notplayer,
) {
if let Some(new) = new_authority {
emit_queue.push((
Destination::Peer(new.0),
@ -407,7 +434,13 @@ impl WorldManager {
}
}
ChunkState::WantToGetAuth { .. } => {
if should_kill(self.my_pos, self.cam_pos, chunk.0, chunk.1, self.is_notplayer) {
if should_kill(
self.my_pos,
self.cam_pos,
chunk.0,
chunk.1,
self.is_notplayer,
) {
debug!("Unloading [want to get auth] chunk {chunk:?}");
*state = ChunkState::UnloadPending;
}
@ -766,18 +799,17 @@ impl WorldManager {
} else {
self.emit_msg(
Destination::Host,
WorldNetMessage::RelinquishAuthority { chunk, chunk_data: None },
WorldNetMessage::RelinquishAuthority {
chunk,
chunk_data: None,
},
);
}
}
WorldNetMessage::RequestAuthorityTransfer { chunk } => {
info!("Got a request for authority transfer");
let state = self.chunk_state.get(&chunk);
if let Some(ChunkState::Authority {
listeners,
..
}) = state
{
if let Some(ChunkState::Authority { listeners, .. }) = state {
let chunk_data = self.outbound_model.get_chunk_data(chunk);
self.emit_msg(
Destination::Peer(source),
@ -899,6 +931,62 @@ impl WorldManager {
})
.collect()
}
pub(crate) fn cut_through_world(&mut self, x: i32, y_min: i32, y_max: i32, radius: i32) {
let start = Instant::now();
info!("Started cut_through_world");
let max_wiggle = 5;
let interval = 300.0;
let cut_x_clip_range =
(x - radius - max_wiggle - CHUNK_SIZE as i32)..(x + radius + max_wiggle);
let cut_x_range = x - radius..x + radius;
for (chunk_coord, chunk_encoded) in self.chunk_storage.iter_mut() {
// Check if this chunk is anywhere close to the cut. Skip if it isn't.
let chunk_start_x = chunk_coord.0 * CHUNK_SIZE as i32;
let chunk_start_y = chunk_coord.1 * CHUNK_SIZE as i32;
if !cut_x_clip_range.contains(&chunk_start_x) {
continue;
}
let mut chunk = Chunk::default();
chunk_encoded.apply_to_chunk(&mut chunk);
for in_chunk_y in 0..(CHUNK_SIZE as i32) {
let global_y = in_chunk_y + chunk_start_y;
// Skip if higher/lower than the cut.
if global_y < y_min || global_y > y_max {
continue;
}
let wiggle = -f32::cos((global_y as f32) / interval * PI * 2.0) * max_wiggle as f32;
let wiggle = wiggle as i32; // TODO find a more accurate way to compute wiggle.
let in_chunk_x_range = cut_x_range.start - chunk_start_x + wiggle
..cut_x_range.end - chunk_start_x + wiggle;
let in_chunk_x_range = in_chunk_x_range.start.clamp(0, CHUNK_SIZE as i32 - 1)
..in_chunk_x_range.end.clamp(0, CHUNK_SIZE as i32);
for in_chunk_x in in_chunk_x_range {
let air_pixel = Pixel {
flags: world_model::chunk::PixelFlags::Normal,
material: 0,
};
chunk.set_pixel(
(in_chunk_y as usize) * CHUNK_SIZE + (in_chunk_x as usize),
air_pixel,
);
}
}
*chunk_encoded = chunk.to_chunk_data();
}
let elapsed = start.elapsed();
info!("Cut through world took {} ms", elapsed.as_millis());
}
}
impl Drop for WorldManager {

View file

@ -6,10 +6,10 @@ use encoding::{NoitaWorldUpdate, PixelRun, PixelRunner};
use rustc_hash::{FxHashMap, FxHashSet};
use tracing::info;
mod chunk;
pub(crate) mod chunk;
pub mod encoding;
const CHUNK_SIZE: usize = 128;
pub(crate) const CHUNK_SIZE: usize = 128;
#[derive(Debug, Encode, Decode, Clone, Copy, Hash, PartialEq, Eq)]
pub(crate) struct ChunkCoord(pub i32, pub i32);
@ -51,6 +51,17 @@ impl ChunkData {
let runs = runner.build();
ChunkData { runs }
}
pub(crate) fn apply_to_chunk(&self, chunk: &mut Chunk) {
let mut offset = 0;
for run in &self.runs {
for _ in 0..run.length {
let pixel = run.data;
chunk.set_compact_pixel(offset, pixel);
offset += 1;
}
}
}
}
impl WorldModel {
@ -188,24 +199,12 @@ impl WorldModel {
pub(crate) fn apply_chunk_data(&mut self, chunk: ChunkCoord, chunk_data: &ChunkData) {
self.updated_chunks.insert(chunk);
let chunk = self.chunks.entry(chunk).or_default();
let mut offset = 0;
for run in &chunk_data.runs {
for _ in 0..run.length {
let pixel = run.data;
chunk.set_compact_pixel(offset, pixel);
offset += 1;
}
}
chunk_data.apply_to_chunk(chunk);
}
pub(crate) fn get_chunk_data(&mut self, chunk: ChunkCoord) -> Option<ChunkData> {
let chunk = self.chunks.get(&chunk)?;
let mut runner = PixelRunner::new();
for i in 0..CHUNK_SIZE * CHUNK_SIZE {
runner.put_pixel(chunk.compact_pixel(i))
}
let runs = runner.build();
Some(ChunkData { runs })
Some(chunk.to_chunk_data())
}
pub(crate) fn forget_chunk(&mut self, chunk: ChunkCoord) {

View file

@ -3,10 +3,14 @@ use std::num::NonZeroU16;
use bitcode::{Decode, Encode};
use crossbeam::atomic::AtomicCell;
use super::{encoding::RawPixel, CHUNK_SIZE};
use super::{
encoding::{PixelRunner, RawPixel},
ChunkData, CHUNK_SIZE,
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Encode, Decode)]
pub enum PixelFlags {
/// Actual material isn't known yet.
#[default]
Unknown,
Normal,
@ -139,4 +143,13 @@ impl Chunk {
self.changed = [false; CHUNK_SIZE * CHUNK_SIZE];
self.any_changed = false;
}
pub fn to_chunk_data(&self) -> ChunkData {
let mut runner = PixelRunner::new();
for i in 0..CHUNK_SIZE * CHUNK_SIZE {
runner.put_pixel(self.compact_pixel(i))
}
let runs = runner.build();
ChunkData { runs }
}
}

View file

@ -0,0 +1,6 @@
local old_GameCutThroughWorldVertical = GameCutThroughWorldVertical
function GameCutThroughWorldVertical(...)
old_GameCutThroughWorldVertical(...)
CrossCall("ew_cut_through_world", ...)
end

View file

@ -0,0 +1,11 @@
np.CrossCallAdd("ew_cut_through_world", function(x, min_y, max_y, radius, _radius2)
if ctx.is_host then
net.proxy_send("cut_through_world", x.." "..min_y.." "..max_y.." "..radius)
end
end)
ModLuaFileAppend("data/scripts/magic/beam_from_sky.lua", "mods/quant.ew/files/system/world_sync_cuts/patch_cut_through_world.lua")
local module = {}
return module

View file

@ -121,6 +121,7 @@ local function load_modules()
ctx.load_system("angry_ghost_memory")
ctx.load_system("gate_boss")
ctx.load_system("tapion")
ctx.load_system("world_sync_cuts")
end
local function load_extra_modules()