Update capturing stuff

- Add ability to capture while normally playing
- Calculate capture area based on coordinate transformations
- Improve and simplify captureScreenshot function
- Move dynamic library wrappers into libraries folder
- Update capture.dll to support cropping and resizing
- Recompile capture.dll with newer PureBasic compiler that uses C backend
- Increase capture.dll worker threads to 6
- Increase capture.dll queue size by one
- Add Round and Rounded methods to Vec2
- Split magic number XML files for easier debugging
- Fix some EmmyLua annotations
- And and fix some comments
This commit is contained in:
David Vogel 2022-07-24 22:05:34 +02:00
parent f2e582622e
commit 0126e706cb
16 changed files with 331 additions and 140 deletions

View File

@ -1,4 +1,4 @@
; Copyright (c) 2019-2020 David Vogel
; Copyright (c) 2019-2022 David Vogel
;
; This software is released under the MIT License.
; https://opensource.org/licenses/MIT
@ -11,6 +11,8 @@ Structure QueueElement
img.i
x.i
y.i
sx.i
sy.i
EndStructure
; Source: https://www.purebasic.fr/english/viewtopic.php?f=13&t=29981&start=15
@ -62,7 +64,7 @@ ProcedureDLL AttachProcess(Instance)
CreateDirectory("mods/noita-mapcap/output/")
For i = 1 To 4
For i = 1 To 6
CreateThread(@Worker(), #Null)
Next
EndProcedure
@ -78,9 +80,15 @@ Procedure Worker(*Dummy)
img = Queue()\img
x = Queue()\x
y = Queue()\y
sx = Queue()\sx
sy = Queue()\sy
DeleteElement(Queue())
UnlockMutex(Mutex)
If sx > 0 And sy > 0
ResizeImage(img, sx, sy)
EndIf
SaveImage(img, "mods/noita-mapcap/output/" + x + "," + y + ".png", #PB_ImagePlugin_PNG)
;SaveImage(img, "" + x + "," + y + ".png", #PB_ImagePlugin_PNG) ; Test
@ -88,7 +96,11 @@ Procedure Worker(*Dummy)
ForEver
EndProcedure
ProcedureDLL Capture(px.i, py.i)
; Takes a screenshot of the client area of this process' active window.
; The portion of the client area that is captured is described by capRect, which is in window coordinates and relative to the client area.
; x and y defines the top left position of the captured rectangle in scaled world coordinates. The scale depends on the window to world pixel ratio.
; sx and sy defines the final dimensions that the screenshot will be resized to. No resize will happen if set to 0.
ProcedureDLL Capture(*capRect.RECT, x.l, y.l, sx.l, sy.l)
Protected hWnd.l = GetProcHwnd()
If Not hWnd
ProcedureReturn #False
@ -99,12 +111,18 @@ ProcedureDLL Capture(px.i, py.i)
ProcedureReturn #False
EndIf
imageID = CreateImage(#PB_Any, rect\right-rect\left, rect\bottom-rect\top)
; Limit the desired capture area to the actual client area of the window.
If *capRect\left < 0 : *capRect\left = 0 : EndIf
If *capRect\right > rect\right-rect\left : *capRect\right = rect\right-rect\left : EndIf
If *capRect\top < 0 : *capRect\top = 0 : EndIf
If *capRect\bottom > rect\bottom-rect\top : *capRect\bottom = rect\bottom-rect\top : EndIf
imageID = CreateImage(#PB_Any, *capRect\right-*capRect\left, *capRect\bottom-*capRect\top)
If Not imageID
ProcedureReturn #False
EndIf
; Get DC of whole screen
; Get DC of window.
windowDC = GetDC_(hWnd)
If Not windowDC
FreeImage(imageID)
@ -117,7 +135,7 @@ ProcedureDLL Capture(px.i, py.i)
FreeImage(imageID)
ProcedureReturn #False
EndIf
If Not BitBlt_(hDC, 0, 0, rect\right-rect\left, rect\bottom-rect\top, windowDC, 0, 0, #SRCCOPY) ; After some time BitBlt will fail, no idea why. Also, that's moments before noita crashes.
If Not BitBlt_(hDC, 0, 0, *capRect\right-*capRect\left, *capRect\bottom-*capRect\top, windowDC, *capRect\left, *capRect\top, #SRCCOPY) ; After some time BitBlt will fail, no idea why. Also, that's moments before noita crashes.
StopDrawing()
ReleaseDC_(hWnd, windowDC)
FreeImage(imageID)
@ -128,17 +146,19 @@ ProcedureDLL Capture(px.i, py.i)
ReleaseDC_(hWnd, windowDC)
LockMutex(Mutex)
; Check if the queue has too many elements, if so, wait. (Simulate go's channels)
While ListSize(Queue()) > 0
; Check if the queue has too many elements, if so, wait. (Emulate go's channels)
While ListSize(Queue()) > 1
UnlockMutex(Mutex)
Delay(10)
Delay(1)
LockMutex(Mutex)
Wend
LastElement(Queue())
AddElement(Queue())
Queue()\img = imageID
Queue()\x = px
Queue()\y = py
Queue()\x = x
Queue()\y = y
Queue()\sx = sx
Queue()\sy = sy
UnlockMutex(Mutex)
SignalSemaphore(Semaphore)
@ -153,12 +173,13 @@ EndProcedure
;Capture(123, 123)
;Delay(1000)
; IDE Options = PureBasic 5.72 (Windows - x64)
; IDE Options = PureBasic 6.00 LTS (Windows - x64)
; ExecutableFormat = Shared dll
; CursorPosition = 90
; FirstLine = 77
; CursorPosition = 94
; FirstLine = 39
; Folding = --
; Optimizer
; EnableThread
; EnableXP
; Executable = capture.dll
; Compiler = PureBasic 5.71 LTS (Windows - x86)
; Compiler = PureBasic 6.00 LTS (Windows - x86)

Binary file not shown.

View File

@ -7,10 +7,15 @@
-- Load library modules --
--------------------------
local JSON = require("noita-api.json")
local CameraAPI = require("noita-api.camera")
local Coords = require("coordinates")
local EntityAPI = require("noita-api.entity")
local Utils = require("noita-api.utils")
local Hilbert = require("hilbert-curve")
local JSON = require("noita-api.json")
local ScreenCapture = require("screen-capture")
local Utils = require("noita-api.utils")
local Vec2 = require("noita-api.vec2")
local MonitorStandby = require("monitor-standby")
----------
-- Code --
@ -194,47 +199,69 @@ function DebugEntityCapture()
end)
end
--- Captures a screenshot at the given coordinates.
--- This will block until all chunks in the given area are loaded.
---
--- @param x number -- Virtual x coordinate (World pixels) of the screen center.
--- @param y number -- Virtual y coordinate (World pixels) of the screen center.
--- @param rx number -- Screen x coordinate of the top left corner of the screenshot rectangle.
--- @param ry number -- Screen y coordinate of the top left corner of the screenshot rectangle.
local function captureScreenshot(x, y, rx, ry)
local virtualWidth, virtualHeight =
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")),
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
---Returns a capturing rectangle in window coordinates, and also the world coordinates for the same rectangle.
---@param pos Vec2|nil -- Position of the viewport center in world coordinates. If set to nil, the viewport center will be queried automatically.
---@return Vec2 topLeftCapture
---@return Vec2 bottomRightCapture
---@return Vec2 topLeftWorld
---@return Vec2 bottomRightWorld
local function GenerateCaptureRectangle(pos)
local topLeft, bottomRight = Coords:ValidRenderingRect()
local virtualHalfWidth, virtualHalfHeight = math.floor(virtualWidth / 2), math.floor(virtualHeight / 2)
local xMin, yMin = x - virtualHalfWidth, y - virtualHalfHeight
local xMax, yMax = xMin + virtualWidth, yMin + virtualHeight
-- Convert valid rendering rectangle into world coordinates, and round it towards the window center.
local topLeftWorld, bottomRightWorld = Coords:ToWorld(topLeft, pos):Rounded("ceil"), Coords:ToWorld(bottomRight, pos):Rounded("floor")
-- Convert back into window coordinates, and round to nearest.
local topLeftCapture, bottomRightCapture = Coords:ToWindow(topLeftWorld, pos):Rounded(), Coords:ToWindow(bottomRightWorld, pos):Rounded()
return topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld
end
---Captures a screenshot at the given position in world coordinates.
---This will block until all chunks in the virtual rectangle are loaded.
---
---Don't set `ensureLoaded` to true when `pos` is nil!
---@param pos Vec2|nil -- Position of the viewport center in world coordinates. If set to nil, the viewport will not be modified.
---@param ensureLoaded boolean|nil -- If true, the function will wait until all chunks in the virtual rectangle are loaded.
local function captureScreenshot(pos, ensureLoaded)
local topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = GenerateCaptureRectangle(pos)
UiCaptureDelay = 0
GameSetCameraPos(x, y)
if pos then CameraAPI.SetPos(pos) end
if ensureLoaded then
repeat
if UiCaptureDelay > 100 then
-- Wiggle the screen a bit, as chunks sometimes don't want to load.
GameSetCameraPos(x + math.random(-100, 100), y + math.random(-100, 100))
if pos then CameraAPI.SetPos(pos + Vec2(math.random(-100, 100), math.random(-100, 100))) end
DrawUI()
wait(0)
UiCaptureDelay = UiCaptureDelay + 1
GameSetCameraPos(x, y)
if pos then CameraAPI.SetPos(pos) end
end
DrawUI()
wait(0)
UiCaptureDelay = UiCaptureDelay + 1
until DoesWorldExistAt(xMin, yMin, xMax, yMax) -- Chunks will be drawn on the *next* frame.
until DoesWorldExistAt(topLeftWorld.x, topLeftWorld.y, bottomRightWorld.x, bottomRightWorld.y)
-- Chunks are loaded an will be drawn on the *next* frame.
end
wait(0) -- Without this line empty chunks may still appear, also it's needed for the UI to disappear.
if not TriggerCapture(rx, ry) then
-- Fetch coordinates again, as they may have changed.
local topLeftCapture, bottomRightCapture, topLeftWorld, bottomRightWorld = GenerateCaptureRectangle(pos)
local outputPixelScale = 4
-- The top left world position needs to be upscaled by the pixel scale.
-- Otherwise it's not possible to stitch the images correctly.
if not ScreenCapture.Capture(topLeftCapture, bottomRightCapture, (topLeftWorld * outputPixelScale):Rounded(), (bottomRightWorld - topLeftWorld) * outputPixelScale) then
UiCaptureProblem = "Screen capture failed. Please restart Noita."
end
-- Reset monitor and PC standby each screenshot.
ResetStandbyTimer()
-- Reset monitor and PC standby every screenshot.
MonitorStandby.ResetTimer()
end
function startCapturingSpiral()
@ -245,9 +272,7 @@ function startCapturingSpiral()
ox, oy = ox + 256, oy + 256 -- Align screen with ingame chunk grid that is 512x512.
local x, y = ox, oy
local virtualWidth, virtualHeight =
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")),
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
local virtualWidth, virtualHeight = tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
local virtualHalfWidth, virtualHalfHeight = math.floor(virtualWidth / 2), math.floor(virtualHeight / 2)
@ -272,7 +297,7 @@ function startCapturingSpiral()
for i = 1, i, 1 do
local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not Utils.FileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry, entityFile)
captureScreenshot(Vec2(x, y), true)
end
x, y = x + CAPTURE_GRID_SIZE, y
end
@ -280,7 +305,7 @@ function startCapturingSpiral()
for i = 1, i, 1 do
local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not Utils.FileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry, entityFile)
captureScreenshot(Vec2(x, y), true)
end
x, y = x, y + CAPTURE_GRID_SIZE
end
@ -289,7 +314,7 @@ function startCapturingSpiral()
for i = 1, i, 1 do
local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not Utils.FileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry, entityFile)
captureScreenshot(Vec2(x, y), true)
end
x, y = x - CAPTURE_GRID_SIZE, y
end
@ -297,7 +322,7 @@ function startCapturingSpiral()
for i = 1, i, 1 do
local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not Utils.FileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry, entityFile)
captureScreenshot(Vec2(x, y), true)
end
x, y = x, y - CAPTURE_GRID_SIZE
end
@ -311,9 +336,7 @@ function startCapturingHilbert(area)
local ox, oy = GameGetCameraPos()
local virtualWidth, virtualHeight =
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")),
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
local virtualWidth, virtualHeight = tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
local virtualHalfWidth, virtualHalfHeight = math.floor(virtualWidth / 2), math.floor(virtualHeight / 2)
@ -342,7 +365,7 @@ function startCapturingHilbert(area)
local t, tLimit = 0, gridMaxSize * gridMaxSize
UiProgress = {Progress = 0, Max = gridWidth * gridHeight}
UiProgress = { Progress = 0, Max = gridWidth * gridHeight }
GameSetCameraFree(true)
@ -367,7 +390,7 @@ function startCapturingHilbert(area)
x, y = x + 256, y + 256 -- Align screen with ingame chunk grid that is 512x512.
local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not Utils.FileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry, entityFile)
captureScreenshot(Vec2(x, y), true)
end
UiProgress.Progress = UiProgress.Progress + 1
end
@ -379,3 +402,46 @@ function startCapturingHilbert(area)
end
)
end
---Starts the capturing screenshots at the given interval.
---This will not move the viewport and is meant to capture the player while playing.
---@param interval integer|nil -- The interval length in frames. Defaults to 60.
---@param minDistance number|nil -- The minimum distance between screenshots. This will prevent screenshots if the player doesn't move much.
---@param maxDistance number|nil -- The maximum distance between screenshots. This will allow more screenshots per interval if the player moves fast.
function StartCapturingLive(interval, minDistance, maxDistance)
interval = interval or 60
minDistance = minDistance or 10
maxDistance = maxDistance or 50
local minDistanceSqr, maxDistanceSqr = minDistance ^ 2, maxDistance ^ 2
--local entityFile = createOrOpenEntityCaptureFile()
-- Coroutine to capture all entities around the viewport every frame.
--[[async_loop(function()
local pos = CameraAPI:GetPos() -- Returns the virtual coordinates of the screen center.
-- Call the protected function and catch any errors.
local ok, err = pcall(captureEntities, entityFile, pos.x, pos.y, 5000)
if not ok then
print(string.format("Entity capture error: %s", err))
end
wait(0)
end)]]
local oldPos
-- Coroutine to calculate next coordinate, and trigger screenshots.
async_loop(function()
local frames = 0
repeat
wait(0)
frames = frames + 1
local distanceSqr
if oldPos then distanceSqr = CameraAPI.GetPos():DistanceSqr(oldPos) else distanceSqr = math.huge end
until (frames >= interval or distanceSqr >= maxDistanceSqr) and distanceSqr >= minDistanceSqr
captureScreenshot()
oldPos = CameraAPI.GetPos()
end)
end

View File

@ -1,44 +0,0 @@
-- Copyright (c) 2019-2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
local ffi = ffi or _G.ffi or require("ffi")
local status, caplib = pcall(ffi.load, "mods/noita-mapcap/bin/capture-b/capture")
if not status then
print("Error loading capture lib: " .. caplib)
end
ffi.cdef [[
typedef long LONG;
typedef struct {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
bool GetRect(RECT* rect);
bool Capture(int x, int y);
int SetThreadExecutionState(int esFlags);
]]
function TriggerCapture(x, y)
return caplib.Capture(x, y)
end
-- Get the client rectangle of the "Main" window of this process in screen coordinates
function GetRect()
local rect = ffi.new("RECT", 0, 0, 0, 0)
if not caplib.GetRect(rect) then
return nil
end
return rect
end
-- Reset computer and monitor standby timer
function ResetStandbyTimer()
ffi.C.SetThreadExecutionState(3) -- ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
end

View File

@ -5,6 +5,9 @@
-- Viewport coordinates transformation (world <-> window) for Noita.
-- For it to work, you have to:
-- - Put Coords:ReadResolutions() inside of the OnMagicNumbersAndWorldSeedInitialized() hook.
--------------------------
-- Load library modules --
--------------------------
@ -19,9 +22,9 @@ local Vec2 = require("noita-api.vec2")
----------
---@class Coords
---@field InternalResolution Vec2
---@field WindowResolution Vec2
---@field VirtualResolution Vec2
---@field InternalResolution Vec2 -- Size of the internal rectangle in window pixels.
---@field WindowResolution Vec2 -- Size of the window client area in window pixels.
---@field VirtualResolution Vec2 -- Size of the virtual rectangle in world/virtual pixels.
local Coords = {
InternalResolution = Vec2(0, 0),
WindowResolution = Vec2(0, 0),

View File

@ -0,0 +1,19 @@
-- Copyright (c) 2019-2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
local ffi = require("ffi")
local MonitorStandby = {}
ffi.cdef([[
int SetThreadExecutionState(int esFlags);
]])
-- Reset computer and monitor standby timer
function MonitorStandby.ResetTimer()
ffi.C.SetThreadExecutionState(3) -- ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
end
return MonitorStandby

View File

@ -237,12 +237,14 @@ end
---Returns the squared distance of self to the given vector.
---@param v Vec2
---@return number
function Vec2:DistanceSqr(v)
return (v - self):LengthSqr()
end
---Returns the distance of self to the given vector.
---@param v Vec2
---@return number
function Vec2:Distance(v)
return (v - self):Length()
end
@ -272,6 +274,49 @@ function Vec2:EqualTo(v, tolerance)
return true
end
---Round returns v rounded by the given method.
---@param x number
---@param method "nearest"|"floor"|"ceil"|"to-zero"|"away-zero"|nil -- Defaults to "nearest".
---@return integer
local function round(x, method)
method = method or "nearest"
if method == "nearest" then
return math.floor(x + 0.5)
elseif method == "floor" then
return math.floor(x)
elseif method == "ceil" then
return math.ceil(x)
elseif method == "to-zero" then
if x >= 0 then
return math.floor(x)
else
return math.ceil(x)
end
elseif method == "away-zero" then
if x >= 0 then
return math.ceil(x)
else
return math.floor(x)
end
end
error(string.format("invalid rounding method %q", method))
end
---Round rounds all vector fields individually by the given rounding method.
---@param method "nearest"|"floor"|"ceil"|"to-zero"|"away-zero"|nil -- Defaults to "nearest".
function Vec2:Round(method)
self[1], self[2] = round(self[1], method), round(self[2], method)
end
---Round rounds all vector fields individually by the given rounding method.
---@param method "nearest"|"floor"|"ceil"|"to-zero"|"away-zero"|nil -- Defaults to "nearest".
---@return Vec2
function Vec2:Rounded(method)
return Vec2(round(self[1], method), round(self[2], method))
end
-------------------------
-- JSON Implementation --
-------------------------

View File

@ -0,0 +1,55 @@
-- Copyright (c) 2019-2022 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
local Vec2 = require("noita-api.vec2")
local ffi = require("ffi")
local ScreenCap = {}
local status, res = pcall(ffi.load, "mods/noita-mapcap/bin/capture-b/capture")
if not status then
print(string.format("Error loading capture lib: %s", res))
return
end
ffi.cdef([[
typedef long LONG;
typedef struct {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
bool GetRect(RECT* rect);
bool Capture(RECT* rect, int x, int y, int sx, int sy);
]])
---Takes a screenshot of the client area of this process' active window.
---@param topLeft Vec2 -- Screenshot rectangle's top left coordinate relative to the window's client area in screen pixels.
---@param bottomRight Vec2 -- Screenshot rectangle's bottom right coordinate relative to the window's client area in screen pixels. The pixel is not included in the screenshot area.
---@param topLeftWorld Vec2 -- The corresponding scaled world coordinates of the screenshot rectangles' top left corner.
---@param finalDimensions Vec2|nil -- The final dimensions that the screenshot will be resized to. If set to zero, no resize will happen.
---@return boolean
function ScreenCap.Capture(topLeft, bottomRight, topLeftWorld, finalDimensions)
finalDimensions = finalDimensions or Vec2(0, 0)
local rect = ffi.new("RECT", { math.floor(topLeft.x + 0.5), math.floor(topLeft.y + 0.5), math.floor(bottomRight.x + 0.5), math.floor(bottomRight.y + 0.5) })
return res.Capture(rect, math.floor(topLeftWorld.x + 0.5), math.floor(topLeftWorld.y + 0.5), math.floor(finalDimensions.x + 0.5), math.floor(finalDimensions.y + 0.5))
end
---Returns the client rectangle of the "Main" window of this process in screen coordinates.
---@return any
function ScreenCap.GetRect()
local rect = ffi.new("RECT")
if not res.GetRect(rect) then
return nil
end
return rect
end
return ScreenCap

View File

@ -0,0 +1,4 @@
<MagicNumbers
VIRTUAL_RESOLUTION_X="1024"
VIRTUAL_RESOLUTION_Y="1024"
></MagicNumbers>

View File

@ -0,0 +1,4 @@
<MagicNumbers
VIRTUAL_RESOLUTION_X="512"
VIRTUAL_RESOLUTION_Y="512"
></MagicNumbers>

View File

@ -0,0 +1,4 @@
<MagicNumbers
VIRTUAL_RESOLUTION_X="64"
VIRTUAL_RESOLUTION_Y="64"
></MagicNumbers>

View File

@ -0,0 +1,3 @@
<MagicNumbers
DEBUG_FREE_CAMERA_SPEED="1"
></MagicNumbers>

View File

@ -1,9 +1,5 @@
<MagicNumbers VIRTUAL_RESOLUTION_X="1280"
VIRTUAL_RESOLUTION_Y="720"
VIRTUAL_RESOLUTION_OFFSET_X="-2"
VIRTUAL_RESOLUTION_OFFSET_Y="0"
<MagicNumbers
DRAW_PARALLAX_BACKGROUND="0"
DEBUG_FREE_CAMERA_SPEED="10"
DEBUG_NO_LOGO_SPLASHES="1"
DEBUG_PAUSE_GRID_UPDATE="1"
DEBUG_PAUSE_BOX2D="1"
@ -16,5 +12,5 @@
UI_QUICKBAR_OFFSET_X="2000"
UI_QUICKBAR_OFFSET_Y="2000"
UI_BARS_POS_X="2000"
UI_BARS_POS_Y="2000">
</MagicNumbers>
UI_BARS_POS_Y="2000"
></MagicNumbers>

View File

@ -0,0 +1,4 @@
<MagicNumbers
VIRTUAL_RESOLUTION_OFFSET_X="-2"
VIRTUAL_RESOLUTION_OFFSET_Y="0"
></MagicNumbers>

View File

@ -8,6 +8,7 @@
--------------------------
local Utils = require("noita-api.utils")
local ScreenCap = require("screen-capture")
----------
-- Code --
@ -33,7 +34,7 @@ function DrawUI()
if not UiProgress then
-- Show informations
local problem
local rect = GetRect()
local rect = ScreenCap.GetRect()
if not rect then
GuiTextCentered(modGUI, 0, 0, '!!! WARNING !!! You are not using "Windowed" mode.')
@ -45,9 +46,7 @@ function DrawUI()
if rect then
local screenWidth, screenHeight = rect.right - rect.left, rect.bottom - rect.top
local virtualWidth, virtualHeight =
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")),
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
local virtualWidth, virtualHeight = tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
local ratioX, ratioY = screenWidth / virtualWidth, screenHeight / virtualHeight
--GuiTextCentered(modGUI, 0, 0, string.format("SCREEN_RESOLUTION_*: %d, %d", screenWidth, screenHeight))
--GuiTextCentered(modGUI, 0, 0, string.format("VIRTUAL_RESOLUTION_*: %d, %d", virtualWidth, virtualHeight))
@ -108,19 +107,14 @@ function DrawUI()
GuiTextCentered(modGUI, 0, 0, "You can freely look around and search a place to start capturing.")
GuiTextCentered(modGUI, 0, 0, "When started the mod will take pictures automatically.")
GuiTextCentered(modGUI, 0, 0, "Use ESC to pause, and close the game to stop the process.")
GuiTextCentered(
modGUI,
0,
0,
'You can resume capturing just by restarting noita and pressing "Start capturing map" again,'
)
GuiTextCentered(modGUI, 0, 0, 'You can resume capturing just by restarting noita and pressing "Start capturing map" again,')
GuiTextCentered(modGUI, 0, 0, "the mod will skip already captured files.")
GuiTextCentered(
modGUI,
0,
0,
'If you want to start a new map, you have to delete all images from the "output" folder!'
)
GuiTextCentered(modGUI, 0, 0, 'If you want to start a new map, you have to delete all images from the "output" folder!')
--GuiTextCentered(modGUI, 0, 0, " ")
--GuiTextCentered(modGUI, 0, 0, MagicNumbersGetValue("VIRTUAL_RESOLUTION_X"))
--GuiTextCentered(modGUI, 0, 0, MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
--GuiTextCentered(modGUI, 0, 0, MagicNumbersGetValue("VIRTUAL_RESOLUTION_OFFSET_X"))
--GuiTextCentered(modGUI, 0, 0, MagicNumbersGetValue("VIRTUAL_RESOLUTION_OFFSET_Y"))
GuiTextCentered(modGUI, 0, 0, " ")
if GuiButton(modGUI, 0, 0, ">> Start capturing map around view <<", 1) then
UiProgress = {}
@ -139,6 +133,10 @@ function DrawUI()
UiProgress = {}
startCapturingHilbert(CAPTURE_AREA_EXTENDED)
end
if GuiButton(modGUI, 0, 0, ">> Start capturing run live <<", 1) then
UiProgress = {}
StartCapturingLive()
end
GuiTextCentered(modGUI, 0, 0, " ")
elseif not UiProgress.Done then
-- Show progress
@ -146,15 +144,9 @@ function DrawUI()
GuiTextCentered(modGUI, 0, 0, string.format("Coordinates: %d, %d", x, y))
GuiTextCentered(modGUI, 0, 0, string.format("Waiting %d frames...", UiCaptureDelay))
if UiProgress.Progress then
GuiTextCentered(
modGUI,
0,
0,
progressBarString(
UiProgress,
{BarLength = 100, CharFull = "l", CharEmpty = ".", Format = "|%s| [%d / %d] [%1.2f%%]"}
)
)
GuiTextCentered(modGUI, 0, 0, progressBarString(
UiProgress, { BarLength = 100, CharFull = "l", CharEmpty = ".", Format = "|%s| [%d / %d] [%1.2f%%]" }
))
end
if UiCaptureProblem then
GuiTextCentered(modGUI, 0, 0, string.format("A problem occurred while capturing: %s", UiCaptureProblem))

View File

@ -21,12 +21,14 @@ end
--------------------------
local Coords = require("coordinates")
local CameraAPI = require("noita-api.camera")
local DebugAPI = require("noita-api.debug")
local Vec2 = require("noita-api.vec2")
-------------------------------
-- Load and run script files --
-------------------------------
dofile("mods/noita-mapcap/files/external.lua")
dofile("mods/noita-mapcap/files/capture.lua")
dofile("mods/noita-mapcap/files/ui.lua")
--dofile("mods/noita-mapcap/files/blablabla.lua")
@ -52,10 +54,24 @@ end
---@param playerEntityID integer
function OnPlayerSpawned(playerEntityID)
modGUI = GuiCreate()
GameSetCameraFree(true)
-- Start entity capturing right when the player spawn.
--DebugEntityCapture()
--[[async(function()
wait(0)
CameraAPI.SetCameraFree(true)
local origin = Vec2(512, -512)
CameraAPI.SetPos(origin)
DebugAPI.Mark(origin, "origin")
local tl, br = Coords:ValidRenderingRect()
local tlWorld, brWorld = Coords:ToWorld(tl), Coords:ToWorld(br)
DebugAPI.Mark(tlWorld, "tl")
DebugAPI.Mark(brWorld, "br")
end)]]
end
---Called when the player dies.
@ -110,7 +126,10 @@ end
---------------
-- Override virtual resolution and some other stuff.
ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic_numbers.xml")
--ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/1024.xml")
ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/fast-cam.xml")
--ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/no-ui.xml")
ModMagicNumbersFileAdd("mods/noita-mapcap/files/magic-numbers/offset.xml")
-- Remove hover animation of newly created perks.
ModLuaFileAppend("data/scripts/perks/perk.lua", "mods/noita-mapcap/files/overrides/perks/perk.lua")