mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-12-22 02:17:33 +00:00
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:
parent
f2e582622e
commit
0126e706cb
@ -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)
|
||||
; Compiler = PureBasic 6.00 LTS (Windows - x86)
|
Binary file not shown.
@ -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)
|
||||
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) 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
|
||||
|
@ -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
|
@ -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),
|
||||
|
19
files/libraries/monitor-standby.lua
Normal file
19
files/libraries/monitor-standby.lua
Normal 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
|
@ -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 --
|
||||
-------------------------
|
||||
|
55
files/libraries/screen-capture.lua
Normal file
55
files/libraries/screen-capture.lua
Normal 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
|
4
files/magic-numbers/1024.xml
Normal file
4
files/magic-numbers/1024.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<MagicNumbers
|
||||
VIRTUAL_RESOLUTION_X="1024"
|
||||
VIRTUAL_RESOLUTION_Y="1024"
|
||||
></MagicNumbers>
|
4
files/magic-numbers/512.xml
Normal file
4
files/magic-numbers/512.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<MagicNumbers
|
||||
VIRTUAL_RESOLUTION_X="512"
|
||||
VIRTUAL_RESOLUTION_Y="512"
|
||||
></MagicNumbers>
|
4
files/magic-numbers/64.xml
Normal file
4
files/magic-numbers/64.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<MagicNumbers
|
||||
VIRTUAL_RESOLUTION_X="64"
|
||||
VIRTUAL_RESOLUTION_Y="64"
|
||||
></MagicNumbers>
|
3
files/magic-numbers/fast-cam.xml
Normal file
3
files/magic-numbers/fast-cam.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<MagicNumbers
|
||||
DEBUG_FREE_CAMERA_SPEED="1"
|
||||
></MagicNumbers>
|
@ -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>
|
4
files/magic-numbers/offset.xml
Normal file
4
files/magic-numbers/offset.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<MagicNumbers
|
||||
VIRTUAL_RESOLUTION_OFFSET_X="-2"
|
||||
VIRTUAL_RESOLUTION_OFFSET_Y="0"
|
||||
></MagicNumbers>
|
42
files/ui.lua
42
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))
|
||||
|
25
init.lua
25
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")
|
||||
|
Loading…
Reference in New Issue
Block a user