noita_entangled_worlds/NoitaPatcher/noitapatcher/nsew/world_ffi.lua
2024-05-10 18:47:01 +03:00

276 lines
8.1 KiB
Lua

--- Noita world functionality exposed.
---@module 'noitapatcher.nsew.world_ffi'
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;
};
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;
void* (__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);
]])
--- Access a pixel in the world.
-- @function get_cell
-- @param chunk_map chunk map
-- @tparam int x coordinate
-- @tparam int y coordinate
-- @return Pointer to a pointer to a cell. You can write a cell created from @{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 @{remove_cell} first.
world_ffi.get_cell = ffi.cast("get_cell_f*", world_info.get_cell)
--- Remove a cell from the world.
-- @function remove_cell
-- @param grid_world
-- @param cell pointer to the cell you want to remove
-- @tparam int x coordinate
-- @tparam int y coordinate
-- @tparam bool noidea no idea
world_ffi.remove_cell = ffi.cast("remove_cell_f*", world_info.remove_cell)
--- Create a new cell.
-- @function construct_cell
-- @param grid_world
-- @tparam int x coordinate
-- @tparam int y coordinate
-- @param material_ptr pointer to material
-- @param pointer to memory to use. nullptr will make this function allocate its own memory
world_ffi.construct_cell = ffi.cast("construct_cell_f*", world_info.construct_cell)
--- Check if a chunk is loaded.
-- @function chunk_loaded
-- @param chunk_map
-- @tparam int x world coordinate
-- @tparam int y world coordinate
-- @usage
-- if world_ffi.chunk_loaded(chunk_map, x, y) then
-- local cell = world_ffi.get_cell(chunk_map, x, y)
-- -- ...
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 pointer to the grid world
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
--- Turn a standard material id into a material pointer.
-- @param id material id that is used in the standard Noita functions
-- @return pointer to internal material data (aka cell data).
-- @usage 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 ptr
end
--- Turn a material pointer into a standard material id.
-- @param ptr pointer to a material (aka cell data)
-- @treturn int material id that is accepted by standard Noita functions such as
-- `CellFactory_GetUIName` and `ConvertMaterialOnAreaInstantly`.
-- @usage local mat_id = world_ffi.get_material_id(cell.vtable.get_material(cell))
-- @see get_material_ptr
function world_ffi.get_material_id(ptr)
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*', ptr) - begin
return offset / celldata_size
end
return world_ffi