diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1241f06..c41611d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -29,5 +29,6 @@
"xmin",
"ymax",
"ymin"
- ]
+ ],
+ "Lua.runtime.version": "LuaJIT"
}
\ No newline at end of file
diff --git a/data/entities/animals/worm.xml b/data/entities/animals/worm.xml
deleted file mode 100644
index b1445c9..0000000
--- a/data/entities/animals/worm.xml
+++ /dev/null
@@ -1,229 +0,0 @@
-
- <_Transform
- position.x="0"
- position.y="0"
- rotation="0"
- scale.x="1"
- scale.y="1" >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/data/entities/animals/worm_big.xml b/data/entities/animals/worm_big.xml
deleted file mode 100644
index f0d286e..0000000
--- a/data/entities/animals/worm_big.xml
+++ /dev/null
@@ -1,299 +0,0 @@
-
- <_Transform
- position.x="0"
- position.y="0"
- rotation="0"
- scale.x="1"
- scale.y="1" >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/data/entities/animals/worm_end.xml b/data/entities/animals/worm_end.xml
deleted file mode 100644
index fab0227..0000000
--- a/data/entities/animals/worm_end.xml
+++ /dev/null
@@ -1,317 +0,0 @@
-
- <_Transform
- position.x="0"
- position.y="0"
- rotation="0"
- scale.x="1"
- scale.y="1" >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/data/entities/animals/worm_skull.xml b/data/entities/animals/worm_skull.xml
deleted file mode 100644
index ec8c18c..0000000
--- a/data/entities/animals/worm_skull.xml
+++ /dev/null
@@ -1,330 +0,0 @@
-
- <_Transform
- position.x="0"
- position.y="0"
- rotation="0"
- scale.x="1"
- scale.y="1" >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/data/entities/animals/worm_tiny.xml b/data/entities/animals/worm_tiny.xml
deleted file mode 100644
index c7994f9..0000000
--- a/data/entities/animals/worm_tiny.xml
+++ /dev/null
@@ -1,248 +0,0 @@
-
- <_Transform
- position.x="0"
- position.y="0"
- rotation="0"
- scale.x="1"
- scale.y="1" >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/files/capture.lua b/files/capture.lua
index d07cc94..a9e5c44 100644
--- a/files/capture.lua
+++ b/files/capture.lua
@@ -11,7 +11,6 @@ local json = dofile_once("mods/noita-mapcap/files/json-serialize.lua")
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 = {
@@ -37,16 +36,108 @@ CAPTURE_AREA_EXTENDED = {
Bottom = 41984 -- in virtual (world) pixels. (Coordinate is not included in the rectangle)
}
-local function preparePlayer()
- local playerEntity = getPlayer()
- addEffectToEntity(playerEntity, "PROTECTION_ALL")
+local componentTypeNamesToDisable = {
+ "AnimalAIComponent",
+ "SimplePhysicsComponent",
+ "CharacterPlatformingComponent",
+ "WormComponent",
+ "WormAIComponent",
+ "DamageModelComponent",
+ "PhysicsBodyCollisionDamageComponent",
+ "ExplodeOnDamageComponent",
+ "SpriteOffsetAnimatorComponent",
+ --"PhysicsBody2Component", -- Disabling will hide barrels and similar stuff, also triggers an assertion.
+ --"PhysicsBodyComponent",
+ --"VelocityComponent", -- Disabling this component may cause a "...\component_updators\advancedfishai_system.cpp at line 107" exception.
+ --"SpriteComponent",
+ --"AudioComponent",
+}
- --addPerkToPlayer("BREATH_UNDERWATER")
- --addPerkToPlayer("INVISIBILITY")
- --addPerkToPlayer("REMOVE_FOG_OF_WAR")
- --addPerkToPlayer("REPELLING_CAPE")
- --addPerkToPlayer("WORM_DETRACTOR")
- setPlayerHP(CAPTURE_FORCE_HP)
+---
+---@return file*|nil
+local function createOrOpenEntityCaptureFile()
+ -- Make sure the file exists.
+ local file = io.open("mods/noita-mapcap/output/entities.json", "a")
+ if file ~= nil then file:close() end
+
+ -- Create or reopen entities CSV file.
+ file = io.open("mods/noita-mapcap/output/entities.json", "r+b") -- Open for reading (r) and writing (+) in binary mode. r+b will not truncate the file to 0.
+ if file == nil then return nil end
+
+ return file
+end
+
+---captureEntities gathers all entities on the screen (around x, y within radius), serializes them, appends them into entityFile and modifies those entities.
+---@param entityFile file*|nil
+---@param x number
+---@param y number
+---@param radius number
+local function captureEntities(entityFile, x, y, radius)
+ if not entityFile then return end
+
+ local entities = noitaAPI.Entity.GetInRadius(x, y, radius)
+ for _, entity in ipairs(entities) do
+ -- Get to the root entity, as we are exporting entire entity trees.
+ local rootEntity = entity:GetRootEntity()
+ -- Make sure to only export entities when they are encountered the first time.
+ if not rootEntity:HasTag("MapCaptured") then
+
+ -- Some hacky way to generate valid JSON that doesn't break when the game crashes.
+ -- Well, as long as it does not crash between write and flush.
+ if entityFile:seek("end") == 0 then
+ -- First line.
+ entityFile:write("[\n\t", json.Marshal(rootEntity), "\n", "]")
+ else
+ -- Following lines.
+ entityFile:seek("end", -2) -- Seek a few bytes back, so we can overwrite some stuff.
+ entityFile:write(",\n\t", json.Marshal(rootEntity), "\n", "]")
+ end
+
+ -- Prevent recapturing.
+ rootEntity:AddTag("MapCaptured")
+
+ -- Disable some components.
+ for _, componentTypeName in ipairs(componentTypeNamesToDisable) do
+ local components = rootEntity:GetComponents(componentTypeName)
+ for _, component in ipairs(components) do
+ rootEntity:SetComponentsEnabled(component, false)
+ end
+ end
+
+ -- Modify the gravity of every VelocityComponent, so stuff will not fall.
+ local component = rootEntity:GetFirstComponent("VelocityComponent")
+ if component then
+ component:SetValue("gravity_x", 0)
+ component:SetValue("gravity_y", 0)
+ end
+
+ -- Modify the gravity of every CharacterPlatformingComponent, so mobs will not fall.
+ local component = rootEntity:GetFirstComponent("CharacterPlatformingComponent")
+ if component then
+ component:SetValue("pixel_gravity", 0)
+ end
+
+ -- Disable the hover and spinning animations of every ItemComponent.
+ local component = rootEntity:GetFirstComponent("ItemComponent")
+ if component then
+ component:SetValue("play_hover_animation", false)
+ component:SetValue("play_spinning_animation", false)
+ end
+
+ -- Disable the hover animation of cards. Disabling the "SpriteOffsetAnimatorComponent" does not help.
+ local components = rootEntity:GetComponents("SpriteOffsetAnimatorComponent")
+ for _, component in ipairs(components) do
+ component:SetValue("x_speed", 0)
+ component:SetValue("y_speed", 0)
+ component:SetValue("x_amount", 0)
+ component:SetValue("y_amount", 0)
+ end
+
+ end
+ end
+
+ -- Ensure everything is written to disk before noita decides to crash.
+ entityFile:flush()
end
--- Captures a screenshot at the given coordinates.
@@ -81,60 +172,24 @@ local function captureScreenshot(x, y, rx, ry, entityFile)
DrawUI()
wait(0)
UiCaptureDelay = UiCaptureDelay + 1
- until DoesWorldExistAt(xMin, yMin, xMax, yMax) -- Chunks will be drawn on the *next* frame.
+
+ -- Capture all entities right after the camera frame was moved.
+ local ok, err = pcall(captureEntities, entityFile, x, y, 5000)
+ if not ok then
+ print(string.format("Entity capture error: %s", err))
+ end
+
+ until DoesWorldExistAt(xMin, yMin, xMax, yMax) and UiCaptureDelay > 25 -- 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.
if not TriggerCapture(rx, ry) then
UiCaptureProblem = "Screen capture failed. Please restart Noita."
end
- -- Capture entities right after capturing the screenshot.
- if entityFile then
- local ok, err = pcall(function()
- local radius = math.sqrt(virtualHalfWidth^2 + virtualHalfHeight^2) + 1
- local entities = noitaAPI.Entity.GetInRadius(x, y, radius)
- for _, entity in ipairs(entities) do
- -- Get to the root entity, as we are exporting entire entity trees.
- local rootEntity = entity:GetRootEntity()
- -- Make sure to only export entities when they are encountered the first time.
- if not rootEntity:HasTag("MapCaptured") then
- -- Some hacky way to generate valid JSON that doesn't break when the game crashes.
- -- Well, as long as it does not crash between write and flush.
- if entityFile:seek("end") == 0 then
- -- First line.
- entityFile:write("[\n\t", json.Marshal(rootEntity), "\n", "]")
- else
- -- Following lines.
- entityFile:seek("end", -2) -- Seek a few bytes back, so we can overwrite some stuff.
- entityFile:write(",\n\t", json.Marshal(rootEntity), "\n", "]")
- end
-
- rootEntity:AddTag("MapCaptured") -- Prevent recapturing.
- end
- end
- end)
- if not ok then
- print("Entity export error:", err)
- end
- entityFile:flush() -- Ensure everything is written to disk before noita decides to crash.
- end
-
-- Reset monitor and PC standby each screenshot.
ResetStandbyTimer()
end
-local function createOrOpenEntityCaptureFile()
- -- Make sure the file exists.
- local file = io.open("mods/noita-mapcap/output/entities.json", "a")
- if file ~= nil then file:close() end
-
- -- Create or reopen entities CSV file.
- file = io.open("mods/noita-mapcap/output/entities.json", "r+b") -- Open for reading (r) and writing (+) in binary mode. r+b will not truncate the file to 0.
- if file == nil then return nil end
-
- return file
-end
-
function startCapturingSpiral()
local entityFile = createOrOpenEntityCaptureFile()
@@ -149,8 +204,6 @@ function startCapturingSpiral()
local virtualHalfWidth, virtualHalfHeight = math.floor(virtualWidth / 2), math.floor(virtualHeight / 2)
- preparePlayer()
-
GameSetCameraFree(true)
-- Coroutine to calculate next coordinate, and trigger screenshots.
@@ -233,8 +286,6 @@ function startCapturingHilbert(area)
UiProgress = {Progress = 0, Max = gridWidth * gridHeight}
- preparePlayer()
-
GameSetCameraFree(true)
-- Coroutine to calculate next coordinate, and trigger screenshots.
diff --git a/files/compatibility.lua b/files/compatibility.lua
index e01e025..d6dd006 100644
--- a/files/compatibility.lua
+++ b/files/compatibility.lua
@@ -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
@@ -9,8 +9,7 @@
local oldPrint = print
function print(...)
local arg = {...}
-
- stringArgs = {}
+ local stringArgs = {}
for i, v in ipairs(arg) do
table.insert(stringArgs, tostring(v))
@@ -23,8 +22,7 @@ end
--[[local logFile = io.open("lualog.txt", "w")
function print(...)
local arg = {...}
-
- stringArgs = {}
+ local stringArgs = {}
local result = ""
for i, v in ipairs(arg) do
diff --git a/files/noita-api.lua b/files/noita-api.lua
index 328bfb1..32ae5a8 100644
--- a/files/noita-api.lua
+++ b/files/noita-api.lua
@@ -48,7 +48,15 @@ function NoitaComponent:MarshalJSON()
if membersTable then
for k, v in pairs(membersTable) do
if not componentValueKeysWithInvalidType[k] then
- members[k] = self:GetValue(k) -- Try to get value with correct type. Assuming nil is an error, but this is not always the case... meh.
+ local packedResult = table.pack(self:GetValue(k)) -- Try to get value with correct type. Assuming nil is an error, but this is not always the case... meh.
+ if packedResult.n == 0 then
+ members[k] = nil -- Write no result as nil. Basically do nothing.
+ elseif packedResult.n == 1 then
+ members[k] = packedResult[1] -- Write single value result as single value.
+ else
+ packedResult.n = nil -- Discard n field, otherwise this is not a pure array.
+ members[k] = packedResult -- Write multi value result as array.
+ end
end
if members[k] == nil then
componentValueKeysWithInvalidType[k] = true
@@ -187,10 +195,15 @@ end
---Returns a table of components filtered by the given parameters.
---@param componentTypeName string
----@param tag string
+---@param tag string|nil
---@return NoitaComponent[]
function NoitaEntity:GetComponents(componentTypeName, tag)
- local componentIDs = EntityGetComponent(self.ID, componentTypeName, tag) or {}
+ local componentIDs
+ if tag ~= nil then
+ componentIDs = EntityGetComponent(self.ID, componentTypeName, tag) or {}
+ else
+ componentIDs = EntityGetComponent(self.ID, componentTypeName) or {}
+ end
local result = {}
for _, componentID in ipairs(componentIDs) do
table.insert(result, setmetatable({ ID = componentID }, NoitaComponent))
@@ -200,10 +213,15 @@ end
---Returns the first component of this entity that fits the given parameters.
---@param componentTypeName string
----@param tag string
+---@param tag string|nil
---@return NoitaComponent|nil
function NoitaEntity:GetFirstComponent(componentTypeName, tag)
- local componentID = EntityGetFirstComponent(self.ID, componentTypeName, tag)
+ local componentID
+ if tag ~= nil then
+ componentID = EntityGetFirstComponent(self.ID, componentTypeName, tag)
+ else
+ componentID = EntityGetFirstComponent(self.ID, componentTypeName)
+ end
if componentID == nil then
return nil
end
@@ -426,15 +444,15 @@ end
---@param fieldName string
---@return any|nil
function NoitaComponent:GetValue(fieldName)
- return ComponentGetValue2(self.ID, fieldName)
+ return ComponentGetValue2(self.ID, fieldName) -- TODO: Rework Noita API to handle vectors, and return a vector instead of some shitty multi value result
end
---Sets the value of a field. Value(s) should have a type matching the field type.
---Reports error if the values weren't given in correct type, the field type is not supported, or the component does not exist.
---@param fieldName string
----@param valueOrValues any|nil
-function NoitaComponent:SetValue(fieldName, valueOrValues)
- return ComponentSetValue2(self.ID, fieldName, valueOrValues)
+---@param ... any|nil -- Vectors use one argument per dimension.
+function NoitaComponent:SetValue(fieldName, ...)
+ return ComponentSetValue2(self.ID, fieldName, ...) -- TODO: Rework Noita API to handle vectors, and use a vector instead of shitty multi value arguments
end
---Returns one or many values matching the type or subtypes of the requested field in a component subobject.
@@ -445,16 +463,16 @@ end
---@param fieldName string
---@return any|nil
function NoitaComponent:ObjectGetValue(objectName, fieldName)
- return ComponentObjectGetValue2(self.ID, objectName, fieldName)
+ return ComponentObjectGetValue2(self.ID, objectName, fieldName) -- TODO: Rework Noita API to handle vectors, and return a vector instead of some shitty multi value result
end
---Sets the value of a field in a component subobject. Value(s) should have a type matching the field type.
---Reports error if the values weren't given in correct type, the field type is not supported or 'object_name' is not a metaobject.
---@param objectName string
---@param fieldName string
----@param valueOrValues any
-function NoitaComponent:ObjectSetValue(objectName, fieldName, valueOrValues)
- return ComponentObjectSetValue2(self.ID, objectName, fieldName, valueOrValues)
+---@param ... any|nil -- Vectors use one argument per dimension.
+function NoitaComponent:ObjectSetValue(objectName, fieldName, ...)
+ return ComponentObjectSetValue2(self.ID, objectName, fieldName, ...) -- TODO: Rework Noita API to handle vectors, and use a vector instead of shitty multi value arguments
end
---Creates a component of type 'component_type_name' and adds it to 'entity_id'.
@@ -473,26 +491,26 @@ function NoitaEntity:EntityAddComponent(componentTypeName, tableOfComponentValue
return setmetatable({ ID = componentID }, NoitaComponent)
end
----'type_stored_in_vector' should be "int", "float" or "string".
+---
---@param arrayMemberName string
----@param typeStoredInVector string
+---@param typeStoredInVector "int"|"float"|"string"
---@return number
function NoitaComponent:GetVectorSize(arrayMemberName, typeStoredInVector)
return ComponentGetVectorSize(self.ID, arrayMemberName, typeStoredInVector)
end
----'type_stored_in_vector' should be "int", "float" or "string".
+---
---@param arrayName string
----@param typeStoredInVector string
+---@param typeStoredInVector "int"|"float"|"string"
---@param index number
---@return number|number|string|nil
function NoitaComponent:GetVectorValue(arrayName, typeStoredInVector, index)
return ComponentGetVectorValue(self.ID, arrayName, typeStoredInVector, index)
end
----'type_stored_in_vector' should be "int", "float" or "string".
+---
---@param arrayName string
----@param typeStoredInVector string
+---@param typeStoredInVector "int"|"float"|"string"
---@return number[]|number|string|nil
function NoitaComponent:GetVector(arrayName, typeStoredInVector)
return ComponentGetVector(self.ID, arrayName, typeStoredInVector)
diff --git a/files/util.lua b/files/util.lua
index 939c7b9..709ee95 100644
--- a/files/util.lua
+++ b/files/util.lua
@@ -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
@@ -23,7 +23,7 @@ function GamePrint(...)
end
for line in result:gmatch("[^\r\n]+") do
- for i, v in ipairs(splitStringByLength(line, 100)) do
+ for i, v in ipairs(SplitStringByLength(line, 100)) do
oldGamePrint(v)
end
end
@@ -105,3 +105,13 @@ function progressBarString(progress, look)
return string.format(look.Format, barString, progress.Progress, progress.Max, factor * 100)
end
+
+---Returns a new table with all arguments stored into keys `1`, `2`, etc. and with a field `"n"` with the total number of arguments.
+---@param ... any
+---@return table
+function table.pack(...)
+ t = {...}
+ t.n = select("#", ...)
+
+ return t
+end