Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#38 Add synchronisation for client and server mods #85

Merged
merged 25 commits into from
Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
acf6cef
Added roadmap sketchup with draw.io
Ismoh Oct 23, 2022
bc7b6a0
Updated NoitaMP-Roadmap
Ismoh Oct 23, 2022
e4e5df2
Updated NoitaMP-Roadmap
Ismoh Oct 23, 2022
826935f
Updated NoitaMP-Roadmap
Ismoh Oct 23, 2022
a33e2da
NoitaMP-Roadmap updated
Ismoh Oct 23, 2022
7a3ffff
NoitaMP-Roadmap finished
Ismoh Oct 23, 2022
3233b17
NoitaMP-Roadmap.svg added
Ismoh Oct 23, 2022
eab91de
NoitaMP-Roadmap.drawio.svg updated
Ismoh Oct 23, 2022
b802594
NoitaMP-Roadmap.drawio.svg updated
Ismoh Oct 23, 2022
eaf2beb
Added roadmap to README.md
Ismoh Oct 23, 2022
0207880
NoitaMP-Roadmap finished
Ismoh Oct 23, 2022
c17fdd6
NoitaMP-Roadmap updated
Ismoh Oct 24, 2022
5c79f2e
NoitaMP-Roadmap.drawio.svg finalised
Ismoh Oct 24, 2022
f01707c
Delete NoitaMP-Roadmap.svg
Ismoh Oct 24, 2022
e1353d8
Rename NoitaMP-Roadmap.drawio.svg to NoitaMP-Roadmap.svg
Ismoh Oct 24, 2022
69c5a35
implement server onNeedModList function and add needModList/Content e…
ofoxsmith Oct 25, 2022
e054131
Finish implementing server side mod sync functions and add onNeedModC…
ofoxsmith Oct 26, 2022
aa823b0
make modListCached a prop on Server class
ofoxsmith Oct 26, 2022
5f212a5
make onNeedModContent also return workshop id to avoid overwriting wo…
ofoxsmith Oct 27, 2022
4a46909
Finish implementing mod synchronisation
ofoxsmith Oct 27, 2022
e91bdfe
make onNeedModList run on connection to server
ofoxsmith Oct 27, 2022
7940e84
move mod conflict gui into drawModConflictWarning function
ofoxsmith Oct 28, 2022
ec1423d
disable auto formatting
ofoxsmith Oct 28, 2022
21cf493
fix Client.lua formatting and add CustomProfiler to onNeedModList/Con…
ofoxsmith Oct 28, 2022
bd1d5da
Merge branch 'develop' into 38-each-player-should-have-same-mods
ofoxsmith Oct 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
311 changes: 311 additions & 0 deletions .github/NoitaMP-Roadmap

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions .github/NoitaMP-Roadmap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 6 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
],
"lua.targetVersion": "5.1",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"[xml]": {
"editor.defaultFormatter": "DotJoshJohnson.xml"
},
"Lua.runtime.version": "LuaJIT",
"Lua.workspace.checkThirdParty": false
"Lua.workspace.checkThirdParty": false,
"[lua]": {
"editor.defaultFormatter": "sumneko.lua"
}
}
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,11 @@ Also, special thanks to the people, who share their libraries, frameworks and ot

I had to build the network library by my own, because Noita provides its own lua51.dll. I struggled to build it, if you are interested in,
I've added all the necessary build files inside of .building/dll_building.7z and here you can see the [stackoverflow question](https://stackoverflow.com/questions/70048918/lua-5-1-package-loadlib-and-require-gcc-building-windows-dll) I've created.

## Roadmap

<div align="center">

![NoitaMP-Roadmap](.github/NoitaMP-Roadmap.svg)

</div>
90 changes: 89 additions & 1 deletion mods/noita-mp/files/scripts/Ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ Ui = {}
----------------------------------------
-- Global private variables:
----------------------------------------

local missingModGuiButton1Hovered = false
local missingModGuiButton2Hovered = false
local missingModGuiDismissed = false
----------------------------------------
-- Global private methods:
----------------------------------------
Expand Down Expand Up @@ -237,18 +239,104 @@ function Ui.new()
menuWidth, menuHeight = renderEzgui(x, y, "mods/noita-mp/files/data/ezgui/PlayerList.xml", self.ezguiMenuData)
end

local function drawModConflictWarning()
if _G.whoAmI() == Client.iAm and Client.missingMods ~= nil and not missingModGuiDismissed then
ofoxsmith marked this conversation as resolved.
Show resolved Hide resolved
gui = gui or GuiCreate()
GuiStartFrame(gui)
GuiIdPushString(gui, "missingModGUI")
local warningMsg = "Warning: Server has mods that you don't have enabled/installed. Missing mods are:"
local npID = 1
local button1ID = 2
local button2ID = 3
local y = 75
GuiZSet(gui, 100)
GuiText(gui, 75, y, warningMsg)
do
local msgW, msgH = GuiGetTextDimensions(gui, warningMsg)
y = y + msgH
end
for _, v in ipairs(Client.missingMods) do
GuiText(gui, 75, y, v)
local msgW, msgH = GuiGetTextDimensions(gui, v)
y = y + msgH
end
GuiZSetForNextWidget(gui, 110)
GuiImageNinePiece(gui, npID, 73, 73, 297, (y - 71) + 15)
if missingModGuiButton1Hovered then
GuiColorSetForNextWidget(gui, 1, 1, 0.69, 1)
else
GuiColorSetForNextWidget(gui, 1, 1, 1, 0.7)
end
GuiText(gui, 100, y + 5, "Install missing mods")
GuiImageNinePiece(gui, button1ID, 100, y + 5, 75, 11, 0)
local clicked, _, hovered = GuiGetPreviousWidgetInfo(gui)
if clicked then
missingModGuiDismissed = true
Client:send(NetworkUtils.events.needModContent.name,
{ NetworkUtils.getNextNetworkMessageId(), Client.missingMods })
end
if hovered then
missingModGuiButton1Hovered = true
else
missingModGuiButton1Hovered = false
end
if missingModGuiButton2Hovered then
GuiColorSetForNextWidget(gui, 1, 0, 0, 1)
else
GuiColorSetForNextWidget(gui, 1, 1, 1, 0.7)
end
GuiText(gui, 185, y + 5, "Continue without syncing (may cause issues)")
GuiImageNinePiece(gui, button2ID, 185, y + 5, 169, 11, 0)
clicked, _, hovered = GuiGetPreviousWidgetInfo(gui)
if clicked then
missingModGuiDismissed = true
end
if hovered then
missingModGuiButton2Hovered = true
else
missingModGuiButton2Hovered = false
end
end
if Client.syncedMods == true then
gui = gui or GuiCreate()
GuiStartFrame(gui)
GuiIdPushString(gui, "missingModGUI")
local warningMsg = "Mods synced. Enable the following mods and restart the game:"
local npID = 1
local y = 75
local w = nil
GuiZSet(gui, 100)
GuiText(gui, 75, y, warningMsg)
do
local msgW, msgH = GuiGetTextDimensions(gui, warningMsg)
w = msgW
y = y + msgH
end
for _, v in ipairs(Client.missingMods) do
GuiText(gui, 75, y, v)
local _, msgH = GuiGetTextDimensions(gui, v)
y = y + msgH
end
GuiZSetForNextWidget(gui, 110)
GuiImageNinePiece(gui, npID, 73, 73, w + 3, y - 71)
end

end

------------------------------------
-- Public methods:
------------------------------------
function self.update()
drawFolding()
drawMenu()
drawModConflictWarning()
end

------------------------------------
-- Apply some private methods
------------------------------------


return self
end

Expand Down
98 changes: 83 additions & 15 deletions mods/noita-mp/files/scripts/net/Client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ local messagePack = require("MessagePack")
--- Client
----------------------------------------------------------------------------------------------------
Client = {}

----------------------------------------
-- Global private variables:
----------------------------------------
Expand Down Expand Up @@ -60,6 +59,9 @@ function Client.new(sockClient)
self.serverInfo = {}
self.otherClients = {}
self.entityCache = {}
self.missingMods = nil
self.requiredMods = nil
self.syncedMods = false

------------------------------------
--- Private methods:
Expand Down Expand Up @@ -159,8 +161,8 @@ function Client.new(sockClient)

if not data.networkMessageId then
logger:error(logger.channels.network,
("Unable to get acknowledgement with networkMessageId = %s, data = %s, peer = %s")
:format(networkMessageId, util.pformat(data), util.pformat(peer)))
("Unable to get acknowledgement with networkMessageId = %s, data = %s, peer = %s")
:format(networkMessageId, util.pformat(data), util.pformat(peer)))
return
end

Expand All @@ -184,7 +186,7 @@ function Client.new(sockClient)
if util.IsEmpty(data) then
error(("onConnect data is empty: %s"):format(data), 3)
end

self:send(NetworkUtils.events.needModList.name, {NetworkUtils.getNextNetworkMessageId()})
-- sendAck(data.networkMessageId)
CustomProfiler.stop("Client.onConnect", cpc4)
end
Expand Down Expand Up @@ -306,15 +308,15 @@ function Client.new(sockClient)

if data.guid == self.guid then
logger:error(logger.channels.network,
"onPlayerInfo: Clients GUID isn't unique! Server will fix this!")
"onPlayerInfo: Clients GUID isn't unique! Server will fix this!")
--self.guid = Guid:getGuid({ self.guid })
--logger:info(logger.channels.network, "onPlayerInfo: New clients GUID: %s", self.guid)
self:disconnect()
end

if _G.NoitaMPVersion ~= tostring(data.version) then
error(("Version mismatch: NoitaMP version of Server: %s and your version: %s")
:format(data.version, _G.NoitaMPVersion), 3)
:format(data.version, _G.NoitaMPVersion), 3)
self:disconnect()
end

Expand Down Expand Up @@ -394,7 +396,7 @@ function Client.new(sockClient)

local serversSeed = tonumber(data.seed)
logger:info(logger.channels.network,
"Client received servers seed (%s) and stored it. Reloading map with that seed!", serversSeed)
"Client received servers seed (%s) and stored it. Reloading map with that seed!", serversSeed)

local localSeed = tonumber(StatsGetValue("world_seed"))
if localSeed ~= serversSeed then
Expand All @@ -408,7 +410,7 @@ function Client.new(sockClient)
local entityId = localPlayerInfo.entityId

self:send(NetworkUtils.events.playerInfo.name,
{ NetworkUtils.getNextNetworkMessageId(), name, guid, nuid, _G.NoitaMPVersion })
{ NetworkUtils.getNextNetworkMessageId(), name, guid, nuid, _G.NoitaMPVersion })

if not NetworkVscUtils.hasNetworkLuaComponents(entityId) then
NetworkVscUtils.addOrUpdateAllVscs(entityId, name, guid, nil)
Expand Down Expand Up @@ -494,14 +496,14 @@ function Client.new(sockClient)
end

EntityUtils.SpawnEntity(owner, newNuid, x, y, rotation, velocity, filename, localEntityId, health,
isPolymorphed)
isPolymorphed)
CustomProfiler.stop("Client.onNewNuid", cpc11)
end

local function onEntityData(data)
local cpc12 = CustomProfiler.start("Client.onEntityData")
logger:debug(logger.channels.network, ("Received entityData for nuid = %s! data = %s")
:format(data.nuid, util.pformat(data)))
:format(data.nuid, util.pformat(data)))

if util.IsEmpty(data.networkMessageId) then
error(("onNewNuid data.networkMessageId is empty: %s"):format(data.networkMessageId), 3)
Expand Down Expand Up @@ -554,7 +556,7 @@ function Client.new(sockClient)
NoitaComponentUtils.setEntityData(localEntityId, x, y, rotation, velocity, health)
else
logger:warn(logger.channels.network, ("Received entityData for self.nuid = %s! data = %s")
:format(data.nuid, util.pformat(data)))
:format(data.nuid, util.pformat(data)))
end
CustomProfiler.stop("Client.onEntityData", cpc12)
end
Expand All @@ -574,6 +576,67 @@ function Client.new(sockClient)
CustomProfiler.stop("Client.onDeadNuids", cpc13)
end

local function onNeedModList(data)
local cpc = CustomProfiler.start("Client.onNeedModList")
local activeMods = ModGetActiveModIDs()
ofoxsmith marked this conversation as resolved.
Show resolved Hide resolved
local function contains(elem)
for _, value in pairs(activeMods) do
if value == elem then
return true
end
end
return false
end

self.requiredMods = data.workshop
local conflicts = {}
for _, v in ipairs(data.workshop) do
if not contains(v.name) then
table.insert(conflicts, v)
end
end
for _, v in ipairs(data.external) do
table.insert(self.requiredMods, v)
if not contains(v.name) then
table.insert(conflicts, v)
end
end

-- Display a prompt if mod conflicts are detected
if #conflicts > 0 then
self.missingMods = conflicts
logger:info("Mod conflicts detected: Missing " .. table.concat(conflicts, ", "))
end
CustomProfiler.stop("Client.onNeedModList", cpc)
end

local function onNeedModContent(data)
local cpc = CustomProfiler.start("Client.onNeedModContent")
for _, v in ipairs(data.items) do
ofoxsmith marked this conversation as resolved.
Show resolved Hide resolved
local modName = v.name
local modID = v.workshopID
local modData = v.data
if modID == "0" then
if not fu.IsDirectory((fu.GetAbsolutePathOfNoitaRootDirectory() .. "/mods/%s/"):format(modName)) then
local fileName = ("%s_%s_mod_sync.7z"):format(tostring(os.date("!")), modName)
fu.WriteBinaryFile(fu.GetAbsoluteDirectoryPathOfMods() .. "/" .. fileName, modData)
fu.Extract7zipArchive(fu.GetAbsoluteDirectoryPathOfMods(), fileName,
(fu.GetAbsolutePathOfNoitaRootDirectory() .. "/mods/%s/"):format(modName))
end
else
if not fu.IsDirectory(("C:/Program Files (x86)/Steam/steamapps/workshop/content/881100/%s/"):format(modID)) then
if not fu.IsDirectory((fu.GetAbsolutePathOfNoitaRootDirectory() .. "/mods/%s/"):format(modName)) then
local fileName = ("%s_%s_mod_sync.7z"):format(tostring(os.date("!")), modName)
fu.WriteBinaryFile(fu.GetAbsoluteDirectoryPathOfMods() .. "/" .. fileName, modData)
fu.Extract7zipArchive(fu.GetAbsoluteDirectoryPathOfMods(), fileName,
(fu.GetAbsolutePathOfNoitaRootDirectory() .. "/mods/%s/"):format(modName))
end
end
end
end
CustomProfiler.stop("Client.onNeedModContent", cpc)
end

-- self:on(
-- "entityAlive",
-- function(data)
Expand Down Expand Up @@ -649,6 +712,11 @@ function Client.new(sockClient)
self:setSchema(NetworkUtils.events.deadNuids.name, NetworkUtils.events.deadNuids.schema)
self:on(NetworkUtils.events.deadNuids.name, onDeadNuids)

self:setSchema(NetworkUtils.events.needModList.name, NetworkUtils.events.needModList.schema)
self:on(NetworkUtils.events.needModList.name, onNeedModList)

self:setSchema(NetworkUtils.events.needModContent.name, NetworkUtils.events.needModContent.schema)
self:on(NetworkUtils.events.needModContent.name, onNeedModContent)
-- self:setSchema("duplicatedGuid", { "newGuid" })
-- self:setSchema("worldFiles", { "relDirPath", "fileName", "fileContent", "fileIndex", "amountOfFiles" })
-- self:setSchema("worldFilesFinished", { "progress" })
Expand Down Expand Up @@ -694,7 +762,7 @@ function Client.new(sockClient)

if self:isConnecting() or self:isConnected() then
logger:warn(logger.channels.network, "Client is still connected to %s:%s. Disconnecting!",
self:getAddress(), self:getPort())
self:getAddress(), self:getPort())
self:disconnect()
end

Expand All @@ -721,8 +789,8 @@ function Client.new(sockClient)
end

GamePrintImportant("Client is connecting..",
"You are trying to connect to " .. self:getAddress() .. ":" .. self:getPort() .. "!",
""
"You are trying to connect to " .. self:getAddress() .. ":" .. self:getPort() .. "!",
""
)

sockClientConnect(self, code)
Expand Down Expand Up @@ -798,7 +866,7 @@ function Client.new(sockClient)

if NetworkUtils.alreadySent(event, data) then
logger:debug(logger.channels.network, ("Network message for %s for data %s already was acknowledged.")
:format(event, util.pformat(data)))
:format(event, util.pformat(data)))
return
end

Expand Down
Loading