mirror of
				https://github.com/Dadido3/noita-mapcap.git
				synced 2025-10-31 03:09:35 +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. | ||||
| -- 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_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 | ||||
| @ -84,18 +90,31 @@ local function captureScreenshot(x, y, rx, ry, entityFile) | ||||
| 
 | ||||
| 	-- 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 = EntityGetInRadius(x, y, radius) | ||||
| 		for _, entityID in ipairs(entities) do | ||||
| 			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 EntityHasTag(entityID, "MapCaptured") then | ||||
| 				local x, y, rotation, scaleX, scaleY = EntityGetTransform(entityID) | ||||
| 				local entityName = EntityGetName(entityID) | ||||
| 				local entityTags = EntityGetTags(entityID) | ||||
| 				entityFile:write(string.format("%d, %s, %f, %f, %f, %f, %f, %q\n", entityID, entityName, x, y, rotation, scaleX, scaleY, entityTags)) | ||||
| 				-- TODO: Correctly escape CSV data | ||||
| 				EntityAddTag(entityID, "MapCaptured") -- Prevent recapturing. | ||||
| 				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", 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) | ||||
| 		if not ok then | ||||
| 			print("Entity export error:", err) | ||||
| 		end | ||||
| 		entityFile:flush() -- Ensure everything is written to disk before noita decides to crash. | ||||
| 	end | ||||
| @ -105,15 +124,13 @@ local function captureScreenshot(x, y, rx, ry, entityFile) | ||||
| end | ||||
| 
 | ||||
| local function createOrOpenEntityCaptureFile() | ||||
| 	-- Create or reopen entities CSV file. | ||||
| 	local file = io.open("mods/noita-mapcap/output/entities.csv", "a+") | ||||
| 	if file == nil then return nil end | ||||
| 	-- Make sure the file exists. | ||||
| 	local file = io.open("mods/noita-mapcap/output/entities.csv", "a") | ||||
| 	if file ~= nil then file:close() end | ||||
| 
 | ||||
| 	if file:seek("end") == 0 then | ||||
| 		-- Empty file: Create header. | ||||
| 		file:write("entityID, entityName, x, y, rotation, scaleX, scaleY, tags\n") | ||||
| 		file:flush() | ||||
| 	end | ||||
| 	-- Create or reopen entities CSV file. | ||||
| 	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. | ||||
| 	if file == nil then return nil end | ||||
| 
 | ||||
| 	return file | ||||
| 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