Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions TombEditor/Controls/TriggerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ public TriggerManager()
InitializeComponent();
}

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public EventType EventType
{
get { return nodeEditor.CurrentEventType; }
set { nodeEditor.CurrentEventType = value; }
}

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Event Event
{
Expand Down
2 changes: 2 additions & 0 deletions TombEditor/Forms/FormEventSetEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ private void LoadEventSetIntoUI(EventSet newEventSet)
}

cbEvents.SelectedItem = newEventSet.LastUsedEvent;
triggerManager.EventType = newEventSet.LastUsedEvent;
triggerManager.Event = newEventSet.Events[newEventSet.LastUsedEvent];

tbName.Text = newEventSet.Name;
Expand Down Expand Up @@ -646,6 +647,7 @@ private void cbEvents_SelectedIndexChanged(object sender, EventArgs e)
if (!_lockUI)
{
SelectedSet.LastUsedEvent = (EventType)cbEvents.SelectedItem;
triggerManager.EventType = SelectedSet.LastUsedEvent;
triggerManager.Event = SelectedSet.Events[SelectedSet.LastUsedEvent];
}
}
Expand Down
40 changes: 37 additions & 3 deletions TombLib/TombLib.Forms/Controls/VisualScripting/NodeEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public enum ConnectionMode

public partial class NodeEditor : UserControl
{
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public EventType CurrentEventType { get; set; }

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Vector2 ViewPosition { get; set; } = new Vector2(60.0f, 60.0f);
Expand Down Expand Up @@ -885,20 +889,50 @@ private void DrawShadow(PaintEventArgs e, VisibleNodeBase node)
e.Graphics.DrawImage(Properties.Resources.misc_Shadow, rect);
}

private bool IsNodeUnsupported(TriggerNode node)
{
if (string.IsNullOrEmpty(node.Function))
return false;

var func = NodeFunctions.FirstOrDefault(f => f.Signature == node.Function);
if (func == null)
return false;

return func.IsUnsupported(CurrentEventType);
}

private void DrawHeader(PaintEventArgs e, VisibleNodeBase node)
{
if (!node.Visible)
return;

var size = TextRenderer.MeasureText(node.Node.Name, Font);
const float LabelOpacity = 0.5f;

bool unsupported = IsNodeUnsupported(node.Node);
var headerText = unsupported ? "Not supported for this event type" : node.Node.Name;
int iconOffset = 0;
var size = TextRenderer.MeasureText(headerText, Font);

var rect = node.ClientRectangle;
rect.Height = size.Height;
rect.Offset(node.Location);
rect.Offset(0, -(int)(size.Height * 1.2f));

using (var b = new SolidBrush(Colors.LightText.ToFloat3Color().ToWinFormsColor(0.5f)))
e.Graphics.DrawString(node.Node.Name, Font, b, rect,
if (unsupported)
{
var matrix = new System.Drawing.Imaging.ColorMatrix { Matrix33 = LabelOpacity, Matrix22 = 0.0f };
var attributes = new System.Drawing.Imaging.ImageAttributes();
attributes.SetColorMatrix(matrix, System.Drawing.Imaging.ColorMatrixFlag.Default, System.Drawing.Imaging.ColorAdjustType.Bitmap);

var icon = Properties.Resources.general_Warning_16;
var iconRect = new Rectangle(rect.X, rect.Y + (rect.Height - icon.Height), icon.Width, icon.Height);
e.Graphics.DrawImage(icon, iconRect, 0, 0, icon.Width, icon.Height, GraphicsUnit.Pixel, attributes);
iconOffset = icon.Width + 2;
Comment on lines +923 to +930
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ImageAttributes is created in the paint path but never disposed, which can leak GDI resources over time (especially during frequent repaints). Also, setting Matrix22 = 0.0f zeroes the blue channel; if the intent is only to apply opacity, keep the RGB diagonal at 1.0 and only change Matrix33. Fix by disposing ImageAttributes (e.g., via a using) and correcting the color matrix so it doesn’t unintentionally alter colors.

Suggested change
var matrix = new System.Drawing.Imaging.ColorMatrix { Matrix33 = LabelOpacity, Matrix22 = 0.0f };
var attributes = new System.Drawing.Imaging.ImageAttributes();
attributes.SetColorMatrix(matrix, System.Drawing.Imaging.ColorMatrixFlag.Default, System.Drawing.Imaging.ColorAdjustType.Bitmap);
var icon = Properties.Resources.general_Warning_16;
var iconRect = new Rectangle(rect.X, rect.Y + (rect.Height - icon.Height), icon.Width, icon.Height);
e.Graphics.DrawImage(icon, iconRect, 0, 0, icon.Width, icon.Height, GraphicsUnit.Pixel, attributes);
iconOffset = icon.Width + 2;
var matrix = new System.Drawing.Imaging.ColorMatrix
{
Matrix00 = 1.0f,
Matrix11 = 1.0f,
Matrix22 = 1.0f,
Matrix33 = LabelOpacity,
Matrix44 = 1.0f
};
using (var attributes = new System.Drawing.Imaging.ImageAttributes())
{
attributes.SetColorMatrix(matrix, System.Drawing.Imaging.ColorMatrixFlag.Default, System.Drawing.Imaging.ColorAdjustType.Bitmap);
var icon = Properties.Resources.general_Warning_16;
var iconRect = new Rectangle(rect.X, rect.Y + (rect.Height - icon.Height), icon.Width, icon.Height);
e.Graphics.DrawImage(icon, iconRect, 0, 0, icon.Width, icon.Height, GraphicsUnit.Pixel, attributes);
iconOffset = icon.Width + 2;
}

Copilot uses AI. Check for mistakes.
}

var textRect = new Rectangle(rect.X + iconOffset, rect.Y, rect.Width - iconOffset, rect.Height);
using (var b = new SolidBrush(Colors.LightText.ToFloat3Color().ToWinFormsColor(LabelOpacity)))
e.Graphics.DrawString(headerText, Font, b, textRect,
new StringFormat { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Center });

var condNode = node as VisibleNodeCondition;
Expand Down
8 changes: 8 additions & 0 deletions TombLib/TombLib/Catalogs/TEN Node Catalogs/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ Comment metadata entry reference (metadata block is indicated by a keyword which
new line.

- **!Section "SECTION"** - this will define where the node will be found inside Tomb Editor.

- **!Supported "TYPE" "TYPE" "..." - defines supported event types for a given node. Each event type should be
enclosed in quotes. If event is not supported by a node, it will display a warning message when misplaced.
Possible event types are: **OnVolumeEnter, OnVolumeInside, OnVolumeLeave, OnLoop, OnLoadGame, OnSaveGame,
OnLevelStart, OnLevelEnd, OnUseItem, OnFreeze**.

- **!Unsupported "TYPE" "TYPE" "..." - defines unsupported event types for a given node. Acts in an opposite way
Comment on lines +44 to +49
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown bold markup is unbalanced for the !Supported / !Unsupported entries (missing closing ** after the tag pattern), which will render the rest of the section incorrectly. Close the bold around just the tag syntax (e.g., **!Supported "TYPE" ...**) and consider the small grammar fix “If an event is not supported…” for clarity.

Suggested change
- **!Supported "TYPE" "TYPE" "..." - defines supported event types for a given node. Each event type should be
enclosed in quotes. If event is not supported by a node, it will display a warning message when misplaced.
Possible event types are: **OnVolumeEnter, OnVolumeInside, OnVolumeLeave, OnLoop, OnLoadGame, OnSaveGame,
OnLevelStart, OnLevelEnd, OnUseItem, OnFreeze**.
- **!Unsupported "TYPE" "TYPE" "..." - defines unsupported event types for a given node. Acts in an opposite way
- **!Supported "TYPE" "TYPE" "..."** - defines supported event types for a given node. Each event type should be
enclosed in quotes. If an event is not supported by a node, it will display a warning message when misplaced.
Possible event types are: **OnVolumeEnter, OnVolumeInside, OnVolumeLeave, OnLoop, OnLoadGame, OnSaveGame,
OnLevelStart, OnLevelEnd, OnUseItem, OnFreeze**.
- **!Unsupported "TYPE" "TYPE" "..."** - defines unsupported event types for a given node. Acts in an opposite way

Copilot uses AI. Check for mistakes.
to `!Supported`. Possible event types are equal to `!Supported`.

- **!Arguments "ARGDESC1" "ARGDESC2" "ARGDESC..."** - infinite amount of args, with **ARGDESC** parameters
separated by commas as follows:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using TombLib.LevelData;

namespace TombLib.LevelData.VisualScripting
{
Expand Down Expand Up @@ -78,6 +79,19 @@ public class NodeFunction
public bool Conditional { get; set; }
public string Signature { get; set; }
public List<ArgumentLayout> Arguments { get; private set; } = new List<ArgumentLayout>();
public List<EventType> SupportedEvents { get; private set; } = new List<EventType>();
public List<EventType> UnsupportedEvents { get; private set; } = new List<EventType>();

public bool IsUnsupported(EventType eventType)
{
if (SupportedEvents.Count > 0 && !SupportedEvents.Contains(eventType))
return true;

if (UnsupportedEvents.Count > 0 && UnsupportedEvents.Contains(eventType))
return true;

return false;
}

public override string ToString() => Name;
public override int GetHashCode() => (Name + Conditional.ToString() + Description + Signature + Arguments.Count.ToString()).GetHashCode();
Expand Down
23 changes: 23 additions & 0 deletions TombLib/TombLib/Utils/ScriptingUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TombLib.LevelData;
using TombLib.LevelData.VisualScripting;

namespace TombLib.Utils
Expand Down Expand Up @@ -59,6 +60,8 @@ public static class ScriptingUtils
private const string _nodeTypeId = _metadataPrefix + "condition";
private const string _nodeArgumentId = _metadataPrefix + "arguments";
private const string _nodeDescriptionId = _metadataPrefix + "description";
private const string _nodeSupportedId = _metadataPrefix + "supported";
private const string _nodeUnsupportedId = _metadataPrefix + "unsupported";
private const string _nodeLayoutNewLine = "newline";

public static string GameNodeScriptPath = Path.Combine("Scripts", "Engine", "NodeCatalogs");
Expand Down Expand Up @@ -156,6 +159,16 @@ private static List<NodeFunction> GetAllNodeFunctions(string path, List<NodeFunc
}
continue;
}
else if (comment.StartsWith(_nodeSupportedId, StringComparison.InvariantCultureIgnoreCase))
{
ParseEventTypeList(comment, _nodeSupportedId, nodeFunction.SupportedEvents);
continue;
}
else if (comment.StartsWith(_nodeUnsupportedId, StringComparison.InvariantCultureIgnoreCase))
{
ParseEventTypeList(comment, _nodeUnsupportedId, nodeFunction.UnsupportedEvents);
continue;
}
}

if (cPoint > 0)
Expand Down Expand Up @@ -257,6 +270,16 @@ private static List<NodeFunction> GetAllNodeFunctions(string path, List<NodeFunc
return result.OrderBy(n => n.Section).ToList();
}

private static void ParseEventTypeList(string comment, string tagId, List<EventType> targetList)
{
var values = TextExtensions.ExtractValues(comment.Substring(tagId.Length, comment.Length - tagId.Length));
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment.Substring(tagId.Length, comment.Length - tagId.Length) is equivalent to comment.Substring(tagId.Length) and is harder to read than necessary. Simplifying this reduces cognitive overhead and removes an easy place for accidental off-by-one edits later.

Suggested change
var values = TextExtensions.ExtractValues(comment.Substring(tagId.Length, comment.Length - tagId.Length));
var values = TextExtensions.ExtractValues(comment.Substring(tagId.Length));

Copilot uses AI. Check for mistakes.
foreach (var v in values)
{
if (Enum.TryParse(v.Trim(), out EventType eventType))
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum.TryParse here is case-sensitive, while the metadata tag match uses case-insensitive comparison. This can cause valid values (e.g., different casing in scripts) to be silently ignored. Use the Enum.TryParse(string, bool ignoreCase, out TEnum) overload with ignoreCase: true, and consider avoiding duplicates when adding to targetList to prevent repeated entries if multiple tags are present.

Suggested change
if (Enum.TryParse(v.Trim(), out EventType eventType))
if (Enum.TryParse(v.Trim(), true, out EventType eventType) && !targetList.Contains(eventType))

Copilot uses AI. Check for mistakes.
targetList.Add(eventType);
}
}

public static List<string> GetAllFunctionNames(string path, List<string> list = null, int depth = 0)
{
var result = list == null ? new List<string>() : list;
Expand Down
Loading