Files
ueblueprint/js/entity/ObjectEntity.js
barsdeveloper 6f674b284d PCG nodes and other fixes (#14)
* Various fixes

* Fix name from SettingsInterface

* Allow path lookbehind for unknown keys entity

* Subraph object name

* Several fixes

* Various fixes

* Fix colors

* Various pin types
2023-09-14 23:07:09 +02:00

1223 lines
47 KiB
JavaScript
Executable File

import Configuration from "../Configuration.js"
import FunctionReferenceEntity from "./FunctionReferenceEntity.js"
import GuidEntity from "./GuidEntity.js"
import IdentifierEntity from "./IdentifierEntity.js"
import IEntity from "./IEntity.js"
import IntegerEntity from "./IntegerEntity.js"
import LinearColorEntity from "./LinearColorEntity.js"
import MacroGraphReferenceEntity from "./MacroGraphReferenceEntity.js"
import MirroredEntity from "./MirroredEntity.js"
import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
import PinEntity from "./PinEntity.js"
import SVGIcon from "../SVGIcon.js"
import SymbolEntity from "./SymbolEntity.js"
import Union from "./Union.js"
import UnknownPinEntity from "./UnknownPinEntity.js"
import Utility from "../Utility.js"
import VariableReferenceEntity from "./VariableReferenceEntity.js"
/** @typedef {import("./VectorEntity.js").default} VectorEntity */
export default class ObjectEntity extends IEntity {
static attributes = {
...super.attributes,
Class: {
type: ObjectReferenceEntity,
},
Name: {
default: "",
},
Archetype: {
type: ObjectReferenceEntity,
},
ExportPath: {
type: ObjectReferenceEntity,
},
ObjectRef: {
type: ObjectReferenceEntity,
},
BlueprintElementType: {
type: ObjectReferenceEntity
},
BlueprintElementInstance: {
type: ObjectReferenceEntity
},
PinTags: {
type: [null],
inlined: true,
},
PinNames: {
type: [String],
inlined: true,
},
AxisKey: {
type: SymbolEntity,
},
InputAxisKey: {
type: SymbolEntity,
},
NumAdditionalInputs: {
type: Number,
},
bIsPureFunc: {
type: Boolean,
},
bIsConstFunc: {
type: Boolean,
},
bIsCaseSensitive: {
type: Boolean,
},
VariableReference: {
type: VariableReferenceEntity,
},
SelfContextInfo: {
type: SymbolEntity,
},
DelegatePropertyName: {
type: String,
},
DelegateOwnerClass: {
type: ObjectReferenceEntity,
},
ComponentPropertyName: {
type: String,
},
EventReference: {
type: FunctionReferenceEntity,
},
FunctionReference: {
type: FunctionReferenceEntity,
},
CustomFunctionName: {
type: String,
},
TargetType: {
type: ObjectReferenceEntity,
},
MacroGraphReference: {
type: MacroGraphReferenceEntity,
},
Enum: {
type: ObjectReferenceEntity,
},
EnumEntries: {
type: [String],
inlined: true,
},
InputKey: {
type: SymbolEntity,
},
MaterialFunction: {
type: ObjectReferenceEntity,
},
bOverrideFunction: {
type: Boolean,
},
bInternalEvent: {
type: Boolean,
},
bConsumeInput: {
type: Boolean,
},
bExecuteWhenPaused: {
type: Boolean,
},
bOverrideParentBinding: {
type: Boolean,
},
bControl: {
type: Boolean,
},
bAlt: {
type: Boolean,
},
bShift: {
type: Boolean,
},
bCommand: {
type: Boolean,
},
CommentColor: {
type: LinearColorEntity,
},
bCommentBubbleVisible_InDetailsPanel: {
type: Boolean,
},
bColorCommentBubble: {
type: Boolean,
},
ProxyFactoryFunctionName: {
type: String,
},
ProxyFactoryClass: {
type: ObjectReferenceEntity,
},
ProxyClass: {
type: ObjectReferenceEntity,
},
R: {
type: Number,
},
G: {
type: Number,
},
StructType: {
type: ObjectReferenceEntity,
},
MaterialExpression: {
type: ObjectReferenceEntity,
},
MaterialExpressionComment: {
type: ObjectReferenceEntity,
},
MoveMode: {
type: SymbolEntity,
},
TimelineName: {
type: String,
},
TimelineGuid: {
type: GuidEntity,
},
SizeX: {
type: new MirroredEntity(ObjectEntity, "NodeWidth"),
},
SizeY: {
type: new MirroredEntity(ObjectEntity, "NodeHeight"),
},
Text: {
type: new MirroredEntity(ObjectEntity, "NodeComment"),
},
MaterialExpressionEditorX: {
type: new MirroredEntity(ObjectEntity, "NodePosX"),
},
MaterialExpressionEditorY: {
type: new MirroredEntity(ObjectEntity, "NodePosY"),
},
NodeTitle: {
type: String,
},
NodeTitleColor: {
type: LinearColorEntity,
},
PositionX: {
type: new MirroredEntity(ObjectEntity, "NodePosX"),
},
PositionY: {
type: new MirroredEntity(ObjectEntity, "NodePosY"),
},
PCGNode: {
type: ObjectReferenceEntity,
},
NodePosX: {
type: IntegerEntity,
},
NodePosY: {
type: IntegerEntity,
},
NodeWidth: {
type: IntegerEntity,
},
NodeHeight: {
type: IntegerEntity,
},
Graph: {
type: ObjectReferenceEntity,
},
SubgraphInstance: {
type: String,
},
SettingsInterface: {
type: ObjectReferenceEntity,
},
InputPins: {
type: [ObjectReferenceEntity],
inlined: true,
},
OutputPins: {
type: [ObjectReferenceEntity],
inlined: true,
},
bExposeToLibrary: {
type: Boolean,
},
bCanRenameNode: {
type: Boolean,
},
bCommentBubblePinned: {
type: Boolean,
},
bCommentBubbleVisible: {
type: Boolean,
},
NodeComment: {
type: String,
},
AdvancedPinDisplay: {
type: IdentifierEntity,
},
EnabledState: {
type: IdentifierEntity,
},
NodeGuid: {
type: GuidEntity,
},
ErrorType: {
type: IntegerEntity,
},
ErrorMsg: {
type: String,
},
CustomProperties: {
type: [new Union(PinEntity, UnknownPinEntity)],
},
}
static nameRegex = /^(\w+?)(?:_(\d+))?$/
static sequencerScriptingNameRegex = /\/Script\/SequencerScripting\.MovieSceneScripting(.+)Channel/
static #keyName = {
"A_AccentGrave": "à",
"Add": "Num +",
"C_Cedille": "ç",
"Decimal": "Num .",
"Divide": "Num /",
"E_AccentAigu": "é",
"E_AccentGrave": "è",
"F1": "F1", // Otherwise F and number will be separated
"F10": "F10",
"F11": "F11",
"F12": "F12",
"F2": "F2",
"F3": "F3",
"F4": "F4",
"F5": "F5",
"F6": "F6",
"F7": "F7",
"F8": "F8",
"F9": "F9",
"Gamepad_Special_Left_X": "Touchpad Button X Axis",
"Gamepad_Special_Left_Y": "Touchpad Button Y Axis",
"Mouse2D": "Mouse XY 2D-Axis",
"Multiply": "Num *",
"Section": "§",
"Subtract": "Num -",
"Tilde": "`",
}
static {
this.cleanupAttributes(this.attributes)
}
/** @param {String} value */
static keyName(value) {
let result = ObjectEntity.#keyName[value]
if (result) {
return result
}
result = Utility.numberFromText(value)?.toString()
if (result) {
return result
}
const match = value.match(/NumPad([a-zA-Z]+)/)
if (match) {
result = Utility.numberFromText(match[1])
if (result) {
return "Num " + result
}
}
}
constructor(values = {}, suppressWarns = false) {
let keys = Object.keys(values)
if (keys.some(k => k.startsWith(Configuration.subObjectAttributeNamePrefix))) {
let subObjectsValues = keys
.filter(k => k.startsWith(Configuration.subObjectAttributeNamePrefix))
.reduce(
(acc, k) => {
acc[k] = values[k]
return acc
},
{}
)
// Reorder sub objects to be the first entries
values = {
...subObjectsValues,
...values,
}
}
super(values, suppressWarns)
/** @type {ObjectReferenceEntity} */ this.Class
/** @type {String} */ this.Name
/** @type {ObjectReferenceEntity} */ this.Archetype
/** @type {ObjectReferenceEntity} */ this.ExportPath
/** @type {ObjectReferenceEntity} */ this.ObjectRef
/** @type {ObjectReferenceEntity} */ this.BlueprintElementType
/** @type {ObjectReferenceEntity} */ this.BlueprintElementInstance
/** @type {null[]} */ this.PinTags
/** @type {String[]} */ this.PinNames
/** @type {SymbolEntity} */ this.AxisKey
/** @type {SymbolEntity} */ this.InputAxisKey
/** @type {Number} */ this.NumAdditionalInputs
/** @type {Boolean} */ this.bIsPureFunc
/** @type {Boolean} */ this.bIsConstFunc
/** @type {Boolean} */ this.bIsCaseSensitive
/** @type {VariableReferenceEntity} */ this.VariableReference
/** @type {SymbolEntity} */ this.SelfContextInfo
/** @type {String} */ this.DelegatePropertyName
/** @type {ObjectReferenceEntity} */ this.DelegateOwnerClass
/** @type {FunctionReferenceEntity} */ this.ComponentPropertyName
/** @type {FunctionReferenceEntity} */ this.EventReference
/** @type {FunctionReferenceEntity} */ this.FunctionReference
/** @type {String} */ this.CustomFunctionName
/** @type {ObjectReferenceEntity} */ this.TargetType
/** @type {MacroGraphReferenceEntity} */ this.MacroGraphReference
/** @type {ObjectReferenceEntity} */ this.Enum
/** @type {String[]} */ this.EnumEntries
/** @type {SymbolEntity} */ this.InputKey
/** @type {ObjectReferenceEntity} */ this.MaterialFunction
/** @type {Boolean} */ this.bOverrideFunction
/** @type {Boolean} */ this.bInternalEvent
/** @type {Boolean} */ this.bConsumeInput
/** @type {Boolean} */ this.bExecuteWhenPaused
/** @type {Boolean} */ this.bOverrideParentBinding
/** @type {Boolean} */ this.bControl
/** @type {Boolean} */ this.bAlt
/** @type {Boolean} */ this.bShift
/** @type {Boolean} */ this.bCommand
/** @type {LinearColorEntity} */ this.CommentColor
/** @type {Boolean} */ this.bCommentBubbleVisible_InDetailsPanel
/** @type {Boolean} */ this.bColorCommentBubble
/** @type {String} */ this.ProxyFactoryFunctionName
/** @type {ObjectReferenceEntity} */ this.ProxyFactoryClass
/** @type {ObjectReferenceEntity} */ this.ProxyClass
/** @type {Number} */ this.R
/** @type {Number} */ this.G
/** @type {ObjectReferenceEntity} */ this.StructType
/** @type {ObjectReferenceEntity} */ this.MaterialExpression
/** @type {ObjectReferenceEntity} */ this.MaterialExpressionComment
/** @type {SymbolEntity} */ this.MoveMode
/** @type {String} */ this.TimelineName
/** @type {GuidEntity} */ this.TimelineGuid
/** @type {MirroredEntity} */ this.SizeX
/** @type {MirroredEntity} */ this.SizeY
/** @type {MirroredEntity} */ this.Text
/** @type {MirroredEntity} */ this.MaterialExpressionEditorX
/** @type {MirroredEntity} */ this.MaterialExpressionEditorY
/** @type {String} */ this.NodeTitle
/** @type {LinearColorEntity} */ this.NodeTitleColor
/** @type {MirroredEntity} */ this.PositionX
/** @type {MirroredEntity} */ this.PositionY
/** @type {ObjectReferenceEntity} */ this.PCGNode
/** @type {IntegerEntity} */ this.NodePosX
/** @type {IntegerEntity} */ this.NodePosY
/** @type {IntegerEntity} */ this.NodeWidth
/** @type {IntegerEntity} */ this.NodeHeight
/** @type {ObjectReferenceEntity} */ this.Graph
/** @type {String} */ this.SubgraphInstance
/** @type {ObjectReferenceEntity} */ this.SettingsInterface
/** @type {ObjectReferenceEntity[]} */ this.InputPins
/** @type {ObjectReferenceEntity[]} */ this.OutputPins
/** @type {Boolean} */ this.bExposeToLibrary
/** @type {Boolean} */ this.bCanRenameNode
/** @type {Boolean} */ this.bCommentBubblePinned
/** @type {Boolean} */ this.bCommentBubbleVisible
/** @type {String} */ this.Text
/** @type {String} */ this.NodeComment
/** @type {IdentifierEntity} */ this.AdvancedPinDisplay
/** @type {IdentifierEntity} */ this.EnabledState
/** @type {GuidEntity} */ this.NodeGuid
/** @type {IntegerEntity} */ this.ErrorType
/** @type {String} */ this.ErrorMsg
/** @type {(PinEntity | UnknownPinEntity)[]} */ this.CustomProperties
// Legacy nodes cleanup
if (this["Pins"] instanceof Array) {
this["Pins"]
.forEach(
/** @param {ObjectReferenceEntity} objectReference */
objectReference => {
const pinObject = this[Configuration.subObjectAttributeNameFromReference(objectReference, true)]
if (pinObject) {
const pinEntity = PinEntity.fromLegacyObject(pinObject)
pinEntity.LinkedTo = []
this.getCustomproperties(true).push(pinEntity)
}
})
delete this["Pins"]
}
this.Class?.sanitize()
if (this.MacroGraphReference) {
this.MacroGraphReference.MacroGraph?.sanitize()
this.MacroGraphReference.GraphBlueprint?.sanitize()
}
/** @type {ObjectEntity} */
const materialSubobject = this.getMaterialSubobject()
if (materialSubobject) {
const obj = materialSubobject
obj.SizeX && (obj.SizeX.getter = () => this.NodeWidth)
obj.SizeY && (obj.SizeY.getter = () => this.NodeHeight)
obj.Text && (obj.Text.getter = () => this.NodeComment)
obj.MaterialExpressionEditorX && (obj.MaterialExpressionEditorX.getter = () => this.NodePosX)
obj.MaterialExpressionEditorY && (obj.MaterialExpressionEditorY.getter = () => this.NodePosY)
}
/** @type {ObjectEntity} */
const pcgObject = this.getPcgSubobject()
if (pcgObject) {
pcgObject.PositionX && (pcgObject.PositionX.getter = () => this.NodePosX)
pcgObject.PositionY && (pcgObject.PositionY.getter = () => this.NodePosY)
}
let inputIndex = 0
let outputIndex = 0
this.CustomProperties?.forEach((pinEntity, i) => {
pinEntity.objectEntity = this
pinEntity.pinIndex = pinEntity.isInput()
? inputIndex++
: pinEntity.isOutput()
? outputIndex++
: i
})
}
getClass() {
return (this.Class?.path ? this.Class.path : this.Class?.type)
?? (this.ExportPath?.path ? this.ExportPath.path : this.ExportPath?.type)
?? ""
}
getType() {
let classValue = this.getClass()
if (this.MacroGraphReference?.MacroGraph?.path) {
return this.MacroGraphReference.MacroGraph.path
}
if (this.MaterialExpression) {
return this.MaterialExpression.type
}
return classValue
}
getObjectName(dropCounter = false) {
if (dropCounter) {
return this.getNameAndCounter()[0]
}
return this.Name
}
/** @returns {[String, Number]} */
getNameAndCounter() {
const result = this.getObjectName(false).match(ObjectEntity.nameRegex)
let name = ""
let counter = null
if (result) {
if (result.length > 1) {
name = result[1]
}
if (result.length > 2) {
counter = parseInt(result[2])
}
return [name, counter]
}
return ["", 0]
}
getCounter() {
return this.getNameAndCounter()[1]
}
getNodeWidth() {
return this.NodeWidth
?? this.isComment() ? Configuration.defaultCommentWidth : undefined
}
/** @param {Number} value */
setNodeWidth(value) {
if (!this.NodeWidth) {
this.NodeWidth = new IntegerEntity()
}
this.NodeWidth.value = value
}
getNodeHeight() {
return this.NodeHeight
?? this.isComment() ? Configuration.defaultCommentHeight : undefined
}
/** @param {Number} value */
setNodeHeight(value) {
if (!this.NodeHeight) {
this.NodeHeight = new IntegerEntity()
}
this.NodeHeight.value = value
}
getNodePosX() {
return this.NodePosX?.value ?? 0
}
/** @param {Number} value */
setNodePosX(value) {
if (!this.NodePosX) {
this.NodePosX = new IntegerEntity()
}
this.NodePosX.value = Math.round(value)
}
getNodePosY() {
return this.NodePosY?.value ?? 0
}
/** @param {Number} value */
setNodePosY(value) {
if (!this.NodePosY) {
this.NodePosY = new IntegerEntity()
}
this.NodePosY.value = Math.round(value)
}
getCustomproperties(canCreate = false) {
if (canCreate && !this.CustomProperties) {
this.CustomProperties = []
}
return this.CustomProperties ?? []
}
/** @returns {PinEntity[]} */
getPinEntities() {
return this.getCustomproperties().filter(v => v.constructor === PinEntity)
}
switchTarget() {
const switchMatch = this.getClass().match(Configuration.switchTargetPattern)
if (switchMatch) {
return switchMatch[1]
}
}
isEvent() {
switch (this.getClass()) {
case Configuration.paths.componentBoundEvent:
case Configuration.paths.customEvent:
case Configuration.paths.event:
case Configuration.paths.inputAxisKeyEvent:
case Configuration.paths.inputVectorAxisEvent:
return true
}
return false
}
isComment() {
switch (this.getClass()) {
case Configuration.paths.comment:
case Configuration.paths.materialGraphNodeComment:
return true
}
return false
}
isMaterial() {
return this.getClass() === Configuration.paths.materialGraphNode
}
/** @return {ObjectEntity} */
getMaterialSubobject() {
const expression = this.MaterialExpression ?? this.MaterialExpressionComment
return expression
? this[Configuration.subObjectAttributeNameFromReference(expression, true)]
: null
}
isPcg() {
return this.getClass() === Configuration.paths.pcgEditorGraphNode
|| this.getPcgSubobject()
}
/** @return {ObjectEntity} */
getPcgSubobject() {
const node = this.PCGNode
return node
? this[Configuration.subObjectAttributeNameFromReference(node, true)]
: null
}
/** @return {ObjectEntity} */
getSettingsObject() {
const settings = this.SettingsInterface
return settings
? this[Configuration.subObjectAttributeNameFromReference(settings, true)]
: null
}
/** @return {ObjectEntity} */
getSubgraphObject() {
const node = this.SubgraphInstance
return node
? this[Configuration.subObjectAttributeNameFromName(node)]
: null
}
isDevelopmentOnly() {
const nodeClass = this.getClass()
return this.EnabledState?.toString() === "DevelopmentOnly"
|| nodeClass.includes("Debug", Math.max(0, nodeClass.lastIndexOf(".")))
}
getHIDAttribute() {
return this.InputKey ?? this.AxisKey ?? this.InputAxisKey
}
getDelegatePin() {
return this.getCustomproperties().find(pin => pin.PinType.PinCategory === "delegate")
}
/** @returns {String} */
nodeDisplayName() {
let input
switch (this.getType()) {
case Configuration.paths.asyncAction:
if (this.ProxyFactoryFunctionName) {
return Utility.formatStringName(this.ProxyFactoryFunctionName)
}
case Configuration.paths.componentBoundEvent:
return `${Utility.formatStringName(this.DelegatePropertyName)} (${this.ComponentPropertyName})`
case Configuration.paths.createDelegate:
return "Create Event"
case Configuration.paths.customEvent:
if (this.CustomFunctionName) {
return this.CustomFunctionName
}
case Configuration.paths.dynamicCast:
if (!this.TargetType) {
return "Bad cast node" // Target type not found
}
return `Cast To ${this.TargetType?.getName()}`
case Configuration.paths.enumLiteral:
return `Literal enum ${this.Enum?.getName()}`
case Configuration.paths.event:
return `Event ${(this.EventReference?.MemberName ?? "").replace(/^Receive/, "")}`
case Configuration.paths.executionSequence:
return "Sequence"
case Configuration.paths.forEachElementInEnum:
return `For Each ${this.Enum?.getName()}`
case Configuration.paths.forEachLoopWithBreak:
return "For Each Loop with Break"
case Configuration.paths.functionEntry:
return this.FunctionReference?.MemberName === "UserConstructionScript"
? "Construction Script"
: this.FunctionReference?.MemberName
case Configuration.paths.functionResult:
return "Return Node"
case Configuration.paths.ifThenElse:
return "Branch"
case Configuration.paths.makeStruct:
if (this.StructType) {
return `Make ${this.StructType.getName()}`
}
case Configuration.paths.materialExpressionConstant:
input ??= [this.getCustomproperties().find(pinEntity => pinEntity.PinName == "Value")?.DefaultValue]
case Configuration.paths.materialExpressionConstant2Vector:
input ??= [
this.getCustomproperties().find(pinEntity => pinEntity.PinName == "X")?.DefaultValue,
this.getCustomproperties().find(pinEntity => pinEntity.PinName == "Y")?.DefaultValue,
]
case Configuration.paths.materialExpressionConstant3Vector:
if (!input) {
/** @type {VectorEntity} */
const vector = this.getCustomproperties()
.find(pinEntity => pinEntity.PinName == "Constant")
?.DefaultValue
input = [vector.X, vector.Y, vector.Z]
}
case Configuration.paths.materialExpressionConstant4Vector:
if (!input) {
/** @type {LinearColorEntity} */
const vector = this.getCustomproperties()
.find(pinEntity => pinEntity.PinName == "Constant")
?.DefaultValue
input = [vector.R, vector.G, vector.B, vector.A].map(v => v.valueOf())
}
if (input.length > 0) {
return input.map(v => Utility.printExponential(v)).reduce((acc, cur) => acc + "," + cur)
}
break
case Configuration.paths.materialExpressionLogarithm:
return "Ln"
case Configuration.paths.materialExpressionLogarithm10:
return "Log10"
case Configuration.paths.materialExpressionLogarithm2:
return "Log2"
case Configuration.paths.materialExpressionMaterialFunctionCall:
const materialFunction = this.getMaterialSubobject()?.MaterialFunction
if (materialFunction) {
return materialFunction.getName()
}
break
case Configuration.paths.materialExpressionSquareRoot:
return "Sqrt"
case Configuration.paths.pcgEditorGraphNodeInput:
return "Input"
case Configuration.paths.pcgEditorGraphNodeOutput:
return "Output"
case Configuration.paths.spawnActorFromClass:
return `SpawnActor ${Utility.formatStringName(
this.getCustomproperties().find(pinEntity => pinEntity.getType() == "class")?.DefaultObject?.getName()
?? "NONE"
)}`
case Configuration.paths.switchEnum:
return `Switch on ${this.Enum?.getName() ?? "Enum"}`
case Configuration.paths.switchInteger:
return `Switch on Int`
case Configuration.paths.variableGet:
return ""
case Configuration.paths.variableSet:
return "SET"
}
let switchTarget = this.switchTarget()
if (switchTarget) {
if (switchTarget[0] !== "E") {
switchTarget = Utility.formatStringName(switchTarget)
}
return `Switch on ${switchTarget}`
}
if (this.isComment()) {
return this.NodeComment
}
const keyNameSymbol = this.getHIDAttribute()
if (keyNameSymbol) {
const keyName = keyNameSymbol.toString()
let title = ObjectEntity.keyName(keyName) ?? Utility.formatStringName(keyName)
if (this.getClass() === Configuration.paths.inputDebugKey) {
title = "Debug Key " + title
} else if (this.getClass() === Configuration.paths.getInputAxisKeyValue) {
title = "Get " + title
}
return title
}
if (this.getClass() === Configuration.paths.macro) {
return Utility.formatStringName(this.MacroGraphReference?.getMacroName())
}
if (this.isMaterial() && this.getMaterialSubobject()) {
let result = this.getMaterialSubobject().nodeDisplayName()
result = result.match(/Material Expression (.+)/)?.[1] ?? result
return result
}
if (this.isPcg() && this.getPcgSubobject()) {
let pcgSubobject = this.getPcgSubobject()
let result = pcgSubobject.NodeTitle ? pcgSubobject.NodeTitle : pcgSubobject.nodeDisplayName()
return result
}
const subgraphObject = this.getSubgraphObject()
if (subgraphObject) {
return subgraphObject.Graph.getName()
}
const settingsObject = this.getSettingsObject()
if (settingsObject) {
if (settingsObject.BlueprintElementInstance) {
return Utility.formatStringName(settingsObject.BlueprintElementType.getName())
}
const settingsSubgraphObject = settingsObject.getSubgraphObject()
if (settingsSubgraphObject && settingsSubgraphObject.Graph) {
return settingsSubgraphObject.Graph.getName()
}
}
let memberName = this.FunctionReference?.MemberName
if (memberName) {
const memberParent = this.FunctionReference.MemberParent?.path ?? ""
switch (memberName) {
case "AddKey":
let result = memberParent.match(ObjectEntity.sequencerScriptingNameRegex)
if (result) {
return `Add Key (${Utility.formatStringName(result[1])})`
}
case "Concat_StrStr":
return "Append"
}
const memberNameTraceLineMatch = memberName.match(Configuration.lineTracePattern)
if (memberNameTraceLineMatch) {
return "Line Trace"
+ (memberNameTraceLineMatch[1] === "Multi" ? " Multi " : " ")
+ (memberNameTraceLineMatch[2] === ""
? "By Channel"
: Utility.formatStringName(memberNameTraceLineMatch[2])
)
}
switch (memberParent) {
case Configuration.paths.slateBlueprintLibrary:
case Configuration.paths.kismetMathLibrary:
case Configuration.paths.timeManagementBlueprintLibrary:
const leadingLetter = memberName.match(/[BF]([A-Z]\w+)/)
if (leadingLetter) {
// Some functions start with B or F (Like FCeil, FMax, BMin)
memberName = leadingLetter[1]
}
switch (memberName) {
case "Abs": return "ABS"
case "BooleanAND": return "AND"
case "BooleanNAND": return "NAND"
case "BooleanOR": return "OR"
case "Exp": return "e"
case "LineTraceSingle": return "Line Trace By Channel"
case "Max": return "MAX"
case "MaxInt64": return "MAX"
case "Min": return "MIN"
case "MinInt64": return "MIN"
case "Not_PreBool": return "NOT"
case "Sin": return "SIN"
case "Sqrt": return "SQRT"
case "Square": return "^2"
// Dot products not respecting MemberName pattern
case "CrossProduct2D": return "cross"
case "Vector4_CrossProduct3": return "cross3"
case "DotProduct2D":
case "Vector4_DotProduct":
return "dot"
case "Vector4_DotProduct3": return "dot3"
}
if (memberName.startsWith("Add_")) {
return "+"
}
if (memberName.startsWith("And_")) {
return "&"
}
if (memberName.startsWith("Conv_")) {
return "" // Conversion nodes do not have visible names
}
if (memberName.startsWith("Cross_")) {
return "cross"
}
if (memberName.startsWith("Dot_")) {
return "dot"
}
if (memberName.startsWith("EqualEqual_")) {
return "=="
}
if (memberName.startsWith("Greater_")) {
return ">"
}
if (memberName.startsWith("GreaterEqual_")) {
return ">="
}
if (memberName.startsWith("Less_")) {
return "<"
}
if (memberName.startsWith("LessEqual_")) {
return "<="
}
if (memberName.startsWith("Multiply_")) {
return String.fromCharCode(0x2a2f)
}
if (memberName.startsWith("Not_")) {
return "~"
}
if (memberName.startsWith("NotEqual_")) {
return "!="
}
if (memberName.startsWith("Or_")) {
return "|"
}
if (memberName.startsWith("Percent_")) {
return "%"
}
if (memberName.startsWith("Xor_")) {
return "^"
}
break
case Configuration.paths.blueprintSetLibrary:
{
const setOperationMatch = memberName.match(/Set_(\w+)/)
if (setOperationMatch) {
return Utility.formatStringName(setOperationMatch[1]).toUpperCase()
}
}
break
case Configuration.paths.blueprintMapLibrary:
{
const setOperationMatch = memberName.match(/Map_(\w+)/)
if (setOperationMatch) {
return Utility.formatStringName(setOperationMatch[1]).toUpperCase()
}
}
break
case Configuration.paths.kismetArrayLibrary:
{
const arrayOperationMath = memberName.match(/Array_(\w+)/)
if (arrayOperationMath) {
return arrayOperationMath[1].toUpperCase()
}
}
break
}
return Utility.formatStringName(memberName)
}
if (this.ObjectRef) {
return this.ObjectRef.getName()
}
return Utility.formatStringName(this.getNameAndCounter()[0])
}
nodeColor() {
switch (this.getType()) {
case Configuration.paths.materialExpressionConstant2Vector:
case Configuration.paths.materialExpressionConstant3Vector:
case Configuration.paths.materialExpressionConstant4Vector:
return Configuration.nodeColors.yellow
case Configuration.paths.makeStruct:
return Configuration.nodeColors.darkBlue
case Configuration.paths.materialExpressionMaterialFunctionCall:
return Configuration.nodeColors.blue
case Configuration.paths.materialExpressionTextureSample:
return Configuration.nodeColors.darkTurquoise
case Configuration.paths.materialExpressionTextureCoordinate:
return Configuration.nodeColors.red
case Configuration.paths.pcgEditorGraphNodeInput:
case Configuration.paths.pcgEditorGraphNodeOutput:
return Configuration.nodeColors.red
}
switch (this.getClass()) {
case Configuration.paths.callFunction:
return this.bIsPureFunc
? Configuration.nodeColors.green
: Configuration.nodeColors.blue
case Configuration.paths.dynamicCast:
return Configuration.nodeColors.turquoise
case Configuration.paths.inputDebugKey:
case Configuration.paths.inputKey:
return Configuration.nodeColors.red
case Configuration.paths.createDelegate:
case Configuration.paths.enumLiteral:
case Configuration.paths.makeArray:
case Configuration.paths.makeMap:
case Configuration.paths.materialGraphNode:
case Configuration.paths.select:
return Configuration.nodeColors.green
case Configuration.paths.executionSequence:
case Configuration.paths.ifThenElse:
case Configuration.paths.macro:
case Configuration.paths.multiGate:
return Configuration.nodeColors.gray
case Configuration.paths.functionEntry:
case Configuration.paths.functionResult:
return Configuration.nodeColors.violet
case Configuration.paths.timeline:
return Configuration.nodeColors.yellow
}
if (this.switchTarget()) {
return Configuration.nodeColors.lime
}
if (this.isEvent()) {
return Configuration.nodeColors.red
}
if (this.isComment()) {
return (this.CommentColor ? this.CommentColor : LinearColorEntity.getWhite())
.toDimmedColor()
.toCSSRGBValues()
}
const pcgSubobject = this.getPcgSubobject()
if (pcgSubobject && pcgSubobject.NodeTitleColor) {
return pcgSubobject.NodeTitleColor.toDimmedColor(0.1).toCSSRGBValues()
}
if (this.bIsPureFunc) {
return Configuration.nodeColors.green
}
return Configuration.nodeColors.blue
}
nodeIcon() {
switch (this.getType()) {
case Configuration.paths.asyncAction:
case Configuration.paths.addDelegate:
case Configuration.paths.createDelegate:
case Configuration.paths.functionEntry:
case Configuration.paths.functionResult:
return SVGIcon.node
case Configuration.paths.customEvent: return SVGIcon.event
case Configuration.paths.doN: return SVGIcon.doN
case Configuration.paths.doOnce: return SVGIcon.doOnce
case Configuration.paths.dynamicCast: return SVGIcon.cast
case Configuration.paths.enumLiteral: return SVGIcon.enum
case Configuration.paths.event: return SVGIcon.event
case Configuration.paths.executionSequence:
case Configuration.paths.multiGate:
return SVGIcon.sequence
case Configuration.paths.flipflop:
return SVGIcon.flipflop
case Configuration.paths.forEachElementInEnum:
case Configuration.paths.forLoop:
case Configuration.paths.forLoopWithBreak:
case Configuration.paths.whileLoop:
return SVGIcon.loop
case Configuration.paths.forEachLoop:
case Configuration.paths.forEachLoopWithBreak:
return SVGIcon.forEachLoop
case Configuration.paths.ifThenElse: return SVGIcon.branchNode
case Configuration.paths.isValid: return SVGIcon.questionMark
case Configuration.paths.makeArray: return SVGIcon.makeArray
case Configuration.paths.makeMap: return SVGIcon.makeMap
case Configuration.paths.makeSet: return SVGIcon.makeSet
case Configuration.paths.makeStruct: return SVGIcon.makeStruct
case Configuration.paths.select: return SVGIcon.select
case Configuration.paths.spawnActorFromClass: return SVGIcon.spawnActor
case Configuration.paths.timeline: return SVGIcon.timer
}
if (this.switchTarget()) {
return SVGIcon.switch
}
if (this.nodeDisplayName().startsWith("Break")) {
return SVGIcon.breakStruct
}
if (this.getClass() === Configuration.paths.macro) {
return SVGIcon.macro
}
if (this.isMaterial() || this.isPcg()) {
return undefined
}
const hidValue = this.getHIDAttribute()?.toString()
if (hidValue) {
if (hidValue.includes("Mouse")) {
return SVGIcon.mouse
} else if (hidValue.includes("Gamepad_Special")) {
return SVGIcon.keyboard // This is called Touchpad in UE
} else if (hidValue.includes("Gamepad") || hidValue.includes("Steam")) {
return SVGIcon.gamepad
} else if (hidValue.includes("Touch")) {
return SVGIcon.touchpad
} else {
return SVGIcon.keyboard
}
}
if (this.getDelegatePin()) {
return SVGIcon.event
}
if (this.ObjectRef?.type === Configuration.paths.ambientSound) {
return SVGIcon.sound
}
return SVGIcon.functionSymbol
}
additionalPinInserter() {
/** @type {() => PinEntity[]} */
let pinEntities
/** @type {(pinEntity: PinEntity) => Number} */
let pinIndexFromEntity
/** @type {(newPinIndex: Number, minIndex: Number, maxIndex: Number) => String} */
let pinNameFromIndex
switch (this.getType()) {
case Configuration.paths.commutativeAssociativeBinaryOperator:
case Configuration.paths.promotableOperator:
switch (this.FunctionReference?.MemberName) {
default:
if (
!this.FunctionReference?.MemberName?.startsWith("Multiply_")
&& !this.FunctionReference?.MemberName?.startsWith("Add_")
) {
break
}
case "And_Int64Int64":
case "And_IntInt":
case "BMax":
case "BMin":
case "BooleanAND":
case "BooleanNAND":
case "BooleanOR":
case "Concat_StrStr":
case "FMax":
case "FMin":
case "Max":
case "MaxInt64":
case "Min":
case "MinInt64":
case "Or_Int64Int64":
case "Or_IntInt":
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isInput())
pinIndexFromEntity ??= pinEntity =>
pinEntity.PinName.match(/^\s*([A-Z])\s*$/)?.[1]?.charCodeAt(0) - "A".charCodeAt(0)
pinNameFromIndex ??= (index, min = -1, max = -1) => {
const result = String.fromCharCode(index >= 0 ? index : max + "A".charCodeAt(0) + 1)
this.NumAdditionalInputs = pinEntities().length - 1
return result
}
break
}
break
case Configuration.paths.multiGate:
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*Out[_\s]+(\d+)\s*$/i)?.[1])
pinNameFromIndex ??= (index, min = -1, max = -1) =>
`Out ${index >= 0 ? index : min > 0 ? "Out 0" : max + 1}`
break
case Configuration.paths.switchInteger:
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*(\d+)\s*$/)?.[1])
pinNameFromIndex ??= (index, min = -1, max = -1) => (index < 0 ? max + 1 : index).toString()
break
case Configuration.paths.switchGameplayTag:
pinNameFromIndex ??= (index, min = -1, max = -1) => {
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`
this.PinNames ??= []
this.PinNames.push(result)
delete this.PinTags[this.PinTags.length - 1]
this.PinTags[this.PinTags.length] = null
return result
}
case Configuration.paths.switchName:
case Configuration.paths.switchString:
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*Case[_\s]+(\d+)\s*$/i)?.[1])
pinNameFromIndex ??= (index, min = -1, max = -1) => {
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`
this.PinNames ??= []
this.PinNames.push(result)
return result
}
break
}
if (pinEntities) {
return () => {
let min = Number.MAX_SAFE_INTEGER
let max = Number.MIN_SAFE_INTEGER
let values = []
const modelPin = pinEntities().reduce(
(acc, cur) => {
const value = pinIndexFromEntity(cur)
if (!isNaN(value)) {
values.push(value)
min = Math.min(value, min)
if (value > max) {
max = value
return cur
}
} else if (acc === undefined) {
return cur
}
return acc
},
undefined
)
if (min === Number.MAX_SAFE_INTEGER || max === Number.MIN_SAFE_INTEGER) {
min = undefined
max = undefined
}
if (!modelPin) {
return null
}
values.sort((a, b) => a < b ? -1 : a === b ? 0 : 1)
let prev = values[0]
let index = values.findIndex(
// Search for a gap
value => {
const result = value - prev > 1
prev = value
return result
}
)
const newPin = new PinEntity(modelPin)
newPin.PinId = GuidEntity.generateGuid()
newPin.PinName = pinNameFromIndex(index, min, max)
newPin.PinToolTip = undefined
this.CustomProperties.push(newPin)
return newPin
}
}
}
}