From 107fddc7fc786e99803c1d57ebfb03e7140ab732 Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Sun, 17 May 2026 04:07:19 +0100 Subject: [PATCH 01/16] "Set creature mood" "SetCreatureLocation" node and helper function --- .../TEN Node Catalogs/Creature Mood.lua | 32 ++++ .../Catalogs/TEN Node Catalogs/Readme.md | 161 ------------------ .../Catalogs/TEN Node Catalogs/_System.lua | 15 +- 3 files changed, 45 insertions(+), 163 deletions(-) create mode 100644 TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua delete mode 100644 TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua new file mode 100644 index 0000000000..c6a3ee57d2 --- /dev/null +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -0,0 +1,32 @@ +-- !Name "Set intelligent creature location" +-- !Section "Creature Mood" +-- !Description "Sets the location of intelligent enemies to a specified location.\nOnly to be used with GUIDE, Sophia-Leigh or Von Croy.\nPlace AI_X1 objects with an OCB to create a location." +-- !Arguments "Newline, WadSlots, 70, [ GUIDE | SOPHIA_LEIGH | VON_CROY ], Creature to set location for." +-- !Arguments "Numerical, 30, [ 0 | 1000 ], Location to set. +LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location) + local moveables = TEN.Objects.GetMoveablesBySlot(objectId) + + for _, moveable in pairs(moveables) do + if moveable:GetStatus() == Objects.MoveableStatus.ACTIVE then + moveable:SetLocationAI(location) + print("Location set for " .. moveable:GetName() .. " to location: " .. location .. ".") + end + + if moveable:GetStatus() == Objects.MoveableStatus.INACTIVE then + print("Warning: " .. moveable:GetName() .. " is inactive. Location not set.") + end + end +end + +-- !Name "Set creature mood" +-- !Section "Creature Mood" +-- !Description "Set creature mood" +-- !Arguments "Newline, Moveables, 50, Moveable to set mood for." +-- !Arguments "Enumeration, 50, [ Attack | Auto | Bored | Escape | Stalk ], Mood to set for creature." +LevelFuncs.Engine.Node.SetCreatureMood = function(moveable, index) + + local mood = LevelFuncs.Engine.Node.GetCreatureMood(index) + local mov = TEN.Objects.GetMoveableByName(moveable) + local movAI = Objects.Creature(mov) + movAI:SetMood(mood) +end \ No newline at end of file diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md deleted file mode 100644 index a20dcacdf6..0000000000 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md +++ /dev/null @@ -1,161 +0,0 @@ -## TEN Node function script files metadata reference - -### Overview - -This folder contains lua script files which will be referenced by both TE and TEN to work with node trigger system. -TE uses metadata from comments in these files to build UI, while TEN uses actual functions to execute node scripts. - -On level compile, all files from this directory are copied to TEN `Scripts\NodeCatalogs` folder and executed -on level start-up, given that level contains any volumes. Therefore, NodeFunctions.lua file in TEN -`Scripts\NodeCatalogs` folder should never be modified. - -### Format - -Lua node scripts should follow this convention: several metadata entries should be followed by actual function -signature which should start with conventional **LevelFuncs.Engine.Node.** prefix. Function signature should be -followed by function body in arbitrary format. Amount of argument metadata entries should be the same as actual -function arguments, and should be listed in the same order. - -### Argument naming and order - -Actual function arguments (e.g. `arg1`, `arg2` and `arg3` in `LevelFuncs.Engine.Node.MyFunction(arg1, arg2, arg3)`) -should not be renamed after node went into production with official TE release - otherwise TE will not be able to -identify arguments on project file reloading. However, since TE version 1.8, it is allowed to rearrange node -arguments, if argument names were left untouched. You still need to preserve metadata entry order to correspond to -function order though. - -Builders must be advised that they should migrate to version 1.8 as soon as possible, because skipping version -1.8, together with possible node argument reorderings in the future, may lead to data loss in existing node setups. - -### Metadata entry reference - -Comment metadata entry reference (metadata block is indicated by a keyword which starts with !): - - - **!Name "NAME"** - NAME will be visible name for this function in node editor. - - - **!Conditional "True"** - this is a condition node, otherwise if "False", it's action - (or if **!Conditional** is not specified). - - - **!Description "DESC"** - DESC will be a tooltip for a given function. You can use `\n` symbol to begin from a - new line. - - - **!Section "SECTION"** - this will define where the node will be found inside Tomb Editor. - - - **!Arguments "ARGDESC1" "ARGDESC2" "ARGDESC..."** - infinite amount of args, with **ARGDESC** parameters - separated by commas as follows: - - - **0-100** - numerical value specifying width of control in percent of node width. For whole-line controls, - this can be omitted. - - - **NewLine** - keyword which specifies if this and following arguments should appear on new UI line, until - another argument with another **NewLine** is encountered. - - - **Boolean, Numerical, Vector3, String, Color, LuaScript, Moveables, Statics, Cameras, Sinks, FlybyCameras, - Volumes, Rooms, SoundEffects, WadSlots, Enumeration, CompareOperator** - keywords which specify argument type - and its appearance in UI. - - - **{DEFAULT}** - default value for this argument, contained in brackets. For Numerical value type, it can be - provided as is, e.g. `{100}`. For String value type, default string must be quoted, e.g. `{"Default string"}`. - Complex value types, such as Color or Vector3, should use TEN Lua API notation, e.g. `TEN.Color(255,255,255)`. - - - **[ENUMDESC1 | ENUMDESC2 | ENUMDESC...]** - custom enumeration descriptors for this argument, as follows: - - - For Numerical and Vector3 value types, first and second ENUMDESC values determine min/max UI range of - value. Optional third argument specifies amount of decimal places (for integer, set it to 0). Optional - fourth and fifth values determine mousewheel increment and alternate increment (with shift button - pressed) respectively. - - For String value type, specifying **NoMultiline** as ENUMDESC hides button which engages multiline editing. - - For Moveables and WadSlots lists, ENUMDESC values will filter out object ID names which contain any of - ENUMDESCs only. - - For Enumeration, ENUMDESC values will be displayed as is but converted to numericals on compilation - in order of appearance. - - - **Any other string value except listed above** - tooltip for a given argument control. - - - **!Ignore** - if this keyword is used, nearest encountered function declaration will be ignored. Useful if you - need to place helper functions which must be ignored by parser (however, it is recommended to use `_System.lua` - file for those). - - -Metadata blocks can appear in any order. - -Metadata parsing happens until real function block starts (which should start with **LevelFuncs.Engine.Node.** -prefix). - -Conditional node functions (those with **!Conditional = "True"** specified) must return boolean value, otherwise -their behaviour is undefined. - -ARGDESC parameters can appear in any order, e.g. `!Argument "Foo, Numerical, 20"` is equal to -`!Argument "Numerical, 20, Foo"`. - -There could be several **!Argument** blocks, which will append arguments to previously parsed ones. - -There could be several **!Description** blocks, which will append as a new line to previous description block. - -ENUMDESC parameters should NOT be quoted, or else parsing will fail miserably. - - -### Argument types (those you specify under **!Arguments** keyword) - - - **Boolean** - a value which can be either `true` or `false`. Appears as a checkbox with argument description - as a label. - - **Numerical** - numerical floating-point value. Range, limits and decimals can be specified by **ENUMDESC** - descriptors (see above). - - **Vector2** - Two floating-point values, can be used for 2D on-screen position. Range, limits and - decimals can be specified by **ENUMDESC** descriptors (see above). - - **Vector3** - Three floating-point values, can be used either for position or rotation. Range, limits and - decimals can be specified by **ENUMDESC** descriptors (see above). - - **String** - Raw text string, length is unlimited. If parsed to functions which draw on-screen strings, `\n` - combination may be used to start a new line. - - **Time** - Time value, consists of 4 fields - hours, minutes, seconds and centiseconds. - - **Color** - RGB color value, appears as color picker in UI. - - **LuaScript** - Existing lua function list from level script file. - - **EventSets** - Existing event sets currently present in level. - - **Moveables** - A list of moveables in level which have lua names assigned. - - **Statics** - A list of statics in level which have lua names assigned. - - **Cameras** - Same as above, but cameras. - - **Sinks** - Same as above, but sinks. - - **FlybyCameras** - Same as above, but flybys. - - **Volumes** - Same as above, but volumes. - - **Rooms** - A list of existing rooms in level. Accessed by room name specified in TE UI. - - **SoundEffects** - A list of sound effects. Internally converted to numerical effect slot ID. - - **WadSlots** - A list of object slots which exist in all loaded wads. Internally accessed by numerical ID and/or - `Objects.ObjID.` lua enumeration which is identical to TE/TEN object slot enumeration. - - **SpriteSlots** - Similar to previous type, but for sprite sequence slots. - - **Enumeration** - Custom enumeration determined by **ENUMDESC** descriptors. Internally these descriptors are - converted to numerical index. - - **CompareOperator** - Comparison operator enumeration, ranging from equal to various less-or-equal and more-or-equal - operators. Internally converted to numerical value and should be passed to `LevelFuncs.CompareValue` helper function - along with operand and reference to check against, like this: `LevelFuncs.CompareValue(operand, reference, operator)`. - -### Example - -``` --- !Name "Check moveable health" --- !Description "Compares selected moveable health with given value." --- !Conditional "True" --- !Arguments "NewLine, Moveables, Moveable to check" "NewLine, CompareOperator, 70, Kind of check" --- !Arguments "Numerical, 30, [ 0 | 1000 | 0 ], Hit points value" - -LevelFuncs.CheckEntityHealth = function(moveableName, operator, value) - local health = TEN.Objects.GetMoveableByName(entityName):GetHP() - return LevelFuncs.CompareValue(health, value, operator) -end -``` - -Here, **!Arguments** entry lists 3 parameters, which will be called "Moveable to check", "Kind of check" -and "Hit points value" respectively. First argument, "Moveable to check", will occupy whole second line of a -node UI, because width is not explicitly stated for it. Next two arguments, "Kind of check" and "Hit points -value", both will occupy same next line, because **NewLine** keyword is not specified for third argument. -Second argument, "Kind of check", will occupy 70 percent of line width, while "Hit points value" will occupy -30 percent. Note that arguments sitting on the same line should sum to 100 percent width, otherwise -UI symmetry is not guaranteed. - -Also note that "Hit points value" argument is placed on separate **!Arguments** block, and this is correct, -since it will appear after previous argument block. Also, being numerical argument, it will allow user to -only define values between 0 and 1000. All other values will be clamped. Decimal places for a value won't -show, as third parameter in square brackets is set to 0. - -**LevelFuncs.CheckEntityHealth** function declaration should contain same amount of arguments and in the same -order as metadata argument entry. Therefore, **moveableName** will be read from "Moveable to check" UI argument, -**operator** will be read from "Kind of check", and so on. diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/_System.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/_System.lua index 372f9d8ac6..04cab6efce 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/_System.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/_System.lua @@ -205,7 +205,18 @@ LevelFuncs.Engine.Node.SetInteractionHighlightType = function(index) [0] = TEN.Objects.InteractionType.PICKUP, [1] = TEN.Objects.InteractionType.TALK, [2] = TEN.Objects.InteractionType.USE, - } + } return interactionIconType[index] end - \ No newline at end of file + +LevelFuncs.Engine.Node.GetCreatureMood = function(index) + local creatureMood = + { + [0] = TEN.Objects.MoodType.ATTACK, + [1] = TEN.Objects.MoodType.AUTO, + [2] = TEN.Objects.MoodType.BORED, + [3] = TEN.Objects.MoodType.ESCAPE, + [4] = TEN.Objects.MoodType.STALK, + } + return creatureMood[index] +end From f84fdbaed7a690f5c65d921557841fce3818432f Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Sun, 17 May 2026 11:16:22 +0100 Subject: [PATCH 02/16] Re-work "Set intelligent creature location" node --- .../TEN Node Catalogs/Creature Mood.lua | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index c6a3ee57d2..577bbb998a 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -1,20 +1,21 @@ -- !Name "Set intelligent creature location" -- !Section "Creature Mood" -- !Description "Sets the location of intelligent enemies to a specified location.\nOnly to be used with GUIDE, Sophia-Leigh or Von Croy.\nPlace AI_X1 objects with an OCB to create a location." --- !Arguments "Newline, WadSlots, 70, [ GUIDE | SOPHIA_LEIGH | VON_CROY ], Creature to set location for." --- !Arguments "Numerical, 30, [ 0 | 1000 ], Location to set. -LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location) - local moveables = TEN.Objects.GetMoveablesBySlot(objectId) - - for _, moveable in pairs(moveables) do - if moveable:GetStatus() == Objects.MoveableStatus.ACTIVE then - moveable:SetLocationAI(location) - print("Location set for " .. moveable:GetName() .. " to location: " .. location .. ".") - end +-- !Arguments "Newline, Moveables, 60, [ guide | sophia_leigh | von_croy ], Creature to set location for." +-- !Arguments "Numerical, 20, [ 0 | 1000 ], Location to set. +-- !Arguments "Boolean, 20, {false}, Debug to console." +LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location,debug) + local moveables = TEN.Objects.GetMoveableByName(objectId) + if moveables:GetStatus() == 1 then + moveables:SetLocationAI(location) + end - if moveable:GetStatus() == Objects.MoveableStatus.INACTIVE then - print("Warning: " .. moveable:GetName() .. " is inactive. Location not set.") - end + if moveables:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. objectId .. " ] is not active. No location set.",TEN.Util.LogLevel.ERROR) + end + + if moveables:GetStatus() == 1 and debug == true then + TEN.Util.PrintLog("Location of [ " .. objectId .. " ] set to location " .. location,TEN.Util.LogLevel.INFO) end end From 6ae14ccee6859d44eecbec86a77efa053b7a3d5f Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Sun, 17 May 2026 11:22:38 +0100 Subject: [PATCH 03/16] Rename section --- TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index 577bbb998a..85c236634d 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -1,5 +1,5 @@ -- !Name "Set intelligent creature location" --- !Section "Creature Mood" +-- !Section "Creature AI" -- !Description "Sets the location of intelligent enemies to a specified location.\nOnly to be used with GUIDE, Sophia-Leigh or Von Croy.\nPlace AI_X1 objects with an OCB to create a location." -- !Arguments "Newline, Moveables, 60, [ guide | sophia_leigh | von_croy ], Creature to set location for." -- !Arguments "Numerical, 20, [ 0 | 1000 ], Location to set. @@ -20,7 +20,7 @@ LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location,debug) end -- !Name "Set creature mood" --- !Section "Creature Mood" +-- !Section "Creature AI" -- !Description "Set creature mood" -- !Arguments "Newline, Moveables, 50, Moveable to set mood for." -- !Arguments "Enumeration, 50, [ Attack | Auto | Bored | Escape | Stalk ], Mood to set for creature." From da79c9f16080e78013f9d7272b007e43d31dfa51 Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Sun, 17 May 2026 11:30:50 +0100 Subject: [PATCH 04/16] "If creature mood is..." node --- .../Catalogs/TEN Node Catalogs/Creature Mood.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index 85c236634d..b9bd1daf1c 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -30,4 +30,19 @@ LevelFuncs.Engine.Node.SetCreatureMood = function(moveable, index) local mov = TEN.Objects.GetMoveableByName(moveable) local movAI = Objects.Creature(mov) movAI:SetMood(mood) +end + +-- !Name "If creature mood is..." +-- !Section "Creature AI" +-- !Description "Checks if creature mood is a specified mood." +-- !Conditional "True" +-- !Arguments "Newline, Moveables, 50, Moveable to check mood for." +-- !Arguments "Enumeration, 50, [ Attack | Auto | Bored | Escape | Stalk ], Mood to check for." +LevelFuncs.Engine.Node.TestCreatureMood = function(moveable, index) + + local mood = LevelFuncs.Engine.Node.GetCreatureMood(index) + local mov = TEN.Objects.GetMoveableByName(moveable) + local movAI = Objects.Creature(mov) + + return movAI:GetMood() == mood end \ No newline at end of file From e20e3bef1efda66711502278175af21ec0e519f1 Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Sun, 17 May 2026 12:26:55 +0100 Subject: [PATCH 05/16] "Set creature target" / "If creature target is..." And guarding against moveables not being active. --- .../TEN Node Catalogs/Creature Mood.lua | 68 +++++++++++++++++-- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index b9bd1daf1c..b765601dda 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -22,27 +22,83 @@ end -- !Name "Set creature mood" -- !Section "Creature AI" -- !Description "Set creature mood" --- !Arguments "Newline, Moveables, 50, Moveable to set mood for." --- !Arguments "Enumeration, 50, [ Attack | Auto | Bored | Escape | Stalk ], Mood to set for creature." +-- !Arguments "Newline, Moveables, 80, Moveable to set mood for." +-- !Arguments "Enumeration, 20, [ Attack | Auto | Bored | Escape | Stalk ], Mood to set for creature." LevelFuncs.Engine.Node.SetCreatureMood = function(moveable, index) local mood = LevelFuncs.Engine.Node.GetCreatureMood(index) local mov = TEN.Objects.GetMoveableByName(moveable) local movAI = Objects.Creature(mov) - movAI:SetMood(mood) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) + end + + if mov:GetStatus() == 1 then + movAI:SetMood(mood) + end end -- !Name "If creature mood is..." -- !Section "Creature AI" -- !Description "Checks if creature mood is a specified mood." -- !Conditional "True" --- !Arguments "Newline, Moveables, 50, Moveable to check mood for." --- !Arguments "Enumeration, 50, [ Attack | Auto | Bored | Escape | Stalk ], Mood to check for." +-- !Arguments "Newline, Moveables, 80, Moveable to check mood for." +-- !Arguments "Enumeration, 20, [ Attack | Auto | Bored | Escape | Stalk ], Mood to check for." LevelFuncs.Engine.Node.TestCreatureMood = function(moveable, index) local mood = LevelFuncs.Engine.Node.GetCreatureMood(index) local mov = TEN.Objects.GetMoveableByName(moveable) local movAI = Objects.Creature(mov) - return movAI:GetMood() == mood + if mov:GetStatus() == 1 then + return movAI:GetMood() == mood + end +end + +-- !Name "Set creature target" +-- !Section "Creature AI" +-- !Description "Set creature target" +-- !Arguments "Newline, Moveables, 50, Moveable to set target for." +-- !Arguments "Moveables, 50, Moveable to set as target." +-- !Arguments "Newline, Boolean, 50, {true}, Retaliate target" +LevelFuncs.Engine.Node.SetCreatureTarget = function(moveable, target, retaliate) + + local mov = TEN.Objects.GetMoveableByName(moveable) + local movAI = Objects.Creature(mov) + local targetMov = TEN.Objects.GetMoveableByName(target) + local targetMovAI = Objects.Creature(targetMov) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) + end + + if mov:GetStatus() == 1 then + movAI:SetTarget(targetMov) + end + + if retaliate and targetMov:GetStatus() == 1 then + targetMovAI:SetTarget(mov) + end +end + +-- !Name "If creature target is..." +-- !Section "Creature AI" +-- !Description "Checks if creature target is a specified moveable." +-- !Conditional "True" +-- !Arguments "Newline, Moveables, 50, Moveable to check target for." +-- !Arguments "Moveables, 50, Moveable to check as target." +LevelFuncs.Engine.Node.TestCreatureTarget = function(moveable, target) + + local mov = TEN.Objects.GetMoveableByName(moveable) + local movAI = Objects.Creature(mov) + local targetMov = TEN.Objects.GetMoveableByName(target) + + if mov:GetStatus() == 1 then + return movAI:GetTarget() == targetMov + end + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) + end end \ No newline at end of file From 895ae5633d892a95aa5898033f24689232eac27b Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Sun, 17 May 2026 13:12:45 +0100 Subject: [PATCH 06/16] "Set creature as friendly" / "If creature is friendly..." nodes --- .../TEN Node Catalogs/Creature Mood.lua | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index b765601dda..dfd8440758 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -12,6 +12,7 @@ LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location,debug) if moveables:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. objectId .. " ] is not active. No location set.",TEN.Util.LogLevel.ERROR) + end if moveables:GetStatus() == 1 and debug == true then @@ -31,7 +32,8 @@ LevelFuncs.Engine.Node.SetCreatureMood = function(moveable, index) local movAI = Objects.Creature(mov) if mov:GetStatus() ~= 1 then - TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) + end if mov:GetStatus() == 1 then @@ -51,8 +53,13 @@ LevelFuncs.Engine.Node.TestCreatureMood = function(moveable, index) local mov = TEN.Objects.GetMoveableByName(moveable) local movAI = Objects.Creature(mov) + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) + + end + if mov:GetStatus() == 1 then - return movAI:GetMood() == mood + return movAI:GetMood() == mood end end @@ -71,6 +78,7 @@ LevelFuncs.Engine.Node.SetCreatureTarget = function(moveable, target, retaliate) if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) + end if mov:GetStatus() == 1 then @@ -95,10 +103,54 @@ LevelFuncs.Engine.Node.TestCreatureTarget = function(moveable, target) local targetMov = TEN.Objects.GetMoveableByName(target) if mov:GetStatus() == 1 then - return movAI:GetTarget() == targetMov + return movAI:GetTarget() == targetMov end if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) end +end + +-- !Name "Set creature as friendly" +-- !Section "Creature AI" +-- !Description "Sets creature as friendly to the player." +-- !Arguments "Newline, Moveables, 70, Moveable to set as friendly." +-- !Arguments "Boolean, 30, {true}, Undo friendly if attacked." +LevelFuncs.Engine.Node.SetCreatureFriendly = function(moveable, undoIfAttacked) + local mov = TEN.Objects.GetMoveableByName(moveable) + local movAI = Objects.Creature(mov) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set as friendly.",TEN.Util.LogLevel.ERROR) + end + + if mov:GetStatus() == 1 then + movAI:SetFriendly(true) + + if undoIfAttacked and movAI:GetHurtByPlayer() == true then + movAI:SetFriendly(false) + end + + if movAI:GetHurtByPlayer() == true then + movAI:SetFriendly(false) + end + end +end + +-- !Name "If creature is friendly..." +-- !Section "Creature AI" +-- !Description "Checks if creature is friendly to the player." +-- !Conditional "True" +-- !Arguments "Newline, Moveables, 50, Moveable to check." +LevelFuncs.Engine.Node.TestCreatureFriendly = function(moveable) + local mov = TEN.Objects.GetMoveableByName(moveable) + local movAI = Objects.Creature(mov) + + if mov:GetStatus() == 1 then + return movAI:IsFriendly() + end + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check if friendly.",TEN.Util.LogLevel.ERROR) + end end \ No newline at end of file From 673a7169f8513b6df593571d07bc534b1687b59a Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Sun, 17 May 2026 13:15:34 +0100 Subject: [PATCH 07/16] Cleanup last commit --- TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index dfd8440758..7a168b9279 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -33,7 +33,6 @@ LevelFuncs.Engine.Node.SetCreatureMood = function(moveable, index) if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) - end if mov:GetStatus() == 1 then @@ -54,8 +53,7 @@ LevelFuncs.Engine.Node.TestCreatureMood = function(moveable, index) local movAI = Objects.Creature(mov) if mov:GetStatus() ~= 1 then - TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) - + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) end if mov:GetStatus() == 1 then @@ -78,7 +76,6 @@ LevelFuncs.Engine.Node.SetCreatureTarget = function(moveable, target, retaliate) if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) - end if mov:GetStatus() == 1 then From ff484d00dc8b19fdca93fa4bb3ffdbebf89e39df Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Mon, 18 May 2026 07:55:01 +0100 Subject: [PATCH 08/16] Add early exits / "Set Creature poison status" / "If creature is poisoned..." nodes --- .../TEN Node Catalogs/Creature Mood.lua | 122 +++++++++++------- 1 file changed, 77 insertions(+), 45 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index 7a168b9279..eedc701462 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -4,18 +4,16 @@ -- !Arguments "Newline, Moveables, 60, [ guide | sophia_leigh | von_croy ], Creature to set location for." -- !Arguments "Numerical, 20, [ 0 | 1000 ], Location to set. -- !Arguments "Boolean, 20, {false}, Debug to console." -LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location,debug) +LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location, debug) local moveables = TEN.Objects.GetMoveableByName(objectId) - if moveables:GetStatus() == 1 then - moveables:SetLocationAI(location) - end - if moveables:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. objectId .. " ] is not active. No location set.",TEN.Util.LogLevel.ERROR) - + return end - - if moveables:GetStatus() == 1 and debug == true then + + moveables:SetLocationAI(location) + + if debug == true then TEN.Util.PrintLog("Location of [ " .. objectId .. " ] set to location " .. location,TEN.Util.LogLevel.INFO) end end @@ -26,18 +24,17 @@ end -- !Arguments "Newline, Moveables, 80, Moveable to set mood for." -- !Arguments "Enumeration, 20, [ Attack | Auto | Bored | Escape | Stalk ], Mood to set for creature." LevelFuncs.Engine.Node.SetCreatureMood = function(moveable, index) - - local mood = LevelFuncs.Engine.Node.GetCreatureMood(index) local mov = TEN.Objects.GetMoveableByName(moveable) - local movAI = Objects.Creature(mov) if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) + return end - if mov:GetStatus() == 1 then - movAI:SetMood(mood) - end + local mood = LevelFuncs.Engine.Node.GetCreatureMood(index) + local movAI = Objects.Creature(mov) + + movAI:SetMood(mood) end -- !Name "If creature mood is..." @@ -47,18 +44,17 @@ end -- !Arguments "Newline, Moveables, 80, Moveable to check mood for." -- !Arguments "Enumeration, 20, [ Attack | Auto | Bored | Escape | Stalk ], Mood to check for." LevelFuncs.Engine.Node.TestCreatureMood = function(moveable, index) - - local mood = LevelFuncs.Engine.Node.GetCreatureMood(index) local mov = TEN.Objects.GetMoveableByName(moveable) - local movAI = Objects.Creature(mov) if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) + return false end - if mov:GetStatus() == 1 then - return movAI:GetMood() == mood - end + local mood = LevelFuncs.Engine.Node.GetCreatureMood(index) + local movAI = Objects.Creature(mov) + + return movAI:GetMood() == mood end -- !Name "Set creature target" @@ -70,19 +66,19 @@ end LevelFuncs.Engine.Node.SetCreatureTarget = function(moveable, target, retaliate) local mov = TEN.Objects.GetMoveableByName(moveable) - local movAI = Objects.Creature(mov) - local targetMov = TEN.Objects.GetMoveableByName(target) - local targetMovAI = Objects.Creature(targetMov) if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) + return end - if mov:GetStatus() == 1 then - movAI:SetTarget(targetMov) - end + local movAI = Objects.Creature(mov) + local targetMov = TEN.Objects.GetMoveableByName(target) + + movAI:SetTarget(targetMov) if retaliate and targetMov:GetStatus() == 1 then + local targetMovAI = Objects.Creature(targetMov) targetMovAI:SetTarget(mov) end end @@ -96,16 +92,16 @@ end LevelFuncs.Engine.Node.TestCreatureTarget = function(moveable, target) local mov = TEN.Objects.GetMoveableByName(moveable) - local movAI = Objects.Creature(mov) - local targetMov = TEN.Objects.GetMoveableByName(target) - - if mov:GetStatus() == 1 then - return movAI:GetTarget() == targetMov - end if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) + return false end + + local movAI = Objects.Creature(mov) + local targetMov = TEN.Objects.GetMoveableByName(target) + + return movAI:GetTarget() == targetMov end -- !Name "Set creature as friendly" @@ -115,22 +111,22 @@ end -- !Arguments "Boolean, 30, {true}, Undo friendly if attacked." LevelFuncs.Engine.Node.SetCreatureFriendly = function(moveable, undoIfAttacked) local mov = TEN.Objects.GetMoveableByName(moveable) - local movAI = Objects.Creature(mov) if mov:GetStatus() ~= 1 then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set as friendly.",TEN.Util.LogLevel.ERROR) + return end - if mov:GetStatus() == 1 then - movAI:SetFriendly(true) + local movAI = Objects.Creature(mov) + + movAI:SetFriendly(true) - if undoIfAttacked and movAI:GetHurtByPlayer() == true then - movAI:SetFriendly(false) - end + if undoIfAttacked and movAI:GetHurtByPlayer() == true then + movAI:SetFriendly(false) + end - if movAI:GetHurtByPlayer() == true then - movAI:SetFriendly(false) - end + if movAI:GetHurtByPlayer() == true then + movAI:SetFriendly(false) end end @@ -139,15 +135,51 @@ end -- !Description "Checks if creature is friendly to the player." -- !Conditional "True" -- !Arguments "Newline, Moveables, 50, Moveable to check." -LevelFuncs.Engine.Node.TestCreatureFriendly = function(moveable) +LevelFuncs.Engine.Node.TestCreatureFriendly = function(moveable) local mov = TEN.Objects.GetMoveableByName(moveable) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check if friendly.",TEN.Util.LogLevel.ERROR) + return false + end + local movAI = Objects.Creature(mov) - if mov:GetStatus() == 1 then - return movAI:IsFriendly() + return movAI:IsFriendly() +end + +-- !Name "Set creature poison status" +-- !Section "Creature AI" +-- !Description "Sets whether the creature is poisoned or not." +-- !Arguments "Newline, Moveables, 70, Moveable to set poison status for." +-- !Arguments "Boolean, 30, {true}, Poison the creature." +LevelFuncs.Engine.Node.SetCreaturePoisoned = function(moveable, poisoned) + local mov = TEN.Objects.GetMoveableByName(moveable) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set poison status.",TEN.Util.LogLevel.ERROR) + return end + local movAI = Objects.Creature(mov) + + movAI:SetPoisoned(poisoned) +end + +-- !Name "If creature is poisoned..." +-- !Section "Creature AI" +-- !Description "Checks if the creature is poisoned." +-- !Conditional "True" +-- !Arguments "Newline, Moveables, Moveable to check poison status for." +LevelFuncs.Engine.Node.TestCreaturePoisoned = function(moveable) + local mov = TEN.Objects.GetMoveableByName(moveable) + if mov:GetStatus() ~= 1 then - TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check if friendly.",TEN.Util.LogLevel.ERROR) + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check poison status.",TEN.Util.LogLevel.ERROR) + return false end + + local movAI = Objects.Creature(mov) + + return movAI:IsPoisoned() end \ No newline at end of file From d129203de636ecf26c0cf239967a12ef287393fd Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Mon, 18 May 2026 09:38:12 +0100 Subject: [PATCH 09/16] More nodes and cleanup --- Installer/Changes.txt | 1 + .../TEN Node Catalogs/Creature Mood.lua | 112 +++++++++++++++++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/Installer/Changes.txt b/Installer/Changes.txt index 837bf09134..54e58a7ad9 100644 --- a/Installer/Changes.txt +++ b/Installer/Changes.txt @@ -13,6 +13,7 @@ WadTool: TEN nodes: * Added a node to unswap a previously swapped mesh. + * Added nodes for checking and setting various aspects of creature AI. Version 1.11 ============ diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index eedc701462..3a24785016 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -152,7 +152,7 @@ end -- !Section "Creature AI" -- !Description "Sets whether the creature is poisoned or not." -- !Arguments "Newline, Moveables, 70, Moveable to set poison status for." --- !Arguments "Boolean, 30, {true}, Poison the creature." +-- !Arguments "Boolean, 30, {true}, Poison" LevelFuncs.Engine.Node.SetCreaturePoisoned = function(moveable, poisoned) local mov = TEN.Objects.GetMoveableByName(moveable) @@ -181,5 +181,113 @@ LevelFuncs.Engine.Node.TestCreaturePoisoned = function(moveable) local movAI = Objects.Creature(mov) - return movAI:IsPoisoned() + return movAI:GetPoisoned() +end + +--!Name "Set hurt by player status" +--!Section "Creature AI" +--!Description "Sets whether the creature has been hurt by the player or not." +--!Arguments "Newline, Moveables, 70, Moveable to set hurt by player status for." +--!Arguments "Boolean, 30, {true}, Hurt by player" +LevelFuncs.Engine.Node.SetHurtByPlayer = function(moveable, hurtByPlayer) + local mov = TEN.Objects.GetMoveableByName(moveable) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set hurt by player status.",TEN.Util.LogLevel.ERROR) + return + end + + local movAI = Objects.Creature(mov) + + movAI:SetHurtByPlayer(hurtByPlayer) +end + +-- !Name "If creature is hurt by player..." +-- !Section "Creature AI" +-- !Description "Checks if the creature has been hurt by the player." +-- !Conditional "True" +-- !Arguments "Newline, Moveables, Moveable to check hurt by player status for." +LevelFuncs.Engine.Node.TestHurtByPlayer = function(moveable) + local mov = TEN.Objects.GetMoveableByName(moveable) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check hurt by player status.",TEN.Util.LogLevel.ERROR) + return false + end + + local movAI = Objects.Creature(mov) + + return movAI:GetHurtByPlayer() +end + +-- !Name "If creature is jumping..." +-- !Section "Creature AI" +-- !Description "Checks if the creature is currently jumping. Only works for creatures that can jump." +-- !Conditional "True" +-- !Arguments "Newline, Moveables, [ baddy | enemy_jeep | mafia | raptor | scientist | skeleton | swat_ | von_croy ], Moveable to check jumping status for." +LevelFuncs.Engine.Node.TestCreatureJumping = function(moveable) + local mov = TEN.Objects.GetMoveableByName(moveable) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check jumping status.",TEN.Util.LogLevel.ERROR) + return false + end + + local movAI = Objects.Creature(mov) + + return movAI:GetJumping() +end + +-- !Name "If creature is monkey-swinging..." +-- !Section "Creature AI" +-- !Description "Checks if the creature is currently monkey-swinging. Only works for creatures that can monkey-swing." +-- !Conditional "True" +-- !Arguments "Newline, Moveables, [ baddy | von_croy ], Moveable to check monkey-swinging status for." +LevelFuncs.Engine.Node.TestCreatureMonkeySwinging = function(moveable) + local mov = TEN.Objects.GetMoveableByName(moveable) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check monkey-swing status.",TEN.Util.LogLevel.ERROR) + return false + end + + local movAI = Objects.Creature(mov) + + return movAI:GetMonkeying() +end + +-- !Name "If creature has reached their goal..." +-- !Section "Creature AI" +-- !Description "Checks if the creature has reached their goal.\nThis setting may be used to find out whether a creature that is using AI nullmesh objects has reached its currently specified AI nullmesh." +-- !Conditional "True" +-- !Arguments "Newline, Moveables, Moveable to check goal status for." +LevelFuncs.Engine.Node.TestCreatureReachedGoal = function(moveable) + local mov = TEN.Objects.GetMoveableByName(moveable) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check location status.",TEN.Util.LogLevel.ERROR) + return false + end + + local movAI = Objects.Creature(mov) + + return movAI:GetAtGoal() +end + +-- !Name "Set creature goal status" +-- !Section "Creature AI" +-- !Description "Sets whether the creature has reached their goal or not.\nThis setting may be used to break out the creature from reaching the next specified AI object nullmesh." +-- !Arguments "Newline, Moveables, Moveable to set goal status for." +-- !Arguments "Boolean, 30, {true}, At goal" +LevelFuncs.Engine.Node.SetCreatureReachedGoal = function(moveable, goal) + local mov = TEN.Objects.GetMoveableByName(moveable) + + if mov:GetStatus() ~= 1 then + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set location status.",TEN.Util.LogLevel.ERROR) + return + end + + local movAI = Objects.Creature(mov) + + movAI:SetAtGoal(goal) end \ No newline at end of file From b7dd7aeea393c18facabb0e8c1b8e421e673320c Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Mon, 18 May 2026 09:43:18 +0100 Subject: [PATCH 10/16] Restored readme file Accidently deleted on branch. --- .../Catalogs/TEN Node Catalogs/Readme.md | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md new file mode 100644 index 0000000000..a20dcacdf6 --- /dev/null +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md @@ -0,0 +1,161 @@ +## TEN Node function script files metadata reference + +### Overview + +This folder contains lua script files which will be referenced by both TE and TEN to work with node trigger system. +TE uses metadata from comments in these files to build UI, while TEN uses actual functions to execute node scripts. + +On level compile, all files from this directory are copied to TEN `Scripts\NodeCatalogs` folder and executed +on level start-up, given that level contains any volumes. Therefore, NodeFunctions.lua file in TEN +`Scripts\NodeCatalogs` folder should never be modified. + +### Format + +Lua node scripts should follow this convention: several metadata entries should be followed by actual function +signature which should start with conventional **LevelFuncs.Engine.Node.** prefix. Function signature should be +followed by function body in arbitrary format. Amount of argument metadata entries should be the same as actual +function arguments, and should be listed in the same order. + +### Argument naming and order + +Actual function arguments (e.g. `arg1`, `arg2` and `arg3` in `LevelFuncs.Engine.Node.MyFunction(arg1, arg2, arg3)`) +should not be renamed after node went into production with official TE release - otherwise TE will not be able to +identify arguments on project file reloading. However, since TE version 1.8, it is allowed to rearrange node +arguments, if argument names were left untouched. You still need to preserve metadata entry order to correspond to +function order though. + +Builders must be advised that they should migrate to version 1.8 as soon as possible, because skipping version +1.8, together with possible node argument reorderings in the future, may lead to data loss in existing node setups. + +### Metadata entry reference + +Comment metadata entry reference (metadata block is indicated by a keyword which starts with !): + + - **!Name "NAME"** - NAME will be visible name for this function in node editor. + + - **!Conditional "True"** - this is a condition node, otherwise if "False", it's action + (or if **!Conditional** is not specified). + + - **!Description "DESC"** - DESC will be a tooltip for a given function. You can use `\n` symbol to begin from a + new line. + + - **!Section "SECTION"** - this will define where the node will be found inside Tomb Editor. + + - **!Arguments "ARGDESC1" "ARGDESC2" "ARGDESC..."** - infinite amount of args, with **ARGDESC** parameters + separated by commas as follows: + + - **0-100** - numerical value specifying width of control in percent of node width. For whole-line controls, + this can be omitted. + + - **NewLine** - keyword which specifies if this and following arguments should appear on new UI line, until + another argument with another **NewLine** is encountered. + + - **Boolean, Numerical, Vector3, String, Color, LuaScript, Moveables, Statics, Cameras, Sinks, FlybyCameras, + Volumes, Rooms, SoundEffects, WadSlots, Enumeration, CompareOperator** - keywords which specify argument type + and its appearance in UI. + + - **{DEFAULT}** - default value for this argument, contained in brackets. For Numerical value type, it can be + provided as is, e.g. `{100}`. For String value type, default string must be quoted, e.g. `{"Default string"}`. + Complex value types, such as Color or Vector3, should use TEN Lua API notation, e.g. `TEN.Color(255,255,255)`. + + - **[ENUMDESC1 | ENUMDESC2 | ENUMDESC...]** - custom enumeration descriptors for this argument, as follows: + + - For Numerical and Vector3 value types, first and second ENUMDESC values determine min/max UI range of + value. Optional third argument specifies amount of decimal places (for integer, set it to 0). Optional + fourth and fifth values determine mousewheel increment and alternate increment (with shift button + pressed) respectively. + - For String value type, specifying **NoMultiline** as ENUMDESC hides button which engages multiline editing. + - For Moveables and WadSlots lists, ENUMDESC values will filter out object ID names which contain any of + ENUMDESCs only. + - For Enumeration, ENUMDESC values will be displayed as is but converted to numericals on compilation + in order of appearance. + + - **Any other string value except listed above** - tooltip for a given argument control. + + - **!Ignore** - if this keyword is used, nearest encountered function declaration will be ignored. Useful if you + need to place helper functions which must be ignored by parser (however, it is recommended to use `_System.lua` + file for those). + + +Metadata blocks can appear in any order. + +Metadata parsing happens until real function block starts (which should start with **LevelFuncs.Engine.Node.** +prefix). + +Conditional node functions (those with **!Conditional = "True"** specified) must return boolean value, otherwise +their behaviour is undefined. + +ARGDESC parameters can appear in any order, e.g. `!Argument "Foo, Numerical, 20"` is equal to +`!Argument "Numerical, 20, Foo"`. + +There could be several **!Argument** blocks, which will append arguments to previously parsed ones. + +There could be several **!Description** blocks, which will append as a new line to previous description block. + +ENUMDESC parameters should NOT be quoted, or else parsing will fail miserably. + + +### Argument types (those you specify under **!Arguments** keyword) + + - **Boolean** - a value which can be either `true` or `false`. Appears as a checkbox with argument description + as a label. + - **Numerical** - numerical floating-point value. Range, limits and decimals can be specified by **ENUMDESC** + descriptors (see above). + - **Vector2** - Two floating-point values, can be used for 2D on-screen position. Range, limits and + decimals can be specified by **ENUMDESC** descriptors (see above). + - **Vector3** - Three floating-point values, can be used either for position or rotation. Range, limits and + decimals can be specified by **ENUMDESC** descriptors (see above). + - **String** - Raw text string, length is unlimited. If parsed to functions which draw on-screen strings, `\n` + combination may be used to start a new line. + - **Time** - Time value, consists of 4 fields - hours, minutes, seconds and centiseconds. + - **Color** - RGB color value, appears as color picker in UI. + - **LuaScript** - Existing lua function list from level script file. + - **EventSets** - Existing event sets currently present in level. + - **Moveables** - A list of moveables in level which have lua names assigned. + - **Statics** - A list of statics in level which have lua names assigned. + - **Cameras** - Same as above, but cameras. + - **Sinks** - Same as above, but sinks. + - **FlybyCameras** - Same as above, but flybys. + - **Volumes** - Same as above, but volumes. + - **Rooms** - A list of existing rooms in level. Accessed by room name specified in TE UI. + - **SoundEffects** - A list of sound effects. Internally converted to numerical effect slot ID. + - **WadSlots** - A list of object slots which exist in all loaded wads. Internally accessed by numerical ID and/or + `Objects.ObjID.` lua enumeration which is identical to TE/TEN object slot enumeration. + - **SpriteSlots** - Similar to previous type, but for sprite sequence slots. + - **Enumeration** - Custom enumeration determined by **ENUMDESC** descriptors. Internally these descriptors are + converted to numerical index. + - **CompareOperator** - Comparison operator enumeration, ranging from equal to various less-or-equal and more-or-equal + operators. Internally converted to numerical value and should be passed to `LevelFuncs.CompareValue` helper function + along with operand and reference to check against, like this: `LevelFuncs.CompareValue(operand, reference, operator)`. + +### Example + +``` +-- !Name "Check moveable health" +-- !Description "Compares selected moveable health with given value." +-- !Conditional "True" +-- !Arguments "NewLine, Moveables, Moveable to check" "NewLine, CompareOperator, 70, Kind of check" +-- !Arguments "Numerical, 30, [ 0 | 1000 | 0 ], Hit points value" + +LevelFuncs.CheckEntityHealth = function(moveableName, operator, value) + local health = TEN.Objects.GetMoveableByName(entityName):GetHP() + return LevelFuncs.CompareValue(health, value, operator) +end +``` + +Here, **!Arguments** entry lists 3 parameters, which will be called "Moveable to check", "Kind of check" +and "Hit points value" respectively. First argument, "Moveable to check", will occupy whole second line of a +node UI, because width is not explicitly stated for it. Next two arguments, "Kind of check" and "Hit points +value", both will occupy same next line, because **NewLine** keyword is not specified for third argument. +Second argument, "Kind of check", will occupy 70 percent of line width, while "Hit points value" will occupy +30 percent. Note that arguments sitting on the same line should sum to 100 percent width, otherwise +UI symmetry is not guaranteed. + +Also note that "Hit points value" argument is placed on separate **!Arguments** block, and this is correct, +since it will appear after previous argument block. Also, being numerical argument, it will allow user to +only define values between 0 and 1000. All other values will be clamped. Decimal places for a value won't +show, as third parameter in square brackets is set to 0. + +**LevelFuncs.CheckEntityHealth** function declaration should contain same amount of arguments and in the same +order as metadata argument entry. Therefore, **moveableName** will be read from "Moveable to check" UI argument, +**operator** will be read from "Kind of check", and so on. From afac1dbb35f2cae298b1acc2248f9938e231c27a Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Mon, 18 May 2026 09:52:14 +0100 Subject: [PATCH 11/16] Fix typo Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index 3a24785016..6644937bd9 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -2,7 +2,7 @@ -- !Section "Creature AI" -- !Description "Sets the location of intelligent enemies to a specified location.\nOnly to be used with GUIDE, Sophia-Leigh or Von Croy.\nPlace AI_X1 objects with an OCB to create a location." -- !Arguments "Newline, Moveables, 60, [ guide | sophia_leigh | von_croy ], Creature to set location for." --- !Arguments "Numerical, 20, [ 0 | 1000 ], Location to set. +-- !Arguments "Numerical, 20, [ 0 | 1000 ], Location to set." -- !Arguments "Boolean, 20, {false}, Debug to console." LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location, debug) local moveables = TEN.Objects.GetMoveableByName(objectId) From 869b7eba13d4e869ceb21d4ba3ee8ab68f969c78 Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Mon, 18 May 2026 09:53:05 +0100 Subject: [PATCH 12/16] Add space after commenting out the metadata Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Catalogs/TEN Node Catalogs/Creature Mood.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index 6644937bd9..1836189c3e 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -184,11 +184,11 @@ LevelFuncs.Engine.Node.TestCreaturePoisoned = function(moveable) return movAI:GetPoisoned() end ---!Name "Set hurt by player status" ---!Section "Creature AI" ---!Description "Sets whether the creature has been hurt by the player or not." ---!Arguments "Newline, Moveables, 70, Moveable to set hurt by player status for." ---!Arguments "Boolean, 30, {true}, Hurt by player" +-- !Name "Set hurt by player status" +-- !Section "Creature AI" +-- !Description "Sets whether the creature has been hurt by the player or not." +-- !Arguments "Newline, Moveables, 70, Moveable to set hurt by player status for." +-- !Arguments "Boolean, 30, {true}, Hurt by player" LevelFuncs.Engine.Node.SetHurtByPlayer = function(moveable, hurtByPlayer) local mov = TEN.Objects.GetMoveableByName(moveable) From 241c3f9321c0dfb07fc9f3168b93a3d9c7720208 Mon Sep 17 00:00:00 2001 From: Stranger1992 <84292688+Stranger1992@users.noreply.github.com> Date: Mon, 18 May 2026 09:57:08 +0100 Subject: [PATCH 13/16] Remove redundant if statement Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index 1836189c3e..9661d7d8fe 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -124,10 +124,6 @@ LevelFuncs.Engine.Node.SetCreatureFriendly = function(moveable, undoIfAttacked) if undoIfAttacked and movAI:GetHurtByPlayer() == true then movAI:SetFriendly(false) end - - if movAI:GetHurtByPlayer() == true then - movAI:SetFriendly(false) - end end -- !Name "If creature is friendly..." From c0200b85bca663496a16963a84fb8ca095e0a173 Mon Sep 17 00:00:00 2001 From: MontyTRC89 Date: Thu, 18 Jun 2026 12:29:59 +0200 Subject: [PATCH 14/16] Creature AI nodes: fix misleading inactive-object log messages The mood condition node logged "No mood set." (copied from the set node); use "Cannot check mood." to match the other check nodes. Also correct the goal nodes, which said "location status" instead of "goal status". --- .../TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index 9661d7d8fe..8d5d70a4e6 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -47,7 +47,7 @@ LevelFuncs.Engine.Node.TestCreatureMood = function(moveable, index) local mov = TEN.Objects.GetMoveableByName(moveable) if mov:GetStatus() ~= 1 then - TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check mood.",TEN.Util.LogLevel.ERROR) return false end @@ -261,7 +261,7 @@ LevelFuncs.Engine.Node.TestCreatureReachedGoal = function(moveable) local mov = TEN.Objects.GetMoveableByName(moveable) if mov:GetStatus() ~= 1 then - TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check location status.",TEN.Util.LogLevel.ERROR) + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check goal status.",TEN.Util.LogLevel.ERROR) return false end @@ -279,7 +279,7 @@ LevelFuncs.Engine.Node.SetCreatureReachedGoal = function(moveable, goal) local mov = TEN.Objects.GetMoveableByName(moveable) if mov:GetStatus() ~= 1 then - TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set location status.",TEN.Util.LogLevel.ERROR) + TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set goal status.",TEN.Util.LogLevel.ERROR) return end From 2353e66017b8bd82c8aa0389e3995b7975ff45c8 Mon Sep 17 00:00:00 2001 From: MontyTRC89 Date: Thu, 18 Jun 2026 14:16:49 +0200 Subject: [PATCH 15/16] Creature target node: guard retaliation against non-creature targets The target argument is an unrestricted Moveables pick and Retaliate defaults to true, so an active non-creature target would be wrapped via Objects.Creature and crash at runtime. Guard that wrap with pcall, skipping retaliation and logging when the target is not a creature. --- .../TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index 8d5d70a4e6..eb6f885c67 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -78,8 +78,12 @@ LevelFuncs.Engine.Node.SetCreatureTarget = function(moveable, target, retaliate) movAI:SetTarget(targetMov) if retaliate and targetMov:GetStatus() == 1 then - local targetMovAI = Objects.Creature(targetMov) - targetMovAI:SetTarget(mov) + local success, targetMovAI = pcall(Objects.Creature, targetMov) + if success then + targetMovAI:SetTarget(mov) + else + TEN.Util.PrintLog("moveable [ " .. target .. " ] is not a creature. Retaliation skipped.",TEN.Util.LogLevel.ERROR) + end end end From 336929e1d2c20eaa2e29c9e22e21129834dfd422 Mon Sep 17 00:00:00 2001 From: MontyTRC89 Date: Thu, 18 Jun 2026 14:37:01 +0200 Subject: [PATCH 16/16] Creature AI nodes: use MoveableStatus.ACTIVE instead of magic number Replace the GetStatus() ~= 1 / == 1 active-status checks across all creature AI nodes with TEN.Objects.MoveableStatus.ACTIVE for readability. --- .../TEN Node Catalogs/Creature Mood.lua | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua index eb6f885c67..392a8b5877 100644 --- a/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua +++ b/TombLib/TombLib/Catalogs/TEN Node Catalogs/Creature Mood.lua @@ -6,7 +6,7 @@ -- !Arguments "Boolean, 20, {false}, Debug to console." LevelFuncs.Engine.Node.SetCreatureLocation = function(objectId, location, debug) local moveables = TEN.Objects.GetMoveableByName(objectId) - if moveables:GetStatus() ~= 1 then + if moveables:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. objectId .. " ] is not active. No location set.",TEN.Util.LogLevel.ERROR) return end @@ -26,7 +26,7 @@ end LevelFuncs.Engine.Node.SetCreatureMood = function(moveable, index) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No mood set.",TEN.Util.LogLevel.ERROR) return end @@ -46,7 +46,7 @@ end LevelFuncs.Engine.Node.TestCreatureMood = function(moveable, index) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check mood.",TEN.Util.LogLevel.ERROR) return false end @@ -67,7 +67,7 @@ LevelFuncs.Engine.Node.SetCreatureTarget = function(moveable, target, retaliate) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) return end @@ -77,7 +77,7 @@ LevelFuncs.Engine.Node.SetCreatureTarget = function(moveable, target, retaliate) movAI:SetTarget(targetMov) - if retaliate and targetMov:GetStatus() == 1 then + if retaliate and targetMov:GetStatus() == TEN.Objects.MoveableStatus.ACTIVE then local success, targetMovAI = pcall(Objects.Creature, targetMov) if success then targetMovAI:SetTarget(mov) @@ -97,7 +97,7 @@ LevelFuncs.Engine.Node.TestCreatureTarget = function(moveable, target) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. No target set.",TEN.Util.LogLevel.ERROR) return false end @@ -116,7 +116,7 @@ end LevelFuncs.Engine.Node.SetCreatureFriendly = function(moveable, undoIfAttacked) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set as friendly.",TEN.Util.LogLevel.ERROR) return end @@ -138,7 +138,7 @@ end LevelFuncs.Engine.Node.TestCreatureFriendly = function(moveable) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check if friendly.",TEN.Util.LogLevel.ERROR) return false end @@ -156,7 +156,7 @@ end LevelFuncs.Engine.Node.SetCreaturePoisoned = function(moveable, poisoned) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set poison status.",TEN.Util.LogLevel.ERROR) return end @@ -174,7 +174,7 @@ end LevelFuncs.Engine.Node.TestCreaturePoisoned = function(moveable) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check poison status.",TEN.Util.LogLevel.ERROR) return false end @@ -192,7 +192,7 @@ end LevelFuncs.Engine.Node.SetHurtByPlayer = function(moveable, hurtByPlayer) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set hurt by player status.",TEN.Util.LogLevel.ERROR) return end @@ -210,7 +210,7 @@ end LevelFuncs.Engine.Node.TestHurtByPlayer = function(moveable) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check hurt by player status.",TEN.Util.LogLevel.ERROR) return false end @@ -228,7 +228,7 @@ end LevelFuncs.Engine.Node.TestCreatureJumping = function(moveable) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check jumping status.",TEN.Util.LogLevel.ERROR) return false end @@ -246,7 +246,7 @@ end LevelFuncs.Engine.Node.TestCreatureMonkeySwinging = function(moveable) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check monkey-swing status.",TEN.Util.LogLevel.ERROR) return false end @@ -264,7 +264,7 @@ end LevelFuncs.Engine.Node.TestCreatureReachedGoal = function(moveable) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot check goal status.",TEN.Util.LogLevel.ERROR) return false end @@ -282,7 +282,7 @@ end LevelFuncs.Engine.Node.SetCreatureReachedGoal = function(moveable, goal) local mov = TEN.Objects.GetMoveableByName(moveable) - if mov:GetStatus() ~= 1 then + if mov:GetStatus() ~= TEN.Objects.MoveableStatus.ACTIVE then TEN.Util.PrintLog("moveable [ " .. moveable .. " ] is not active. Cannot set goal status.",TEN.Util.LogLevel.ERROR) return end