Compare commits

..

No commits in common. "master" and "v2.20.0" have entirely different histories.

13 changed files with 72 additions and 248 deletions

2
.gitignore vendored
View File

@ -109,5 +109,3 @@ $RECYCLE.BIN/
/bin/stitch/*.dzi /bin/stitch/*.dzi
/bin/stitch/*_files/ /bin/stitch/*_files/
/files/magic-numbers/generated.xml /files/magic-numbers/generated.xml
/bin/stitch/captures/*

View File

@ -1,7 +1,6 @@
{ {
"cSpell.words": [ "cSpell.words": [
"aabb", "aabb",
"acidflow",
"appdata", "appdata",
"autosetup", "autosetup",
"backbuffer", "backbuffer",
@ -41,24 +40,20 @@
"nfnt", "nfnt",
"Niccoli", "Niccoli",
"noita", "noita",
"noitamap",
"Nolla", "Nolla",
"NXML", "NXML",
"openseadragon",
"pixelated", "pixelated",
"polymorphed", "polymorphed",
"promptui", "promptui",
"rasterizer", "rasterizer",
"Regen", "Regen",
"respawn", "respawn",
"runfast",
"savegames", "savegames",
"schollz", "schollz",
"screenshake", "screenshake",
"svenstaro", "svenstaro",
"tcnksm", "tcnksm",
"tdewolff", "tdewolff",
"unmodded",
"unstitchable", "unstitchable",
"upscaled", "upscaled",
"Vogel", "Vogel",

View File

@ -1,10 +1,6 @@
# Capture areas # Capture areas
A list of available capture areas. A list of available capture areas.
Other game-modes or mods may use a different biome setup, and therefore the coordinates shown here are not valid for them.
The values shown are for an unmodded `New Game` world.
The noita-mapcap mod will always automatically determine the required coordinates so that it correctly captures the base layout or multiples of it.
Coordinates are in in-game "virtual" or "world" pixels. Coordinates are in in-game "virtual" or "world" pixels.
`Right` and `Bottom` coordinates are not included in the rectangle. `Right` and `Bottom` coordinates are not included in the rectangle.

View File

@ -5,10 +5,7 @@ It works with the regular Noita build and the dev build.
![Title image](images/title.png) ![Title image](images/title.png)
Map captures created with this mod can be viewed on [map.runfast.stream] (may contain spoilers). A resulting image with nearly 3.8 gigapixels can be [seen here](https://easyzoom.com/image/223556) (May contain spoilers).
If you are interested in creating similar captures, or if you want to contribute your own captures to [map.runfast.stream], you can take a look at [github.com/acidflow-noita/noitamap].
There you'll find detailed step-by-step instructions on how to quickly capture large parts of the Noita world with as little visual glitches and other issues as possible.
## System requirements ## System requirements
@ -58,7 +55,8 @@ To the top left of the window are 3 buttons:
- ![Output directory button](files/ui-gfx/open-output-16x16.png) Reveals the output directory in your file browser. - ![Output directory button](files/ui-gfx/open-output-16x16.png) Reveals the output directory in your file browser.
This will contain raw screenshots and other recorded data that later can be stitched. This will contain raw screenshots and other recorded data that later can be stitched.
- ![Stitch button](files/ui-gfx/stitch-16x16.png) Reveals the stitching tool directory in your file browser. - ![Stitch button](files/ui-gfx/stitch-16x16.png) Reveals the stitching tool
directory in your file browser.
To stitch the final result, click ![Stitch button](files/ui-gfx/stitch-16x16.png) to open the directory of the stitching tool. To stitch the final result, click ![Stitch button](files/ui-gfx/stitch-16x16.png) to open the directory of the stitching tool.
Start `stitch.exe` and proceed with the default values. Start `stitch.exe` and proceed with the default values.
@ -81,10 +79,6 @@ After a few minutes the file `output.png` will be created.
- `Spiral`: Will capture the world in a spiral. - `Spiral`: Will capture the world in a spiral.
The center starting point of the spiral can either be your current viewport, the world center or some custom coordinates. The center starting point of the spiral can either be your current viewport, the world center or some custom coordinates.
- `Animation`: Will capture an image sequence.
This will capture whatever you see frame by frame and stores it in the output folder by frame number.
You can't stitch the resulting images, but instead you can use something like ffmpeg to render the sequence into a video file.
### Advanced mod settings ### Advanced mod settings
- `World seed`: If non empty, this will set the next new game to this seed. - `World seed`: If non empty, this will set the next new game to this seed.
@ -152,7 +146,7 @@ The sliders are at their default values:
There is not a lot you can do about it: There is not a lot you can do about it:
- ~~You can try to increase the usable address space of your `.../Noita/noita_dev.exe` or `.../Noita/noita.exe` with [Large Address Aware] or a similar tool. - ~~You can try to increase the usable address space of your `.../Noita/noita_dev.exe` or `.../Noita/noita.exe` with [Large Address Aware](https://www.techpowerup.com/forums/threads/large-address-aware.112556/) or a similar tool.
This will help with any crashes that are related to out of memory exceptions.~~ This will help with any crashes that are related to out of memory exceptions.~~
`Large Address Aware` is already set in newer Noita builds. `Large Address Aware` is already set in newer Noita builds.
@ -196,28 +190,17 @@ This will cause fast moving objects to completely disappear, and slow moving obj
To disable median blending, use the stitcher with `Blend tile limit` set to 1. To disable median blending, use the stitcher with `Blend tile limit` set to 1.
This will cause the stitcher to only use the newest image tile for every resulting pixel. This will cause the stitcher to only use the newest image tile for every resulting pixel.
## Viewing and hosting captures ## Additional information
The resulting stitched images are quite big. The resulting stitched images are quite big.
You can read [this comment](https://github.com/Dadido3/noita-mapcap/issues/7#issuecomment-723591552) that addresses how you can view, convert or even self-host your images. You can read [this comment](https://github.com/Dadido3/noita-mapcap/issues/7#issuecomment-723591552) that addresses how you can view, convert or even self-host your images.
You can use [github.com/Dadido3/noita-mapcap-openseadragon] if you want to host a browser based viewer on your own web space.
If you want to make your captures available to a wider audience, you should check out the [github.com/acidflow-noita/noitamap] project, which aims to make maps of all game modes (including mods) available to the public.
## Acknowledgements ## Acknowledgements
This mod uses the [LuaNXML] library by [Zatherz]. This mod uses the [LuaNXML](https://github.com/zatherz/luanxml) library by [Zatherz](https://github.com/zatherz).
Thanks to [Daniel Niccoli](https://github.com/danielniccoli) for figuring out how to change some in-game options by manipulating process memory. Thanks to [Daniel Niccoli](https://github.com/danielniccoli) for figuring out how to change some in-game options by manipulating process memory.
## License ## License
[MIT](LICENSE) [MIT](LICENSE)
[github.com/acidflow-noita/noitamap]: https://github.com/acidflow-noita/noitamap
[github.com/Dadido3/noita-mapcap-openseadragon]: https://github.com/Dadido3/noita-mapcap-openseadragon
[Large Address Aware]: https://www.techpowerup.com/forums/threads/large-address-aware.112556/
[LuaNXML]: https://github.com/zatherz/luanxml
[map.runfast.stream]: https://map.runfast.stream
[Zatherz]: https://github.com/zatherz

View File

@ -132,6 +132,8 @@ ProcedureDLL Capture(*capRect.RECT, x.l, y.l, sx.l, sy.l)
ProcedureReturn #False ProcedureReturn #False
EndIf EndIf
;Protected *pixelBuf = AllocateMemory(3 * width * height)
hDC = StartDrawing(ImageOutput(imageID)) hDC = StartDrawing(ImageOutput(imageID))
If Not hDC If Not hDC
FreeImage(imageID) FreeImage(imageID)
@ -140,11 +142,19 @@ ProcedureDLL Capture(*capRect.RECT, x.l, y.l, sx.l, sy.l)
*pixelBuffer = DrawingBuffer() *pixelBuffer = DrawingBuffer()
glReadPixels_(*capRect\left, *capRect\top, capWidth, capHeight, #GL_BGR_EXT, #GL_UNSIGNED_BYTE, *pixelBuffer) glReadPixels_(*capRect\left, *capRect\top, capWidth, capHeight, #GL_BGR_EXT, #GL_UNSIGNED_BYTE, *pixelBuffer)
If glGetError_() <> #GL_NO_ERROR
StopDrawing() ; For y = 0 To *capRect\height - 1
FreeImage(imageID) ; *Line.Pixel = Buffer + Pitch * y
ProcedureReturn #False ;
EndIf ; For x = 0 To *capRect\width - 1
;
; *Line\Pixel = ColorTable(pos2) ; Write the pixel directly to the memory !
; *Line+Offset
;
; ; You can try with regular plot to see the speed difference
; ; Plot(x, y, ColorTable(pos2))
; Next
; Next
StopDrawing() StopDrawing()
@ -178,8 +188,8 @@ EndProcedure
; IDE Options = PureBasic 6.04 LTS (Windows - x64) ; IDE Options = PureBasic 6.04 LTS (Windows - x64)
; ExecutableFormat = Shared dll ; ExecutableFormat = Shared dll
; CursorPosition = 99 ; CursorPosition = 116
; FirstLine = 72 ; FirstLine = 99
; Folding = - ; Folding = -
; Optimizer ; Optimizer
; EnableThread ; EnableThread

Binary file not shown.

View File

@ -166,37 +166,6 @@ local function captureScreenshot(pos, ensureLoaded, dontOverwrite, ctx, outputPi
MonitorStandby.ResetTimer() MonitorStandby.ResetTimer()
end end
---Captures a screenshot of the current viewport.
---This is used to capture animations, therefore the resulting image may not be suitable for stitching.
---@param outputPixelScale number? The resulting image pixel to world pixel ratio.
---@param frameNumber integer The frame number of the animation.
local function captureScreenshotAnimation(outputPixelScale, frameNumber)
if outputPixelScale == 0 or outputPixelScale == nil then
outputPixelScale = Coords:PixelScale()
end
local rectTopLeft, rectBottomRight = ScreenCapture.GetRect()
if not rectTopLeft or not rectBottomRight then
error(string.format("couldn't determine capturing rectangle"))
end
if Coords:InternalRectSize() ~= rectBottomRight - rectTopLeft then
error(string.format("internal rectangle size seems to have changed from %s to %s", Coords:InternalRectSize(), rectBottomRight - rectTopLeft))
end
local topLeftWorld, bottomRightWorld = Coords:ToWorld(rectTopLeft), Coords:ToWorld(rectBottomRight)
---We will use this to get our fame number into the filename.
---@type Vec2
local outputTopLeft = Vec2(frameNumber, 0)
if not ScreenCapture.Capture(rectTopLeft, rectBottomRight, outputTopLeft, (bottomRightWorld - topLeftWorld) * outputPixelScale) then
error(string.format("failed to capture screenshot"))
end
-- Reset monitor and PC standby every screenshot.
MonitorStandby.ResetTimer()
end
---Map capture process runner context error handler callback. Just rolls off the tongue. ---Map capture process runner context error handler callback. Just rolls off the tongue.
---@param err string ---@param err string
---@param scope "init"|"do"|"end" ---@param scope "init"|"do"|"end"
@ -781,56 +750,6 @@ function Capture:StartCapturingPlayerPath(interval, outputPixelScale)
self.PlayerPathCapturingCtx:Run(handleInit, handleDo, handleEnd, handleErr) self.PlayerPathCapturingCtx:Run(handleInit, handleDo, handleEnd, handleErr)
end end
---Starts to capture an animation.
---This stores sequences of images that can't be stitched, but can be rendered into a video instead.
---Use `Capture.MapCapturingCtx` to stop, control or view the process.
---@param outputPixelScale number? -- The resulting image pixel to world pixel ratio.
function Capture:StartCapturingAnimation(outputPixelScale)
---Queries the mod settings for the live capture parameters.
---@return integer interval -- The interval length in frames.
local function querySettings()
local interval = 1--tonumber(ModSettingGet("noita-mapcap.live-interval")) or 30
return interval
end
-- Create file that signals that there are files in the output directory.
local file = io.open("mods/noita-mapcap/output/nonempty", "a")
if file ~= nil then file:close() end
---Process main callback.
---@param ctx ProcessRunnerCtx
local function handleDo(ctx)
Modification.SetCameraFree(false)
local frame = 0
repeat
local interval = querySettings()
-- Wait until we are allowed to take a new screenshot.
local delayFrames = 0
repeat
wait(0)
delayFrames = delayFrames + 1
until ctx:IsStopping() or delayFrames >= interval
captureScreenshotAnimation(outputPixelScale, frame)
frame = frame + 1
until ctx:IsStopping()
end
---Process end callback.
---@param ctx ProcessRunnerCtx
local function handleEnd(ctx)
Modification.SetCameraFree()
end
-- Run process, if there is no other running right now.
self.MapCapturingCtx:Run(nil, handleDo, handleEnd, mapCapturingCtxErrHandler)
end
---Starts the capturing process based on user/mod settings. ---Starts the capturing process based on user/mod settings.
function Capture:StartCapturing() function Capture:StartCapturing()
Message:CatchException("Capture:StartCapturing", function() Message:CatchException("Capture:StartCapturing", function()
@ -843,8 +762,6 @@ function Capture:StartCapturing()
if mode == "live" then if mode == "live" then
self:StartCapturingLive(outputPixelScale) self:StartCapturingLive(outputPixelScale)
self:StartCapturingPlayerPath(5, outputPixelScale) -- Capture player path with an interval of 5 frames. self:StartCapturingPlayerPath(5, outputPixelScale) -- Capture player path with an interval of 5 frames.
elseif mode == "animation" then
self:StartCapturingAnimation(outputPixelScale)
elseif mode == "area" then elseif mode == "area" then
local area = ModSettingGet("noita-mapcap.area") local area = ModSettingGet("noita-mapcap.area")
if area == "custom" then if area == "custom" then
@ -853,11 +770,9 @@ function Capture:StartCapturing()
self:StartCapturingAreaScan(topLeft, bottomRight, captureGridSize, outputPixelScale, captureDelay) self:StartCapturingAreaScan(topLeft, bottomRight, captureGridSize, outputPixelScale, captureDelay)
else else
---@type fun():Vec2, Vec2 local predefinedArea = Config.CaptureArea[area]
local predefinedAreaFunction = Config.CaptureArea[area] if predefinedArea then
if predefinedAreaFunction then self:StartCapturingAreaScan(predefinedArea.TopLeft, predefinedArea.BottomRight, captureGridSize, outputPixelScale, captureDelay)
local topLeft, bottomRight = predefinedAreaFunction()
self:StartCapturingAreaScan(topLeft, bottomRight, captureGridSize, outputPixelScale, captureDelay)
else else
Message:ShowRuntimeError("PredefinedArea", string.format("Unknown predefined capturing area %q", tostring(area))) Message:ShowRuntimeError("PredefinedArea", string.format("Unknown predefined capturing area %q", tostring(area)))
end end

View File

@ -120,7 +120,7 @@ function Check:Regular(interval)
-- This is not perfect, as it doesn't take rounding and cropping into account, so the actual captured area may be a few pixels smaller. -- This is not perfect, as it doesn't take rounding and cropping into account, so the actual captured area may be a few pixels smaller.
local mode = ModSettingGet("noita-mapcap.capture-mode") local mode = ModSettingGet("noita-mapcap.capture-mode")
local captureGridSize = tonumber(ModSettingGet("noita-mapcap.grid-size")) local captureGridSize = tonumber(ModSettingGet("noita-mapcap.grid-size"))
if (mode ~= "live" and mode ~= "animation") and (Coords.VirtualResolution.x < captureGridSize or Coords.VirtualResolution.y < captureGridSize) then if mode ~= "live" and (Coords.VirtualResolution.x < captureGridSize or Coords.VirtualResolution.y < captureGridSize) then
Message:ShowGeneralSettingsProblem( Message:ShowGeneralSettingsProblem(
"The virtual resolution is smaller than the capture grid size.", "The virtual resolution is smaller than the capture grid size.",
"This means that you will get black areas in your final stitched image.", "This means that you will get black areas in your final stitched image.",

View File

@ -3,7 +3,6 @@
-- This software is released under the MIT License. -- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT -- https://opensource.org/licenses/MIT
local NXML = require("luanxml.nxml")
local Vec2 = require("noita-api.vec2") local Vec2 = require("noita-api.vec2")
-- List of components that will be disabled on every encountered entity. -- List of components that will be disabled on every encountered entity.
@ -28,65 +27,40 @@ Config.ComponentsToDisable = {
--"AudioComponent", --"AudioComponent",
} }
local CHUNK_SIZE = 512
---Returns the rectangle of the base area as two vectors.
---@return Vec2 TopLeft Top left corner in world coordinates.
---@return Vec2 BottomRight Bottom right corner in world coordinates. This pixel is not included in the final rectangle.
local function getBaseArea()
local xml = NXML.parse(ModTextFileGetContent("data/biome/_biomes_all.xml"))
local width, height = BiomeMapGetSize()
local offsetX, offsetY = math.floor(width/2), xml.attr.biome_offset_y -- TODO: This may not be right. Check what Noita is really doing when we have a biome map with an odd width.
return Vec2(-offsetX, -offsetY)*CHUNK_SIZE, Vec2(-offsetX+width, -offsetY+height)*CHUNK_SIZE
--return Vec2(-17920, -7168), Vec2(17920, 17408) -- Coordinates for a "New Game" without mods or anything.
end
---A list of capture areas.
---This contains functions that determine the capture area based on the biome size and other parameters.
---The returned vectors are the top left corner, and the bottom right corner of the capture area in world coordinates.
---The bottom right corner pixel is not included in the rectangle.
---@type table<string, fun():Vec2, Vec2>
Config.CaptureArea = { Config.CaptureArea = {
-- Base layout: Every part outside this is based on a similar layout, but uses different materials/seeds. -- Base layout: Every part outside this is based on a similar layout, but uses different materials/seeds.
["1x1"] = getBaseArea, ["1x1"] = {
TopLeft = Vec2(-17920, -7168), -- in world coordinates.
BottomRight = Vec2(17920, 17408), -- in world coordinates. This pixel is not included in the rectangle.
},
-- Main world: The main world with 3 parts: sky, normal and hell. -- Main world: The main world with 3 parts: sky, normal and hell.
["1x3"] = function() ["1x3"] = {
local width, height = BiomeMapGetSize() TopLeft = Vec2(-17920, -31744), -- in world coordinates.
local topLeft, bottomRight = getBaseArea() BottomRight = Vec2(17920, 41984), -- in world coordinates. This pixel is not included in the rectangle.
return topLeft + Vec2(0, -height)*CHUNK_SIZE, bottomRight + Vec2(0, height)*CHUNK_SIZE },
--return Vec2(-17920, -31744), Vec2(17920, 41984) -- Coordinates for a "New Game" without mods or anything.
end,
-- -1 parallel world: The parallel world with 3 parts: sky, normal and hell. -- -1 parallel world: The parallel world with 3 parts: sky, normal and hell.
["1x3 -1"] = function() ["1x3 -1"] = {
local width, height = BiomeMapGetSize() TopLeft = Vec2(-17920, -31744) + Vec2(-35840, 0), -- in world coordinates.
local topLeft, bottomRight = getBaseArea() BottomRight = Vec2(17920, 41984) + Vec2(-35840, 0), -- in world coordinates. This pixel is not included in the rectangle.
return topLeft + Vec2(-width, -height)*CHUNK_SIZE, bottomRight + Vec2(-width, height)*CHUNK_SIZE },
--return Vec2(-17920, -31744) + Vec2(-35840, 0), Vec2(17920, 41984) + Vec2(-35840, 0) -- Coordinates for a "New Game" without mods or anything.
end,
-- +1 parallel world: The parallel world with 3 parts: sky, normal and hell. -- +1 parallel world: The parallel world with 3 parts: sky, normal and hell.
["1x3 +1"] = function() ["1x3 +1"] = {
local width, height = BiomeMapGetSize() TopLeft = Vec2(-17920, -31744) + Vec2(35840, 0), -- in world coordinates.
local topLeft, bottomRight = getBaseArea() BottomRight = Vec2(17920, 41984) + Vec2(35840, 0), -- in world coordinates. This pixel is not included in the rectangle.
return topLeft + Vec2(width, -height)*CHUNK_SIZE, bottomRight + Vec2(width, height)*CHUNK_SIZE },
--return Vec2(-17920, -31744) + Vec2(35840, 0), Vec2(17920, 41984) + Vec2(35840, 0) -- Coordinates for a "New Game" without mods or anything.
end,
-- Extended: Main world + a fraction of the parallel worlds to the left and right. -- Extended: Main world + a fraction of the parallel worlds to the left and right.
["1.5x3"] = function() ["1.5x3"] = {
local width, height = BiomeMapGetSize() TopLeft = Vec2(-25600, -31744), -- in world coordinates.
local topLeft, bottomRight = getBaseArea() BottomRight = Vec2(25600, 41984), -- in world coordinates. This pixel is not included in the rectangle.
return topLeft + Vec2(-math.floor(0.25*width), -height)*CHUNK_SIZE, bottomRight + Vec2(math.floor(0.25*width), height)*CHUNK_SIZE },
--return Vec2(-25600, -31744), Vec2(25600, 41984) -- Coordinates for a "New Game" without mods or anything. These coordinates may not exactly be 1.5 of the base width for historic reasons.
end,
-- Extended: Main world + each parallel world to the left and right. -- Extended: Main world + each parallel world to the left and right.
["3x3"] = function() ["3x3"] = {
local width, height = BiomeMapGetSize() TopLeft = Vec2(-53760, -31744), -- in world coordinates.
local topLeft, bottomRight = getBaseArea() BottomRight = Vec2(53760, 41984), -- in world coordinates. This pixel is not included in the rectangle.
return topLeft + Vec2(-width, -height)*CHUNK_SIZE, bottomRight + Vec2(width, height)*CHUNK_SIZE },
--return Vec2(-53760, -31744), Vec2(53760, 41984) -- Coordinates for a "New Game" without mods or anything.
end,
} }

View File

@ -238,36 +238,6 @@ function Modification.SetMemoryOptions(memory)
mPlayerNeverDies = function(value) ffi.cast("char*", 0x0131D89C+6)[0] = value end, mPlayerNeverDies = function(value) ffi.cast("char*", 0x0131D89C+6)[0] = value end,
mFreezeAI = function(value) ffi.cast("char*", 0x0131D89C+7)[0] = value end, mFreezeAI = function(value) ffi.cast("char*", 0x0131D89C+7)[0] = value end,
}, },
{_Offset = 0x0118718C, _BuildString = "Build Apr 8 2024 18:07:16", -- Steam dev build.
mPostFxDisabled = function(value) ffi.cast("char*", 0x0131D8DC+0)[0] = value end,
mGuiDisabled = function(value) ffi.cast("char*", 0x0131D8DC+1)[0] = value end,
mGuiHalfSize = function(value) ffi.cast("char*", 0x0131D8DC+2)[0] = value end,
mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x0131D8DC+3)[0] = value end,
mTrailerMode = function(value) ffi.cast("char*", 0x0131D8DC+4)[0] = value end,
mDayTimeRotationPause = function(value) ffi.cast("char*", 0x0131D8DC+5)[0] = value end,
mPlayerNeverDies = function(value) ffi.cast("char*", 0x0131D8DC+6)[0] = value end,
mFreezeAI = function(value) ffi.cast("char*", 0x0131D8DC+7)[0] = value end,
},
{_Offset = 0x0118FD3C, _BuildString = "Build Aug 12 2024 21:10:13", -- Steam dev build.
mPostFxDisabled = function(value) ffi.cast("char*", 0x01327D3C+0)[0] = value end,
mGuiDisabled = function(value) ffi.cast("char*", 0x01327D3C+1)[0] = value end,
mGuiHalfSize = function(value) ffi.cast("char*", 0x01327D3C+2)[0] = value end,
mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x01327D3C+3)[0] = value end,
mTrailerMode = function(value) ffi.cast("char*", 0x01327D3C+4)[0] = value end,
mDayTimeRotationPause = function(value) ffi.cast("char*", 0x01327D3C+5)[0] = value end,
mPlayerNeverDies = function(value) ffi.cast("char*", 0x01327D3C+6)[0] = value end,
mFreezeAI = function(value) ffi.cast("char*", 0x01327D3C+7)[0] = value end,
},
{_Offset = 0x0118FD3C, _BuildString = "Build Aug 12 2024 21:43:22", -- Steam dev build.
mPostFxDisabled = function(value) ffi.cast("char*", 0x01327D3C+0)[0] = value end,
mGuiDisabled = function(value) ffi.cast("char*", 0x01327D3C+1)[0] = value end,
mGuiHalfSize = function(value) ffi.cast("char*", 0x01327D3C+2)[0] = value end,
mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x01327D3C+3)[0] = value end,
mTrailerMode = function(value) ffi.cast("char*", 0x01327D3C+4)[0] = value end,
mDayTimeRotationPause = function(value) ffi.cast("char*", 0x01327D3C+5)[0] = value end,
mPlayerNeverDies = function(value) ffi.cast("char*", 0x01327D3C+6)[0] = value end,
mFreezeAI = function(value) ffi.cast("char*", 0x01327D3C+7)[0] = value end,
},
}, },
}, },
[false] = { [false] = {
@ -384,27 +354,6 @@ function Modification.SetMemoryOptions(memory)
ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled. ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
end, end,
}, },
{_Offset = 0x01001DF4, _BuildString = "Build Apr 8 2024 18:11:27", -- Steam build.
enableModDetection = function(value)
local ptr = ffi.cast("char*", 0x006B3355+6)
Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE)
ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
end,
},
{_Offset = 0x01007CA4, _BuildString = "Build Aug 12 2024 21:14:23", -- Steam build.
enableModDetection = function(value)
local ptr = ffi.cast("char*", 0x006B3925+6)
Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE)
ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
end,
},
{_Offset = 0x01007CA4, _BuildString = "Build Aug 12 2024 21:48:01", -- Steam build.
enableModDetection = function(value)
local ptr = ffi.cast("char*", 0x006B3925+6)
Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE)
ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
end,
},
}, },
}, },
} }

8
go.mod
View File

@ -35,9 +35,9 @@ require (
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/tdewolff/minify/v2 v2.20.10 // indirect github.com/tdewolff/minify/v2 v2.20.10 // indirect
github.com/tdewolff/parse/v2 v2.7.7 // indirect github.com/tdewolff/parse/v2 v2.7.7 // indirect
golang.org/x/image v0.18.0 // indirect golang.org/x/image v0.14.0 // indirect
golang.org/x/net v0.23.0 // indirect golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect
star-tex.org/x/tex v0.4.0 // indirect star-tex.org/x/tex v0.4.0 // indirect
) )

20
go.sum
View File

@ -4,6 +4,10 @@ github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1 h1:LejjvYg4tCW5HO
github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1/go.mod h1:cnC/60IoLiDM0GhdKYJ6oO7AwpZe1IQfPnSKlAURgHw= github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1/go.mod h1:cnC/60IoLiDM0GhdKYJ6oO7AwpZe1IQfPnSKlAURgHw=
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw= github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw=
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic= github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic=
github.com/Dadido3/go-libwebp v0.1.0 h1:aMM8wwOSyq8mk/xL9ze3vQHPygDU8TMXKH0TViwkBLE=
github.com/Dadido3/go-libwebp v0.1.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo=
github.com/Dadido3/go-libwebp v0.2.0 h1:SmssjSkrDkwSOGEpdultvneADGYaEqPbv6FcgausaK0=
github.com/Dadido3/go-libwebp v0.2.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo=
github.com/Dadido3/go-libwebp v0.3.0 h1:Qr3Gt8Kn4qgemezDVnjAJffMB9C0QJhxP+9u0U5mC94= github.com/Dadido3/go-libwebp v0.3.0 h1:Qr3Gt8Kn4qgemezDVnjAJffMB9C0QJhxP+9u0U5mC94=
github.com/Dadido3/go-libwebp v0.3.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo= github.com/Dadido3/go-libwebp v0.3.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
@ -87,11 +91,11 @@ github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -99,14 +103,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE=
gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU=

View File

@ -67,9 +67,9 @@ modSettings = {
{ {
id = "capture-mode", id = "capture-mode",
ui_name = "Mode", ui_name = "Mode",
ui_description = "How the mod captures:\n- Live: Capture as you play along.\n- Area: Capture a defined area of the world.\n- Spiral: Capture in a spiral around a starting point indefinitely.\n- Animation: Capture the screen frame by frame.", ui_description = "How the mod captures:\n- Live: Capture as you play along.\n- Area: Capture a defined area of the world.\n- Spiral: Capture in a spiral around a starting point indefinitely.",
value_default = "live", value_default = "live",
values = { { "live", "Live" }, { "area", "Area" }, { "spiral", "Spiral" }, { "animation", "Animation"} }, values = { { "live", "Live" }, { "area", "Area" }, { "spiral", "Spiral" } },
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
}, },
{ {
@ -143,7 +143,7 @@ modSettings = {
value_default = "512", value_default = "512",
allowed_characters = "0123456789", allowed_characters = "0123456789",
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "area" or modSettings:GetNextValue("capture-mode") == "spiral" end, show_fn = function() return modSettings:GetNextValue("capture-mode") ~= "live" end,
}, },
{ {
id = "pixel-scale", id = "pixel-scale",
@ -167,7 +167,7 @@ modSettings = {
value_display_multiplier = 1, value_display_multiplier = 1,
value_display_formatting = " $0 frames", value_display_formatting = " $0 frames",
scope = MOD_SETTING_SCOPE_RUNTIME, scope = MOD_SETTING_SCOPE_RUNTIME,
show_fn = function() return modSettings:GetNextValue("capture-mode") == "area" or modSettings:GetNextValue("capture-mode") == "spiral" end, show_fn = function() return modSettings:GetNextValue("capture-mode") ~= "live" end,
}, },
{ {
id = "custom-resolution-live", id = "custom-resolution-live",