mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
refactor a bunch of things to get ready for rust world sync
This commit is contained in:
parent
4ff165b815
commit
067570672d
32 changed files with 540 additions and 3126 deletions
24
blob_guy/Cargo.lock
generated
24
blob_guy/Cargo.lock
generated
|
@ -675,9 +675,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
@ -1301,6 +1301,15 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.0.0"
|
||||
|
@ -1497,6 +1506,12 @@ version = "3.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
|
@ -1578,9 +1593,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
|||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.5"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -1683,6 +1698,7 @@ version = "1.6.0"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"eyre",
|
||||
"iced-x86",
|
||||
"libloading",
|
||||
"noita_api_macro",
|
||||
"object",
|
||||
|
|
6
ewext/Cargo.lock
generated
6
ewext/Cargo.lock
generated
|
@ -94,9 +94,9 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
|||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
@ -139,7 +139,6 @@ dependencies = [
|
|||
"backtrace",
|
||||
"bimap",
|
||||
"eyre",
|
||||
"iced-x86",
|
||||
"libloading",
|
||||
"noita_api",
|
||||
"rand",
|
||||
|
@ -261,6 +260,7 @@ version = "1.6.0"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"eyre",
|
||||
"iced-x86",
|
||||
"libloading",
|
||||
"noita_api_macro",
|
||||
"object 0.37.1",
|
||||
|
|
|
@ -18,7 +18,6 @@ opt-level = 3
|
|||
|
||||
[dependencies]
|
||||
backtrace = "0.3.74"
|
||||
iced-x86 = "1.21.0"
|
||||
eyre = "0.6.12"
|
||||
noita_api = {path = "../noita_api"}
|
||||
shared = {path = "../shared"}
|
||||
|
|
230
ewext/src/lib.rs
230
ewext/src/lib.rs
|
@ -2,43 +2,51 @@
|
|||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _unwind_resume() {}
|
||||
|
||||
use addr_grabber::{grab_addrs, grabbed_fns, grabbed_globals};
|
||||
use bimap::BiHashMap;
|
||||
use eyre::{Context, OptionExt, bail};
|
||||
use modules::{Module, ModuleCtx, entity_sync::EntitySync};
|
||||
use net::NetManager;
|
||||
use noita::{ParticleWorldState, ntypes::Entity, pixel::NoitaPixelRun};
|
||||
use noita_api::add_lua_fn;
|
||||
use noita_api::addr_grabber::{grab_addrs, grabbed_fns, grabbed_globals};
|
||||
use noita_api::noita::types::Entity;
|
||||
use noita_api::noita::world::ParticleWorldState;
|
||||
use noita_api::{
|
||||
DamageModelComponent, EntityID, VariableStorageComponent,
|
||||
lua::{
|
||||
LUA, LuaGetValue, LuaState, RawString, ValuesOnStack,
|
||||
LUA, LuaGetValue, LuaState, RawString,
|
||||
lua_bindings::{LUA_REGISTRYINDEX, lua_State},
|
||||
},
|
||||
};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use shared::des::{Gid, RemoteDes};
|
||||
use shared::{Destination, NoitaInbound, NoitaOutbound, PeerId, SpawnOnce, WorldPos};
|
||||
#[cfg(target_arch = "x86")]
|
||||
use std::arch::asm;
|
||||
use std::array::IntoIter;
|
||||
use std::backtrace::Backtrace;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::{
|
||||
arch::asm,
|
||||
borrow::Cow,
|
||||
cell::{LazyCell, RefCell},
|
||||
ffi::{c_int, c_void},
|
||||
ffi::c_int,
|
||||
sync::{LazyLock, Mutex, OnceLock, TryLockError},
|
||||
time::Instant,
|
||||
};
|
||||
use std::{num::NonZero, sync::MutexGuard};
|
||||
mod addr_grabber;
|
||||
mod modules;
|
||||
mod net;
|
||||
pub mod noita;
|
||||
|
||||
thread_local! {
|
||||
static STATE: LazyCell<RefCell<ExtState>> = LazyCell::new(|| {
|
||||
#[cfg(debug_assertions)]
|
||||
println!("Initializing ExtState");
|
||||
ExtState::default().into()
|
||||
ExtState {
|
||||
modules: Default::default(),
|
||||
player_entity_map: Default::default(),
|
||||
fps_by_player: Default::default(),
|
||||
dont_spawn: Default::default(),
|
||||
cam_pos: Default::default(),
|
||||
}.into()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -92,14 +100,33 @@ impl Drop for TimeTracker {
|
|||
}
|
||||
}*/
|
||||
|
||||
#[derive(Default)]
|
||||
struct Modules {
|
||||
entity_sync: Option<EntitySync>,
|
||||
pub struct WorldSync {
|
||||
pub particle_world_state: MaybeUninit<ParticleWorldState>,
|
||||
pub world_num: u8,
|
||||
}
|
||||
impl Default for WorldSync {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
particle_world_state: MaybeUninit::uninit(),
|
||||
world_num: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Modules {
|
||||
entity_sync: EntitySync,
|
||||
world: WorldSync,
|
||||
}
|
||||
|
||||
impl Modules {
|
||||
fn iter_mut(&mut self) -> IntoIter<&mut dyn Module, 2> {
|
||||
let modules: [&mut dyn Module; 2] = [&mut self.entity_sync, &mut self.world];
|
||||
modules.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtState {
|
||||
particle_world_state: Option<ParticleWorldState>,
|
||||
modules: Modules,
|
||||
player_entity_map: BiHashMap<PeerId, EntityID>,
|
||||
fps_by_player: FxHashMap<PeerId, u8>,
|
||||
|
@ -118,46 +145,20 @@ impl ExtState {
|
|||
}
|
||||
}
|
||||
|
||||
fn init_particle_world_state(lua: LuaState) {
|
||||
#[cfg(debug_assertions)]
|
||||
println!("\nInitializing particle world state");
|
||||
let world_pointer = lua.to_integer(1);
|
||||
let chunk_map_pointer = lua.to_integer(2);
|
||||
let material_list_pointer = lua.to_integer(3);
|
||||
#[cfg(debug_assertions)]
|
||||
println!("pws stuff: {world_pointer:?} {chunk_map_pointer:?}");
|
||||
|
||||
fn init_particle_world_state(lua: LuaState) -> eyre::Result<()> {
|
||||
STATE.with(|state| {
|
||||
state.borrow_mut().particle_world_state = Some(ParticleWorldState {
|
||||
_world_ptr: world_pointer as *mut c_void,
|
||||
chunk_map_ptr: chunk_map_pointer as *mut c_void,
|
||||
material_list_ptr: material_list_pointer as _,
|
||||
runner: Default::default(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn encode_area(lua: LuaState) -> ValuesOnStack {
|
||||
let lua = lua.raw();
|
||||
let start_x = unsafe { LUA.lua_tointeger(lua, 1) } as i32;
|
||||
let start_y = unsafe { LUA.lua_tointeger(lua, 2) } as i32;
|
||||
let end_x = unsafe { LUA.lua_tointeger(lua, 3) } as i32;
|
||||
let end_y = unsafe { LUA.lua_tointeger(lua, 4) } as i32;
|
||||
let encoded_buffer = unsafe { LUA.lua_tointeger(lua, 5) } as *mut NoitaPixelRun;
|
||||
|
||||
STATE.with(|state| {
|
||||
let mut state = state.borrow_mut();
|
||||
let pws = state.particle_world_state.as_mut().unwrap();
|
||||
let runs = unsafe { pws.encode_area(start_x, start_y, end_x, end_y, encoded_buffer) };
|
||||
unsafe { LUA.lua_pushinteger(lua, runs as isize) };
|
||||
});
|
||||
ValuesOnStack(1)
|
||||
let world = &mut state.borrow_mut().modules.world;
|
||||
world.particle_world_state = MaybeUninit::new(ParticleWorldState::new()?);
|
||||
world.world_num = lua.to_integer(1) as u8;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ephemerial(entity_id: u32) -> eyre::Result<()> {
|
||||
unsafe {
|
||||
let entity_manager = grabbed_globals().entity_manager.read();
|
||||
let mut entity: *mut Entity;
|
||||
let entity: *mut Entity;
|
||||
#[cfg(target_arch = "x86")]
|
||||
asm!(
|
||||
"mov ecx, {entity_manager}",
|
||||
"push {entity_id:e}",
|
||||
|
@ -169,10 +170,16 @@ pub fn ephemerial(entity_id: u32) -> eyre::Result<()> {
|
|||
out("ecx") _,
|
||||
out("eax") entity,
|
||||
);
|
||||
if entity.is_null() {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
{
|
||||
std::hint::black_box((entity_manager, grabbed_fns().get_entity));
|
||||
entity = Default::default();
|
||||
}
|
||||
if let Some(entity) = entity.as_mut() {
|
||||
entity.filename_index = 0;
|
||||
} else {
|
||||
bail!("Entity {entity_id} not found");
|
||||
}
|
||||
entity.cast::<c_void>().offset(0x8).cast::<u32>().write(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -229,9 +236,7 @@ fn netmanager_recv(_lua: LuaState) -> eyre::Result<Option<RawString>> {
|
|||
NoitaInbound::Ready { .. } => bail!("Unexpected Ready message"),
|
||||
NoitaInbound::ProxyToDes(proxy_to_des) => ExtState::with_global(|state| {
|
||||
let _lock = IN_MODULE_LOCK.lock().unwrap();
|
||||
if let Some(entity_sync) = &mut state.modules.entity_sync
|
||||
&& let Err(e) = entity_sync.handle_proxytodes(proxy_to_des)
|
||||
{
|
||||
if let Err(e) = state.modules.entity_sync.handle_proxytodes(proxy_to_des) {
|
||||
let _ = print_error(e);
|
||||
}
|
||||
})?,
|
||||
|
@ -240,22 +245,26 @@ fn netmanager_recv(_lua: LuaState) -> eyre::Result<Option<RawString>> {
|
|||
message: shared::RemoteMessage::RemoteDes(remote_des),
|
||||
} => ExtState::with_global(|state| {
|
||||
let _lock = IN_MODULE_LOCK.lock().unwrap();
|
||||
if let Some(entity_sync) = &mut state.modules.entity_sync {
|
||||
match entity_sync.handle_remotedes(
|
||||
source,
|
||||
remote_des,
|
||||
netmanager,
|
||||
&state.player_entity_map,
|
||||
&mut state.dont_spawn,
|
||||
&mut state.cam_pos,
|
||||
) {
|
||||
Ok(()) => {}
|
||||
Err(s) => {
|
||||
let _ = print_error(s);
|
||||
}
|
||||
match state.modules.entity_sync.handle_remotedes(
|
||||
source,
|
||||
remote_des,
|
||||
netmanager,
|
||||
&state.player_entity_map,
|
||||
&mut state.dont_spawn,
|
||||
&mut state.cam_pos,
|
||||
) {
|
||||
Ok(()) => {}
|
||||
Err(s) => {
|
||||
let _ = print_error(s);
|
||||
}
|
||||
}
|
||||
})?,
|
||||
NoitaInbound::ProxyToWorldSync(msg) => ExtState::with_global(|state| {
|
||||
let _lock = IN_MODULE_LOCK.lock().unwrap();
|
||||
if let Err(e) = state.modules.world.handle_remote(msg) {
|
||||
let _ = print_error(e);
|
||||
}
|
||||
})?,
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
|
@ -294,11 +303,11 @@ fn on_world_initialized(lua: LuaState) {
|
|||
std::thread::current().id()
|
||||
);
|
||||
grab_addrs(lua);
|
||||
|
||||
STATE.with(|state| {
|
||||
let modules = &mut state.borrow_mut().modules;
|
||||
modules.entity_sync = Some(EntitySync::default());
|
||||
ExtState::with_global(|state| {
|
||||
state.modules.world.particle_world_state =
|
||||
MaybeUninit::new(ParticleWorldState::new().unwrap());
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
static IN_MODULE_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
@ -318,8 +327,8 @@ fn with_every_module(
|
|||
camera_pos: &mut state.cam_pos,
|
||||
};
|
||||
let mut errs = Vec::new();
|
||||
for module in state.modules.entity_sync.iter_mut() {
|
||||
if let Err(e) = f(&mut ctx, module as &mut dyn Module) {
|
||||
for module in state.modules.iter_mut() {
|
||||
if let Err(e) = f(&mut ctx, module) {
|
||||
errs.push(e);
|
||||
}
|
||||
}
|
||||
|
@ -424,17 +433,6 @@ fn probe(_lua: LuaState) {
|
|||
});
|
||||
}
|
||||
|
||||
fn __gc(_lua: LuaState) {
|
||||
#[cfg(debug_assertions)]
|
||||
println!(
|
||||
"ewext collected in thread {:?}",
|
||||
std::thread::current().id()
|
||||
);
|
||||
NETMANAGER.lock().unwrap().take();
|
||||
// TODO this doesn't actually work because it's a thread local
|
||||
STATE.with(|state| state.take());
|
||||
}
|
||||
|
||||
pub(crate) fn print_error(error: eyre::Report) -> eyre::Result<()> {
|
||||
let lua = LuaState::current()?;
|
||||
lua.get_global(c"EwextPrintError");
|
||||
|
@ -476,12 +474,10 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
// Detect module unload. Adapted from NoitaPatcher.
|
||||
LUA.lua_newuserdata(lua, 0);
|
||||
LUA.lua_createtable(lua, 0, 0);
|
||||
add_lua_fn!(__gc);
|
||||
LUA.lua_setmetatable(lua, -2);
|
||||
LUA.lua_setfield(lua, LUA_REGISTRYINDEX, c"luaclose_ewext".as_ptr());
|
||||
|
||||
add_lua_fn!(init_particle_world_state);
|
||||
add_lua_fn!(encode_area);
|
||||
add_lua_fn!(make_ephemerial);
|
||||
add_lua_fn!(on_world_initialized);
|
||||
add_lua_fn!(test_fn);
|
||||
|
@ -512,12 +508,7 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
peer_n = peer_n.overflowing_add(rng).0
|
||||
}
|
||||
let gid = peer_n.overflowing_mul(rng).0;
|
||||
let entity_sync = state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.ok_or_eyre("No entity sync module loaded")?;
|
||||
entity_sync.sync_projectile(
|
||||
state.modules.entity_sync.sync_projectile(
|
||||
EntityID(NonZero::try_from(entity)?),
|
||||
Gid(gid),
|
||||
peer,
|
||||
|
@ -529,12 +520,10 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
|
||||
fn des_item_thrown(lua: LuaState) -> eyre::Result<()> {
|
||||
ExtState::with_global(|state| {
|
||||
let entity_sync = state
|
||||
state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.ok_or_eyre("No entity sync module loaded")?;
|
||||
entity_sync.cross_item_thrown(LuaGetValue::get(lua, -1)?)?;
|
||||
.cross_item_thrown(LuaGetValue::get(lua, -1)?)?;
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
|
@ -542,11 +531,6 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
|
||||
fn des_death_notify(lua: LuaState) -> eyre::Result<()> {
|
||||
ExtState::with_global(|state| {
|
||||
let entity_sync = state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.ok_or_eyre("No entity sync module loaded")?;
|
||||
let entity_killed = EntityID::try_from(lua.to_integer(1))
|
||||
.wrap_err("Expected to have a valid entity_killed")?;
|
||||
let wait_on_kill = lua.to_bool(2);
|
||||
|
@ -557,7 +541,7 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
.wrap_err("Expected to have a valid filepath")?;
|
||||
let entity_responsible = EntityID::try_from(lua.to_integer(6)).ok();
|
||||
let pos = WorldPos::from_f64(x, y);
|
||||
entity_sync.cross_death_notify(
|
||||
state.modules.entity_sync.cross_death_notify(
|
||||
entity_killed,
|
||||
wait_on_kill,
|
||||
pos,
|
||||
|
@ -571,15 +555,10 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
|
||||
fn notrack(lua: LuaState) -> eyre::Result<()> {
|
||||
ExtState::with_global(|state| {
|
||||
let entity_sync = state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.ok_or_eyre("No entity sync module loaded")?;
|
||||
let entity_killed: Option<EntityID> = LuaGetValue::get(lua, -1)?;
|
||||
let entity_killed =
|
||||
entity_killed.ok_or_eyre("Expected to have a valid entity_killed")?;
|
||||
entity_sync.notrack_entity(entity_killed);
|
||||
state.modules.entity_sync.notrack_entity(entity_killed);
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
|
@ -587,15 +566,10 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
|
||||
fn track(lua: LuaState) -> eyre::Result<()> {
|
||||
ExtState::with_global(|state| {
|
||||
let entity_sync = state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.ok_or_eyre("No entity sync module loaded")?;
|
||||
let entity_killed: Option<EntityID> = LuaGetValue::get(lua, -1)?;
|
||||
let entity_killed =
|
||||
entity_killed.ok_or_eyre("Expected to have a valid entity_killed")?;
|
||||
entity_sync.track_entity(entity_killed);
|
||||
state.modules.entity_sync.track_entity(entity_killed);
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
|
@ -630,12 +604,7 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
fn find_by_gid(lua: LuaState) -> eyre::Result<Option<EntityID>> {
|
||||
ExtState::with_global(|state| {
|
||||
let gid = lua.to_string(1)?.parse::<u64>()?;
|
||||
let entity_sync = state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.ok_or_eyre("No entity sync module loaded")?;
|
||||
Ok(entity_sync.find_by_gid(Gid(gid)))
|
||||
Ok(state.modules.entity_sync.find_by_gid(Gid(gid)))
|
||||
})?
|
||||
}
|
||||
add_lua_fn!(find_by_gid);
|
||||
|
@ -649,11 +618,6 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
let file = lua.to_string(5)?;
|
||||
let gid = Gid(lua.to_string(6)?.parse::<u64>()?);
|
||||
let is_mine = lua.to_bool(7);
|
||||
let entity_sync = state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.ok_or_eyre("No entity sync module loaded")?;
|
||||
let mut temp = try_lock_netmanager()?;
|
||||
let net = temp.as_mut().ok_or_eyre("Netmanager not available")?;
|
||||
if is_mine {
|
||||
|
@ -669,7 +633,11 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
ry,
|
||||
)),
|
||||
})?;
|
||||
for (has_interest, peer) in entity_sync.iter_peers(&state.player_entity_map) {
|
||||
for (has_interest, peer) in state
|
||||
.modules
|
||||
.entity_sync
|
||||
.iter_peers(&state.player_entity_map)
|
||||
{
|
||||
if has_interest {
|
||||
net.send(&NoitaOutbound::RemoteMessage {
|
||||
reliable: true,
|
||||
|
@ -694,7 +662,7 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
})?;
|
||||
}
|
||||
}
|
||||
} else if let Some(peer) = entity_sync.find_peer_by_gid(gid) {
|
||||
} else if let Some(peer) = state.modules.entity_sync.find_peer_by_gid(gid) {
|
||||
net.send(&NoitaOutbound::RemoteMessage {
|
||||
reliable: true,
|
||||
destination: Destination::Peer(*peer),
|
||||
|
@ -733,24 +701,14 @@ pub unsafe extern "C" fn luaopen_ewext(lua: *mut lua_State) -> c_int {
|
|||
|
||||
fn set_log(lua: LuaState) -> eyre::Result<()> {
|
||||
ExtState::with_global(|state| {
|
||||
state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_perf(lua.to_bool(1));
|
||||
state.modules.entity_sync.set_perf(lua.to_bool(1));
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
add_lua_fn!(set_log);
|
||||
fn set_cache(lua: LuaState) -> eyre::Result<()> {
|
||||
ExtState::with_global(|state| {
|
||||
state
|
||||
.modules
|
||||
.entity_sync
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_cache(lua.to_bool(1));
|
||||
state.modules.entity_sync.set_cache(lua.to_bool(1));
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ use crate::net::NetManager;
|
|||
|
||||
pub(crate) mod entity_sync;
|
||||
|
||||
pub(crate) mod world_sync;
|
||||
|
||||
pub(crate) struct ModuleCtx<'a> {
|
||||
pub(crate) net: &'a mut NetManager,
|
||||
pub(crate) player_map: &'a mut BiHashMap<PeerId, EntityID>,
|
||||
|
|
42
ewext/src/modules/world_sync.rs
Normal file
42
ewext/src/modules/world_sync.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use crate::WorldSync;
|
||||
use crate::modules::{Module, ModuleCtx};
|
||||
use noita_api::noita::world::ParticleWorldState;
|
||||
use shared::world_sync::ProxyToWorldSync;
|
||||
impl Module for WorldSync {
|
||||
fn on_world_update(&mut self, _ctx: &mut ModuleCtx) -> eyre::Result<()> {
|
||||
std::hint::black_box(unsafe {
|
||||
self.particle_world_state.assume_init_ref().encode_world()
|
||||
})?;
|
||||
std::hint::black_box(unsafe {
|
||||
self.particle_world_state.assume_init_ref().decode_world()
|
||||
})?;
|
||||
//TODO
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl WorldSync {
|
||||
pub fn handle_remote(&mut self, msg: ProxyToWorldSync) -> eyre::Result<()> {
|
||||
match msg {
|
||||
ProxyToWorldSync::Updates(updates) => {
|
||||
for _chunk in updates {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
trait WorldData {
|
||||
unsafe fn encode_world(&self) -> eyre::Result<()>;
|
||||
unsafe fn decode_world(&self) -> eyre::Result<()>;
|
||||
}
|
||||
impl WorldData for ParticleWorldState {
|
||||
unsafe fn encode_world(&self) -> eyre::Result<()> {
|
||||
//TODO
|
||||
Ok(())
|
||||
}
|
||||
unsafe fn decode_world(&self) -> eyre::Result<()> {
|
||||
//TODO
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
use std::{ffi::c_void, mem};
|
||||
|
||||
pub(crate) mod ntypes;
|
||||
pub(crate) mod pixel;
|
||||
|
||||
pub(crate) struct ParticleWorldState {
|
||||
pub(crate) _world_ptr: *mut c_void,
|
||||
pub(crate) chunk_map_ptr: *mut c_void,
|
||||
pub(crate) material_list_ptr: *const c_void,
|
||||
|
||||
pub(crate) runner: pixel::PixelRunner<pixel::RawPixel>,
|
||||
}
|
||||
|
||||
impl ParticleWorldState {
|
||||
fn get_cell_raw(&self, x: i32, y: i32) -> Option<&ntypes::Cell> {
|
||||
let x = x as isize;
|
||||
let y = y as isize;
|
||||
let chunk_index = (((((y) >> 9) - 256) & 511) * 512 + ((((x) >> 9) - 256) & 511)) * 4;
|
||||
// Deref 1/3
|
||||
let chunk_arr = unsafe { self.chunk_map_ptr.offset(8).cast::<*const c_void>().read() };
|
||||
// Deref 2/3
|
||||
let chunk = unsafe { chunk_arr.offset(chunk_index).cast::<*const c_void>().read() };
|
||||
if chunk.is_null() {
|
||||
return None;
|
||||
}
|
||||
// Deref 3/3
|
||||
let pixel_array = unsafe { chunk.cast::<*const c_void>().read() };
|
||||
let pixel = unsafe { pixel_array.offset((((y & 511) << 9) | x & 511) * 4) };
|
||||
if pixel.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe { pixel.cast::<*const ntypes::Cell>().read().as_ref() }
|
||||
}
|
||||
|
||||
fn get_cell_material_id(&self, cell: &ntypes::Cell) -> u16 {
|
||||
let mat_ptr = cell.material_ptr();
|
||||
let offset = unsafe { mat_ptr.cast::<c_void>().offset_from(self.material_list_ptr) };
|
||||
(offset / ntypes::CELLDATA_SIZE) as u16
|
||||
}
|
||||
|
||||
fn get_cell_type(&self, cell: &ntypes::Cell) -> Option<ntypes::CellType> {
|
||||
unsafe { Some(cell.material_ptr().as_ref()?.cell_type) }
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn encode_area(
|
||||
&mut self,
|
||||
start_x: i32,
|
||||
start_y: i32,
|
||||
end_x: i32,
|
||||
end_y: i32,
|
||||
mut pixel_runs: *mut pixel::NoitaPixelRun,
|
||||
) -> usize {
|
||||
// Allow compiler to generate better code.
|
||||
assert_eq!(start_x % 128, 0);
|
||||
assert_eq!(start_y % 128, 0);
|
||||
assert!((end_x - start_x) <= 128);
|
||||
assert!((end_y - start_y) <= 128);
|
||||
|
||||
for y in start_y..end_y {
|
||||
for x in start_x..end_x {
|
||||
let mut raw_pixel = pixel::RawPixel {
|
||||
material: 0,
|
||||
flags: 0,
|
||||
};
|
||||
let cell = self.get_cell_raw(x, y);
|
||||
if let Some(cell) = cell {
|
||||
let cell_type = self.get_cell_type(cell).unwrap_or(ntypes::CellType::None);
|
||||
match cell_type {
|
||||
ntypes::CellType::None => {}
|
||||
// Nobody knows how box2d pixels work.
|
||||
ntypes::CellType::Solid => {}
|
||||
ntypes::CellType::Liquid => {
|
||||
raw_pixel.material = self.get_cell_material_id(cell);
|
||||
let cell: &ntypes::LiquidCell = unsafe { mem::transmute(cell) };
|
||||
raw_pixel.flags = cell.is_static as u8;
|
||||
}
|
||||
ntypes::CellType::Gas | ntypes::CellType::Fire => {
|
||||
raw_pixel.material = self.get_cell_material_id(cell);
|
||||
}
|
||||
// ???
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.runner.put_pixel(raw_pixel);
|
||||
}
|
||||
}
|
||||
|
||||
let built_runner = self.runner.build();
|
||||
let runs = built_runner.len();
|
||||
for run in built_runner {
|
||||
let noita_pixel_run = unsafe { pixel_runs.as_mut().unwrap() };
|
||||
noita_pixel_run.length = (run.length - 1) as u16;
|
||||
noita_pixel_run.material = run.data.material;
|
||||
noita_pixel_run.flags = run.data.flags;
|
||||
pixel_runs = unsafe { pixel_runs.offset(1) };
|
||||
}
|
||||
self.runner.clear();
|
||||
runs
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
// Type defs borrowed from NoitaPatcher.
|
||||
|
||||
use std::ffi::{c_char, c_void};
|
||||
|
||||
pub(crate) const CELLDATA_SIZE: isize = 0x290;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct StdString {
|
||||
buffer: *const i8,
|
||||
sso_buffer: [i8; 12],
|
||||
size: usize,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[expect(dead_code)]
|
||||
pub(crate) enum CellType {
|
||||
None = 0,
|
||||
Liquid = 1,
|
||||
Gas = 2,
|
||||
Solid = 3,
|
||||
Fire = 4,
|
||||
Invalid = 4294967295,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct CellData {
|
||||
name: StdString,
|
||||
ui_name: StdString,
|
||||
material_type: i32,
|
||||
id_2: i32,
|
||||
pub(crate) cell_type: CellType,
|
||||
// Has a bunch of other fields that aren't that relevant.
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct CellVTable {}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct Cell {
|
||||
pub(crate) vtable: *const CellVTable,
|
||||
|
||||
hp: i32,
|
||||
unknown1: [u8; 8],
|
||||
is_burning: bool,
|
||||
unknown2: [u8; 3],
|
||||
material_ptr: *const CellData,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct LiquidCell {
|
||||
cell: Cell,
|
||||
x: i32,
|
||||
y: i32,
|
||||
unknown1: c_char,
|
||||
unknown2: c_char,
|
||||
pub(crate) is_static: bool,
|
||||
// Has a bunch of other fields that aren't that relevant.
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
pub(crate) fn material_ptr(&self) -> *const CellData {
|
||||
self.material_ptr
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct Entity {
|
||||
_unknown0: [u8; 8],
|
||||
_filename_index: u32,
|
||||
// More stuff, not that relevant currently.
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct EntityManager {
|
||||
_fld: c_void,
|
||||
// Unknown
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct ThiscallFn(c_void);
|
|
@ -1,78 +0,0 @@
|
|||
#[repr(C, packed)]
|
||||
pub(crate) struct NoitaPixelRun {
|
||||
pub(crate) length: u16,
|
||||
pub(crate) material: u16,
|
||||
pub(crate) flags: u8,
|
||||
}
|
||||
|
||||
/// Copied from proxy.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub(crate) struct RawPixel {
|
||||
pub material: u16,
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
/// Copied from proxy.
|
||||
/// Stores a run of pixels.
|
||||
/// Not specific to Noita side - length is an actual length
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PixelRun<Pixel> {
|
||||
pub length: u32,
|
||||
pub data: Pixel,
|
||||
}
|
||||
|
||||
/// Copied from proxy.
|
||||
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
||||
pub(crate) struct PixelRunner<Pixel> {
|
||||
pub(crate) current_pixel: Option<Pixel>,
|
||||
pub(crate) current_run_len: u32,
|
||||
pub(crate) runs: Vec<PixelRun<Pixel>>,
|
||||
}
|
||||
|
||||
impl<Pixel: Eq + Copy> Default for PixelRunner<Pixel> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Pixel: Eq + Copy> PixelRunner<Pixel> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
current_pixel: None,
|
||||
current_run_len: 0,
|
||||
runs: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn put_pixel(&mut self, pixel: Pixel) {
|
||||
if let Some(current) = self.current_pixel {
|
||||
if pixel != current {
|
||||
self.runs.push(PixelRun {
|
||||
length: self.current_run_len,
|
||||
data: current,
|
||||
});
|
||||
self.current_pixel = Some(pixel);
|
||||
self.current_run_len = 1;
|
||||
} else {
|
||||
self.current_run_len += 1;
|
||||
}
|
||||
} else {
|
||||
self.current_pixel = Some(pixel);
|
||||
self.current_run_len = 1;
|
||||
}
|
||||
}
|
||||
pub(crate) fn build(&mut self) -> &[PixelRun<Pixel>] {
|
||||
if self.current_run_len > 0 {
|
||||
self.runs.push(PixelRun {
|
||||
length: self.current_run_len,
|
||||
data: self.current_pixel.expect("has current pixel"),
|
||||
});
|
||||
}
|
||||
&mut self.runs
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.current_pixel = None;
|
||||
self.current_run_len = 0;
|
||||
self.runs.clear();
|
||||
}
|
||||
}
|
16
noita-proxy/Cargo.lock
generated
16
noita-proxy/Cargo.lock
generated
|
@ -890,9 +890,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
@ -2625,9 +2625,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
|||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.5"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -4041,9 +4041,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.51"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f"
|
||||
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
@ -6410,9 +6410,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "winresource"
|
||||
version = "0.1.22"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a179ac8923651ff1d15efbee760b4dd3679fd85fa5a8b2bb1109b7248f80e30f"
|
||||
checksum = "edcacf11b6f48dd21b9ba002f991bdd5de29b2da8cc2800412f4b80f677e4957"
|
||||
dependencies = [
|
||||
"toml",
|
||||
"version_check",
|
||||
|
|
|
@ -55,13 +55,13 @@ use util::{args::Args, steam_helper::LobbyExtraData};
|
|||
mod bookkeeping;
|
||||
use crate::net::messages::NetMsg;
|
||||
use crate::net::omni::OmniPeerId;
|
||||
use crate::net::world::world_model::ChunkCoord;
|
||||
use crate::player_cosmetics::{
|
||||
display_player_skin, get_player_skin, player_path, player_select_current_color_slot,
|
||||
player_skin_display_color_picker, shift_hue,
|
||||
};
|
||||
pub use bookkeeping::{mod_manager, releases, self_update};
|
||||
use shared::WorldPos;
|
||||
use shared::world_sync::ChunkCoord;
|
||||
mod lobby_code;
|
||||
pub mod net;
|
||||
mod player_cosmetics;
|
||||
|
@ -126,7 +126,7 @@ pub enum LocalHealthMode {
|
|||
#[serde(default)]
|
||||
pub struct GameSettings {
|
||||
seed: u64,
|
||||
world_num: u16,
|
||||
world_num: u8,
|
||||
debug_mode: Option<bool>,
|
||||
use_constant_seed: bool,
|
||||
duplicate: Option<bool>,
|
||||
|
@ -1660,7 +1660,7 @@ impl App {
|
|||
|
||||
fn connect_screen(&mut self, ctx: &Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if self.app_saved_state.times_started % 20 == 0 {
|
||||
if self.app_saved_state.times_started.is_multiple_of(20) {
|
||||
let image = egui::Image::new(egui::include_image!("../assets/longleg.png"))
|
||||
.texture_options(TextureOptions::NEAREST);
|
||||
image.paint_at(ui, ctx.screen_rect());
|
||||
|
|
|
@ -25,12 +25,12 @@ use std::{
|
|||
thread::{self, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use world::{NoitaWorldUpdate, WorldManager};
|
||||
use world::WorldManager;
|
||||
|
||||
use crate::lobby_code::LobbyKind;
|
||||
use crate::mod_manager::{ModmanagerSettings, get_mods};
|
||||
use crate::net::world::world_model::ChunkData;
|
||||
use crate::net::world::world_model::chunk::{Pixel, PixelFlags};
|
||||
use crate::net::world::world_model::{ChunkCoord, ChunkData};
|
||||
use crate::player_cosmetics::{PlayerPngDesc, create_player_png, get_player_skin};
|
||||
use crate::steam_helper::LobbyExtraData;
|
||||
use crate::{
|
||||
|
@ -38,6 +38,7 @@ use crate::{
|
|||
bookkeeping::save_state::{SaveState, SaveStateEntry},
|
||||
};
|
||||
use shared::des::ProxyToDes;
|
||||
use shared::world_sync::{ChunkCoord, ProxyToWorldSync};
|
||||
use tangled::Reliability;
|
||||
use tracing::{error, info, warn};
|
||||
mod audio;
|
||||
|
@ -535,12 +536,10 @@ impl NetManager {
|
|||
for msg in state.world.get_emitted_msgs() {
|
||||
self.do_message_request(msg)
|
||||
}
|
||||
state.world.update();
|
||||
|
||||
let updates = state.world.get_noita_updates();
|
||||
for update in updates {
|
||||
state.try_ms_write(&ws_encode_proxy_bin(0, &update));
|
||||
}
|
||||
let updates = state.world.update();
|
||||
state.try_ms_write(&NoitaInbound::ProxyToWorldSync(ProxyToWorldSync::Updates(
|
||||
updates,
|
||||
)));
|
||||
|
||||
if state.had_a_disconnect {
|
||||
self.broadcast(&NetMsg::NoitaDisconnected, Reliability::Reliable);
|
||||
|
@ -718,6 +717,10 @@ impl NetManager {
|
|||
sendm: &Sender<FxHashMap<u16, u32>>,
|
||||
) {
|
||||
match net_msg {
|
||||
NetMsg::ForwardWorldSyncToProxy(msg) => state.world.handle_noita_msg(src, msg),
|
||||
NetMsg::ForwardProxyToWorldSync(msg) => {
|
||||
state.try_ms_write(&NoitaInbound::ProxyToWorldSync(msg));
|
||||
}
|
||||
NetMsg::AudioData(data, global, tx, ty, vol) => {
|
||||
if !self.is_cess.load(Ordering::Relaxed) {
|
||||
let audio = self.audio.lock().unwrap().clone();
|
||||
|
@ -1144,8 +1147,6 @@ impl NetManager {
|
|||
},
|
||||
);
|
||||
}
|
||||
// Binary message to proxy
|
||||
3 => self.handle_bin_message_to_proxy(&raw_msg[1..], state),
|
||||
0 => {
|
||||
let flags = String::from_utf8_lossy(&raw_msg[1..]).into();
|
||||
let msg = NetMsg::Flags(flags);
|
||||
|
@ -1167,6 +1168,19 @@ impl NetManager {
|
|||
);
|
||||
}
|
||||
}
|
||||
NoitaOutbound::WorldSyncToProxy(world_sync_msg) => {
|
||||
if self.is_host() {
|
||||
state
|
||||
.world
|
||||
.handle_noita_msg(self.peer.my_id(), world_sync_msg)
|
||||
} else {
|
||||
self.send(
|
||||
self.peer.host_id(),
|
||||
&NetMsg::ForwardWorldSyncToProxy(world_sync_msg),
|
||||
Reliability::Reliable,
|
||||
);
|
||||
}
|
||||
}
|
||||
NoitaOutbound::RemoteMessage {
|
||||
reliable,
|
||||
destination,
|
||||
|
@ -1432,29 +1446,6 @@ impl NetManager {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_bin_message_to_proxy(&self, msg: &[u8], state: &mut NetInnerState) {
|
||||
let key = msg[0];
|
||||
let data = &msg[1..];
|
||||
match key {
|
||||
// world frame
|
||||
0 => {
|
||||
let update = NoitaWorldUpdate::load(data);
|
||||
state.world.add_update(update);
|
||||
}
|
||||
// world end
|
||||
1 => {
|
||||
let pos = data[1..]
|
||||
.split(|b| *b == b':')
|
||||
.map(|s| String::from_utf8_lossy(s).parse::<i32>().unwrap_or(0))
|
||||
.collect::<Vec<i32>>();
|
||||
state.world.add_end(data[0], &pos);
|
||||
}
|
||||
key => {
|
||||
error!("Unknown bin msg from mod: {:?}", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn end_run(&self, state: &mut NetInnerState) {
|
||||
self.init_settings.save_state.reset();
|
||||
{
|
||||
|
@ -1468,7 +1459,7 @@ impl NetManager {
|
|||
.modmanager_settings
|
||||
.get_progress()
|
||||
.unwrap_or_default();
|
||||
if settings.world_num == u16::MAX {
|
||||
if settings.world_num == u8::MAX {
|
||||
settings.world_num = 0
|
||||
} else {
|
||||
settings.world_num += 1
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use super::{omni::OmniPeerId, world::WorldNetMessage};
|
||||
use crate::net::world::world_model::{ChunkCoord, ChunkData};
|
||||
use crate::net::world::world_model::ChunkData;
|
||||
use crate::{GameSettings, player_cosmetics::PlayerPngDesc};
|
||||
use bitcode::{Decode, Encode};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use shared::world_sync::ChunkCoord;
|
||||
pub(crate) type Destination = shared::Destination<OmniPeerId>;
|
||||
|
||||
pub(crate) struct MessageRequest<T> {
|
||||
|
@ -27,7 +27,9 @@ pub(crate) enum NetMsg {
|
|||
PlayerColor(PlayerPngDesc, bool, Option<OmniPeerId>, String),
|
||||
RemoteMsg(shared::RemoteMessage),
|
||||
ForwardDesToProxy(shared::des::DesToProxy),
|
||||
ForwardWorldSyncToProxy(shared::world_sync::WorldSyncToProxy),
|
||||
ForwardProxyToDes(shared::des::ProxyToDes),
|
||||
ForwardProxyToWorldSync(shared::world_sync::ProxyToWorldSync),
|
||||
NoitaDisconnected,
|
||||
Flags(String),
|
||||
RespondFlagNormal(String, bool),
|
||||
|
|
|
@ -4,22 +4,19 @@ use rand::{Rng, rng};
|
|||
use rayon::iter::IntoParallelIterator;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::f32::consts::TAU;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::time::Duration;
|
||||
use std::{cmp, env, mem, thread};
|
||||
use std::{cmp, mem, thread};
|
||||
use tracing::{debug, info, warn};
|
||||
use wide::f32x8;
|
||||
use world_model::{
|
||||
CHUNK_SIZE, ChunkCoord, ChunkData, ChunkDelta, WorldModel,
|
||||
ChunkData, ChunkDelta, WorldModel,
|
||||
chunk::{Chunk, Pixel},
|
||||
};
|
||||
|
||||
pub use world_model::encoding::NoitaWorldUpdate;
|
||||
|
||||
use crate::bookkeeping::save_state::{SaveState, SaveStateEntry};
|
||||
|
||||
use super::{
|
||||
|
@ -30,12 +27,6 @@ use super::{
|
|||
|
||||
pub mod world_model;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum WorldUpdateKind {
|
||||
Update(NoitaWorldUpdate),
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Debug, Decode, Encode, Clone)]
|
||||
pub(crate) enum WorldNetMessage {
|
||||
// Authority request
|
||||
|
@ -75,13 +66,13 @@ pub(crate) enum WorldNetMessage {
|
|||
RelinquishAuthority {
|
||||
chunk: ChunkCoord,
|
||||
chunk_data: Option<ChunkData>,
|
||||
world_num: i32,
|
||||
world_num: u8,
|
||||
},
|
||||
// Ttell how to update a chunk storage
|
||||
// Tell how to update a chunk storage
|
||||
UpdateStorage {
|
||||
chunk: ChunkCoord,
|
||||
chunk_data: Option<ChunkData>,
|
||||
world_num: i32,
|
||||
world_num: u8,
|
||||
priority: Option<u8>,
|
||||
},
|
||||
// When listening
|
||||
|
@ -200,7 +191,7 @@ pub(crate) struct WorldManager {
|
|||
chunk_last_update: FxHashMap<ChunkCoord, u64>,
|
||||
/// Stores last priority we used for that chunk, in case transfer fails and we'll need to request authority normally.
|
||||
last_request_priority: FxHashMap<ChunkCoord, u8>,
|
||||
world_num: i32,
|
||||
world_num: u8,
|
||||
pub materials: FxHashMap<u16, (u32, u32, CellType, u32)>,
|
||||
is_storage_recent: FxHashSet<ChunkCoord>,
|
||||
explosion_pointer: FxHashMap<ChunkCoord, Vec<usize>>,
|
||||
|
@ -330,64 +321,23 @@ impl WorldManager {
|
|||
self.chunk_storage.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn add_update(&mut self, update: NoitaWorldUpdate) {
|
||||
self.outbound_model
|
||||
.apply_noita_update(&update, &mut self.is_storage_recent);
|
||||
}
|
||||
|
||||
pub(crate) fn add_end(&mut self, priority: u8, pos: &[i32]) {
|
||||
let updated_chunks = self
|
||||
.outbound_model
|
||||
.updated_chunks()
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
self.current_update += 1;
|
||||
let chunks_to_send: Vec<Vec<(OmniPeerId, u8)>> = updated_chunks
|
||||
.iter()
|
||||
.map(|chunk| self.chunk_updated_locally(*chunk, priority, pos))
|
||||
.collect();
|
||||
let mut chunk_packet: HashMap<OmniPeerId, Vec<(ChunkDelta, u8)>> = HashMap::new();
|
||||
for (chunk, who_sending) in updated_chunks.iter().zip(chunks_to_send.iter()) {
|
||||
let Some(delta) = self.outbound_model.get_chunk_delta(*chunk, false) else {
|
||||
continue;
|
||||
};
|
||||
for (peer, pri) in who_sending {
|
||||
chunk_packet
|
||||
.entry(*peer)
|
||||
.or_default()
|
||||
.push((delta.clone(), *pri));
|
||||
}
|
||||
}
|
||||
let mut emit_queue = Vec::new();
|
||||
for (peer, chunkpacket) in chunk_packet {
|
||||
emit_queue.push((
|
||||
Destination::Peer(peer),
|
||||
WorldNetMessage::ChunkPacket { chunkpacket },
|
||||
));
|
||||
}
|
||||
for (dst, msg) in emit_queue {
|
||||
self.emit_msg(dst, msg)
|
||||
}
|
||||
self.outbound_model.reset_change_tracking();
|
||||
}
|
||||
|
||||
fn chunk_updated_locally(
|
||||
&mut self,
|
||||
chunk: ChunkCoord,
|
||||
priority: u8,
|
||||
pos: &[i32],
|
||||
pos: Option<(i32, i32, i32, i32, bool)>,
|
||||
world_num: u8,
|
||||
) -> Vec<(OmniPeerId, u8)> {
|
||||
if pos.len() == 6 {
|
||||
self.my_pos = (pos[0], pos[1]);
|
||||
self.cam_pos = (pos[2], pos[3]);
|
||||
self.is_notplayer = pos[4] == 1;
|
||||
if self.world_num != pos[5] {
|
||||
self.world_num = pos[5];
|
||||
if let Some((px, py, cx, cy, is_not)) = pos {
|
||||
self.my_pos = (px, py);
|
||||
self.cam_pos = (cx, cy);
|
||||
self.is_notplayer = is_not;
|
||||
if self.world_num != world_num {
|
||||
self.world_num = world_num;
|
||||
self.reset();
|
||||
}
|
||||
} else if self.world_num != pos[0] {
|
||||
self.world_num = pos[0];
|
||||
} else if self.world_num != world_num {
|
||||
self.world_num = world_num;
|
||||
self.reset();
|
||||
}
|
||||
let entry = self.chunk_state.entry(chunk).or_insert_with(|| {
|
||||
|
@ -508,7 +458,7 @@ impl WorldManager {
|
|||
chunks_to_send
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self) {
|
||||
pub(crate) fn update(&mut self) -> Vec<NoitaWorldUpdate> {
|
||||
fn should_kill(
|
||||
my_pos: (i32, i32),
|
||||
cam_pos: (i32, i32),
|
||||
|
@ -638,18 +588,11 @@ impl WorldManager {
|
|||
}
|
||||
retain
|
||||
});
|
||||
self.get_noita_updates()
|
||||
}
|
||||
|
||||
pub(crate) fn get_noita_updates(&mut self) -> Vec<Vec<u8>> {
|
||||
// Sends random data to noita to check if it crashes.
|
||||
if env::var_os("NP_WORLD_SYNC_TEST").is_some() && self.current_update % 10 == 0 {
|
||||
let chunk_data = ChunkData::make_random();
|
||||
self.inbound_model
|
||||
.apply_chunk_data(ChunkCoord(0, 0), &chunk_data)
|
||||
}
|
||||
let updates = self.inbound_model.get_all_noita_updates();
|
||||
self.inbound_model.reset_change_tracking();
|
||||
updates
|
||||
pub(crate) fn get_noita_updates(&mut self) -> Vec<NoitaWorldUpdate> {
|
||||
self.inbound_model.get_all_noita_updates()
|
||||
}
|
||||
|
||||
pub(crate) fn reset(&mut self) {
|
||||
|
@ -3137,6 +3080,7 @@ use crate::net::world::world_model::chunk::PixelFlags;
|
|||
use rand::seq::SliceRandom;
|
||||
#[cfg(test)]
|
||||
use serial_test::serial;
|
||||
use shared::world_sync::{CHUNK_SIZE, ChunkCoord, NoitaWorldUpdate, WorldSyncToProxy};
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
#[serial]
|
||||
|
@ -3406,3 +3350,52 @@ fn test_cut_perf() {
|
|||
}
|
||||
println!("total micros: {}", total / iters);
|
||||
}
|
||||
|
||||
impl WorldManager {
|
||||
pub fn handle_noita_msg(&mut self, _: OmniPeerId, msg: WorldSyncToProxy) {
|
||||
match msg {
|
||||
WorldSyncToProxy::Updates(updates) => {
|
||||
for update in updates {
|
||||
self.outbound_model
|
||||
.apply_noita_update(update, &mut self.is_storage_recent)
|
||||
}
|
||||
}
|
||||
WorldSyncToProxy::End(pos, priority, world_num) => {
|
||||
let updated_chunks = self
|
||||
.outbound_model
|
||||
.updated_chunks()
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
self.current_update += 1;
|
||||
let chunks_to_send: Vec<Vec<(OmniPeerId, u8)>> = updated_chunks
|
||||
.iter()
|
||||
.map(|chunk| self.chunk_updated_locally(*chunk, priority, pos, world_num))
|
||||
.collect();
|
||||
let mut chunk_packet: HashMap<OmniPeerId, Vec<(ChunkDelta, u8)>> = HashMap::new();
|
||||
for (chunk, who_sending) in updated_chunks.iter().zip(chunks_to_send.iter()) {
|
||||
let Some(delta) = self.outbound_model.get_chunk_delta(*chunk, false) else {
|
||||
continue;
|
||||
};
|
||||
for (peer, pri) in who_sending {
|
||||
chunk_packet
|
||||
.entry(*peer)
|
||||
.or_default()
|
||||
.push((delta.clone(), *pri));
|
||||
}
|
||||
}
|
||||
let mut emit_queue = Vec::new();
|
||||
for (peer, chunkpacket) in chunk_packet {
|
||||
emit_queue.push((
|
||||
Destination::Peer(peer),
|
||||
WorldNetMessage::ChunkPacket { chunkpacket },
|
||||
));
|
||||
}
|
||||
for (dst, msg) in emit_queue {
|
||||
self.emit_msg(dst, msg)
|
||||
}
|
||||
self.outbound_model.reset_change_tracking();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,13 @@ use std::sync::Arc;
|
|||
|
||||
use bitcode::{Decode, Encode};
|
||||
use chunk::{Chunk, CompactPixel, Pixel, PixelFlags};
|
||||
use encoding::{NoitaWorldUpdate, PixelRun, PixelRunner};
|
||||
use encoding::PixelRunner;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use shared::world_sync::{CHUNK_SIZE, ChunkCoord, NoitaWorldUpdate, PixelRun};
|
||||
use tracing::info;
|
||||
|
||||
pub(crate) mod chunk;
|
||||
pub mod encoding;
|
||||
|
||||
pub(crate) const CHUNK_SIZE: usize = 128;
|
||||
|
||||
#[derive(Debug, Encode, Decode, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct ChunkCoord(pub i32, pub i32);
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct WorldModel {
|
||||
chunks: FxHashMap<ChunkCoord, Chunk>,
|
||||
|
@ -38,7 +33,7 @@ pub(crate) struct ChunkDelta {
|
|||
}
|
||||
|
||||
impl ChunkData {
|
||||
pub(crate) fn make_random() -> Self {
|
||||
/*pub(crate) fn make_random() -> Self {
|
||||
let mut runner = PixelRunner::new();
|
||||
for i in 0..CHUNK_SIZE * CHUNK_SIZE {
|
||||
runner.put_pixel(
|
||||
|
@ -51,7 +46,7 @@ impl ChunkData {
|
|||
}
|
||||
let runs = runner.build();
|
||||
ChunkData { runs }
|
||||
}
|
||||
}*/
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new(mat: u16) -> Self {
|
||||
|
@ -123,17 +118,17 @@ impl WorldModel {
|
|||
self.updated_chunks.insert(chunk_coord);
|
||||
}*/
|
||||
|
||||
fn get_pixel(&self, x: i32, y: i32) -> Pixel {
|
||||
/*fn get_pixel(&self, x: i32, y: i32) -> Pixel {
|
||||
let (chunk_coord, offset) = Self::get_chunk_coords(x, y);
|
||||
self.chunks
|
||||
.get(&chunk_coord)
|
||||
.map(|chunk| chunk.pixel(offset))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn apply_noita_update(
|
||||
&mut self,
|
||||
update: &NoitaWorldUpdate,
|
||||
update: NoitaWorldUpdate,
|
||||
changed: &mut FxHashSet<ChunkCoord>,
|
||||
) {
|
||||
fn set_pixel(pixel: Pixel, chunk: &mut Chunk, offset: usize) -> bool {
|
||||
|
@ -145,21 +140,23 @@ impl WorldModel {
|
|||
false
|
||||
}
|
||||
}
|
||||
let header = &update.header;
|
||||
let runs = &update.runs;
|
||||
let mut x = 0;
|
||||
let mut y = 0;
|
||||
let (mut chunk_coord, _) = Self::get_chunk_coords(header.x, header.y);
|
||||
let mut chunk = self.chunks.entry(chunk_coord).or_default();
|
||||
for run in runs {
|
||||
let (start_x, start_y) = (
|
||||
update.coord.0 * CHUNK_SIZE as i32,
|
||||
update.coord.1 * CHUNK_SIZE as i32,
|
||||
);
|
||||
let mut chunk_coord = update.coord;
|
||||
let mut chunk = self.chunks.entry(update.coord).or_default();
|
||||
for run in update.runs {
|
||||
let flags = if run.data.flags > 0 {
|
||||
PixelFlags::Fluid
|
||||
} else {
|
||||
PixelFlags::Normal
|
||||
};
|
||||
for _ in 0..run.length {
|
||||
let xs = header.x + x;
|
||||
let ys = header.y + y;
|
||||
let xs = start_x + x;
|
||||
let ys = start_y + y;
|
||||
let (new_chunk_coord, offset) = Self::get_chunk_coords(xs, ys);
|
||||
if chunk_coord != new_chunk_coord {
|
||||
chunk_coord = new_chunk_coord;
|
||||
|
@ -178,37 +175,32 @@ impl WorldModel {
|
|||
changed.remove(&chunk_coord);
|
||||
}
|
||||
}
|
||||
x += 1;
|
||||
if x == i32::from(header.w) + 1 {
|
||||
if x == CHUNK_SIZE as i32 {
|
||||
x = 0;
|
||||
y += 1;
|
||||
} else {
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_noita_update(&self, x: i32, y: i32, w: u32, h: u32) -> NoitaWorldUpdate {
|
||||
assert!(w <= 256);
|
||||
assert!(h <= 256);
|
||||
let mut runner = PixelRunner::new();
|
||||
for j in 0..(h as i32) {
|
||||
for i in 0..(w as i32) {
|
||||
runner.put_pixel(self.get_pixel(x + i, y + j).to_raw())
|
||||
}
|
||||
}
|
||||
runner.into_noita_update(x, y, (w - 1) as u8, (h - 1) as u8)
|
||||
}
|
||||
|
||||
pub fn get_all_noita_updates(&self) -> Vec<Vec<u8>> {
|
||||
pub fn get_all_noita_updates(&mut self) -> Vec<NoitaWorldUpdate> {
|
||||
let mut updates = Vec::new();
|
||||
for chunk_coord in &self.updated_chunks {
|
||||
let update = self.get_noita_update(
|
||||
chunk_coord.0 * (CHUNK_SIZE as i32),
|
||||
chunk_coord.1 * (CHUNK_SIZE as i32),
|
||||
CHUNK_SIZE as u32,
|
||||
CHUNK_SIZE as u32,
|
||||
);
|
||||
updates.push(update.save());
|
||||
for coord in self.updated_chunks.drain() {
|
||||
if let Some(chunk) = self.chunks.get_mut(&coord) {
|
||||
chunk.clear_changed();
|
||||
let mut runner = PixelRunner::new();
|
||||
for j in 0..CHUNK_SIZE {
|
||||
for i in 0..CHUNK_SIZE {
|
||||
runner.put_pixel(chunk.pixel(i + j * CHUNK_SIZE).to_raw())
|
||||
}
|
||||
}
|
||||
updates.push(NoitaWorldUpdate {
|
||||
coord,
|
||||
runs: runner.build(),
|
||||
});
|
||||
}
|
||||
}
|
||||
updates
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use std::num::NonZeroU16;
|
||||
|
||||
use super::{ChunkData, encoding::PixelRunner};
|
||||
use bitcode::{Decode, Encode};
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
|
||||
use super::{
|
||||
CHUNK_SIZE, ChunkData,
|
||||
encoding::{PixelRunner, RawPixel},
|
||||
};
|
||||
use shared::world_sync::{CHUNK_SIZE, RawPixel};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Encode, Decode)]
|
||||
pub enum PixelFlags {
|
||||
|
|
|
@ -1,46 +1,12 @@
|
|||
use bitcode::{Decode, Encode};
|
||||
use bytemuck::{AnyBitPattern, NoUninit, bytes_of, pod_read_unaligned};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
#[derive(Debug, Clone, Copy, AnyBitPattern, NoUninit, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Header {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: u8,
|
||||
pub h: u8,
|
||||
pub run_count: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct NoitaWorldUpdate {
|
||||
pub(crate) header: Header,
|
||||
pub(crate) runs: Vec<PixelRun<RawPixel>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
|
||||
pub(crate) struct RawPixel {
|
||||
pub material: u16,
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
struct ByteParser<'a> {
|
||||
use shared::world_sync::PixelRun;
|
||||
/*struct ByteParser<'a> {
|
||||
data: &'a [u8],
|
||||
}
|
||||
|
||||
/// Stores a run of pixels.
|
||||
/// Not specific to Noita side - length is an actual length
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct PixelRun<Pixel> {
|
||||
pub length: u32,
|
||||
pub data: Pixel,
|
||||
}
|
||||
}*/
|
||||
|
||||
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
||||
pub struct PixelRunner<Pixel> {
|
||||
current_pixel: Option<Pixel>,
|
||||
current_run_len: u32,
|
||||
current_run_len: u16,
|
||||
runs: Vec<PixelRun<Pixel>>,
|
||||
}
|
||||
|
||||
|
@ -86,24 +52,7 @@ impl<Pixel: Eq + Copy> PixelRunner<Pixel> {
|
|||
}
|
||||
}
|
||||
|
||||
impl PixelRunner<RawPixel> {
|
||||
/// Note: w/h are actualy width/height -1
|
||||
pub fn into_noita_update(self, x: i32, y: i32, w: u8, h: u8) -> NoitaWorldUpdate {
|
||||
let runs = self.build();
|
||||
NoitaWorldUpdate {
|
||||
header: Header {
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
run_count: runs.len() as u16,
|
||||
},
|
||||
runs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ByteParser<'a> {
|
||||
/*impl<'a> ByteParser<'a> {
|
||||
fn new(data: &'a [u8]) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
|
@ -117,7 +66,7 @@ impl<'a> ByteParser<'a> {
|
|||
|
||||
fn next_run(&mut self) -> PixelRun<RawPixel> {
|
||||
PixelRun {
|
||||
length: u32::from(self.next::<u16>()) + 1,
|
||||
length: self.next::<u16>() + 1,
|
||||
data: RawPixel {
|
||||
material: self.next(),
|
||||
flags: self.next(),
|
||||
|
@ -125,37 +74,4 @@ impl<'a> ByteParser<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoitaWorldUpdate {
|
||||
pub fn load(data: &[u8]) -> Self {
|
||||
let mut parser = ByteParser::new(data);
|
||||
|
||||
let header: Header = parser.next();
|
||||
let mut runs = Vec::with_capacity(header.run_count.into());
|
||||
|
||||
for _ in 0..header.run_count {
|
||||
runs.push(parser.next_run());
|
||||
}
|
||||
|
||||
assert!(parser.data.is_empty());
|
||||
|
||||
Self { header, runs }
|
||||
}
|
||||
pub fn save(&self) -> Vec<u8> {
|
||||
let header = Header {
|
||||
run_count: self.runs.len() as u16,
|
||||
..self.header
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(bytes_of(&header));
|
||||
|
||||
for run in &self.runs {
|
||||
let len = u16::try_from(run.length - 1).unwrap();
|
||||
buf.extend_from_slice(bytes_of(&len));
|
||||
buf.extend_from_slice(bytes_of(&run.data.material));
|
||||
buf.extend_from_slice(bytes_of(&run.data.flags));
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
20
noita_api/Cargo.lock
generated
20
noita_api/Cargo.lock
generated
|
@ -58,9 +58,9 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
|||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
@ -128,6 +128,15 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
|
@ -140,6 +149,12 @@ version = "1.0.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.8"
|
||||
|
@ -171,6 +186,7 @@ version = "1.6.0"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"eyre",
|
||||
"iced-x86",
|
||||
"libloading",
|
||||
"noita_api_macro",
|
||||
"object",
|
||||
|
|
|
@ -12,4 +12,5 @@ base64 = "0.22.1"
|
|||
rustc-hash = "2.0.0"
|
||||
smallvec = "1.15.1"
|
||||
object = "0.37.1"
|
||||
rayon = "1.10.0"
|
||||
rayon = "1.10.0"
|
||||
iced-x86 = "1.21.0"
|
|
@ -1,10 +1,8 @@
|
|||
use std::{mem, os::raw::c_void, ptr, sync::OnceLock};
|
||||
|
||||
use crate::lua::LuaState;
|
||||
use crate::noita::types::{EntityManager, ThiscallFn};
|
||||
use iced_x86::{Decoder, DecoderOptions, Mnemonic};
|
||||
use noita_api::lua::LuaState;
|
||||
|
||||
use crate::noita::ntypes::{EntityManager, ThiscallFn};
|
||||
|
||||
static GRABBED: OnceLock<Grabbed> = OnceLock::new();
|
||||
|
||||
pub(crate) unsafe fn grab_addr_from_instruction(
|
||||
|
@ -41,42 +39,16 @@ struct Grabbed {
|
|||
unsafe impl Sync for Grabbed {}
|
||||
unsafe impl Send for Grabbed {}
|
||||
|
||||
pub(crate) struct GrabbedGlobals {
|
||||
pub struct GrabbedGlobals {
|
||||
// These 3 actually point to a pointer.
|
||||
pub(crate) _game_global: *mut usize,
|
||||
pub(crate) _world_state_entity: *mut usize,
|
||||
pub(crate) entity_manager: *const *mut EntityManager,
|
||||
pub entity_manager: *const *mut EntityManager,
|
||||
}
|
||||
|
||||
pub(crate) struct GrabbedFns {
|
||||
pub(crate) get_entity: *const ThiscallFn, //unsafe extern "C" fn(*const EntityManager, u32) -> *mut Entity,
|
||||
pub struct GrabbedFns {
|
||||
pub get_entity: *const ThiscallFn, //unsafe extern "C" fn(*const EntityManager, u32) -> *mut Entity,
|
||||
}
|
||||
|
||||
pub(crate) fn grab_addrs(lua: LuaState) {
|
||||
lua.get_global(c"GameGetWorldStateEntity");
|
||||
let base = lua.to_cfunction(-1).unwrap() as *const c_void;
|
||||
let world_state_entity =
|
||||
unsafe { grab_addr_from_instruction(base, 0x007aa7ce - 0x007aa540, Mnemonic::Mov).cast() };
|
||||
#[cfg(debug_assertions)]
|
||||
println!(
|
||||
"World state entity addr: 0x{:x}",
|
||||
world_state_entity as usize
|
||||
);
|
||||
lua.pop_last();
|
||||
|
||||
lua.get_global(c"GameGetFrameNum");
|
||||
let base = lua.to_cfunction(-1).unwrap() as *const c_void;
|
||||
let load_game_global =
|
||||
unsafe { grab_addr_from_instruction(base, 0x007bf3c9 - 0x007bf140, Mnemonic::Call) }; // CALL load_game_global
|
||||
#[cfg(debug_assertions)]
|
||||
println!("Load game global addr: 0x{:x}", load_game_global as usize);
|
||||
let game_global = unsafe {
|
||||
grab_addr_from_instruction(load_game_global, 0x00439c17 - 0x00439bb0, Mnemonic::Mov).cast()
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
println!("Game global addr: 0x{:x}", game_global as usize);
|
||||
lua.pop_last();
|
||||
|
||||
pub fn grab_addrs(lua: LuaState) {
|
||||
lua.get_global(c"EntityGetFilename");
|
||||
let base = lua.to_cfunction(-1).unwrap() as *const c_void;
|
||||
let get_entity = unsafe {
|
||||
|
@ -86,30 +58,22 @@ pub(crate) fn grab_addrs(lua: LuaState) {
|
|||
Mnemonic::Call,
|
||||
))
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
println!("get_entity addr: 0x{:x}", get_entity as usize);
|
||||
let entity_manager =
|
||||
unsafe { grab_addr_from_instruction(base, 0x00797821 - 0x00797570, Mnemonic::Mov).cast() };
|
||||
#[cfg(debug_assertions)]
|
||||
println!("entity_manager addr: 0x{:x}", entity_manager as usize);
|
||||
lua.pop_last();
|
||||
|
||||
GRABBED
|
||||
.set(Grabbed {
|
||||
globals: GrabbedGlobals {
|
||||
_game_global: game_global,
|
||||
_world_state_entity: world_state_entity,
|
||||
entity_manager,
|
||||
},
|
||||
globals: GrabbedGlobals { entity_manager },
|
||||
fns: GrabbedFns { get_entity },
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub(crate) fn grabbed_fns() -> &'static GrabbedFns {
|
||||
pub fn grabbed_fns() -> &'static GrabbedFns {
|
||||
&GRABBED.get().expect("to be initialized early").fns
|
||||
}
|
||||
|
||||
pub(crate) fn grabbed_globals() -> &'static GrabbedGlobals {
|
||||
pub fn grabbed_globals() -> &'static GrabbedGlobals {
|
||||
&GRABBED.get().expect("to be initialized early").globals
|
||||
}
|
|
@ -16,6 +16,7 @@ use std::{
|
|||
pub mod lua;
|
||||
pub mod serialize;
|
||||
pub use noita_api_macro::add_lua_fn;
|
||||
pub mod addr_grabber;
|
||||
pub mod noita;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -33,7 +33,9 @@ pub fn get_functions() -> eyre::Result<(&'static types::CellVTable, *mut types::
|
|||
0xb1, 0x70, 0x00, 0x60, 0xb1, 0x70, 0x00, 0xb0, 0xf5, 0x70, 0x00, 0xd0, 0xf5, 0x70, 0x00,
|
||||
0xf0, 0xcd, 0x70, 0x00, 0x50, 0xf7, 0x70, 0x00, 0xe0, 0xc0, 0x4a, 0x00, 0xf0, 0xf7, 0x70,
|
||||
0x00, 0x20, 0xc0, 0x4a, 0x00, 0x60, 0xf1, 0x70, 0x00, 0xf0, 0xea, 0x70, 0x00, 0x90, 0xef,
|
||||
0x70, 0x00, 0x60, 0xf3, 0x70, 0x00, 0x50, 0xaf, 0x70, 0x00, 0xd0, 0xb1, 0x70, 0x00,
|
||||
0x70, 0x00, 0x60, 0xf3, 0x70, 0x00, 0x50, 0xaf, 0x70, 0x00, 0xd0, 0xb1, 0x70,
|
||||
0x00,
|
||||
//TODO i should search for a function in the vtable then find the vtable prob
|
||||
];
|
||||
let start = rdata.address() as *const c_void;
|
||||
let cellvtable_ptr = unsafe {
|
||||
|
|
|
@ -102,10 +102,10 @@ impl Debug for ChunkMap {
|
|||
#[derive(Debug)]
|
||||
pub struct GridWorldVTable {
|
||||
//ptr is 0x10013bc
|
||||
unknown: [*const c_void; 3],
|
||||
pub get_chunk_map: *const c_void,
|
||||
unknownmagic: *const c_void,
|
||||
unknown2: [*const c_void; 29],
|
||||
unknown: [*const ThiscallFn; 3],
|
||||
pub get_chunk_map: *const ThiscallFn,
|
||||
unknownmagic: *const ThiscallFn,
|
||||
unknown2: [*const ThiscallFn; 29],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -473,6 +473,7 @@ pub union CellVTable {
|
|||
gas: GasCellVTable,
|
||||
//ptr is 0xff8a6c
|
||||
solid: SolidCellVTable,
|
||||
//ptr is 0x10096e0
|
||||
fire: FireCellVTable,
|
||||
}
|
||||
|
||||
|
@ -485,69 +486,73 @@ impl Debug for CellVTable {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SolidCellVTable {
|
||||
unknown0: *const c_void,
|
||||
unknown1: *const c_void,
|
||||
unknown2: *const c_void,
|
||||
unknown3: *const c_void,
|
||||
unknown4: *const c_void,
|
||||
unknown5: *const c_void,
|
||||
unknown6: *const c_void,
|
||||
unknown0: *const ThiscallFn,
|
||||
unknown1: *const ThiscallFn,
|
||||
unknown2: *const ThiscallFn,
|
||||
unknown3: *const ThiscallFn,
|
||||
unknown4: *const ThiscallFn,
|
||||
unknown5: *const ThiscallFn,
|
||||
unknown6: *const ThiscallFn,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NoneCellVTable {
|
||||
unknown: [*const c_void; 41],
|
||||
unknown: [*const ThiscallFn; 41],
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GasCellVTable {}
|
||||
pub struct GasCellVTable {
|
||||
unknown: [*const ThiscallFn; 41],
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FireCellVTable {}
|
||||
pub struct FireCellVTable {
|
||||
unknown: [*const ThiscallFn; 41],
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LiquidCellVTable {
|
||||
pub destroy: *const c_void,
|
||||
pub get_cell_type: *const c_void,
|
||||
unknown01: *const c_void,
|
||||
unknown02: *const c_void,
|
||||
unknown03: *const c_void,
|
||||
pub get_color: *const c_void,
|
||||
unknown04: *const c_void,
|
||||
pub set_color: *const c_void,
|
||||
unknown05: *const c_void,
|
||||
unknown06: *const c_void,
|
||||
unknown07: *const c_void,
|
||||
unknown08: *const c_void,
|
||||
pub get_material: *const c_void,
|
||||
unknown09: *const c_void,
|
||||
unknown10: *const c_void,
|
||||
unknown11: *const c_void,
|
||||
unknown12: *const c_void,
|
||||
unknown13: *const c_void,
|
||||
unknown14: *const c_void,
|
||||
unknown15: *const c_void,
|
||||
pub get_position: *const c_void,
|
||||
unknown16: *const c_void,
|
||||
unknown17: *const c_void,
|
||||
unknown18: *const c_void,
|
||||
unknown19: *const c_void,
|
||||
unknown20: *const c_void,
|
||||
unknown21: *const c_void,
|
||||
unknown22: *const c_void,
|
||||
unknown23: *const c_void,
|
||||
pub is_burning: *const c_void,
|
||||
unknown24: *const c_void,
|
||||
unknown25: *const c_void,
|
||||
unknown26: *const c_void,
|
||||
pub stop_burning: *const c_void,
|
||||
unknown27: *const c_void,
|
||||
unknown28: *const c_void,
|
||||
unknown29: *const c_void,
|
||||
unknown30: *const c_void,
|
||||
unknown31: *const c_void,
|
||||
pub remove: *const c_void,
|
||||
unknown32: *const c_void,
|
||||
pub destroy: *const ThiscallFn,
|
||||
pub get_cell_type: *const ThiscallFn,
|
||||
unknown01: *const ThiscallFn,
|
||||
unknown02: *const ThiscallFn,
|
||||
unknown03: *const ThiscallFn,
|
||||
pub get_color: *const ThiscallFn,
|
||||
unknown04: *const ThiscallFn,
|
||||
pub set_color: *const ThiscallFn,
|
||||
unknown05: *const ThiscallFn,
|
||||
unknown06: *const ThiscallFn,
|
||||
unknown07: *const ThiscallFn,
|
||||
unknown08: *const ThiscallFn,
|
||||
pub get_material: *const ThiscallFn,
|
||||
unknown09: *const ThiscallFn,
|
||||
unknown10: *const ThiscallFn,
|
||||
unknown11: *const ThiscallFn,
|
||||
unknown12: *const ThiscallFn,
|
||||
unknown13: *const ThiscallFn,
|
||||
unknown14: *const ThiscallFn,
|
||||
unknown15: *const ThiscallFn,
|
||||
pub get_position: *const ThiscallFn,
|
||||
unknown16: *const ThiscallFn,
|
||||
unknown17: *const ThiscallFn,
|
||||
unknown18: *const ThiscallFn,
|
||||
unknown19: *const ThiscallFn,
|
||||
unknown20: *const ThiscallFn,
|
||||
unknown21: *const ThiscallFn,
|
||||
unknown22: *const ThiscallFn,
|
||||
unknown23: *const ThiscallFn,
|
||||
pub is_burning: *const ThiscallFn,
|
||||
unknown24: *const ThiscallFn,
|
||||
unknown25: *const ThiscallFn,
|
||||
unknown26: *const ThiscallFn,
|
||||
pub stop_burning: *const ThiscallFn,
|
||||
unknown27: *const ThiscallFn,
|
||||
unknown28: *const ThiscallFn,
|
||||
unknown29: *const ThiscallFn,
|
||||
unknown30: *const ThiscallFn,
|
||||
unknown31: *const ThiscallFn,
|
||||
pub remove: *const ThiscallFn,
|
||||
unknown32: *const ThiscallFn,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
|
@ -558,7 +563,7 @@ pub struct Position {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct Cell {
|
||||
pub vtable: &'static CellVTable,
|
||||
|
||||
|
@ -585,10 +590,10 @@ pub enum FullCell {
|
|||
impl From<&Cell> for FullCell {
|
||||
fn from(value: &Cell) -> Self {
|
||||
match value.material.cell_type {
|
||||
CellType::Liquid => FullCell::LiquidCell(value.get_liquid().clone()),
|
||||
CellType::Fire => FullCell::FireCell(value.get_fire().clone()),
|
||||
CellType::Gas => FullCell::GasCell(value.get_gas().clone()),
|
||||
CellType::None | CellType::Solid => FullCell::Cell(value.clone()),
|
||||
CellType::Liquid => FullCell::LiquidCell(*value.get_liquid()),
|
||||
CellType::Fire => FullCell::FireCell(*value.get_fire()),
|
||||
CellType::Gas => FullCell::GasCell(*value.get_gas()),
|
||||
CellType::None | CellType::Solid => FullCell::Cell(*value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -627,37 +632,99 @@ unsafe impl Sync for CellPtr {}
|
|||
unsafe impl Send for CellPtr {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FireCell {
|
||||
pub cell: Cell,
|
||||
pub x: isize,
|
||||
pub y: isize,
|
||||
unknown1: u8,
|
||||
unknown2: u8,
|
||||
unknown3: u8,
|
||||
unknown4: u8,
|
||||
pub lifetime: isize,
|
||||
unknown: isize,
|
||||
}
|
||||
|
||||
impl FireCell {
|
||||
///# Safety
|
||||
pub unsafe fn create(
|
||||
mat: &'static CellData,
|
||||
vtable: &'static CellVTable,
|
||||
world: *mut GridWorld,
|
||||
) -> Self {
|
||||
let lifetime = if let Some(world) = unsafe { world.as_mut() } {
|
||||
world.rng *= 0x343fd;
|
||||
world.rng += 0x269ec3;
|
||||
(world.rng >> 0x10 & 0x7fff) % 0x15
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
let mut cell = Cell::create(mat, vtable);
|
||||
cell.is_burning = true;
|
||||
Self {
|
||||
cell,
|
||||
x: 0,
|
||||
y: 0,
|
||||
lifetime,
|
||||
unknown: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GasCell {
|
||||
pub cell: Cell,
|
||||
unknown5: isize,
|
||||
unknown6: isize,
|
||||
pub x: isize,
|
||||
pub y: isize,
|
||||
unknown1: u8,
|
||||
unknown2: u8,
|
||||
unknown3: u8,
|
||||
unknown4: u8,
|
||||
unknown5: isize,
|
||||
unknown6: isize,
|
||||
unknown7: isize,
|
||||
pub color: Color,
|
||||
pub not_color: Color,
|
||||
unknown7: isize,
|
||||
unknown8: isize,
|
||||
lifetime: isize,
|
||||
}
|
||||
|
||||
impl GasCell {
|
||||
///# Safety
|
||||
pub unsafe fn create(
|
||||
mat: &'static CellData,
|
||||
vtable: &'static CellVTable,
|
||||
world: *mut GridWorld,
|
||||
) -> Self {
|
||||
let (bool, lifetime) = if let Some(world) = unsafe { world.as_mut() } {
|
||||
let life = ((mat.lifetime as f32 * 0.3) as u64).max(1);
|
||||
world.rng *= 0x343fd;
|
||||
world.rng += 0x269ec3;
|
||||
(
|
||||
(world.rng >> 0x10 & 0x7fff) % 0x65 < 0x32,
|
||||
(((world.rng >> 0x10 & 0x7fff) as u64 % (life * 2 + 1)) - life) as isize,
|
||||
)
|
||||
} else {
|
||||
(false, -1)
|
||||
};
|
||||
let mut cell = Cell::create(mat, vtable);
|
||||
cell.is_burning = true;
|
||||
Self {
|
||||
cell,
|
||||
unknown5: if bool { 1 } else { 0xff },
|
||||
unknown6: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
unknown1: 0,
|
||||
unknown2: 0,
|
||||
unknown3: 0,
|
||||
unknown4: 0,
|
||||
unknown7: 0,
|
||||
unknown8: 0,
|
||||
color: mat.default_primary_color,
|
||||
lifetime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LiquidCell {
|
||||
pub cell: Cell,
|
||||
pub x: isize,
|
||||
|
@ -760,3 +827,16 @@ pub struct GameGlobal {
|
|||
unknown2: [isize; 11],
|
||||
pub pause_state: isize,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Entity {
|
||||
_unknown0: [u8; 8],
|
||||
pub filename_index: u32,
|
||||
// More stuff, not that relevant currently.
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct EntityManager {
|
||||
_fld: c_void,
|
||||
// Unknown
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct ThiscallFn(c_void);
|
||||
|
|
|
@ -187,9 +187,7 @@ end
|
|||
|
||||
local DEST_PROXY = 1
|
||||
local DEST_BROADCAST = 2
|
||||
local DEST_PROXY_BIN = 3
|
||||
local DEST_FLAGS = 0
|
||||
|
||||
local MOD_RELIABLE = 4 -- 0b101
|
||||
|
||||
function net.send_internal(msg, dest, reliable)
|
||||
|
@ -228,10 +226,6 @@ function net.proxy_send(key, value)
|
|||
net.send_internal(key .. " " .. value, DEST_PROXY)
|
||||
end
|
||||
|
||||
function net.proxy_bin_send(key, value)
|
||||
ewext.netmanager_send(string.char(DEST_PROXY_BIN, key) .. value)
|
||||
end
|
||||
|
||||
function net.proxy_notify_game_over()
|
||||
net.proxy_send("game_over", 1)
|
||||
end
|
||||
|
|
|
@ -1,914 +0,0 @@
|
|||
local item_sync = dofile_once("mods/quant.ew/files/system/item_sync.lua")
|
||||
local effect_sync = dofile_once("mods/quant.ew/files/system/game_effect_sync/game_effect_sync.lua")
|
||||
local stain_sync = dofile_once("mods/quant.ew/files/system/effect_data_sync/effect_data_sync.lua")
|
||||
|
||||
local ffi = require("ffi")
|
||||
local rpc = net.new_rpc_namespace()
|
||||
|
||||
local EnemyData = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy" },
|
||||
})
|
||||
|
||||
-- Variant of EnemyData for when we don't have any motion (or no VelocityComponent).
|
||||
local EnemyDataNoMotion = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y" },
|
||||
})
|
||||
|
||||
local EnemyDataWorm = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy", "tx", "ty" },
|
||||
})
|
||||
|
||||
local EnemyDataKolmi = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy" },
|
||||
bool = { "enabled" },
|
||||
vecfloat = { "legs" },
|
||||
})
|
||||
|
||||
local EnemyDataMom = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy" },
|
||||
vecbool = { "orbs" },
|
||||
})
|
||||
|
||||
local EnemyDataFish = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy" },
|
||||
u8 = { "r" },
|
||||
})
|
||||
|
||||
local HpData = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "hp", "max_hp" },
|
||||
})
|
||||
|
||||
local should_wait = {}
|
||||
|
||||
local first = true
|
||||
|
||||
local FULL_TURN = math.pi * 2
|
||||
|
||||
local frame = 0
|
||||
|
||||
local enemy_sync = {}
|
||||
|
||||
local unsynced_enemys = {}
|
||||
|
||||
local dead_entities = {}
|
||||
--this basically never happens, doesn't seem that useful anymore. Perhaps should be removed to conserve memory.
|
||||
--local confirmed_kills = {}
|
||||
|
||||
local spawned_by_us = {}
|
||||
|
||||
-- HACK
|
||||
local times_spawned_last_minute = {}
|
||||
|
||||
local DISTANCE_LIMIT = 128 * 6
|
||||
|
||||
for filename, _ in pairs(constants.phys_sync_allowed) do
|
||||
util.add_tag_to(filename, "prop_physics")
|
||||
-- Idk it just causes the minecart to not appear at all.
|
||||
-- util.replace_text_in(filename, 'kill_entity_after_initialized="1"', 'kill_entity_after_initialized="0"')
|
||||
end
|
||||
|
||||
util.add_cross_call("ew_es_death_notify", function(enemy_id, responsible_id)
|
||||
local player_data = player_fns.get_player_data_by_local_entity_id(responsible_id)
|
||||
local responsible
|
||||
if player_data ~= nil then
|
||||
responsible = player_data.peer_id
|
||||
else
|
||||
responsible = responsible_id
|
||||
end
|
||||
local damage = EntityGetFirstComponentIncludingDisabled(enemy_id, "DamageModelComponent")
|
||||
table.insert(dead_entities, { enemy_id, responsible, ComponentGetValue2(damage, "wait_for_kill_flag_on_death") })
|
||||
end)
|
||||
|
||||
local function world_exists_for(entity)
|
||||
local x, y = EntityGetFirstHitboxCenter(entity)
|
||||
local w, h = 5, 5 -- TODO
|
||||
w = w * 0.5
|
||||
h = h * 0.5
|
||||
return DoesWorldExistAt(x - w, y - h, x + w, y + h)
|
||||
end
|
||||
|
||||
local function table_extend(to, from)
|
||||
for _, e in ipairs(from) do
|
||||
to[#to + 1] = e
|
||||
end
|
||||
end
|
||||
|
||||
local function table_extend_filtered(to, from, filter)
|
||||
for _, e in ipairs(from) do
|
||||
if filter(e) then
|
||||
to[#to + 1] = e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_sync_entities(return_all)
|
||||
local entities = EntityGetWithTag("enemy") or {}
|
||||
table_extend(entities, EntityGetWithTag("ew_enemy_sync_extra"))
|
||||
table_extend(entities, EntityGetWithTag("plague_rat"))
|
||||
table_extend(entities, EntityGetWithTag("seed_f"))
|
||||
table_extend(entities, EntityGetWithTag("seed_e"))
|
||||
table_extend(entities, EntityGetWithTag("seed_d"))
|
||||
table_extend(entities, EntityGetWithTag("seed_c"))
|
||||
table_extend(entities, EntityGetWithTag("perk_fungus_tiny"))
|
||||
table_extend(entities, EntityGetWithTag("helpless_animal"))
|
||||
table_extend_filtered(entities, EntityGetWithTag("hittable"), function(ent)
|
||||
local name = EntityGetName(ent)
|
||||
local file = EntityGetFilename(ent)
|
||||
return name == "$item_essence_stone"
|
||||
or name == "$animal_fish_giga"
|
||||
or file == "data/entities/buildings/spittrap_left.xml"
|
||||
or file == "data/entities/buildings/spittrap_right.xml"
|
||||
or file == "data/entities/buildings/thundertrap_left.xml"
|
||||
or file == "data/entities/buildings/thundertrap_right.xml"
|
||||
or file == "data/entities/buildings/arrowtrap_left.xml"
|
||||
or file == "data/entities/buildings/arrowtrap_right.xml"
|
||||
or file == "data/entities/buildings/firetrap_left.xml"
|
||||
or file == "data/entities/buildings/firetrap_right.xml"
|
||||
--data/entities/buildings/statue_trap_left.xml
|
||||
--data/entities/buildings/statue_trap_right.xml
|
||||
end)
|
||||
table_extend_filtered(entities, EntityGetWithTag("prop_physics"), function(ent)
|
||||
local f = EntityGetFilename(ent)
|
||||
if f ~= nil then
|
||||
return constants.phys_sync_allowed[f]
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
local entities2 = {}
|
||||
if return_all then
|
||||
table_extend_filtered(entities2, entities, function(ent)
|
||||
return not EntityHasTag(ent, "ew_no_enemy_sync")
|
||||
end)
|
||||
else
|
||||
table_extend_filtered(entities2, entities, function(ent)
|
||||
local x, y = EntityGetTransform(ent)
|
||||
local has_anyone = EntityHasTag(ent, "worm")
|
||||
or EntityGetFirstComponent(ent, "BossHealthBarComponent") ~= nil
|
||||
or #EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "ew_peer") ~= 0
|
||||
or #EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "polymorphed_player") ~= 0
|
||||
return has_anyone and not EntityHasTag(ent, "ew_no_enemy_sync")
|
||||
end)
|
||||
end
|
||||
|
||||
return entities2
|
||||
end
|
||||
|
||||
local was_held = {}
|
||||
|
||||
function enemy_sync.host_upload_entities()
|
||||
local entities = get_sync_entities()
|
||||
local enemy_data_list = {}
|
||||
for i, enemy_id in ipairs(entities) do
|
||||
if not world_exists_for(enemy_id) then
|
||||
goto continue
|
||||
end
|
||||
local filename = EntityGetFilename(enemy_id)
|
||||
filename = constants.interned_filename_to_index[filename] or filename
|
||||
|
||||
local x, y, rot = EntityGetTransform(enemy_id)
|
||||
local character_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "CharacterDataComponent")
|
||||
local vx, vy = 0, 0
|
||||
if character_data ~= nil then
|
||||
vx, vy = ComponentGetValue2(character_data, "mVelocity")
|
||||
else
|
||||
local velocity = EntityGetFirstComponentIncludingDisabled(enemy_id, "VelocityComponent")
|
||||
if velocity ~= nil then
|
||||
vx, vy = ComponentGetValue2(velocity, "mVelocity")
|
||||
end
|
||||
end
|
||||
local ai_component = EntityGetFirstComponentIncludingDisabled(enemy_id, "AnimalAIComponent")
|
||||
if ai_component ~= 0 and ai_component ~= nil then
|
||||
ComponentSetValue2(ai_component, "max_distance_to_cam_to_start_hunting", math.pow(2, 29))
|
||||
end
|
||||
|
||||
local phys_info = util.get_phys_info(enemy_id, true)
|
||||
if phys_info == nil then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local hp, max_hp, has_hp = util.get_ent_health(enemy_id)
|
||||
if has_hp then
|
||||
util.ensure_component_present(enemy_id, "LuaComponent", "ew_death_notify", {
|
||||
script_death = "mods/quant.ew/files/resource/cbs/death_notify.lua",
|
||||
})
|
||||
end
|
||||
|
||||
-- TODO: figure out how to sync those.
|
||||
-- local laser_sight_data = nil
|
||||
-- local laser_sight = EntityGetFirstComponentIncludingDisabled(enemy_id, "SpriteComponent", "laser_sight")
|
||||
-- if laser_sight ~= nil and laser_sight ~= 0 then
|
||||
-- -- local x, y, r =
|
||||
-- end
|
||||
|
||||
local death_triggers = {}
|
||||
for _, com in ipairs(EntityGetComponent(enemy_id, "LuaComponent") or {}) do
|
||||
local script = ComponentGetValue2(com, "script_death")
|
||||
if script ~= nil and script ~= "" then
|
||||
table.insert(death_triggers, constants.interned_filename_to_index[script] or script)
|
||||
end
|
||||
end
|
||||
local en_data
|
||||
local worm = EntityGetFirstComponentIncludingDisabled(enemy_id, "WormAIComponent")
|
||||
or EntityGetFirstComponentIncludingDisabled(enemy_id, "BossDragonComponent")
|
||||
if EntityHasTag(enemy_id, "boss_centipede") then
|
||||
local legs = {}
|
||||
for _, leg in ipairs(EntityGetAllChildren(enemy_id, "foot")) do
|
||||
local limb = EntityGetFirstComponentIncludingDisabled(leg, "IKLimbComponent")
|
||||
local lx, ly = ComponentGetValue2(limb, "end_position")
|
||||
table.insert(legs, lx)
|
||||
table.insert(legs, ly)
|
||||
end
|
||||
en_data = EnemyDataKolmi({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
enabled = EntityGetFirstComponent(enemy_id, "BossHealthBarComponent", "disabled_at_start") ~= nil,
|
||||
legs = legs,
|
||||
})
|
||||
elseif EntityHasTag(enemy_id, "boss_wizard") then
|
||||
local orbs = { false, false, false, false, false, false, false, false }
|
||||
for _, child in ipairs(EntityGetAllChildren(enemy_id) or {}) do
|
||||
local var = EntityGetFirstComponentIncludingDisabled(child, "VariableStorageComponent")
|
||||
if EntityHasTag(child, "touchmagic_immunity") and var ~= nil then
|
||||
local n = ComponentGetValue2(var, "value_int")
|
||||
orbs[n] = true
|
||||
end
|
||||
end
|
||||
en_data = EnemyDataMom({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
orbs = orbs,
|
||||
})
|
||||
elseif worm ~= nil then
|
||||
local tx, ty = ComponentGetValue2(worm, "mTargetVec")
|
||||
en_data = EnemyDataWorm({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
tx = tx,
|
||||
ty = ty,
|
||||
})
|
||||
elseif math.abs(vx) < 0.01 and math.abs(vy) < 0.01 then
|
||||
en_data = EnemyDataNoMotion({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
})
|
||||
elseif EntityGetFirstComponentIncludingDisabled(enemy_id, "AdvancedFishAIComponent") ~= nil then
|
||||
en_data = EnemyDataFish({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
r = math.floor((rot % FULL_TURN) / FULL_TURN * 255),
|
||||
})
|
||||
else
|
||||
en_data = EnemyData({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
})
|
||||
end
|
||||
|
||||
local wand
|
||||
local inv = EntityGetFirstComponentIncludingDisabled(enemy_id, "Inventory2Component")
|
||||
if inv ~= nil then
|
||||
local item = ComponentGetValue2(inv, "mActualActiveItem")
|
||||
if item ~= nil and EntityGetIsAlive(item) then
|
||||
if not EntityHasTag(item, "ew_global_item") then
|
||||
item_sync.make_item_global(item)
|
||||
else
|
||||
wand = item_sync.get_global_item_id(item)
|
||||
if wand == nil then
|
||||
EntityRemoveTag(item, "ew_global_item")
|
||||
goto continue
|
||||
end
|
||||
if not item_sync.is_my_item(wand) then
|
||||
item_sync.take_authority(wand)
|
||||
end
|
||||
was_held[wand] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local effect_data = effect_sync.get_sync_data(enemy_id, true)
|
||||
|
||||
local has_laser
|
||||
local animations = {}
|
||||
|
||||
for _, sprite in ipairs(EntityGetComponent(enemy_id, "SpriteComponent") or {}) do
|
||||
local animation
|
||||
if sprite ~= nil then
|
||||
animation = ComponentGetValue2(sprite, "rect_animation")
|
||||
end
|
||||
table.insert(animations, animation)
|
||||
if ComponentHasTag(sprite, "laser_sight") then
|
||||
has_laser = true
|
||||
end
|
||||
end
|
||||
local laser
|
||||
if has_laser and EntityGetName(enemy_id) ~= "$animal_turret" then
|
||||
local ai = EntityGetFirstComponentIncludingDisabled(enemy_id, "AnimalAIComponent")
|
||||
if ai ~= nil then
|
||||
local target = ComponentGetValue2(ai, "mGreatestPrey")
|
||||
local peer = player_fns.get_player_data_by_local_entity_id(target)
|
||||
if peer ~= nil then
|
||||
laser = peer.peer_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local dont_cull = EntityGetFirstComponent(enemy_id, "BossHealthBarComponent") ~= nil
|
||||
or worm ~= nil
|
||||
or EntityHasTag(enemy_id, "seed_f")
|
||||
or EntityHasTag(enemy_id, "seed_e")
|
||||
or EntityHasTag(enemy_id, "seed_d")
|
||||
or EntityHasTag(enemy_id, "seed_c")
|
||||
or EntityGetFilename(enemy_id) == "data/entities/buildings/essence_eater.xml"
|
||||
|
||||
local stains = stain_sync.get_stains(enemy_id)
|
||||
|
||||
table.insert(
|
||||
enemy_data_list,
|
||||
{ filename, en_data, phys_info, wand, effect_data, animations, dont_cull, death_triggers, stains, laser }
|
||||
)
|
||||
::continue::
|
||||
end
|
||||
|
||||
rpc.handle_enemy_data(enemy_data_list, first)
|
||||
first = false
|
||||
if #dead_entities > 0 then
|
||||
rpc.handle_death_data(dead_entities)
|
||||
end
|
||||
dead_entities = {}
|
||||
end
|
||||
|
||||
local function host_upload_health()
|
||||
local entities = get_sync_entities()
|
||||
|
||||
local enemy_health_list = {}
|
||||
for i, enemy_id in ipairs(entities) do
|
||||
if not world_exists_for(enemy_id) then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local hp, max_hp, has_hp = util.get_ent_health(enemy_id)
|
||||
|
||||
if has_hp then
|
||||
table.insert(
|
||||
enemy_health_list,
|
||||
HpData({
|
||||
enemy_id = enemy_id,
|
||||
hp = hp,
|
||||
max_hp = max_hp,
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
if #enemy_health_list > 0 then
|
||||
rpc.handle_enemy_health(enemy_health_list)
|
||||
end
|
||||
end
|
||||
|
||||
function enemy_sync.client_cleanup()
|
||||
local entities = get_sync_entities(true)
|
||||
for _, enemy_id in ipairs(entities) do
|
||||
if not EntityHasTag(enemy_id, "ew_replicated") then
|
||||
EntityKill(enemy_id)
|
||||
elseif not spawned_by_us[enemy_id] then
|
||||
EntityKill(enemy_id)
|
||||
end
|
||||
end
|
||||
for remote_id, enemy_data in pairs(ctx.entity_by_remote_id) do
|
||||
if frame > enemy_data.frame then
|
||||
EntityKill(enemy_data.id)
|
||||
ctx.entity_by_remote_id[remote_id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function enemy_sync.on_world_update_host()
|
||||
local rt = math.floor(tonumber(ModSettingGet("quant.ew.enemy_sync") or 2) or 2 + 0.5)
|
||||
local n = 0
|
||||
if rt == 3 then
|
||||
n = 2
|
||||
elseif rt == 2 then
|
||||
n = 1
|
||||
end
|
||||
if rt == 1 or GameGetFrameNum() % rt == n then
|
||||
enemy_sync.host_upload_entities()
|
||||
end
|
||||
if GameGetFrameNum() % 10 == 5 then
|
||||
host_upload_health()
|
||||
end
|
||||
for wand, _ in pairs(was_held) do
|
||||
if EntityGetRootEntity(wand) == wand then
|
||||
was_held[wand] = nil
|
||||
if item_sync.is_my_item(item_sync.get_global_item_id(wand)) then
|
||||
item_sync.make_item_global(wand)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function enemy_sync.on_world_update_client()
|
||||
if GameGetFrameNum() % 12 == 1 then
|
||||
enemy_sync.client_cleanup()
|
||||
end
|
||||
if GameGetFrameNum() % (60 * 60) == 1 then
|
||||
times_spawned_last_minute = {}
|
||||
end
|
||||
end
|
||||
|
||||
local kolmi_spawn
|
||||
|
||||
local function sync_enemy(enemy_info_raw, force_no_cull, host_fps)
|
||||
local filename = enemy_info_raw[1]
|
||||
filename = constants.interned_index_to_filename[filename] or filename
|
||||
|
||||
local en_data = enemy_info_raw[2]
|
||||
local dont_cull = enemy_info_raw[7]
|
||||
local death_triggers = enemy_info_raw[8]
|
||||
local stains = enemy_info_raw[9]
|
||||
local has_laser = enemy_info_raw[10]
|
||||
local remote_enemy_id = en_data.enemy_id
|
||||
local x, y = en_data.x, en_data.y
|
||||
if not force_no_cull and not dont_cull then
|
||||
local my_x, my_y = EntityGetTransform(ctx.my_player.entity)
|
||||
if my_x == nil then
|
||||
goto continue
|
||||
end
|
||||
local c_x, c_y = GameGetCameraPos()
|
||||
local dx, dy = my_x - x, my_y - y
|
||||
local cdx, cdy = c_x - x, c_y - y
|
||||
if
|
||||
dx * dx + dy * dy > DISTANCE_LIMIT * DISTANCE_LIMIT
|
||||
and cdx * cdx + cdy * cdy > DISTANCE_LIMIT * DISTANCE_LIMIT
|
||||
then
|
||||
if ctx.entity_by_remote_id[remote_enemy_id] ~= nil then
|
||||
EntityKill(ctx.entity_by_remote_id[remote_enemy_id].id)
|
||||
ctx.entity_by_remote_id[remote_enemy_id] = nil
|
||||
end
|
||||
unsynced_enemys[remote_enemy_id] = enemy_info_raw
|
||||
goto continue
|
||||
else
|
||||
unsynced_enemys[remote_enemy_id] = nil
|
||||
end
|
||||
else
|
||||
unsynced_enemys[remote_enemy_id] = nil
|
||||
end
|
||||
local vx = 0
|
||||
local vy = 0
|
||||
if ffi.typeof(en_data) ~= EnemyDataNoMotion then
|
||||
vx, vy = en_data.vx, en_data.vy
|
||||
end
|
||||
local phys_infos = enemy_info_raw[3]
|
||||
local gid = enemy_info_raw[4]
|
||||
local effects = enemy_info_raw[5]
|
||||
local animation = enemy_info_raw[6]
|
||||
local has_died = filename == nil
|
||||
|
||||
local frame_now = GameGetFrameNum()
|
||||
|
||||
--[[if confirmed_kills[remote_enemy_id] then
|
||||
goto continue
|
||||
end]]
|
||||
|
||||
if
|
||||
ctx.entity_by_remote_id[remote_enemy_id] ~= nil
|
||||
and not EntityGetIsAlive(ctx.entity_by_remote_id[remote_enemy_id].id)
|
||||
then
|
||||
ctx.entity_by_remote_id[remote_enemy_id] = nil
|
||||
end
|
||||
|
||||
if ctx.entity_by_remote_id[remote_enemy_id] == nil then
|
||||
if filename == nil or filename == "" or not ModDoesFileExist(filename) then
|
||||
goto continue
|
||||
end
|
||||
times_spawned_last_minute[remote_enemy_id] = (times_spawned_last_minute[remote_enemy_id] or 0) + 1
|
||||
if times_spawned_last_minute[remote_enemy_id] > 5 then
|
||||
if times_spawned_last_minute[remote_enemy_id] == 6 then
|
||||
print("Entity has been spawned again more than 5 times in last minute, skipping " .. filename)
|
||||
end
|
||||
goto continue
|
||||
end
|
||||
local enemy_id
|
||||
enemy_id = EntityLoad(filename, x, y)
|
||||
if enemy_id == nil then
|
||||
print("entity is nil " .. tostring(filename))
|
||||
goto continue
|
||||
end
|
||||
spawned_by_us[enemy_id] = true
|
||||
EntityAddTag(enemy_id, "ew_replicated")
|
||||
EntityAddTag(enemy_id, "polymorphable_NOT")
|
||||
for _, com in ipairs(EntityGetComponent(enemy_id, "LuaComponent") or {}) do
|
||||
local script = ComponentGetValue2(com, "script_damage_received")
|
||||
if
|
||||
(
|
||||
script ~= nil
|
||||
and (
|
||||
script == "data/scripts/animals/leader_damage.lua"
|
||||
or script == "data/scripts/animals/giantshooter_death.lua"
|
||||
or script == "data/scripts/animals/blob_damage.lua"
|
||||
)
|
||||
)
|
||||
or ComponentGetValue2(com, "script_source_file")
|
||||
== "data/scripts/props/suspended_container_physics_objects.lua"
|
||||
then
|
||||
EntityRemoveComponent(enemy_id, com)
|
||||
end
|
||||
end
|
||||
EntityAddComponent2(enemy_id, "LuaComponent", {
|
||||
_tags = "ew_immortal",
|
||||
script_damage_about_to_be_received = "mods/quant.ew/files/resource/cbs/immortal.lua",
|
||||
})
|
||||
local damage_component = EntityGetFirstComponentIncludingDisabled(enemy_id, "DamageModelComponent")
|
||||
if damage_component and damage_component ~= 0 then
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", true)
|
||||
end
|
||||
for _, name in ipairs({
|
||||
"AnimalAIComponent",
|
||||
"PhysicsAIComponent",
|
||||
"CameraBoundComponent",
|
||||
"AdvancedFishAIComponent",
|
||||
"AIAttackComponent",
|
||||
}) do
|
||||
local ai_component = EntityGetFirstComponentIncludingDisabled(enemy_id, name)
|
||||
if ai_component ~= 0 then
|
||||
EntityRemoveComponent(enemy_id, ai_component)
|
||||
end
|
||||
end
|
||||
ctx.entity_by_remote_id[remote_enemy_id] = { id = enemy_id, frame = frame_now }
|
||||
|
||||
for _, phys_component in ipairs(EntityGetComponent(enemy_id, "PhysicsBody2Component") or {}) do
|
||||
if phys_component ~= nil and phys_component ~= 0 then
|
||||
ComponentSetValue2(phys_component, "destroy_body_if_entity_destroyed", true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Make sure stuff doesn't decide to explode on clients by itself.
|
||||
local expl_component = EntityGetFirstComponent(enemy_id, "ExplodeOnDamageComponent")
|
||||
if expl_component ~= nil and expl_component ~= 0 then
|
||||
ComponentSetValue2(expl_component, "explode_on_damage_percent", 0)
|
||||
ComponentSetValue2(expl_component, "physics_body_modified_death_probability", 0)
|
||||
ComponentSetValue2(expl_component, "explode_on_death_percent", 0)
|
||||
end
|
||||
local pick_up = EntityGetFirstComponentIncludingDisabled(enemy_id, "ItemPickUpperComponent")
|
||||
if pick_up ~= nil then
|
||||
EntityRemoveComponent(enemy_id, pick_up)
|
||||
end
|
||||
for _, sprite in pairs(EntityGetComponent(enemy_id, "SpriteComponent", "character") or {}) do
|
||||
ComponentRemoveTag(sprite, "character")
|
||||
end
|
||||
|
||||
local ghost = EntityGetFirstComponentIncludingDisabled(enemy_id, "GhostComponent")
|
||||
if ghost ~= nil then
|
||||
ComponentSetValue2(ghost, "die_if_no_home", false)
|
||||
end
|
||||
if not EntityHasTag(enemy_id, "effectable_prop") then
|
||||
util.make_ephemerial(enemy_id)
|
||||
end
|
||||
end
|
||||
|
||||
local enemy_data_new = ctx.entity_by_remote_id[remote_enemy_id]
|
||||
enemy_data_new.frame = frame_now
|
||||
local enemy_id = enemy_data_new.id
|
||||
|
||||
if not has_died then
|
||||
local laser = EntityGetFirstComponentIncludingDisabled(enemy_id, "LaserEmitterComponent", "ew_laser")
|
||||
if has_laser then
|
||||
if laser == nil then
|
||||
laser = EntityAddComponent2(enemy_id, "LaserEmitterComponent", { _tags = "ew_laser" })
|
||||
ComponentObjectSetValue2(laser, "laser", "max_cell_durability_to_destroy", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "damage_to_cells", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "max_length", 1024)
|
||||
ComponentObjectSetValue2(laser, "laser", "beam_radius", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "beam_particle_chance", 80)
|
||||
ComponentObjectSetValue2(laser, "laser", "beam_particle_fade", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "hit_particle_chance", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "audio_enabled", false)
|
||||
ComponentObjectSetValue2(laser, "laser", "damage_to_entities", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "beam_particle_type", 225)
|
||||
end
|
||||
local target = ctx.players[has_laser].entity
|
||||
local lx, ly = EntityGetTransform(target)
|
||||
if lx ~= nil then
|
||||
local did_hit, _, _ = RaytracePlatforms(x, y, lx, ly)
|
||||
ComponentSetValue2(laser, "is_emitting", not did_hit)
|
||||
if not did_hit then
|
||||
local dy = ly - y
|
||||
local dx = lx - x
|
||||
local theta = math.atan2(dy, dx)
|
||||
ComponentSetValue2(laser, "laser_angle_add_rad", theta)
|
||||
ComponentObjectSetValue2(laser, "laser", "max_length", math.sqrt(dx * dx + dy * dy))
|
||||
end
|
||||
end
|
||||
elseif laser ~= nil then
|
||||
ComponentSetValue2(laser, "is_emitting", false)
|
||||
end
|
||||
if not util.set_phys_info(enemy_id, phys_infos, host_fps) or enemy_id == kolmi_spawn then
|
||||
local m = host_fps / ctx.my_player.fps
|
||||
vx, vy = vx * m, vy * m
|
||||
local character_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "CharacterDataComponent")
|
||||
if character_data ~= nil then
|
||||
ComponentSetValue2(character_data, "mVelocity", vx, vy)
|
||||
else
|
||||
local velocity_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "VelocityComponent")
|
||||
if velocity_data ~= nil then
|
||||
ComponentSetValue2(velocity_data, "mVelocity", vx, vy)
|
||||
end
|
||||
end
|
||||
if ffi.typeof(en_data) == EnemyDataFish then
|
||||
EntitySetTransform(enemy_id, x, y, en_data.r / 255 * FULL_TURN)
|
||||
else
|
||||
EntitySetTransform(enemy_id, x, y)
|
||||
end
|
||||
end
|
||||
local worm = EntityGetFirstComponentIncludingDisabled(enemy_id, "WormAIComponent")
|
||||
or EntityGetFirstComponentIncludingDisabled(enemy_id, "BossDragonComponent")
|
||||
if worm ~= nil and ffi.typeof(en_data) == EnemyDataWorm then
|
||||
local tx, ty = en_data.tx, en_data.ty
|
||||
ComponentSetValue2(worm, "mTargetVec", tx, ty)
|
||||
end
|
||||
if ffi.typeof(en_data) == EnemyDataKolmi and en_data.enabled then
|
||||
if kolmi_spawn ~= enemy_id then
|
||||
for _, c in ipairs(EntityGetComponentIncludingDisabled(enemy_id, "LuaComponent") or {}) do
|
||||
EntityRemoveComponent(enemy_id, c)
|
||||
end
|
||||
kolmi_spawn = enemy_id
|
||||
end
|
||||
EntitySetComponentsWithTagEnabled(enemy_id, "enabled_at_start", false)
|
||||
EntitySetComponentsWithTagEnabled(enemy_id, "disabled_at_start", true)
|
||||
for i, leg in ipairs(EntityGetAllChildren(enemy_id, "foot")) do
|
||||
local limb = EntityGetFirstComponentIncludingDisabled(leg, "IKLimbComponent")
|
||||
ComponentSetValue2(limb, "end_position", en_data.legs[2 * i - 2], en_data.legs[2 * i - 1])
|
||||
end
|
||||
end
|
||||
|
||||
local indexed = {}
|
||||
for _, com in ipairs(EntityGetComponent(enemy_id, "LuaComponent") or {}) do
|
||||
local script = ComponentGetValue2(com, "script_death")
|
||||
local has = false
|
||||
for _, inx in ipairs(death_triggers) do
|
||||
local script2 = constants.interned_index_to_filename[inx] or inx
|
||||
if script == script2 then
|
||||
has = true
|
||||
indexed[script] = true
|
||||
end
|
||||
end
|
||||
if not has then
|
||||
ComponentSetValue2(com, "script_death", "")
|
||||
end
|
||||
end
|
||||
for _, inx in ipairs(death_triggers) do
|
||||
local script = constants.interned_index_to_filename[inx] or inx
|
||||
if indexed[script] == nil then
|
||||
EntityAddComponent(enemy_id, "LuaComponent", {
|
||||
script_death = script,
|
||||
execute_every_n_frame = "-1",
|
||||
})
|
||||
end
|
||||
end
|
||||
if ffi.typeof(en_data) == EnemyDataMom then
|
||||
local orbs = en_data.orbs
|
||||
for _, child in ipairs(EntityGetAllChildren(enemy_id) or {}) do
|
||||
local var = EntityGetFirstComponentIncludingDisabled(child, "VariableStorageComponent")
|
||||
local damage_component = EntityGetFirstComponentIncludingDisabled(child, "DamageModelComponent")
|
||||
if EntityHasTag(child, "touchmagic_immunity") and var ~= nil then
|
||||
local n = ComponentGetValue2(var, "value_int")
|
||||
if orbs[n - 1] then
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", true)
|
||||
else
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", false)
|
||||
EntityKill(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
effect_sync.apply_effects(effects, enemy_id, true)
|
||||
if stains ~= nil then
|
||||
stain_sync.sync_stains(stains, enemy_id)
|
||||
end
|
||||
end
|
||||
|
||||
local inv = EntityGetFirstComponentIncludingDisabled(enemy_id, "Inventory2Component")
|
||||
local item
|
||||
if inv ~= nil then
|
||||
item = ComponentGetValue2(inv, "mActualActiveItem")
|
||||
end
|
||||
if gid ~= nil and (item == nil or item == 0 or not EntityGetIsAlive(item)) then
|
||||
local wand = item_sync.find_by_gid(gid)
|
||||
if wand ~= nil and EntityGetIsAlive(wand) then
|
||||
EntityAddTag(wand, "ew_client_item")
|
||||
local inventory
|
||||
for _, child in pairs(EntityGetAllChildren(enemy_id) or {}) do
|
||||
if EntityGetName(child) == "inventory_quick" then
|
||||
inventory = child
|
||||
end
|
||||
end
|
||||
if inventory == nil then
|
||||
inventory = EntityCreateNew("inventory_quick")
|
||||
EntityAddChild(enemy_id, inventory)
|
||||
end
|
||||
if EntityGetParent(wand) ~= inventory then
|
||||
if EntityGetParent(wand) ~= 0 then
|
||||
EntityRemoveFromParent(wand)
|
||||
end
|
||||
EntityAddChild(inventory, wand)
|
||||
end
|
||||
np.SetActiveHeldEntity(enemy_id, wand, false, false)
|
||||
elseif should_wait[gid] == nil or should_wait[gid] < GameGetFrameNum() then
|
||||
item_sync.rpc.request_send_again(gid)
|
||||
should_wait[gid] = GameGetFrameNum() + 15
|
||||
end
|
||||
end
|
||||
|
||||
for i, sprite in pairs(EntityGetComponent(enemy_id, "SpriteComponent") or {}) do
|
||||
if animation[i] ~= nil then
|
||||
ComponentSetValue2(sprite, "rect_animation", animation[i])
|
||||
ComponentSetValue2(sprite, "next_rect_animation", animation[i])
|
||||
end
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.handle_death_data(death_data)
|
||||
for _, remote_data in ipairs(death_data) do
|
||||
local remote_id = remote_data[1]
|
||||
--[[if confirmed_kills[remote_id] then
|
||||
GamePrint("Remote id has been killed already..?")
|
||||
goto continue
|
||||
end
|
||||
confirmed_kills[remote_id] = true]]
|
||||
local responsible_entity = 0
|
||||
local peer_data = player_fns.peer_get_player_data(remote_data[2], true)
|
||||
if peer_data ~= nil then
|
||||
responsible_entity = peer_data.entity
|
||||
elseif ctx.entity_by_remote_id[remote_data[2]] ~= nil then
|
||||
responsible_entity = ctx.entity_by_remote_id[remote_data[2]]
|
||||
end
|
||||
|
||||
if unsynced_enemys[remote_id] ~= nil then
|
||||
sync_enemy(unsynced_enemys[remote_id], true, 60)
|
||||
end
|
||||
local enemy_data = ctx.entity_by_remote_id[remote_id]
|
||||
if enemy_data ~= nil and EntityGetIsAlive(enemy_data.id) then
|
||||
local enemy_id = enemy_data.id
|
||||
local immortal = EntityGetFirstComponentIncludingDisabled(enemy_id, "LuaComponent", "ew_immortal")
|
||||
if immortal ~= 0 then
|
||||
EntityRemoveComponent(enemy_id, immortal)
|
||||
end
|
||||
local protection_component_id = GameGetGameEffect(enemy_id, "PROTECTION_ALL")
|
||||
if protection_component_id ~= 0 then
|
||||
EntitySetComponentIsEnabled(enemy_id, protection_component_id, false)
|
||||
end
|
||||
|
||||
local damage_component = EntityGetFirstComponentIncludingDisabled(enemy_id, "DamageModelComponent")
|
||||
if damage_component and damage_component ~= 0 then
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", false)
|
||||
ComponentSetValue2(damage_component, "ui_report_damage", false)
|
||||
ComponentSetValue2(damage_component, "hp", 2 ^ -38)
|
||||
end
|
||||
|
||||
-- Enable explosion back
|
||||
local expl_component = EntityGetFirstComponent(enemy_id, "ExplodeOnDamageComponent")
|
||||
if expl_component ~= nil and expl_component ~= 0 then
|
||||
ComponentSetValue2(expl_component, "explode_on_death_percent", 1)
|
||||
end
|
||||
|
||||
local current_hp = util.get_ent_health(enemy_id)
|
||||
local dmg = current_hp
|
||||
if dmg > 0 then
|
||||
EntityInflictDamage(enemy_id, dmg + 0.1, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity)
|
||||
end
|
||||
|
||||
EntityInflictDamage(enemy_id, 1000000000, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity) -- Just to be sure
|
||||
if not remote_data[3] then
|
||||
EntityKill(enemy_id)
|
||||
else
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", true)
|
||||
ComponentSetValue2(damage_component, "kill_now", true)
|
||||
end
|
||||
ctx.entity_by_remote_id[remote_id] = nil
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
function rpc.handle_enemy_data(enemy_data, is_first)
|
||||
if is_first then
|
||||
for _, n in pairs(ctx.entity_by_remote_id) do
|
||||
EntityKill(n.id)
|
||||
end
|
||||
ctx.entity_by_remote_id = {}
|
||||
end
|
||||
frame = GameGetFrameNum()
|
||||
for _, enemy_info_raw in ipairs(enemy_data) do
|
||||
sync_enemy(enemy_info_raw, false, ctx.rpc_player_data.fps)
|
||||
end
|
||||
end
|
||||
|
||||
function rpc.handle_enemy_health(enemy_health_data)
|
||||
for _, en_data in ipairs(enemy_health_data) do
|
||||
local remote_enemy_id = en_data.enemy_id
|
||||
local hp = en_data.hp
|
||||
local max_hp = en_data.max_hp
|
||||
|
||||
if
|
||||
ctx.entity_by_remote_id[remote_enemy_id] == nil
|
||||
or not EntityGetIsAlive(ctx.entity_by_remote_id[remote_enemy_id].id)
|
||||
then
|
||||
goto continue
|
||||
end
|
||||
local enemy_data = ctx.entity_by_remote_id[remote_enemy_id]
|
||||
local enemy_id = enemy_data.id
|
||||
|
||||
local current_hp = util.get_ent_health(enemy_id)
|
||||
local dmg = current_hp - hp
|
||||
if dmg > 0 then
|
||||
-- Make sure the enemy doesn't die from the next EntityInflictDamage.
|
||||
if EntityGetName(enemy_id) ~= "$animal_boss_sky" then
|
||||
util.set_ent_health(enemy_id, { dmg * 2, dmg * 2 })
|
||||
else
|
||||
util.set_ent_health(enemy_id, { hp + dmg, max_hp })
|
||||
end
|
||||
-- Deal damage, so that game displays damage numbers.
|
||||
EntityInflictDamage(enemy_id, dmg, "DAMAGE_CURSE", "", "NONE", 0, 0, GameGetWorldStateEntity())
|
||||
end
|
||||
util.set_ent_health(enemy_id, { hp, max_hp })
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
function enemy_sync.on_projectile_fired(
|
||||
shooter_id,
|
||||
projectile_id,
|
||||
initial_rng,
|
||||
position_x,
|
||||
position_y,
|
||||
target_x,
|
||||
target_y,
|
||||
send_message,
|
||||
unknown1,
|
||||
multicast_index,
|
||||
unknown3
|
||||
)
|
||||
local not_a_player = not EntityHasTag(shooter_id, "ew_no_enemy_sync")
|
||||
and not EntityHasTag(shooter_id, "player_unit")
|
||||
and not EntityHasTag(shooter_id, "ew_client")
|
||||
if not_a_player and ctx.is_host then
|
||||
local projectileComponent = EntityGetFirstComponentIncludingDisabled(projectile_id, "ProjectileComponent")
|
||||
if projectileComponent ~= nil then
|
||||
local entity_that_shot = ComponentGetValue2(projectileComponent, "mEntityThatShot")
|
||||
if entity_that_shot == 0 then
|
||||
rpc.replicate_projectile(
|
||||
util.serialize_entity(projectile_id),
|
||||
position_x,
|
||||
position_y,
|
||||
target_x,
|
||||
target_y,
|
||||
shooter_id,
|
||||
initial_rng
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.replicate_projectile(seri_ent, position_x, position_y, target_x, target_y, remote_source_ent, rng)
|
||||
if rng ~= nil then
|
||||
np.SetProjectileSpreadRNG(rng)
|
||||
end
|
||||
if ctx.entity_by_remote_id[remote_source_ent] == nil then
|
||||
return
|
||||
end
|
||||
local source_ent = ctx.entity_by_remote_id[remote_source_ent].id
|
||||
local ent = util.deserialize_entity(seri_ent)
|
||||
GameShootProjectile(source_ent, position_x, position_y, target_x, target_y, ent)
|
||||
end
|
||||
|
||||
return enemy_sync
|
|
@ -25,13 +25,8 @@ end
|
|||
|
||||
function module.on_world_initialized()
|
||||
initial_world_state_entity = GameGetWorldStateEntity()
|
||||
ewext.on_world_initialized()
|
||||
local grid_world = world_ffi.get_grid_world()
|
||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||
grid_world = tonumber(ffi.cast("intptr_t", grid_world))
|
||||
chunk_map = tonumber(ffi.cast("intptr_t", chunk_map))
|
||||
local material_list = tonumber(ffi.cast("intptr_t", world_ffi.get_material_ptr(0)))
|
||||
ewext.init_particle_world_state(grid_world, chunk_map, material_list)
|
||||
ewext.on_world_initialized(ctx.proxy_opt.world_num)
|
||||
ewext.init_particle_world_state()
|
||||
ewext.module_on_world_init()
|
||||
log = ModSettingGet("quant.ew.log_performance") or false
|
||||
ewext.set_log(log)
|
||||
|
|
|
@ -1,865 +0,0 @@
|
|||
-- Synchronizes item pickup and item drop
|
||||
ModLuaFileAppend("data/scripts/items/utility_box.lua", "mods/quant.ew/files/resource/cbs/chest_sync.lua")
|
||||
ModLuaFileAppend("data/scripts/items/chest_random.lua", "mods/quant.ew/files/resource/cbs/chest_sync.lua")
|
||||
ModLuaFileAppend("data/scripts/items/chest_random_super.lua", "mods/quant.ew/files/resource/cbs/chest_sync.lua")
|
||||
|
||||
dofile_once("data/scripts/lib/coroutines.lua")
|
||||
|
||||
local rpc = net.new_rpc_namespace()
|
||||
|
||||
local item_sync = {}
|
||||
|
||||
local pending_remove = {}
|
||||
local pickup_handlers = {}
|
||||
|
||||
local dead_entities = {}
|
||||
|
||||
local frame = {}
|
||||
|
||||
local gid_last_frame_updated = {}
|
||||
|
||||
local wait_on_send = {}
|
||||
|
||||
local wait_for_gid = {}
|
||||
|
||||
function rpc.open_chest(gid)
|
||||
if wait_for_gid[gid] == nil or wait_for_gid[gid] < 10000 then
|
||||
wait_for_gid[gid] = GameGetFrameNum() + 36000
|
||||
wait_on_send[gid] = GameGetFrameNum() + 36000
|
||||
local ent = item_sync.find_by_gid(gid)
|
||||
if ent ~= nil then
|
||||
local file
|
||||
local name = EntityGetFilename(ent)
|
||||
if name == "data/entities/items/pickup/utility_box.xml" then
|
||||
file = "data/scripts/items/utility_box.lua"
|
||||
elseif name == "data/entities/items/pickup/chest_random_super.xml" then
|
||||
file = "data/scripts/items/chest_random_super.lua"
|
||||
elseif name == "data/entities/items/pickup/chest_random.xml" then
|
||||
file = "data/scripts/items/chest_random.lua"
|
||||
end
|
||||
if file ~= nil then
|
||||
EntityAddComponent2(ent, "LuaComponent", {
|
||||
script_source_file = file,
|
||||
execute_on_added = true,
|
||||
call_init_function = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
util.add_cross_call("ew_chest_opened", function(chest_id)
|
||||
local gid = item_sync.get_global_item_id(chest_id)
|
||||
if gid ~= nil then
|
||||
wait_for_gid[gid] = GameGetFrameNum() + 36000
|
||||
wait_on_send[gid] = GameGetFrameNum() + 36000
|
||||
rpc.open_chest(gid)
|
||||
end
|
||||
end)
|
||||
|
||||
util.add_cross_call("ew_item_death_notify", function(enemy_id, responsible_id)
|
||||
local player_data = player_fns.get_player_data_by_local_entity_id(responsible_id)
|
||||
local responsible
|
||||
if player_data ~= nil then
|
||||
responsible = player_data.peer_id
|
||||
else
|
||||
responsible = responsible_id
|
||||
end
|
||||
local gid = item_sync.get_global_item_id(enemy_id)
|
||||
if gid ~= nil then
|
||||
table.insert(dead_entities, { gid, responsible })
|
||||
end
|
||||
end)
|
||||
|
||||
function item_sync.ensure_notify_component(ent)
|
||||
local notify = EntityGetFirstComponentIncludingDisabled(ent, "LuaComponent", "ew_notify_component")
|
||||
if notify == nil then
|
||||
EntityAddComponent2(ent, "LuaComponent", {
|
||||
_tags = "enabled_in_world,enabled_in_hand,enabled_in_inventory,ew_notify_component,ew_remove_on_send",
|
||||
script_throw_item = "mods/quant.ew/files/resource/cbs/item_notify.lua",
|
||||
script_item_picked_up = "mods/quant.ew/files/resource/cbs/item_notify.lua",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function mark_in_inventory(my_player)
|
||||
local items = inventory_helper.get_all_inventory_items(my_player)
|
||||
for _, ent in pairs(items) do
|
||||
if not EntityHasTag(ent, "polymorphed_player") then
|
||||
item_sync.ensure_notify_component(ent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function allocate_global_id()
|
||||
local current = tonumber(GlobalsGetValue("ew_global_item_id", "1"))
|
||||
GlobalsSetValue("ew_global_item_id", tostring(current + 1))
|
||||
return ctx.my_id .. ":" .. current
|
||||
end
|
||||
|
||||
-- Try to guess if the item is in world.
|
||||
local function is_item_on_ground(item)
|
||||
return EntityGetRootEntity(item) == item
|
||||
end
|
||||
|
||||
function item_sync.get_global_item_id(item)
|
||||
local gid = EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
|
||||
if gid == nil then
|
||||
return nil
|
||||
end
|
||||
local ret = ComponentGetValue2(gid, "value_string")
|
||||
return ret
|
||||
end
|
||||
|
||||
local function is_wand(ent)
|
||||
if ent == nil or ent == 0 then
|
||||
return false
|
||||
end
|
||||
local ability = EntityGetFirstComponentIncludingDisabled(ent, "AbilityComponent")
|
||||
if ability == nil then
|
||||
return false
|
||||
end
|
||||
return ComponentGetValue2(ability, "use_gun_script") == true
|
||||
end
|
||||
|
||||
local function is_safe_to_remove()
|
||||
return not ctx.is_wand_pickup
|
||||
end
|
||||
|
||||
function item_sync.remove_item_with_id(gid)
|
||||
local item_ent_id = item_sync.find_by_gid(gid)
|
||||
if is_safe_to_remove() or not is_wand(item_ent_id) then
|
||||
item_sync.remove_item_with_id_now(gid)
|
||||
else
|
||||
table.insert(pending_remove, gid)
|
||||
EntitySetTransform(item_ent_id, 0, 0)
|
||||
util.make_ephemerial(item_ent_id)
|
||||
end
|
||||
end
|
||||
|
||||
local find_by_gid_cache = {}
|
||||
function item_sync.find_by_gid(gid)
|
||||
if find_by_gid_cache[gid] ~= nil then
|
||||
if
|
||||
EntityGetIsAlive(find_by_gid_cache[gid])
|
||||
and EntityHasTag(find_by_gid_cache[gid], "ew_global_item")
|
||||
and is_item_on_ground(find_by_gid_cache[gid])
|
||||
then
|
||||
return find_by_gid_cache[gid]
|
||||
else
|
||||
find_by_gid_cache[gid] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--print("find_by_gid: searching")
|
||||
|
||||
local candidate
|
||||
for _, item in ipairs(EntityGetWithTag("ew_global_item") or {}) do
|
||||
local i_gid = item_sync.get_global_item_id(item)
|
||||
if i_gid ~= nil then
|
||||
find_by_gid_cache[i_gid] = item
|
||||
if i_gid == gid then
|
||||
if is_item_on_ground(item) then
|
||||
return item
|
||||
else
|
||||
candidate = item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return candidate
|
||||
end
|
||||
|
||||
function item_sync.remove_item_with_id_now(gid)
|
||||
local item = item_sync.find_by_gid(gid)
|
||||
if item ~= nil then
|
||||
find_by_gid_cache[gid] = nil
|
||||
for _, audio in ipairs(EntityGetComponent(item, "AudioComponent") or {}) do
|
||||
if string.sub(ComponentGetValue2(audio, "event_root"), 1, 10) == "collision/" then
|
||||
EntitySetComponentIsEnabled(item, audio, false)
|
||||
end
|
||||
end
|
||||
EntityKill(item)
|
||||
return item
|
||||
end
|
||||
end
|
||||
|
||||
function item_sync.host_localize_item(gid, peer_id)
|
||||
if ctx.item_prevent_localize[gid] then
|
||||
print("Item localize for " .. gid .. " prevented")
|
||||
return
|
||||
end
|
||||
ctx.item_prevent_localize[gid] = true
|
||||
|
||||
if table.contains(pending_remove, gid) then
|
||||
print("Item localize prevented, already taken")
|
||||
return
|
||||
end
|
||||
|
||||
local item_ent_id = item_sync.find_by_gid(gid)
|
||||
if item_ent_id ~= nil then
|
||||
for _, handler in ipairs(pickup_handlers) do
|
||||
handler(item_ent_id)
|
||||
end
|
||||
end
|
||||
if peer_id ~= ctx.my_id then
|
||||
item_sync.remove_item_with_id(gid)
|
||||
end
|
||||
rpc.item_localize(peer_id, gid)
|
||||
if peer_id == ctx.my_id then
|
||||
item_sync.take_authority(gid)
|
||||
else
|
||||
rpc.hand_authority_over_to(peer_id, gid)
|
||||
end
|
||||
end
|
||||
|
||||
local function make_global(item, give_authority_to)
|
||||
if not EntityGetIsAlive(item) then
|
||||
print("Thrown item vanished before we could send it")
|
||||
return
|
||||
end
|
||||
item_sync.ensure_notify_component(item)
|
||||
local gid_component =
|
||||
EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
|
||||
local gid
|
||||
if gid_component == nil then
|
||||
gid = allocate_global_id()
|
||||
if give_authority_to ~= nil then
|
||||
gid = give_authority_to .. ":" .. gid
|
||||
end
|
||||
EntityAddComponent2(item, "VariableStorageComponent", {
|
||||
_tags = "enabled_in_world,enabled_in_hand,enabled_in_inventory,ew_global_item_id",
|
||||
value_string = gid,
|
||||
})
|
||||
else
|
||||
gid = ComponentGetValue2(gid_component, "value_string")
|
||||
end
|
||||
--local vel = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
|
||||
--if vel then
|
||||
-- local vx, vy = ComponentGetValue2(vel, "mVelocity")
|
||||
--end
|
||||
|
||||
local item_data = inventory_helper.serialize_single_item(item)
|
||||
item_data.gid = gid
|
||||
|
||||
local _, _, has_hp = util.get_ent_health(item)
|
||||
if has_hp then
|
||||
util.ensure_component_present(item, "LuaComponent", "ew_item_death_notify", {
|
||||
script_death = "mods/quant.ew/files/resource/cbs/item_death_notify.lua",
|
||||
})
|
||||
end
|
||||
|
||||
ctx.item_prevent_localize[gid] = false
|
||||
rpc.item_globalize(item_data)
|
||||
if wait_on_send[gid] ~= nil then
|
||||
wait_on_send[gid] = GameGetFrameNum() + 30
|
||||
end
|
||||
end
|
||||
|
||||
function item_sync.make_item_global(item, instant, give_authority_to)
|
||||
EntityAddTag(item, "ew_global_item")
|
||||
if instant then
|
||||
make_global(item, give_authority_to)
|
||||
else
|
||||
async(function()
|
||||
wait(1) -- Wait 1 frame so that game sets proper velocity.
|
||||
make_global(item, give_authority_to)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function remove_client_items_from_world()
|
||||
if GameGetFrameNum() % 5 ~= 3 then
|
||||
return
|
||||
end
|
||||
for _, item in ipairs(EntityGetWithTag("ew_client_item")) do
|
||||
if is_item_on_ground(item) then
|
||||
item_sync.remove_item_with_id(item_sync.get_global_item_id(item))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function is_peers_item(gid, peer)
|
||||
if gid == nil then
|
||||
return false
|
||||
end
|
||||
return string.sub(gid, 1, 16) == peer
|
||||
end
|
||||
|
||||
function item_sync.is_my_item(gid)
|
||||
if gid == nil then
|
||||
return false
|
||||
end
|
||||
return string.sub(gid, 1, 16) == ctx.my_id
|
||||
end
|
||||
|
||||
function item_sync.take_authority(gid)
|
||||
if gid ~= nil and not item_sync.is_my_item(gid) then
|
||||
local new_id = allocate_global_id()
|
||||
rpc.give_authority_to(gid, new_id)
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_everywhere()
|
||||
rpc.opts_reliable()
|
||||
function rpc.give_authority_to(gid, new_id)
|
||||
local item
|
||||
local to_remove = {}
|
||||
for _, ent in ipairs(EntityGetWithTag("ew_global_item") or {}) do
|
||||
local i_gid = item_sync.get_global_item_id(ent)
|
||||
if i_gid == gid then
|
||||
if item == nil then
|
||||
item = ent
|
||||
else
|
||||
table.insert(to_remove, gid)
|
||||
end
|
||||
end
|
||||
end
|
||||
find_by_gid_cache[gid] = nil
|
||||
if table.contains(pending_remove, gid) then
|
||||
for i, id in ipairs(pending_remove) do
|
||||
if id == gid then
|
||||
table.remove(pending_remove, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
table.insert(pending_remove, new_id)
|
||||
end
|
||||
for _, g in ipairs(to_remove) do
|
||||
item_sync.remove_item_with_id(g)
|
||||
end
|
||||
if item ~= nil then
|
||||
find_by_gid_cache[new_id] = item
|
||||
local var = EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
|
||||
ComponentSetValue2(var, "value_string", new_id)
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.hand_authority_over_to(peer_id, gid)
|
||||
if peer_id == ctx.my_id then
|
||||
if item_sync.find_by_gid(gid) ~= nil then
|
||||
item_sync.take_authority(gid)
|
||||
elseif wait_for_gid[gid] == nil then
|
||||
rpc.request_send_again(gid)
|
||||
wait_for_gid[gid] = GameGetFrameNum() + 300
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.handle_death_data(death_data)
|
||||
for _, remote_data in ipairs(death_data) do
|
||||
local remote_id = remote_data[1]
|
||||
local responsible_entity = 0
|
||||
local peer_data = player_fns.peer_get_player_data(remote_data[2], true)
|
||||
if peer_data ~= nil then
|
||||
responsible_entity = peer_data.entity
|
||||
elseif ctx.entity_by_remote_id[remote_data[2]] ~= nil then
|
||||
responsible_entity = ctx.entity_by_remote_id[remote_data[2]]
|
||||
end
|
||||
|
||||
local enemy_id = item_sync.find_by_gid(remote_id)
|
||||
if enemy_id ~= nil and EntityGetIsAlive(enemy_id) then
|
||||
local immortal = EntityGetFirstComponentIncludingDisabled(enemy_id, "LuaComponent", "ew_immortal")
|
||||
if immortal ~= 0 then
|
||||
EntityRemoveComponent(enemy_id, immortal)
|
||||
end
|
||||
local protection_component_id = GameGetGameEffect(enemy_id, "PROTECTION_ALL")
|
||||
if protection_component_id ~= 0 then
|
||||
EntitySetComponentIsEnabled(enemy_id, protection_component_id, false)
|
||||
end
|
||||
|
||||
local damage_component = EntityGetFirstComponentIncludingDisabled(enemy_id, "DamageModelComponent")
|
||||
if damage_component and damage_component ~= 0 then
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", false)
|
||||
ComponentSetValue2(damage_component, "ui_report_damage", false)
|
||||
ComponentSetValue2(damage_component, "hp", 2 ^ -38)
|
||||
end
|
||||
|
||||
-- Enable explosion back
|
||||
local expl_component = EntityGetFirstComponent(enemy_id, "ExplodeOnDamageComponent")
|
||||
if expl_component ~= nil and expl_component ~= 0 then
|
||||
ComponentSetValue2(expl_component, "explode_on_death_percent", 1)
|
||||
end
|
||||
|
||||
local current_hp = util.get_ent_health(enemy_id)
|
||||
local dmg = current_hp
|
||||
if dmg > 0 then
|
||||
EntityInflictDamage(enemy_id, dmg + 0.1, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity)
|
||||
end
|
||||
|
||||
EntityInflictDamage(enemy_id, 1000000000, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity) -- Just to be sure
|
||||
EntityKill(enemy_id)
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
local DISTANCE_LIMIT = 128 * 4
|
||||
|
||||
local ignore = {}
|
||||
|
||||
local function send_item_positions(all)
|
||||
local position_data = {}
|
||||
local cx, cy = GameGetCameraPos()
|
||||
local cap = {}
|
||||
for _, item in ipairs(EntityGetWithTag("ew_global_item")) do
|
||||
local gid = item_sync.get_global_item_id(item)
|
||||
-- Only send info about items created by us.
|
||||
local tg = EntityHasTag(item, "ew_no_spawn")
|
||||
if gid ~= nil and item_sync.is_my_item(gid) and (is_item_on_ground(item) or tg) then
|
||||
local x, y = EntityGetTransform(item)
|
||||
local dx, dy = x - cx, y - cy
|
||||
if
|
||||
not tg
|
||||
and (ignore[gid] == nil or ignore[gid] < GameGetFrameNum())
|
||||
and dx * dx + dy * dy > 4 * DISTANCE_LIMIT * DISTANCE_LIMIT
|
||||
then
|
||||
local ent = EntityGetClosestWithTag(x, y, "ew_peer")
|
||||
local nx, ny
|
||||
local ndx, ndy
|
||||
if ent ~= 0 then
|
||||
nx, ny = EntityGetTransform(ent)
|
||||
ndx, ndy = x - nx, y - ny
|
||||
end
|
||||
if ent == 0 or ndx * ndx + ndy * ndy > DISTANCE_LIMIT * DISTANCE_LIMIT then
|
||||
ent = EntityGetClosestWithTag(x, y, "polymorphed_player")
|
||||
if ent ~= 0 then
|
||||
nx, ny = EntityGetTransform(ent)
|
||||
ndx, ndy = x - nx, y - ny
|
||||
end
|
||||
if ent == 0 or ndx * ndx + ndy * ndy > DISTANCE_LIMIT * DISTANCE_LIMIT then
|
||||
ignore[gid] = GameGetFrameNum() + 60
|
||||
goto continue
|
||||
end
|
||||
end
|
||||
local data = player_fns.get_player_data_by_local_entity_id(ent)
|
||||
if data ~= nil then
|
||||
local peer = data.peer_id
|
||||
rpc.hand_authority_over_to(peer, gid)
|
||||
ignore[gid] = nil
|
||||
else
|
||||
ignore[gid] = GameGetFrameNum() + 60
|
||||
end
|
||||
else
|
||||
local phys_info = util.get_phys_info(item, true)
|
||||
if
|
||||
tg
|
||||
or (
|
||||
(phys_info[1][1] ~= nil or phys_info[2][1] ~= nil or all)
|
||||
and (
|
||||
#EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "ew_peer") ~= 0
|
||||
or #EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "polymorphed_player") ~= 0
|
||||
)
|
||||
)
|
||||
then
|
||||
local costcom = EntityGetFirstComponentIncludingDisabled(item, "ItemCostComponent")
|
||||
local cost = 0
|
||||
if costcom ~= nil then
|
||||
cost = ComponentGetValue2(costcom, "cost")
|
||||
local vel = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
|
||||
if math.abs(cx - x) < DISTANCE_LIMIT and math.abs(cy - y) < DISTANCE_LIMIT then
|
||||
if
|
||||
EntityGetFirstComponentIncludingDisabled(
|
||||
item,
|
||||
"VariableStorageComponent",
|
||||
"ew_try_stealable"
|
||||
) ~= nil
|
||||
then
|
||||
ComponentSetValue2(costcom, "stealable", true)
|
||||
ComponentSetValue2(vel, "gravity_y", 400)
|
||||
elseif
|
||||
EntityGetFirstComponentIncludingDisabled(
|
||||
item,
|
||||
"VariableStorageComponent",
|
||||
"ew_try_float"
|
||||
) ~= nil
|
||||
then
|
||||
ComponentSetValue2(vel, "gravity_y", 400)
|
||||
end
|
||||
else
|
||||
if
|
||||
EntityGetFirstComponentIncludingDisabled(
|
||||
item,
|
||||
"VariableStorageComponent",
|
||||
"ew_try_stealable"
|
||||
) ~= nil
|
||||
then
|
||||
ComponentSetValue2(costcom, "stealable", false)
|
||||
ComponentSetValue2(vel, "gravity_y", 0)
|
||||
elseif
|
||||
EntityGetFirstComponentIncludingDisabled(
|
||||
item,
|
||||
"VariableStorageComponent",
|
||||
"ew_try_float"
|
||||
) ~= nil
|
||||
then
|
||||
ComponentSetValue2(vel, "gravity_y", 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
position_data[gid] = { x, y, phys_info, cost }
|
||||
if EntityHasTag(item, "egg_item") then
|
||||
if
|
||||
EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_egg") ~= nil
|
||||
then
|
||||
position_data[gid][5] = true
|
||||
end
|
||||
elseif tg then
|
||||
local f = EntityGetFilename(item)
|
||||
if cap[f] == nil then
|
||||
cap[f] = tonumber(ModSettingGet("quant.ew.rocks") or 16) or 16
|
||||
end
|
||||
if cap[f] == 0 then
|
||||
position_data[gid] = nil
|
||||
goto continue
|
||||
end
|
||||
cap[f] = cap[f] - 1
|
||||
position_data[gid][5] = false
|
||||
local velocity = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
|
||||
if velocity ~= nil then
|
||||
local vx, vy = ComponentGetValue2(velocity, "mVelocity")
|
||||
position_data[gid][6] = { vx, vy }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
rpc.update_positions(position_data, all)
|
||||
if #dead_entities > 0 then
|
||||
rpc.handle_death_data(dead_entities)
|
||||
end
|
||||
dead_entities = {}
|
||||
end
|
||||
|
||||
util.add_cross_call("ew_thrown", function(thrown_item)
|
||||
if
|
||||
thrown_item ~= nil
|
||||
and (item_sync.get_global_item_id(thrown_item) == nil or item_sync.is_my_item(
|
||||
item_sync.get_global_item_id(thrown_item)
|
||||
))
|
||||
and EntityGetFirstComponentIncludingDisabled(thrown_item, "VariableStorageComponent", "ew_egg") == nil
|
||||
then
|
||||
item_sync.make_item_global(thrown_item)
|
||||
end
|
||||
end)
|
||||
|
||||
util.add_cross_call("ew_picked", function(picked_item)
|
||||
if picked_item ~= nil and EntityHasTag(picked_item, "ew_global_item") then
|
||||
local gid = item_sync.get_global_item_id(picked_item)
|
||||
if gid ~= nil then
|
||||
if ctx.is_host then
|
||||
item_sync.host_localize_item(gid, ctx.my_id)
|
||||
else
|
||||
rpc.item_localize_req(gid)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function item_sync.on_world_update()
|
||||
-- TODO check that we not removing item we are going to pick now, instead of checking if picker gui is open.
|
||||
if is_safe_to_remove() then
|
||||
if #pending_remove > 0 then
|
||||
local gid = table.remove(pending_remove)
|
||||
item_sync.remove_item_with_id_now(gid)
|
||||
end
|
||||
end
|
||||
if GameGetFrameNum() % 120 == 35 then
|
||||
for _, ent in ipairs(EntityGetWithTag("mimic_potion")) do
|
||||
if not EntityHasTag(ent, "polymorphed_player") and is_item_on_ground(ent) then
|
||||
if not EntityHasTag(ent, "ew_global_item") then
|
||||
if ctx.is_host then
|
||||
item_sync.make_item_global(ent)
|
||||
else
|
||||
EntityKill(ent)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, wand in ipairs(EntityGetWithTag("wand")) do
|
||||
local com = EntityGetFirstComponentIncludingDisabled(wand, "ItemComponent")
|
||||
if com ~= nil then
|
||||
ComponentSetValue2(com, "item_pickup_radius", 256)
|
||||
end
|
||||
end
|
||||
end
|
||||
local rt = math.floor(tonumber(ModSettingGet("quant.ew.item_sync") or 4) or 4 + 0.5)
|
||||
local n = 0
|
||||
if rt == 5 then
|
||||
n = 3
|
||||
elseif rt == 3 then
|
||||
n = 1
|
||||
elseif rt == 4 then
|
||||
n = 2
|
||||
end
|
||||
if GameGetFrameNum() % 60 == 3 then
|
||||
send_item_positions(true)
|
||||
elseif rt == 1 or GameGetFrameNum() % rt == n then
|
||||
send_item_positions(false)
|
||||
end
|
||||
if GameGetFrameNum() % 30 == 23 then
|
||||
for gid, num in pairs(wait_for_gid) do
|
||||
if num < GameGetFrameNum() then
|
||||
wait_for_gid[gid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if GameGetFrameNum() % 5 == 4 then
|
||||
mark_in_inventory(ctx.my_player)
|
||||
end
|
||||
|
||||
remove_client_items_from_world()
|
||||
end
|
||||
|
||||
function item_sync.on_should_send_updates()
|
||||
if not ctx.is_host then
|
||||
return
|
||||
end
|
||||
local item_list = {}
|
||||
for _, item in ipairs(EntityGetWithTag("ew_global_item") or {}) do
|
||||
if is_item_on_ground(item) and not EntityHasTag(item, "mimic_potion") then
|
||||
local item_data = inventory_helper.serialize_single_item(item)
|
||||
local gid = item_sync.get_global_item_id(item)
|
||||
if gid ~= nil then
|
||||
item_data.gid = gid
|
||||
table.insert(item_list, item_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
rpc.initial_items(item_list)
|
||||
end
|
||||
|
||||
function item_sync.on_draw_debug_window(imgui)
|
||||
local mx, my = DEBUG_GetMouseWorld()
|
||||
local ent = EntityGetClosestWithTag(mx, my, "ew_global_item")
|
||||
if ent ~= nil and ent ~= 0 then
|
||||
if imgui.CollapsingHeader("Item gid") then
|
||||
local x, y = EntityGetTransform(ent)
|
||||
GameCreateSpriteForXFrames("mods/quant.ew/files/resource/debug/marker.png", x, y, true, 0, 0, 1, true)
|
||||
local gid = item_sync.get_global_item_id(ent)
|
||||
imgui.Text("GID: " .. tostring(gid))
|
||||
local prevented = ctx.item_prevent_localize[gid]
|
||||
if prevented then
|
||||
imgui.Text("Localize prevented")
|
||||
else
|
||||
imgui.Text("Localize allowed")
|
||||
end
|
||||
local on_ground, reason = is_item_on_ground(ent)
|
||||
if on_ground then
|
||||
imgui.Text("On ground: " .. reason)
|
||||
else
|
||||
imgui.Text("Not on ground: " .. reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function add_stuff_to_globalized_item(item, gid)
|
||||
EntityAddTag(item, "ew_global_item")
|
||||
item_sync.ensure_notify_component(item)
|
||||
local gid_c = EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
|
||||
if gid_c == nil then
|
||||
EntityAddComponent2(item, "VariableStorageComponent", {
|
||||
_tags = "ew_global_item_id",
|
||||
value_string = gid,
|
||||
})
|
||||
else
|
||||
ComponentSetValue2(gid_c, "value_string", gid)
|
||||
end
|
||||
ctx.item_prevent_localize[gid] = false
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.initial_items(item_list)
|
||||
-- Only run once ever, as it tends to duplicate items otherwise
|
||||
if GameHasFlagRun("ew_initial_items") then
|
||||
return
|
||||
end
|
||||
GameAddFlagRun("ew_initial_items")
|
||||
for _, item_data in ipairs(item_list) do
|
||||
local item = item_sync.find_by_gid(item_data.gid)
|
||||
if item == nil then
|
||||
local item_new = inventory_helper.deserialize_single_item(item_data)
|
||||
add_stuff_to_globalized_item(item_new, item_data.gid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.item_globalize(item_data)
|
||||
if wait_for_gid[item_data.gid] ~= nil then
|
||||
if wait_for_gid[item_data.gid] > GameGetFrameNum() + 10000 then
|
||||
return
|
||||
end
|
||||
wait_for_gid[item_data.gid] = GameGetFrameNum() + 30
|
||||
end
|
||||
local a = item_sync.find_by_gid(item_data.gid)
|
||||
if is_safe_to_remove() or not is_wand(a) then
|
||||
local k = item_sync.remove_item_with_id_now(item_data.gid)
|
||||
local n = item_sync.find_by_gid(item_data.gid)
|
||||
if n ~= nil and k ~= n then
|
||||
return
|
||||
end
|
||||
else
|
||||
local n = item_sync.find_by_gid(item_data.gid)
|
||||
if n ~= nil then
|
||||
return
|
||||
end
|
||||
end
|
||||
local item = inventory_helper.deserialize_single_item(item_data)
|
||||
add_stuff_to_globalized_item(item, item_data.gid)
|
||||
for _, com in ipairs(EntityGetComponent(item, "VariableStorageComponent") or {}) do
|
||||
if ComponentGetValue2(com, "name") == "throw_time" then
|
||||
ComponentSetValue2(com, "value_int", GameGetFrameNum())
|
||||
end
|
||||
end
|
||||
local damage_component = EntityGetFirstComponentIncludingDisabled(item, "DamageModelComponent")
|
||||
if damage_component and damage_component ~= 0 then
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", true)
|
||||
EntityAddComponent2(item, "LuaComponent", {
|
||||
_tags = "ew_immortal",
|
||||
script_damage_about_to_be_received = "mods/quant.ew/files/resource/cbs/immortal.lua",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.item_localize(l_peer_id, item_id)
|
||||
local item_ent_id = item_sync.find_by_gid(item_id)
|
||||
if item_ent_id ~= nil then
|
||||
for _, handler in ipairs(pickup_handlers) do
|
||||
handler(item_ent_id)
|
||||
end
|
||||
end
|
||||
if l_peer_id ~= ctx.my_id then
|
||||
item_sync.remove_item_with_id(item_id)
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.item_localize_req(gid)
|
||||
if not ctx.is_host then
|
||||
return
|
||||
end
|
||||
item_sync.host_localize_item(gid, ctx.rpc_peer_id)
|
||||
end
|
||||
|
||||
local function cleanup(peer)
|
||||
for gid, num in pairs(gid_last_frame_updated[peer]) do
|
||||
if frame[peer] > num then
|
||||
local item = item_sync.find_by_gid(gid)
|
||||
if is_item_on_ground(item) then
|
||||
item_sync.remove_item_with_id(gid)
|
||||
gid_last_frame_updated[peer][gid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
local is_duplicate = {}
|
||||
for _, item in ipairs(EntityGetWithTag("ew_global_item") or {}) do
|
||||
local gid = item_sync.get_global_item_id(item)
|
||||
if gid ~= nil and is_peers_item(gid, peer) then
|
||||
if is_duplicate[gid] then
|
||||
item_sync.remove_item_with_id(gid)
|
||||
else
|
||||
is_duplicate[gid] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function rpc.kill_egg(gid)
|
||||
item_sync.remove_item_with_id_now(gid)
|
||||
end
|
||||
|
||||
function rpc.update_positions(position_data, all)
|
||||
if frame[ctx.rpc_peer_id] == nil or all then
|
||||
frame[ctx.rpc_peer_id] = GameGetFrameNum()
|
||||
if gid_last_frame_updated[ctx.rpc_peer_id] == nil then
|
||||
gid_last_frame_updated[ctx.rpc_peer_id] = {}
|
||||
end
|
||||
end
|
||||
local cx, cy = GameGetCameraPos()
|
||||
for gid, el in pairs(position_data) do
|
||||
if table.contains(pending_remove, gid) then
|
||||
goto continue
|
||||
end
|
||||
local x, y = el[1], el[2]
|
||||
local name = EntityGetFilename(item)
|
||||
local is_chest = name == "data/entities/items/pickup/utility_box.xml"
|
||||
or name == "data/entities/items/pickup/chest_random_super.xml"
|
||||
or name == "data/entities/items/pickup/chest_random.xml"
|
||||
if is_chest or el[5] ~= nil or (math.abs(x - cx) < DISTANCE_LIMIT and math.abs(y - cy) < DISTANCE_LIMIT) then
|
||||
if el[5] == nil then
|
||||
gid_last_frame_updated[ctx.rpc_peer_id][gid] = frame[ctx.rpc_peer_id]
|
||||
end
|
||||
local phys_info = el[3]
|
||||
local price = el[4]
|
||||
local item = item_sync.find_by_gid(gid)
|
||||
if item ~= nil then
|
||||
if not util.set_phys_info(item, phys_info, ctx.rpc_player_data.fps) then
|
||||
EntitySetTransform(item, x, y)
|
||||
if el[6] ~= nil then
|
||||
local vx, vy = el[6][1], el[6][2]
|
||||
local velocity = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
|
||||
if velocity ~= nil then
|
||||
ComponentSetValue2(velocity, "mVelocity", vx, vy)
|
||||
end
|
||||
end
|
||||
end
|
||||
local costcom = EntityGetFirstComponentIncludingDisabled(item, "ItemCostComponent")
|
||||
if costcom ~= nil then
|
||||
if price == 0 then
|
||||
EntitySetComponentsWithTagEnabled(item, "shop_cost", false)
|
||||
ComponentSetValue2(costcom, "cost", 0)
|
||||
else
|
||||
EntitySetComponentsWithTagEnabled(item, "shop_cost", true)
|
||||
ComponentSetValue2(costcom, "cost", price)
|
||||
end
|
||||
end
|
||||
elseif wait_for_gid[gid] == nil then
|
||||
if el[5] == true then
|
||||
rpc.kill_egg(gid)
|
||||
elseif el[5] ~= false then
|
||||
util.log("Requesting again " .. gid)
|
||||
rpc.request_send_again(gid)
|
||||
wait_for_gid[gid] = GameGetFrameNum() + 300
|
||||
end
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
if all then
|
||||
cleanup(ctx.rpc_peer_id)
|
||||
end
|
||||
end
|
||||
|
||||
function rpc.request_send_again(gid)
|
||||
if gid ~= nil and not item_sync.is_my_item(gid) then
|
||||
return
|
||||
end
|
||||
local item = item_sync.find_by_gid(gid)
|
||||
if item == nil then
|
||||
util.log("Requested to send item again, but this item wasn't found: " .. gid)
|
||||
return
|
||||
end
|
||||
if wait_on_send[gid] == nil or wait_on_send[gid] < GameGetFrameNum() then
|
||||
wait_on_send[gid] = GameGetFrameNum() + 240
|
||||
item_sync.make_item_global(item)
|
||||
end
|
||||
end
|
||||
|
||||
ctx.cap.item_sync = {
|
||||
globalize = item_sync.make_item_global,
|
||||
register_pickup_handler = function(handler)
|
||||
table.insert(pickup_handlers, handler)
|
||||
end,
|
||||
}
|
||||
|
||||
item_sync.rpc = rpc
|
||||
|
||||
return item_sync
|
|
@ -1,318 +0,0 @@
|
|||
--- World read / write functionality.
|
||||
---@module 'noitapatcher.nsew.world'
|
||||
local world = {}
|
||||
|
||||
local ffi = require("ffi")
|
||||
local world_ffi = require("noitapatcher.nsew.world_ffi")
|
||||
|
||||
print("get_cell: " .. tostring(world_ffi.get_cell))
|
||||
|
||||
local C = ffi.C
|
||||
|
||||
ffi.cdef([[
|
||||
|
||||
enum ENCODE_CONST {
|
||||
// Maximum amount of runs 128*128 pixels can result in, plus one just in case.
|
||||
PIXEL_RUN_MAX = 16385,
|
||||
|
||||
LIQUID_FLAG_STATIC = 1,
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) EncodedAreaHeader {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
|
||||
uint16_t pixel_run_count;
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) PixelRun {
|
||||
uint16_t length;
|
||||
int16_t material;
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) EncodedArea {
|
||||
struct EncodedAreaHeader header;
|
||||
struct PixelRun pixel_runs[PIXEL_RUN_MAX];
|
||||
};
|
||||
|
||||
]])
|
||||
|
||||
world.last_material_id = 0
|
||||
|
||||
world.EncodedAreaHeader = ffi.typeof("struct EncodedAreaHeader")
|
||||
world.PixelRun = ffi.typeof("struct PixelRun")
|
||||
world.EncodedArea = ffi.typeof("struct EncodedArea")
|
||||
|
||||
local pliquid_cell = ffi.typeof("struct CLiquidCell*")
|
||||
|
||||
--- Total bytes taken up by the encoded area
|
||||
-- @tparam EncodedArea encoded_area
|
||||
-- @treturn int total number of bytes that encodes the area
|
||||
-- @usage
|
||||
-- local data = ffi.string(area, world.encoded_size(area))
|
||||
-- peer:send(data)
|
||||
function world.encoded_size(encoded_area)
|
||||
return (ffi.sizeof(world.EncodedAreaHeader) + encoded_area.header.pixel_run_count * ffi.sizeof(world.PixelRun))
|
||||
end
|
||||
|
||||
--[[
|
||||
--- Encode the given rectangle of the world
|
||||
-- The rectangle defined by {`start_x`, `start_y`, `end_x`, `end_y`} must not
|
||||
-- exceed 256 in width or height.
|
||||
-- @param chunk_map
|
||||
-- @tparam int start_x coordinate
|
||||
-- @tparam int start_y coordinate
|
||||
-- @tparam int end_x coordinate
|
||||
-- @tparam int end_y coordinate
|
||||
-- @tparam EncodedArea encoded_area memory to use, if nil this function allocates its own memory
|
||||
-- @return returns an EncodedArea or nil if the area could not be encoded
|
||||
-- @see decode
|
||||
function world.encode_area(chunk_map, start_x, start_y, end_x, end_y, encoded_area)
|
||||
start_x = ffi.cast('int32_t', start_x)
|
||||
start_y = ffi.cast('int32_t', start_y)
|
||||
end_x = ffi.cast('int32_t', end_x)
|
||||
end_y = ffi.cast('int32_t', end_y)
|
||||
|
||||
encoded_area = encoded_area or world.EncodedArea()
|
||||
|
||||
local width = end_x - start_x
|
||||
local height = end_y - start_y
|
||||
|
||||
if width <= 0 or height <= 0 then
|
||||
print("Invalid world part, negative dimension")
|
||||
return nil
|
||||
end
|
||||
|
||||
if width > 256 or height > 256 then
|
||||
print("Invalid world part, dimension greater than 256")
|
||||
return nil
|
||||
end
|
||||
|
||||
encoded_area.header.x = start_x
|
||||
encoded_area.header.y = start_y
|
||||
encoded_area.header.width = width - 1
|
||||
encoded_area.header.height = height - 1
|
||||
|
||||
local run_count = 1
|
||||
|
||||
local current_run = encoded_area.pixel_runs[0]
|
||||
local run_length = 0
|
||||
local current_material = 0
|
||||
local current_flags = 0
|
||||
|
||||
local y = start_y
|
||||
while y < end_y do
|
||||
local x = start_x
|
||||
while x < end_x do
|
||||
local material_number = 0
|
||||
local flags = 0
|
||||
|
||||
local ppixel = world_ffi.get_cell(chunk_map, x, y)
|
||||
local pixel = ppixel[0]
|
||||
if pixel ~= nil then
|
||||
local cell_type = pixel.vtable.get_cell_type(pixel)
|
||||
|
||||
if cell_type ~= C.CELL_TYPE_SOLID then
|
||||
local material_ptr = pixel.vtable.get_material(pixel)
|
||||
material_number = world_ffi.get_material_id(material_ptr)
|
||||
end
|
||||
|
||||
if cell_type == C.CELL_TYPE_LIQUID then
|
||||
local liquid_cell = ffi.cast(pliquid_cell, pixel)
|
||||
if liquid_cell.is_static then
|
||||
flags = bit.bor(flags, C.LIQUID_FLAG_STATIC)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if x == start_x and y == start_y then
|
||||
-- Initial run
|
||||
current_material = material_number
|
||||
current_flags = flags
|
||||
elseif current_material ~= material_number or current_flags ~= flags then
|
||||
-- Next run
|
||||
current_run.length = run_length - 1
|
||||
current_run.material = current_material
|
||||
current_run.flags = current_flags
|
||||
|
||||
if run_count == C.PIXEL_RUN_MAX then
|
||||
print("Area too complicated to encode")
|
||||
return nil
|
||||
end
|
||||
|
||||
current_run = encoded_area.pixel_runs[run_count]
|
||||
run_count = run_count + 1
|
||||
|
||||
run_length = 0
|
||||
current_material = material_number
|
||||
current_flags = flags
|
||||
end
|
||||
|
||||
run_length = run_length + 1
|
||||
|
||||
x = x + 1
|
||||
end
|
||||
y = y + 1
|
||||
end
|
||||
|
||||
current_run.length = run_length - 1
|
||||
current_run.material = current_material
|
||||
current_run.flags = current_flags
|
||||
|
||||
encoded_area.header.pixel_run_count = run_count
|
||||
|
||||
return encoded_area
|
||||
end
|
||||
]]
|
||||
|
||||
--- Encode the given rectangle of the world
|
||||
-- The rectangle defined by {`start_x`, `start_y`, `end_x`, `end_y`} must not
|
||||
-- exceed 256 in width or height.
|
||||
-- @tparam int start_x coordinate
|
||||
-- @tparam int start_y coordinate
|
||||
-- @tparam int end_x coordinate
|
||||
-- @tparam int end_y coordinate
|
||||
-- @tparam EncodedArea encoded_area memory to use, if nil this function allocates its own memory
|
||||
-- @return returns an EncodedArea or nil if the area could not be encoded
|
||||
-- @see decode
|
||||
function world.encode_area(start_x_ini, start_y_ini, end_x_ini, end_y_ini, encoded_area)
|
||||
local start_x = ffi.cast("int32_t", start_x_ini)
|
||||
local start_y = ffi.cast("int32_t", start_y_ini)
|
||||
local end_x = ffi.cast("int32_t", end_x_ini)
|
||||
local end_y = ffi.cast("int32_t", end_y_ini)
|
||||
|
||||
encoded_area = encoded_area or world.EncodedArea()
|
||||
|
||||
local width = end_x - start_x
|
||||
local height = end_y - start_y
|
||||
|
||||
if width <= 0 or height <= 0 then
|
||||
print("Invalid world part, negative dimension")
|
||||
return nil
|
||||
end
|
||||
|
||||
if width > 128 or height > 128 then
|
||||
print("Invalid world part, dimension greater than 128")
|
||||
return nil
|
||||
end
|
||||
|
||||
encoded_area.header.x = start_x
|
||||
encoded_area.header.y = start_y
|
||||
encoded_area.header.width = width - 1
|
||||
encoded_area.header.height = height - 1
|
||||
|
||||
encoded_area.header.pixel_run_count = ewext.encode_area(
|
||||
start_x_ini,
|
||||
start_y_ini,
|
||||
end_x_ini,
|
||||
end_y_ini,
|
||||
tonumber(ffi.cast("intptr_t", encoded_area.pixel_runs))
|
||||
)
|
||||
return encoded_area
|
||||
end
|
||||
|
||||
--local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*")
|
||||
|
||||
--- Load an encoded area back into the world.
|
||||
-- @param grid_world
|
||||
-- @tparam EncodedAreaHeader header header of the encoded area
|
||||
-- @param received pointer or ffi array of PixelRun from the encoded area
|
||||
-- @see encode_area
|
||||
function world.decode(grid_world, header, pixel_runs)
|
||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||
|
||||
local top_left_x = header.x
|
||||
local top_left_y = header.y
|
||||
local width = header.width + 1
|
||||
local height = header.height + 1
|
||||
local bottom_right_x = top_left_x + width
|
||||
local bottom_right_y = top_left_y + height
|
||||
|
||||
local current_run_ix = 0
|
||||
local current_run = pixel_runs[current_run_ix]
|
||||
local new_material = current_run.material
|
||||
local flags = current_run.flags
|
||||
local left = current_run.length + 1
|
||||
|
||||
local y = top_left_y
|
||||
while y < bottom_right_y do
|
||||
local x = top_left_x
|
||||
while x < bottom_right_x do
|
||||
if world_ffi.chunk_loaded(chunk_map, x, y) then
|
||||
local ppixel = world_ffi.get_cell(chunk_map, x, y)
|
||||
|
||||
local current_material = 0
|
||||
|
||||
if new_material == -1 then
|
||||
goto next_pixel
|
||||
end
|
||||
|
||||
if ppixel[0] ~= nil then
|
||||
local pixel = ppixel[0]
|
||||
local cell_type = pixel.vtable.get_cell_type(pixel)
|
||||
if cell_type == C.CELL_TYPE_SOLID then
|
||||
goto next_pixel
|
||||
end
|
||||
current_material = world_ffi.get_material_id(pixel.vtable.get_material(pixel))
|
||||
|
||||
if new_material ~= current_material then
|
||||
world_ffi.remove_cell(grid_world, pixel, x, y, false)
|
||||
end
|
||||
end
|
||||
|
||||
if current_material ~= new_material and new_material ~= 0 then
|
||||
if new_material > world.last_material_id then
|
||||
goto next_pixel
|
||||
end
|
||||
local mat_ptr = world_ffi.get_material_ptr(new_material)
|
||||
if mat_ptr == nil then
|
||||
goto next_pixel
|
||||
end
|
||||
local pixel = world_ffi.construct_cell(grid_world, x, y, mat_ptr, nil)
|
||||
if pixel == nil then
|
||||
-- TODO: This can happen when the material texture has a
|
||||
-- transparent pixel at the given coordinate. There's
|
||||
-- probably a better way to deal with this, but for now
|
||||
-- we skip positions like this.
|
||||
goto next_pixel
|
||||
end
|
||||
|
||||
local cell_type = pixel.vtable.get_cell_type(pixel)
|
||||
|
||||
if cell_type == C.CELL_TYPE_LIQUID then
|
||||
local liquid_cell = ffi.cast(pliquid_cell, pixel)
|
||||
liquid_cell.is_static = bit.band(flags, C.CELL_TYPE_LIQUID) == C.LIQUID_FLAG_STATIC
|
||||
end
|
||||
|
||||
ppixel[0] = pixel
|
||||
end
|
||||
end
|
||||
|
||||
::next_pixel::
|
||||
|
||||
left = left - 1
|
||||
if left <= 0 then
|
||||
current_run_ix = current_run_ix + 1
|
||||
if current_run_ix >= header.pixel_run_count then
|
||||
-- No more runs, done
|
||||
assert(x == bottom_right_x - 1)
|
||||
assert(y == bottom_right_y - 1)
|
||||
return
|
||||
end
|
||||
|
||||
current_run = pixel_runs[current_run_ix]
|
||||
new_material = current_run.material
|
||||
flags = current_run.flags
|
||||
left = current_run.length + 1
|
||||
end
|
||||
|
||||
x = x + 1
|
||||
end
|
||||
y = y + 1
|
||||
end
|
||||
end
|
||||
|
||||
return world
|
|
@ -1,226 +0,0 @@
|
|||
local world_ffi = require("noitapatcher.nsew.world_ffi")
|
||||
local world = dofile_once("mods/quant.ew/files/system/world_sync/world.lua")
|
||||
local rect = require("noitapatcher.nsew.rect")
|
||||
local ffi = require("ffi")
|
||||
|
||||
-- local rpc = net.new_rpc_namespace()
|
||||
|
||||
--local rect_optimiser = rect.Optimiser_new()
|
||||
local encoded_area = world.EncodedArea()
|
||||
|
||||
local world_sync = {}
|
||||
|
||||
local KEY_WORLD_FRAME = 0
|
||||
local KEY_WORLD_END = 1
|
||||
|
||||
local CHUNK_SIZE = 128
|
||||
|
||||
local iter_fast = 0
|
||||
|
||||
local iter_slow = 0
|
||||
|
||||
local iter_slow_2 = 0
|
||||
|
||||
--[[local function do_benchmark()
|
||||
local world_ffi = require("noitapatcher.nsew.world_ffi")
|
||||
local grid_world = world_ffi.get_grid_world()
|
||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||
local start = GameGetRealWorldTimeSinceStarted()
|
||||
local iters = 10000
|
||||
for i=1, iters do
|
||||
world.encode_area(chunk_map, 0, 0, 128, 128, encode_area)
|
||||
-- world_ffi.get_cell(chunk_map, 0, 0)
|
||||
end
|
||||
local end_time = GameGetRealWorldTimeSinceStarted()
|
||||
local elapsed = (end_time - start) * 1000 * 1000 * 1000 / (iters * 128 * 128)
|
||||
print("Benchmark:", elapsed, "ns/pixel")
|
||||
end]]
|
||||
|
||||
function world_sync.on_world_initialized()
|
||||
local c = 0
|
||||
while true do
|
||||
local name = CellFactory_GetName(c)
|
||||
if name == "unknown" then
|
||||
break
|
||||
end
|
||||
c = c + 1
|
||||
end
|
||||
c = c - 1
|
||||
print("Last material id: " .. c)
|
||||
world.last_material_id = c
|
||||
-- do_benchmark()
|
||||
end
|
||||
|
||||
local function send_chunks(cx, cy)
|
||||
local chx, chy = cx * CHUNK_SIZE, cy * CHUNK_SIZE
|
||||
local crect = rect.Rectangle(chx, chy, chx + CHUNK_SIZE, chy + CHUNK_SIZE)
|
||||
if DoesWorldExistAt(crect.left, crect.top, crect.right, crect.bottom) then
|
||||
local area = world.encode_area(crect.left, crect.top, crect.right, crect.bottom, encoded_area)
|
||||
if area ~= nil then
|
||||
--if ctx.proxy_opt.debug then
|
||||
-- GameCreateSpriteForXFrames("mods/quant.ew/files/resource/debug/box_128x128.png", crect.left+64, crect.top + 64, true, 0, 0, 11, true)
|
||||
--end
|
||||
local str = ffi.string(area, world.encoded_size(area))
|
||||
net.proxy_bin_send(KEY_WORLD_FRAME, str)
|
||||
end
|
||||
end
|
||||
end
|
||||
local int = 4 -- ctx.proxy_opt.world_sync_interval
|
||||
|
||||
local function get_all_chunks(ocx, ocy, pos_data, priority, give_0)
|
||||
--local grid_world = world_ffi.get_grid_world()
|
||||
--local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||
--local thread_impl = grid_world.mThreadImpl
|
||||
if GameGetFrameNum() % int == 0 then
|
||||
send_chunks(ocx, ocy)
|
||||
local pri = priority
|
||||
if give_0 then
|
||||
pri = 0
|
||||
end
|
||||
net.proxy_bin_send(KEY_WORLD_END, string.char(pri) .. pos_data)
|
||||
elseif GameGetFrameNum() % int == 2 then
|
||||
if iter_fast == 0 then
|
||||
send_chunks(ocx + 1, ocy)
|
||||
send_chunks(ocx + 1, ocy + 1)
|
||||
elseif iter_fast == 1 then
|
||||
send_chunks(ocx, ocy + 1)
|
||||
send_chunks(ocx - 1, ocy + 1)
|
||||
elseif iter_fast == 2 then
|
||||
send_chunks(ocx - 1, ocy)
|
||||
send_chunks(ocx - 1, ocy - 1)
|
||||
else
|
||||
send_chunks(ocx, ocy - 1)
|
||||
send_chunks(ocx + 1, ocy - 1)
|
||||
end
|
||||
net.proxy_bin_send(KEY_WORLD_END, string.char(math.min(priority + 1, 16)) .. pos_data)
|
||||
iter_fast = iter_fast + 1
|
||||
if iter_fast == 4 then
|
||||
iter_fast = 0
|
||||
end
|
||||
elseif GameGetFrameNum() % (int * 4) == 3 then
|
||||
if iter_slow == 0 then
|
||||
send_chunks(ocx + 2, ocy - 1)
|
||||
send_chunks(ocx + 2, ocy)
|
||||
send_chunks(ocx + 2, ocy + 1)
|
||||
send_chunks(ocx + 2, ocy + 2)
|
||||
elseif iter_slow == 1 then
|
||||
send_chunks(ocx + 1, ocy + 2)
|
||||
send_chunks(ocx, ocy + 2)
|
||||
send_chunks(ocx - 1, ocy + 2)
|
||||
send_chunks(ocx - 2, ocy + 2)
|
||||
elseif iter_slow == 2 then
|
||||
send_chunks(ocx - 2, ocy + 1)
|
||||
send_chunks(ocx - 2, ocy)
|
||||
send_chunks(ocx - 2, ocy - 1)
|
||||
send_chunks(ocx - 2, ocy - 2)
|
||||
else
|
||||
send_chunks(ocx - 1, ocy - 2)
|
||||
send_chunks(ocx, ocy - 2)
|
||||
send_chunks(ocx + 1, ocy - 2)
|
||||
send_chunks(ocx + 2, ocy - 2)
|
||||
end
|
||||
net.proxy_bin_send(KEY_WORLD_END, string.char(math.min(priority + 2, 16)) .. pos_data)
|
||||
iter_slow = iter_slow + 1
|
||||
if iter_slow == 4 then
|
||||
iter_slow = 0
|
||||
end
|
||||
elseif (priority == 0 and not GameHasFlagRun("ending_game_completed")) and GameGetFrameNum() % (int * 3) == 1 then
|
||||
if iter_slow_2 == 0 then
|
||||
send_chunks(ocx + 3, ocy)
|
||||
send_chunks(ocx + 3, ocy + 1)
|
||||
send_chunks(ocx + 3, ocy + 2)
|
||||
send_chunks(ocx + 3, ocy + 3)
|
||||
elseif iter_slow_2 == 1 then
|
||||
send_chunks(ocx + 2, ocy + 3)
|
||||
send_chunks(ocx + 1, ocy + 3)
|
||||
send_chunks(ocx, ocy + 3)
|
||||
send_chunks(ocx - 1, ocy + 3)
|
||||
elseif iter_slow_2 == 2 then
|
||||
send_chunks(ocx - 2, ocy + 3)
|
||||
send_chunks(ocx - 3, ocy + 3)
|
||||
send_chunks(ocx - 3, ocy + 2)
|
||||
send_chunks(ocx - 3, ocy + 1)
|
||||
elseif iter_slow_2 == 3 then
|
||||
send_chunks(ocx - 3, ocy)
|
||||
send_chunks(ocx - 3, ocy - 1)
|
||||
send_chunks(ocx - 3, ocy - 2)
|
||||
send_chunks(ocx - 3, ocy - 3)
|
||||
elseif iter_slow_2 == 4 then
|
||||
send_chunks(ocx - 2, ocy - 3)
|
||||
send_chunks(ocx - 1, ocy - 3)
|
||||
send_chunks(ocx, ocy - 3)
|
||||
send_chunks(ocx + 1, ocy - 3)
|
||||
else
|
||||
send_chunks(ocx + 2, ocy - 3)
|
||||
send_chunks(ocx + 3, ocy - 3)
|
||||
send_chunks(ocx + 3, ocy - 2)
|
||||
send_chunks(ocx + 3, ocy - 1)
|
||||
end
|
||||
net.proxy_bin_send(KEY_WORLD_END, string.char(math.min(priority + 2, 16)) .. pos_data)
|
||||
iter_slow_2 = iter_slow_2 + 1
|
||||
if iter_slow_2 == 6 then
|
||||
iter_slow_2 = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local wait
|
||||
|
||||
function world_sync.on_world_update()
|
||||
if ctx.run_ended or (wait ~= nil and wait > GameGetFrameNum()) then
|
||||
return
|
||||
end
|
||||
wait = nil
|
||||
int = math.floor(tonumber(ModSettingGet("quant.ew.world_sync") or 4) or 4 + 0.5)
|
||||
local cx, cy = GameGetCameraPos()
|
||||
cx, cy = math.floor(cx / CHUNK_SIZE), math.floor(cy / CHUNK_SIZE)
|
||||
local player_data = ctx.my_player
|
||||
if not EntityGetIsAlive(player_data.entity) then
|
||||
return
|
||||
end
|
||||
local px, py = EntityGetTransform(player_data.entity)
|
||||
-- Original Chunk x/y
|
||||
local ocx, ocy = math.floor(px / CHUNK_SIZE), math.floor(py / CHUNK_SIZE)
|
||||
local n = 0
|
||||
if EntityHasTag(ctx.my_player.entity, "ew_notplayer") or GameHasFlagRun("ending_game_completed") then
|
||||
n = 1
|
||||
end
|
||||
local pos_data
|
||||
if GameGetFrameNum() % int ~= 0 and GameGetFrameNum() % (int * 4) == 3 then
|
||||
pos_data = ocx .. ":" .. ocy .. ":" .. cx .. ":" .. cy .. ":" .. n .. ":" .. ctx.proxy_opt.world_num
|
||||
else
|
||||
pos_data = ctx.proxy_opt.world_num
|
||||
end
|
||||
if math.abs(cx - ocx) > 2 or math.abs(cy - ocy) > 2 then
|
||||
if ctx.spectating_over_peer_id ~= nil and ctx.spectating_over_peer_id ~= ctx.my_id then
|
||||
if GameGetFrameNum() % 3 ~= 2 then
|
||||
get_all_chunks(cx, cy, pos_data, 16, false)
|
||||
else
|
||||
get_all_chunks(ocx, ocy, pos_data, 16, true)
|
||||
end
|
||||
else
|
||||
wait = GameGetFrameNum() + 30
|
||||
end
|
||||
else
|
||||
local pri = 0
|
||||
if EntityHasTag(ctx.my_player.entity, "ew_notplayer") then
|
||||
pri = 16
|
||||
end
|
||||
get_all_chunks(ocx, ocy, pos_data, pri, true)
|
||||
end
|
||||
end
|
||||
|
||||
local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*")
|
||||
|
||||
function world_sync.handle_world_data(datum)
|
||||
local grid_world = world_ffi.get_grid_world()
|
||||
local header = ffi.cast("struct EncodedAreaHeader const*", ffi.cast("char const*", datum))
|
||||
local runs = ffi.cast(PixelRun_const_ptr, ffi.cast("const char*", datum) + ffi.sizeof(world.EncodedAreaHeader))
|
||||
world.decode(grid_world, header, runs)
|
||||
end
|
||||
|
||||
net.net_handling.proxy[0] = function(_, value)
|
||||
world_sync.handle_world_data(value)
|
||||
end
|
||||
|
||||
return world_sync
|
|
@ -91,7 +91,7 @@ local function load_modules()
|
|||
ctx.dofile_and_add_hooks("mods/quant.ew/files/system/weather_sync.lua")
|
||||
ctx.load_system("polymorph")
|
||||
|
||||
ctx.load_system("world_sync")
|
||||
-- ctx.load_system("world_sync")
|
||||
|
||||
-- ctx.load_system("spawn_hooks")
|
||||
ctx.dofile_and_add_hooks("mods/quant.ew/files/system/proxy_info.lua")
|
||||
|
|
|
@ -4,6 +4,7 @@ pub mod message_socket;
|
|||
|
||||
pub mod basic_types;
|
||||
pub mod des;
|
||||
pub mod world_sync;
|
||||
|
||||
pub use basic_types::*;
|
||||
|
||||
|
@ -44,6 +45,7 @@ pub enum NoitaInbound {
|
|||
my_peer_id: PeerId,
|
||||
},
|
||||
ProxyToDes(des::ProxyToDes),
|
||||
ProxyToWorldSync(world_sync::ProxyToWorldSync),
|
||||
RemoteMessage {
|
||||
source: PeerId,
|
||||
message: RemoteMessage,
|
||||
|
@ -54,6 +56,7 @@ pub enum NoitaInbound {
|
|||
pub enum NoitaOutbound {
|
||||
Raw(Vec<u8>),
|
||||
DesToProxy(des::DesToProxy),
|
||||
WorldSyncToProxy(world_sync::WorldSyncToProxy),
|
||||
RemoteMessage {
|
||||
reliable: bool,
|
||||
destination: Destination<PeerId>,
|
||||
|
|
35
shared/src/world_sync.rs
Normal file
35
shared/src/world_sync.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use bitcode::{Decode, Encode};
|
||||
/// Stores a run of pixels.
|
||||
/// Not specific to Noita side - length is an actual length
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct PixelRun<Pixel> {
|
||||
pub length: u16,
|
||||
pub data: Pixel,
|
||||
}
|
||||
|
||||
pub const CHUNK_SIZE: usize = 128;
|
||||
|
||||
#[derive(Debug, Encode, Decode, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct ChunkCoord(pub i32, pub i32);
|
||||
|
||||
#[derive(Debug, Encode, Decode, Clone)]
|
||||
pub struct NoitaWorldUpdate {
|
||||
pub coord: ChunkCoord,
|
||||
pub runs: Vec<PixelRun<RawPixel>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct RawPixel {
|
||||
pub material: u16,
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, Clone)]
|
||||
pub enum WorldSyncToProxy {
|
||||
Updates(Vec<NoitaWorldUpdate>),
|
||||
End(Option<(i32, i32, i32, i32, bool)>, u8, u8),
|
||||
}
|
||||
#[derive(Debug, Encode, Decode, Clone)]
|
||||
pub enum ProxyToWorldSync {
|
||||
Updates(Vec<NoitaWorldUpdate>),
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue