diff --git a/.debug/grep-jit-profiler.sh b/.debug/grep-jit-profiler.sh new file mode 100644 index 000000000..d1cdff40d --- /dev/null +++ b/.debug/grep-jit-profiler.sh @@ -0,0 +1,8 @@ +cat jit.p.log | grep '3.%' -A 3 -B 3 >'30%+.log' +cat jit.p.log | grep '4.%' -A 3 -B 3 >'40%+.log' +cat jit.p.log | grep '5.%' -A 3 -B 3 >'50%+.log' +cat jit.p.log | grep '6.%' -A 3 -B 3 >'60%+.log' +cat jit.p.log | grep '7.%' -A 3 -B 3 >'70%+.log' +cat jit.p.log | grep '8.%' -A 3 -B 3 >'80%+.log' +cat jit.p.log | grep '9.%' -A 3 -B 3 >'90%+.log' +cat jit.p.log | grep '10.%' -A 3 -B 3 >'100%.log' diff --git a/.vscode/settings.json b/.vscode/settings.json index d521a6060..b5cc4ed0b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -77,4 +77,7 @@ "prettier.printWidth": 110, "lua.debug.settings.luaVersion": "jit", "lua-local.interpreter": "", + "[shellscript]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + }, } diff --git a/mods/noita-mp/config.lua b/mods/noita-mp/config.lua index 0c6d48a42..09fadf8ab 100644 --- a/mods/noita-mp/config.lua +++ b/mods/noita-mp/config.lua @@ -24,7 +24,15 @@ entityUtils.include = { } entityUtils.exclude = { byComponentsName = { "WorldStateComponent" }, - byFilename = { "particle", "tree_entity.xml", "vegetation", "custom_cards" } + byFilename = { + "controls", + "custom_cards", + "particle", + "player_stats", + "tree_entity.xml", + "vegetation", + "vines", + } } entityUtils.remove = { diff --git a/mods/noita-mp/files/scripts/Gui.lua b/mods/noita-mp/files/scripts/Gui.lua index d9188aa81..1eb1e2740 100644 --- a/mods/noita-mp/files/scripts/Gui.lua +++ b/mods/noita-mp/files/scripts/Gui.lua @@ -343,7 +343,7 @@ function Gui:drawPlayMenu() self.imGui.Separator() if self.imGui.Button("Start Server!") then - self.server:start(self.server:getAddress(), self.server:getPort()) + self.server:preStart(self.server:getAddress(), self.server:getPort()) self.showPlayMenu = false end else @@ -359,7 +359,7 @@ function Gui:drawPlayMenu() self.imGui.Separator() if self.imGui.Button("Join self.server..") then - self.client:connect(serverIp, tonumber(serverPort)) + self.client:preConnect(serverIp, tonumber(serverPort)) self.showPlayMenu = false end end diff --git a/mods/noita-mp/files/scripts/extensions/tableExtensions.lua b/mods/noita-mp/files/scripts/extensions/tableExtensions.lua index 9c92a0339..c082a5bcc 100644 --- a/mods/noita-mp/files/scripts/extensions/tableExtensions.lua +++ b/mods/noita-mp/files/scripts/extensions/tableExtensions.lua @@ -41,6 +41,11 @@ function table.contains(tbl, key) if key == "" then return false, -1 end + for k, v in pairs(tbl) do + if string.contains(key, v) then + return true, k + end + end end for k, v in pairs(tbl) do if v == key then diff --git a/mods/noita-mp/files/scripts/net/Client.lua b/mods/noita-mp/files/scripts/net/Client.lua index f3f250319..df377d6b8 100644 --- a/mods/noita-mp/files/scripts/net/Client.lua +++ b/mods/noita-mp/files/scripts/net/Client.lua @@ -715,7 +715,7 @@ end ---@param port number|nil port number from 1 to max of 65535 or nil ---@param code number|nil connection code 0 = connecting first time, 1 = connected second time with loaded seed ---@see sock.connect -function Client:connect(ip, port, code) +function Client:preConnect(ip, port, code) local cpc = self.customProfiler:start("Client.connect") if self:isConnecting() or self:isConnected() then @@ -748,20 +748,17 @@ function Client:connect(ip, port, code) "" ) - -- Inheritance: https://ozzypig.com/2018/05/10/object-oriented-programming-in-lua-part-5-inheritance - self.sock.connect(self, code) --sockClientConnect(self, code) - + self:connect(code) -- FYI: If you want to send data after connected, do it in the "connect" callback function - self.customProfiler:stop("Client.connect", cpc16) + self.customProfiler:stop("Client.connect", cpc) end ----Disconnects from the server. Inherit from sock.disconnect. ----@see sock.disconnect -function Client:disconnect() +--- Disconnects from the server. +---@see SockClient.disconnect +function Client:preDisconnect() local cpc = self.customProfiler:start("Client.disconnect") - if self.isConnected() then - -- Inheritance: https://ozzypig.com/2018/05/10/object-oriented-programming-in-lua-part-5-inheritance - self.sock.disconnect(self) --sockClientDisconnect(self) + if self:isConnected() then + self:disconnect() else self.logger:info(self.logger.channels.network, "Client isn't connected, no need to disconnect!") end @@ -771,15 +768,15 @@ end local prevTime = 0 ---Updates the Client by checking for network events and handling them. Inherit from sock.update. ---@param startFrameTime number required ----@see sock.update -function Client:update(startFrameTime) +---@see SockClient.update +function Client:preUpdate(startFrameTime) local cpc = self.customProfiler:start("Client.update") if not self:isConnected() and not self:isConnecting() or self:isDisconnected() then self.customProfiler:stop("Client.update", cpc) return end - self.sendMinaInformation() + self:sendMinaInformation() --self.entityUtils.destroyClientEntities() --self.entityUtils.processEntityNetworking() @@ -798,11 +795,10 @@ function Client:update(startFrameTime) --self.entityUtils.destroyClientEntities() --self.entityUtils.syncEntityData() - self.entityUtils.syncDeadNuids() + self.entityUtils:syncDeadNuids(nil, self) end - -- Inheritance: https://ozzypig.com/2018/05/10/object-oriented-programming-in-lua-part-5-inheritance - self.update(self) --sockClientUpdate(self) + self:update() self.customProfiler:stop("Client.update", cpc) end @@ -950,7 +946,7 @@ end ---@see Server.amIServer function Client:amIClient() --local cpc24 = self.customProfiler:start("Client.amIClient") DO NOT PROFILE, stack overflow error! See self.customProfiler.lua - if not self.server.amIServer() then + if not self.server:amIServer() then --self.customProfiler:stop("Client.amIClient", cpc24) return true end @@ -1011,14 +1007,13 @@ function Client.new(clientObject, serverOrAddress, port, maxChannels, server, np if not clientObject.logger or clientObject.logger ~= clientObject.noitaMpSettings.logger then clientObject.logger = clientObject.noitaMpSettings.logger or error("Client:new requires a server object!", 2) end - if not clientObject.messagePack then - clientObject.messagePack = server.messagePack or require("MessagePack") - end if not clientObject.minaUtils then clientObject.minaUtils = server.minaUtils or require("MinaUtils") end if not clientObject.networkUtils then clientObject.networkUtils = server.networkUtils or require("NetworkUtils") + :new(clientObject.customProfiler, clientObject.guidUtils, clientObject.logger, + clientObject.networkCacheUtils, clientObject.utils) end if not clientObject.noitaPatcherUtils then clientObject.noitaPatcherUtils = server.noitaPatcherUtils or @@ -1028,12 +1023,6 @@ function Client.new(clientObject, serverOrAddress, port, maxChannels, server, np if not clientObject.server then clientObject.server = server or error("Client:new requires a server object!", 2) end - if not clientObject.sock then - clientObject.sock = server.sock or require("sock") - end - if not clientObject.zstandard then - clientObject.zstandard = server.zstandard or require("zstd") - end if not clientObject.utils then clientObject.utils = server.utils or error("Client:new requires a server object!", 2) end diff --git a/mods/noita-mp/files/scripts/net/Server.lua b/mods/noita-mp/files/scripts/net/Server.lua index 63a5572ff..4146bd6dc 100644 --- a/mods/noita-mp/files/scripts/net/Server.lua +++ b/mods/noita-mp/files/scripts/net/Server.lua @@ -696,7 +696,7 @@ end ---Starts a server on ip and port. Both can be nil, then ModSettings will be used. ---@param ip string|nil localhost or 127.0.0.1 or nil ---@param port number|nil port number from 1 to max of 65535 or nil -function Server:start(ip, port) +function Server:preStart(ip, port) local cpc = self.customProfiler:start("Server.start") if not ip then --ip = tostring(ModSettingGet("noita-mp.server_ip")) @@ -712,7 +712,7 @@ function Server:start(ip, port) self.logger:info(self.logger.channels.network, ("Starting server on %s:%s.."):format(ip, port)) - local success = getmetatable(self).__index.start(self, ip, port) + local success = self:start(ip, port, self.fileUtils, self.logger) if success == true then self.logger:info(self.logger.channels.network, ("Server started on %s:%s"):format(self:getAddress(), self:getPort())) @@ -759,7 +759,7 @@ local prevTime = 0 ---Updates the server by checking for network events and handling them. ---@param startFrameTime number required ---@see SockServer.update -function Server:update(startFrameTime) +function Server:preUpdate(startFrameTime) local cpc = self.customProfiler:start("Server.update") if not self:isRunning() then --if not self.host then server not established @@ -777,7 +777,7 @@ function Server:update(startFrameTime) end self:sendMinaInformation() - self.entityUtils:syncEntities(startFrameTime) + self.entityUtils:syncEntities(startFrameTime, self, nil) local nowTime = GameGetRealWorldTimeSinceStarted() * 1000 -- *1000 to get milliseconds local elapsedTime = nowTime - prevTime @@ -789,12 +789,12 @@ function Server:update(startFrameTime) --updateVariables() --self.entityUtils:syncEntityData() - self.entityUtils:syncDeadNuids() + self.entityUtils:syncDeadNuids(self, nil) --end self.customProfiler:stop("Server.update.tick", cpc1) end - getmetatable(self).__index.update(self) + self:update() self.customProfiler:stop("Server.update", cpc) end @@ -851,7 +851,7 @@ function Server:sendNewNuid(ownerName, ownerGuid, entityId, serializedEntityStri end local event = self.networkUtils.events.newNuid.name - local data = { self.networkUtils.getNextNetworkMessageId(), ownerName, ownerGuid, entityId, serializedEntityString, nuid, x, y, + local data = { self.networkUtils:getNextNetworkMessageId(), ownerName, ownerGuid, entityId, serializedEntityString, nuid, x, y, initialSerializedEntityString } local sent = self:sendToAll(event, data) @@ -922,7 +922,7 @@ function Server:sendMinaInformation() local guid = self.minaUtils:getLocalMinaGuid() local entityId = self.minaUtils:getLocalMinaEntityId() local nuid = self.minaUtils:getLocalMinaNuid() - local _name, _guid, _nuid, _filename, health, rotation, velocity, x, y = NoitaComponentUtils.getEntityData(entityId) -- TODO: rework this + local _name, _guid, _nuid, _filename, health, rotation, velocity, x, y = self.noitaComponentUtils:getEntityData(entityId) -- TODO: rework this local data = { self.networkUtils:getNextNetworkMessageId(), self.fileUtils:GetVersionByFile(), name, guid, entityId, nuid, { x = x, y = y }, health } @@ -1051,9 +1051,12 @@ function Server.new(address, port, maxPeers, maxChannels, inBandwidth, outBandwi serverObject.logger, nil) end - if not serverObject.networkUtils then - ---@type NetworkUtils - serverObject.networkUtils = require("NetworkUtils") --:new() + if not serverObject.guidUtils then + ---@type GuidUtils + ---@see GuidUtils + serverObject.guidUtils = require("GuidUtils") + :new(nil, serverObject.customProfiler, serverObject.fileUtils, serverObject.logger, + nil, nil, serverObject.utils) end if not serverObject.networkCache then @@ -1066,6 +1069,13 @@ function Server.new(address, port, maxPeers, maxChannels, inBandwidth, outBandwi serverObject.networkCacheUtils = require("NetworkCacheUtils") --:new() end + if not serverObject.networkUtils then + ---@type NetworkUtils + serverObject.networkUtils = require("NetworkUtils") + :new(serverObject.customProfiler, serverObject.guidUtils, serverObject.logger, + serverObject.networkCacheUtils, serverObject.utils) + end + if not serverObject.noitaComponentUtils then ---@type NoitaComponentUtils serverObject.noitaComponentUtils = require("NoitaComponentUtils") @@ -1091,55 +1101,18 @@ function Server.new(address, port, maxPeers, maxChannels, inBandwidth, outBandwi :new(nil, {}, serverObject.customProfiler, serverObject.entityCacheUtils, serverObject.entityCache, serverObject.globalsUtils, serverObject.noitaMpSettings.logger, serverObject.minaUtils, serverObject.networkUtils, serverObject.networkVscUtils, serverObject.noitaComponentUtils, - serverObject.nuidUtils, serverObject, serverObject.noitaMpSettings.utils) or + serverObject.nuidUtils, serverObject, serverObject.noitaMpSettings.utils, np) or error("Unable to create EntityUtils!", 2) serverObject.entityCache.entityUtils = serverObject.entityUtils end - if not serverObject.guidUtils then - ---@type GuidUtils - ---@see GuidUtils - serverObject.guidUtils = require("GuidUtils") - :new(nil, serverObject.customProfiler, serverObject.fileUtils, serverObject.logger, - nil, nil, serverObject.utils) - end - - if not serverObject.messagePack then - serverObject.messagePack = require("MessagePack") - end - if not serverObject.noitaPatcherUtils then serverObject.noitaPatcherUtils = require("NoitaPatcherUtils") :new(nil, nil, serverObject.customProfiler, np) end - --if not serverObject.sock then - -- serverObject.sock = require("sock") - --end - if not serverObject.zstandard then - serverObject.zstandard = require("zstd") - end - -- [[ Attributes ]] - serverObject.address = address - serverObject.port = port - serverObject.host = nil - serverObject.messageTimeout = 0 - serverObject.maxChannels = maxChannels - serverObject.maxPeers = maxPeers - serverObject.sendMode = "reliable" - serverObject.defaultSendMode = "reliable" - serverObject.sendChannel = 0 - serverObject.defaultSendChannel = 0 - serverObject.peers = {} - serverObject.clients = {} - --serverObject.listener = newListener() - --serverObject.logger = newLogger("SERVER") - serverObject.serialize = nil - serverObject.deserialize = nil - serverObject.packetsSent = 0 - serverObject.packetsReceived = 0 serverObject.acknowledgeMaxSize = 500 serverObject.guid = nil @@ -1158,6 +1131,7 @@ function Server.new(address, port, maxPeers, maxChannels, inBandwidth, outBandwi serverObject.guid = tostring(serverObject.noitaMpSettings:get("noita-mp.guid", "string")) serverObject.guidUtils:setGuid(nil, serverObject, serverObject.guid) + print("Server loaded, instantiated and inherited!") serverObject.customProfiler:stop("Server.new", cpc) return serverObject end diff --git a/mods/noita-mp/files/scripts/net/SockClient.lua b/mods/noita-mp/files/scripts/net/SockClient.lua index eb9399059..97e9f8e37 100644 --- a/mods/noita-mp/files/scripts/net/SockClient.lua +++ b/mods/noita-mp/files/scripts/net/SockClient.lua @@ -107,12 +107,6 @@ function SockClient:establishClient(serverOrAddress, port) self.connection = serverOrAddress self.connectId = self.connection:connect_id() end - - -- Serialization is set in Client.setConfigSettings() - --if bitserLoaded then - -- self:setSerialization(bitser.dumps, bitser.loads) - --end - --self:setSerialization(zstandard.compress, zstandard.decompress) end --- Check for network events and handle them. diff --git a/mods/noita-mp/files/scripts/net/SockServer.lua b/mods/noita-mp/files/scripts/net/SockServer.lua index c0db24e35..42d9a7ac8 100644 --- a/mods/noita-mp/files/scripts/net/SockServer.lua +++ b/mods/noita-mp/files/scripts/net/SockServer.lua @@ -95,8 +95,8 @@ end ---@param fileUtils FileUtils ---@param logger Logger function SockServer:start(ip, port, fileUtils, logger) - ip = ip or self.address - port = port or self.port + ip = ip or self.address or error("ip is nil", 2) + port = port or self.port or error("port is nil", 2) self.address = ip self.port = port @@ -116,14 +116,9 @@ function SockServer:start(ip, port, fileUtils, logger) self.address = string.sub(self:getSocketAddress(), 1, string.find(self:getSocketAddress(), ":", 1, true) - 1) --ip self.port = port - self:setBandwidthLimit(0, 0) + self:setBandwidthLimit(self.inBandwidth, self.outBandwidth) self.logger:info(logger.channels.network, ("Started server on %s:%s"):format(self.address, self.port)) - -- Serialization is set in Server.setConfigSettings() - --if bitserLoaded then - -- self:setSerialization(bitser.dumps, bitser.loads) - --end - --self:setSerialization(zstandard.compress, zstandard.decompress) return true end @@ -699,43 +694,31 @@ end ---@param outBandwidth number|nil ---@return SockServer function SockServer.new(address, port, maxPeers, maxChannels, inBandwidth, outBandwidth) - address = address or "localhost" - port = port or 14017 - maxPeers = maxPeers or 64 - maxChannels = maxChannels or 1 - inBandwidth = inBandwidth or 0 - outBandwidth = outBandwidth or 0 - ---@class SockServer - local sockServer = setmetatable({ - address = address, - port = port, - host = nil, - - messageTimeout = 0, - maxChannels = maxChannels, - maxPeers = maxPeers, - sendMode = "reliable", - defaultSendMode = "reliable", - sendChannel = 0, - defaultSendChannel = 0, - - peers = {}, - clients = {}, - - listener = require("SockListener"):newListener(),--newListener(), - --logger = newLogger("SERVER"), - - serialize = nil, - deserialize = nil, - - packetsSent = 0, - packetsReceived = 0, - - }, SockServer) - - sockServer.zipTable = require("NetworkUtils").zipTable - + local sockServer = setmetatable({}, SockServer) + sockServer.address = address or "localhost" + sockServer.clients = {} + sockServer.defaultSendChannel = 0 + sockServer.defaultSendMode = "reliable" + sockServer.deserialize = nil + sockServer.host = nil + sockServer.inBandwidth = inBandwidth or 0 + sockServer.listener = require("SockListener"):newListener() --newListener(), + sockServer.maxChannels = maxChannels or 1 + sockServer.maxPeers = maxPeers or 4 + sockServer.messageTimeout = 0 + sockServer.outBandwidth = outBandwidth or 0 + sockServer.packetsReceived = 0 + sockServer.packetsSent = 0 + sockServer.peers = {} + sockServer.port = port or 14017 + sockServer.sendChannel = 0 + sockServer.sendMode = "reliable" + sockServer.serialize = nil + + sockServer.zipTable = require("NetworkUtils").zipTable + + print("SockServer loaded and instantiated!") return sockServer end diff --git a/mods/noita-mp/files/scripts/util/EntityUtils.lua b/mods/noita-mp/files/scripts/util/EntityUtils.lua index 46c0f2c7e..0096684fa 100644 --- a/mods/noita-mp/files/scripts/util/EntityUtils.lua +++ b/mods/noita-mp/files/scripts/util/EntityUtils.lua @@ -65,7 +65,7 @@ function EntityUtils:onEntityRemoved(entityId, nuid) -- end self.entityCache:delete(entityId) -- NetworkCacheUtils.delete ? - GlobalsUtils.setDeadNuid(nuid) + self.globalsUtils:setDeadNuid(nuid) self.customProfiler:stop("OnEntityRemoved", cpc) end @@ -86,13 +86,14 @@ function EntityUtils:isEntityPolymorphed(entityId) end local prevEntityIndex = 1 ----Adds or updates all network components to the entity. ----Sends the entity data to all other peers. +--- Adds or updates all network components to the entity. +--- Sends the entity data to all other peers. ---@param startFrameTime number Time at the very beginning of the frame. -function EntityUtils:syncEntities(startFrameTime) +---@param server Server|nil Either server or client must not be nil! +---@param client Client|nil Either server or client must not be nil! +function EntityUtils:syncEntities(startFrameTime, server, client) local cpc = self.customProfiler:start("EntityUtils.syncEntities") local start = GameGetRealWorldTimeSinceStarted() * 1000 - local who = whoAmI() local localPlayerId = self.minaUtils:getLocalMinaEntityId() local playerX, playerY = EntityGetTransform(localPlayerId) local radius = ModSettingGetNextValue("noita-mp.radius_include_entities") @@ -106,7 +107,7 @@ function EntityUtils:syncEntities(startFrameTime) -- table.sort(entityIds) - if who == self.client.iAm then + if client then --table.insertIfNotExist(playerEntityIds, localPlayerId) --table.insertAllButNotDuplicates(playerEntityIds, -- get_player_inventory_contents("inventory_quick")) -- wands and items @@ -125,8 +126,8 @@ function EntityUtils:syncEntities(startFrameTime) -- end --end if not self.networkVscUtils:hasNuidSet(localPlayerId) then - self.client:sendNeedNuid(self.minaUtils:getLocalMinaName(), self.minaUtils:getLocalMinaGuid(), - EntityGetRootEntity(localPlayerId)) + self.client:sendNeedNuid( + self.minaUtils:getLocalMinaName(), self.minaUtils:getLocalMinaGuid(), EntityGetRootEntity(localPlayerId)) end end @@ -148,11 +149,11 @@ function EntityUtils:syncEntities(startFrameTime) --[[ Check if this entityId belongs to client ]] -- - if who == self.client.iAm then + if client then if not table.contains(playerEntityIds, entityId) then if EntityGetIsAlive(entityId) and entityId ~= self.minaUtils:getLocalMinaEntityId() and - not self.minaUtils:isRemoteMinae(entityId) and + not self.minaUtils:isRemoteMinae(client, server, entityId) and not self.networkVscUtils:hasNetworkLuaComponents(entityId) then local distance = -1 @@ -166,7 +167,7 @@ function EntityUtils:syncEntities(startFrameTime) --for i = 1, #Client.otherClients do -- TODO NOT YET IMPLEMENTED -- local remoteX, remoteY = EntityGetTransform(Client.otherClients[i].) --end - local nuidRemote, entityIdRemote = GlobalsUtils.getNuidEntityPair(self.client.serverInfo.nuid) -- TODO rework with getRemoteMina + local nuidRemote, entityIdRemote = self.globalsUtils:getNuidEntityPair(self.client.serverInfo.nuid) -- TODO rework with getRemoteMina if EntityGetIsAlive(entityIdRemote) then local remoteX, remoteY = EntityGetTransform(entityIdRemote) distance = get_distance2(localX, localY, remoteX, remoteY) @@ -195,7 +196,7 @@ function EntityUtils:syncEntities(startFrameTime) --else if self.exclude.byFilename[filename] or --table.contains(EntityUtils.exclude.byFilename, filename) or - findByFilename(filename, self.exclude.byFilename) + findByFilename(self, filename, self.exclude.byFilename) then exclude = true break -- work around for continue: repeat until true with break @@ -213,7 +214,7 @@ function EntityUtils:syncEntities(startFrameTime) if self.include.byFilename[filename] or --table.contains(EntityUtils.include.byFilename, filename) or - findByFilename(filename, self.include.byFilename) + findByFilename(self, filename, self.include.byFilename) then exclude = false else @@ -241,11 +242,11 @@ function EntityUtils:syncEntities(startFrameTime) local ownerName = self.minaUtils:getLocalMinaName() local ownerGuid = self.minaUtils:getLocalMinaGuid() - if who == self.server.iAm and not hasNuid then + if server and not hasNuid then nuid = self.nuidUtils:getNextNuid() -- Server.sendNewNuid this will be executed below - elseif who == self.client.iAm and not hasNuid then - self.client:sendNeedNuid(ownerName, ownerGuid, entityId) + elseif client and not hasNuid then + client:sendNeedNuid(ownerName, ownerGuid, entityId) else error("Unable to get whoAmI()!", 2) return @@ -259,7 +260,7 @@ function EntityUtils:syncEntities(startFrameTime) local compOwnerName, compOwnerGuid, compNuid, filenameUnused, health, rotation, velocity, x, y = self.noitaComponentUtils:getEntityData(entityId) if cachedValue == nil or cachedValue.fullySerialised == false then - if who == self.iAm then + if server then if not hasNuid then nuid = compNuid if not nuid then @@ -294,7 +295,7 @@ function EntityUtils:syncEntities(startFrameTime) --[[ Check if moved or anything else changed, but only on each tick ]] -- - if self.networkUtils:isTick() and not self.minaUtils:isRemoteMinae(entityId) then + if self.networkUtils:isTick() and not self.minaUtils:isRemoteMinae(client, server, entityId) then local changed = false --local compOwnerName, compOwnerGuid, compNuid, filenameUnused, health, rotation, velocity, x, y = NoitaComponentUtils.getEntityData(entityId) @@ -335,6 +336,9 @@ function EntityUtils:syncEntities(startFrameTime) end end --end + if not self.entityCacheUtils then --TODO: temporary dirty whacky hacky fix + self.entityCacheUtils = self.server.entityCacheUtils + end self.entityCacheUtils:set(entityId, nuid, compOwnerGuid, compOwnerName, filename, x, y, rotation, velocity.x, velocity.y, health.current, health.max, true, nil) if changed then @@ -422,14 +426,27 @@ function EntityUtils:spawnEntity(owner, nuid, x, y, rotation, velocity, filename end --- Synchronises dead nuids between server and client. -function EntityUtils:syncDeadNuids() - local cpc = self.customProfiler:start("EntityUtils.syncDeadNuids") +---@param server Server|nil Either server or client must not be nil! +---@param client Client|nil Either server or client must not be nil! +function EntityUtils:syncDeadNuids(server, client) + local cpc = self.customProfiler:start("EntityUtils.syncDeadNuids") + local deadNuids = self.nuidUtils:getEntityIdsByKillIndicator() if #deadNuids > 0 then - local clientOrServer = self.networkUtils:getClientOrServer() - clientOrServer.sendDeadNuids(deadNuids) + if server then + server:sendDeadNuids(deadNuids) + else + if client then + client:sendDeadNuids(deadNuids) + else + error( + ("EntityUtils.syncDeadNuids: server %s and client %s must not be nil!"):format(self.utils:pformat(server), + self.utils:pformat(client)), + 2) + end + end + self.customProfiler:stop("EntityUtils.syncDeadNuids", cpc) end - self.customProfiler:stop("EntityUtils.syncDeadNuids", cpc) end ---Destroys the entity by the given nuid. @@ -565,7 +582,7 @@ end ---@param utils Utils|nil ---@return EntityUtils function EntityUtils:new(entityUtilsObject, client, customProfiler, enitityCacheUtils, entityCache, globalsUtils, logger, minaUtils, networkUtils, - networkVscUtils, noitaComponentUtils, nuidUtils, server, utils) + networkVscUtils, noitaComponentUtils, nuidUtils, server, utils, np) ---@class EntityUtils entityUtilsObject = setmetatable(entityUtilsObject or self, EntityUtils) @@ -664,6 +681,11 @@ function EntityUtils:new(entityUtilsObject, client, customProfiler, enitityCache require("Utils", "mods/noita-mp/files/scripts/util") end + if not entityUtilsObject.noitaPatcherUtils then + entityUtilsObject.noitaPatcherUtils = require("NoitaPatcherUtils") + :new(nil, nil, server.customProfiler, np) + end + entityUtilsObject.customProfiler:stop("EntityUtils:new", cpc) return entityUtilsObject end diff --git a/mods/noita-mp/files/scripts/util/Logger.lua b/mods/noita-mp/files/scripts/util/Logger.lua index c3d4de481..cafb310fa 100644 --- a/mods/noita-mp/files/scripts/util/Logger.lua +++ b/mods/noita-mp/files/scripts/util/Logger.lua @@ -1,5 +1,5 @@ ---@class Logger ----Class for being able to log per level. +--- Class for being able to log per level. local Logger = { --[[ Attributes ]] diff --git a/mods/noita-mp/files/scripts/util/NetworkUtils.lua b/mods/noita-mp/files/scripts/util/NetworkUtils.lua index f5f2796c7..6877354a3 100644 --- a/mods/noita-mp/files/scripts/util/NetworkUtils.lua +++ b/mods/noita-mp/files/scripts/util/NetworkUtils.lua @@ -1,132 +1,145 @@ -NetworkUtils = {} - -NetworkUtils.networkMessageIdCounter = 0 - -NetworkUtils.events = { - connect = { - name = "connect", - schema = { "code" }, - isCacheable = false - }, - --- connect2 is used to let the other clients know, who was connected - connect2 = { - name = "connect2", - schema = { "networkMessageId", "name", "guid" }, - resendIdentifiers = { "name", "guid" }, - isCacheable = true - }, - disconnect = { - name = "disconnect", - schema = { "code" }, - isCacheable = false - }, - --- disconnect2 is used to let the other clients know, who was disconnected - disconnect2 = { - name = "disconnect2", - schema = { "networkMessageId", "name", "guid" }, - resendIdentifiers = { "name", "guid" }, - isCacheable = true - }, - --- acknowledgement is used to let the sender know if the message was acknowledged - acknowledgement = { - name = "acknowledgement", - schema = { "networkMessageId", "event", "status", "ackedAt" }, - ack = "ack", - sent = "sent", - resendIdentifiers = { "networkMessageId", "status" }, - isCacheable = false - }, - --- seed is used to send the servers seed - seed = { - name = "seed", - schema = { "networkMessageId", "seed" }, - resendIdentifiers = { "seed" }, - isCacheable = true - }, - --- minaInformation is used to send local mina name, guid, etc pp to all peers. - minaInformation = { - name = "minaInformation", - schema = { "networkMessageId", "version", "name", "guid", "entityId", "nuid", "transform", "health" }, - resendIdentifiers = { "version", "name", "guid" }, - isCacheable = true - }, - --- newGuid is used to send a new GUID to a client, which GUID isn't unique all peers - newGuid = { - name = "newGuid", - schema = { "networkMessageId", "oldGuid", "newGuid" }, - resendIdentifiers = { "oldGuid", "newGuid" }, - isCacheable = true - }, - --- newNuid is used to let clients spawn entities by the servers permission - newNuid = { - --- constant name for the event - name = "newNuid", - - --- network schema to decode the message - schema = { "networkMessageId", "ownerName", "ownerGuid", "localEntityId", "x", "y", - "initialSerializedEntityString", "currentSerializedEntityString", "nuid" }, - - --- resendIdentifiers defines the schema for detection of resend mechanism. - --- Based on the values the network message will be send again. - resendIdentifiers = { "ownerName", "ownerGuid", "localEntityId", "initialSerializedEntityString", "nuid" }, - - --- identifier whether to cache this message, if it wasn't acknowledged - isCacheable = true, - }, - --- needNuid is used to ask for a nuid from client to servers - needNuid = { - name = "needNuid", - schema = { "networkMessageId", "ownerName", "ownerGuid", "localEntityId", "x", "y", - "initialSerializedEntityString", "currentSerializedEntityString" }, - resendIdentifiers = { "ownerGuid", "localEntityId", "initialSerializedEntityString" }, - isCacheable = true - }, - --- lostNuid is used to ask for the entity to spawn, when a client has a nuid stored, but no entityId (not sure - --- atm, why this is happening, but this is due to reduce out of sync stuff) - lostNuid = { - name = "lostNuid", - schema = { "networkMessageId", "nuid" }, - resendIdentifiers = { "nuid" }, - isCacheable = true - }, - --- deadNuids is used to let clients know, which entities were killed or destroyed - deadNuids = { - name = "deadNuids", - schema = { "networkMessageId", "deadNuids" }, - resendIdentifiers = { "deadNuids" }, - isCacheable = true - }, - --- needModList is used to let clients sync enabled mods with the server - needModList = { - name = "needModList", - schema = { "networkMessageId", "workshop", "external" }, - resendIdentifiers = { "workshop", "external" }, - isCacheable = true - }, - --- needModContent is used to sync mod content from server to client - needModContent = { - name = "needModContent", - schema = { "networkMessageId", "get", "items" }, - resendIdentifiers = { "get", "items" }, - isCacheable = true +---@class NetworkUtils +--- Class for network related stuff. +local NetworkUtils = { + --[[ Attributes ]] + + networkMessageIdCounter = 0, + --- Network events with name, schema, resendIdentifiers and isCacheable + events = { + connect = { + name = "connect", + schema = { "code" }, + isCacheable = false + }, + --- connect2 is used to let the other clients know, who was connected + connect2 = { + name = "connect2", + schema = { "networkMessageId", "name", "guid" }, + resendIdentifiers = { "name", "guid" }, + isCacheable = true + }, + disconnect = { + name = "disconnect", + schema = { "code" }, + isCacheable = false + }, + --- disconnect2 is used to let the other clients know, who was disconnected + disconnect2 = { + name = "disconnect2", + schema = { "networkMessageId", "name", "guid" }, + resendIdentifiers = { "name", "guid" }, + isCacheable = true + }, + --- acknowledgement is used to let the sender know if the message was acknowledged + acknowledgement = { + name = "acknowledgement", + schema = { "networkMessageId", "event", "status", "ackedAt" }, + ack = "ack", + sent = "sent", + resendIdentifiers = { "networkMessageId", "status" }, + isCacheable = false + }, + --- seed is used to send the servers seed + seed = { + name = "seed", + schema = { "networkMessageId", "seed" }, + resendIdentifiers = { "seed" }, + isCacheable = true + }, + --- minaInformation is used to send local mina name, guid, etc pp to all peers. + minaInformation = { + name = "minaInformation", + schema = { "networkMessageId", "version", "name", "guid", "entityId", "nuid", "transform", "health" }, + resendIdentifiers = { "version", "name", "guid" }, + isCacheable = true + }, + --- newGuid is used to send a new GUID to a client, which GUID isn't unique all peers + newGuid = { + name = "newGuid", + schema = { "networkMessageId", "oldGuid", "newGuid" }, + resendIdentifiers = { "oldGuid", "newGuid" }, + isCacheable = true + }, + --- newNuid is used to let clients spawn entities by the servers permission + newNuid = { + --- constant name for the event + name = "newNuid", + + --- network schema to decode the message + schema = { "networkMessageId", "ownerName", "ownerGuid", "localEntityId", "x", "y", + "initialSerializedEntityString", "currentSerializedEntityString", "nuid" }, + + --- resendIdentifiers defines the schema for detection of resend mechanism. + --- Based on the values the network message will be send again. + resendIdentifiers = { "ownerName", "ownerGuid", "localEntityId", "initialSerializedEntityString", "nuid" }, + + --- identifier whether to cache this message, if it wasn't acknowledged + isCacheable = true, + }, + --- needNuid is used to ask for a nuid from client to servers + needNuid = { + name = "needNuid", + schema = { "networkMessageId", "ownerName", "ownerGuid", "localEntityId", "x", "y", + "initialSerializedEntityString", "currentSerializedEntityString" }, + resendIdentifiers = { "ownerGuid", "localEntityId", "initialSerializedEntityString" }, + isCacheable = true + }, + --- lostNuid is used to ask for the entity to spawn, when a client has a nuid stored, but no entityId (not sure + --- atm, why this is happening, but this is due to reduce out of sync stuff) + lostNuid = { + name = "lostNuid", + schema = { "networkMessageId", "nuid" }, + resendIdentifiers = { "nuid" }, + isCacheable = true + }, + --- deadNuids is used to let clients know, which entities were killed or destroyed + deadNuids = { + name = "deadNuids", + schema = { "networkMessageId", "deadNuids" }, + resendIdentifiers = { "deadNuids" }, + isCacheable = true + }, + --- needModList is used to let clients sync enabled mods with the server + needModList = { + name = "needModList", + schema = { "networkMessageId", "workshop", "external" }, + resendIdentifiers = { "workshop", "external" }, + isCacheable = true + }, + --- needModContent is used to sync mod content from server to client + needModContent = { + name = "needModContent", + schema = { "networkMessageId", "get", "items" }, + resendIdentifiers = { "get", "items" }, + isCacheable = true + } } } -local function getIndexFromSchema(data, schema, resendIdentifier) - local cpc = CustomProfiler.start("NetworkUtils.getIndexFromSchema") + +--- Finding index from schema based on resendIdentifier +---@deprecated Will be removed in future, beacuse unused +---@private +---@param self NetworkUtils +---@param data table +---@param schema table +---@param resendIdentifier string +---@return integer index +local getIndexFromSchema = function(self, data, schema, resendIdentifier) + local cpc = self.customProfiler:start("NetworkUtils.getIndexFromSchema") for i, value in ipairs(data) do if schema[i] == resendIdentifier then - CustomProfiler.stop("NetworkUtils.getIndexFromSchema", cpc) + self.customProfiler:stop("NetworkUtils.getIndexFromSchema", cpc) return i end end - CustomProfiler.stop("NetworkUtils.getIndexFromSchema", cpc) - return nil + self.customProfiler:stop("NetworkUtils.getIndexFromSchema", cpc) + return -1 end ----Default enhanced serialization function +--- Default enhanced serialization function ---@param value any ---@return unknown function NetworkUtils:serialize(value) @@ -179,53 +192,37 @@ function NetworkUtils:deserialize(value) return deserialized end ---- Sometimes you don't care if it's the client or server, but you need one of them to send the messages. ---- @return Client|Server Client or Server 'object' ---- @public -function NetworkUtils.getClientOrServer() - local cpc = CustomProfiler.start("NetworkUtils.getClientOrServer") - local who = _G.whoAmI() - if who == Client.iAm then - CustomProfiler.stop("NetworkUtils.getClientOrServer", cpc) - return Client - elseif who == Server.iAm then - CustomProfiler.stop("NetworkUtils.getClientOrServer", cpc) - return Server - else - CustomProfiler.stop("NetworkUtils.getClientOrServer", cpc) - error(("Unable to identify whether I am Client or Server.. whoAmI() == %s"):format(who), 2) - end -end - -function NetworkUtils.getNextNetworkMessageId() - local cpc = CustomProfiler.start("NetworkUtils.getNextNetworkMessageId") - NetworkUtils.networkMessageIdCounter = NetworkUtils.networkMessageIdCounter + 1 - CustomProfiler.stop("NetworkUtils.getNextNetworkMessageId", cpc) - return NetworkUtils.networkMessageIdCounter +--- Returns the network message id counter and increases it by one +---@return integer networkMessageIdCounter +function NetworkUtils:getNextNetworkMessageId() + local cpc = self.customProfiler:start("NetworkUtils.getNextNetworkMessageId") + self.networkMessageIdCounter = self.networkMessageIdCounter + 1 + self.customProfiler:stop("NetworkUtils.getNextNetworkMessageId", cpc) + return self.networkMessageIdCounter end --- Checks if the event within its data was already sent ---- @param peer table If Server, then it's the peer, if Client, then it's the 'self' object ---- @param event string ---- @param data table ---- @return boolean -function NetworkUtils.alreadySent(peer, event, data) - local cpc = CustomProfiler.start("NetworkUtils.alreadySent") +---@param peer table If Server, then it's the peer, if Client, then it's the 'self' object +---@param event string +---@param data table +---@return boolean +function NetworkUtils:alreadySent(peer, event, data) + local cpc = self.customProfiler:start("NetworkUtils.alreadySent") if not peer then error("'peer' must not be nil! When Server, then peer or Server.clients[i]. When Client, then self.", 2) end if not event then error("'event' must not be nil!", 2) end - if not NetworkUtils.events[event] then + if not self.events[event] then error(("'event' \"%s\" is unknown. Did you add this event in NetworkUtils.events?"):format(event), 2) end if not data then error("'data' must not be nil!", 2) end - if Utils.IsEmpty(peer.clientCacheId) then - Logger.info(Logger.channels.testing, ("peer.guid = '%s'"):format(peer.guid)) - peer.clientCacheId = GuidUtils.toNumber(peer.guid) --error("peer.clientCacheId must not be nil!", 2) + if self.utils:IsEmpty(peer.clientCacheId) then + self.logger:info(self.logger.channels.testing, ("peer.guid = '%s'"):format(peer.guid)) + peer.clientCacheId = self.guidUtils:toNumber(peer.guid) --error("peer.clientCacheId must not be nil!", 2) end local clientCacheId = peer.clientCacheId @@ -235,33 +232,33 @@ function NetworkUtils.alreadySent(peer, event, data) end -- [[ if the event isn't store in the cache, it wasn't already send ]] -- - if not NetworkUtils.events[event].isCacheable then - Logger.trace(Logger.channels.testing, ("event %s is not cacheable!"):format(event)) - CustomProfiler.stop("NetworkUtils.alreadySent", cpc) + if not self.events[event].isCacheable then + self.logger:trace(self.logger.channels.testing, ("event %s is not cacheable!"):format(event)) + self.customProfiler:stop("NetworkUtils.alreadySent", cpc) return false end --[[ if the network message is already stored ]] -- - local message = NetworkCacheUtils.get(peer.guid, networkMessageId, event) + local message = self.networkCacheUtils:get(peer.guid, networkMessageId, event) if message ~= nil then print(("Got message %s by cache with clientCacheId '%s', event '%s' and networkMessageId '%s'") :format(message, clientCacheId, event, networkMessageId)) - if message.status == NetworkUtils.events.acknowledgement.ack then + if message.status == self.events.acknowledgement.ack then print(("Got message %s by cache with clientCacheId '%s', event '%s' and networkMessageId '%s'") :format(message, clientCacheId, event, networkMessageId)) - CustomProfiler.stop("NetworkUtils.alreadySent", cpc) + self.customProfiler:stop("NetworkUtils.alreadySent", cpc) return true end else - Logger.trace(Logger.channels.testing, + self.logger:trace(self.logger.channels.testing, ("NetworkUtils.alreadySent: NetworkCacheUtils.get(peer.guid %s, networkMessageId %s, event %s) returned message = nil") :format(peer.guid, networkMessageId, event)) end --- Compare if the current data matches the cached checksum - local matchingData = NetworkCacheUtils.getByChecksum(peer.guid, event, data) + local matchingData = self.networkCacheUtils:getByChecksum(peer.guid, event, data) if matchingData ~= nil then - if matchingData.status == NetworkUtils.events.acknowledgement.sent then + if matchingData.status == self.events.acknowledgement.sent then local now = GameGetRealWorldTimeSinceStarted() local diff = now - matchingData.sendAt if diff >= peer:getRoundTripTime() then @@ -271,35 +268,40 @@ function NetworkUtils.alreadySent(peer, event, data) end return true else - Logger.trace(Logger.channels.testing, + self.logger:trace(self.logger.channels.testing, ("NetworkUtils.alreadySent: NetworkCacheUtils.getByChecksum(peer.guid %s, event %s, data %s) returned matchingData = nil") :format(peer.guid, event, data)) end - CustomProfiler.stop("NetworkUtils.alreadySent", cpc) + self.customProfiler:stop("NetworkUtils.alreadySent", cpc) return false end local prevTimeInMs = 0 -function NetworkUtils.isTick() - -- TODO: ADD LuaDoc - local cpc = CustomProfiler.start("NetworkUtils.isTick") +--- Checks if the current time is a tick. +---@return boolean +function NetworkUtils:isTick() + local cpc = self.customProfiler:start("NetworkUtils.isTick") local nowTimeInMs = GameGetRealWorldTimeSinceStarted() * 1000 local elapsedTime = nowTimeInMs - prevTimeInMs - local cpc2 = CustomProfiler.start("ModSettingGet") + local cpc2 = self.customProfiler:start("ModSettingGet") local oneTickInMs = 1000 / tonumber(ModSettingGet("noita-mp.tick_rate")) - CustomProfiler.stop("ModSettingGet", cpc2) + self.customProfiler:stop("ModSettingGet", cpc2) if elapsedTime >= oneTickInMs then prevTimeInMs = nowTimeInMs - CustomProfiler.stop("NetworkUtils.isTick", cpc) + self.customProfiler:stop("NetworkUtils.isTick", cpc) return true end - CustomProfiler.stop("NetworkUtils.isTick", cpc) + self.customProfiler:stop("NetworkUtils.isTick", cpc) return false end --- links variables to keys based on their order --- note that it only works for boolean and number values, not strings --- credits to sock.lua +--- links variables to keys based on their order +--- note that it only works for boolean and number values, not strings. +--- Credits to sock.lua +---@param items any +---@param keys any +---@param event any +---@return table function NetworkUtils:zipTable(items, keys, event) local data = {} @@ -318,11 +320,46 @@ function NetworkUtils:zipTable(items, keys, event) return data end --- Because of stack overflow errors when loading lua files, --- I decided to put Utils 'classes' into globals -_G.NetworkUtils = NetworkUtils +--- Constructor for NetworkUtils class. +---@param customProfiler CustomProfiler +---@param guidUtils GuidUtils +---@param logger Logger +---@param networkCacheUtils NetworkCacheUtils +---@param utils Utils|nil +---@return NetworkUtils +function NetworkUtils:new(customProfiler, guidUtils, logger, networkCacheUtils, utils) + ---@class NetworkUtils + local networkUtils = setmetatable(self, NetworkUtils) + + local cpc = customProfiler:start("NetworkUtils:new") + + --[[ Imports ]] + --Initialize all imports to avoid recursive imports + + if not networkUtils.customProfiler then + networkUtils.customProfiler = customProfiler or error("NetworkUtils:new requires a CustomProfiler object", 2) + end + if not networkUtils.guidUtils then + networkUtils.guidUtils = guidUtils or error("NetworkUtils:new requires a GuidUtils object", 2) + end + if not networkUtils.logger then + networkUtils.logger = logger or error("NetworkUtils:new requires a Logger object", 2) + end + if not networkUtils.messagePack then + networkUtils.messagePack = require("MessagePack") + end + if not networkUtils.networkCacheUtils then + networkUtils.networkCacheUtils = networkCacheUtils or error("NetworkUtils:new requires a NetworkCacheUtils object", 2) + end + if not networkUtils.utils then + networkUtils.utils = utils or require("Utils"):new(nil) + end + if not networkUtils.zstandard then + networkUtils.zstandard = require("zstd") + end + + networkUtils.customProfiler:stop("NetworkUtils:new", cpc) + return networkUtils +end --- But still return for Noita Components, --- which does not have access to _G, --- because of own context/vm return NetworkUtils diff --git a/mods/noita-mp/files/scripts/util/NoitaComponentUtils.lua b/mods/noita-mp/files/scripts/util/NoitaComponentUtils.lua index de746ddf5..307c71930 100644 --- a/mods/noita-mp/files/scripts/util/NoitaComponentUtils.lua +++ b/mods/noita-mp/files/scripts/util/NoitaComponentUtils.lua @@ -158,7 +158,7 @@ function NoitaComponentUtils:setInitialSerializedEntityString(entityId, initialS end function NoitaComponentUtils:hasInitialSerializedEntityString(entityId) - local status, result = pcall(self.getInitialSerializedEntityString, entityId) + local status, result = pcall(self.getInitialSerializedEntityString, self, entityId) return status end diff --git a/mods/noita-mp/files/scripts/util/NoitaPatcherUtils.lua b/mods/noita-mp/files/scripts/util/NoitaPatcherUtils.lua index 6fe2dd92d..cb5173f61 100644 --- a/mods/noita-mp/files/scripts/util/NoitaPatcherUtils.lua +++ b/mods/noita-mp/files/scripts/util/NoitaPatcherUtils.lua @@ -16,9 +16,15 @@ function OnProjectileFiredPost() end ---@return string encodedBase64 function NoitaPatcherUtils:serializeEntity(entityId) local cpc = self.customProfiler:start("NoitaPatcherUtils.serializeEntity") - local encodedBase64 = self.base64.encode(self.np.SerializeEntity(entityId), nil, false) + local binaryString = self.np.SerializeEntity(entityId) + --local encodedBase64 = self.base64.encode(binaryString, nil, false) + if not self.base64_fast then + self.base64_fast = require("base64_fast") + end + local encodedBase64 = self.base64_fast.encode(binaryString) self.customProfiler:stop("NoitaPatcherUtils.serializeEntity", cpc) return encodedBase64 + --return binaryString end ---Deserialize an entity from a base64 string and create it at the given position. @@ -29,7 +35,12 @@ end ---@return number function NoitaPatcherUtils:deserializeEntity(entityId, serializedEntityString, x, y) local cpc = self.customProfiler:start("NoitaPatcherUtils.deserializeEntity") - local decoded = self.base64.decode(serializedEntityString, nil, false) + --local decoded = self.base64.decode(serializedEntityString, nil, false) + --local entityId = self.np.DeserializeEntity(entityId, decoded, x, y) + if not self.base64_fast then + self.base64_fast = require("base64_fast") + end + local decoded = self.base64_fast.decode(serializedEntityString) local entityId = self.np.DeserializeEntity(entityId, decoded, x, y) self.customProfiler:stop("NoitaPatcherUtils.deserializeEntity", cpc) return entityId diff --git a/mods/noita-mp/files/scripts/util/NuidUtils.lua b/mods/noita-mp/files/scripts/util/NuidUtils.lua index 3b0144c9a..f12f532df 100644 --- a/mods/noita-mp/files/scripts/util/NuidUtils.lua +++ b/mods/noita-mp/files/scripts/util/NuidUtils.lua @@ -20,22 +20,24 @@ function NuidUtils:getNextNuid() if not self.worldStateXmlFileExists then self.worldStateXmlFileExists = self.fileUtils:Exists(worldStateXmlAbsPath) local f = io.open(worldStateXmlAbsPath, "r") - local xml = self.nxml.parse(f:read("*a")) - f:close() - - for v in xml:first_of("WorldStateComponent"):first_of("lua_globals"):each_of("E") do - if string.contains(v.attr.key, "nuid") then - local nuid = self.globalUtils:parseXmlValueToNuidAndEntityId(v.attr.key, v.attr.value) - if nuid ~= nil then - nuid = tonumber(nuid) or -1 - if nuid > self.counter then - self.counter = nuid + if f then + local xml = self.nxml.parse(f:read("*a")) + f:close() + + for v in xml:first_of("WorldStateComponent"):first_of("lua_globals"):each_of("E") do + if string.contains(v.attr.key, "nuid") then + local nuid = self.globalUtils:parseXmlValueToNuidAndEntityId(v.attr.key, v.attr.value) + if nuid ~= nil then + nuid = tonumber(nuid) or -1 + if nuid > self.counter then + self.counter = nuid + end end end end + self.logger:info(self.logger.channels.nuid, + ("Loaded nuids after loading a savegame. Latest nuid from world_state.xml aka Globals = %s."):format(self.counter)) end - self.logger:info(self.logger.channels.nuid, - ("Loaded nuids after loading a savegame. Latest nuid from world_state.xml aka Globals = %s."):format(self.counter)) end self.xmlParsed = true end diff --git a/mods/noita-mp/files/scripts/util/Utils.lua b/mods/noita-mp/files/scripts/util/Utils.lua index 06e1a23c5..54c7de315 100644 --- a/mods/noita-mp/files/scripts/util/Utils.lua +++ b/mods/noita-mp/files/scripts/util/Utils.lua @@ -55,7 +55,7 @@ function Utils:isEmpty(var) end ---Formats anything pretty. ----@param var number|string|table variable to print +---@param var number|string|table|nil variable to print ---@return number|string|table formatted variable function Utils:pformat(var) return self.pprint.pformat(var, self.pprint.defaults) diff --git a/mods/noita-mp/init.lua b/mods/noita-mp/init.lua index e9c707d48..bf8741f5c 100644 --- a/mods/noita-mp/init.lua +++ b/mods/noita-mp/init.lua @@ -90,8 +90,15 @@ local function OnEntityLoaded() --for guessEntityId = entityUtils:previousHighestAliveEntityId, entityUtils:previousHighestAliveEntityId + 1024, 1 do for guessEntityId = entityUtils.previousHighestAliveEntityId, EntitiesGetMaxID(), 1 do local entityId = guessEntityId - GamePrint(("OnEntityLoaded(entityId = %s)"):format(entityId)) - while EntityGetIsAlive(entityId) and entityId > entityUtils.previousHighestAliveEntityId do + local filename = nil + if EntityGetIsAlive(entityId) then + filename = EntityGetFilename(entityId) + end + while EntityGetIsAlive(entityId) + and entityId > entityUtils.previousHighestAliveEntityId + and not table.contains(entityUtils.exclude.byFilename, filename) + do + GamePrint(("OnEntityLoaded(entityId = %s)"):format(entityId)) if entityId > entityUtils.previousHighestAliveEntityId then entityUtils.previousHighestAliveEntityId = entityId end @@ -231,8 +238,8 @@ function OnPausePreUpdate() local startFrameTime = GameGetRealWorldTimeSinceStarted() local cpc = customProfiler:start("init.OnPausePreUpdate") OnEntityLoaded() - server:update(startFrameTime) - client:update(startFrameTime) + server:preUpdate(startFrameTime) + client:preUpdate(startFrameTime) customProfiler:stop("init.OnPausePreUpdate", cpc) end @@ -289,8 +296,8 @@ function OnWorldPreUpdate() end end - server:update(startFrameTime) - client:update(startFrameTime) + server:preUpdate(startFrameTime) + client:preUpdate(startFrameTime) local cpc1 = customProfiler:start("init.OnWorldPreUpdate.collectgarbage.count") if collectgarbage("count") >= 102412345.0 then diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/base64_fast.lua b/mods/noita-mp/lua_modules/share/lua/5.1/base64_fast.lua new file mode 100644 index 000000000..16b9a3718 --- /dev/null +++ b/mods/noita-mp/lua_modules/share/lua/5.1/base64_fast.lua @@ -0,0 +1,125 @@ + +--Base64 encoding & decoding in Lua +--Written by Cosmin Apreutesei. Public Domain. + +--Original code from: +--https://github.com/kengonakajima/luvit-base64/issues/1 +--http://lua-users.org/wiki/BaseSixtyFour + +--if not ... then require'base64_test'; return end + +local base64 = {} + +local ffi = require'ffi' +local bit = require'bit' +local shl = bit.lshift +local shr = bit.rshift +local bor = bit.bor +local band = bit.band +local u8a = ffi.typeof'uint8_t[?]' +local u8p = ffi.typeof'uint8_t*' +local u16a = ffi.typeof'uint16_t[?]' +local u16p = ffi.typeof'uint16_t*' + +local b64chars_s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +local b64chars = u8a(#b64chars_s + 1) +ffi.copy(b64chars, b64chars_s) + +local b64digits = u16a(4096) +for j=0,63,1 do + for k=0,63,1 do + b64digits[j*64+k] = bor(shl(b64chars[k], 8), b64chars[j]) + end +end + +local EQ = string.byte'=' + +function base64.encode(s, sn, dbuf, dn) + + local sn = sn or #s + local min_dn = math.ceil(sn / 3) * 4 + local dn = dn or min_dn + assert(dn >= min_dn, 'buffer too small') + local dp = dbuf and ffi.cast(u8p, dbuf) or u8a(dn) + local sp = ffi.cast(u8p, s) + local dpw = ffi.cast(u16p, dp) + local si = 0 + local di = 0 + + while sn > 2 do + local n = sp[si] + n = shl(n, 8) + n = bor(n, sp[si+1]) + n = shl(n, 8) + n = bor(n, sp[si+2]) + local c1 = shr(n, 12) + local c2 = band(n, 0x00000fff) + dpw[di ] = b64digits[c1] + dpw[di+1] = b64digits[c2] + sn = sn - 3 + di = di + 2 + si = si + 3 + end + + di = di * 2 + + if sn > 0 then + local c1 = shr(band(sp[si], 0xfc), 2) + local c2 = shl(band(sp[si], 0x03), 4) + if sn > 1 then + si = si + 1 + c2 = bor(c2, shr(band(sp[si], 0xf0), 4)) + end + dp[di ] = b64chars[c1] + dp[di+1] = b64chars[c2] + di = di + 2 + if sn == 2 then + local c3 = shl(band(sp[si], 0xf), 2) + si = si + 1 + c3 = bor(c3, shr(band(sp[si], 0xc0), 6)) + dp[di] = b64chars[c3] + di = di + 1 + end + if sn == 1 then + dp[di] = EQ + di = di + 1 + end + dp[di] = EQ + end + + if dbuf then + return dp, min_dn + else + return ffi.string(dp, dn) + end +end + +--TODO: rewrite with ffi. +function base64.decode(s, sn, dbuf, dn) + s = s:gsub('[^'..b64chars_s..'=]', '') + return (s:gsub('.', function(x) + if x == '=' then return '' end + local r, f = '', b64chars_s:find(x, 1, true)-1 + for i=6,1,-1 do + r = r .. (f%2^i - f%2^(i-1) > 0 and '1' or '0') + end + return r + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if #x ~= 8 then return '' end + local c = 0 + for i = 1,8 do + c = c + (x:sub(i, i) == '1' and 2^(8-i) or 0) + end + return string.char(c) + end)) +end + +function base64.urlencode(s) + return base64.encode(s):gsub('/', '_'):gsub('+', '-'):gsub('=*$', '') +end + +function base64.urldecode(s) + return base64.decode(s):gsub('_', '/'):gsub('-', '+'):gsub('=*$', '') +end + +return base64 diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/jit/p.lua b/mods/noita-mp/lua_modules/share/lua/5.1/jit/p.lua index ebb0bd521..d7919682f 100644 --- a/mods/noita-mp/lua_modules/share/lua/5.1/jit/p.lua +++ b/mods/noita-mp/lua_modules/share/lua/5.1/jit/p.lua @@ -52,7 +52,7 @@ local zone -- Load jit.zone module on demand. -- Output file handle. local out - +local outputFilePath ------------------------------------------------------------------------------ local prof_ud @@ -133,10 +133,22 @@ local function prof_top(count1, count2, samples, indent) local pct = floor(v * 100 / samples + 0.5) if pct < prof_min then break end if not prof_raw then + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format("%s%2d%% %s\n", indent, pct, k)) elseif prof_raw == "r" then + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format("%s%5d %s\n", indent, v, k)) else + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format("%s %d\n", k, v)) end if count2 then @@ -180,27 +192,49 @@ local function prof_annotate(count1, samples) for _, file in ipairs(files) do local f0 = file:byte() if f0 == 40 or f0 == 91 then - --out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file)) + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end + out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file)) break end local fp, err = io.open(file) if not fp then + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format("====== ERROR: %s: %s\n", file, err)) break end + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format("\n====== %s ======\n", file)) local fl = files[file] local n, show = 1, false if ann ~= 0 then for i = 1, ann do if fl[i] then - show = true; out:write("@@ 1 @@\n"); break + show = true + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end + out:write("@@ 1 @@\n") + break end end end for line in fp:lines() do if line:byte() == 27 then - --out:write("[Cannot annotate bytecode file]\n") + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end + out:write("[Cannot annotate bytecode file]\n") break end local v = fl[n] @@ -216,13 +250,25 @@ local function prof_annotate(count1, samples) end elseif v2 then show = n + ann + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format("@@ %d @@\n", n)) end if not show then goto next end end if v then + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format(fmtv, v, line)) else + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format(fmtn, line)) end ::next:: @@ -240,9 +286,19 @@ local function prof_finish() profile.stop() local samples = prof_samples if samples == 0 then - if prof_raw ~= true then out:write("[No samples collected]\n") end + if prof_raw ~= true then + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end + out:write("[No samples collected]\n") + end return end + if io.type(out) ~= "file" then + print("out is closed, reopening..") + out = assert(io.open(outputFilePath, "a+")) + end out:write(format("Frame: %d\n", GameGetFrameNum())) if prof_ann then prof_annotate(prof_count1, samples) @@ -322,6 +378,7 @@ end local function start(mode, outfile) if not outfile then outfile = os.getenv("LUAJIT_PROFILEFILE") end if outfile then + outputFilePath = outfile out = outfile == "-" and stdout or assert(io.open(outfile, "a+")) else out = stdout diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/sock.lua b/mods/noita-mp/lua_modules/share/lua/5.1/sock.lua deleted file mode 100644 index 277d63f12..000000000 --- a/mods/noita-mp/lua_modules/share/lua/5.1/sock.lua +++ /dev/null @@ -1,932 +0,0 @@ ---- A Lua networking library for LÖVE games. --- * [Source code](https://github.com/camchenry/sock.lua) --- * [Examples](https://github.com/camchenry/sock.lua/tree/master/examples) --- @module sock - ----@class sock -local sock = { - _VERSION = 'sock.lua v0.3.0', - _DESCRIPTION = 'A Lua networking library for LÖVE games', - _URL = 'https://github.com/camchenry/sock.lua', - _LICENSE = [[ - MIT License - - Copyright (c) 2016 Cameron McHenry - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - ]] -} - -local enet = require("enet") - -print("lua-enet version = master branch 21.10.2015") -print(("enet version = %s"):format(enet.linked_version())) -- 1.3.17 - --- Current folder trick --- http://kiki.to/blog/2014/04/12/rule-5-beware-of-multiple-files/ -local currentFolder = (...):gsub('%.[^%.]+$', '') - -local bitserLoaded = false - -if bitser then - bitserLoaded = true -end - --- Try to load some common serialization libraries --- This is for convenience, you may still specify your own serializer -if not bitserLoaded then - bitserLoaded, bitser = pcall(require, "bitser") -end - --- Try to load relatively -if not bitserLoaded then - bitserLoaded, bitser = pcall(require, currentFolder .. ".bitser") -end - - - ---- All of the possible connection statuses for a client connection. --- @see Client:getState -sock.CONNECTION_STATES = { - "disconnected", -- Disconnected from the server. - "connecting", -- In the process of connecting to the server. - "acknowledging_connect", -- - "connection_pending", -- - "connection_succeeded", -- - "connected", -- Successfully connected to the server. - "disconnect_later", -- Disconnecting, but only after sending all queued packets. - "disconnecting", -- In the process of disconnecting from the server. - "acknowledging_disconnect", -- - "zombie", -- - "unknown", -- -} - ---- States that represent the client connecting to a server. -sock.CONNECTING_STATES = { - "connecting", -- In the process of connecting to the server. - "acknowledging_connect", -- - "connection_pending", -- - "connection_succeeded", -- -} - ---- States that represent the client disconnecting from a server. -sock.DISCONNECTING_STATES = { - "disconnect_later", -- Disconnecting, but only after sending all queued packets. - "disconnecting", -- In the process of disconnecting from the server. - "acknowledging_disconnect", -- -} - ---- Valid modes for sending messages. -sock.SEND_MODES = { - "reliable", -- Message is guaranteed to arrive, and arrive in the order in which it is sent. - "unsequenced", -- Message has no guarantee on the order that it arrives. - "unreliable", -- Message is not guaranteed to arrive. -} - -local function isValidSendMode(mode) - for _, validMode in ipairs(sock.SEND_MODES) do - if mode == validMode then - return true - end - end - return false -end - -local Logger = {} -local Logger_mt = { __index = Logger } - -local function newLogger(source) - local logger = setmetatable({ - source = source, - messages = {}, - - -- Makes print info more concise, but should still log the full line - shortenLines = true, - -- Print all incoming event data - printEventData = true, - printErrors = true, - printWarnings = true, - }, Logger_mt) - - return logger -end - -function Logger:log(event, data) - local time = os.date("%X") -- something like 24:59:59 - local shortLine = ("%s [%s] %s"):format(time, event, data) - local fullLine = ("%s [%s %s] %s"):format(time, self.source, event, - utils:pformat(data)) --local fullLine = ("[%s][%s][%s] %s"):format(self.source, time, event, data) - - -- The printed message may or may not be the full message - local line = fullLine - if self.shortenLines then - line = shortLine - end - - if self.printEventData then - --print(line) - _G.Logger.info(_G.Logger.channels.network, fullLine) - elseif self.printWarnings and event == "warning" then - --print(line) - _G.Logger.warn(_G.Logger.channels.network, fullLine) - elseif self.printErrors and event == "error" then - --print(line) - error(fullLine, 2) - end - - -- The logged message is always the full message - table.insert(self.messages, fullLine) - - -- if event ~= nil or data ~= nil then - -- print(fullLine) - -- end - -- TODO: Dump to a log file -end - - - ---- Manages all clients and receives network events. ----@class SockServer -local Server = {} -local Server_mt = { __index = Server } -sock.getServerClass = function() - return Server -end - ---- NoitaMp Moved this part from newServer to Server:start ----@param ip string ----@param port number ----@param fileUtils FileUtils ----@param logger Logger -function Server:start(ip, port, fileUtils, logger) - ip = ip or self.address - port = port or self.port - - self.address = ip - self.port = port - - -- ip, max peers, max channels, in bandwidth, out bandwidth - -- number of channels for the client and server must match - self.host = enet.host_create(ip .. ":" .. port, self.maxPeers, self.maxChannels) - - if not self.host then - --error("Failed to create the host. Is there another server running on :" .. self.port .. "?") - self:log("", { "Failed to create the host. Is there another server running on :" .. self.port .. "?" }) - local pid = fileUtils:GetPidOfRunningEnetHostByPort() - fileUtils:KillProcess(pid) - return false - end - - self.address = string.sub(self:getSocketAddress(), 1, string.find(self:getSocketAddress(), ":", 1, true) - 1) --ip - self.port = port - - self:setBandwidthLimit(0, 0) - - logger:info(logger.channels.network, ("Started server on %s:%s"):format(self.address, self.port)) - -- Serialization is set in Server.setConfigSettings() - --if bitserLoaded then - -- self:setSerialization(bitser.dumps, bitser.loads) - --end - --self:setSerialization(zstandard.compress, zstandard.decompress) - return true -end - ---- Check for network events and handle them. -function Server:update() - local event = self.host:service(self.messageTimeout) - - while event do - if event.type == "connect" then - local eventClient = require("Client"):new(nil, event.peer, nil, nil, self) - eventClient:establishClient(event.peer) - eventClient:setSerialization(self.serialize, self.deserialize) - eventClient.clientCacheId = self.guidUtils.toNumber(eventClient.guid) - table.insert(self.peers, event.peer) - table.insert(self.clients, eventClient) - self:_activateTriggers("connect", event.data, eventClient) - self:log(event.type, tostring(event.peer) .. " connected") - elseif event.type == "receive" then - local eventName, data = self:__unpack(event.data) - local eventClient = self:getClient(event.peer) - - self:_activateTriggers(eventName, data, eventClient) - self:log(eventName, data) - elseif event.type == "disconnect" then - -- remove from the active peer list - for i, peer in pairs(self.peers) do - if peer == event.peer then - table.remove(self.peers, i) - end - end - local eventClient = self:getClient(event.peer) - for i, client in pairs(self.clients) do - if client == eventClient then - table.remove(self.clients, i) - end - end - self:_activateTriggers("disconnect", event.data, eventClient) - self:log(event.type, tostring(event.peer) .. " disconnected") - else - self:log(event.type, - ("event = %s, type = %s, data = %s, peer = %s"):format(event, event.type, event.data, event.peer)) - end - - event = self.host:service(self.messageTimeout) - end -end - --- Creates the unserialized message that will be used in callbacks --- In: serialized message (string) --- Out: event (string), data (mixed) -function Server:__unpack(data) - if not self.deserialize then - self:log("error", "Can't deserialize message: deserialize was not set") - error("Can't deserialize message: deserialize was not set") - end - - local message = self.deserialize(data) - local eventName, data = message[1], message[2] - return eventName, data -end - ---- Added for NoitaMP ----@param data any ----@return any -function Server:unpack(data) - if not self.deserialize then - self:log("error", "Can't deserialize message: deserialize was not set") - error("Can't deserialize message: deserialize was not set") - end - - return self.deserialize(data) -end - --- Creates the serialized message that will be sent over the network --- In: event (string), data (mixed) --- Out: serialized message (string) -function Server:__pack(event, data) - local message = { event, data } - local serializedMessage - - if not self.serialize then - self:log("error", "Can't serialize message: serialize was not set") - error("Can't serialize message: serialize was not set") - end - - -- 'Data' = binary data class in Love - if type(data) == "userdata" and data.type and data:typeOf("Data") then - message[2] = data:getString() - serializedMessage = self.serialize(message) - else - serializedMessage = self.serialize(message) - end - - return serializedMessage -end - ---- Added for NoitaMP ----@param data any ----@return any -function Server:pack(data) - if not self.serialize then - self:log("error", "Can't serialize message: serialize was not set") - error("Can't serialize message: serialize was not set") - end - - return self.serialize(data) -end - ---- Send a message to all clients, except one. --- Useful for when the client does something locally, but other clients --- need to be updated at the same time. This way avoids duplicating objects by --- never sending its own event to itself in the first place. --- @tparam Client client The client to not receive the message. --- @tparam string event The event to trigger with this message. --- @param data The data to send. -function Server:sendToAllBut(client, event, data) - error("Server:sendToAllBut is deprecated, because cache wont work. Use Server:sendToPeer instead.", 2) - local serializedMessage = self:__pack(event, data) - - for _, p in pairs(self.peers) do - if p ~= client.connection then - self.packetsSent = self.packetsSent + 1 - p:send(serializedMessage, self.sendChannel, self.sendMode) - end - end - - self:resetSendSettings() -end - ---- Send a message to all clients. --- @tparam string event The event to trigger with this message. --- @param data The data to send. ---@usage ---server:sendToAll("gameStarting", true) -function Server:sendToAll(event, data) - error("Server:sendToAll is deprecated, because cache wont work. Use Server:sendToPeer instead.", 2) - local serializedMessage = self:__pack(event, data) - - self.packetsSent = self.packetsSent + #self.peers - - self.host:broadcast(serializedMessage, self.sendChannel, self.sendMode) - - self:resetSendSettings() - - return true -end - -function Server:sendToAll2(event, data) - error("Server:sendToAll2 is deprecated, because cache wont work. Use Server:sendToPeer instead.", 2) - local cpc = CustomProfiler.start("Server:sendToAll2") - local serializedMessage = self:__pack(event, data) - - for _, p in pairs(self.peers) do - self.packetsSent = self.packetsSent + 1 - p:send(serializedMessage, self.sendChannel, self.sendMode) - end - - self:resetSendSettings() - CustomProfiler.stop("Server:sendToAll2", cpc) -end - ---- Send a message to a single peer. Useful to send data to a newly connected player ---- without sending to everyone who already received it. ---- @param peer Client|Server|enet_peer The enet peer to receive the message. ---- @param event string The event to trigger with this message. ---- @param data table to send to the peer. ---- Usage: server:sendToPeer(peer, "initialGameInfo", {...}) -function Server:sendToPeer(peer, event, data) - local cpc = CustomProfiler.start("Server:sendToPeer") - local networkMessageId = data[1] or data.networkMessageId - if Utils.IsEmpty(networkMessageId) then - error("networkMessageId is empty!", 3) - end - self.packetsSent = self.packetsSent + 1 - peer:send(event, data) - self:resetSendSettings() - CustomProfiler.stop("Server:sendToPeer", cpc) - return networkMessageId -end - ---- Add a callback to an event. --- @tparam string event The event that will trigger the callback. --- @tparam function callback The callback to be triggered. --- @treturn function The callback that was passed in. ---@usage ---server:on("connect", function(data, client) --- print("Client connected!") ---end) -function Server:on(event, callback) - return self.listener:addCallback(event, callback) -end - -function Server:_activateTriggers(event, data, client) - local result = self.listener:trigger(event, data, client) - - self.packetsReceived = self.packetsReceived + 1 - - if not result then - self:log("warning", "Server tried to activate trigger: '" .. tostring(event) .. "' but it does not exist.") - end -end - ---- Remove a specific callback for an event. --- @tparam function callback The callback to remove. --- @treturn boolean Whether or not the callback was removed. ---@usage ---local callback = server:on("chatMessage", function(message) --- print(message) ---end) ---server:removeCallback(callback) -function Server:removeCallback(callback) - return self.listener:removeCallback(callback) -end - ---- Log an event. --- Alias for Server.logger:log. --- @tparam string event The type of event that happened. --- @tparam string data The message to log. ---@usage ---if somethingBadHappened then --- server:log("error", "Something bad happened!") ---end -function Server:log(event, data) - return self.logger:log(event, data) -end - ---- Reset all send options to their default values. -function Server:resetSendSettings() - self.sendMode = self.defaultSendMode - self.sendChannel = self.defaultSendChannel -end - ---- Enables an adaptive order-2 PPM range coder for the transmitted data of all peers. Both the client and server must both either have compression enabled or disabled. --- --- Note: lua-enet does not currently expose a way to disable the compression after it has been enabled. -function Server:enableCompression() - return self.host:compress_with_range_coder() -end - ---- Destroys the server and frees the port it is bound to. -function Server:destroy() - self.host:destroy() -end - ---- Set the send mode for the next outgoing message. --- The mode will be reset after the next message is sent. The initial default --- is "reliable". --- @tparam string mode A valid send mode. --- @see SEND_MODES --- @usage ---server:setSendMode("unreliable") ---server:sendToAll("playerState", {...}) -function Server:setSendMode(mode) - if not isValidSendMode(mode) then - self:log("warning", "Tried to use invalid send mode: '" .. mode .. "'. Defaulting to reliable.") - mode = "reliable" - end - - self.sendMode = mode -end - ---- Set the default send mode for all future outgoing messages. --- The initial default is "reliable". --- @tparam string mode A valid send mode. --- @see SEND_MODES -function Server:setDefaultSendMode(mode) - if not isValidSendMode(mode) then - self:log("error", "Tried to set default send mode to invalid mode: '" .. mode .. "'") - error("Tried to set default send mode to invalid mode: '" .. mode .. "'") - end - - self.defaultSendMode = mode -end - ---- Set the send channel for the next outgoing message. --- The channel will be reset after the next message. Channels are zero-indexed --- and cannot exceed the maximum number of channels allocated. The initial --- default is 0. --- @tparam number channel Channel to send data on. --- @usage ---server:setSendChannel(2) -- the third channel ---server:sendToAll("importantEvent", "The message") -function Server:setSendChannel(channel) - if channel > (self.maxChannels - 1) then - self:log("warning", - "Tried to use invalid channel: " .. channel .. " (max is " .. self.maxChannels - 1 .. "). Defaulting to 0.") - channel = 0 - end - - self.sendChannel = channel -end - ---- Set the default send channel for all future outgoing messages. --- The initial default is 0. --- @tparam number channel Channel to send data on. -function Server:setDefaultSendChannel(channel) - self.defaultSendChannel = channel -end - ---- Set the data schema for an event. --- --- Schemas allow you to set a specific format that the data will be sent. If the --- client and server both know the format ahead of time, then the table keys --- do not have to be sent across the network, which saves bandwidth. --- @tparam string event The event to set the data schema for. --- @tparam {string,...} schema The data schema. --- @usage --- server = sock.newServer(...) --- client = sock.newClient(...) --- --- -- Without schemas --- client:send("update", { --- x = 4, --- y = 100, --- vx = -4.5, --- vy = 23.1, --- rotation = 1.4365, --- }) --- server:on("update", function(data, client) --- -- data = { --- -- x = 4, --- -- y = 100, --- -- vx = -4.5, --- -- vy = 23.1, --- -- rotation = 1.4365, --- -- } --- end) --- --- --- -- With schemas --- server:setSchema("update", { --- "x", --- "y", --- "vx", --- "vy", --- "rotation", --- }) --- -- client no longer has to send the keys, saving bandwidth --- client:send("update", { --- 4, --- 100, --- -4.5, --- 23.1, --- 1.4365, --- }) --- server:on("update", function(data, client) --- -- data = { --- -- x = 4, --- -- y = 100, --- -- vx = -4.5, --- -- vy = 23.1, --- -- rotation = 1.4365, --- -- } --- end) -function Server:setSchema(event, schema) - return self.listener:setSchema(event, schema) -end - ---- Set the incoming and outgoing bandwidth limits. --- @tparam number incoming The maximum incoming bandwidth in bytes. --- @tparam number outgoing The maximum outgoing bandwidth in bytes. -function Server:setBandwidthLimit(incoming, outgoing) - return self.host:bandwidth_limit(incoming, outgoing) -end - ---- Set the maximum number of channels. --- @tparam number limit The maximum number of channels allowed. If it is 0, --- then the maximum number of channels available on the system will be used. -function Server:setMaxChannels(limit) - self.host:channel_limit(limit) -end - ---- Set the timeout to wait for packets. --- @tparam number timeout Time to wait for incoming packets in milliseconds. The --- initial default is 0. -function Server:setMessageTimeout(timeout) - self.messageTimeout = timeout -end - ---- Set the serialization functions for sending and receiving data. --- Both the client and server must share the same serialization method. --- @tparam function serialize The serialization function to use. --- @tparam function deserialize The deserialization function to use. --- @usage ---bitser = require "bitser" -- or any library you like ---server = sock.newServer("localhost", 1337) ---server:setSerialization(bitser.dumps, bitser.loads) -function Server:setSerialization(serialize, deserialize) - assert(type(serialize) == "function", "Serialize must be a function, got: '" .. type(serialize) .. "'") - assert(type(deserialize) == "function", "Deserialize must be a function, got: '" .. type(deserialize) .. "'") - self.serialize = serialize - self.deserialize = deserialize -end - ---- Gets the Client object associated with an enet peer. --- @tparam peer peer An enet peer. --- @treturn Client Object associated with the peer. -function Server:getClient(peer) - for _, client in pairs(self.clients) do - if peer == client.connection then - return client - end - end -end - ---- Gets the Client object that has the given connection id. --- @tparam number connectId The unique client connection id. --- @treturn Client -function Server:getClientByConnectId(connectId) - for _, client in pairs(self.clients) do - if connectId == client.connectId then - return client - end - end -end - ---- Get the Client object that has the given peer index. --- @treturn Client -function Server:getClientByIndex(index) - for _, client in pairs(self.clients) do - if index == client:getIndex() then - return client - end - end -end - ---- Get the enet_peer that has the given index. --- @treturn enet_peer The underlying enet peer object. -function Server:getPeerByIndex(index) - return self.host:get_peer(index) -end - ---- Get the total sent data since the server was created. --- @treturn number The total sent data in bytes. -function Server:getTotalSentData() - return self.host:total_sent_data() -end - ---- Get the total received data since the server was created. --- @treturn number The total received data in bytes. -function Server:getTotalReceivedData() - return self.host:total_received_data() -end - ---- Get the total number of packets (messages) sent since the server was created. --- Everytime a message is sent or received, the corresponding figure is incremented. --- Therefore, this is not necessarily an accurate indicator of how many packets were actually --- exchanged over the network. --- @treturn number The total number of sent packets. -function Server:getTotalSentPackets() - return self.packetsSent -end - ---- Get the total number of packets (messages) received since the server was created. --- @treturn number The total number of received packets. --- @see Server:getTotalSentPackets -function Server:getTotalReceivedPackets() - return self.packetsReceived -end - ---- Get the last time when network events were serviced. --- @treturn number Timestamp of the last time events were serviced. -function Server:getLastServiceTime() - return self.host:service_time() -end - ---- Get the number of allocated slots for peers. --- @treturn number Number of allocated slots. -function Server:getMaxPeers() - return self.maxPeers -end - ---- Get the number of allocated channels. --- Channels are zero-indexed, e.g. 16 channels allocated means that the --- maximum channel that can be used is 15. --- @treturn number Number of allocated channels. -function Server:getMaxChannels() - return self.maxChannels -end - ---- Get the timeout for packets. --- @treturn number Time to wait for incoming packets in milliseconds. --- initial default is 0. -function Server:getMessageTimeout() - return self.messageTimeout -end - ---- Get the socket address of the host. --- @treturn string A description of the socket address, in the format --- "A.B.C.D:port" where A.B.C.D is the IP address of the used socket. -function Server:getSocketAddress() - return self.host:get_socket_address() -end - ---- Get the current send mode. --- @treturn string --- @see SEND_MODES -function Server:getSendMode() - return self.sendMode -end - ---- Get the default send mode. --- @treturn string --- @see SEND_MODES -function Server:getDefaultSendMode() - return self.defaultSendMode -end - ---- Get the IP address or hostname that the server was created with. --- @treturn string -function Server:getAddress() - return self.address -end - ---- Get the port that the server is hosted on. --- @treturn number -function Server:getPort() - return self.port -end - ---- Get the table of Clients actively connected to the server. --- @return {Client,...} -function Server:getClients() - return self.clients -end - ---- Get the number of Clients that are currently connected to the server. --- @treturn number The number of active clients. -function Server:getClientCount() - return #self.clients -end - -function Server:getRoundTripTime() - return 0 -end - ---- Connects to servers. ----@class SockClient -local Client = {} -local Client_mt = { __index = Client } -sock.getClientClass = function() - return Client -end - - - ---- Creates a new Server object. --- @tparam ?string address Hostname or IP address to bind to. (default: "localhost") --- @tparam ?number port Port to listen to for data. (default: 1337) --- @tparam ?number maxPeers Maximum peers that can connect to the server. (default: 64) --- @tparam ?number maxChannels Maximum channels available to send and receive data. (default: 1) --- @tparam ?number inBandwidth Maximum incoming bandwidth (default: 0) --- @tparam ?number outBandwidth Maximum outgoing bandwidth (default: 0) --- @return A new Server object. --- @see Server --- @within sock --- @usage ---local sock = require "sock" --- --- -- Local server hosted on localhost:1337 (by default) ---server = sock.newServer() --- --- -- Local server only, on port 1234 ---server = sock.newServer("localhost", 1234) --- --- -- Server hosted on static IP 123.45.67.89, on port 1337 ---server = sock.newServer("123.45.67.89", 1337) --- --- -- Server hosted on any IP, on port 1337 ---server = sock.newServer("*", 1337) --- --- -- Limit peers to 10, channels to 2 ---server = sock.newServer("*", 1337, 10, 2) --- --- -- Limit incoming/outgoing bandwidth to 1kB/s (1000 bytes/s) ---server = sock.newServer("*", 1337, 10, 2, 1000, 1000) ----Creates a new Server instance. ----@param address string|nil The IP address or hostname to bind to. Default: "localhost" Available: "localhost", "*", "xxx.xxx.xxx.xxx" or nil ----@param port number|nil The port to listen to for data. Default: 14017 ----@param maxPeers number|nil ----@param maxChannels number|nil ----@param inBandwidth number|nil ----@param outBandwidth number|nil ----@return SockServer -sock.newServer = function(address, port, maxPeers, maxChannels, inBandwidth, outBandwidth) - address = address or "localhost" - port = port or 14017 - maxPeers = maxPeers or 64 - maxChannels = maxChannels or 1 - inBandwidth = inBandwidth or 0 - outBandwidth = outBandwidth or 0 - - local server = setmetatable({ - address = address, - port = port, - host = nil, - - messageTimeout = 0, - maxChannels = maxChannels, - maxPeers = maxPeers, - sendMode = "reliable", - defaultSendMode = "reliable", - sendChannel = 0, - defaultSendChannel = 0, - - peers = {}, - clients = {}, - - listener = newListener(), - logger = newLogger("SERVER"), - - serialize = nil, - deserialize = nil, - - packetsSent = 0, - packetsReceived = 0, - - }, Server_mt) - - server.zipTable = function(items, keys, event) - return zipTable(items, keys, event) - end - - -- -- ip, max peers, max channels, in bandwidth, out bandwidth - -- -- number of channels for the client and server must match - -- server.host = enet.host_create(server.address .. ":" .. server.port, server.maxPeers, server.maxChannels) - - - -- if not server.host then - -- error("Failed to create the host. Is there another server running on :" .. server.port .. "?") - -- end - - -- server:setBandwidthLimit(inBandwidth, outBandwidth) - - -- if bitserLoaded then - -- server:setSerialization(bitser.dumps, bitser.loads) - -- end - - return server -end - ---- Creates a new Client instance. --- @tparam ?string/peer serverOrAddress Usually the IP address or hostname to connect to. It can also be an enet peer. (default: "localhost") --- @tparam ?number port Port number of the server to connect to. (default: 1337) --- @tparam ?number maxChannels Maximum channels available to send and receive data. (default: 1) --- @return A new Client object. --- @see Client --- @within sock --- @usage ---local sock = require "sock" --- --- -- Client that will connect to localhost:1337 (by default) ---client = sock.newClient() --- --- -- Client that will connect to localhost:1234 ---client = sock.newClient("localhost", 1234) --- --- -- Client that will connect to 123.45.67.89:1234, using two channels --- -- NOTE: Server must also allocate two channels! ---client = sock.newClient("123.45.67.89", 1234, 2) ----Creates a new Client instance. ----@param address string|nil The IP address or hostname to connect to. Default: "localhost" Available: "localhost", "*", "xxx.xxx.xxx.xxx" or nil ----@param port number|nil The port to listen to for data. Default: 14017 ----@param maxChannels number|nil ----@return SockClient client -sock.newClient = function(address, port, maxChannels) - address = address or "localhost" - port = port or 14017 - maxChannels = maxChannels or 1 - - local client = setmetatable({ - address = nil, - port = nil, - host = nil, - - connection = nil, -- aka peer - connectId = nil, - - messageTimeout = 0, - maxChannels = maxChannels, - sendMode = "reliable", - defaultSendMode = "reliable", - sendChannel = 0, - defaultSendChannel = 0, - - listener = newListener(), - logger = newLogger("CLIENT"), - - serialize = nil, - deserialize = nil, - - packetsReceived = 0, - packetsSent = 0, - - }, Client_mt) - - client.zipTable = function(items, keys, event) - return zipTable(items, keys, event) - end - - -- -- Two different forms for client creation: - -- -- 1. Pass in (address, port) and connect to that. - -- -- 2. Pass in (enet peer) and set that as the existing connection. - -- -- The first would be the common usage for regular client code, while the - -- -- latter is mostly used for creating clients in the server-side code. - - -- -- First form: (address, port) - -- if port ~= nil and type(port) == "number" and serverOrAddress ~= nil and type(serverOrAddress) == "string" then - -- client.address = serverOrAddress - -- client.port = port - -- client.host = enet.host_create() - - -- -- Second form: (enet peer) - -- elseif type(serverOrAddress) == "userdata" then - -- client.connection = serverOrAddress - -- client.connectId = client.connection:connect_id() - -- end - - -- if bitserLoaded then - -- client:setSerialization(bitser.dumps, bitser.loads) - -- end - - return client -end - -return sock diff --git a/mods/noita-mp/lua_modules/share/lua/5.1/zstd.lua b/mods/noita-mp/lua_modules/share/lua/5.1/zstd.lua index beaf772bb..bb8836f48 100644 --- a/mods/noita-mp/lua_modules/share/lua/5.1/zstd.lua +++ b/mods/noita-mp/lua_modules/share/lua/5.1/zstd.lua @@ -87,6 +87,9 @@ unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize); local arr_utint8_t = ffi_typeof "uint8_t[?]" local ptr_zstd_inbuffer_t = ffi_typeof "ZSTD_inBuffer[1]" local ptr_zstd_outbuffer_t = ffi_typeof "ZSTD_outBuffer[1]" +local _M = { + version = '0.2.3' +} local noitaMpSettings = require("NoitaMpSettings") :new(nil, nil, {}, nil, nil, nil, nil, nil, nil) @@ -96,12 +99,8 @@ local fileUtils = require("FileUtils") local zstd = ffi_load(fileUtils:GetAbsolutePathOfNoitaRootDirectory() .. "/mods/noita-mp/lua_modules/lib/lua/5.1/libzstd") local file = assert(io.popen("zstd -vV", "r")) local zstdVersion = file:read("*a") -print("zstd version = " .. zstdVersion) - - -local _M = { - version = '0.2.3' -} +file:close() +print("zstd version = " .. _M.version .. " -> " .. zstdVersion) local function init_cstream (cstream, cLevel)