diff --git a/miscs/2022-06-21_teaser.gif b/miscs/2022-06-21_teaser.gif index 93030e103..eed2a3b2e 100644 Binary files a/miscs/2022-06-21_teaser.gif and b/miscs/2022-06-21_teaser.gif differ diff --git a/mods/noita-mp/files/scripts/init/init_.lua b/mods/noita-mp/files/scripts/init/init_.lua index 21e899cc3..74531db8e 100644 --- a/mods/noita-mp/files/scripts/init/init_.lua +++ b/mods/noita-mp/files/scripts/init/init_.lua @@ -23,6 +23,18 @@ if require then dofile("mods/noita-mp/files/scripts/init/init_package_loading.lua") end +if not _G.NoitaPatcherUtils then + -- If require is not available, we are in Noita Components lua context and should use dofile_once instead. + -- But make sure to load files only when needed, to avoid loading them into memory. + if not require then + -- NoitaPatcher won't be available in Noita Components context! + else + print("Initialise NoitaPatcherUtils..") + ---Globally accessible noitapatcher in _G.noitapatcher. + ---@alias _G.NoitaPatcherUtils NoitaPatcherUtils + _G.NoitaPatcherUtils = require("NoitaPatcherUtils") + end +end if not _G.Logger then if not require then @@ -243,18 +255,6 @@ if not _G.Client then end end -if not _G.NoitaPatcherUtils then - -- If require is not available, we are in Noita Components lua context and should use dofile_once instead. - -- But make sure to load files only when needed, to avoid loading them into memory. - if not require then - -- NoitaPatcher won't be available in Noita Components context! - else - ---Globally accessible noitapatcher in _G.noitapatcher. - ---@alias _G.NoitaPatcherUtils NoitaPatcherUtils - _G.NoitaPatcherUtils = require("NoitaPatcherUtils") - end -end - if not _G.guiI then if not require then -- imGui won't be available in Noita Components context! diff --git a/mods/noita-mp/files/scripts/net/Client.lua b/mods/noita-mp/files/scripts/net/Client.lua index e474440e3..b88c99a93 100644 --- a/mods/noita-mp/files/scripts/net/Client.lua +++ b/mods/noita-mp/files/scripts/net/Client.lua @@ -511,14 +511,22 @@ function ClientInit.new(sockClient) error(("onNewNuidSerialized data.entityId is empty: %s"):format(data.entityId), 2) end - if Utils.IsEmpty(data.serializedEntity) then - error(("onNewNuidSerialized data.serializedEntity is empty: %s"):format(data.serializedEntity), 2) + if Utils.IsEmpty(data.serializedEntityString) then + error(("onNewNuidSerialized data.serializedEntityString is empty: %s"):format(data.serializedEntityString), 2) end if Utils.IsEmpty(data.nuid) then error(("onNewNuidSerialized data.nuid is empty: %s"):format(data.nuid), 2) end + if Utils.IsEmpty(data.x) then + error(("onNewNuidSerialized data.x is empty: %s"):format(data.x), 2) + end + + if Utils.IsEmpty(data.y) then + error(("onNewNuidSerialized data.y is empty: %s"):format(data.y), 2) + end + -- FOR TESTING ONLY, DO NOT MERGE --print(Utils.pformat(data)) --os.exit() @@ -529,7 +537,13 @@ function ClientInit.new(sockClient) -- end --end - EntitySerialisationUtils.deserializeEntireRootEntity(data.serializedEntity, data.nuid) + local nuid, entityId = GlobalsUtils.getNuidEntityPair(data.nuid) + + if Utils.IsEmpty(entityId) then + entityId = EntityCreateNew(data.nuid) + end + + entityId = NoitaPatcherUtils.deserializeEntity(entityId, data.serializedEntityString, data.x, data.y) --EntitySerialisationUtils.deserializeEntireRootEntity(data.serializedEntity, data.nuid) sendAck(data.networkMessageId, NetworkUtils.events.newNuidSerialized.name) CustomProfiler.stop("ClientInit.onNewNuidSerialized", cpc32) diff --git a/mods/noita-mp/files/scripts/net/Server.lua b/mods/noita-mp/files/scripts/net/Server.lua index b608fccbb..9f8966806 100644 --- a/mods/noita-mp/files/scripts/net/Server.lua +++ b/mods/noita-mp/files/scripts/net/Server.lua @@ -974,15 +974,16 @@ function ServerInit.new(sockServer) end function self.sendNewNuid(owner, localEntityId, newNuid, x, y, rotation, velocity, filename, health, isPolymorphed) - local cpc017 = CustomProfiler.start("Server.sendNewNuid") - local event = NetworkUtils.events.newNuid.name - local data = { NetworkUtils.getNextNetworkMessageId(), owner, localEntityId, newNuid, x, y, rotation, velocity, filename, health, isPolymorphed } - local sent = self:sendToAll(event, data) - CustomProfiler.stop("Server.sendNewNuid", cpc017) - return sent + local cpc017 = CustomProfiler.start("Server.sendNewNuid") + local event = NetworkUtils.events.newNuid.name + local data = { NetworkUtils.getNextNetworkMessageId(), owner, localEntityId, newNuid, x, y, rotation, velocity, filename, health, + isPolymorphed } + local sent = self:sendToAll(event, data) + CustomProfiler.stop("Server.sendNewNuid", cpc017) + return sent end - function self.sendNewNuidSerialized(ownerName, ownerGuid, entityId, serializedEntity, nuid) + function self.sendNewNuidSerialized(ownerName, ownerGuid, entityId, serializedEntityString, nuid, x, y) local cpc026 = CustomProfiler.start("Server.sendNewNuidSerialized") if Utils.IsEmpty(ownerName) then @@ -994,15 +995,21 @@ function ServerInit.new(sockServer) if Utils.IsEmpty(entityId) then error(("entityId must not be nil or empty %s"):format(entityId), 2) end - if Utils.IsEmpty(serializedEntity) then - error(("serializedEntity must not be nil or empty %s"):format(serializedEntity), 2) + if Utils.IsEmpty(serializedEntityString) or type(serializedEntityString) ~= "string" then + error(("serializedEntityString must not be nil or empty %s or is not of type 'string'."):format(serializedEntityString), 2) end if Utils.IsEmpty(nuid) then error(("nuid must not be nil or empty %s"):format(nuid), 2) end + if Utils.IsEmpty(x) then + error(("x must not be nil or empty %s"):format(x), 2) + end + if Utils.IsEmpty(y) then + error(("y must not be nil or empty %s"):format(y), 2) + end local event = NetworkUtils.events.newNuidSerialized.name - local data = { NetworkUtils.getNextNetworkMessageId(), ownerName, ownerGuid, entityId, serializedEntity, nuid } + local data = { NetworkUtils.getNextNetworkMessageId(), ownerName, ownerGuid, entityId, serializedEntityString, nuid, x, y } local sent = self:sendToAll(event, data) CustomProfiler.stop("Server.sendNewNuidSerialized", cpc026) diff --git a/mods/noita-mp/files/scripts/util/EntityUtils.lua b/mods/noita-mp/files/scripts/util/EntityUtils.lua index b84b7f8a3..7a7476d9e 100644 --- a/mods/noita-mp/files/scripts/util/EntityUtils.lua +++ b/mods/noita-mp/files/scripts/util/EntityUtils.lua @@ -206,40 +206,6 @@ local function findByFilename(filename, filenames) return false end -local function getParentsUntilRootEntity(who, entityId) - local cpc = CustomProfiler.start("EntityUtils.getParentsUntilRootEntity") - local parentNuids = {} - local parentEntityId = EntityGetParent(entityId) -- returns 0, if entity has no parent - - while parentEntityId and parentEntityId > 0 do - local hasParentNuid, parentNuid = NetworkVscUtils.hasNuidSet(parentEntityId) - if not parentNuid then - local localPlayer = MinaUtils.getLocalMinaInformation() - local ownerName = localPlayer.name - local ownerGuid = localPlayer.guid - if who == Server.iAm and not parentNuid then - parentNuid = NuidUtils.getNextNuid() - NetworkVscUtils.addOrUpdateAllVscs(parentEntityId, ownerName, ownerGuid, parentNuid) - local _, _, _, filename, health, rotation, velocity, x, y = NoitaComponentUtils.getEntityData(parentEntityId) - Server.sendNewNuid({ ownerName, ownerGuid }, parentEntityId, parentNuid, x, y, rotation, velocity, - filename, health, EntityUtils.isEntityPolymorphed(entityId)) - elseif who == Client.iAm and not parentNuid then - Client.sendNeedNuid(ownerName, ownerGuid, entityId) - -- TODO: return and wait for nuid? Otherwise child will never know who is the parent. - else - error("Unable to get whoAmI() unused!", 2) - end - end - if type(parentNuid) == "number" then - table.insert(parentNuids, 1, parentNuid) - end - parentEntityId = EntityGetParent(parentEntityId) - end - CustomProfiler.stop("EntityUtils.getParentsUntilRootEntity", cpc) - return parentNuids -end - - --- public global methods: @@ -517,8 +483,8 @@ function EntityUtils.processAndSyncEntityNetworking(startFrameTime) NetworkVscUtils.addOrUpdateAllVscs(entityId, compOwnerName, compOwnerGuid, nuid) end end - Server.sendNewNuid({ compOwnerName, compOwnerGuid }, entityId, nuid, x, y, rotation, velocity, - filename, health, EntityUtils.isEntityPolymorphed(entityId)) + --Server.sendNewNuid({ compOwnerName, compOwnerGuid }, entityId, nuid, x, y, rotation, velocity, + -- filename, health, EntityUtils.isEntityPolymorphed(entityId)) --local finished, serializedEntity = EntitySerialisationUtils.serializeEntireRootEntity(entityId, nuid, startFrameTime) --local ONLYFORTESTING = EntitySerialisationUtils.deserializeEntireRootEntity(serializedEntity) local serializedEntityString = NoitaPatcherUtils.serializeEntity(entityId) @@ -527,16 +493,16 @@ function EntityUtils.processAndSyncEntityNetworking(startFrameTime) finished = true end if finished == true then - Server.sendNewNuidSerialized(compOwnerName, compOwnerGuid, entityId, serializedEntityString, nuid) + Server.sendNewNuidSerialized(compOwnerName, compOwnerGuid, entityId, serializedEntityString, nuid, x, y) else - Logger.warn(Logger.channels.entity, - "EntitySerialisationUtils.serializeEntireRootEntity took too long. Breaking loop by returning entityId.") - -- when executionTime is too long, return the next entityCacheIndex to continue with it - --prevEntityIndex = entityIndex + 1 - EntityCacheUtils.set(entityId, nuid, compOwnerGuid, compOwnerName, filename, x, y, rotation, - velocity.x, velocity.y, health.current, health.max, finished, serializedEntity) - CustomProfiler.stop("EntityUtils.processAndSyncEntityNetworking", cpc) - return -- completely end function, because it took too long + -- Logger.warn(Logger.channels.entity, + -- "EntitySerialisationUtils.serializeEntireRootEntity took too long. Breaking loop by returning entityId.") + -- -- when executionTime is too long, return the next entityCacheIndex to continue with it + -- --prevEntityIndex = entityIndex + 1 + -- EntityCacheUtils.set(entityId, nuid, compOwnerGuid, compOwnerName, filename, x, y, rotation, + -- velocity.x, velocity.y, health.current, health.max, finished, serializedEntityString) + -- CustomProfiler.stop("EntityUtils.processAndSyncEntityNetworking", cpc) + -- return -- completely end function, because it took too long end end end diff --git a/mods/noita-mp/files/scripts/util/NetworkUtils.lua b/mods/noita-mp/files/scripts/util/NetworkUtils.lua index 5951dba5c..732e82405 100644 --- a/mods/noita-mp/files/scripts/util/NetworkUtils.lua +++ b/mods/noita-mp/files/scripts/util/NetworkUtils.lua @@ -85,8 +85,8 @@ NetworkUtils.events = { }, newNuidSerialized = { name = "newNuidSerialized", - schema = { "networkMessageId", "ownerName", "ownerGuid", "entityId", "serializedEntity", "nuid" }, - resendIdentifiers = { "ownerName", "ownerGuid", "entityId" }, + schema = { "networkMessageId", "ownerName", "ownerGuid", "entityId", "serializedEntityString", "nuid", "x", "y" }, + resendIdentifiers = { "ownerName", "ownerGuid", "entityId", "nuid" }, isCacheable = true }, --- needNuid is used to ask for a nuid from client to servers diff --git a/mods/noita-mp/files/scripts/util/NoitaPatcherUtils.lua b/mods/noita-mp/files/scripts/util/NoitaPatcherUtils.lua index fc5433835..77511322f 100644 --- a/mods/noita-mp/files/scripts/util/NoitaPatcherUtils.lua +++ b/mods/noita-mp/files/scripts/util/NoitaPatcherUtils.lua @@ -1,6 +1,7 @@ local NoitaPatcherUtils = {} local np = require("noitapatcher") +local base64 = require("base64") -- see https://dexter.xn--dpping-wxa.eu/NoitaPatcher/Example.html#example @@ -11,7 +12,14 @@ function OnProjectileFired() end function OnProjectileFiredPost() end function NoitaPatcherUtils.serializeEntity(entityId) - return np.SerializeEntity(entityId) + local binaryString = np.SerializeEntity(entityId) + local encoded = base64.encode(binaryString) + return encoded +end + +function NoitaPatcherUtils.deserializeEntity(entityId, serializedEntityString, x, y) + local decoded = base64.decode(serializedEntityString) + return np.DeserializeEntity(entityId, decoded, x, y) end return NoitaPatcherUtils \ No newline at end of file diff --git a/mods/noita-mp/lua_modules/lib/lua/5.1/nsew.version b/mods/noita-mp/lua_modules/lib/lua/5.1/nsew.version deleted file mode 100644 index 6823e7a04..000000000 --- a/mods/noita-mp/lua_modules/lib/lua/5.1/nsew.version +++ /dev/null @@ -1 +0,0 @@ -release-0.0.5 diff --git a/mods/noita-mp/lua_modules/lib/lua/5.1/nsew_native.dll b/mods/noita-mp/lua_modules/lib/lua/5.1/nsew_native.dll deleted file mode 100644 index cbb914520..000000000 Binary files a/mods/noita-mp/lua_modules/lib/lua/5.1/nsew_native.dll and /dev/null differ diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/load.lua b/mods/noita-mp/lua_modules/share/lua/5.1/nsew/load.lua deleted file mode 100644 index 539a902f6..000000000 --- a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/load.lua +++ /dev/null @@ -1,43 +0,0 @@ ---- Helper module for loading NSEW into your mod. --- --- This is not a module you would load in using `load = require("nsew.load")`. --- Instead this file exists to help make Lua's `require` function work with the --- NSEW modules. --- --- @module nsew.load - -__nsew_path = nil - ---- Setup the environment for `require`-ing NSEW modules. --- The usage example shows how you would use this file for the following Noita --- mod structure: --- ---
nsew_client/
---├── init.lua
---├── mod.xml
---├── files
---│   └── ...
---├── deps
---│   └── nsew
---│       ├── load.lua
---│       └── < .. all other nsew files .. >
--- --- You can adapt this example for your own mod. :^) --- --- @tparam string path path to the directory that contains the 'nsew' folder --- @usage --- -- nsew_client/init.lua --- --- -- This tells NSEW where its files are, and configures Lua `package` globals --- -- so that [`local world = require("nsew.world")`] will work. --- local nsew_do_load = dofile_once("mods/nsew_client/deps/nsew/load.lua") --- nsew_do_load("mods/nsew_client/deps") --- --- local world = require("nsew.world") --- -- ... -function do_load(path) - __nsew_path = path .. "/nsew/" - package.path = package.path .. ";" .. path .. "/?.lua" -end - -return do_load diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/native_dll.lua b/mods/noita-mp/lua_modules/share/lua/5.1/nsew/native_dll.lua deleted file mode 100644 index c7ddd4d8e..000000000 --- a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/native_dll.lua +++ /dev/null @@ -1,11 +0,0 @@ ---- Native library. Primarily for internal use. --- @module 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 diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/rect.lua b/mods/noita-mp/lua_modules/share/lua/5.1/nsew/rect.lua deleted file mode 100644 index 41b847ecf..000000000 --- a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/rect.lua +++ /dev/null @@ -1,134 +0,0 @@ ---- Rectangle utilities. --- @module nsew.rect - -local rect = {} - -local ffi = require("ffi") -local native_dll = require("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; -}; - -]]) - -local Rectangle_mt = { - __index = { - area = function(r) - return (r.right - r.left) * (r.bottom - r.top) - end, - height = function(r) - return r.bottom - r.top - end, - width = function(r) - return r.right - r.left - end, - }, -} -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 it iterator returning squares --- @tparam int size maximum width and height --- @return rectangle iterator where the extents never exceed `size` -function rect.parts(it, size) - local region - local posx - local posy - return function() - if region == nil then - region = it() - 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 - -local Optimiser_mt = { - __gc = function(opt) - native_dll.lib.rectangle_optimiser_delete(opt.impl) - end, - - __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() - end, - get = function(opt, index) - return native_dll.lib.rectangle_optimiser_get(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 - - ret = native_dll.lib.rectangle_optimiser_get(opt.impl, index) - index = index + 1 - return ret - end - end, - } -} -rect.Optimiser = ffi.metatype("struct lua_nsew_rectangle_optimiser", Optimiser_mt) - ---- Create a new rectangle Optimiser --- @treturn Optimiser empty optimiser -function rect.Optimiser_new() - return rect.Optimiser(native_dll.lib.rectangle_optimiser_new()) -end - -return rect diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/world.lua b/mods/noita-mp/lua_modules/share/lua/5.1/nsew/world.lua deleted file mode 100644 index 69c153d38..000000000 --- a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/world.lua +++ /dev/null @@ -1,239 +0,0 @@ ---- World read / write functionality. --- @module nsew.world -local world = {} - -local ffi = require("ffi") -local world_ffi = require("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]; -}; - -]]) - -world.EncodedAreaHeader = ffi.typeof("struct EncodedAreaHeader") -world.PixelRun = ffi.typeof("struct PixelRun") -world.EncodedArea = ffi.typeof("struct EncodedArea") - -local pliquid_cell = ffi.typeof("struct CLiquidCell*") - ---- Total bytes taken up by the encoded area --- @tparam EncodedArea encoded_area --- @treturn int total number of bytes that encodes the area --- @usage --- local data = ffi.string(area, world.encoded_size(area)) --- peer:send(data) -function world.encoded_size(encoded_area) - return (ffi.sizeof(world.EncodedAreaHeader) + encoded_area.header.pixel_run_count * ffi.sizeof(world.PixelRun)) -end - ---- Encode the given rectangle of the world --- The rectangle defined by {`start_x`, `start_y`, `end_x`, `end_y`} must not --- exceed 256 in width or height. --- @param chunk_map --- @tparam int start_x coordinate --- @tparam int start_y coordinate --- @tparam int end_x coordinate --- @tparam int end_y coordinate --- @tparam EncodedArea encoded_area memory to use, if nil this function allocates its own memory --- @return returns an EncodedArea or nil if the area could not be encoded --- @see decode -function world.encode_area(chunk_map, start_x, start_y, end_x, end_y, encoded_area) - start_x = ffi.cast('int32_t', start_x) - start_y = ffi.cast('int32_t', start_y) - end_x = ffi.cast('int32_t', end_x) - end_y = ffi.cast('int32_t', end_y) - - encoded_area = encoded_area or world.EncodedArea() - - local width = end_x - start_x - local height = end_y - start_y - - if width <= 0 or height <= 0 then - print("Invalid world part, negative dimension") - return nil - end - - if width > 256 or height > 256 then - print("Invalid world part, dimension greater than 256") - return nil - end - - encoded_area.header.x = start_x - encoded_area.header.y = start_y - encoded_area.header.width = width - 1 - encoded_area.header.height = height - 1 - - local run_count = 1 - - local current_run = encoded_area.pixel_runs[0] - local run_length = 0 - local current_material = 0 - local current_flags = 0 - - local y = start_y - while y < end_y do - local x = start_x - while x < end_x do - local material_number = 0 - local flags = 0 - - local ppixel = world_ffi.get_cell(chunk_map, x, y) - local pixel = ppixel[0] - if pixel ~= nil then - local cell_type = pixel.vtable.get_cell_type(pixel) - - if cell_type ~= C.CELL_TYPE_SOLID then - local material_ptr = pixel.vtable.get_material(pixel) - material_number = world_ffi.get_material_id(material_ptr) - end - - if cell_type == C.CELL_TYPE_LIQUID then - local liquid_cell = ffi.cast(pliquid_cell, pixel) - if liquid_cell.is_static then - flags = bit.bor(flags, C.LIQUID_FLAG_STATIC) - end - end - end - - if x == start_x and y == start_y then - -- Initial run - current_material = material_number - current_flags = flags - elseif current_material ~= material_number or current_flags ~= flags then - -- Next run - current_run.length = run_length - 1 - current_run.material = current_material - current_run.flags = current_flags - - if run_count == C.PIXEL_RUN_MAX then - print("Area too complicated to encode") - return nil - end - - current_run = encoded_area.pixel_runs[run_count] - run_count = run_count + 1 - - run_length = 0 - current_material = material_number - current_flags = flags - end - - run_length = run_length + 1 - - x = x + 1 - end - y = y + 1 - end - - current_run.length = run_length - 1 - current_run.material = current_material - current_run.flags = current_flags - - encoded_area.header.pixel_run_count = run_count - - return encoded_area -end - -local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*") - ---- Load an encoded area back into the world. --- @param grid_world --- @tparam EncodedAreaHeader header header of the encoded area --- @param received pointer or ffi array of PixelRun from the encoded area --- @see encode_area -function world.decode(grid_world, header, pixel_runs) - local chunk_map = grid_world.vtable.get_chunk_map(grid_world) - - local top_left_x = header.x - local top_left_y = header.y - local width = header.width + 1 - local height = header.height + 1 - local bottom_right_x = top_left_x + width - local bottom_right_y = top_left_y + height - - local current_run_ix = 0 - local current_run = pixel_runs[current_run_ix] - local new_material = current_run.material - local flags = current_run.flags - local left = current_run.length + 1 - - local y = top_left_y - while y < bottom_right_y do - local x = top_left_x - while x < bottom_right_x do - if world_ffi.chunk_loaded(chunk_map, x, y) then - local ppixel = world_ffi.get_cell(chunk_map, x, y) - local current_material = 0 - - if 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) - 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 - - 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 diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/world_ffi.lua b/mods/noita-mp/lua_modules/share/lua/5.1/nsew/world_ffi.lua deleted file mode 100644 index 24a5a6420..000000000 --- a/mods/noita-mp/lua_modules/share/lua/5.1/nsew/world_ffi.lua +++ /dev/null @@ -1,270 +0,0 @@ ---- Noita world functionality exposed. --- @module nsew.world_ffi - -local world_ffi = {} - -local ffi = require("ffi") - -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*", 0x007c27f0) - ---- 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*", 0x006ad810) - ---- 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*", 0x00696fc0) - ---- 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*", 0x007c26d0) - -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**", 0x01014760)[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] - return grid_world -end - -local material_props_size = 0x28c - ---- 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**", 0x01014760)[0] - local cell_factory = ffi.cast('char**', (game_global + 0x18))[0] - local begin = ffi.cast('char**', cell_factory + 0x18)[0] - local ptr = begin + material_props_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**", 0x01014760)[0] - 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 / material_props_size -end - -return world_ffi