fix telekenesis crash

This commit is contained in:
bgkillas 2025-01-25 15:13:03 -05:00
parent 1717a46b7a
commit 82256e68e8
9 changed files with 958 additions and 908 deletions

View file

@ -108,13 +108,35 @@ impl EntityID {
pub fn kill(self) { pub fn kill(self) {
// Shouldn't ever error. // Shouldn't ever error.
for (i, id) in raw::physics_body_id_get_from_entity(self, None) let body_id = raw::physics_body_id_get_from_entity(self, None).unwrap_or_default();
.unwrap_or_default() if !body_id.is_empty() {
.iter() for com in raw::entity_get_with_tag("ew_peer".into())
.enumerate() .unwrap_or_default()
{ .iter()
let n = 17000.0 + (64.0 * (self.0.get() as usize + i) as f64); .filter_map(|e| {
let _ = raw::physics_body_id_set_transform(*id, n, n, 0.0, 0.0, 0.0, 0.0); e.map(|e| {
e.try_get_first_component_including_disabled::<TelekinesisComponent>(None)
})
})
.flatten()
.flatten()
{
if body_id.contains(&com.get_body_id()) {
let _ = raw::component_set_value(*com, "mState", 0);
}
}
for (i, id) in body_id.iter().enumerate() {
let n = 17000.0;
let _ = raw::physics_body_id_set_transform(
*id,
n + 64.0 * self.0.get() as f64,
n + 64.0 * i as f64,
0.0,
0.0,
0.0,
0.0,
);
}
} }
let _ = raw::entity_kill(self); let _ = raw::entity_kill(self);
} }
@ -526,6 +548,12 @@ impl StatusEffectDataComponent {
} }
} }
impl TelekinesisComponent {
pub fn get_body_id(self) -> PhysicsBodyID {
raw::component_get_value_old::<PhysicsBodyID>(*self, "mBodyID").unwrap_or(PhysicsBodyID(0))
}
}
pub fn game_print(value: impl AsRef<str>) { pub fn game_print(value: impl AsRef<str>) {
let _ = raw::game_print(value.as_ref().into()); let _ = raw::game_print(value.as_ref().into());
} }
@ -560,6 +588,21 @@ pub mod raw {
ret.wrap_err_with(|| eyre!("Getting {field} for {component:?}")) ret.wrap_err_with(|| eyre!("Getting {field} for {component:?}"))
} }
pub(crate) fn component_get_value_old<T>(component: ComponentID, field: &str) -> eyre::Result<T>
where
T: LuaGetValue,
{
let lua = LuaState::current()?;
lua.get_global(c"ComponentGetValue");
lua.push_integer(component.0.into());
lua.push_string(field);
lua.call(2, T::size_on_stack())
.wrap_err("Failed to call ComponentGetValue")?;
let ret = T::get(lua, -1);
lua.pop_last_n(T::size_on_stack());
ret.wrap_err_with(|| eyre!("Getting {field} for {component:?}"))
}
pub(crate) fn component_object_get_value<T>( pub(crate) fn component_object_get_value<T>(
component: ComponentID, component: ComponentID,
object: &str, object: &str,

View file

@ -1,24 +1,22 @@
-- You're supposed to `dofile_once("path/to/load.lua")` this file.
-- You're supposed to `dofile_once("path/to/load.lua")` this file.
local orig_do_mod_appends = do_mod_appends
local orig_do_mod_appends = do_mod_appends do_mod_appends = function(filename, ...)
do_mod_appends = orig_do_mod_appends
do_mod_appends = function(filename, ...) do_mod_appends(filename, ...)
do_mod_appends = orig_do_mod_appends
do_mod_appends(filename, ...) local noitapatcher_path = string.match(filename, "(.*)/load.lua")
if not noitapatcher_path then
local noitapatcher_path = string.match(filename, "(.*)/load.lua") print("Couldn't detect NoitaPatcher path")
if not noitapatcher_path then end
print("Couldn't detect NoitaPatcher path")
end __nsew_path = noitapatcher_path .. "/noitapatcher/nsew/"
__nsew_path = noitapatcher_path .. "/noitapatcher/nsew/" package.cpath = package.cpath .. ";./" .. noitapatcher_path .. "/?.dll"
package.path = package.path .. ";./" .. noitapatcher_path .. "/?.lua"
package.cpath = package.cpath .. ";./" .. noitapatcher_path .. "/?.dll"
package.path = package.path .. ";./" .. noitapatcher_path .. "/?.lua" -- Lua's loader should now be setup properly:
-- local np = require("noitapatcher")
-- Lua's loader should now be setup properly: -- local nsew = require("noitapatcher.nsew")
-- local np = require("noitapatcher") end
-- local nsew = require("noitapatcher.nsew")
end

View file

@ -1,11 +1,11 @@
---Native library. Primarily for internal use. ---Native library. Primarily for internal use.
---@module 'noitapatcher.nsew.native_dll' ---@module 'noitapatcher.nsew.native_dll'
local ffi = require("ffi") local ffi = require("ffi")
native_dll = {} native_dll = {}
---The NSEW support dll loaded in with `ffi.load`. ---The NSEW support dll loaded in with `ffi.load`.
native_dll.lib = ffi.load(__nsew_path .. "nsew_native.dll") native_dll.lib = ffi.load(__nsew_path .. "nsew_native.dll")
return native_dll return native_dll

View file

@ -1,167 +1,167 @@
---Rectangle utilities. ---Rectangle utilities.
---@module 'noitapatcher.nsew.rect' ---@module 'noitapatcher.nsew.rect'
---@class Rect ---@class Rect
local rect = {} local rect = {}
local ffi = require("ffi") local ffi = require("ffi")
local native_dll = require("noitapatcher.nsew.native_dll") local native_dll = require("noitapatcher.nsew.native_dll")
ffi.cdef([[ ffi.cdef([[
struct nsew_rectangle { struct nsew_rectangle {
int32_t left; int32_t left;
int32_t top; int32_t top;
int32_t right; int32_t right;
int32_t bottom; int32_t bottom;
}; };
struct nsew_rectangle_optimiser; struct nsew_rectangle_optimiser;
struct nsew_rectangle_optimiser* rectangle_optimiser_new(); struct nsew_rectangle_optimiser* rectangle_optimiser_new();
void rectangle_optimiser_delete(struct nsew_rectangle_optimiser* rectangle_optimiser); void rectangle_optimiser_delete(struct nsew_rectangle_optimiser* rectangle_optimiser);
void rectangle_optimiser_reset(struct nsew_rectangle_optimiser* rectangle_optimiser); void rectangle_optimiser_reset(struct nsew_rectangle_optimiser* rectangle_optimiser);
void rectangle_optimiser_submit(struct nsew_rectangle_optimiser* rectangle_optimiser, struct nsew_rectangle* rectangle); void rectangle_optimiser_submit(struct nsew_rectangle_optimiser* rectangle_optimiser, struct nsew_rectangle* rectangle);
void rectangle_optimiser_scan(struct nsew_rectangle_optimiser* rectangle_optimiser); void rectangle_optimiser_scan(struct nsew_rectangle_optimiser* rectangle_optimiser);
int32_t rectangle_optimiser_size(const struct nsew_rectangle_optimiser* rectangle_optimiser); int32_t rectangle_optimiser_size(const struct nsew_rectangle_optimiser* rectangle_optimiser);
const struct nsew_rectangle* rectangle_optimiser_get(const struct nsew_rectangle_optimiser* rectangle_optimiser, int32_t index); const struct nsew_rectangle* rectangle_optimiser_get(const struct nsew_rectangle_optimiser* rectangle_optimiser, int32_t index);
struct lua_nsew_rectangle_optimiser { struct lua_nsew_rectangle_optimiser {
struct nsew_rectangle_optimiser* impl; struct nsew_rectangle_optimiser* impl;
}; };
]]) ]])
---@class Rectangle_fields ---@class Rectangle_fields
---@field top integer ---@field top integer
---@field bottom integer ---@field bottom integer
---@field right integer ---@field right integer
---@field left integer ---@field left integer
---@alias Rectangle Rectangle_mt | Rectangle_fields ---@alias Rectangle Rectangle_mt | Rectangle_fields
---@class Optimiser_fields ---@class Optimiser_fields
---@field top integer ---@field top integer
---@field bottom integer ---@field bottom integer
---@field right integer ---@field right integer
---@field left integer ---@field left integer
---@alias Optimiser Optimiser_fields | Optimiser_mt ---@alias Optimiser Optimiser_fields | Optimiser_mt
---@class Rectangle_mt ---@class Rectangle_mt
local Rectangle_mt_index = { local Rectangle_mt_index = {
---@param r Rectangle ---@param r Rectangle
---@return integer ---@return integer
area = function(r) area = function(r)
return (r.right - r.left) * (r.bottom - r.top) return (r.right - r.left) * (r.bottom - r.top)
end, end,
---@param r Rectangle ---@param r Rectangle
---@return integer ---@return integer
height = function(r) height = function(r)
return r.bottom - r.top return r.bottom - r.top
end, end,
---@param r Rectangle ---@param r Rectangle
---@return integer ---@return integer
width = function(r) width = function(r)
return r.right - r.left return r.right - r.left
end, end,
} }
local Rectangle_mt = { local Rectangle_mt = {
__index = Rectangle_mt_index, __index = Rectangle_mt_index,
} }
---@type fun(left, top, right, bottom): Rectangle ---@type fun(left, top, right, bottom): Rectangle
---@diagnostic disable-next-line: assign-type-mismatch ---@diagnostic disable-next-line: assign-type-mismatch
rect.Rectangle = ffi.metatype("struct nsew_rectangle", Rectangle_mt) rect.Rectangle = ffi.metatype("struct nsew_rectangle", Rectangle_mt)
---Given an iterator that returns rectangles, return an iterator where the ---Given an iterator that returns rectangles, return an iterator where the
---rectangle extents never exceed `size`. ---rectangle extents never exceed `size`.
---@param iterator fun(): Rectangle? returning rectangles ---@param iterator fun(): Rectangle? returning rectangles
---@param size integer maximum width and height ---@param size integer maximum width and height
---@return fun(): Rectangle? rectangles where the extents never exceed `size` ---@return fun(): Rectangle? rectangles where the extents never exceed `size`
function rect.parts(iterator, size) function rect.parts(iterator, size)
local region local region
local posx local posx
local posy local posy
return function() return function()
if region == nil then if region == nil then
region = iterator() region = iterator()
if region == nil then if region == nil then
return nil return nil
end end
posx = region.left posx = region.left
posy = region.top posy = region.top
end end
local endx = math.min(posx + size, region.right) local endx = math.min(posx + size, region.right)
local endy = math.min(posy + size, region.bottom) local endy = math.min(posy + size, region.bottom)
local ret = rect.Rectangle(posx, posy, endx, endy) local ret = rect.Rectangle(posx, posy, endx, endy)
-- Setup for next iteration: place to the right, wraparound, or -- Setup for next iteration: place to the right, wraparound, or
-- we're done with this region. -- we're done with this region.
if endx ~= region.right then if endx ~= region.right then
posx = endx posx = endx
elseif endy ~= region.bottom then elseif endy ~= region.bottom then
posx = region.left posx = region.left
posy = endy posy = endy
else else
region = nil region = nil
end end
return ret return ret
end end
end end
---@class Optimiser_mt ---@class Optimiser_mt
local Optimiser_mt_index = { local Optimiser_mt_index = {
submit = function(opt, rectangle) submit = function(opt, rectangle)
native_dll.lib.rectangle_optimiser_submit(opt.impl, rectangle) native_dll.lib.rectangle_optimiser_submit(opt.impl, rectangle)
end, end,
scan = function(opt) scan = function(opt)
native_dll.lib.rectangle_optimiser_scan(opt.impl) native_dll.lib.rectangle_optimiser_scan(opt.impl)
end, end,
reset = function(opt) reset = function(opt)
native_dll.lib.rectangle_optimiser_reset(opt.impl) native_dll.lib.rectangle_optimiser_reset(opt.impl)
end, end,
size = function(opt) size = function(opt)
return native_dll.lib.rectangle_optimiser_size(opt.impl) return native_dll.lib.rectangle_optimiser_size(opt.impl)
end, end,
get = function(opt, index) get = function(opt, index)
return native_dll.lib.rectangle_optimiser_get(opt.impl, index) return native_dll.lib.rectangle_optimiser_get(opt.impl, index)
end, end,
iterate = function(opt) iterate = function(opt)
local size = native_dll.lib.rectangle_optimiser_size(opt.impl) local size = native_dll.lib.rectangle_optimiser_size(opt.impl)
local index = 0 local index = 0
return function() return function()
if index >= size then if index >= size then
return nil return nil
end end
local ret = native_dll.lib.rectangle_optimiser_get(opt.impl, index) local ret = native_dll.lib.rectangle_optimiser_get(opt.impl, index)
index = index + 1 index = index + 1
return ret return ret
end end
end, end,
} }
local Optimiser_mt = { local Optimiser_mt = {
__gc = function(opt) __gc = function(opt)
native_dll.lib.rectangle_optimiser_delete(opt.impl) native_dll.lib.rectangle_optimiser_delete(opt.impl)
end, end,
__index = Optimiser_mt_index, __index = Optimiser_mt_index,
} }
---@type fun(unknown): Optimiser ---@type fun(unknown): Optimiser
---@diagnostic disable-next-line: assign-type-mismatch ---@diagnostic disable-next-line: assign-type-mismatch
rect.Optimiser = ffi.metatype("struct lua_nsew_rectangle_optimiser", Optimiser_mt) rect.Optimiser = ffi.metatype("struct lua_nsew_rectangle_optimiser", Optimiser_mt)
---Create a new rectangle Optimiser ---Create a new rectangle Optimiser
---@return Optimiser optimiser ---@return Optimiser optimiser
function rect.Optimiser_new() function rect.Optimiser_new()
return rect.Optimiser(native_dll.lib.rectangle_optimiser_new()) return rect.Optimiser(native_dll.lib.rectangle_optimiser_new())
end end
return rect return rect

View file

@ -1,272 +1,273 @@
---@diagnostic disable: cast-local-type ---@diagnostic disable: cast-local-type
---World read / write functionality. ---World read / write functionality.
---@module 'noitapatcher.nsew.world' ---@module 'noitapatcher.nsew.world'
---@class World ---@class World
local world = {} local world = {}
local ffi = require("ffi") local ffi = require("ffi")
local world_ffi = require("noitapatcher.nsew.world_ffi") local world_ffi = require("noitapatcher.nsew.world_ffi")
local C = ffi.C local C = ffi.C
ffi.cdef([[ ffi.cdef([[
enum ENCODE_CONST { enum ENCODE_CONST {
PIXEL_RUN_MAX = 4096, PIXEL_RUN_MAX = 4096,
LIQUID_FLAG_STATIC = 1, LIQUID_FLAG_STATIC = 1,
}; };
struct __attribute__ ((__packed__)) EncodedAreaHeader { struct __attribute__ ((__packed__)) EncodedAreaHeader {
int32_t x; int32_t x;
int32_t y; int32_t y;
uint8_t width; uint8_t width;
uint8_t height; uint8_t height;
uint16_t pixel_run_count; uint16_t pixel_run_count;
}; };
struct __attribute__ ((__packed__)) PixelRun { struct __attribute__ ((__packed__)) PixelRun {
uint16_t length; uint16_t length;
int16_t material; int16_t material;
uint8_t flags; uint8_t flags;
}; };
struct __attribute__ ((__packed__)) EncodedArea { struct __attribute__ ((__packed__)) EncodedArea {
struct EncodedAreaHeader header; struct EncodedAreaHeader header;
struct PixelRun pixel_runs[PIXEL_RUN_MAX]; struct PixelRun pixel_runs[PIXEL_RUN_MAX];
}; };
]]) ]])
---@class PixelRun ---@class PixelRun
---@field flags integer ---@field flags integer
---@field material integer ---@field material integer
---@field length integer ---@field length integer
---@class EncodedAreaHeader ---@class EncodedAreaHeader
---@field x integer ---@field x integer
---@field y integer ---@field y integer
---@field width integer ---@field width integer
---@field height integer ---@field height integer
---@field pixel_run_count integer ---@field pixel_run_count integer
---@class EncodedArea ---@class EncodedArea
---@field header EncodedAreaHeader ---@field header EncodedAreaHeader
---@field pixel_runs PixelRun[] a pointer ---@field pixel_runs PixelRun[] a pointer
world.EncodedAreaHeader = ffi.typeof("struct EncodedAreaHeader") world.EncodedAreaHeader = ffi.typeof("struct EncodedAreaHeader")
world.PixelRun = ffi.typeof("struct PixelRun") world.PixelRun = ffi.typeof("struct PixelRun")
---@type fun(): EncodedArea ---@type fun(): EncodedArea
---@diagnostic disable-next-line: assign-type-mismatch ---@diagnostic disable-next-line: assign-type-mismatch
world.EncodedArea = ffi.typeof("struct EncodedArea") world.EncodedArea = ffi.typeof("struct EncodedArea")
local pliquid_cell = ffi.typeof("struct CLiquidCell*") local pliquid_cell = ffi.typeof("struct CLiquidCell*")
---Total bytes taken up by the encoded area ---Total bytes taken up by the encoded area
---@param encoded_area EncodedArea ---@param encoded_area EncodedArea
---@return integer total number of bytes that encodes the area ---@return integer total number of bytes that encodes the area
---```lua ---```lua
---local data = ffi.string(area, world.encoded_size(area)) ---local data = ffi.string(area, world.encoded_size(area))
---peer:send(data) ---peer:send(data)
---``` ---```
function world.encoded_size(encoded_area) function world.encoded_size(encoded_area)
return ffi.sizeof(world.EncodedAreaHeader) + encoded_area.header.pixel_run_count * ffi.sizeof(world.PixelRun) return ffi.sizeof(world.EncodedAreaHeader) + encoded_area.header.pixel_run_count * ffi.sizeof(world.PixelRun)
end end
---Encode the given rectangle of the world ---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. ---The rectangle defined by {`start_x`, `start_y`, `end_x`, `end_y`} must not exceed 256 in width or height.
---@param chunk_map unknown ---@param chunk_map unknown
---@param start_x integer coordinate ---@param start_x integer coordinate
---@param start_y integer coordinate ---@param start_y integer coordinate
---@param end_x integer coordinate ---@param end_x integer coordinate
---@param end_y integer coordinate ---@param end_y integer coordinate
---@param encoded_area EncodedArea? memory to use, if nil this function allocates its own memory ---@param encoded_area EncodedArea? memory to use, if nil this function allocates its own memory
---@return EncodedArea? encoded_area returns an EncodedArea or nil if the area could not be encoded ---@return EncodedArea? encoded_area returns an EncodedArea or nil if the area could not be encoded
---@see decode ---@see decode
function world.encode_area(chunk_map, start_x, start_y, end_x, end_y, encoded_area) 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_x = ffi.cast("int32_t", start_x)
start_y = ffi.cast('int32_t', start_y) start_y = ffi.cast("int32_t", start_y)
end_x = ffi.cast('int32_t', end_x) end_x = ffi.cast("int32_t", end_x)
end_y = ffi.cast('int32_t', end_y) end_y = ffi.cast("int32_t", end_y)
---@cast start_x integer ---@cast start_x integer
---@cast start_y integer ---@cast start_y integer
---@cast end_x integer ---@cast end_x integer
---@cast end_x integer ---@cast end_x integer
encoded_area = encoded_area or world.EncodedArea() encoded_area = encoded_area or world.EncodedArea()
local width = end_x - start_x local width = end_x - start_x
local height = end_y - start_y local height = end_y - start_y
if width <= 0 or height <= 0 then if width <= 0 or height <= 0 then
print("Invalid world part, negative dimension") print("Invalid world part, negative dimension")
return nil return nil
end end
if width > 256 or height > 256 then if width > 256 or height > 256 then
print("Invalid world part, dimension greater than 256") print("Invalid world part, dimension greater than 256")
return nil return nil
end end
encoded_area.header.x = start_x encoded_area.header.x = start_x
encoded_area.header.y = start_y encoded_area.header.y = start_y
encoded_area.header.width = width - 1 encoded_area.header.width = width - 1
encoded_area.header.height = height - 1 encoded_area.header.height = height - 1
local run_count = 1 local run_count = 1
local current_run = encoded_area.pixel_runs[0] local current_run = encoded_area.pixel_runs[0]
local run_length = 0 local run_length = 0
local current_material = 0 local current_material = 0
local current_flags = 0 local current_flags = 0
local y = start_y local y = start_y
while y < end_y do while y < end_y do
local x = start_x local x = start_x
while x < end_x do while x < end_x do
local material_number = 0 local material_number = 0
local flags = 0 local flags = 0
local ppixel = world_ffi.get_cell(chunk_map, x, y) local ppixel = world_ffi.get_cell(chunk_map, x, y)
local pixel = ppixel[0] local pixel = ppixel[0]
if pixel ~= nil then if pixel ~= nil then
local cell_type = pixel.vtable.get_cell_type(pixel) local cell_type = pixel.vtable.get_cell_type(pixel)
if cell_type ~= C.CELL_TYPE_SOLID then if cell_type ~= C.CELL_TYPE_SOLID then
local material_ptr = pixel.vtable.get_material(pixel) local material_ptr = pixel.vtable.get_material(pixel)
material_number = world_ffi.get_material_id(material_ptr) material_number = world_ffi.get_material_id(material_ptr)
end end
if cell_type == C.CELL_TYPE_LIQUID then if cell_type == C.CELL_TYPE_LIQUID then
local liquid_cell = ffi.cast(pliquid_cell, pixel) local liquid_cell = ffi.cast(pliquid_cell, pixel)
if liquid_cell.is_static then if liquid_cell.is_static then
flags = bit.bor(flags, C.LIQUID_FLAG_STATIC) flags = bit.bor(flags, C.LIQUID_FLAG_STATIC)
end end
end end
end end
if x == start_x and y == start_y then if x == start_x and y == start_y then
-- Initial run -- Initial run
current_material = material_number current_material = material_number
current_flags = flags current_flags = flags
elseif current_material ~= material_number or current_flags ~= flags then elseif current_material ~= material_number or current_flags ~= flags then
-- Next run -- Next run
current_run.length = run_length - 1 current_run.length = run_length - 1
current_run.material = current_material current_run.material = current_material
current_run.flags = current_flags current_run.flags = current_flags
if run_count == C.PIXEL_RUN_MAX then if run_count == C.PIXEL_RUN_MAX then
print("Area too complicated to encode") print("Area too complicated to encode")
return nil return nil
end end
current_run = encoded_area.pixel_runs[run_count] current_run = encoded_area.pixel_runs[run_count]
run_count = run_count + 1 run_count = run_count + 1
run_length = 0 run_length = 0
current_material = material_number current_material = material_number
current_flags = flags current_flags = flags
end end
run_length = run_length + 1 run_length = run_length + 1
x = x + 1 x = x + 1
end end
y = y + 1 y = y + 1
end end
current_run.length = run_length - 1 current_run.length = run_length - 1
current_run.material = current_material current_run.material = current_material
current_run.flags = current_flags current_run.flags = current_flags
encoded_area.header.pixel_run_count = run_count encoded_area.header.pixel_run_count = run_count
return encoded_area return encoded_area
end end
local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*") local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*")
---Load an encoded area back into the world. ---Load an encoded area back into the world.
---@param grid_world unknown ---@param grid_world unknown
---@param header EncodedAreaHeader header of the encoded area ---@param header EncodedAreaHeader header of the encoded area
---@param pixel_runs PixelRun[] or ffi array of PixelRun from the encoded area ---@param pixel_runs PixelRun[] or ffi array of PixelRun from the encoded area
---@see encode_area ---@see encode_area
function world.decode(grid_world, header, pixel_runs) function world.decode(grid_world, header, pixel_runs)
local chunk_map = grid_world.vtable.get_chunk_map(grid_world) local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
local top_left_x = header.x local top_left_x = header.x
local top_left_y = header.y local top_left_y = header.y
local width = header.width + 1 local width = header.width + 1
local height = header.height + 1 local height = header.height + 1
local bottom_right_x = top_left_x + width local bottom_right_x = top_left_x + width
local bottom_right_y = top_left_y + height local bottom_right_y = top_left_y + height
local current_run_ix = 0 local current_run_ix = 0
local current_run = pixel_runs[current_run_ix] local current_run = pixel_runs[current_run_ix]
local new_material = current_run.material local new_material = current_run.material
local flags = current_run.flags local flags = current_run.flags
local left = current_run.length + 1 local left = current_run.length + 1
local y = top_left_y local y = top_left_y
while y < bottom_right_y do while y < bottom_right_y do
local x = top_left_x local x = top_left_x
while x < bottom_right_x do while x < bottom_right_x do
if world_ffi.chunk_loaded(chunk_map, x, y) then if world_ffi.chunk_loaded(chunk_map, x, y) then
local ppixel = world_ffi.get_cell(chunk_map, x, y) local ppixel = world_ffi.get_cell(chunk_map, x, y)
local current_material = 0 local current_material = 0
if ppixel[0] ~= nil then if ppixel[0] ~= nil then
local pixel = ppixel[0] local pixel = ppixel[0]
current_material = world_ffi.get_material_id(pixel.vtable.get_material(pixel)) current_material = world_ffi.get_material_id(pixel.vtable.get_material(pixel))
if new_material ~= current_material then if new_material ~= current_material then
world_ffi.remove_cell(grid_world, pixel, x, y, false) world_ffi.remove_cell(grid_world, pixel, x, y, false)
end end
end end
if current_material ~= new_material and new_material ~= 0 then if current_material ~= new_material and new_material ~= 0 then
local pixel = world_ffi.construct_cell(grid_world, x, y, world_ffi.get_material_ptr(new_material), nil) local pixel =
if pixel == nil then world_ffi.construct_cell(grid_world, x, y, world_ffi.get_material_ptr(new_material), nil)
-- TODO: This can happen when the material texture has a if pixel == nil then
-- transparent pixel at the given coordinate. There's -- TODO: This can happen when the material texture has a
-- probably a better way to deal with this, but for now -- transparent pixel at the given coordinate. There's
-- we skip positions like this. -- probably a better way to deal with this, but for now
goto next_pixel -- we skip positions like this.
end goto next_pixel
local cell_type = pixel.vtable.get_cell_type(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) if cell_type == C.CELL_TYPE_LIQUID then
liquid_cell.is_static = bit.band(flags, C.CELL_TYPE_LIQUID) == C.LIQUID_FLAG_STATIC local liquid_cell = ffi.cast(pliquid_cell, pixel)
end liquid_cell.is_static = bit.band(flags, C.CELL_TYPE_LIQUID) == C.LIQUID_FLAG_STATIC
end
ppixel[0] = pixel
end ppixel[0] = pixel
end end
end
::next_pixel::
::next_pixel::
left = left - 1
if left <= 0 then left = left - 1
current_run_ix = current_run_ix + 1 if left <= 0 then
if current_run_ix >= header.pixel_run_count then current_run_ix = current_run_ix + 1
-- No more runs, done if current_run_ix >= header.pixel_run_count then
assert(x == bottom_right_x - 1) -- No more runs, done
assert(y == bottom_right_y - 1) assert(x == bottom_right_x - 1)
return assert(y == bottom_right_y - 1)
end return
end
current_run = pixel_runs[current_run_ix]
new_material = current_run.material current_run = pixel_runs[current_run_ix]
flags = current_run.flags new_material = current_run.material
left = current_run.length + 1 flags = current_run.flags
end left = current_run.length + 1
end
x = x + 1
end x = x + 1
y = y + 1 end
end y = y + 1
end end
end
return world
return world

View file

@ -1,425 +1,425 @@
---@diagnostic disable: assign-type-mismatch ---@diagnostic disable: assign-type-mismatch
---Noita world functionality exposed. ---Noita world functionality exposed.
---@module 'noitapatcher.nsew.world_ffi' ---@module 'noitapatcher.nsew.world_ffi'
---@class WorldFFI ---@class WorldFFI
local world_ffi = {} local world_ffi = {}
local ffi = require("ffi") local ffi = require("ffi")
local np = require("noitapatcher") local np = require("noitapatcher")
local world_info = np.GetWorldInfo() local world_info = np.GetWorldInfo()
if not world_info then if not world_info then
error("Couldn't get world info from NoitaPatcher.") error("Couldn't get world info from NoitaPatcher.")
end end
local gg_ptr = world_info.game_global local gg_ptr = world_info.game_global
ffi.cdef([[ ffi.cdef([[
typedef void* __thiscall placeholder_memfn(void*); typedef void* __thiscall placeholder_memfn(void*);
struct Position { struct Position {
int x; int x;
int y; int y;
}; };
struct Colour { struct Colour {
uint8_t r; uint8_t r;
uint8_t g; uint8_t g;
uint8_t b; uint8_t b;
uint8_t a; uint8_t a;
}; };
struct AABB { struct AABB {
struct Position top_left; struct Position top_left;
struct Position bottom_right; struct Position bottom_right;
}; };
struct std_string { /* VC++ std::string */ struct std_string { /* VC++ std::string */
char *buffer; char *buffer;
char sso_buffer[12]; char sso_buffer[12];
size_t size; size_t size;
size_t capacity; size_t capacity;
}; };
typedef enum cell_type { typedef enum cell_type {
none=0, none=0,
liquid=1, liquid=1,
gas=2, gas=2,
solid=3, solid=3,
fire=4, fire=4,
invalid=4294967295 invalid=4294967295
} cell_type; } cell_type;
struct CellData { struct CellData {
struct std_string name; struct std_string name;
struct std_string ui_name; struct std_string ui_name;
int material_type; int material_type;
int id_2; int id_2;
enum cell_type cell_type; enum cell_type cell_type;
int platform_type; int platform_type;
unsigned int wang_color; unsigned int wang_color;
int gfx_glow; int gfx_glow;
unsigned int gfx_glow_color; unsigned int gfx_glow_color;
char unknown1[24]; char unknown1[24];
unsigned int default_primary_colour; unsigned int default_primary_colour;
char unknown2[36]; char unknown2[36];
bool cell_holes_in_texture; bool cell_holes_in_texture;
bool stainable; bool stainable;
bool burnable; bool burnable;
bool on_fire; bool on_fire;
int fire_hp; int fire_hp;
int autoignition_temperature; int autoignition_temperature;
int _100_minus_autoignition_temp; int _100_minus_autoignition_temp;
int temperature_of_fire; int temperature_of_fire;
int generates_smoke; int generates_smoke;
int generates_flames; int generates_flames;
bool requires_oxygen; bool requires_oxygen;
char padding1[3]; char padding1[3];
struct std_string on_fire_convert_to_material; struct std_string on_fire_convert_to_material;
int on_fire_convert_to_material_id; int on_fire_convert_to_material_id;
struct std_string on_fire_flame_material; struct std_string on_fire_flame_material;
int on_fire_flame_material_id; int on_fire_flame_material_id;
struct std_string on_fire_smoke_material; struct std_string on_fire_smoke_material;
int on_fire_smoke_material_id; int on_fire_smoke_material_id;
struct ConfigExplosion *explosion_config; struct ConfigExplosion *explosion_config;
int durability; int durability;
int crackability; int crackability;
bool electrical_conductivity; bool electrical_conductivity;
bool slippery; bool slippery;
char padding2[2]; char padding2[2];
float stickyness; float stickyness;
struct std_string cold_freezes_to_material; struct std_string cold_freezes_to_material;
struct std_string warmth_melts_to_material; struct std_string warmth_melts_to_material;
int warmth_melts_to_material_id; int warmth_melts_to_material_id;
int cold_freezes_to_material_id; int cold_freezes_to_material_id;
int16_t cold_freezes_chance_rev; int16_t cold_freezes_chance_rev;
int16_t warmth_melts_chance_rev; int16_t warmth_melts_chance_rev;
bool cold_freezes_to_dont_do_reverse_reaction; bool cold_freezes_to_dont_do_reverse_reaction;
char padding3[3]; char padding3[3];
int lifetime; int lifetime;
int hp; int hp;
float density; float density;
bool liquid_sand; bool liquid_sand;
bool liquid_slime; bool liquid_slime;
bool liquid_static; bool liquid_static;
bool liquid_stains_self; bool liquid_stains_self;
int liquid_sticks_to_ceiling; int liquid_sticks_to_ceiling;
float liquid_gravity; float liquid_gravity;
int liquid_viscosity; int liquid_viscosity;
int liquid_stains; int liquid_stains;
unsigned int liquid_stains_custom_color; unsigned int liquid_stains_custom_color;
float liquid_sprite_stain_shaken_drop_chance; float liquid_sprite_stain_shaken_drop_chance;
float liquid_sprite_stain_ignited_drop_chance; float liquid_sprite_stain_ignited_drop_chance;
int8_t liquid_sprite_stains_check_offset; int8_t liquid_sprite_stains_check_offset;
char padding4[3]; char padding4[3];
float liquid_sprite_stains_status_threshold; float liquid_sprite_stains_status_threshold;
float liquid_damping; float liquid_damping;
float liquid_flow_speed; float liquid_flow_speed;
bool liquid_sand_never_box2d; bool liquid_sand_never_box2d;
char unknown7[3]; char unknown7[3];
int8_t gas_speed; int8_t gas_speed;
int8_t gas_upwards_speed; int8_t gas_upwards_speed;
int8_t gas_horizontal_speed; int8_t gas_horizontal_speed;
int8_t gas_downwards_speed; int8_t gas_downwards_speed;
float solid_friction; float solid_friction;
float solid_restitution; float solid_restitution;
float solid_gravity_scale; float solid_gravity_scale;
int solid_static_type; int solid_static_type;
float solid_on_collision_splash_power; float solid_on_collision_splash_power;
bool solid_on_collision_explode; bool solid_on_collision_explode;
bool solid_on_sleep_convert; bool solid_on_sleep_convert;
bool solid_on_collision_convert; bool solid_on_collision_convert;
bool solid_on_break_explode; bool solid_on_break_explode;
bool solid_go_through_sand; bool solid_go_through_sand;
bool solid_collide_with_self; bool solid_collide_with_self;
char padding5[2]; char padding5[2];
struct std_string solid_on_collision_material; struct std_string solid_on_collision_material;
int solid_on_collision_material_id; int solid_on_collision_material_id;
struct std_string solid_break_to_type; struct std_string solid_break_to_type;
int solid_break_to_type_id; int solid_break_to_type_id;
struct std_string convert_to_box2d_material; struct std_string convert_to_box2d_material;
int convert_to_box2d_material_id; int convert_to_box2d_material_id;
int vegetation_full_lifetime_growth; int vegetation_full_lifetime_growth;
struct std_string vegetation_sprite; struct std_string vegetation_sprite;
bool vegetation_random_flip_x_scale; bool vegetation_random_flip_x_scale;
char padding6[3]; char padding6[3];
char unknown11[12]; char unknown11[12];
float wang_noise_percent; float wang_noise_percent;
float wang_curvature; float wang_curvature;
int wang_noise_type; int wang_noise_type;
char unknown12[12]; char unknown12[12];
bool danger_fire; bool danger_fire;
bool danger_radioactive; bool danger_radioactive;
bool danger_poison; bool danger_poison;
bool danger_water; bool danger_water;
char unknown13[24]; char unknown13[24];
bool always_ignites_damagemodel; bool always_ignites_damagemodel;
bool ignore_self_reaction_warning; bool ignore_self_reaction_warning;
char padding7[2]; char padding7[2];
char unknown14[12]; char unknown14[12];
float audio_size_multiplier; float audio_size_multiplier;
bool audio_is_soft; bool audio_is_soft;
char padding8[3]; char padding8[3];
char unknown15[8]; char unknown15[8];
bool show_in_creative_mode; bool show_in_creative_mode;
bool is_just_particle_fx; bool is_just_particle_fx;
char padding9[2]; char padding9[2];
// struct grid_CosmeticParticleConfig *ParticleEffect; // struct grid_CosmeticParticleConfig *ParticleEffect;
}; };
enum CellType { enum CellType {
CELL_TYPE_NONE = 0, CELL_TYPE_NONE = 0,
CELL_TYPE_LIQUID = 1, CELL_TYPE_LIQUID = 1,
CELL_TYPE_GAS = 2, CELL_TYPE_GAS = 2,
CELL_TYPE_SOLID = 3, CELL_TYPE_SOLID = 3,
CELL_TYPE_FIRE = 4, CELL_TYPE_FIRE = 4,
}; };
struct Cell_vtable { struct Cell_vtable {
void (__thiscall *destroy)(struct Cell*, char dealloc); void (__thiscall *destroy)(struct Cell*, char dealloc);
enum CellType (__thiscall *get_cell_type)(struct Cell*); enum CellType (__thiscall *get_cell_type)(struct Cell*);
void* field2_0x8; void* field2_0x8;
void* field3_0xc; void* field3_0xc;
void* field4_0x10; void* field4_0x10;
struct Colour (__thiscall *get_colour)(struct Cell*); struct Colour (__thiscall *get_colour)(struct Cell*);
void* field6_0x18; void* field6_0x18;
void (__thiscall *set_colour)(struct Cell*, struct Colour); void (__thiscall *set_colour)(struct Cell*, struct Colour);
void* field8_0x20; void* field8_0x20;
void* field9_0x24; void* field9_0x24;
void* field10_0x28; void* field10_0x28;
void* field11_0x2c; void* field11_0x2c;
struct CellData* (__thiscall *get_material)(void *); struct CellData* (__thiscall *get_material)(void *);
void* field13_0x34; void* field13_0x34;
void* field14_0x38; void* field14_0x38;
void* field15_0x3c; void* field15_0x3c;
void* field16_0x40; void* field16_0x40;
void* field17_0x44; void* field17_0x44;
void* field18_0x48; void* field18_0x48;
void* field19_0x4c; void* field19_0x4c;
struct Position * (__thiscall *get_position)(void *, struct Position *); struct Position * (__thiscall *get_position)(void *, struct Position *);
void* field21_0x54; void* field21_0x54;
void* field22_0x58; void* field22_0x58;
void* field23_0x5c; void* field23_0x5c;
void* field24_0x60; void* field24_0x60;
void* field25_0x64; void* field25_0x64;
void* field26_0x68; void* field26_0x68;
void* field27_0x6c; void* field27_0x6c;
void* field28_0x70; void* field28_0x70;
bool (__thiscall *is_burning)(struct Cell*); bool (__thiscall *is_burning)(struct Cell*);
void* field30_0x78; void* field30_0x78;
void* field31_0x7c; void* field31_0x7c;
void* field32_0x80; void* field32_0x80;
void (__thiscall *stop_burning)(struct Cell*); void (__thiscall *stop_burning)(struct Cell*);
void* field34_0x88; void* field34_0x88;
void* field35_0x8c; void* field35_0x8c;
void* field36_0x90; void* field36_0x90;
void* field37_0x94; void* field37_0x94;
void* field38_0x98; void* field38_0x98;
void (__thiscall *remove)(struct Cell*); void (__thiscall *remove)(struct Cell*);
void* field40_0xa0; void* field40_0xa0;
}; };
// In the Noita code this would be the ICellBurnable class // In the Noita code this would be the ICellBurnable class
struct Cell { struct Cell {
struct Cell_vtable* vtable; struct Cell_vtable* vtable;
int hp; int hp;
char unknown1[8]; char unknown1[8];
bool is_burning; bool is_burning;
char unknown2[3]; char unknown2[3];
uintptr_t material_ptr; uintptr_t material_ptr;
}; };
struct CLiquidCell { struct CLiquidCell {
struct Cell cell; struct Cell cell;
int x; int x;
int y; int y;
char unknown1; char unknown1;
char unknown2; char unknown2;
bool is_static; bool is_static;
char unknown3; char unknown3;
int unknown4[3]; int unknown4[3];
struct Colour colour; struct Colour colour;
unsigned not_colour; unsigned not_colour;
}; };
typedef struct Cell (*cell_array)[0x40000]; typedef struct Cell (*cell_array)[0x40000];
struct ChunkMap { struct ChunkMap {
int unknown[2]; int unknown[2];
cell_array* (*cells)[0x40000]; cell_array* (*cells)[0x40000];
int unknown2[8]; int unknown2[8];
}; };
struct GridWorld_vtable { struct GridWorld_vtable {
placeholder_memfn* unknown[3]; placeholder_memfn* unknown[3];
struct ChunkMap* (__thiscall *get_chunk_map)(struct GridWorld* this); struct ChunkMap* (__thiscall *get_chunk_map)(struct GridWorld* this);
placeholder_memfn* unknown2[30]; placeholder_memfn* unknown2[30];
}; };
struct GridWorld { struct GridWorld {
struct GridWorld_vtable* vtable; struct GridWorld_vtable* vtable;
int unknown[318]; int unknown[318];
int world_update_count; int world_update_count;
struct ChunkMap chunk_map; struct ChunkMap chunk_map;
int unknown2[41]; int unknown2[41];
struct GridWorldThreadImpl* mThreadImpl; struct GridWorldThreadImpl* mThreadImpl;
}; };
struct GridWorldThreaded_vtable; struct GridWorldThreaded_vtable;
struct GridWorldThreaded { struct GridWorldThreaded {
struct GridWorldThreaded_vtable* vtable; struct GridWorldThreaded_vtable* vtable;
int unknown[287]; int unknown[287];
struct AABB update_region; struct AABB update_region;
}; };
struct vec_pGridWorldThreaded { struct vec_pGridWorldThreaded {
struct GridWorldThreaded** begin; struct GridWorldThreaded** begin;
struct GridWorldThreaded** end_; struct GridWorldThreaded** end_;
struct GridWorldThreaded** capacity_end; struct GridWorldThreaded** capacity_end;
}; };
struct WorldUpdateParams { struct WorldUpdateParams {
struct AABB update_region; struct AABB update_region;
int unknown; int unknown;
struct GridWorldThreaded* grid_world_threaded; struct GridWorldThreaded* grid_world_threaded;
}; };
struct vec_WorldUpdateParams { struct vec_WorldUpdateParams {
struct WorldUpdateParams* begin; struct WorldUpdateParams* begin;
struct WorldUpdateParams* end_; struct WorldUpdateParams* end_;
struct WorldUpdateParams* capacity_end; struct WorldUpdateParams* capacity_end;
}; };
struct GridWorldThreadImpl { struct GridWorldThreadImpl {
int chunk_update_count; int chunk_update_count;
struct vec_pGridWorldThreaded updated_grid_worlds; struct vec_pGridWorldThreaded updated_grid_worlds;
int world_update_params_count; int world_update_params_count;
struct vec_WorldUpdateParams world_update_params; struct vec_WorldUpdateParams world_update_params;
int grid_with_area_count; int grid_with_area_count;
struct vec_pGridWorldThreaded with_area_grid_worlds; struct vec_pGridWorldThreaded with_area_grid_worlds;
int another_count; int another_count;
int another_vec[3]; int another_vec[3];
int some_kind_of_ptr; int some_kind_of_ptr;
int some_kind_of_counter; int some_kind_of_counter;
int last_vec[3]; int last_vec[3];
}; };
typedef struct Cell** __thiscall get_cell_f(struct ChunkMap*, int x, int y); typedef struct Cell** __thiscall get_cell_f(struct ChunkMap*, int x, int y);
typedef bool __thiscall chunk_loaded_f(struct ChunkMap*, int x, int y); typedef bool __thiscall chunk_loaded_f(struct ChunkMap*, int x, int y);
typedef void __thiscall remove_cell_f(struct GridWorld*, void* cell, int x, int y, bool); typedef void __thiscall remove_cell_f(struct GridWorld*, void* cell, int x, int y, bool);
typedef struct Cell* __thiscall construct_cell_f(struct GridWorld*, int x, int y, void* material_ptr, void* memory); typedef struct Cell* __thiscall construct_cell_f(struct GridWorld*, int x, int y, void* material_ptr, void* memory);
]]) ]])
--local function check_celldata_field(f, o) --local function check_celldata_field(f, o)
-- local offset = ffi.offsetof("struct CellData", f) -- local offset = ffi.offsetof("struct CellData", f)
-- assert(offset == o, "Expected field " .. f .. " to be at offset " .. o) -- assert(offset == o, "Expected field " .. f .. " to be at offset " .. o)
--end --end
-- --
--check_celldata_field("wang_color", 0x40) --check_celldata_field("wang_color", 0x40)
--check_celldata_field("generates_flames", 0xa4) --check_celldata_field("generates_flames", 0xa4)
--check_celldata_field("durability", 0x104) --check_celldata_field("durability", 0x104)
--check_celldata_field("cold_freezes_to_material", 0x114) --check_celldata_field("cold_freezes_to_material", 0x114)
--check_celldata_field("liquid_sand", 0x160) --check_celldata_field("liquid_sand", 0x160)
--check_celldata_field("liquid_sprite_stain_ignited_drop_chance", 0x17c) --check_celldata_field("liquid_sprite_stain_ignited_drop_chance", 0x17c)
--check_celldata_field("gas_horizontal_speed", 0x196) --check_celldata_field("gas_horizontal_speed", 0x196)
--check_celldata_field("solid_on_sleep_convert", 0x1ad) --check_celldata_field("solid_on_sleep_convert", 0x1ad)
--check_celldata_field("solid_break_to_type", 0x1d0) --check_celldata_field("solid_break_to_type", 0x1d0)
--check_celldata_field("vegetation_sprite", 0x20c) --check_celldata_field("vegetation_sprite", 0x20c)
--check_celldata_field("wang_noise_type", 0x23c) --check_celldata_field("wang_noise_type", 0x23c)
--check_celldata_field("ignore_self_reaction_warning", 0x269) --check_celldata_field("ignore_self_reaction_warning", 0x269)
--check_celldata_field("is_just_particle_fx", 0x289) --check_celldata_field("is_just_particle_fx", 0x289)
---@class ChunkMap pointer type ---@class ChunkMap pointer type
---@class GridWorld pointer type ---@class GridWorld pointer type
---@class CellData pointer type ---@class CellData pointer type
---@class Cell pointer type ---@class Cell pointer type
---Access a pixel in the world. ---Access a pixel in the world.
---You can write a cell created from world_ffi.construct_cell to this pointer to add a cell into the world. ---You can write a cell created from world_ffi.construct_cell to this pointer to add a cell into the world.
---If there's already a cell at this position, make sure to call world_ffi.remove_cell first. ---If there's already a cell at this position, make sure to call world_ffi.remove_cell first.
---@type fun(chunk_map: ChunkMap, x: integer, y: integer): Cell ---@type fun(chunk_map: ChunkMap, x: integer, y: integer): Cell
world_ffi.get_cell = ffi.cast("get_cell_f*", world_info.get_cell) world_ffi.get_cell = ffi.cast("get_cell_f*", world_info.get_cell)
---Remove a cell from the world. bool return has unknown meaning. ---Remove a cell from the world. bool return has unknown meaning.
---@type fun(grid_world: GridWorld, cell: Cell, x: integer, y: integer): boolean ---@type fun(grid_world: GridWorld, cell: Cell, x: integer, y: integer): boolean
world_ffi.remove_cell = ffi.cast("remove_cell_f*", world_info.remove_cell) world_ffi.remove_cell = ffi.cast("remove_cell_f*", world_info.remove_cell)
---Create a new cell. If memory is null pointer it will allocate its own memory. ---Create a new cell. If memory is null pointer it will allocate its own memory.
---@type fun(grid_world: GridWorld, x: integer, y: integer, material: CellData, memory: ffi.cdata*) ---@type fun(grid_world: GridWorld, x: integer, y: integer, material: CellData, memory: ffi.cdata*)
world_ffi.construct_cell = ffi.cast("construct_cell_f*", world_info.construct_cell) world_ffi.construct_cell = ffi.cast("construct_cell_f*", world_info.construct_cell)
---Check if a chunk is loaded. x and y are world coordinates. ---Check if a chunk is loaded. x and y are world coordinates.
---```lua ---```lua
---if world_ffi.chunk_loaded(chunk_map, x, y) then ---if world_ffi.chunk_loaded(chunk_map, x, y) then
--- local cell = world_ffi.get_cell(chunk_map, x, y) --- local cell = world_ffi.get_cell(chunk_map, x, y)
--- .. --- ..
---``` ---```
---@type fun(chunk_map: ChunkMap, x: integer, y: integer): boolean ---@type fun(chunk_map: ChunkMap, x: integer, y: integer): boolean
world_ffi.chunk_loaded = ffi.cast("chunk_loaded_f*", world_info.chunk_loaded) world_ffi.chunk_loaded = ffi.cast("chunk_loaded_f*", world_info.chunk_loaded)
world_ffi.Position = ffi.typeof("struct Position") world_ffi.Position = ffi.typeof("struct Position")
world_ffi.Colour = ffi.typeof("struct Colour") world_ffi.Colour = ffi.typeof("struct Colour")
world_ffi.AABB = ffi.typeof("struct AABB") world_ffi.AABB = ffi.typeof("struct AABB")
world_ffi.CellType = ffi.typeof("enum CellType") world_ffi.CellType = ffi.typeof("enum CellType")
world_ffi.Cell = ffi.typeof("struct Cell") world_ffi.Cell = ffi.typeof("struct Cell")
world_ffi.CLiquidCell = ffi.typeof("struct CLiquidCell") world_ffi.CLiquidCell = ffi.typeof("struct CLiquidCell")
world_ffi.ChunkMap = ffi.typeof("struct ChunkMap") world_ffi.ChunkMap = ffi.typeof("struct ChunkMap")
world_ffi.GridWorld = ffi.typeof("struct GridWorld") world_ffi.GridWorld = ffi.typeof("struct GridWorld")
world_ffi.GridWorldThreaded = ffi.typeof("struct GridWorldThreaded") world_ffi.GridWorldThreaded = ffi.typeof("struct GridWorldThreaded")
world_ffi.WorldUpdateParams = ffi.typeof("struct WorldUpdateParams") world_ffi.WorldUpdateParams = ffi.typeof("struct WorldUpdateParams")
world_ffi.GridWorldThreadImpl = ffi.typeof("struct GridWorldThreadImpl") world_ffi.GridWorldThreadImpl = ffi.typeof("struct GridWorldThreadImpl")
---Get the grid world. ---Get the grid world.
---@return GridWorld ---@return GridWorld
function world_ffi.get_grid_world() function world_ffi.get_grid_world()
local game_global = ffi.cast("void*", gg_ptr) local game_global = ffi.cast("void*", gg_ptr)
local world_data = ffi.cast("void**", ffi.cast("char*", game_global) + 0xc)[0] local world_data = ffi.cast("void**", ffi.cast("char*", game_global) + 0xc)[0]
local grid_world = ffi.cast("struct GridWorld**", ffi.cast("char*", world_data) + 0x44)[0] local grid_world = ffi.cast("struct GridWorld**", ffi.cast("char*", world_data) + 0x44)[0]
return grid_world return grid_world
end end
local celldata_size = 0x290 local celldata_size = 0x290
local CellData_ptr = ffi.typeof("struct CellData*") local CellData_ptr = ffi.typeof("struct CellData*")
---Turn a standard material id into a material pointer. ---Turn a standard material id into a material pointer.
---@param id integer material id that is used in the standard Noita functions ---@param id integer material id that is used in the standard Noita functions
---@return CellData material to internal material data (aka cell data). ---@return CellData material to internal material data (aka cell data).
---```lua ---```lua
---local gold_ptr = world_ffi.get_material_ptr(CellFactory_GetType("gold")) ---local gold_ptr = world_ffi.get_material_ptr(CellFactory_GetType("gold"))
---``` ---```
function world_ffi.get_material_ptr(id) function world_ffi.get_material_ptr(id)
local game_global = ffi.cast("char*", gg_ptr) local game_global = ffi.cast("char*", gg_ptr)
local cell_factory = ffi.cast('char**', (game_global + 0x18))[0] local cell_factory = ffi.cast("char**", (game_global + 0x18))[0]
local begin = ffi.cast('char**', cell_factory + 0x18)[0] local begin = ffi.cast("char**", cell_factory + 0x18)[0]
local ptr = begin + celldata_size * id local ptr = begin + celldata_size * id
return ffi.cast(CellData_ptr, ptr) --[[@as CellData]] return ffi.cast(CellData_ptr, ptr) --[[@as CellData]]
end end
---Turn a material pointer into a standard material id. ---Turn a material pointer into a standard material id.
---@param material CellData to a material (aka cell data) ---@param material CellData to a material (aka cell data)
---@return integer material id that is accepted by standard Noita functions such as `CellFactory_GetUIName` and `ConvertMaterialOnAreaInstantly`. ---@return integer material id that is accepted by standard Noita functions such as `CellFactory_GetUIName` and `ConvertMaterialOnAreaInstantly`.
---```lua ---```lua
---local mat_id = world_ffi.get_material_id(cell.vtable.get_material(cell)) ---local mat_id = world_ffi.get_material_id(cell.vtable.get_material(cell))
---``` ---```
---See: `world_ffi.get_material_ptr` ---See: `world_ffi.get_material_ptr`
function world_ffi.get_material_id(material) function world_ffi.get_material_id(material)
local game_global = ffi.cast("char*", gg_ptr) local game_global = ffi.cast("char*", gg_ptr)
local cell_factory = ffi.cast('char**', (game_global + 0x18))[0] local cell_factory = ffi.cast("char**", (game_global + 0x18))[0]
local begin = ffi.cast('char**', cell_factory + 0x18)[0] local begin = ffi.cast("char**", cell_factory + 0x18)[0]
local offset = ffi.cast('char*', material) - begin local offset = ffi.cast("char*", material) - begin
return offset / celldata_size return offset / celldata_size
end end
return world_ffi return world_ffi

View file

@ -911,4 +911,4 @@ function rpc.replicate_projectile(seri_ent, position_x, position_y, target_x, ta
GameShootProjectile(source_ent, position_x, position_y, target_x, target_y, ent) GameShootProjectile(source_ent, position_x, position_y, target_x, target_y, ent)
end end
return enemy_sync return enemy_sync

View file

@ -862,4 +862,4 @@ ctx.cap.item_sync = {
item_sync.rpc = rpc item_sync.rpc = rpc
return item_sync return item_sync

View file

@ -1,6 +1,7 @@
local rpc = net.new_rpc_namespace() local rpc = net.new_rpc_namespace()
local tele = {} local tele = {}
local who_has_tele = {} local who_has_tele = {}
local is_holding
rpc.opts_reliable() rpc.opts_reliable()
function rpc.end_tele() function rpc.end_tele()
@ -29,6 +30,12 @@ function rpc.send_tele(body_gid, n, extent, aimangle, bodyangle, distance, mindi
if not table.contains(who_has_tele, ctx.rpc_peer_id) then if not table.contains(who_has_tele, ctx.rpc_peer_id) then
table.insert(who_has_tele, ctx.rpc_peer_id) table.insert(who_has_tele, ctx.rpc_peer_id)
ComponentSetValue2(com, "mState", 1) ComponentSetValue2(com, "mState", 1)
if is_holding == ent then
local mycom = EntityGetFirstComponent(ctx.my_player.entity, "TelekinesisComponent")
if mycom ~= nil then
ComponentSetValue2(mycom, "mState", 0)
end
end
end end
ComponentSetValue(com, "mBodyID", body_id) ComponentSetValue(com, "mBodyID", body_id)
ComponentSetValue2(com, "mStartBodyMaxExtent", extent) ComponentSetValue2(com, "mStartBodyMaxExtent", extent)
@ -92,6 +99,7 @@ function tele.on_world_update()
end end
end end
if gid ~= nil then if gid ~= nil then
is_holding = ent
has_tele = true has_tele = true
rpc.send_tele( rpc.send_tele(
ComponentGetValue2(gid, "value_string"), ComponentGetValue2(gid, "value_string"),