2025-06-27 23:23:49 -04:00
|
|
|
use crate::chunk::{CellType, Chunk, ChunkPos};
|
2025-07-08 11:49:35 -04:00
|
|
|
use crate::noita::ParticleWorldState;
|
2025-06-29 19:46:10 -04:00
|
|
|
use crate::{CHUNK_AMOUNT, CHUNK_SIZE, State};
|
2025-07-01 12:26:35 -04:00
|
|
|
#[cfg(target_arch = "x86")]
|
|
|
|
use noita_api::EntityID;
|
2025-07-07 00:08:11 -04:00
|
|
|
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
2025-07-01 12:26:35 -04:00
|
|
|
use rustc_hash::{FxBuildHasher, FxHashMap};
|
2025-07-01 15:39:51 -04:00
|
|
|
use std::f32::consts::PI;
|
2025-06-29 18:31:49 -04:00
|
|
|
#[derive(Default, Debug, Clone, Copy)]
|
2025-06-27 10:26:09 -04:00
|
|
|
pub struct Pos {
|
2025-07-01 12:26:35 -04:00
|
|
|
pub x: f32,
|
|
|
|
pub y: f32,
|
2025-06-27 10:26:09 -04:00
|
|
|
}
|
|
|
|
impl Pos {
|
2025-07-01 12:26:35 -04:00
|
|
|
pub fn new(x: f32, y: f32) -> Self {
|
2025-06-27 10:26:09 -04:00
|
|
|
Self { x, y }
|
|
|
|
}
|
2025-06-29 18:31:49 -04:00
|
|
|
pub fn to_chunk(self) -> ChunkPos {
|
2025-06-30 11:20:26 -04:00
|
|
|
ChunkPos::new(self.x.floor() as isize, self.y.floor() as isize)
|
2025-06-27 14:11:41 -04:00
|
|
|
}
|
|
|
|
}
|
2025-06-29 19:56:57 -04:00
|
|
|
const OFFSET: isize = CHUNK_AMOUNT as isize / 2;
|
2025-07-04 17:03:49 -04:00
|
|
|
impl State {
|
2025-07-08 11:49:35 -04:00
|
|
|
pub fn particle_world_state(&self) -> &ParticleWorldState {
|
|
|
|
unsafe { self.particle_world_state.assume_init_ref() }
|
|
|
|
}
|
2025-06-27 23:23:49 -04:00
|
|
|
pub fn update(&mut self) -> eyre::Result<()> {
|
2025-07-03 14:46:21 -04:00
|
|
|
if noita_api::raw::input_is_mouse_button_just_down(1)? {
|
2025-07-08 13:06:25 -04:00
|
|
|
unsafe { self.particle_world_state().debug_mouse_pos()? };
|
2025-07-03 14:46:21 -04:00
|
|
|
}
|
2025-06-27 23:23:49 -04:00
|
|
|
if self.blobs.is_empty() {
|
2025-07-08 10:24:37 -04:00
|
|
|
self.blobs.push(Blob::new(256.0, -(64.0 + 32.0)));
|
2025-06-27 23:23:49 -04:00
|
|
|
}
|
2025-07-05 21:19:25 -04:00
|
|
|
'upper: for blob in self.blobs.iter_mut() {
|
2025-07-01 19:11:31 -04:00
|
|
|
blob.update_pos()?;
|
2025-06-29 18:31:49 -04:00
|
|
|
let c = blob.pos.to_chunk();
|
2025-07-07 00:08:11 -04:00
|
|
|
if self
|
|
|
|
.world
|
|
|
|
.par_iter_mut()
|
|
|
|
.enumerate()
|
|
|
|
.try_for_each(|(i, chunk)| unsafe {
|
|
|
|
let x = i as isize / CHUNK_AMOUNT as isize + c.x;
|
|
|
|
let y = i as isize % CHUNK_AMOUNT as isize + c.y;
|
2025-07-08 11:49:35 -04:00
|
|
|
self.particle_world_state.assume_init_ref().encode_area(
|
|
|
|
x - OFFSET,
|
|
|
|
y - OFFSET,
|
|
|
|
chunk,
|
|
|
|
)
|
2025-07-07 00:08:11 -04:00
|
|
|
})
|
|
|
|
.is_err()
|
2025-06-29 18:31:49 -04:00
|
|
|
{
|
2025-07-07 00:08:11 -04:00
|
|
|
blob.update(&mut [])?;
|
|
|
|
continue 'upper;
|
2025-07-02 17:39:23 -04:00
|
|
|
}
|
2025-07-01 12:26:35 -04:00
|
|
|
blob.update(&mut self.world)?;
|
2025-07-07 00:13:47 -04:00
|
|
|
self.world.iter().enumerate().for_each(|(i, chunk)| unsafe {
|
|
|
|
let x = i as isize / CHUNK_AMOUNT as isize + c.x;
|
|
|
|
let y = i as isize % CHUNK_AMOUNT as isize + c.y;
|
2025-07-08 11:49:35 -04:00
|
|
|
let _ = self.particle_world_state.assume_init_ref().decode_area(
|
|
|
|
x - OFFSET,
|
|
|
|
y - OFFSET,
|
|
|
|
chunk,
|
|
|
|
);
|
2025-07-07 00:13:47 -04:00
|
|
|
});
|
2025-06-27 14:11:41 -04:00
|
|
|
}
|
2025-06-27 23:23:49 -04:00
|
|
|
Ok(())
|
2025-06-27 14:11:41 -04:00
|
|
|
}
|
2025-06-27 10:26:09 -04:00
|
|
|
}
|
2025-07-01 18:38:54 -04:00
|
|
|
pub const SIZE: usize = 24;
|
2025-06-27 10:26:09 -04:00
|
|
|
pub struct Blob {
|
|
|
|
pub pos: Pos,
|
2025-07-01 12:26:35 -04:00
|
|
|
pub pixels: FxHashMap<(isize, isize), Pixel>,
|
|
|
|
}
|
|
|
|
#[derive(Default, Copy, Clone)]
|
|
|
|
pub struct Pixel {
|
|
|
|
pub pos: Pos,
|
|
|
|
velocity: Pos,
|
|
|
|
acceleration: Pos,
|
2025-07-01 15:39:51 -04:00
|
|
|
stop: Option<usize>,
|
2025-07-01 17:44:36 -04:00
|
|
|
mutated: bool,
|
2025-07-01 12:26:35 -04:00
|
|
|
}
|
2025-07-01 16:04:39 -04:00
|
|
|
const DIRECTIONS: [f32; 7] = [
|
2025-07-01 15:39:51 -04:00
|
|
|
0.0,
|
|
|
|
PI / 4.0,
|
|
|
|
-PI / 4.0,
|
|
|
|
PI / 3.0,
|
|
|
|
-PI / 3.0,
|
2025-07-01 16:04:39 -04:00
|
|
|
PI / 2.3,
|
|
|
|
-PI / 2.3,
|
2025-07-01 15:39:51 -04:00
|
|
|
];
|
2025-07-01 12:26:35 -04:00
|
|
|
impl Pixel {
|
|
|
|
fn new(pos: Pos) -> Self {
|
|
|
|
Pixel {
|
|
|
|
pos,
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
2025-06-27 10:26:09 -04:00
|
|
|
}
|
|
|
|
impl Blob {
|
2025-07-01 19:11:31 -04:00
|
|
|
pub fn update_pos(&mut self) -> eyre::Result<()> {
|
2025-07-01 12:26:35 -04:00
|
|
|
#[cfg(target_arch = "x86")]
|
|
|
|
{
|
|
|
|
let player = EntityID::get_closest_with_tag(
|
|
|
|
self.pos.x as f64,
|
|
|
|
self.pos.y as f64,
|
|
|
|
"player_unit",
|
|
|
|
)?;
|
|
|
|
let (x, y) = player.position()?;
|
2025-07-01 19:11:31 -04:00
|
|
|
self.pos.x = x as f32;
|
2025-07-02 11:20:38 -04:00
|
|
|
self.pos.y = y as f32 - 7.0;
|
2025-07-01 12:26:35 -04:00
|
|
|
}
|
2025-07-01 19:11:31 -04:00
|
|
|
Ok(())
|
|
|
|
}
|
2025-07-02 11:20:38 -04:00
|
|
|
fn mean(&self) -> (isize, isize) {
|
|
|
|
let n = self
|
|
|
|
.pixels
|
|
|
|
.keys()
|
|
|
|
.fold((0, 0), |acc, x| (acc.0 + x.0, acc.1 + x.1));
|
|
|
|
(
|
|
|
|
n.0 / self.pixels.len() as isize,
|
|
|
|
n.1 / self.pixels.len() as isize,
|
|
|
|
)
|
|
|
|
}
|
2025-07-06 20:34:09 -04:00
|
|
|
pub fn update(&mut self, map: &mut [Chunk]) -> eyre::Result<()> {
|
2025-07-02 11:20:38 -04:00
|
|
|
let mean = self.mean();
|
|
|
|
let theta = (mean.1 as f32 - self.pos.y).atan2(mean.0 as f32 - self.pos.x);
|
2025-07-01 17:44:36 -04:00
|
|
|
for p in self.pixels.values_mut() {
|
|
|
|
p.mutated = false;
|
2025-07-01 12:26:35 -04:00
|
|
|
}
|
2025-07-01 17:44:36 -04:00
|
|
|
let mut keys = self.pixels.keys().cloned().collect::<Vec<(isize, isize)>>();
|
|
|
|
while !keys.is_empty()
|
|
|
|
&& let Some((c, p)) = self.pixels.remove_entry(&keys.remove(0))
|
2025-07-01 12:26:35 -04:00
|
|
|
{
|
2025-07-02 11:20:38 -04:00
|
|
|
self.run(c, p, theta);
|
2025-07-01 12:26:35 -04:00
|
|
|
}
|
2025-07-02 11:20:38 -04:00
|
|
|
for _ in 0..5 {
|
|
|
|
let mut boundary: FxHashMap<(isize, isize), i8> = FxHashMap::default();
|
|
|
|
for (a, b) in self.pixels.keys().cloned() {
|
|
|
|
let k = (a - 1, b);
|
|
|
|
boundary.entry(k).and_modify(|u| *u += 1).or_default();
|
|
|
|
let k = (a, b - 1);
|
|
|
|
boundary.entry(k).and_modify(|u| *u += 1).or_default();
|
|
|
|
let k = (a + 1, b);
|
|
|
|
boundary.entry(k).and_modify(|u| *u += 1).or_default();
|
|
|
|
let k = (a, b + 1);
|
|
|
|
boundary.entry(k).and_modify(|u| *u += 1).or_default();
|
|
|
|
boundary.insert((a, b), i8::MIN);
|
|
|
|
}
|
|
|
|
let mut boundary2: FxHashMap<(isize, isize), i8> = FxHashMap::default();
|
|
|
|
for ((a, b), p) in boundary {
|
|
|
|
boundary2.insert((a, b), p);
|
|
|
|
let k = (a - 1, b);
|
|
|
|
boundary2.entry(k).and_modify(|u| *u += 1);
|
|
|
|
let k = (a, b - 1);
|
|
|
|
boundary2.entry(k).and_modify(|u| *u += 1);
|
|
|
|
let k = (a + 1, b);
|
|
|
|
boundary2.entry(k).and_modify(|u| *u += 1);
|
|
|
|
let k = (a, b + 1);
|
|
|
|
boundary2.entry(k).and_modify(|u| *u += 1);
|
|
|
|
}
|
|
|
|
let mut is_some = false;
|
|
|
|
for (a, b) in boundary2 {
|
|
|
|
if b >= 3 {
|
|
|
|
let far = self.far();
|
|
|
|
self.pixels.insert(a, far);
|
|
|
|
is_some = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !is_some {
|
|
|
|
break;
|
2025-07-01 13:10:44 -04:00
|
|
|
}
|
2025-07-01 12:26:35 -04:00
|
|
|
}
|
2025-07-01 19:11:31 -04:00
|
|
|
let start = self.pos.to_chunk();
|
|
|
|
let mut last = ChunkPos::new(isize::MAX, isize::MAX);
|
|
|
|
let mut k = 0;
|
2025-07-01 12:26:35 -04:00
|
|
|
for (x, y) in self.pixels.keys() {
|
|
|
|
let c = ChunkPos::new(*x, *y);
|
2025-06-27 14:11:41 -04:00
|
|
|
if c != last {
|
2025-07-01 13:10:44 -04:00
|
|
|
let new =
|
|
|
|
(c.x - start.x + OFFSET) * CHUNK_AMOUNT as isize + (c.y - start.y + OFFSET);
|
|
|
|
if new < 0 || new as usize >= CHUNK_AMOUNT * CHUNK_AMOUNT {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
k = new as usize;
|
2025-06-27 14:11:41 -04:00
|
|
|
last = c;
|
|
|
|
}
|
2025-07-01 12:26:35 -04:00
|
|
|
let n = x.rem_euclid(CHUNK_SIZE as isize) as usize * CHUNK_SIZE
|
|
|
|
+ y.rem_euclid(CHUNK_SIZE as isize) as usize;
|
2025-06-28 15:20:06 -04:00
|
|
|
|
2025-07-01 13:10:44 -04:00
|
|
|
map[k][n] = match map[k][n] {
|
2025-07-02 17:39:23 -04:00
|
|
|
CellType::Unknown => {
|
|
|
|
map[k].modified = true;
|
|
|
|
CellType::Blob
|
|
|
|
}
|
2025-07-01 13:10:44 -04:00
|
|
|
_ => CellType::Ignore,
|
2025-06-28 15:20:06 -04:00
|
|
|
}
|
2025-06-27 14:11:41 -04:00
|
|
|
}
|
2025-07-01 12:26:35 -04:00
|
|
|
Ok(())
|
2025-06-27 14:11:41 -04:00
|
|
|
}
|
2025-07-01 17:44:36 -04:00
|
|
|
pub fn far(&mut self) -> Pixel {
|
|
|
|
let (a, b) = (self.pos.x.floor() as isize, self.pos.y.floor() as isize);
|
|
|
|
let mut l = isize::MIN;
|
|
|
|
let mut ret = (0, 0);
|
|
|
|
for (c, d) in self.pixels.keys() {
|
|
|
|
let dx = c - a;
|
|
|
|
let dy = d - b;
|
|
|
|
let m = dx * dx + dy * dy;
|
|
|
|
if m > l {
|
|
|
|
l = m;
|
|
|
|
ret = (*c, *d);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.pixels.remove(&ret).unwrap()
|
|
|
|
}
|
2025-07-01 12:26:35 -04:00
|
|
|
pub fn new(x: f32, y: f32) -> Self {
|
|
|
|
let mut pixels = FxHashMap::with_capacity_and_hasher(SIZE * SIZE, FxBuildHasher);
|
|
|
|
for i in 0..SIZE * SIZE {
|
|
|
|
let a = (i / SIZE) as f32 - SIZE as f32 / 2.0 + 0.5;
|
|
|
|
let b = (i % SIZE) as f32 - SIZE as f32 / 2.0 + 0.5;
|
|
|
|
let p = Pixel::new(Pos::new(x + a, y + b));
|
|
|
|
pixels.insert((p.pos.x.floor() as isize, p.pos.y.floor() as isize), p);
|
|
|
|
}
|
2025-06-27 14:11:41 -04:00
|
|
|
Blob {
|
|
|
|
pos: Pos::new(x, y),
|
2025-07-01 12:26:35 -04:00
|
|
|
pixels,
|
2025-06-27 14:11:41 -04:00
|
|
|
}
|
2025-06-27 10:26:09 -04:00
|
|
|
}
|
2025-07-02 11:20:38 -04:00
|
|
|
pub fn run(&mut self, c: (isize, isize), mut p: Pixel, theta: f32) -> bool {
|
2025-07-01 17:44:36 -04:00
|
|
|
if p.mutated {
|
|
|
|
self.pixels.insert(c, p);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
p.mutated = true;
|
2025-07-02 11:20:38 -04:00
|
|
|
let dx = self.pos.x - p.pos.x;
|
|
|
|
let dy = self.pos.y - p.pos.y;
|
|
|
|
let dist = dy.hypot(dx).max(0.1);
|
|
|
|
let phi = (3.0 * (dy.atan2(dx) - theta).abs() / PI + 1.0).min(3.0);
|
|
|
|
let mag = 512.0 * phi;
|
|
|
|
let ax = mag * dx / dist;
|
|
|
|
let ay = mag * dy / dist;
|
|
|
|
p.velocity.x += ax / 60.0;
|
|
|
|
p.velocity.y += ay / 60.0;
|
2025-07-01 17:44:36 -04:00
|
|
|
let damping = 0.9;
|
|
|
|
p.velocity.x *= damping;
|
|
|
|
p.velocity.y *= damping;
|
|
|
|
if let Some(n) = p.stop.as_mut() {
|
|
|
|
let t = p.acceleration.y.atan2(p.acceleration.x) + DIRECTIONS[*n / 2];
|
|
|
|
p.pos.x = p.pos.x.floor() + 0.5 + t.cos() * if n.is_multiple_of(2) { 1.0 } else { 1.5 };
|
|
|
|
p.pos.y = p.pos.y.floor() + 0.5 + t.sin() * if n.is_multiple_of(2) { 1.0 } else { 1.5 };
|
|
|
|
let mut m = (p.pos.x.floor() as isize, p.pos.y.floor() as isize);
|
|
|
|
let mut k = *n;
|
|
|
|
if self.pixels.contains_key(&m) {
|
|
|
|
loop {
|
|
|
|
if k.div_ceil(2) == DIRECTIONS.len() {
|
|
|
|
k = 0;
|
|
|
|
} else {
|
|
|
|
k += 1;
|
|
|
|
}
|
|
|
|
if k == *n {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
let t = p.acceleration.y.atan2(p.acceleration.x) + DIRECTIONS[*n / 2];
|
|
|
|
let x = p.pos.x.floor()
|
|
|
|
+ 0.5
|
|
|
|
+ t.cos() * if n.is_multiple_of(2) { 1.0 } else { 1.5 };
|
|
|
|
let y = p.pos.y.floor()
|
|
|
|
+ 0.5
|
|
|
|
+ t.sin() * if n.is_multiple_of(2) { 1.0 } else { 1.5 };
|
|
|
|
m = (x.floor() as isize, y.floor() as isize);
|
|
|
|
if !self.pixels.contains_key(&m) {
|
|
|
|
p.pos.x = x;
|
|
|
|
p.pos.y = y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if n.div_ceil(2) == DIRECTIONS.len() {
|
|
|
|
*n = 0;
|
|
|
|
} else {
|
|
|
|
*n += 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p.pos.x += p.velocity.x / 60.0;
|
|
|
|
p.pos.y += p.velocity.y / 60.0;
|
|
|
|
}
|
|
|
|
let n = (p.pos.x.floor() as isize, p.pos.y.floor() as isize);
|
|
|
|
if c != n {
|
|
|
|
if let Some(b) = self.pixels.remove(&n) {
|
2025-07-02 11:20:38 -04:00
|
|
|
if self.run(n, b, theta) && !self.pixels.contains_key(&n) {
|
2025-07-01 17:44:36 -04:00
|
|
|
p.stop = None;
|
|
|
|
self.pixels.insert(n, p);
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
p.pos.x = c.0 as f32 + 0.5;
|
|
|
|
p.pos.y = c.1 as f32 + 0.5;
|
|
|
|
if p.stop.is_none() {
|
|
|
|
p.stop = Some(0)
|
|
|
|
}
|
|
|
|
self.pixels.insert(c, p);
|
|
|
|
false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p.stop = None;
|
|
|
|
self.pixels.insert(n, p);
|
|
|
|
true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.pixels.insert(c, p);
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2025-06-27 10:26:09 -04:00
|
|
|
}
|