mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-11-18 17:17:31 +00:00
Serialize entities to JSON
- Add JSON library that can marshal Noita entities and components - Add Noita API wrapper that exposes entities and components as objects - Change how the entities file is written, to support lightweight and crash proof appending of JSON data #9
This commit is contained in:
parent
861272187a
commit
833ab41eeb
@ -3,6 +3,12 @@
|
|||||||
-- This software is released under the MIT License.
|
-- This software is released under the MIT License.
|
||||||
-- https://opensource.org/licenses/MIT
|
-- https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
---@type NoitaAPI
|
||||||
|
local noitaAPI = dofile_once("mods/noita-mapcap/files/noita-api.lua")
|
||||||
|
|
||||||
|
---@type JSONLib
|
||||||
|
local jsonSerialize = dofile_once("mods/noita-mapcap/files/json-serialize.lua")
|
||||||
|
|
||||||
CAPTURE_PIXEL_SIZE = 1 -- Screen to virtual pixel ratio.
|
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_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
|
CAPTURE_FORCE_HP = 4 -- * 25HP
|
||||||
@ -84,18 +90,31 @@ local function captureScreenshot(x, y, rx, ry, entityFile)
|
|||||||
|
|
||||||
-- Capture entities right after capturing the screenshot.
|
-- Capture entities right after capturing the screenshot.
|
||||||
if entityFile then
|
if entityFile then
|
||||||
local radius = math.sqrt(virtualHalfWidth^2 + virtualHalfHeight^2) + 1
|
local ok, err = pcall(function()
|
||||||
local entities = EntityGetInRadius(x, y, radius)
|
local radius = math.sqrt(virtualHalfWidth^2 + virtualHalfHeight^2) + 1
|
||||||
for _, entityID in ipairs(entities) do
|
local entities = noitaAPI.Entity.GetInRadius(x, y, radius)
|
||||||
-- Make sure to only export entities when they are encountered the first time.
|
for _, entity in ipairs(entities) do
|
||||||
if not EntityHasTag(entityID, "MapCaptured") then
|
-- Get to the root entity, as we are exporting entire entity trees.
|
||||||
local x, y, rotation, scaleX, scaleY = EntityGetTransform(entityID)
|
local rootEntity = entity:GetRootEntity()
|
||||||
local entityName = EntityGetName(entityID)
|
-- Make sure to only export entities when they are encountered the first time.
|
||||||
local entityTags = EntityGetTags(entityID)
|
if not rootEntity:HasTag("MapCaptured") then
|
||||||
entityFile:write(string.format("%d, %s, %f, %f, %f, %f, %f, %q\n", entityID, entityName, x, y, rotation, scaleX, scaleY, entityTags))
|
-- Some hacky way to generate valid JSON that doesn't break when the game crashes.
|
||||||
-- TODO: Correctly escape CSV data
|
-- Well, as long as it does not crash between write and flush.
|
||||||
EntityAddTag(entityID, "MapCaptured") -- Prevent recapturing.
|
if entityFile:seek("end") == 0 then
|
||||||
|
-- First line.
|
||||||
|
entityFile:write("[\n\t", jsonSerialize.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", jsonSerialize.Marshal(rootEntity), "\n", "]")
|
||||||
|
end
|
||||||
|
|
||||||
|
rootEntity:AddTag("MapCaptured") -- Prevent recapturing.
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
|
if not ok then
|
||||||
|
print("Entity export error:", err)
|
||||||
end
|
end
|
||||||
entityFile:flush() -- Ensure everything is written to disk before noita decides to crash.
|
entityFile:flush() -- Ensure everything is written to disk before noita decides to crash.
|
||||||
end
|
end
|
||||||
@ -105,15 +124,13 @@ local function captureScreenshot(x, y, rx, ry, entityFile)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function createOrOpenEntityCaptureFile()
|
local function createOrOpenEntityCaptureFile()
|
||||||
-- Create or reopen entities CSV file.
|
-- Make sure the file exists.
|
||||||
local file = io.open("mods/noita-mapcap/output/entities.csv", "a+")
|
local file = io.open("mods/noita-mapcap/output/entities.csv", "a")
|
||||||
if file == nil then return nil end
|
if file ~= nil then file:close() end
|
||||||
|
|
||||||
if file:seek("end") == 0 then
|
-- Create or reopen entities CSV file.
|
||||||
-- Empty file: Create header.
|
file = io.open("mods/noita-mapcap/output/entities.csv", "r+b") -- Open for reading (r) and writing (+) in binary mode. r+b will not truncate the file to 0.
|
||||||
file:write("entityID, entityName, x, y, rotation, scaleX, scaleY, tags\n")
|
if file == nil then return nil end
|
||||||
file:flush()
|
|
||||||
end
|
|
||||||
|
|
||||||
return file
|
return file
|
||||||
end
|
end
|
||||||
|
207
files/json-serialize.lua
Normal file
207
files/json-serialize.lua
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
-- Copyright (c) 2022 David Vogel
|
||||||
|
--
|
||||||
|
-- This software is released under the MIT License.
|
||||||
|
-- https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
-- Simple library to marshal JSON values.
|
||||||
|
|
||||||
|
---@type NoitaAPI
|
||||||
|
local noitaAPI = dofile_once("mods/noita-mapcap/files/noita-api.lua")
|
||||||
|
|
||||||
|
---@class JSONLib
|
||||||
|
local lib = {}
|
||||||
|
|
||||||
|
---Maps single characters to escaped strings.
|
||||||
|
---
|
||||||
|
---Copyright (c) 2020 rxi
|
||||||
|
---@see [github.com/rxi/json.lua](https://github.com/rxi/json.lua/blob/master/json.lua)
|
||||||
|
local escapeCharacters = {
|
||||||
|
["\\"] = "\\",
|
||||||
|
["\""] = "\"",
|
||||||
|
["\b"] = "b",
|
||||||
|
["\f"] = "f",
|
||||||
|
["\n"] = "n",
|
||||||
|
["\r"] = "r",
|
||||||
|
["\t"] = "t",
|
||||||
|
}
|
||||||
|
|
||||||
|
---escapeRune returns the escaped string for a given rune.
|
||||||
|
---
|
||||||
|
---Copyright (c) 2020 rxi
|
||||||
|
---@see [github.com/rxi/json.lua](https://github.com/rxi/json.lua/blob/master/json.lua)
|
||||||
|
---@param rune string
|
||||||
|
---@return string
|
||||||
|
local function escapeCharacter(rune)
|
||||||
|
return "\\" .. (escapeCharacters[rune] or string.format("u%04x", rune:byte()))
|
||||||
|
end
|
||||||
|
|
||||||
|
---escapeString returns the escaped version of the given string.
|
||||||
|
---
|
||||||
|
---Copyright (c) 2020 rxi
|
||||||
|
---@see [github.com/rxi/json.lua](https://github.com/rxi/json.lua/blob/master/json.lua)
|
||||||
|
---@param str string
|
||||||
|
---@return string
|
||||||
|
local function escapeString(str)
|
||||||
|
local result, count = str:gsub('[%z\1-\31\\"]', escapeCharacter)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---MarshalString returns the JSON representation of a string value.
|
||||||
|
---@param val string
|
||||||
|
---@return string
|
||||||
|
function lib.MarshalString(val)
|
||||||
|
return string.format("%q", escapeString(val)) -- TODO: Escape strings correctly.
|
||||||
|
end
|
||||||
|
|
||||||
|
---MarshalNumber returns the JSON representation of a number value.
|
||||||
|
---@param val number
|
||||||
|
---@return string
|
||||||
|
function lib.MarshalNumber(val)
|
||||||
|
-- TODO: Marshal NaN, +Inf, -Inf, ... correctly
|
||||||
|
|
||||||
|
return tostring(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
---MarshalBoolean returns the JSON representation of a boolean value.
|
||||||
|
---@param val number
|
||||||
|
---@return string
|
||||||
|
function lib.MarshalBoolean(val)
|
||||||
|
return tostring(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
---MarshalObject returns the JSON representation of a table object.
|
||||||
|
---
|
||||||
|
---This only works with string keys. Number keys will be converted into strings.
|
||||||
|
---@param val table<string,any>
|
||||||
|
---@return string
|
||||||
|
function lib.MarshalObject(val)
|
||||||
|
local result = "{"
|
||||||
|
|
||||||
|
for k, v in pairs(val) do
|
||||||
|
result = result .. lib.MarshalString(k) .. ": " .. lib.Marshal(v)
|
||||||
|
-- Append character depending on whether this is the last element or not.
|
||||||
|
if next(val, k) == nil then
|
||||||
|
result = result .. "}"
|
||||||
|
else
|
||||||
|
result = result .. ", "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---MarshalArray returns the JSON representation of an array object.
|
||||||
|
---
|
||||||
|
---@param val table<number,any>
|
||||||
|
---@param customMarshalFunction function|nil -- Custom function for marshalling the array values.
|
||||||
|
---@return string
|
||||||
|
function lib.MarshalArray(val, customMarshalFunction)
|
||||||
|
local result = "["
|
||||||
|
|
||||||
|
-- TODO: Check if the type of all array entries is the same.
|
||||||
|
|
||||||
|
local length = #val
|
||||||
|
for i, v in ipairs(val) do
|
||||||
|
if customMarshalFunction then
|
||||||
|
result = result .. customMarshalFunction(v)
|
||||||
|
else
|
||||||
|
result = result .. lib.Marshal(v)
|
||||||
|
end
|
||||||
|
-- Append character depending on whether this is the last element or not.
|
||||||
|
if i == length then
|
||||||
|
result = result .. "]"
|
||||||
|
else
|
||||||
|
result = result .. ", "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---MarshalNoitaComponent returns the JSON representation of the given Noita component.
|
||||||
|
---@param component NoitaComponent
|
||||||
|
---@return string
|
||||||
|
function lib.MarshalNoitaComponent(component)
|
||||||
|
local resultObject = {
|
||||||
|
typeName = component:GetTypeName(),
|
||||||
|
members = component:GetMembers(),
|
||||||
|
--objectMembers = component:ObjectGetMembers
|
||||||
|
}
|
||||||
|
|
||||||
|
return lib.Marshal(resultObject)
|
||||||
|
end
|
||||||
|
|
||||||
|
---MarshalNoitaEntity returns the JSON representation of the given Noita entity.
|
||||||
|
---@param entity NoitaEntity
|
||||||
|
---@return string
|
||||||
|
function lib.MarshalNoitaEntity(entity)
|
||||||
|
local result = {
|
||||||
|
name = entity:GetName(),
|
||||||
|
filename = entity:GetFilename(),
|
||||||
|
tags = entity:GetTags(),
|
||||||
|
children = entity:GetAllChildren(),
|
||||||
|
components = entity:GetAllComponents(),
|
||||||
|
transform = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
result.transform.x, result.transform.y, result.transform.rotation, result.transform.scaleX, result.transform.scaleY = entity:GetTransform()
|
||||||
|
|
||||||
|
return lib.Marshal(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Marshal marshals any value into JSON representation.
|
||||||
|
---@param val any
|
||||||
|
---@return string
|
||||||
|
function lib.Marshal(val)
|
||||||
|
local t = type(val)
|
||||||
|
|
||||||
|
if t == "nil" then
|
||||||
|
return "null"
|
||||||
|
elseif t == "number" then
|
||||||
|
return lib.MarshalNumber(val)
|
||||||
|
elseif t == "string" then
|
||||||
|
return lib.MarshalString(val)
|
||||||
|
elseif t == "boolean" then
|
||||||
|
return lib.MarshalBoolean(val)
|
||||||
|
elseif t == "table" then
|
||||||
|
-- Check if object is instance of class...
|
||||||
|
if getmetatable(val) == noitaAPI.MetaTables.Component then
|
||||||
|
return lib.MarshalNoitaComponent(val)
|
||||||
|
elseif getmetatable(val) == noitaAPI.MetaTables.Entity then
|
||||||
|
return lib.MarshalNoitaEntity(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If not, fall back to array or object handling.
|
||||||
|
local commonKeyType, commonValueType
|
||||||
|
for k, v in pairs(val) do
|
||||||
|
local keyType, valueType = type(k), type(v)
|
||||||
|
commonKeyType = commonKeyType or keyType
|
||||||
|
if commonKeyType ~= keyType then
|
||||||
|
-- Different types detected, abort.
|
||||||
|
commonKeyType = "mixed"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
commonValueType = commonValueType or valueType
|
||||||
|
if commonValueType ~= valueType then
|
||||||
|
-- Different types detected, abort.
|
||||||
|
commonValueType = "mixed"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Decide based on common types.
|
||||||
|
if commonKeyType == "number" and commonValueType ~= "mixed" then
|
||||||
|
return lib.MarshalArray(val) -- This will falsely detect sparse integer key maps as arrays. But meh.
|
||||||
|
elseif commonKeyType == "string" then
|
||||||
|
return lib.MarshalObject(val) -- This will not detect if there are number keys, which would work with MarshalObject.
|
||||||
|
elseif commonKeyType == nil and commonValueType == nil then
|
||||||
|
return "null" -- Fallback in case of empty table. There is no other way than using null, as we don't have type information without table elements.
|
||||||
|
end
|
||||||
|
|
||||||
|
error(string.format("unsupported table type. CommonKeyType = %s. CommonValueType = %s. MetaTable = %s", commonKeyType or "nil", commonValueType or "nil", getmetatable(val) or "nil"))
|
||||||
|
end
|
||||||
|
|
||||||
|
error(string.format("unsupported type %q", t))
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib
|
473
files/noita-api.lua
Normal file
473
files/noita-api.lua
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
-- Copyright (c) 2022 David Vogel
|
||||||
|
--
|
||||||
|
-- This software is released under the MIT License.
|
||||||
|
-- https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
-- Noita modding API, but a bit more beautiful.
|
||||||
|
-- Current modding API version: 7
|
||||||
|
|
||||||
|
-- State: Working but incomplete. If something is missing, add it by hand!
|
||||||
|
-- It would be optimal to generate this API wrapper automatically...
|
||||||
|
|
||||||
|
local EntityAPI = {}
|
||||||
|
|
||||||
|
---@class NoitaEntity
|
||||||
|
---@field ID integer -- Noita entity ID.
|
||||||
|
local NoitaEntity = {}
|
||||||
|
NoitaEntity.__index = NoitaEntity
|
||||||
|
|
||||||
|
local ComponentAPI = {}
|
||||||
|
|
||||||
|
---@class NoitaComponent
|
||||||
|
---@field ID integer -- Noita component ID.
|
||||||
|
local NoitaComponent = {}
|
||||||
|
NoitaComponent.__index = NoitaComponent
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param filename string
|
||||||
|
---@param posX number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@param posY number -- Y coordinate in world (virtual) pixels.
|
||||||
|
---@return NoitaEntity|nil
|
||||||
|
function EntityAPI.Load(filename, posX, posY)
|
||||||
|
local entityID = EntityLoad(filename, posX, posY)
|
||||||
|
if entityID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = entityID }, NoitaEntity)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param filename string
|
||||||
|
---@param posX number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@param posY number -- Y coordinate in world (virtual) pixels.
|
||||||
|
---@return NoitaEntity|nil
|
||||||
|
function EntityAPI.LoadEndGameItem(filename, posX, posY)
|
||||||
|
local entityID = EntityLoadEndGameItem(filename, posX, posY)
|
||||||
|
if entityID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = entityID }, NoitaEntity)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param filename string
|
||||||
|
---@param posX number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@param posY number -- Y coordinate in world (virtual) pixels.
|
||||||
|
function EntityAPI.LoadCameraBound(filename, posX, posY)
|
||||||
|
return EntityLoadCameraBound(filename, posX, posY)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param filename string
|
||||||
|
---@param entity NoitaEntity
|
||||||
|
function EntityAPI.LoadToEntity(filename, entity)
|
||||||
|
return EntityLoadToEntity(filename, entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---Note: works only in dev builds.
|
||||||
|
---@param filename string
|
||||||
|
function NoitaEntity:Save(filename)
|
||||||
|
return EntitySave(self.ID, filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param name string
|
||||||
|
---@return NoitaEntity|nil
|
||||||
|
function EntityAPI.CreateNew(name)
|
||||||
|
local entityID = EntityCreateNew(name)
|
||||||
|
if entityID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = entityID }, NoitaEntity)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
function NoitaEntity:Kill()
|
||||||
|
return EntityKill(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
function NoitaEntity:IsAlive()
|
||||||
|
return EntityGetIsAlive(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param componentTypeName string
|
||||||
|
---@param tableOfComponentValues string[]|nil
|
||||||
|
---@return NoitaComponent|nil
|
||||||
|
function NoitaEntity:AddComponent(componentTypeName, tableOfComponentValues)
|
||||||
|
local componentID = EntityAddComponent(self.ID, componentTypeName, tableOfComponentValues)
|
||||||
|
if componentID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = componentID }, NoitaComponent)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param component NoitaComponent
|
||||||
|
function NoitaEntity:RemoveComponent(component)
|
||||||
|
return EntityRemoveComponent(self.ID, component.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns a table of with all components of this entity.
|
||||||
|
---@return NoitaComponent[]
|
||||||
|
function NoitaEntity:GetAllComponents()
|
||||||
|
local componentIDs = EntityGetAllComponents(self.ID) or {}
|
||||||
|
local result = {}
|
||||||
|
for _, componentID in ipairs(componentIDs) do
|
||||||
|
table.insert(result, setmetatable({ ID = componentID }, NoitaComponent))
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns a table of components filtered by the given parameters.
|
||||||
|
---@param componentTypeName string
|
||||||
|
---@param tag string
|
||||||
|
---@return NoitaComponent[]
|
||||||
|
function NoitaEntity:GetComponents(componentTypeName, tag)
|
||||||
|
local componentIDs = EntityGetComponent(self.ID, componentTypeName, tag) or {}
|
||||||
|
local result = {}
|
||||||
|
for _, componentID in ipairs(componentIDs) do
|
||||||
|
table.insert(result, setmetatable({ ID = componentID }, NoitaComponent))
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns the first component of this entity that fits the given parameters.
|
||||||
|
---@param componentTypeName string
|
||||||
|
---@param tag string
|
||||||
|
---@return NoitaComponent|nil
|
||||||
|
function NoitaEntity:GetFirstComponent(componentTypeName, tag)
|
||||||
|
local componentID = EntityGetFirstComponent(self.ID, componentTypeName, tag)
|
||||||
|
if componentID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = componentID }, NoitaComponent)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Sets the transform of the entity.
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param rotation number
|
||||||
|
---@param scaleX number
|
||||||
|
---@param scaleY number
|
||||||
|
function NoitaEntity:SetTransform(x, y, rotation, scaleX, scaleY)
|
||||||
|
return EntitySetTransform(self.ID, x, y, rotation, scaleX, scaleY)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Sets the transform and tries to immediately refresh components that calculate values based on an entity's transform.
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param rotation number
|
||||||
|
---@param scaleX number
|
||||||
|
---@param scaleY number
|
||||||
|
function NoitaEntity:SetAndApplyTransform(x, y, rotation, scaleX, scaleY)
|
||||||
|
return EntityApplyTransform(self.ID, x, y, rotation, scaleX, scaleY)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns the transformation of the entity.
|
||||||
|
---@return number x, number y, number rotation, number scaleX, number scaleY
|
||||||
|
function NoitaEntity:GetTransform()
|
||||||
|
return EntityGetTransform(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param child NoitaEntity
|
||||||
|
function NoitaEntity:AddChild(child)
|
||||||
|
return EntityAddChild(self.ID, child.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@return NoitaEntity[]
|
||||||
|
function NoitaEntity:GetAllChildren()
|
||||||
|
local entityIDs = EntityGetAllChildren(self.ID) or {}
|
||||||
|
local result = {}
|
||||||
|
for _, entityID in ipairs(entityIDs) do
|
||||||
|
table.insert(result, setmetatable({ ID = entityID }, NoitaEntity))
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@return NoitaEntity|nil
|
||||||
|
function NoitaEntity:GetParent()
|
||||||
|
local entityID = EntityGetParent(self.ID)
|
||||||
|
if entityID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = entityID }, NoitaEntity)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns the given entity if it has no parent, otherwise walks up the parent hierarchy to the topmost parent and returns it.
|
||||||
|
---@return NoitaEntity
|
||||||
|
function NoitaEntity:GetRootEntity()
|
||||||
|
local entityID = EntityGetRootEntity(self.ID)
|
||||||
|
return setmetatable({ ID = entityID }, NoitaEntity)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
function NoitaEntity:RemoveFromParent()
|
||||||
|
return EntityRemoveFromParent(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param tag string
|
||||||
|
---@param enabled boolean
|
||||||
|
function NoitaEntity:SetComponentsWithTagEnabled(tag, enabled)
|
||||||
|
return EntitySetComponentsWithTagEnabled(self.ID, tag, enabled)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param component NoitaComponent
|
||||||
|
---@param enabled boolean
|
||||||
|
function NoitaEntity:SetComponentsEnabled(component, enabled)
|
||||||
|
return EntitySetComponentIsEnabled(self.ID, component.ID, enabled)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@return string
|
||||||
|
function NoitaEntity:GetName()
|
||||||
|
return EntityGetName(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param name string
|
||||||
|
function NoitaEntity:SetName(name)
|
||||||
|
return EntitySetName(self.ID, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns an array of all the entity's tags.
|
||||||
|
---@return string[]
|
||||||
|
function NoitaEntity:GetTags()
|
||||||
|
---@type string
|
||||||
|
local tagsString = EntityGetTags(self.ID)
|
||||||
|
local result = {}
|
||||||
|
for tag in tagsString:gmatch('([^,]+)') do
|
||||||
|
table.insert(result, tag)
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns all entities with 'tag'.
|
||||||
|
---@param tag string
|
||||||
|
---@return NoitaEntity[]
|
||||||
|
function EntityAPI.GetWithTag(tag)
|
||||||
|
local entityIDs = EntityGetWithTag(tag) or {}
|
||||||
|
local result = {}
|
||||||
|
for _, entityID in ipairs(entityIDs) do
|
||||||
|
table.insert(result, setmetatable({ ID = entityID }, NoitaEntity))
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns all entities in 'radius' distance from 'x','y'.
|
||||||
|
---@param posX number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@param posY number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@param radius number -- Radius in world (virtual) pixels.
|
||||||
|
---@return NoitaEntity[]
|
||||||
|
function EntityAPI.GetInRadius(posX, posY, radius)
|
||||||
|
local entityIDs = EntityGetInRadius(posX, posY, radius) or {}
|
||||||
|
local result = {}
|
||||||
|
for _, entityID in ipairs(entityIDs) do
|
||||||
|
table.insert(result, setmetatable({ ID = entityID }, NoitaEntity))
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns all entities in 'radius' distance from 'x','y' that have the given tag.
|
||||||
|
---@param posX number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@param posY number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@param radius number -- Radius in world (virtual) pixels.
|
||||||
|
---@param tag string
|
||||||
|
---@return NoitaEntity[]
|
||||||
|
function EntityAPI.GetInRadiusWithTag(posX, posY, radius, tag)
|
||||||
|
local entityIDs = EntityGetInRadiusWithTag(posX, posY, radius, tag) or {}
|
||||||
|
local result = {}
|
||||||
|
for _, entityID in ipairs(entityIDs) do
|
||||||
|
table.insert(result, setmetatable({ ID = entityID }, NoitaEntity))
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param posX number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@param posY number -- X coordinate in world (virtual) pixels.
|
||||||
|
---@return NoitaEntity|nil
|
||||||
|
function EntityAPI.GetClosest(posX, posY)
|
||||||
|
local entityID = EntityGetClosest(posX, posY)
|
||||||
|
if entityID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = entityID }, NoitaEntity)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param name string
|
||||||
|
---@return NoitaEntity|nil
|
||||||
|
function EntityAPI.GetWithName(name)
|
||||||
|
local entityID = EntityGetWithName(name)
|
||||||
|
if entityID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = entityID }, NoitaEntity)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param tag string
|
||||||
|
function NoitaEntity:AddTag(tag)
|
||||||
|
return EntityAddTag(self.ID, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param tag string
|
||||||
|
function NoitaEntity:RemoveTag(tag)
|
||||||
|
return EntityRemoveTag(self.ID, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param tag string
|
||||||
|
---@return boolean
|
||||||
|
function NoitaEntity:HasTag(tag)
|
||||||
|
return EntityHasTag(self.ID, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@return string -- example: 'data/entities/items/flute.xml'.
|
||||||
|
function NoitaEntity:GetFilename()
|
||||||
|
return EntityGetFilename(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param tag string
|
||||||
|
function NoitaComponent:AddTag(tag)
|
||||||
|
return ComponentAddTag(self.ID, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param tag string
|
||||||
|
function NoitaComponent:RemoveTag(tag)
|
||||||
|
return ComponentRemoveTag(self.ID, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
---@param tag string
|
||||||
|
---@return boolean
|
||||||
|
function NoitaComponent:HasTag(tag)
|
||||||
|
return ComponentHasTag(self.ID, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns one or many values matching the type or subtypes of the requested field.
|
||||||
|
---Reports error and returns nil if the field type is not supported or field was not found.
|
||||||
|
---@param fieldName string
|
||||||
|
---@return any|nil
|
||||||
|
function NoitaComponent:GetValue(fieldName)
|
||||||
|
return ComponentGetValue2(self.ID, fieldName)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns one or many values matching the type or subtypes of the requested field in a component subobject.
|
||||||
|
---Reports error and returns nil if the field type is not supported or 'object_name' is not a metaobject.
|
||||||
|
---@param objectName string
|
||||||
|
---@param fieldName string
|
||||||
|
---@return any
|
||||||
|
function NoitaComponent:ObjectGetValue(objectName, fieldName)
|
||||||
|
return ComponentObjectGetValue2(self.ID, objectName, fieldName)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Creates a component of type 'component_type_name' and adds it to 'entity_id'.
|
||||||
|
---'table_of_component_values' should be a string-indexed table, where keys are field names and values are field values of correct type.
|
||||||
|
---The value setting works like ComponentObjectSetValue2(), with the exception that multivalue types are not supported.
|
||||||
|
---Additional supported values are _tags:comma_separated_string and _enabled:bool, which basically work like the those fields work in entity XML files.
|
||||||
|
---Returns the created component, if creation succeeded, or nil.
|
||||||
|
---@param componentTypeName string
|
||||||
|
---@param tableOfComponentValues table<string, any>
|
||||||
|
---@return NoitaComponent|nil
|
||||||
|
function NoitaEntity:EntityAddComponent(componentTypeName, tableOfComponentValues)
|
||||||
|
local componentID = EntityAddComponent2(self.ID, componentTypeName, tableOfComponentValues)
|
||||||
|
if componentID == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return setmetatable({ ID = componentID }, NoitaComponent)
|
||||||
|
end
|
||||||
|
|
||||||
|
---'type_stored_in_vector' should be "int", "float" or "string".
|
||||||
|
---@param arrayMemberName string
|
||||||
|
---@param typeStoredInVector 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 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
|
||||||
|
---@return number[]|number|string|nil
|
||||||
|
function NoitaComponent:GetVector(arrayName, typeStoredInVector)
|
||||||
|
return ComponentGetVector(self.ID, arrayName, typeStoredInVector)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns true if the given component exists and is enabled, else false.
|
||||||
|
---@return boolean
|
||||||
|
function NoitaComponent:GetIsEnabled()
|
||||||
|
return ComponentGetIsEnabled(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns a string-indexed table of string.
|
||||||
|
---@return table<string, string>|nil
|
||||||
|
function NoitaComponent:GetMembers()
|
||||||
|
return ComponentGetMembers(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Returns a string-indexed table of string or nil.
|
||||||
|
---@param objectName string
|
||||||
|
---@return table<string, string>|nil
|
||||||
|
function NoitaComponent:ObjectGetMembers(objectName)
|
||||||
|
return ComponentObjectGetMembers(self.ID, objectName)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return string string
|
||||||
|
function NoitaComponent:GetTypeName()
|
||||||
|
return ComponentGetTypeName(self.ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: Add missing Noita API methods and functions.
|
||||||
|
|
||||||
|
---@class NoitaAPI
|
||||||
|
local api = {
|
||||||
|
Component = ComponentAPI,
|
||||||
|
Entity = EntityAPI,
|
||||||
|
MetaTables = {
|
||||||
|
Component = NoitaComponent,
|
||||||
|
Entity = NoitaEntity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return api
|
Loading…
Reference in New Issue
Block a user