EVERYTHING

This commit is contained in:
niko
2026-06-01 17:19:55 +02:00
parent d063b81359
commit b62bac090b
1081 changed files with 1716544 additions and 0 deletions
+934
View File
@@ -0,0 +1,934 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using BlockSpace.Voxels;
using TMPro;
using UnityEngine;
public class Node : MonoBehaviour
{
private const float PortHighlightScale = 1.25f;
private const float PortPulseScale = 0.22f;
private const float PortPulseDuration = 0.16f;
private const float SelectedScale = 1.035f;
private static readonly Color SelectedTextColor = new Color(0.55f, 0.92f, 1f, 1f);
[Header("Identity")]
[SerializeField]
private int nodeId;
[Header("Definition")]
public NodeDefinition Definition;
[Header("Runtime")]
public List<NodePort> InputPorts = new List<NodePort>();
public List<NodePort> OutputPorts = new List<NodePort>();
private NodeBehavior behavior;
private NodeGraphManager graphManager;
private bool selected;
private bool visibleInEditMode = true;
private readonly List<NodeSerializedConnection> pendingSerializedConnections = new List<NodeSerializedConnection>();
private readonly Dictionary<string, BSValue> pendingInputValues = new Dictionary<string, BSValue>(StringComparer.Ordinal);
private readonly Dictionary<string, BSValue> pendingOutputValues = new Dictionary<string, BSValue>(StringComparer.Ordinal);
private readonly List<TMP_Text> cachedPortTexts = new List<TMP_Text>();
private readonly Dictionary<TMP_Text, Color> defaultTextColors = new Dictionary<TMP_Text, Color>();
private readonly List<Renderer> cachedRenderers = new List<Renderer>();
private Vector3 defaultScale = Vector3.one;
[Header("Node Name")]
public TMP_Text NodeNameText;
[Header("Boards")]
public GameObject SmallBoard;
public GameObject MediumBoard;
public GameObject LargeBoard;
public GameObject ExtraLargeBoard;
[Header("Input Port Parents")]
public Transform InputRunPortParent;
public Transform InputValuePortParent;
[Header("Output Port Parents")]
public Transform OutputRunPortParent;
public Transform OutputValuePortParent;
[Header("Input Text Parent")]
public Transform InputTextParent;
[Header("Output Text Parent")]
public Transform OutputTextParent;
[Header("Port Prefabs")]
public GameObject InputRunPortPrefab;
public GameObject InputValuePortPrefab;
public GameObject OutputRunPortPrefab;
public GameObject OutputValuePortPrefab;
[Header("Text Prefab")]
public TMP_Text PortTextPrefab;
[Header("Port Layout")]
public float FirstPortY;
public float PortYOffset = -1.25f;
[Header("Text Layout")]
public float FirstTextY = 0.0869f;
public float TextYOffset = -0.0618f;
private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
private static readonly int ColorId = Shader.PropertyToID("_Color");
private static readonly int TintColorId = Shader.PropertyToID("_TintColor");
public NodeBoardSize BoardSize { get; private set; }
public int NodeId => nodeId;
private void Awake()
{
defaultScale = base.transform.localScale;
CaptureSerializedRuntimeState();
ReinitializeFromDefinition();
}
private void Start()
{
NodeGraphManager.Instance?.RegisterNode(this);
}
private void LateUpdate()
{
UpdatePortVisualState();
UpdatePortTintState();
}
public void AssignGraph(NodeGraphManager manager)
{
graphManager = manager;
for (int i = 0; i < InputPorts.Count; i++)
{
InputPorts[i].Owner = this;
}
for (int j = 0; j < OutputPorts.Count; j++)
{
OutputPorts[j].Owner = this;
}
}
public void SetNodeId(int id)
{
nodeId = Mathf.Max(0, id);
}
public void ReinitializeFromDefinition()
{
if (Definition == null)
{
InputPorts.Clear();
OutputPorts.Clear();
ClearVisualCaches();
return;
}
BuildPorts();
BoardSize = Definition.GetBoardSize();
BuildVisuals();
CreateBehavior();
ApplySelectionVisual();
ApplyVisibility();
}
public bool TryGetPort(string portName, PortDirection direction, out NodePort port)
{
List<NodePort> list = ((direction == PortDirection.Input) ? InputPorts : OutputPorts);
for (int i = 0; i < list.Count; i++)
{
NodePort nodePort = list[i];
if (nodePort != null && string.Equals(nodePort.Name, portName, StringComparison.Ordinal))
{
port = nodePort;
return true;
}
}
port = null;
return false;
}
public bool TryGetPort(string portName, PortDirection direction, PortKind kind, out NodePort port)
{
if (TryGetPort(portName, direction, out port) && port.Kind == kind)
{
return true;
}
port = null;
return false;
}
public BSValue GetInputValue(string portName)
{
if (!TryGetPort(portName, PortDirection.Input, PortKind.Value, out var port))
{
return BSValue.None;
}
return port.Value;
}
public BSValue GetOutputValue(string portName)
{
if (!TryGetPort(portName, PortDirection.Output, PortKind.Value, out var port))
{
return BSValue.None;
}
return port.Value;
}
public bool TryGetInputGroup(string portName, out VoxelGroup group)
{
group = null;
if (TryGetPort(portName, PortDirection.Input, PortKind.Value, out var port))
{
return port.Value.TryResolveGroup(out group);
}
return false;
}
public VoxelGroup GetInputGroup(string portName)
{
TryGetInputGroup(portName, out var group);
return group;
}
public void SetInputValue(string portName, BSValue value)
{
if (TryGetPort(portName, PortDirection.Input, PortKind.Value, out var port))
{
if (graphManager != null)
{
graphManager.AssignInputValue(this, port, value, broadcast: true);
}
else
{
SetInputValueLocal(port, value, pulsePort: true);
}
}
}
public void SetOutputValue(string portName, BSValue value)
{
if (TryGetPort(portName, PortDirection.Output, PortKind.Value, out var port))
{
if (graphManager != null)
{
graphManager.ApplyOutputValue(this, port, value, broadcast: false);
}
else
{
SetOutputValueLocal(port, value);
}
}
}
public void SetOutputValueLocalOnly(string portName, BSValue value)
{
if (TryGetPort(portName, PortDirection.Output, PortKind.Value, out var port))
{
if (graphManager != null)
{
graphManager.ApplyOutputValue(this, port, value, broadcast: false);
}
else
{
SetOutputValueLocal(port, value);
}
}
}
public void TriggerRun(string outputPortName)
{
if (TryGetPort(outputPortName, PortDirection.Output, PortKind.Run, out var port))
{
if (graphManager != null)
{
graphManager.TriggerOutputRun(this, port, broadcast: false);
}
else
{
TriggerRunLocal(port);
}
}
}
public void ReceiveRun(string inputPortName)
{
if (TryGetPort(inputPortName, PortDirection.Input, PortKind.Run, out var port))
{
PulsePort(port);
behavior?.OnRun(inputPortName);
}
}
public void TickBehavior()
{
behavior?.OnGraphUpdate();
}
public bool Connect(string outputPortName, Node targetNode, string inputPortName)
{
if (graphManager == null || targetNode == null)
{
return false;
}
return graphManager.Connect(this, outputPortName, targetNode, inputPortName, broadcast: true);
}
public void Disconnect(string outputPortName, Node targetNode, string inputPortName)
{
if (!(graphManager == null) && !(targetNode == null))
{
graphManager.Disconnect(this, outputPortName, targetNode, inputPortName, broadcast: true);
}
}
public void SetSelected(bool isSelected)
{
if (selected != isSelected)
{
selected = isSelected;
ApplySelectionVisual();
}
}
public void SetVisibleInEditMode(bool visible)
{
if (visibleInEditMode != visible)
{
visibleInEditMode = visible;
ApplyVisibility();
}
}
public Bounds GetWorldBounds()
{
if (cachedRenderers.Count == 0)
{
CacheVisuals();
}
if (cachedRenderers.Count == 0)
{
return new Bounds(base.transform.position, Vector3.one * 0.2f);
}
Bounds result = new Bounds(cachedRenderers[0].bounds.center, cachedRenderers[0].bounds.size);
for (int i = 1; i < cachedRenderers.Count; i++)
{
if (cachedRenderers[i] != null)
{
result.Encapsulate(cachedRenderers[i].bounds);
}
}
return result;
}
public Vector3 GetPortWorldPosition(NodePort port)
{
if (port != null && port.VisualTransform != null)
{
return port.VisualTransform.position;
}
return base.transform.position;
}
public Vector3 GetPortWorldDirection(NodePort port)
{
if (port == null)
{
return base.transform.forward;
}
if (port.VisualTransform != null)
{
if (port.Direction != PortDirection.Output)
{
return -port.VisualTransform.right;
}
return port.VisualTransform.right;
}
if (port.Direction != PortDirection.Output)
{
return -base.transform.right;
}
return base.transform.right;
}
internal void SetInputValueLocal(NodePort port, BSValue value, bool pulsePort)
{
if (port != null && port.Direction == PortDirection.Input && port.Kind == PortKind.Value)
{
BSValue bSValue = value.Normalize();
bool num = !BSValueEquals(port.Value, bSValue);
port.Value = bSValue;
UpdatePortLabel(port);
if (pulsePort)
{
PulsePort(port);
}
if (num)
{
behavior?.OnInputValueChanged(port.Name, port.Value);
}
}
}
internal void SetOutputValueLocal(NodePort port, BSValue value)
{
if (port == null || port.Direction != PortDirection.Output || port.Kind != PortKind.Value)
{
return;
}
port.Value = value.Normalize();
UpdatePortLabel(port);
PulsePort(port);
for (int i = 0; i < port.Connections.Count; i++)
{
NodePort nodePort = port.Connections[i];
if (nodePort != null && !(nodePort.Owner == null))
{
nodePort.Owner.SetInputValueLocal(nodePort, port.Value, pulsePort: true);
}
}
}
internal void TriggerRunLocal(NodePort port)
{
if (port == null || port.Direction != PortDirection.Output || port.Kind != PortKind.Run)
{
return;
}
PulsePort(port);
for (int i = 0; i < port.Connections.Count; i++)
{
NodePort nodePort = port.Connections[i];
if (nodePort != null && !(nodePort.Owner == null))
{
nodePort.Owner.ReceiveRun(nodePort.Name);
}
}
}
internal void PulsePort(NodePort port)
{
if (port != null)
{
port.PulseUntil = Mathf.Max(port.PulseUntil, Time.unscaledTime + 0.16f);
}
}
internal void SetPortHighlight(NodePort port, bool highlighted)
{
if (port != null)
{
port.Highlighted = highlighted;
}
}
internal List<NodeSerializedConnection> ConsumePendingSerializedConnections()
{
List<NodeSerializedConnection> result = new List<NodeSerializedConnection>(pendingSerializedConnections);
pendingSerializedConnections.Clear();
return result;
}
private void CaptureSerializedRuntimeState()
{
pendingSerializedConnections.Clear();
pendingInputValues.Clear();
pendingOutputValues.Clear();
CapturePortState(InputPorts, pendingInputValues, captureConnections: false);
CapturePortState(OutputPorts, pendingOutputValues, captureConnections: true);
}
private void CapturePortState(List<NodePort> ports, Dictionary<string, BSValue> valueStore, bool captureConnections)
{
if (ports == null)
{
return;
}
for (int i = 0; i < ports.Count; i++)
{
NodePort nodePort = ports[i];
if (nodePort == null || string.IsNullOrEmpty(nodePort.Name))
{
continue;
}
valueStore[nodePort.Name] = nodePort.Value.Normalize();
if (!captureConnections || nodePort.Direction != PortDirection.Output || nodePort.Connections == null)
{
continue;
}
for (int j = 0; j < nodePort.Connections.Count; j++)
{
NodePort nodePort2 = nodePort.Connections[j];
if (nodePort2 != null && !(nodePort2.Owner == null) && !string.IsNullOrEmpty(nodePort2.Name))
{
pendingSerializedConnections.Add(new NodeSerializedConnection
{
outputPortName = nodePort.Name,
inputNode = nodePort2.Owner,
inputPortName = nodePort2.Name
});
}
}
}
}
private void BuildPorts()
{
InputPorts.Clear();
OutputPorts.Clear();
for (int i = 0; i < Definition.InputPorts.Count; i++)
{
NodePortDefinition nodePortDefinition = Definition.InputPorts[i];
pendingInputValues.TryGetValue(nodePortDefinition.PortName, out var value);
InputPorts.Add(new NodePort
{
Index = i,
Name = nodePortDefinition.PortName,
Direction = PortDirection.Input,
Kind = nodePortDefinition.Kind,
Value = (value.IsInitialized ? value.Normalize() : BSValue.FromText(nodePortDefinition.DefaultValue)),
Owner = this
});
}
for (int j = 0; j < Definition.OutputPorts.Count; j++)
{
NodePortDefinition nodePortDefinition2 = Definition.OutputPorts[j];
pendingOutputValues.TryGetValue(nodePortDefinition2.PortName, out var value2);
OutputPorts.Add(new NodePort
{
Index = j,
Name = nodePortDefinition2.PortName,
Direction = PortDirection.Output,
Kind = nodePortDefinition2.Kind,
Value = (value2.IsInitialized ? value2.Normalize() : BSValue.FromText(nodePortDefinition2.DefaultValue)),
Owner = this
});
}
}
private void BuildVisuals()
{
ApplyBoardSize();
ApplyNodeName();
ClearChildren(InputRunPortParent);
ClearChildren(InputValuePortParent);
ClearChildren(OutputRunPortParent);
ClearChildren(OutputValuePortParent);
ClearChildren(InputTextParent);
ClearChildren(OutputTextParent);
ClearVisualCaches();
BuildInputVisuals();
BuildOutputVisuals();
CacheVisuals();
}
private void ApplyBoardSize()
{
if (SmallBoard != null)
{
SmallBoard.SetActive(value: false);
}
if (MediumBoard != null)
{
MediumBoard.SetActive(value: false);
}
if (LargeBoard != null)
{
LargeBoard.SetActive(value: false);
}
if (ExtraLargeBoard != null)
{
ExtraLargeBoard.SetActive(value: false);
}
switch (BoardSize)
{
case NodeBoardSize.Small:
if (SmallBoard != null)
{
SmallBoard.SetActive(value: true);
}
break;
case NodeBoardSize.Medium:
if (MediumBoard != null)
{
MediumBoard.SetActive(value: true);
}
break;
case NodeBoardSize.Large:
if (LargeBoard != null)
{
LargeBoard.SetActive(value: true);
}
break;
case NodeBoardSize.ExtraLarge:
if (ExtraLargeBoard != null)
{
ExtraLargeBoard.SetActive(value: true);
}
break;
}
}
private void ApplyNodeName()
{
if (NodeNameText != null)
{
NodeNameText.text = ((Definition != null) ? Definition.NodeName : "Node");
}
}
private void BuildInputVisuals()
{
for (int i = 0; i < InputPorts.Count; i++)
{
NodePort nodePort = InputPorts[i];
bool num = nodePort.Kind == PortKind.Run;
Transform transform = (num ? InputRunPortParent : InputValuePortParent);
GameObject gameObject = (num ? InputRunPortPrefab : InputValuePortPrefab);
if (!(transform == null) && !(gameObject == null))
{
GameObject gameObject2 = UnityEngine.Object.Instantiate(gameObject, transform);
gameObject2.name = nodePort.Name;
Vector3 localPosition = gameObject2.transform.localPosition;
localPosition.y = FirstPortY + PortYOffset * (float)i;
gameObject2.transform.localPosition = localPosition;
nodePort.AttachVisual(gameObject2.transform);
nodePort.AttachVisualRenderers(gameObject2.GetComponentsInChildren<Renderer>(includeInactive: true));
if (PortTextPrefab != null && InputTextParent != null)
{
TMP_Text tMP_Text = UnityEngine.Object.Instantiate(PortTextPrefab, InputTextParent);
tMP_Text.name = nodePort.Name + " Text";
Vector3 localPosition2 = tMP_Text.transform.localPosition;
localPosition2.y = FirstTextY + TextYOffset * (float)i;
tMP_Text.transform.localPosition = localPosition2;
nodePort.AttachLabel(tMP_Text);
UpdatePortLabel(nodePort);
RegisterPortText(tMP_Text);
}
}
}
}
private void BuildOutputVisuals()
{
for (int i = 0; i < OutputPorts.Count; i++)
{
NodePort nodePort = OutputPorts[i];
bool num = nodePort.Kind == PortKind.Run;
Transform transform = (num ? OutputRunPortParent : OutputValuePortParent);
GameObject gameObject = (num ? OutputRunPortPrefab : OutputValuePortPrefab);
if (!(transform == null) && !(gameObject == null))
{
GameObject gameObject2 = UnityEngine.Object.Instantiate(gameObject, transform);
gameObject2.name = nodePort.Name;
Vector3 localPosition = gameObject2.transform.localPosition;
localPosition.y = FirstPortY + PortYOffset * (float)i;
gameObject2.transform.localPosition = localPosition;
nodePort.AttachVisual(gameObject2.transform);
nodePort.AttachVisualRenderers(gameObject2.GetComponentsInChildren<Renderer>(includeInactive: true));
if (PortTextPrefab != null && OutputTextParent != null)
{
TMP_Text tMP_Text = UnityEngine.Object.Instantiate(PortTextPrefab, OutputTextParent);
tMP_Text.name = nodePort.Name + " Text";
Vector3 localPosition2 = tMP_Text.transform.localPosition;
localPosition2.y = FirstTextY + TextYOffset * (float)i;
tMP_Text.transform.localPosition = localPosition2;
nodePort.AttachLabel(tMP_Text);
UpdatePortLabel(nodePort);
RegisterPortText(tMP_Text);
}
}
}
}
private void UpdatePortLabel(NodePort port)
{
if (port != null && !(port.LabelText == null))
{
port.LabelText.text = ((port.Kind == PortKind.Value) ? (port.Name + ": " + FormatPortValue(port.Value)) : port.Name);
}
}
private static string FormatPortValue(BSValue value)
{
value = value.Normalize();
return value.Kind switch
{
BSValueKind.None => "None",
BSValueKind.Number => value.AsText(),
BSValueKind.Text => string.IsNullOrEmpty(value.AsText()) ? "\"\"" : value.AsText(),
BSValueKind.Bool => value.AsBool() ? "True" : "False",
BSValueKind.Player => $"Player {value.AsPlayerId()}",
BSValueKind.Group => $"Group {value.AsGroupId()}",
_ => value.ToString(),
};
}
private void RegisterPortText(TMP_Text text)
{
if (!(text == null))
{
cachedPortTexts.Add(text);
if (!defaultTextColors.ContainsKey(text))
{
defaultTextColors.Add(text, text.color);
}
}
}
private void ClearVisualCaches()
{
cachedPortTexts.Clear();
defaultTextColors.Clear();
cachedRenderers.Clear();
}
private void CacheVisuals()
{
cachedRenderers.Clear();
cachedRenderers.AddRange(GetComponentsInChildren<Renderer>(includeInactive: true));
cachedPortTexts.Clear();
defaultTextColors.Clear();
TMP_Text[] componentsInChildren = GetComponentsInChildren<TMP_Text>(includeInactive: true);
for (int i = 0; i < componentsInChildren.Length; i++)
{
RegisterPortText(componentsInChildren[i]);
}
}
private void ApplySelectionVisual()
{
base.transform.localScale = defaultScale * (selected ? 1.035f : 1f);
for (int i = 0; i < cachedPortTexts.Count; i++)
{
TMP_Text tMP_Text = cachedPortTexts[i];
if (!(tMP_Text == null))
{
Color value;
Color color = (defaultTextColors.TryGetValue(tMP_Text, out value) ? value : Color.white);
tMP_Text.color = (selected ? SelectedTextColor : color);
}
}
}
private void ApplyVisibility()
{
for (int i = 0; i < cachedRenderers.Count; i++)
{
if (cachedRenderers[i] != null)
{
cachedRenderers[i].enabled = visibleInEditMode;
}
}
for (int j = 0; j < cachedPortTexts.Count; j++)
{
if (cachedPortTexts[j] != null)
{
cachedPortTexts[j].enabled = visibleInEditMode;
}
}
}
private void UpdatePortVisualState()
{
UpdatePortVisualState(InputPorts);
UpdatePortVisualState(OutputPorts);
}
private void UpdatePortTintState()
{
UpdatePortTintState(InputPorts);
UpdatePortTintState(OutputPorts);
}
private void UpdatePortTintState(List<NodePort> ports)
{
for (int i = 0; i < ports.Count; i++)
{
NodePort nodePort = ports[i];
if (nodePort == null || nodePort.Kind != PortKind.Value || nodePort.VisualTransform == null)
{
continue;
}
if (nodePort.VisualRenderers == null)
{
nodePort.AttachVisualRenderers(nodePort.VisualTransform.GetComponentsInChildren<Renderer>(includeInactive: true));
}
if (nodePort.VisualRenderers == null || nodePort.VisualRenderers.Length == 0)
{
continue;
}
BSValueKind kind = ((nodePort.Direction != PortDirection.Input) ? nodePort.Value.Normalize().Kind : ((nodePort.SourceGroupId > 0) ? BSValueKind.Group : ((nodePort.IncomingConnection == null) ? nodePort.Value.Normalize().Kind : nodePort.IncomingConnection.Value.Normalize().Kind)));
Color color = NodeWirePalette.ForValueKind(kind);
if (nodePort.LastTint == color)
{
continue;
}
nodePort.LastTint = color;
NodePort nodePort2 = nodePort;
if (nodePort2.VisualBlock == null)
{
nodePort2.VisualBlock = new MaterialPropertyBlock();
}
nodePort.VisualBlock.Clear();
nodePort.VisualBlock.SetColor(BaseColorId, color);
nodePort.VisualBlock.SetColor(ColorId, color);
nodePort.VisualBlock.SetColor(TintColorId, color);
for (int j = 0; j < nodePort.VisualRenderers.Length; j++)
{
Renderer renderer = nodePort.VisualRenderers[j];
if (!(renderer == null))
{
renderer.SetPropertyBlock(nodePort.VisualBlock);
Material sharedMaterial = renderer.sharedMaterial;
if (sharedMaterial != null && !sharedMaterial.HasProperty(BaseColorId) && !sharedMaterial.HasProperty(ColorId) && !sharedMaterial.HasProperty(TintColorId))
{
renderer.material.color = color;
}
}
}
}
}
private void UpdatePortVisualState(List<NodePort> ports)
{
for (int i = 0; i < ports.Count; i++)
{
NodePort nodePort = ports[i];
if (nodePort != null && !(nodePort.VisualTransform == null))
{
float num = 0f;
if (nodePort.PulseUntil > Time.unscaledTime)
{
num = Mathf.Clamp01((nodePort.PulseUntil - Time.unscaledTime) / 0.16f);
}
float num2 = (nodePort.Highlighted ? 1.25f : 1f);
num2 += num * 0.22f;
nodePort.VisualTransform.localScale = nodePort.BaseLocalScale * num2;
}
}
}
private void ClearChildren(Transform parent)
{
if (parent == null)
{
return;
}
for (int num = parent.childCount - 1; num >= 0; num--)
{
Transform child = parent.GetChild(num);
if (!(child == null))
{
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(child.gameObject);
}
else
{
UnityEngine.Object.DestroyImmediate(child.gameObject);
}
}
}
}
private void CreateBehavior()
{
if (behavior != null)
{
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(behavior);
}
else
{
UnityEngine.Object.DestroyImmediate(behavior);
}
behavior = null;
}
if (Definition == null)
{
return;
}
Type type = FindType(Definition.BehaviorClassName);
if (!(type == null) && typeof(NodeBehavior).IsAssignableFrom(type))
{
behavior = base.gameObject.AddComponent(type) as NodeBehavior;
if (behavior != null)
{
behavior.Node = this;
}
}
}
private static Type FindType(string typeName)
{
Type type = Type.GetType(typeName);
if (type != null)
{
return type;
}
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
type = assemblies[i].GetType(typeName);
if (type != null)
{
return type;
}
}
return null;
}
private static bool BSValueEquals(BSValue a, BSValue b)
{
a = a.Normalize();
b = b.Normalize();
if (a.Kind != b.Kind)
{
return false;
}
return a.Kind switch
{
BSValueKind.Number => Math.Abs(a.Number - b.Number) <= 0.0001,
BSValueKind.Text => string.Equals(a.Text ?? string.Empty, b.Text ?? string.Empty, StringComparison.Ordinal),
BSValueKind.Bool => a.Bool == b.Bool,
BSValueKind.Player => a.PlayerId == b.PlayerId,
BSValueKind.Group => a.GroupId == b.GroupId,
_ => true,
};
}
}