New node type

This commit is contained in:
barsdeveloper
2023-04-01 22:17:50 +02:00
parent a2bf17ab21
commit 82aaf5a9cb
11 changed files with 123 additions and 12 deletions

View File

@@ -0,0 +1,45 @@
/// <reference types="cypress" />
import { generateNodeTest } from "../fixtures/testUtilities"
import Blueprint from "../../js/Blueprint"
import Configuration from "../../js/Configuration"
import SVGIcon from "../../js/SVGIcon"
const tests = [
{
name: "MoveCharacterRandomLocation",
subtitle: "Custom Event",
value: String.raw`
Begin Object Class=/Script/BlueprintGraph.K2Node_CustomEvent Name="K2Node_CustomEvent_4"
CustomFunctionName="MoveCharacterRandomLocation"
NodePosX=-368
NodePosY=64
NodeGuid=9C3BF2E5A27C4B45825C025A224639EA
CustomProperties Pin (PinId=B563D2CC4FC67B5F348BE18F59F694A4,PinName="OutputDelegate",Direction="EGPD_Output",PinType.PinCategory="delegate",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(MemberParent=/Script/Engine.BlueprintGeneratedClass'"/Temp/Untitled_1.Untitled_C"',MemberName="MoveCharacterRandomLocation",MemberGuid=9C3BF2E5A27C4B45825C025A224639EA),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=0DE0B9A2469DB01A69BD5C8BB17D15BB,PinName="then",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_Knot_8 C5BBC59C45ACF577B59616A9D79986B3,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
End Object
`,
color: Configuration.nodeColors.red,
icon: SVGIcon.event,
pins: 2,
delegate: true,
development: false,
},
]
/** @type {Blueprint} */
let blueprint
before(() => {
cy.visit(`http://127.0.0.1:${Cypress.env("UEBLUEPRINT_TEST_SERVER_PORT")}/empty.html`, {
onLoad: () => {
cy.get("ueb-blueprint")
.then(b => blueprint = b[0])
.click(100, 300)
}
})
})
tests.forEach(
testObject => generateNodeTest(testObject, () => blueprint)
)

View File

@@ -7,6 +7,7 @@ import SVGIcon from "../../js/SVGIcon"
const tests = [
{
name: "Has Matching Gameplay Tag",
subtitle: "Target is Gameplay Tag Asset Interface",
value: String.raw`
Begin Object Class=/Script/BlueprintGraph.K2Node_Message Name="K2Node_Message_0"
bIsPureFunc=True
@@ -31,6 +32,7 @@ const tests = [
},
{
name: "Can Jump",
subtitle: "Target is Character",
value: String.raw`
Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_8"
bIsPureFunc=True
@@ -51,6 +53,7 @@ const tests = [
},
{
name: "Set Finish On Message",
subtitle: "Target is BTTask Blueprint Base",
value: String.raw`
Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_9"
FunctionReference=(MemberParent=/Script/CoreUObject.Class'"/Script/AIModule.BTTask_BlueprintBase"',MemberName="SetFinishOnMessage")
@@ -137,6 +140,7 @@ const tests = [
},
{
name: "Line Trace Component",
subtitle: "Target is Primitive Component",
value: String.raw`
Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_23"
FunctionReference=(MemberParent=/Script/CoreUObject.Class'"/Script/Engine.PrimitiveComponent"',MemberName="K2_LineTraceComponent")
@@ -204,6 +208,23 @@ const tests = [
delegate: false,
development: false,
},
{
name: "Create Event",
value: String.raw`
Begin Object Class=/Script/BlueprintGraph.K2Node_CreateDelegate Name="K2Node_CreateDelegate_1"
NodePosX=368
NodePosY=-224
NodeGuid=0FA4EE58928C4CF285441256561E250A
CustomProperties Pin (PinId=4735A6AC4F9F7A3AFD64B2801F623052,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "CreateDelegate_ObjectInputName", "Object"),PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=/Script/CoreUObject.Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=0A66F49740A5DDD42C1AECA040844EBF,PinName="OutputDelegate",PinFriendlyName=NSLOCTEXT("K2Node", "CreateDelegate_DelegateOutName", "Event"),Direction="EGPD_Output",PinType.PinCategory="delegate",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
End Object
`,
color: Configuration.nodeColors.green,
icon: SVGIcon.node,
pins: 2,
delete: false,
development: false,
},
]
/** @type {Blueprint} */

View File

@@ -29,6 +29,7 @@ export function generateNodeTest(nodeTest, getBlueprint) {
}
})
it("It's called " + nodeTest.name, () => expect(node.getNodeDisplayName()).to.be.equal(nodeTest.name))
it("Has expected subtitle " + nodeTest.subtitle, () => expect(node.querySelector(".ueb-node-subtitle-text")?.innerText).to.be.equal(nodeTest.subtitle))
it("Has the correct icon", () => expect(node.entity.nodeIcon()).to.be.deep.equal(nodeTest.icon))
it(`Has ${nodeTest.pins} pins`, () => expect(node.querySelectorAll("ueb-pin")).to.be.lengthOf(nodeTest.pins))
it("Expected development", () => expect(node.entity.isDevelopmentOnly()).equals(nodeTest.development))

32
dist/ueblueprint.js vendored
View File

@@ -78,7 +78,7 @@ class Configuration {
static nameRegexSpaceReplacement = new RegExp(
"^K2(?:[Nn]ode)?_"
+ "|(?<=[a-z])(?=[A-Z0-9])" // ("Alpha2", "AlphaBravo") => ("Alpha 2", "Alpha Bravo")
+ "|(?<=[A-Z])(?=[A-Z][a-z]|[0-9])" // ("ALPHABravo", "ALPHA2") => ("ALPHA Bravo", "ALPHA 2")
+ "|(?<=[A-Z])(?=[A-Z][a-z](?![a-z]+_)|[0-9])" // ("ALPHABravo", "ALPHA2", "BTTask_") => ("ALPHA Bravo", "ALPHA 2", "BTTask")
+ "|(?<=[014-9]|[23](?!D(?:[^a-z]|$)))(?=[a-zA-Z])" // ("3Times", "3D", "3Delta") => ("3 Times", "3D", "3 Delta")
+ "|\\s*_+\\s*" // "Alpha__Bravo" => "Alpha Bravo"
+ "|\\s{2,}",
@@ -107,6 +107,7 @@ class Configuration {
comment: "/Script/UnrealEd.EdGraphNode_Comment",
commutativeAssociativeBinaryOperator: "/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator",
componentBoundEvent: "/Script/BlueprintGraph.K2Node_ComponentBoundEvent",
createDelegate: "/Script/BlueprintGraph.K2Node_CreateDelegate",
customEvent: "/Script/BlueprintGraph.K2Node_CustomEvent",
doN: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:Do N",
doOnce: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:DoOnce",
@@ -2465,6 +2466,14 @@ class SVGIcon {
</svg>
`
static node = y`
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="15" rx="1" fill="white" fill-opacity="0.5"/>
<rect x="0.5" y="0.5" width="15" height="14" rx="0.5" stroke="white"/>
<rect x="1" width="14" height="5" fill="white"/>
</svg>
`
static questionMark = y`
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 15C9.10456 15 10 14.1046 10 13C10 11.8954 9.10456 11 8 11C6.89544 11 6 11.8954 6 13C6 14.1046 6.89544 15 8 15Z" fill="white" />
@@ -2964,6 +2973,12 @@ class ObjectEntity extends IEntity {
switch (this.getType()) {
case Configuration.nodeType.componentBoundEvent:
return `${Utility.formatStringName(this.DelegatePropertyName)} (${this.ComponentPropertyName})`
case Configuration.nodeType.createDelegate:
return "Create Event"
case Configuration.nodeType.customEvent:
if (this.CustomFunctionName) {
return this.CustomFunctionName
}
case Configuration.nodeType.dynamicCast:
if (!this.TargetType) {
return "Bad cast node" // Target type not found
@@ -3077,6 +3092,7 @@ class ObjectEntity extends IEntity {
case Configuration.nodeType.inputAxisKeyEvent:
case Configuration.nodeType.inputDebugKey:
return Configuration.nodeColors.red
case Configuration.nodeType.createDelegate:
case Configuration.nodeType.enumLiteral:
case Configuration.nodeType.makeArray:
case Configuration.nodeType.makeMap:
@@ -3101,6 +3117,7 @@ class ObjectEntity extends IEntity {
nodeIcon() {
switch (this.getType()) {
case Configuration.nodeType.createDelegate: return SVGIcon.node
case Configuration.nodeType.customEvent: return SVGIcon.event
case Configuration.nodeType.doN: return SVGIcon.doN
case Configuration.nodeType.doOnce: return SVGIcon.doOnce
@@ -6235,7 +6252,7 @@ class NodeTemplate extends ISelectableDraggableTemplate {
/** @typedef {typeof NodeTemplate} NodeTemplateConstructor */
hasSubtitle = false
#hasSubtitle = false
static nodeStyleClasses = ["ueb-node-style-default"]
@@ -6298,7 +6315,7 @@ class NodeTemplate extends ISelectableDraggableTemplate {
${name ? y`
<div class="ueb-node-name-text ueb-ellipsis-nowrap-text">
${name}
${this.hasSubtitle && this.getTargetType().length > 0 ? y`
${this.#hasSubtitle && this.getTargetType().length > 0 ? y`
<div class="ueb-node-subtitle-text ueb-ellipsis-nowrap-text">
Target is ${Utility.formatStringName(this.getTargetType())}
</div>
@@ -6343,7 +6360,7 @@ class NodeTemplate extends ISelectableDraggableTemplate {
return this.element.getPinEntities()
.filter(v => !v.isHidden())
.map(pinEntity => {
this.hasSubtitle = this.hasSubtitle
this.#hasSubtitle = this.#hasSubtitle
|| pinEntity.PinName === "self" && pinEntity.getDisplayName() === "Target";
let pinElement = /** @type {PinElementConstructor} */(ElementFactory.getConstructor("ueb-pin"))
.newObject(pinEntity, undefined, this.element);
@@ -6985,6 +7002,7 @@ class MinimalPinTemplate extends PinTemplate {
/**
* @typedef {import("../../element/PinElement").PinElementConstructor} PinElementConstructor
* @typedef {import("lit").PropertyValues} PropertyValues
* @typedef {import ("../../element/NodeElement.js").default} NodeElement
*/
class EventNodeTemplate extends NodeTemplate {
@@ -7000,6 +7018,8 @@ class EventNodeTemplate extends NodeTemplate {
renderTop() {
const icon = this.renderNodeIcon();
const name = this.renderNodeName();
const customEvent = this.element.getType() === Configuration.nodeType.customEvent
&& (this.element.entity.CustomFunctionName || this.element.entity.FunctionReference.MemberParent);
return y`
<div class="ueb-node-name">
${icon ? y`
@@ -7008,7 +7028,7 @@ class EventNodeTemplate extends NodeTemplate {
${name ? y`
<div class="ueb-node-name-text ueb-ellipsis-nowrap-text">
${name}
${this.hasSubtitle && this.element.entity.FunctionReference.MemberParent ? y`
${customEvent ? y`
<div class="ueb-node-subtitle-text ueb-ellipsis-nowrap-text">
Custom Event
</div>
@@ -7358,6 +7378,8 @@ class NodeElement extends ISelectableDraggableElement {
switch (nodeEntity.getClass()) {
case Configuration.nodeType.comment:
return CommentNodeTemplate
case Configuration.nodeType.createDelegate:
return NodeTemplate
case Configuration.nodeType.event:
case Configuration.nodeType.customEvent:
return EventNodeTemplate

File diff suppressed because one or more lines are too long

View File

@@ -54,7 +54,7 @@ export default class Configuration {
static nameRegexSpaceReplacement = new RegExp(
"^K2(?:[Nn]ode)?_"
+ "|(?<=[a-z])(?=[A-Z0-9])" // ("Alpha2", "AlphaBravo") => ("Alpha 2", "Alpha Bravo")
+ "|(?<=[A-Z])(?=[A-Z][a-z]|[0-9])" // ("ALPHABravo", "ALPHA2") => ("ALPHA Bravo", "ALPHA 2")
+ "|(?<=[A-Z])(?=[A-Z][a-z](?![a-z]+_)|[0-9])" // ("ALPHABravo", "ALPHA2", "BTTask_") => ("ALPHA Bravo", "ALPHA 2", "BTTask")
+ "|(?<=[014-9]|[23](?!D(?:[^a-z]|$)))(?=[a-zA-Z])" // ("3Times", "3D", "3Delta") => ("3 Times", "3D", "3 Delta")
+ "|\\s*_+\\s*" // "Alpha__Bravo" => "Alpha Bravo"
+ "|\\s{2,}",
@@ -83,6 +83,7 @@ export default class Configuration {
comment: "/Script/UnrealEd.EdGraphNode_Comment",
commutativeAssociativeBinaryOperator: "/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator",
componentBoundEvent: "/Script/BlueprintGraph.K2Node_ComponentBoundEvent",
createDelegate: "/Script/BlueprintGraph.K2Node_CreateDelegate",
customEvent: "/Script/BlueprintGraph.K2Node_CustomEvent",
doN: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:Do N",
doOnce: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:DoOnce",

View File

@@ -240,6 +240,14 @@ export default class SVGIcon {
</svg>
`
static node = html`
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="15" rx="1" fill="white" fill-opacity="0.5"/>
<rect x="0.5" y="0.5" width="15" height="14" rx="0.5" stroke="white"/>
<rect x="1" width="14" height="5" fill="white"/>
</svg>
`
static questionMark = html`
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 15C9.10456 15 10 14.1046 10 13C10 11.8954 9.10456 11 8 11C6.89544 11 6 11.8954 6 13C6 14.1046 6.89544 15 8 15Z" fill="white" />

View File

@@ -141,6 +141,8 @@ export default class NodeElement extends ISelectableDraggableElement {
switch (nodeEntity.getClass()) {
case Configuration.nodeType.comment:
return CommentNodeTemplate
case Configuration.nodeType.createDelegate:
return NodeTemplate
case Configuration.nodeType.event:
case Configuration.nodeType.customEvent:
return EventNodeTemplate

View File

@@ -421,6 +421,12 @@ export default class ObjectEntity extends IEntity {
switch (this.getType()) {
case Configuration.nodeType.componentBoundEvent:
return `${Utility.formatStringName(this.DelegatePropertyName)} (${this.ComponentPropertyName})`
case Configuration.nodeType.createDelegate:
return "Create Event"
case Configuration.nodeType.customEvent:
if (this.CustomFunctionName) {
return this.CustomFunctionName
}
case Configuration.nodeType.dynamicCast:
if (!this.TargetType) {
return "Bad cast node" // Target type not found
@@ -534,6 +540,7 @@ export default class ObjectEntity extends IEntity {
case Configuration.nodeType.inputAxisKeyEvent:
case Configuration.nodeType.inputDebugKey:
return Configuration.nodeColors.red
case Configuration.nodeType.createDelegate:
case Configuration.nodeType.enumLiteral:
case Configuration.nodeType.makeArray:
case Configuration.nodeType.makeMap:
@@ -558,6 +565,7 @@ export default class ObjectEntity extends IEntity {
nodeIcon() {
switch (this.getType()) {
case Configuration.nodeType.createDelegate: return SVGIcon.node
case Configuration.nodeType.customEvent: return SVGIcon.event
case Configuration.nodeType.doN: return SVGIcon.doN
case Configuration.nodeType.doOnce: return SVGIcon.doOnce

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit"
import Configuration from "../../Configuration.js"
import ElementFactory from "../../element/ElementFactory.js"
import MinimalPinTemplate from "../pin/MinimalPinTemplate.js"
import NodeTemplate from "./NodeTemplate.js"
@@ -21,6 +22,8 @@ export default class EventNodeTemplate extends NodeTemplate {
renderTop() {
const icon = this.renderNodeIcon()
const name = this.renderNodeName()
const customEvent = this.element.getType() === Configuration.nodeType.customEvent
&& (this.element.entity.CustomFunctionName || this.element.entity.FunctionReference.MemberParent)
return html`
<div class="ueb-node-name">
${icon ? html`
@@ -29,7 +32,7 @@ export default class EventNodeTemplate extends NodeTemplate {
${name ? html`
<div class="ueb-node-name-text ueb-ellipsis-nowrap-text">
${name}
${this.hasSubtitle && this.element.entity.FunctionReference.MemberParent ? html`
${customEvent ? html`
<div class="ueb-node-subtitle-text ueb-ellipsis-nowrap-text">
Custom Event
</div>

View File

@@ -16,7 +16,7 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
/** @typedef {typeof NodeTemplate} NodeTemplateConstructor */
hasSubtitle = false
#hasSubtitle = false
static nodeStyleClasses = ["ueb-node-style-default"]
@@ -79,7 +79,7 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
${name ? html`
<div class="ueb-node-name-text ueb-ellipsis-nowrap-text">
${name}
${this.hasSubtitle && this.getTargetType().length > 0 ? html`
${this.#hasSubtitle && this.getTargetType().length > 0 ? html`
<div class="ueb-node-subtitle-text ueb-ellipsis-nowrap-text">
Target is ${Utility.formatStringName(this.getTargetType())}
</div>
@@ -124,7 +124,7 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
return this.element.getPinEntities()
.filter(v => !v.isHidden())
.map(pinEntity => {
this.hasSubtitle = this.hasSubtitle
this.#hasSubtitle = this.#hasSubtitle
|| pinEntity.PinName === "self" && pinEntity.getDisplayName() === "Target"
let pinElement = /** @type {PinElementConstructor} */(ElementFactory.getConstructor("ueb-pin"))
.newObject(pinEntity, undefined, this.element)