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) {
// Shouldn't ever error.
for (i, id) in raw::physics_body_id_get_from_entity(self, None)
.unwrap_or_default()
.iter()
.enumerate()
{
let n = 17000.0 + (64.0 * (self.0.get() as usize + i) as f64);
let _ = raw::physics_body_id_set_transform(*id, n, n, 0.0, 0.0, 0.0, 0.0);
let body_id = raw::physics_body_id_get_from_entity(self, None).unwrap_or_default();
if !body_id.is_empty() {
for com in raw::entity_get_with_tag("ew_peer".into())
.unwrap_or_default()
.iter()
.filter_map(|e| {
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);
}
@ -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>) {
let _ = raw::game_print(value.as_ref().into());
}
@ -560,6 +588,21 @@ pub mod raw {
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>(
component: ComponentID,
object: &str,

View file

@ -1,24 +1,22 @@
-- You're supposed to `dofile_once("path/to/load.lua")` this file.
local orig_do_mod_appends = do_mod_appends
do_mod_appends = function(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
print("Couldn't detect NoitaPatcher path")
end
__nsew_path = noitapatcher_path .. "/noitapatcher/nsew/"
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")
-- local nsew = require("noitapatcher.nsew")
end
-- You're supposed to `dofile_once("path/to/load.lua")` this file.
local orig_do_mod_appends = do_mod_appends
do_mod_appends = function(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
print("Couldn't detect NoitaPatcher path")
end
__nsew_path = noitapatcher_path .. "/noitapatcher/nsew/"
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")
-- local nsew = require("noitapatcher.nsew")
end

View file

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

View file

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

View file

@ -1,272 +1,273 @@
---@diagnostic disable: cast-local-type
---World read / write functionality.
---@module 'noitapatcher.nsew.world'
---@class World
local world = {}
local ffi = require("ffi")
local world_ffi = require("noitapatcher.nsew.world_ffi")
local C = ffi.C
ffi.cdef([[
enum ENCODE_CONST {
PIXEL_RUN_MAX = 4096,
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];
};
]])
---@class PixelRun
---@field flags integer
---@field material integer
---@field length integer
---@class EncodedAreaHeader
---@field x integer
---@field y integer
---@field width integer
---@field height integer
---@field pixel_run_count integer
---@class EncodedArea
---@field header EncodedAreaHeader
---@field pixel_runs PixelRun[] a pointer
world.EncodedAreaHeader = ffi.typeof("struct EncodedAreaHeader")
world.PixelRun = ffi.typeof("struct PixelRun")
---@type fun(): EncodedArea
---@diagnostic disable-next-line: assign-type-mismatch
world.EncodedArea = ffi.typeof("struct EncodedArea")
local pliquid_cell = ffi.typeof("struct CLiquidCell*")
---Total bytes taken up by the encoded area
---@param encoded_area EncodedArea
---@return integer total number of bytes that encodes the area
---```lua
---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 unknown
---@param start_x integer coordinate
---@param start_y integer coordinate
---@param end_x integer coordinate
---@param end_y integer coordinate
---@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
---@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)
---@cast start_x integer
---@cast start_y integer
---@cast end_x integer
---@cast end_x integer
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
local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*")
---Load an encoded area back into the world.
---@param grid_world unknown
---@param header EncodedAreaHeader header of the encoded area
---@param pixel_runs PixelRun[] 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 ppixel[0] ~= nil then
local pixel = ppixel[0]
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
local pixel = world_ffi.construct_cell(grid_world, x, y, world_ffi.get_material_ptr(new_material), 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
---@diagnostic disable: cast-local-type
---World read / write functionality.
---@module 'noitapatcher.nsew.world'
---@class World
local world = {}
local ffi = require("ffi")
local world_ffi = require("noitapatcher.nsew.world_ffi")
local C = ffi.C
ffi.cdef([[
enum ENCODE_CONST {
PIXEL_RUN_MAX = 4096,
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];
};
]])
---@class PixelRun
---@field flags integer
---@field material integer
---@field length integer
---@class EncodedAreaHeader
---@field x integer
---@field y integer
---@field width integer
---@field height integer
---@field pixel_run_count integer
---@class EncodedArea
---@field header EncodedAreaHeader
---@field pixel_runs PixelRun[] a pointer
world.EncodedAreaHeader = ffi.typeof("struct EncodedAreaHeader")
world.PixelRun = ffi.typeof("struct PixelRun")
---@type fun(): EncodedArea
---@diagnostic disable-next-line: assign-type-mismatch
world.EncodedArea = ffi.typeof("struct EncodedArea")
local pliquid_cell = ffi.typeof("struct CLiquidCell*")
---Total bytes taken up by the encoded area
---@param encoded_area EncodedArea
---@return integer total number of bytes that encodes the area
---```lua
---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 unknown
---@param start_x integer coordinate
---@param start_y integer coordinate
---@param end_x integer coordinate
---@param end_y integer coordinate
---@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
---@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)
---@cast start_x integer
---@cast start_y integer
---@cast end_x integer
---@cast end_x integer
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
local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*")
---Load an encoded area back into the world.
---@param grid_world unknown
---@param header EncodedAreaHeader header of the encoded area
---@param pixel_runs PixelRun[] 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 ppixel[0] ~= nil then
local pixel = ppixel[0]
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
local pixel =
world_ffi.construct_cell(grid_world, x, y, world_ffi.get_material_ptr(new_material), 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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
local rpc = net.new_rpc_namespace()
local tele = {}
local who_has_tele = {}
local is_holding
rpc.opts_reliable()
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
table.insert(who_has_tele, ctx.rpc_peer_id)
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
ComponentSetValue(com, "mBodyID", body_id)
ComponentSetValue2(com, "mStartBodyMaxExtent", extent)
@ -92,6 +99,7 @@ function tele.on_world_update()
end
end
if gid ~= nil then
is_holding = ent
has_tele = true
rpc.send_tele(
ComponentGetValue2(gid, "value_string"),