diff --git a/AREAS.md b/AREAS.md index 2736376..eb27675 100644 --- a/AREAS.md +++ b/AREAS.md @@ -1,7 +1,7 @@ # Capture areas A list of available capture areas. -Coordinates are in in-game "virtual" pixels. +Coordinates are in in-game "virtual" or "world" pixels. `Right` and `Bottom` coordinates are not included in the rectangle. The dimensions of the capture rectangle are exactly: diff --git a/files/capture.lua b/files/capture.lua index a92d016..f31e2ec 100644 --- a/files/capture.lua +++ b/files/capture.lua @@ -1,34 +1,34 @@ --- 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 -CAPTURE_PIXEL_SIZE = 1 -- Screen to virtual pixel ratio -CAPTURE_GRID_SIZE = 512 -- in ingame pixels. There will always be exactly 4 images overlapping if the virtual resolution is 1024x1024 +CAPTURE_PIXEL_SIZE = 1 -- Screen to virtual pixel ratio. +CAPTURE_GRID_SIZE = 512 -- in virtual (world) pixels. There will always be exactly 4 images overlapping if the virtual resolution is 1024x1024. CAPTURE_FORCE_HP = 4 -- * 25HP -- "Base layout" (Base layout. Every part outside this is based on a similar layout, but uses different materials/seeds) CAPTURE_AREA_BASE_LAYOUT = { - Left = -17920, -- in ingame pixels. - Top = -7168, -- in ingame pixels. - Right = 17920, -- in ingame pixels. (Coordinate is not included in the rectangle) - Bottom = 17408 -- in ingame pixels. (Coordinate is not included in the rectangle) + Left = -17920, -- in virtual (world) pixels. + Top = -7168, -- in virtual (world) pixels. + Right = 17920, -- in virtual (world) pixels. (Coordinate is not included in the rectangle) + Bottom = 17408 -- in virtual (world) pixels. (Coordinate is not included in the rectangle) } -- "Main world" (The main world with 3 parts: sky, normal and hell) CAPTURE_AREA_MAIN_WORLD = { - Left = -17920, -- in ingame pixels. - Top = -31744, -- in ingame pixels. - Right = 17920, -- in ingame pixels. (Coordinate is not included in the rectangle) - Bottom = 41984 -- in ingame pixels. (Coordinate is not included in the rectangle) + Left = -17920, -- in virtual (world) pixels. + Top = -31744, -- in virtual (world) pixels. + Right = 17920, -- in virtual (world) pixels. (Coordinate is not included in the rectangle) + Bottom = 41984 -- in virtual (world) pixels. (Coordinate is not included in the rectangle) } -- "Extended" (Main world + a fraction of the parallel worlds to the left and right) CAPTURE_AREA_EXTENDED = { - Left = -25600, -- in ingame pixels. - Top = -31744, -- in ingame pixels. - Right = 25600, -- in ingame pixels. (Coordinate is not included in the rectangle) - Bottom = 41984 -- in ingame pixels. (Coordinate is not included in the rectangle) + Left = -25600, -- in virtual (world) pixels. + Top = -31744, -- in virtual (world) pixels. + Right = 25600, -- in virtual (world) pixels. (Coordinate is not included in the rectangle) + Bottom = 41984 -- in virtual (world) pixels. (Coordinate is not included in the rectangle) } local function preparePlayer() @@ -43,7 +43,15 @@ local function preparePlayer() setPlayerHP(CAPTURE_FORCE_HP) end -local function captureScreenshot(x, y, rx, ry) +--- 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. +--- @param entityFile file* +local function captureScreenshot(x, y, rx, ry, entityFile) local virtualWidth, virtualHeight = tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y")) @@ -56,7 +64,7 @@ local function captureScreenshot(x, y, rx, ry) GameSetCameraPos(x, y) repeat if UiCaptureDelay > 100 then - -- Wiggle the screen a bit, as chunks sometimes don't want to load + -- Wiggle the screen a bit, as chunks sometimes don't want to load. GameSetCameraPos(x + math.random(-100, 100), y + math.random(-100, 100)) DrawUI() wait(0) @@ -67,21 +75,33 @@ local function captureScreenshot(x, y, rx, ry) DrawUI() wait(0) UiCaptureDelay = UiCaptureDelay + 1 - until DoesWorldExistAt(xMin, yMin, xMax, yMax) -- Chunks will be drawn on the *next* frame + until DoesWorldExistAt(xMin, yMin, xMax, yMax) -- Chunks will be drawn on the *next* frame. - wait(0) -- Without this line empty chunks may still appear, also it's needed for the UI to disappear + 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 UiCaptureProblem = "Screen capture failed. Please restart Noita." end - -- Reset monitor and PC standby each screenshot + -- Capture entities right after capturing the screenshot. + if entityFile then + local radius = math.sqrt(virtualHalfWidth^2 + virtualHalfHeight^2) + 1 + local entities = EntityGetInRadius(x, y, radius) + for i, v in ipairs(entities) do + entityFile:write(tostring(v) .. "\n") + end + entityFile:flush() + end + + -- Reset monitor and PC standby each screenshot. ResetStandbyTimer() end function startCapturingSpiral() - local ox, oy = GameGetCameraPos() + --local entityFile = io.open("mods/noita-mapcap/output/entities.txt", "a+") + + local ox, oy = GameGetCameraPos() -- Returns the virtual coordinates of the screen center. ox, oy = math.floor(ox / CAPTURE_GRID_SIZE) * CAPTURE_GRID_SIZE, math.floor(oy / CAPTURE_GRID_SIZE) * CAPTURE_GRID_SIZE - ox, oy = ox + 256, oy + 256 -- Align screen with ingame chunk grid that is 512x512 + ox, oy = ox + 256, oy + 256 -- Align screen with ingame chunk grid that is 512x512. local x, y = ox, oy local virtualWidth, virtualHeight = @@ -94,13 +114,13 @@ function startCapturingSpiral() GameSetCameraFree(true) - -- Coroutine to calculate next coordinate, and trigger screenshots + -- Coroutine to calculate next coordinate, and trigger screenshots. local i = 1 async_loop( function() -- +x for i = 1, i, 1 do - local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight + local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then captureScreenshot(x, y, rx, ry) end @@ -108,7 +128,7 @@ function startCapturingSpiral() end -- +y for i = 1, i, 1 do - local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight + local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then captureScreenshot(x, y, rx, ry) end @@ -117,7 +137,7 @@ function startCapturingSpiral() i = i + 1 -- -x for i = 1, i, 1 do - local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight + local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then captureScreenshot(x, y, rx, ry) end @@ -125,7 +145,7 @@ function startCapturingSpiral() end -- -y for i = 1, i, 1 do - local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight + local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then captureScreenshot(x, y, rx, ry) end @@ -145,11 +165,11 @@ function startCapturingHilbert(area) local virtualHalfWidth, virtualHalfHeight = math.floor(virtualWidth / 2), math.floor(virtualHeight / 2) - -- Get size of the rectangle in grid/chunk coordinates + -- Get size of the rectangle in grid/chunk coordinates. local gridLeft = math.floor(area.Left / CAPTURE_GRID_SIZE) local gridTop = math.floor(area.Top / CAPTURE_GRID_SIZE) - local gridRight = math.ceil(area.Right / CAPTURE_GRID_SIZE) -- This grid coordinate is not included - local gridBottom = math.ceil(area.Bottom / CAPTURE_GRID_SIZE) -- This grid coordinate is not included + local gridRight = math.ceil(area.Right / CAPTURE_GRID_SIZE) -- This grid coordinate is not included. + local gridBottom = math.ceil(area.Bottom / CAPTURE_GRID_SIZE) -- This grid coordinate is not included. -- Edge case if area.Left == area.Right then @@ -159,13 +179,13 @@ function startCapturingHilbert(area) gridBottom = gridTop end - -- Size of the grid in chunks + -- Size of the grid in chunks. local gridWidth = gridRight - gridLeft local gridHeight = gridBottom - gridTop - -- Hilbert curve can only fit into a square, so get the longest side + -- Hilbert curve can only fit into a square, so get the longest side. local gridPOTSize = math.ceil(math.log(math.max(gridWidth, gridHeight)) / math.log(2)) - -- Max size (Already rounded up to the next power of two) + -- Max size (Already rounded up to the next power of two). local gridMaxSize = math.pow(2, gridPOTSize) local t, tLimit = 0, gridMaxSize * gridMaxSize @@ -176,15 +196,15 @@ function startCapturingHilbert(area) GameSetCameraFree(true) - -- Coroutine to calculate next coordinate, and trigger screenshots + -- Coroutine to calculate next coordinate, and trigger screenshots. async( function() while t < tLimit do local hx, hy = mapHilbert(t, gridPOTSize) if hx < gridWidth and hy < gridHeight then local x, y = (hx + gridLeft) * CAPTURE_GRID_SIZE, (hy + gridTop) * CAPTURE_GRID_SIZE - x, y = x + 256, y + 256 -- Align screen with ingame chunk grid that is 512x512 - local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight + 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 fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then captureScreenshot(x, y, rx, ry) end