From 0126e706cb9a1fc1e692825c1a97022a08713df1 Mon Sep 17 00:00:00 2001 From: David Vogel Date: Sun, 24 Jul 2022 22:05:34 +0200 Subject: [PATCH] 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 --- bin/capture-b/Capture.pb | 51 ++++-- bin/capture-b/capture.dll | Bin 210944 -> 214016 bytes files/capture.lua | 152 +++++++++++++----- files/external.lua | 44 ----- files/libraries/coordinates.lua | 9 +- files/libraries/monitor-standby.lua | 19 +++ files/libraries/noita-api/vec2.lua | 45 ++++++ files/libraries/screen-capture.lua | 55 +++++++ files/magic-numbers/1024.xml | 4 + files/magic-numbers/512.xml | 4 + files/magic-numbers/64.xml | 4 + files/magic-numbers/fast-cam.xml | 3 + .../no-ui.xml} | 10 +- files/magic-numbers/offset.xml | 4 + files/ui.lua | 42 ++--- init.lua | 25 ++- 16 files changed, 331 insertions(+), 140 deletions(-) delete mode 100644 files/external.lua create mode 100644 files/libraries/monitor-standby.lua create mode 100644 files/libraries/screen-capture.lua create mode 100644 files/magic-numbers/1024.xml create mode 100644 files/magic-numbers/512.xml create mode 100644 files/magic-numbers/64.xml create mode 100644 files/magic-numbers/fast-cam.xml rename files/{magic_numbers.xml => magic-numbers/no-ui.xml} (66%) create mode 100644 files/magic-numbers/offset.xml diff --git a/bin/capture-b/Capture.pb b/bin/capture-b/Capture.pb index 5342226..d60f7f8 100644 --- a/bin/capture-b/Capture.pb +++ b/bin/capture-b/Capture.pb @@ -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,8 +80,14 @@ 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 @@ -98,13 +110,19 @@ ProcedureDLL Capture(px.i, py.i) If Not GetRect(@rect) ProcedureReturn #False EndIf + + ; 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, rect\right-rect\left, rect\bottom-rect\top) + 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) \ No newline at end of file +; Compiler = PureBasic 6.00 LTS (Windows - x86) \ No newline at end of file diff --git a/bin/capture-b/capture.dll b/bin/capture-b/capture.dll index f61c5bac0ee87c996461c029b1090cd359694829..ac14685d5313cd04f23861f6aa6bee7ea5f9c69b 100644 GIT binary patch delta 35900 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�KI!F_QEH;a8C_$X8@N*+@2% z`cy9v@I|VUdP!NcC@Zjz>=-tHoybmRXL8Fq8{S1+DqfWCNYACW(iiEcWGHu)d&>Rg zAy7S89xnUIDmhtJ%S+{S`MP{tRzHxR%dcd_;g=e>MXgb9bOx$MFQa$Rhfw1tv?JCF z+ll33m$B>|jKX-VE4~81 zgID4t!GJUl6Q_xb#2pw9)x>+^Gr=-0%uXhkp+N#8*eh%)`)f& z$RUmq7l;=`FVdD|Ay+Tb2XYRCfg1+nD~gPR5tdA5KuNmH4mFd*{LK_G)yymA6Z4%h zWqX6hQmo86vp&$X!E6LOi(SrcW%sa0*>h|?`?%DMz{}4Uh-R)8+m0VfmDNUM`j!ox~yH1aXFVKvWU6 zL^F{@&L($(p}J32k~qauv#I&iQL2!tqw1+oa{)wK0kuU)}fL#T3+)P-2(HY0z z=4&J*OB+zZlQGZ}(BfE3gYCtJ;1BQ~gdY(|%qA|7E2(n+EpI3c7RCx{;e=2R7N)N_ zUYaCDOC|CftpxMoV!#L1ZP8AsDcT2jBPW1$Uqj}SMbIJz7+QZSmP(^?sS2urvSb8C zhuhAz<%jV5ghHWGXb>z#QFIqaiMzm9U6np)Gc7_82pf?OXg9PEIuJ!sN7NH-hh4++ zaXq3Vv4UJjW`YMfN}gg$8FeL7%hWSgtP&J%B>;ia6+{5Fh1#tKcTorq<2LmeY}!Yv1%_Q`+L$(_&1nnTingW) z(Os}O{N$_VH!bRKpL3&eX8 zY2-=j4dqThqGz%T+12bu{tmxOI4>B25h6u1NqtBvle7cZ8cy6vJR{0T3u-M@LOloN z387Qz3i=y8PB~E-r4=pseC=_E&<|i0$K!E$J0gWRK=`piY&;Cpkz7Avf#@qE@PAIN z{@`TLo3i~w3&3J1bQg#N|(^;Qo51$WGa|JY!i2!N4#{jQ3X-} zmGQ#%fDkLOx7bIl1=GX3!7~PsU>(E=RxR4?xL?}`2-r^1L5yKV5k>r6ez5pfs~f9- z<+TRwO-Mupkw#pn=QBSUYjLY+C<`)@rGrGM;kd3K0TJ~@FQ5&m1jFbn7L0Gh&*C`{ z{g{yqd5=_4GeCe1R1m$FInPW6dsx7pVXv}2Tqf8y6Mh~4k#FKTVUjRMm?`Ff`?io? zOSRUjScAj8OV*GEU^-aZ zAAVcwpd6(vRJLX8nPLbj`mnWZG*`*hafN($VLXiKD?+p2A&wI>Aqp5TBSkuh)}I_f zZ=jDLylaFp;g3h-I>bx*J>5y!U1`L0Wv+2LLO;P-ST7`r3)LWu7RgNRC%Z!EkSJ$> zUn=@#97=VNBam4;h!%%n1SVj^FgGwWTe00(9&}7S_6VC`;<~UQr=&&j_ zh1Iax>@n6FoJs^YgImFE28W`x26dc2znOoLTV>30C##; zE`vw{snS8rq0$NnMXgZ*;)o6CZuAmb3TlVc>u3WaBtQ%nlf+qIL*9#@)nc=#CmBkA zNM?}bK*_E(;Bt|?v=bqZyNx}>o?&&E z4&EN$%x~urcmzXdIOiOG1rH=53B*em355M^_#XT`eia6+Ibll-CEUQPB!Zb%FCbP! zsC|~u3hWEfkF+PpkV#}Jxt9D!cA)IRofJ^#!7w#ZB+b(S{8VtGpQJ8wFReWY)@{}L zOSqDK#Pt_g;f;vI=^_i@tLYFIRnl+hCR$%9f}@$nErht?7*g>Ns|3b-H%ZV*H*7BoU8SgzMnF9Z0R(nc=o>KE z7^Y@0S8ODfh?!9Rsjjq+a-MR7@|m((d0McLTqGZ92N)j6X|-;vQ&|G1>tfxo?L-05 z0pgjR^i-xNyOl%uU?EJ15Tb-Q2rH6>*+Po20IIxPSS@4-8-y(oWn>FELY|N>91#k@ zc@+s4gks@Zh)^or6&?r`LX}W0yc9CULn4x;+iGSj$>*dM=&wJuQ+ZT*S@}@;gZqm= z#j}E|P$M)7aFy4331&ta@dLmk9)%1`1*mM%FCDNZSi*(SOm 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) end + if ensureLoaded then + repeat + if UiCaptureDelay > 100 then + -- Wiggle the screen a bit, as chunks sometimes don't want to load. + if pos then CameraAPI.SetPos(pos + Vec2(math.random(-100, 100), math.random(-100, 100))) end + DrawUI() + wait(0) + UiCaptureDelay = UiCaptureDelay + 1 + if pos then CameraAPI.SetPos(pos) end + end + DrawUI() wait(0) UiCaptureDelay = UiCaptureDelay + 1 - GameSetCameraPos(x, y) - 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 diff --git a/files/external.lua b/files/external.lua deleted file mode 100644 index d1de6cc..0000000 --- a/files/external.lua +++ /dev/null @@ -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 diff --git a/files/libraries/coordinates.lua b/files/libraries/coordinates.lua index 159c810..776bd8c 100644 --- a/files/libraries/coordinates.lua +++ b/files/libraries/coordinates.lua @@ -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), diff --git a/files/libraries/monitor-standby.lua b/files/libraries/monitor-standby.lua new file mode 100644 index 0000000..2d30a54 --- /dev/null +++ b/files/libraries/monitor-standby.lua @@ -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 diff --git a/files/libraries/noita-api/vec2.lua b/files/libraries/noita-api/vec2.lua index 364be2a..5afd8b3 100644 --- a/files/libraries/noita-api/vec2.lua +++ b/files/libraries/noita-api/vec2.lua @@ -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 -- ------------------------- diff --git a/files/libraries/screen-capture.lua b/files/libraries/screen-capture.lua new file mode 100644 index 0000000..13cbf45 --- /dev/null +++ b/files/libraries/screen-capture.lua @@ -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 diff --git a/files/magic-numbers/1024.xml b/files/magic-numbers/1024.xml new file mode 100644 index 0000000..d167e62 --- /dev/null +++ b/files/magic-numbers/1024.xml @@ -0,0 +1,4 @@ + diff --git a/files/magic-numbers/512.xml b/files/magic-numbers/512.xml new file mode 100644 index 0000000..453c872 --- /dev/null +++ b/files/magic-numbers/512.xml @@ -0,0 +1,4 @@ + diff --git a/files/magic-numbers/64.xml b/files/magic-numbers/64.xml new file mode 100644 index 0000000..740b25c --- /dev/null +++ b/files/magic-numbers/64.xml @@ -0,0 +1,4 @@ + diff --git a/files/magic-numbers/fast-cam.xml b/files/magic-numbers/fast-cam.xml new file mode 100644 index 0000000..df607df --- /dev/null +++ b/files/magic-numbers/fast-cam.xml @@ -0,0 +1,3 @@ + diff --git a/files/magic_numbers.xml b/files/magic-numbers/no-ui.xml similarity index 66% rename from files/magic_numbers.xml rename to files/magic-numbers/no-ui.xml index 8cfa992..d190a93 100644 --- a/files/magic_numbers.xml +++ b/files/magic-numbers/no-ui.xml @@ -1,9 +1,5 @@ - - + UI_BARS_POS_Y="2000" +> diff --git a/files/magic-numbers/offset.xml b/files/magic-numbers/offset.xml new file mode 100644 index 0000000..1a97429 --- /dev/null +++ b/files/magic-numbers/offset.xml @@ -0,0 +1,4 @@ + diff --git a/files/ui.lua b/files/ui.lua index 4cfba07..b330031 100644 --- a/files/ui.lua +++ b/files/ui.lua @@ -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)) diff --git a/init.lua b/init.lua index 8317619..bca34dc 100644 --- a/init.lua +++ b/init.lua @@ -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")