mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
Support cuts through world in world sync. Closes #192.
This commit is contained in:
parent
5e186ef8e5
commit
75dd2bdef2
7 changed files with 162 additions and 31 deletions
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
@ -912,4 +1000,4 @@ impl Drop for WorldManager {
|
|||
|
||||
impl SaveStateEntry for FxHashMap<ChunkCoord, ChunkData> {
|
||||
const FILENAME: &'static str = "world_chunks";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
local old_GameCutThroughWorldVertical = GameCutThroughWorldVertical
|
||||
|
||||
function GameCutThroughWorldVertical(...)
|
||||
old_GameCutThroughWorldVertical(...)
|
||||
CrossCall("ew_cut_through_world", ...)
|
||||
end
|
11
quant.ew/files/system/world_sync_cuts/world_sync_cuts.lua
Normal file
11
quant.ew/files/system/world_sync_cuts/world_sync_cuts.lua
Normal 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
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue