├── .clang-format
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── ActionsExtension.uplugin
├── Config
└── FilterPlugin.ini
├── Docs
├── .gitignore
├── ActionsExtension
│ └── assets
│ │ ├── logo.dark.png
│ │ └── logo.png
├── README.md
├── SUMMARY.md
├── _layouts
│ ├── layout.html
│ └── website
│ │ ├── layout.html
│ │ └── page.html
├── assets
│ ├── favicon.ico
│ ├── logo.dark.png
│ └── logo.png
├── book.json
├── debugging.md
├── downloads
│ ├── book.epub
│ ├── book.mobi
│ ├── book.pdf
│ └── implementation.md
├── gulpfile.js
├── installation.md
├── lib
│ ├── Install.bat
│ ├── LaunchLocal.bat
│ ├── Publish.bat
│ └── RefreshSummary.bat
├── package.json
├── quick-start.md
├── styles
│ └── style.less
└── usage
│ ├── blueprints
│ ├── README.md
│ ├── delegates.md
│ ├── img
│ │ ├── editable_variable.png
│ │ ├── events_call.png
│ │ ├── events_implementation.png
│ │ ├── expose_on_spawn.png
│ │ └── new_variable.png
│ └── variables.md
│ ├── cpp
│ ├── README.md
│ ├── delegates.md
│ ├── img
│ │ ├── action_class.png
│ │ ├── create_action.png
│ │ ├── custom_activation.png
│ │ ├── delegate_declaration.png
│ │ ├── delegate_usage.png
│ │ └── variable_definition.png
│ └── variables.md
│ └── img
│ ├── action_node.png
│ ├── context_blueprint.png
│ ├── gameplay_debugger.png
│ ├── level_blueprint_add_action.png
│ ├── open_level_blueprint.png
│ ├── patron.png
│ ├── patron_small.png
│ ├── plugin_enabled.png
│ ├── popup_blueprint.png
│ ├── quick_start_play.gif
│ ├── set_action_type.png
│ └── simple_action.png
├── LICENSE
├── README.md
├── Resources
└── Icon128.png
└── Source
├── Actions
├── Actions.Build.cs
├── Private
│ ├── Action.cpp
│ ├── ActionLibrary.cpp
│ ├── ActionsModule.cpp
│ ├── ActionsSubsystem.cpp
│ ├── BTT_RunAction.cpp
│ ├── GameplayDebugger_Actions.cpp
│ └── GameplayDebugger_Actions.h
└── Public
│ ├── Action.h
│ ├── ActionLibrary.h
│ ├── ActionsModule.h
│ ├── ActionsSubsystem.h
│ └── BTT_RunAction.h
├── Editor
├── ActionsEditor.Build.cs
├── Private
│ └── ActionsEditor.cpp
└── Public
│ └── ActionsEditor.h
├── Graph
├── ActionsGraph.Build.cs
├── Private
│ ├── ActionNodeHelpers.cpp
│ ├── ActionReflection.cpp
│ ├── ActionsGraphModule.cpp
│ └── K2Node_Action.cpp
└── Public
│ ├── ActionNodeHelpers.h
│ ├── ActionReflection.h
│ ├── ActionsGraphModule.h
│ └── K2Node_Action.h
└── Test
├── ActionsTest.Build.cs
├── Private
├── Actions.spec.cpp
├── ActionsTest.cpp
├── TestAction.h
└── TestHelpers.cpp
└── Public
├── ActionsTest.h
└── TestHelpers.h
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | AccessModifierOffset: -4
4 | AlignAfterOpenBracket: DontAlign
5 | AlignConsecutiveAssignments: false
6 | AlignConsecutiveDeclarations: false
7 | AlignEscapedNewlines: Left
8 | AlignOperands: true
9 | AlignTrailingComments: true
10 | AllowAllParametersOfDeclarationOnNextLine: true
11 | AllowShortBlocksOnASingleLine: true
12 | AllowShortCaseLabelsOnASingleLine: false
13 | AllowShortFunctionsOnASingleLine: Empty
14 | AllowShortIfStatementsOnASingleLine: Never
15 | AllowShortLambdasOnASingleLine: None
16 | AllowShortLoopsOnASingleLine: false
17 | AlwaysBreakAfterReturnType: None
18 | AlwaysBreakBeforeMultilineStrings: true
19 | AlwaysBreakTemplateDeclarations: Yes
20 | BinPackArguments: true
21 | BinPackParameters: true
22 | BreakBeforeBinaryOperators: None
23 | BreakBeforeInheritanceComma: false
24 | BreakInheritanceList: BeforeColon
25 | BreakBeforeTernaryOperators: true
26 | BreakConstructorInitializersBeforeComma: false
27 | BreakConstructorInitializers: BeforeComma
28 | BreakAfterJavaFieldAnnotations: false
29 | BreakStringLiterals: true
30 | BreakBeforeBraces: Custom
31 | BraceWrapping:
32 | AfterCaseLabel: false
33 | AfterClass: true
34 | AfterEnum: true
35 | AfterFunction: true
36 | AfterNamespace: true
37 | AfterStruct: true
38 | AfterUnion: true
39 | AfterExternBlock: true
40 | AfterControlStatement: Always
41 | BeforeElse: true
42 | BeforeCatch: true
43 | BeforeLambdaBody: false
44 | SplitEmptyFunction: false
45 | SplitEmptyRecord: false
46 | SplitEmptyNamespace: false
47 | ColumnLimit: 110
48 | CommentPragmas: '^ IWYU pragma:'
49 | CompactNamespaces: false
50 | AllowAllConstructorInitializersOnNextLine: false
51 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
52 | ConstructorInitializerIndentWidth: 4
53 | ContinuationIndentWidth: 4
54 | Cpp11BracedListStyle: true
55 | DerivePointerAlignment: false
56 | DisableFormat: false
57 | FixNamespaceComments: true
58 | ForEachMacros:
59 | - for
60 | IncludeBlocks: Regroup
61 | IncludeCategories:
62 | - Regex: '.*\.generated\.h'
63 | Priority: 100
64 | - Regex: '.*(PCH).*'
65 | Priority: -1
66 | - Regex: '".*"'
67 | Priority: 2
68 | - Regex: '^<.*\.(h)>'
69 | Priority: 3
70 | - Regex: '^<.*>'
71 | Priority: 4
72 | IncludeIsMainRegex: '([-_](test|unittest))?$'
73 | IndentCaseLabels: true
74 | IndentPPDirectives: AfterHash
75 | IndentWidth: 4
76 | IndentWrappedFunctionNames: false
77 | InsertBraces: true
78 | JavaScriptQuotes: Leave
79 | JavaScriptWrapImports: true
80 | KeepEmptyLinesAtTheStartOfBlocks: false
81 | MacroBlockBegin: ''
82 | MacroBlockEnd: ''
83 | MaxEmptyLinesToKeep: 2
84 | NamespaceIndentation: All
85 | ObjCBinPackProtocolList: Never
86 | ObjCBlockIndentWidth: 2
87 | ObjCSpaceAfterProperty: false
88 | ObjCSpaceBeforeProtocolList: true
89 | PenaltyBreakAssignment: 2
90 | PenaltyBreakBeforeFirstCallParameter: 1
91 | PenaltyBreakComment: 300
92 | PenaltyBreakFirstLessLess: 120
93 | PenaltyBreakString: 1000
94 | PenaltyBreakTemplateDeclaration: 10
95 | PenaltyExcessCharacter: 1000000
96 | PenaltyReturnTypeOnItsOwnLine: 200
97 | PointerAlignment: Left
98 | RawStringFormats:
99 | - Language: Cpp
100 | Delimiters:
101 | - cc
102 | - CC
103 | - cpp
104 | - Cpp
105 | - CPP
106 | - 'c++'
107 | - 'C++'
108 | CanonicalDelimiter: ''
109 | BasedOnStyle: google
110 | - Language: TextProto
111 | Delimiters:
112 | - pb
113 | - PB
114 | - proto
115 | - PROTO
116 | EnclosingFunctions:
117 | - EqualsProto
118 | - EquivToProto
119 | - PARSE_PARTIAL_TEXT_PROTO
120 | - PARSE_TEST_PROTO
121 | - PARSE_TEXT_PROTO
122 | - ParseTextOrDie
123 | - ParseTextProtoOrDie
124 | CanonicalDelimiter: ''
125 | ReflowComments: true
126 | SortIncludes: true
127 | SortUsingDeclarations: true
128 | SpaceAfterCStyleCast: true
129 | SpaceAfterTemplateKeyword: true
130 | SpaceBeforeAssignmentOperators: true
131 | SpaceBeforeCpp11BracedList: false
132 | SpaceBeforeCtorInitializerColon: true
133 | SpaceBeforeInheritanceColon: true
134 | SpaceBeforeParens: ControlStatements
135 | SpaceBeforeRangeBasedForLoopColon: true
136 | SpaceInEmptyParentheses: false
137 | SpacesBeforeTrailingComments: 4
138 | SpacesInAngles: false
139 | SpacesInContainerLiterals: true
140 | SpacesInCStyleCastParentheses: false
141 | SpacesInParentheses: false
142 | SpacesInSquareBrackets: false
143 | Standard: Auto
144 | TabWidth: 4
145 | UseTab: Always
146 | ...
147 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve Actions Extension
4 | title: ''
5 | labels: Bug, To Be Reproduced
6 | assignees: muit
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Logs**
21 | It's helps us a lot to have a log. If you can, drop the last one here.
22 | *Logs can be found under "MyProject/Saved/Logs/MyProject.log"*
23 |
24 | **[Optional] Screenshots if any**
25 | If applicable, add screenshots or videos to help explain your problem.
26 |
27 | **Environment (please complete the following information):**
28 | - Engine Version: [e.g. 4.21.1]
29 | - Plugin Version: [e.g. 1.0]
30 | - OS: [e.g. Windows]
31 |
32 | **[Optional] Additional context**
33 | Add any other context about the problem here.
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea or feature for Actions Extension
4 | title: ''
5 | labels: Feature
6 | assignees: muit
7 |
8 | ---
9 |
10 | **Describe the feature requested**
11 | When, how, what is the request about
12 |
13 | **How does it help the user (you)?**
14 | Ex. It reduces the amount of work I have to do
15 |
16 | **Is it related to a problem?**
17 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
18 |
19 | **[Optional] Propose an approach to the feature (the less we think the better!)**
20 | How would it work? Ex. I would suggest using instanced objects
21 |
22 | **Any other possible approach?**
23 |
24 | **[Optional] Additional context**
25 | Add any other context or screenshots about the feature request
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /Binaries
2 | /Intermediate
3 |
--------------------------------------------------------------------------------
/ActionsExtension.uplugin:
--------------------------------------------------------------------------------
1 | {
2 | "FileVersion": 3,
3 | "Version": 10,
4 | "VersionName": "1.1",
5 | "FriendlyName": "Actions Extension",
6 | "Description": "A plugin that adds asynchronous tasks called Actions to UE4",
7 | "Category": "Piperift",
8 | "CreatedBy": "Piperift",
9 | "CreatedByURL": "http://piperift.com/",
10 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/2924990b22c1452aa20a21af60c4aa12",
11 | "DocsURL": "https://piperift.com/ActionsExtension",
12 | "SupportURL": "info@piperift.com",
13 | "EngineVersion": "5.5",
14 | "CanContainContent": false,
15 | "Modules": [
16 | {
17 | "Name": "Actions",
18 | "Type": "Runtime",
19 | "LoadingPhase": "PreDefault",
20 | "WhitelistPlatforms": [
21 | "Win64",
22 | "Mac",
23 | "IOS",
24 | "Android",
25 | "Linux"
26 | ]
27 | },
28 | {
29 | "Name": "ActionsGraph",
30 | "Type": "UncookedOnly",
31 | "LoadingPhase": "Default",
32 | "WhitelistPlatforms": [
33 | "Win64",
34 | "Mac",
35 | "IOS",
36 | "Android",
37 | "Linux"
38 | ]
39 | },
40 | {
41 | "Name": "ActionsEditor",
42 | "Type": "Editor",
43 | "LoadingPhase": "PostEngineInit",
44 | "WhitelistPlatforms": [
45 | "Win64",
46 | "Mac",
47 | "IOS",
48 | "Android",
49 | "Linux"
50 | ]
51 | },
52 | {
53 | "Name" : "ActionsTest",
54 | "Type" : "DeveloperTool",
55 | "LoadingPhase" : "PreDefault",
56 | "WhitelistPlatforms": [
57 | "Win64",
58 | "Linux",
59 | "Mac"
60 | ]
61 | }
62 | ]
63 | }
--------------------------------------------------------------------------------
/Config/FilterPlugin.ini:
--------------------------------------------------------------------------------
1 | [FilterPlugin]
2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
4 | ;
5 | ; Examples:
6 | ; /README.txt
7 | ; /Extras/...
8 | ; /Binaries/ThirdParty/*.dll
9 |
--------------------------------------------------------------------------------
/Docs/.gitignore:
--------------------------------------------------------------------------------
1 | /_book/
2 | /node_modules/
3 | /downloads/book/
4 | /public/
5 | /book.pdf
6 | /book.mobi
7 | /book.epub
8 | /package-lock.json
--------------------------------------------------------------------------------
/Docs/ActionsExtension/assets/logo.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/ActionsExtension/assets/logo.dark.png
--------------------------------------------------------------------------------
/Docs/ActionsExtension/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/ActionsExtension/assets/logo.png
--------------------------------------------------------------------------------
/Docs/README.md:
--------------------------------------------------------------------------------
1 | # Actions Extension
2 |
3 | **Actions Extension** is a plugin that adds **blueprintable async tasks** called actions. It can be used for a lot of things but some examples are AI or API Rest.
4 |
5 | If you like our plugins, consider becoming a Patron. It will go a long way in helping me create more awesome tech!
6 |
7 | [](https://www.patreon.com/bePatron?u=16503983)
8 |
9 | ## What is an Action?
10 | 
11 |
12 | **Actions** are quite similar to async task nodes (*like Delays or AIMoves*) in their concept, but have some extra features that make them widely useful.
13 |
14 | An Action is a blueprint (or c++ class) that executes inside another object to encapsulate logic.
15 |
16 | ## Where can I use an Action?
17 | **Any object with world context** can have an action and its usage goes from AI behaviors to API Rest calls.
18 |
19 | We have tried both options extensively and the results are a lot more simple than normal code. You get better parallel programming, quality of code. At the end it just becomes easier to deal with complex logic.
20 |
21 | This system is also heavily focused on the usage of Actions inside Actions, creating a **tree of dependencies**. This is specially useful for **AI**.
22 |
23 |
24 |
25 | ## Quick Start
26 |
27 | Check [Quick Start](quick-start.md) to see how to setup and configure the plugin.
--------------------------------------------------------------------------------
/Docs/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Actions Extension Documentation
2 |
3 | * [1 Installation](installation.md)
4 | * [2 Quick Start](quick-start.md)
5 | * 3 Usage
6 | * [Blueprints](usage/blueprints/README.md)
7 | * [Delegates](usage/blueprints/delegates.md)
8 | * [Variables](usage/blueprints/variables.md)
9 | * [Cpp](usage/cpp/README.md)
10 | * [Delegates](usage/cpp/delegates.md)
11 | * [Variables](usage/cpp/variables.md)
12 | * [Debugging](debugging.md)
13 |
--------------------------------------------------------------------------------
/Docs/_layouts/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {% block title %}{{ config.title|d("GitBook", true) }}{% endblock %}
8 |
9 |
10 |
11 | {% if config.author %}
12 | {% endif %}
13 | {% if config.isbn %}
14 | {% endif %}
15 | {% block style %}
16 | {% for resource in plugins.resources.css %}
17 | {% if resource.url %}
18 |
19 | {% else %}
20 |
21 | {% endif %}
22 | {% endfor %}
23 | {% endblock %}
24 | {% block head %}{% endblock %}
25 |
26 |
27 |
28 |
29 |
30 |
31 | {% block body %}{% endblock %}
32 | {% block javascript %}{% endblock %}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Docs/_layouts/website/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block head %}
4 | {{ super() }}
5 |
6 |
7 |
8 |
9 |
10 |
11 | {% endblock %}
12 |
13 | {% block style %}
14 | {### Include theme css before plugins css ###}
15 |
16 |
17 |
18 | {{ super() }}
19 |
20 | {### Custom stylesheets for the book ###}
21 |
22 | {% for type, style in config.styles %}
23 | {% if fileExists(style) and type == "website" %}
24 |
25 | {% endif %}
26 | {% endfor %}
27 | {% endblock %}
28 |
29 | {% block body %}{% endblock %}
--------------------------------------------------------------------------------
/Docs/_layouts/website/page.html:
--------------------------------------------------------------------------------
1 | {% extends "./layout.html" %}
2 |
3 | {% block title %}{{ page.title }} · {{ super() }}{% endblock %}
4 |
5 | {% block description %}{{ page.description }}{% endblock %}
6 |
7 | {% block head %}
8 | {{ super() }}
9 | {% if page.next and page.next.path %}
10 |
11 | {% endif %}
12 | {% if page.previous and page.previous.path %}
13 |
14 | {% endif %}
15 | {% endblock %}
16 |
17 | {% block javascript %}
18 |
19 |
20 | {% for resource in plugins.resources.js %}
21 | {% if resource.url %}
22 |
23 | {% else %}
24 |
25 | {% endif %}
26 | {% endfor %}
27 | {% endblock %}
28 |
29 | {% block body %}
30 |
31 |
32 | {% block book_sidebar %}
33 |
45 | {% block search_input %}
46 |
47 |
48 |
49 | {% endblock %}
50 | {% block book_summary %}
51 |
54 | {% endblock %}
55 | {% endblock %}
56 |
57 |
58 |
59 | {% block book_body %}
60 |
61 | {% block book_inner %}
62 | {% include "website/header.html" %}
63 |
64 |
65 |
66 | {% block search_results %}
67 |
68 |
69 | {% block page %}
70 | {{ page.content|safe }}
71 | {% endblock %}
72 |
73 |
74 |
75 | {% block search_has_results %}
76 |
{{ 'SEARCH_RESULTS_TITLE'|t|safe }}
77 |
78 | {% endblock %}
79 |
80 |
81 | {% block search_no_results %}
82 |
{{ 'SEARCH_NO_RESULTS_TITLE'|t|safe }}
83 | {% endblock %}
84 |
85 |
86 |
87 | {% endblock %}
88 |
89 |
90 | {% endblock %}
91 |
92 |
93 | {% block book_navigation %}
94 | {% if page.previous and page.previous.path %}
95 |
96 |
97 |
98 | {% endif %}
99 | {% if page.next and page.next.path %}
100 |
101 |
102 |
103 | {% endif %}
104 | {% endblock %}
105 | {% endblock %}
106 |
107 |
108 |
114 |
115 | {% endblock %}
--------------------------------------------------------------------------------
/Docs/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/assets/favicon.ico
--------------------------------------------------------------------------------
/Docs/assets/logo.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/assets/logo.dark.png
--------------------------------------------------------------------------------
/Docs/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/assets/logo.png
--------------------------------------------------------------------------------
/Docs/book.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Actions Extension Documentation",
3 | "author": "Piperift",
4 | "languaje": "es",
5 | "ignores": ["node_modules", "_book", "downloads", "lib"],
6 | "logo": {
7 | "white": "/assets/logo.dark.png",
8 | "sepia": "/assets/logo.png",
9 | "night": "/assets/logo.png"
10 | },
11 |
12 | "url": {
13 | "relativePath": "/ActionsExtension"
14 | },
15 |
16 | "plugins": [
17 | "custom-favicon",
18 | "copy-code-button",
19 | "styles-less",
20 | "links",
21 | "prism",
22 | "-highlight",
23 | "collapsible-chapters",
24 | "hide-published-with",
25 | "hints",
26 | "-lunr",
27 | "-search",
28 | "search-plus"
29 | ],
30 | "pluginsConfig": {
31 | "favicon": "./assets/favicon.ico",
32 | "links": {
33 | "links": [{
34 | "label": "PDF",
35 | "icon": "fa fa-file-pdf-o",
36 | "url": "downloads/book.pdf"
37 | }, {
38 | "label": "EPUB",
39 | "icon": "fa fa-leanpub",
40 | "url": "downloads/book.epub"
41 | }, {
42 | "label": "MOBI",
43 | "icon": "fa fa-book",
44 | "url": "downloads/book.mobi"
45 | }]
46 | }
47 | },
48 | "styles": {
49 | "website": "./styles/style.less",
50 | "ebook": "./styles/style.less",
51 | "pdf": "./styles/style.less"
52 | }
53 | }
--------------------------------------------------------------------------------
/Docs/debugging.md:
--------------------------------------------------------------------------------
1 | # Debugging
2 |
3 | ## Gameplay Debugger
4 |
5 | Actions can be debugged in-game with [gameplay debugger](https://docs.unrealengine.com/en-us/Gameplay/Tools/GameplayDebugger).
6 |
7 | Actions will display for the focused actor, its controller and the current player controller (the first local player). It is displayed as a tree of sub-actions.
8 |
9 | 
10 |
11 | ## Blueprint Debugger
12 |
13 | Like any other normal Blueprint, blueprint debugging is supported such as instance selection or visualization of graphs.
--------------------------------------------------------------------------------
/Docs/downloads/book.epub:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/downloads/book.epub
--------------------------------------------------------------------------------
/Docs/downloads/book.mobi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/downloads/book.mobi
--------------------------------------------------------------------------------
/Docs/downloads/book.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/downloads/book.pdf
--------------------------------------------------------------------------------
/Docs/downloads/implementation.md:
--------------------------------------------------------------------------------
1 | # Implementation
2 |
3 | ## Blueprints
4 |
5 | *Check the **[Test Project](https://mega.nz/#!JMowlKCA!wZv-L6oNSJCwDw1CUbTFyjPOXvd6viB-QLgK-u36xtY)** for a detailed example*
6 |
7 | ### Adding the interface
8 |
9 | For an actor to have a faction, we need to add a FactionAgentInterface.
10 | This will allow the system to set and get the current interface.
11 |
12 |
13 |
14 | To add an interface we have to go into **Class Settings**
15 |
16 | From here we click on **Add**, inside Interfaces and search for FactionAgentInterface
17 |
18 | ### Getting and Settings the Faction
19 |
20 | The system needs you to tell him how to set the faction and how to get it. This is done by overriding two functions as seen below.
21 | The Faction variable that is seen is a normal blueprint variable, that can be edited normally.
22 |
23 | 
24 |
25 | 
26 |
27 | ### Factions in Controllers
28 |
29 | Sometimes you may want a controller to share a faction with its controlled pawn or character.
30 |
31 | This process is exactly the same as before, except that instead of getting and setting a variable, we will get and set the faction from our pawn.
32 |
33 | ## C++
--------------------------------------------------------------------------------
/Docs/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var exec = require("child_process").exec;
3 | var runSequence = require('run-sequence');
4 | var gulpGitbook = require('gulp-gitbook');
5 |
6 | gulp.task('default', function() {
7 | // place code for your default task here
8 | });
9 |
10 | gulp.task('build', function(cb) {
11 | runSequence(
12 | //'refresh-summary',
13 | 'generate-book',
14 | function() {
15 | gulpGitbook(".", cb);
16 | });
17 | });
18 |
19 | gulp.task('build-light', function(cb) {
20 | /*runSequence(
21 | 'refresh-summary',
22 | function() {
23 | gulpGitbook(".", cb);
24 | });*/
25 | gulpGitbook(".", cb);
26 | });
27 |
28 | gulp.task('serve', function(cb) {
29 | /*runSequence(
30 | 'refresh-summary',
31 | function() {
32 | gulpGitbook.serve(".", cb);
33 | });*/
34 | gulpGitbook.serve(".", cb);
35 | });
36 |
37 | gulp.task('refresh-summary', function(cb) {
38 | exec('node ./node_modules/gitbook-summary/bin/summary.js sm', function(error, stdout, stderr) {
39 | cb(error);
40 | });
41 | });
42 |
43 | gulp.task('generate-book', function(cb) {
44 | gulpGitbook.pdf(".", {outputDir: "./downloads/book.pdf"}, function(err) {
45 | if(err) { cb(err); return; }
46 |
47 | gulpGitbook.epub(".", {outputDir: "./downloads/book.epub"}, function(err) {
48 | if(err) { cb(err); return; }
49 |
50 | gulpGitbook.mobi(".", {outputDir: "./downloads/book.mobi"}, cb);
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/Docs/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Manually
4 |
5 | This are the general steps for installing the plugin into your project:
6 |
7 | **1.** Download the last release from [here](https://github.com/PipeRift/ActionsExtension/releases)
8 | *Make sure you download the same version that your project uses*
9 |
10 | **2.** Extract the folder “ActionsExtension” into the **Plugins folder** of your existing project (e.g "MyProject/Plugins")
11 |
12 | **2.** Done! You can now open the project
13 |
14 | ## From Marketplace
15 |
16 | Install from the launcher:
17 | [AVAILABLE HERE](https://www.unrealengine.com/marketplace/actions-extension)
--------------------------------------------------------------------------------
/Docs/lib/Install.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cd ..
3 | call npm install
4 | pause
--------------------------------------------------------------------------------
/Docs/lib/LaunchLocal.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cd ..
3 | :begin
4 | call npm start
5 | goto :begin
--------------------------------------------------------------------------------
/Docs/lib/Publish.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cd ..
3 | call npm run publish
4 | pause
--------------------------------------------------------------------------------
/Docs/lib/RefreshSummary.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cd ..
3 | call node ./node_modules/gulp-cli/bin/gulp.js refresh-summary
4 | pause
--------------------------------------------------------------------------------
/Docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "actions-extension-documentation",
3 | "description": "",
4 | "directories": {
5 | "lib": "lib"
6 | },
7 | "devDependencies": {
8 | "del": "^3.0.0",
9 | "fs-extra": "^7.0.1",
10 | "gitbook": "^3.2.3",
11 | "gitbook-cli": "^2.3.2",
12 | "gitbook-plugin-search-plus": "^1.0.4-alpha-3",
13 | "gitbook-summary": "^1.2.2",
14 | "gulp": "^3.9.1",
15 | "gulp-cli": "^2.2.0",
16 | "gulp-exec": "^3.0.2",
17 | "gulp-gitbook": "0.0.2",
18 | "run-sequence": "^2.2.1"
19 | },
20 | "author": "Piperift",
21 | "homepage": "https://piperift.com",
22 | "dependencies": {
23 | "ebook-convert": "^2.0.1",
24 | "gitbook-plugin-collapsible-chapters": "^0.1.8",
25 | "gitbook-plugin-copy-code-button": "0.0.2",
26 | "gitbook-plugin-custom-favicon": "0.0.4",
27 | "gitbook-plugin-hide-published-with": "^1.0.3",
28 | "gitbook-plugin-hints": "^1.0.2",
29 | "gitbook-plugin-links": "^3.0.1",
30 | "gitbook-plugin-page-toc": "^1.1.0",
31 | "gitbook-plugin-prism": "^2.4.0",
32 | "gitbook-plugin-search-plus": "^1.0.3",
33 | "gitbook-plugin-styles-less": "^1.0.0"
34 | },
35 | "scripts": {
36 | "postinstall": "node ./node_modules/gitbook-cli/bin/gitbook.js install",
37 | "start": "node ./node_modules/gulp-cli/bin/gulp.js serve",
38 | "build": "node ./node_modules/gulp-cli/bin/gulp.js build",
39 | "build-light": "node ./node_modules/gulp-cli/bin/gulp.js build-light",
40 | "publish": "npm install && npm run build && cd _book && git init && git commit --allow-empty -m UpdateDocs && git checkout -b gh-pages && git add . && git rm *.md -f && git commit -am UpdateDocs && git push https://github.com/Piperift/ActionsExtension.git gh-pages --force"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Docs/quick-start.md:
--------------------------------------------------------------------------------
1 | # Quick Start
2 |
3 | Quick Start will show the basic steps to follow to setup the plugin and start using it at a base level.
4 |
5 | ## Setting Up the Project
6 |
7 | We can start by creating an empty project ([How to create UE4 projects](https://docs.unrealengine.com/en-US/Engine/Basics/Projects/Browser)) or instead using your own. Then installing the plugin from marketplace or inside Plugins folder (See [Installation](installation.md)).
8 |
9 | If everything went right, we should see the plugin enabled under *Edit->Plugins->Piperift*
10 |
11 | 
12 |
13 | Actions Extension doesn't require anything else to work. 🎊
14 |
15 | ## Creating The action
16 |
17 | Lets start by creating a very simple action.
18 |
19 | First we go to the **content browser, right click, Blueprint Class**
20 |
21 | 
22 |
23 | Then we **select Action class** (or any other child class of Action)
24 |
25 | 
26 |
27 | Then we **open the blueprint** we created and add the following functions on Activate. This will be called when the action starts its execution, then wait 1 second, and finish.
28 |
29 | 
30 |
31 | {% hint style='danger' %} Make sure your actions call **Succeed** or **Fail**. Otherwise the action will run until its owner is destroyed or the game closes. {% endhint %}
32 |
33 | ## Calling The action
34 |
35 | Now that we have our action ready, we have to execute it. For that we will go to our level blueprint.
36 |
37 | 
38 |
39 | Then from our BeginPlay we **add the node "Action"**
40 |
41 | 
42 |
43 | Finally, we assign the action we created previously to the class pin
44 |
45 | 
46 |
47 | ## The Result
48 |
49 | After all the previous steps we will see that the message prints exactly 1 second after we hit play.
50 |
51 | 
--------------------------------------------------------------------------------
/Docs/styles/style.less:
--------------------------------------------------------------------------------
1 | @font-family-serif: 'Josefin Slab', serif;
2 | @font-family-sans: 'Karla', sans-serif;
3 |
4 | .book {
5 | &.font-family-0 {
6 | font-family: @font-family-serif;
7 | }
8 | &.font-family-1 {
9 | font-family: @font-family-sans;
10 | }
11 | }
12 |
13 | h1,
14 | h2,
15 | h3,
16 | h4,
17 | h5,
18 | h6 {
19 | font-family: @font-family-serif;
20 | }
21 |
22 | .book-logo {
23 | margin: 10%;
24 |
25 | img {
26 | text-align: center;
27 | width: 100%;
28 | height: auto;
29 | }
30 | }
31 |
32 | .book {
33 | .book-logo-sepia, .book-logo-night {
34 | display: none;
35 | }
36 |
37 | &.color-theme-1 .book-logo {
38 | &-white { display: none; }
39 | &-sepia { display: block; }
40 | &-night { display: none; }
41 | }
42 | &.color-theme-2 .book-logo {
43 | &-white { display: none; }
44 | &-sepia { display: none; }
45 | &-night { display: block; }
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/Docs/usage/blueprints/README.md:
--------------------------------------------------------------------------------
1 | # Usage in Blueprints
2 |
3 | ## Call an Action
4 |
5 | To execute an action we have to use the Action node.
6 |
7 | You can find it by right clicking on a graph and searching for **"Action"**:
8 |
9 | 
10 |
11 | Then we have to assign the action class we want to use.
12 |
13 | 
14 |
15 | After this, **all its variables and delegates will show up** for you to use and the action is ready to execute.
16 |
17 | ## Create an Action
18 |
19 | To create an action, we have to go to
**content browser -> right click -> Blueprint Class**
20 |
21 | 
22 |
23 | Then we **select "Action" class** or one of Action's children
24 |
25 | 
26 |
27 | Then we **open the blueprint** we created.
28 |
29 | All actions have 3 main events:
30 |
31 | - **Activate**: When it gets created
32 | - **Tick**: When it ticks, if it is enabled. TickRate is applied.
33 | - **Finish**: When the action finished and why (*Success, Fail or Cancel*)
34 |
35 |
36 |
37 | {% hint style='danger' %} Make sure your actions call **Succeed** or **Fail**. Otherwise the action will run until its owner is destroyed or the game closes. {% endhint %}
--------------------------------------------------------------------------------
/Docs/usage/blueprints/delegates.md:
--------------------------------------------------------------------------------
1 | Exposing Delegates
2 |
3 | **Actions can expose Event Dispatchers to action calls.**
4 |
5 | - If the event dispatcher has any parameter, it will show up as an event pin
6 | - If it has no parameters, an execution pin will show up
7 |
8 | So as an example, if we have the following two events:
9 |
10 | - **SimpleEvent** with no parameters
11 |
12 | - **EventWithParameter** with a boolean
13 |
14 | 
15 |
16 | Our Action will look like this:
17 |
18 | 
19 |
20 |
21 |
22 | {% hint style='tip' %} If an event doesn't appear on an action node, **right click -> Refresh Node** to refresh it {% endhint %}
23 |
24 |
--------------------------------------------------------------------------------
/Docs/usage/blueprints/img/editable_variable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/blueprints/img/editable_variable.png
--------------------------------------------------------------------------------
/Docs/usage/blueprints/img/events_call.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/blueprints/img/events_call.png
--------------------------------------------------------------------------------
/Docs/usage/blueprints/img/events_implementation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/blueprints/img/events_implementation.png
--------------------------------------------------------------------------------
/Docs/usage/blueprints/img/expose_on_spawn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/blueprints/img/expose_on_spawn.png
--------------------------------------------------------------------------------
/Docs/usage/blueprints/img/new_variable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/blueprints/img/new_variable.png
--------------------------------------------------------------------------------
/Docs/usage/blueprints/variables.md:
--------------------------------------------------------------------------------
1 | # Exposing Variables
2 |
3 | Most of the times, when we have an action, we want to feed it with variables to customize its behavior, and as you will see, it's very easy to do so.
4 |
5 | First open your Action blueprint and add a new variable of any type
6 |
7 | 
8 |
9 | Mark the variable as Editable
10 |
11 | 
12 |
13 | Finally, check "**Expose on Spawn**" as true
14 |
15 | 
16 |
17 | This variable will now show up on all action nodes.
18 |
19 | {% hint style='tip' %} If a variable doesn't appear on an action node, **right click -> Refresh Node** to refresh it {% endhint %}
--------------------------------------------------------------------------------
/Docs/usage/cpp/README.md:
--------------------------------------------------------------------------------
1 | # Usage in C++
2 |
3 | {% hint style='tip' %} All functions are well commented on code. Feel free to give them a look for detailed information. {% endhint %}
4 |
5 | ## Call an Action
6 |
7 | There are multiple ways of creating an action, here are some:
8 |
9 | 
10 |
11 | By default actions will **auto activate**, but if we want to do some kind of setup, we can leave activation for later:
12 |
13 | 
14 |
15 | {% hint style='danger' %} Note that Actions require an owner with access to world. Otherwise, activation will fail. {% endhint %}
16 |
17 | ## Create an Action
18 |
19 | You can simply create a child class of UAction (or any other action class).
20 |
21 | 
22 |
23 | {% hint style='danger' %} Make sure your actions call **Succeed** or **Fail**. Otherwise the action will run until its owner is destroyed or the game closes. {% endhint %}
--------------------------------------------------------------------------------
/Docs/usage/cpp/delegates.md:
--------------------------------------------------------------------------------
1 | Exposing Delegates
2 |
3 | **Actions can expose Multicast delegates to action calls.**
4 |
5 | - If the delegate has any parameter, it will show up as an event pin
6 | - If it has no parameters, an execution pin will show up
7 |
8 | So as an example, if we have the following two events:
9 |
10 | - **NoParameter** with no parameters
11 | - **WithParameter** with a boolean
12 |
13 | We first have to declare our delegates
14 |
15 | 
16 |
17 | Then we add their variables as BlueprintAssignable, and that's it.
18 |
19 | 
20 |
21 | *Ignore those errors, it's just Intellisense doing its usual magic. Everything is fine, I promise*
22 |
23 |
--------------------------------------------------------------------------------
/Docs/usage/cpp/img/action_class.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/cpp/img/action_class.png
--------------------------------------------------------------------------------
/Docs/usage/cpp/img/create_action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/cpp/img/create_action.png
--------------------------------------------------------------------------------
/Docs/usage/cpp/img/custom_activation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/cpp/img/custom_activation.png
--------------------------------------------------------------------------------
/Docs/usage/cpp/img/delegate_declaration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/cpp/img/delegate_declaration.png
--------------------------------------------------------------------------------
/Docs/usage/cpp/img/delegate_usage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/cpp/img/delegate_usage.png
--------------------------------------------------------------------------------
/Docs/usage/cpp/img/variable_definition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/cpp/img/variable_definition.png
--------------------------------------------------------------------------------
/Docs/usage/cpp/variables.md:
--------------------------------------------------------------------------------
1 | # Exposing Variables
2 |
3 | Most of the times, when we have an action, we want to feed it with variables to customize its behavior, and as you will see, it's very easy to do so.
4 |
5 | Member variables marked as **BlueprintReadWrite** and with metadata **ExposeOnSpawn="true"** will show on Blueprint Action nodes.
6 |
7 | 
8 |
9 | With C++ in particular, any public variable can be edited as usual before Activation.
10 |
--------------------------------------------------------------------------------
/Docs/usage/img/action_node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/action_node.png
--------------------------------------------------------------------------------
/Docs/usage/img/context_blueprint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/context_blueprint.png
--------------------------------------------------------------------------------
/Docs/usage/img/gameplay_debugger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/gameplay_debugger.png
--------------------------------------------------------------------------------
/Docs/usage/img/level_blueprint_add_action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/level_blueprint_add_action.png
--------------------------------------------------------------------------------
/Docs/usage/img/open_level_blueprint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/open_level_blueprint.png
--------------------------------------------------------------------------------
/Docs/usage/img/patron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/patron.png
--------------------------------------------------------------------------------
/Docs/usage/img/patron_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/patron_small.png
--------------------------------------------------------------------------------
/Docs/usage/img/plugin_enabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/plugin_enabled.png
--------------------------------------------------------------------------------
/Docs/usage/img/popup_blueprint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/popup_blueprint.png
--------------------------------------------------------------------------------
/Docs/usage/img/quick_start_play.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/quick_start_play.gif
--------------------------------------------------------------------------------
/Docs/usage/img/set_action_type.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/set_action_type.png
--------------------------------------------------------------------------------
/Docs/usage/img/simple_action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Docs/usage/img/simple_action.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Actions Extension
2 | [](https://github.com/PipeRift/ActionsExtension/releases)
3 | 
4 | [](https://discord.gg/nnsdr22)
5 |
6 | Actions Extension is a lightweight plugin that adds asynchronous actions to UE4. This actions can be used for any task that requires waiting like AI or Rest services.
7 |
8 | If you like our plugins, consider becoming a Patron. It will go a long way in helping me create more awesome tech!
9 |
10 | [](https://www.patreon.com/bePatron?u=16503983)
11 |
12 | ## [Documentation](https://piperift.com/ActionsExtension)
13 |
14 |
--------------------------------------------------------------------------------
/Resources/Icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PipeRift/ActionsExtension/9e9fa614aad9dd0bc98d31b947c6fa4ffe8569a0/Resources/Icon128.png
--------------------------------------------------------------------------------
/Source/Actions/Actions.Build.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2020 Piperift. All Rights Reserved.
2 |
3 | using UnrealBuildTool;
4 |
5 | public class Actions : ModuleRules
6 | {
7 | public Actions(ReadOnlyTargetRules TargetRules) : base(TargetRules)
8 | {
9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
10 |
11 | PublicDependencyModuleNames.AddRange(new string[] {
12 | "Core",
13 | "CoreUObject",
14 | "Engine",
15 | "AIModule"
16 | });
17 |
18 | PrivateDependencyModuleNames.AddRange(new string[] { });
19 |
20 | if (TargetRules.bBuildDeveloperTools || (Target.Configuration != UnrealTargetConfiguration.Shipping && Target.Configuration != UnrealTargetConfiguration.Test))
21 | {
22 | PrivateDependencyModuleNames.Add("GameplayDebugger");
23 | PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=1");
24 | }
25 | else
26 | {
27 | PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=0");
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Source/Actions/Private/Action.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "Action.h"
4 |
5 | #include "TimerManager.h"
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 |
13 | #if WITH_GAMEPLAY_DEBUGGER
14 | # include "GameplayDebugger_Actions.h"
15 | #endif // WITH_GAMEPLAY_DEBUGGER
16 |
17 | DEFINE_LOG_CATEGORY(ActionLog);
18 |
19 | #include UE_INLINE_GENERATED_CPP_BY_NAME(Action)
20 |
21 |
22 | UAction* CreateAction(UObject* Owner, const TSubclassOf Type, bool bAutoActivate /*= false*/)
23 | {
24 | if (!IsValid(Owner) || !Type.Get() || Type == UAction::StaticClass())
25 | {
26 | return nullptr;
27 | }
28 |
29 | UAction* Action = NewObject(Owner, Type);
30 | if (bAutoActivate)
31 | {
32 | Action->Activate();
33 | }
34 | return Action;
35 | }
36 |
37 | UAction* CreateAction(UObject* Owner, const UAction* Template, bool bAutoActivate /*= false*/)
38 | {
39 | if (!IsValid(Owner) || !Template)
40 | {
41 | return nullptr;
42 | }
43 |
44 | UClass* const Type = Template->GetClass();
45 | check(Type);
46 |
47 | if (Type == UAction::StaticClass())
48 | {
49 | return nullptr;
50 | }
51 |
52 | UAction* Action = NewObject(Owner, Type, NAME_None, RF_NoFlags, const_cast(Template));
53 | if (bAutoActivate)
54 | {
55 | Action->Activate();
56 | }
57 | return Action;
58 | }
59 |
60 |
61 | bool UAction::Activate()
62 | {
63 | UActionsSubsystem* Subsystem = GetSubsystem();
64 | if (!IsValid(Subsystem)) [[unlikely]]
65 | {
66 | UE_LOG(ActionLog, Error, TEXT("Action subsystem not found for '%s'!"), *GetName());
67 | Destroy();
68 | return false;
69 | }
70 |
71 | if (!IsValid(this) || !IsValid(GetOuter()) || State != EActionState::Preparing)
72 | {
73 | UE_LOG(
74 | ActionLog, Warning, TEXT("Action '%s' is already running or pending destruction."), *GetName());
75 | Destroy();
76 | return false;
77 | }
78 |
79 | if (!CanActivate())
80 | {
81 | UE_LOG(ActionLog, Log, TEXT("Could not activate. CanActivate() Failed."));
82 | Destroy();
83 | return false;
84 | }
85 |
86 | // Add this action to its parent
87 | if (auto* ParentAction = GetParentAction())
88 | {
89 | ParentAction->AddChildren(this);
90 | }
91 | else
92 | {
93 | Subsystem->AddRootAction(this);
94 | }
95 |
96 | if (bWantsToTick)
97 | {
98 | Subsystem->AddActionToTickGroup(this);
99 | }
100 |
101 | State = EActionState::Running;
102 | OnActivation();
103 | return IsRunning() || Succeeded();
104 | }
105 |
106 | void UAction::Cancel()
107 | {
108 | if (!IsValid(this))
109 | {
110 | return;
111 | }
112 |
113 | if (!IsRunning())
114 | {
115 | Destroy();
116 | return;
117 | }
118 |
119 | OnFinish(State = EActionState::Cancelled);
120 | Destroy();
121 | }
122 |
123 | void UAction::OnFinish(const EActionState Reason)
124 | {
125 | // Stop any timers or latent actions for the action
126 | if (UWorld* World = GetWorld())
127 | {
128 | World->GetLatentActionManager().RemoveActionsForObject(this);
129 | World->GetTimerManager().ClearAllTimersForObject(this);
130 | }
131 |
132 | OnFinishedDelegate.Broadcast(Reason);
133 |
134 | const UObject* Parent = GetParent();
135 | // If we're in the process of being garbage collected it is unsafe to call out to blueprints
136 | if (Parent && !Parent->HasAnyFlags(RF_BeginDestroyed) && !Parent->IsUnreachable())
137 | {
138 | ReceiveFinished(Reason);
139 | }
140 | }
141 |
142 | void UAction::Finish(bool bSuccess)
143 | {
144 | if (!IsRunning() || !IsValid(this))
145 | {
146 | return;
147 | }
148 |
149 | State = bSuccess ? EActionState::Success : EActionState::Failure;
150 | OnFinish(State);
151 |
152 | // Remove from parent action
153 | if (auto* ParentAction = GetParentAction())
154 | {
155 | ParentAction->RemoveChildren(this);
156 | }
157 |
158 | Destroy();
159 | }
160 |
161 | void UAction::Destroy()
162 | {
163 | if (!IsValid(this))
164 | {
165 | return;
166 | }
167 |
168 | // Cancel and destroy all children tasks
169 | for (auto* Children : ChildrenActions)
170 | {
171 | if (Children)
172 | {
173 | Children->Cancel();
174 | }
175 | }
176 | ChildrenActions.Reset();
177 |
178 | MarkAsGarbage();
179 | }
180 |
181 | void UAction::AddChildren(UAction* Child)
182 | {
183 | ChildrenActions.Add(Child);
184 | }
185 |
186 | void UAction::RemoveChildren(UAction* Child)
187 | {
188 | ChildrenActions.RemoveSwap(Child, false);
189 | }
190 |
191 | bool UAction::ReceiveCanActivate_Implementation()
192 | {
193 | return true;
194 | }
195 |
196 | bool UAction::GetWantsToTick() const
197 | {
198 | return bWantsToTick;
199 | }
200 |
201 | bool UAction::IsRunning() const
202 | {
203 | return State == EActionState::Running;
204 | }
205 |
206 | bool UAction::Succeeded() const
207 | {
208 | return State == EActionState::Success;
209 | }
210 |
211 | bool UAction::Failed() const
212 | {
213 | return State == EActionState::Failure;
214 | }
215 |
216 | EActionState UAction::GetState() const
217 | {
218 | return State;
219 | }
220 |
221 | UObject* const UAction::GetParent() const
222 | {
223 | return GetOuter();
224 | }
225 |
226 | UAction* UAction::GetParentAction() const
227 | {
228 | return Cast(GetOuter());
229 | }
230 |
231 | float UAction::GetTickRate() const
232 | {
233 | // Reduce TickRate Precision to 0.1ms
234 | return FMath::FloorToFloat(TickRate * 10000.f) * 0.0001f;
235 | }
236 |
237 | UObject* UAction::GetOwner() const
238 | {
239 | return Owner.Get();
240 | }
241 |
242 | AActor* UAction::GetOwnerActor() const
243 | {
244 | // With this function we can predict the owner is more likely to be an actor so we check it first
245 | if (AActor* Actor = Cast(Owner))
246 | {
247 | return Actor;
248 | }
249 | else if (auto* Component = Cast(Owner))
250 | {
251 | return Component->GetOwner();
252 | }
253 | return nullptr;
254 | }
255 |
256 | UActorComponent* UAction::GetOwnerComponent() const
257 | {
258 | return Cast(Owner);
259 | }
260 |
261 | UWorld* UAction::GetWorld() const
262 | {
263 | // If we are a CDO, we must return nullptr to fool UObject::ImplementsGetWorld
264 | if (HasAllFlags(RF_ClassDefaultObject))
265 | {
266 | return nullptr;
267 | }
268 |
269 | if (const UObject* InOwner = GetOwner())
270 | {
271 | return InOwner->GetWorld();
272 | }
273 | return nullptr;
274 | }
275 |
276 | #if WITH_GAMEPLAY_DEBUGGER
277 | void UAction::DescribeSelfToGameplayDebugger(FGameplayDebugger_Actions& Debugger, int8 Indent) const
278 | {
279 | FString ColorText = TEXT("");
280 | switch (State)
281 | {
282 | case EActionState::Running:
283 | ColorText = TEXT("{cyan}");
284 | break;
285 | case EActionState::Success:
286 | ColorText = TEXT("{green}");
287 | break;
288 | default:
289 | ColorText = TEXT("{red}");
290 | }
291 |
292 | FString IndentString = "";
293 | for (int32 I = 0; I < Indent; ++I)
294 | {
295 | IndentString += " ";
296 | }
297 |
298 | const FString CanceledSuffix = (State == EActionState::Cancelled) ? TEXT("CANCELLED") : FString{};
299 |
300 | if (IsRunning())
301 | {
302 | Debugger.AddTextLine(
303 | FString::Printf(TEXT("%s%s>%s %s"), *IndentString, *ColorText, *GetName(), *CanceledSuffix));
304 |
305 | for (const auto* ChildAction : ChildrenActions)
306 | {
307 | if (ChildAction)
308 | {
309 | ChildAction->DescribeSelfToGameplayDebugger(Debugger, Indent + 1);
310 | }
311 | else
312 | {
313 | ensureMsgf(false, TEXT("Invalid child on action %s while debugging"), *GetName());
314 | }
315 | }
316 | }
317 | }
318 | #endif // WITH_GAMEPLAY_DEBUGGER
319 |
320 | void UAction::SetWantsToTick(bool bValue)
321 | {
322 | if (bValue != bWantsToTick)
323 | {
324 | bWantsToTick = bValue;
325 | UActionsSubsystem* Subsystem = GetSubsystem();
326 | if (bValue)
327 | {
328 | Subsystem->AddActionToTickGroup(this);
329 | }
330 | else
331 | {
332 | Subsystem->RemoveActionFromTickGroup(this);
333 | }
334 | }
335 | }
336 |
337 | void UAction::PostInitProperties()
338 | {
339 | Super::PostInitProperties();
340 |
341 | UObject* Outer = GetOuter();
342 | if (UAction* Parent = Cast(Outer))
343 | {
344 | Owner = Parent->GetOwner();
345 | }
346 | else
347 | {
348 | Owner = Outer;
349 | }
350 | }
351 |
352 | UActionsSubsystem* UAction::GetSubsystem() const
353 | {
354 | const UWorld* World = GetWorld();
355 | const UGameInstance* GI = World ? World->GetGameInstance() : nullptr;
356 | return UGameInstance::GetSubsystem(GI);
357 | }
358 |
--------------------------------------------------------------------------------
/Source/Actions/Private/ActionLibrary.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "ActionLibrary.h"
4 |
--------------------------------------------------------------------------------
/Source/Actions/Private/ActionsModule.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "ActionsModule.h"
4 |
5 | #if WITH_GAMEPLAY_DEBUGGER
6 | # include "GameplayDebugger.h"
7 | # include "GameplayDebugger_Actions.h"
8 | #endif // WITH_GAMEPLAY_DEBUGGER
9 |
10 |
11 | DEFINE_LOG_CATEGORY(LogActions)
12 |
13 | #define LOCTEXT_NAMESPACE "ActionsModule"
14 |
15 | void FActionsModule::StartupModule()
16 | {
17 | UE_LOG(LogActions, Log, TEXT("ActionsExtension: Log Started"));
18 |
19 | // Register Gameplay debugger
20 | #if WITH_GAMEPLAY_DEBUGGER
21 | IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
22 | GameplayDebuggerModule.RegisterCategory("Actions",
23 | IGameplayDebugger::FOnGetCategory::CreateStatic(&FGameplayDebugger_Actions::MakeInstance),
24 | EGameplayDebuggerCategoryState::EnabledInGameAndSimulate);
25 | GameplayDebuggerModule.NotifyCategoriesChanged();
26 | #endif
27 | }
28 |
29 | void FActionsModule::ShutdownModule()
30 | {
31 | UE_LOG(LogActions, Log, TEXT("ActionsExtension: Log Ended"));
32 | }
33 |
34 | #undef LOCTEXT_NAMESPACE
35 |
36 | IMPLEMENT_MODULE(FActionsModule, Actions)
--------------------------------------------------------------------------------
/Source/Actions/Private/ActionsSubsystem.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "ActionsSubsystem.h"
4 |
5 | #include "Action.h"
6 |
7 | #include
8 |
9 |
10 | #if WITH_GAMEPLAY_DEBUGGER
11 | # include "GameplayDebugger_Actions.h"
12 | #endif // WITH_GAMEPLAY_DEBUGGER
13 |
14 |
15 | void FActionsTickGroup::Tick(float DeltaTime)
16 | {
17 | if (Actions.Num() <= 0)
18 | {
19 | return;
20 | }
21 |
22 | if (TickRate > KINDA_SMALL_NUMBER)
23 | {
24 | TickTimeElapsed += DeltaTime;
25 | if (TickTimeElapsed < TickRate)
26 | {
27 | return;
28 | }
29 |
30 | // Delayed Tick
31 | DelayedTick(TickTimeElapsed);
32 |
33 | TickTimeElapsed = 0.f;
34 | }
35 | else
36 | {
37 | // Normal Tick
38 | DelayedTick(DeltaTime);
39 | }
40 | }
41 |
42 | void FActionsTickGroup::DelayedTick(float DeltaTime)
43 | {
44 | for (int32 i = 0; i < Actions.Num(); ++i)
45 | {
46 | auto* const Action = Actions[i];
47 | if (!Action)
48 | {
49 | Actions.RemoveAtSwap(i, 1, false);
50 | --i;
51 | }
52 | else if (Action->CanTick())
53 | {
54 | Action->DoTick(DeltaTime);
55 | }
56 | }
57 | }
58 |
59 | void FActionOwner::CancelAll(bool bShouldShrink)
60 | {
61 | for (auto& Action : Actions)
62 | {
63 | if (Action)
64 | {
65 | Action->Cancel();
66 | }
67 | }
68 |
69 | if (bShouldShrink)
70 | Actions.Empty();
71 | else
72 | Actions.Reset();
73 | }
74 |
75 | void FActionOwner::CancelByPredicate(const TFunctionRef& Predicate, bool bShouldShrink)
76 | {
77 | for (int32 i = 0; i < Actions.Num(); ++i)
78 | {
79 | auto* Action = Actions[i].Get();
80 | if (Action && Predicate(Action))
81 | {
82 | // Cancel action
83 | Action->Cancel();
84 |
85 | // Remove action
86 | Actions.RemoveAtSwap(i, 1, false);
87 | --i;
88 | }
89 | }
90 |
91 | if (bShouldShrink)
92 | {
93 | Actions.Shrink();
94 | }
95 | }
96 |
97 |
98 | void UActionsSubsystem::Initialize(FSubsystemCollectionBase& Collection)
99 | {
100 | Super::Initialize(Collection);
101 | }
102 |
103 | void UActionsSubsystem::Deinitialize()
104 | {
105 | CancelAll();
106 | Super::Deinitialize();
107 | }
108 |
109 | void UActionsSubsystem::Tick(float DeltaTime)
110 | {
111 | // Cancel destroyed object actions or of which the outer is invalid
112 | for (auto RootIt = ActionOwners.CreateIterator(); RootIt; ++RootIt)
113 | {
114 | if (!RootIt->Owner.IsValid())
115 | {
116 | RootIt->CancelAll(false);
117 | RootIt.RemoveCurrent();
118 | }
119 | else
120 | {
121 | // Remove garbage collected actions
122 | RootIt->Actions.RemoveAllSwap(
123 | [](const UAction* Action) {
124 | return !IsValid(Action);
125 | },
126 | false);
127 |
128 | if (RootIt->Actions.Num() <= 0)
129 | {
130 | RootIt.RemoveCurrent();
131 | }
132 | }
133 | }
134 |
135 | // Tick all tick groups
136 | const float TimeDilation = GetWorld()->GetWorldSettings()->GetEffectiveTimeDilation();
137 | for (int32 i = 0; i < TickGroups.Num(); ++i)
138 | {
139 | auto& TickGroup = TickGroups[i];
140 |
141 | TickGroup.Tick(DeltaTime * TimeDilation);
142 |
143 | if (TickGroup.Actions.Num() <= 0)
144 | {
145 | // Tick group is empty so we remove and ignore it
146 | TickGroups.RemoveAtSwap(i, 1, false);
147 | --i;
148 | }
149 | }
150 | TickGroups.Shrink();
151 | }
152 |
153 | void UActionsSubsystem::CancelAll()
154 | {
155 | for (auto& RootAction : ActionOwners)
156 | {
157 | RootAction.CancelAll(false);
158 | }
159 | ActionOwners.Reset();
160 | }
161 |
162 | void UActionsSubsystem::CancelAllByOwner(UObject* Object)
163 | {
164 | const FSetElementId OwnerId = ActionOwners.FindId(Object);
165 | if (OwnerId.IsValidId())
166 | {
167 | ActionOwners[OwnerId].CancelAll(false);
168 | ActionOwners.Remove(OwnerId);
169 | }
170 | }
171 |
172 | void UActionsSubsystem::CancelByPredicate(TFunctionRef Predicate)
173 | {
174 | for (auto& RootAction : ActionOwners)
175 | {
176 | RootAction.CancelByPredicate(Predicate);
177 | }
178 | }
179 |
180 | void UActionsSubsystem::CancelByOwnerPredicate(UObject* Object, TFunctionRef Predicate)
181 | {
182 | if (FActionOwner* const Owner = ActionOwners.Find(Object))
183 | {
184 | Owner->CancelByPredicate(Predicate);
185 | }
186 | }
187 |
188 | void UActionsSubsystem::AddRootAction(UAction* Child)
189 | {
190 | check(Child);
191 |
192 | // Registry for GC Canceling
193 | UObject* Owner = Child->GetOuter();
194 |
195 | FSetElementId OwnerId = ActionOwners.FindId(Owner);
196 | if (!OwnerId.IsValidId())
197 | {
198 | OwnerId = ActionOwners.Add({Owner});
199 | }
200 | ActionOwners[OwnerId].Actions.Add(Child);
201 | }
202 |
203 | void UActionsSubsystem::AddActionToTickGroup(UAction* Child)
204 | {
205 | const float TickRate = Child->GetTickRate();
206 |
207 | // Registry for tick groups
208 | FActionsTickGroup* Group = TickGroups.FindByKey(TickRate);
209 | if (!Group)
210 | {
211 | TickGroups.Add({TickRate});
212 | Group = &TickGroups.Last();
213 | }
214 |
215 | Group->Actions.Add(Child);
216 | }
217 |
218 | void UActionsSubsystem::RemoveActionFromTickGroup(UAction* Child)
219 | {
220 | const float TickRate = Child->GetTickRate();
221 | int32 Index = TickGroups.Find(TickRate);
222 | if (Index != INDEX_NONE)
223 | {
224 | auto& Group = TickGroups[Index];
225 | if (Group.Actions.Num() > 1)
226 | {
227 | Group.Actions.RemoveSwap(Child, false);
228 | }
229 | else
230 | {
231 | TickGroups.RemoveAtSwap(Index);
232 | }
233 | }
234 | }
235 |
236 | #if WITH_GAMEPLAY_DEBUGGER
237 | void UActionsSubsystem::DescribeOwnerToGameplayDebugger(
238 | UObject* Owner, const FName& BaseName, FGameplayDebugger_Actions& Debugger) const
239 | {
240 | static const FString StateColorText = TEXT("{green}");
241 |
242 | Debugger.AddTextLine(
243 | FString::Printf(TEXT("%s%s: %s"), *StateColorText, *BaseName.ToString(), *Owner->GetName()));
244 |
245 | if (const FActionOwner* const RootAction = ActionOwners.Find(Owner))
246 | {
247 | for (const UAction* Action : RootAction->Actions)
248 | {
249 | if (IsValid(Action))
250 | {
251 | Action->DescribeSelfToGameplayDebugger(Debugger, 1);
252 | }
253 | }
254 | }
255 |
256 | Debugger.AddTextLine(TEXT(""));
257 | }
258 | #endif // WITH_GAMEPLAY_DEBUGGER
259 |
--------------------------------------------------------------------------------
/Source/Actions/Private/BTT_RunAction.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "BTT_RunAction.h"
4 |
5 |
6 | EBTNodeResult::Type UBTT_RunAction::ExecuteTask(UBehaviorTreeComponent& InOwnerComp, uint8* NodeMemory)
7 | {
8 | if (!ActionType)
9 | {
10 | return EBTNodeResult::Failed;
11 | }
12 |
13 | AActor* OwnerActor = InOwnerComp.GetTypedOuter();
14 | check(OwnerActor);
15 |
16 | Action = CreateAction(OwnerActor, ActionType, false);
17 | check(Action);
18 |
19 | Action->OnFinishedDelegate.AddDynamic(this, &UBTT_RunAction::OnRunActionFinished);
20 | Action->Activate();
21 |
22 | OwnerComp = &InOwnerComp;
23 | return Action ? EBTNodeResult::InProgress : EBTNodeResult::Failed;
24 | }
25 |
26 | EBTNodeResult::Type UBTT_RunAction::AbortTask(UBehaviorTreeComponent& InOwnerComp, uint8* NodeMemory)
27 | {
28 | if (IsValid(Action))
29 | {
30 | Action->Cancel();
31 | }
32 | return EBTNodeResult::Aborted;
33 | }
34 |
35 | void UBTT_RunAction::DescribeRuntimeValues(const UBehaviorTreeComponent& InOwnerComp, uint8* NodeMemory,
36 | EBTDescriptionVerbosity::Type Verbosity, TArray& Values) const
37 | {
38 | Super::DescribeRuntimeValues(InOwnerComp, NodeMemory, Verbosity, Values);
39 |
40 | if (Action)
41 | {
42 | const FString State = ToString(Action->GetState());
43 | Values.Add(FString::Printf(TEXT("state: %s"), *State));
44 | }
45 | }
46 |
47 | FString UBTT_RunAction::GetStaticDescription() const
48 | {
49 | FString ActionName = (ActionType) ? ActionType->GetClass()->GetName() : "None";
50 | ActionName.RemoveFromEnd("_C", ESearchCase::CaseSensitive);
51 | return FString::Printf(TEXT("Action: %s"), *ActionName);
52 | }
53 |
54 | void UBTT_RunAction::OnRunActionFinished(const EActionState Reason)
55 | {
56 | if (OwnerComp)
57 | {
58 | switch (Reason)
59 | {
60 | case EActionState::Success:
61 | FinishLatentTask(*OwnerComp, EBTNodeResult::Succeeded);
62 | break;
63 | case EActionState::Failure:
64 | FinishLatentTask(*OwnerComp, EBTNodeResult::Failed);
65 | break;
66 | case EActionState::Cancelled: // Do Nothing
67 | break;
68 | default:
69 | FinishLatentTask(*OwnerComp, EBTNodeResult::Aborted);
70 | break;
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/Source/Actions/Private/GameplayDebugger_Actions.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "GameplayDebugger_Actions.h"
4 |
5 | #include "ActionsSubsystem.h"
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 |
13 |
14 | #if WITH_GAMEPLAY_DEBUGGER
15 |
16 | FGameplayDebugger_Actions::FGameplayDebugger_Actions() {}
17 |
18 | TSharedRef FGameplayDebugger_Actions::MakeInstance()
19 | {
20 | return MakeShareable(new FGameplayDebugger_Actions());
21 | }
22 |
23 | void FGameplayDebugger_Actions::CollectData(APlayerController* OwnerPC, AActor* DebugActor)
24 | {
25 | UWorld* World = OwnerPC->GetWorld();
26 |
27 | UGameInstance* GI = World ? World->GetGameInstance() : nullptr;
28 | if (!GI)
29 | {
30 | return;
31 | }
32 |
33 | auto* Subsystem = GI->GetSubsystem();
34 | check(Subsystem);
35 |
36 | Subsystem->DescribeOwnerToGameplayDebugger(OwnerPC, TEXT("Player Controller"), *this);
37 |
38 | if (DebugActor)
39 | {
40 | Subsystem->DescribeOwnerToGameplayDebugger(DebugActor, TEXT("Actor"), *this);
41 |
42 | if (const APawn* DebugPawn = Cast(DebugActor))
43 | {
44 | AController* DebugController = DebugPawn->GetController();
45 | if (DebugController && OwnerPC != DebugController)
46 | {
47 | Subsystem->DescribeOwnerToGameplayDebugger(DebugController, TEXT("Controller"), *this);
48 | }
49 | }
50 | }
51 | }
52 | #endif // ENABLE_GAMEPLAY_DEBUGGER
53 |
--------------------------------------------------------------------------------
/Source/Actions/Private/GameplayDebugger_Actions.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "CoreMinimal.h"
6 | #if WITH_GAMEPLAY_DEBUGGER
7 | # include "GameplayDebuggerCategory.h"
8 | #endif
9 |
10 | #if WITH_GAMEPLAY_DEBUGGER
11 |
12 | class ACTIONS_API FGameplayDebugger_Actions : public FGameplayDebuggerCategory
13 | {
14 | public:
15 | FGameplayDebugger_Actions();
16 |
17 | static TSharedRef MakeInstance();
18 |
19 | virtual void CollectData(APlayerController* OwnerPC, AActor* DebugActor) override;
20 | };
21 |
22 | #endif // WITH_GAMEPLAY_DEBUGGER
23 |
--------------------------------------------------------------------------------
/Source/Actions/Public/Action.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "ActionsModule.h"
6 | #include "ActionsSubsystem.h"
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "Action.generated.h"
16 |
17 |
18 | DECLARE_LOG_CATEGORY_EXTERN(ActionLog, Log, All);
19 |
20 |
21 | class AActor;
22 | class UActorComponent;
23 | class UAction;
24 |
25 |
26 | /**
27 | * Creates a new action. Templated version
28 | * @param ActionType
29 | * @param Owner of the action. If destroyed, the action will follow.
30 | * @param bAutoActivate if true activates the action. If false, Action->Activate() can be called later.
31 | */
32 | template
33 | ActionType* CreateAction(UObject* Owner, bool bAutoActivate = false)
34 | {
35 | static_assert(
36 | !std::is_same_v, "Instantiating UAction is not allowed. Use a child class.");
37 | static_assert(TIsDerivedFrom::IsDerived, "Provided class must inherit UAction.");
38 | return Cast(CreateAction(Owner, ActionType::StaticClass(), bAutoActivate));
39 | }
40 |
41 | /**
42 | * Creates a new action. Templated version
43 | * @param ActionType
44 | * @param Owner of the action. If destroyed, the action will follow.
45 | * @param Template whose properties and class are used to create the action.
46 | * @param bAutoActivate if true activates the action. If false, Action->Activate() can be called later.
47 | */
48 | template
49 | ActionType* CreateAction(UObject* Owner, const ActionType* Template, bool bAutoActivate = false)
50 | requires(!std::is_same_v && TIsDerivedFrom::IsDerived)
51 | {
52 | return Cast(CreateAction(Owner, (const UAction*) Template, bAutoActivate));
53 | }
54 |
55 | /**
56 | * Creates a new action
57 | * @param Owner of the action. If destroyed, the action will follow.
58 | * @param Type of the action to create
59 | * @param bAutoActivate if true activates the action. If false, Action->Activate() can be called later.
60 | */
61 | ACTIONS_API UAction* CreateAction(
62 | UObject* Owner, const TSubclassOf Type, bool bAutoActivate = false);
63 |
64 | /**
65 | * Creates a new action
66 | * @param Owner of the action. If destroyed, the action will follow.
67 | * @param Template whose properties and class are used to create the action.
68 | * @param bAutoActivate if true activates the action. If false, Action->Activate() can be called later.
69 | */
70 | ACTIONS_API UAction* CreateAction(UObject* Owner, const UAction* Template, bool bAutoActivate = false);
71 |
72 |
73 | /**
74 | * Result of a node execution
75 | */
76 | UENUM(Blueprintable)
77 | enum class EActionState : uint8
78 | {
79 | Preparing UMETA(Hidden),
80 | Running UMETA(Hidden),
81 | Success,
82 | Failure,
83 | Cancelled
84 | };
85 |
86 | inline FString ToString(EActionState Value)
87 | {
88 | const UEnum* EnumPtr = FindObject(nullptr, TEXT("/Script/Actions.EActionState"), true);
89 | return EnumPtr ? EnumPtr->GetNameByValue((int64) Value).ToString() : TEXT("Invalid");
90 | }
91 |
92 | DECLARE_DYNAMIC_MULTICAST_DELEGATE(FActionActivatedDelegate);
93 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FActionFinishedDelegate, const EActionState, Reason);
94 |
95 |
96 | /**
97 | *
98 | */
99 | UCLASS(Blueprintable, EditInlineNew, meta = (ExposedAsyncProxy))
100 | class ACTIONS_API UAction : public UObject
101 | {
102 | GENERATED_BODY()
103 |
104 | /************************************************************************/
105 | /* PROPERTIES */
106 | /************************************************************************/
107 | private:
108 | UPROPERTY()
109 | TWeakObjectPtr Owner;
110 |
111 | UPROPERTY()
112 | EActionState State = EActionState::Preparing;
113 |
114 | UPROPERTY(SaveGame)
115 | TArray ChildrenActions;
116 |
117 |
118 | /** If true the action will tick. Tick can be enabled or disabled while running. */
119 | UPROPERTY(EditAnywhere, Category = Action)
120 | bool bWantsToTick = false;
121 |
122 | protected:
123 | // Tick length in seconds. 0 is default tick rate
124 | UPROPERTY(EditDefaultsOnly, Category = Action)
125 | float TickRate = 0.15f;
126 |
127 |
128 | public:
129 | /** Delegates */
130 |
131 | // Notify when the action is activated
132 | UPROPERTY()
133 | FActionActivatedDelegate OnActivationDelegate;
134 |
135 | // Notify when the action finished
136 | UPROPERTY()
137 | FActionFinishedDelegate OnFinishedDelegate;
138 |
139 |
140 | /************************************************************************/
141 | /* METHODS */
142 | /************************************************************************/
143 |
144 | /** Called to active an action if not already. */
145 | UFUNCTION(BlueprintCallable, Category = Action)
146 | bool Activate();
147 |
148 | /** Internal Use Only. Called when the action is stopped from running by its owner */
149 | void Cancel();
150 |
151 | /** Internal Use Only. Called by the subsystem when TickRate exceeds */
152 | void DoTick(float DeltaTime)
153 | {
154 | Tick(DeltaTime);
155 | ReceiveTick(DeltaTime);
156 | }
157 |
158 | protected:
159 | UFUNCTION(BlueprintPure, Category = Action)
160 | virtual bool CanActivate()
161 | {
162 | return ReceiveCanActivate();
163 | }
164 |
165 | virtual void OnActivation()
166 | {
167 | OnActivationDelegate.Broadcast();
168 | ReceiveActivate();
169 | }
170 |
171 | virtual void Tick(float DeltaTime) {}
172 |
173 | virtual void OnFinish(const EActionState Reason);
174 |
175 | private:
176 | void Finish(bool bSuccess = true);
177 |
178 | void Destroy();
179 |
180 | void AddChildren(UAction* Child);
181 | void RemoveChildren(UAction* Child);
182 |
183 |
184 | public:
185 | UFUNCTION(BlueprintCallable, Category = Action, meta = (KeyWords = "Finish"))
186 | void Succeed()
187 | {
188 | Finish(true);
189 | }
190 |
191 | UFUNCTION(BlueprintCallable, Category = Action, meta = (KeyWords = "Finish"))
192 | void Fail(FName Error = NAME_None)
193 | {
194 | UE_LOG(LogActions, Log, TEXT("Action '%s' failed: %s"), *GetName(), *Error.ToString());
195 | Finish(false);
196 | }
197 |
198 |
199 | /** Events */
200 | protected:
201 | /** Event called to check if an action can activate. */
202 | UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "Can Activate"))
203 | bool ReceiveCanActivate();
204 |
205 | /** Called when this action is activated */
206 | UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Activate"))
207 | void ReceiveActivate();
208 |
209 | /** Called when tick is received based on TickRate */
210 | UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Tick"))
211 | void ReceiveTick(float DeltaTime);
212 |
213 | /** Called when this action finishes */
214 | UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Finished"))
215 | void ReceiveFinished(const EActionState Reason);
216 |
217 |
218 | public:
219 | bool CanTick() const
220 | {
221 | return bWantsToTick && IsRunning();
222 | }
223 |
224 | UFUNCTION(BlueprintCallable, Category = Action)
225 | void SetWantsToTick(bool bValue);
226 |
227 | UFUNCTION(BlueprintPure, Category = Action)
228 | bool GetWantsToTick() const;
229 |
230 | UFUNCTION(BlueprintPure, Category = Action)
231 | float GetTickRate() const;
232 |
233 | UFUNCTION(BlueprintPure, Category = Action)
234 | bool IsRunning() const;
235 |
236 | UFUNCTION(BlueprintPure, Category = Action)
237 | bool Succeeded() const;
238 |
239 | UFUNCTION(BlueprintPure, Category = Action)
240 | bool Failed() const;
241 |
242 | UFUNCTION(BlueprintPure, Category = Action)
243 | EActionState GetState() const;
244 |
245 | UFUNCTION(BlueprintPure, Category = Action)
246 | UObject* const GetParent() const;
247 |
248 | UFUNCTION(BlueprintPure, Category = Action)
249 | UAction* GetParentAction() const;
250 |
251 | /** @return the object that executes the root action */
252 | UFUNCTION(BlueprintPure, Category = Action)
253 | UObject* GetOwner() const;
254 |
255 | /** @return the actor if any that executes the root action */
256 | UFUNCTION(BlueprintPure, BlueprintPure, Category = Action)
257 | AActor* GetOwnerActor() const;
258 |
259 | /** @return the component if any that executes the root action */
260 | UFUNCTION(BlueprintPure, Category = Action)
261 | UActorComponent* GetOwnerComponent() const;
262 |
263 | UWorld* GetWorld() const override;
264 |
265 | #if WITH_GAMEPLAY_DEBUGGER
266 | void DescribeSelfToGameplayDebugger(class FGameplayDebugger_Actions& Debugger, int8 Indent) const;
267 | #endif // WITH_GAMEPLAY_DEBUGGER
268 |
269 | //~ Begin UObject Interface
270 | void PostInitProperties() override;
271 | //~ End UObject Interface
272 |
273 | protected:
274 | UActionsSubsystem* GetSubsystem() const;
275 | };
276 |
--------------------------------------------------------------------------------
/Source/Actions/Public/ActionLibrary.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "Action.h"
6 |
7 | #include
8 | #include
9 |
10 | #include "ActionLibrary.generated.h"
11 |
12 |
13 | UCLASS()
14 | class ACTIONS_API UActionLibrary : public UBlueprintFunctionLibrary
15 | {
16 | GENERATED_BODY()
17 |
18 | public:
19 | UFUNCTION(BlueprintCallable, Category = Action,
20 | meta = (BlueprintInternalUseOnly = "true", DefaultToSelf = "Owner", WorldContext = "Owner"))
21 | static UAction* CreateAction(UObject* Owner, const TSubclassOf Type, bool bAutoActivate = false)
22 | {
23 | return ::CreateAction(Owner, Type.Get(), bAutoActivate);
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/Source/Actions/Public/ActionsModule.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 | #pragma once
3 |
4 | #include
5 | #include
6 |
7 | #if WITH_EDITOR
8 | # include "Developer/AssetTools/Public/AssetToolsModule.h"
9 | # include "Developer/AssetTools/Public/IAssetTools.h"
10 |
11 | #endif // WITH_EDITOR
12 |
13 | DECLARE_LOG_CATEGORY_EXTERN(LogActions, All, All);
14 |
15 |
16 | class FActionsModule : public IModuleInterface
17 | {
18 | public:
19 | /** IModuleInterface implementation */
20 | virtual void StartupModule() override;
21 | virtual void ShutdownModule() override;
22 |
23 | virtual bool SupportsDynamicReloading() override
24 | {
25 | return true;
26 | }
27 |
28 | #if WITH_EDITOR
29 | EAssetTypeCategories::Type GetAssetCategoryBit() const
30 | {
31 | return EAssetTypeCategories::Misc;
32 | }
33 | #endif
34 |
35 |
36 | FORCEINLINE static FActionsModule& Get()
37 | {
38 | return FModuleManager::LoadModuleChecked("Actions");
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/Source/Actions/Public/ActionsSubsystem.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "ActionsSubsystem.generated.h"
12 |
13 |
14 | class UAction;
15 |
16 | /**
17 | * Contains a list of actions with the same TickRate
18 | */
19 | USTRUCT()
20 | struct FActionsTickGroup
21 | {
22 | GENERATED_BODY()
23 |
24 | UPROPERTY()
25 | float TickRate = 0.f;
26 |
27 | UPROPERTY(Transient)
28 | float TickTimeElapsed = 0.f;
29 |
30 | UPROPERTY()
31 | TArray Actions;
32 |
33 |
34 | FActionsTickGroup(float TickRate = 0.f) : TickRate(TickRate) {}
35 |
36 | inline void Tick(float DeltaTime);
37 |
38 | private:
39 | inline void DelayedTick(float DeltaTime);
40 |
41 | public:
42 | bool operator==(const FActionsTickGroup& Other) const
43 | {
44 | return FMath::IsNearlyEqual(TickRate, Other.TickRate);
45 | }
46 | bool operator!=(const FActionsTickGroup& Other) const
47 | {
48 | return !(*this == Other);
49 | }
50 |
51 | friend const uint32 GetTypeHash(const FActionsTickGroup& InGroup)
52 | {
53 | return GetTypeHash(InGroup.TickRate);
54 | }
55 | };
56 |
57 | /**
58 | * Represents a dependency of an objects with all its actions
59 | * Used to cancel actions whose owner is destroyed
60 | */
61 | USTRUCT()
62 | struct FActionOwner
63 | {
64 | GENERATED_BODY()
65 |
66 | UPROPERTY()
67 | TWeakObjectPtr Owner;
68 |
69 | UPROPERTY()
70 | TArray> Actions;
71 |
72 |
73 | FActionOwner(UObject* Owner = nullptr) : Owner(Owner) {}
74 |
75 | void CancelAll(bool bShouldShrink = true);
76 | void CancelByPredicate(const TFunctionRef& Predicate, bool bShouldShrink = true);
77 |
78 | /**
79 | * Operator overloading & Hashes
80 | */
81 | bool operator==(const FActionOwner& Other) const
82 | {
83 | return Owner == Other.Owner;
84 | }
85 | bool operator!=(const FActionOwner& Other) const
86 | {
87 | return !(*this == Other);
88 | }
89 | friend uint32 GetTypeHash(const FActionOwner& InAction)
90 | {
91 | return GetTypeHash(InAction.Owner);
92 | }
93 | };
94 |
95 |
96 | /**
97 | * Actions Subsystem
98 | * Keeps track of all running actions and their lifetime.
99 | * It also does a global tick based on tick rate for all actions.
100 | */
101 | UCLASS()
102 | class ACTIONS_API UActionsSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
103 | {
104 | GENERATED_BODY()
105 |
106 | friend UAction;
107 |
108 | private:
109 | UPROPERTY(SaveGame)
110 | TSet ActionOwners;
111 |
112 | UPROPERTY(Transient)
113 | TArray TickGroups;
114 |
115 |
116 | protected:
117 | virtual void Initialize(FSubsystemCollectionBase& Collection) override;
118 | virtual void Deinitialize() override;
119 |
120 | public:
121 | //~ Begin Tickable GameObject Interface
122 | virtual void Tick(float DeltaTime) override;
123 |
124 | virtual bool IsTickable() const override
125 | {
126 | return ActionOwners.Num() > 0 || TickGroups.Num() > 0;
127 | }
128 |
129 | virtual TStatId GetStatId() const override
130 | {
131 | RETURN_QUICK_DECLARE_CYCLE_STAT(UActionsSubsystem, STATGROUP_Tickables);
132 | }
133 | //~ End Tickable GameObject Interface
134 |
135 |
136 | /** Cancel all current actions of the game. Use with care! */
137 | void CancelAll();
138 |
139 | /** Cancel all actions executing inside an object
140 | * @param Owner of the actions to cancel
141 | */
142 | UFUNCTION(BlueprintCallable, Category = ActionSubsystem)
143 | void CancelAllByOwner(UObject* Object);
144 |
145 | /** Cancel all actions matching a predicate */
146 | void CancelByPredicate(TFunctionRef Predicate);
147 |
148 | /** Cancel all actions with matching owner and predicate */
149 | void CancelByOwnerPredicate(UObject* Object, TFunctionRef Predicate);
150 |
151 | private:
152 | void AddRootAction(UAction* Child);
153 | void AddActionToTickGroup(UAction* Child);
154 | void RemoveActionFromTickGroup(UAction* Child);
155 |
156 | public:
157 | #if WITH_GAMEPLAY_DEBUGGER
158 | void DescribeOwnerToGameplayDebugger(
159 | UObject* Owner, const FName& BaseName, class FGameplayDebugger_Actions& Debugger) const;
160 | #endif // WITH_GAMEPLAY_DEBUGGER
161 |
162 |
163 | FORCEINLINE static UActionsSubsystem* Get(UWorld* World)
164 | {
165 | UGameInstance* GI = World ? World->GetGameInstance() : nullptr;
166 | return UGameInstance::GetSubsystem(GI);
167 | }
168 | };
169 |
--------------------------------------------------------------------------------
/Source/Actions/Public/BTT_RunAction.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "Action.h"
6 | #include "BehaviorTree/BTTaskNode.h"
7 | #include "CoreMinimal.h"
8 |
9 | #include "BTT_RunAction.generated.h"
10 |
11 |
12 |
13 | /**
14 | *
15 | */
16 | UCLASS()
17 | class ACTIONS_API UBTT_RunAction : public UBTTaskNode
18 | {
19 | GENERATED_BODY()
20 |
21 | public:
22 | UPROPERTY(Instanced, EditAnywhere, BlueprintReadWrite, Category = "Node", meta = (DisplayName = "Action"))
23 | UAction* ActionType;
24 |
25 | UPROPERTY()
26 | UAction* Action;
27 |
28 | UPROPERTY(Transient)
29 | UBehaviorTreeComponent* OwnerComp;
30 |
31 |
32 | virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& InOwnerComp, uint8* NodeMemory) override;
33 | virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& InOwnerComp, uint8* NodeMemory) override;
34 | virtual void DescribeRuntimeValues(const UBehaviorTreeComponent& InOwnerComp, uint8* NodeMemory,
35 | EBTDescriptionVerbosity::Type Verbosity, TArray& Values) const override;
36 | virtual FString GetStaticDescription() const override;
37 |
38 | UFUNCTION()
39 | void OnRunActionFinished(const EActionState Reason);
40 | };
41 |
--------------------------------------------------------------------------------
/Source/Editor/ActionsEditor.Build.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2019 Piperift. All Rights Reserved.
2 |
3 | using UnrealBuildTool;
4 |
5 | public class ActionsEditor : ModuleRules
6 | {
7 | public ActionsEditor(ReadOnlyTargetRules TargetRules) : base(TargetRules)
8 | {
9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
10 |
11 | PublicDependencyModuleNames.AddRange(new string[] {
12 | });
13 |
14 | PrivateDependencyModuleNames.AddRange(new string[] {
15 | "Core",
16 | "UnrealEd",
17 | "Actions"
18 | });
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Source/Editor/Private/ActionsEditor.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2019 Piperift. All Rights Reserved.
2 |
3 | #include "ActionsEditor.h"
4 |
5 | #include
6 | #include
7 |
8 |
9 | #define LOCTEXT_NAMESPACE "FActionsEditorModule"
10 |
11 | DEFINE_LOG_CATEGORY(LogActionsEd)
12 |
13 | void FActionsEditorModule::StartupModule()
14 | {
15 | UE_LOG(LogActionsEd, Log, TEXT("UMapComponent: Log Started"));
16 |
17 | RegisterPropertyTypeCustomizations();
18 | PrepareAutoGeneratedDefaultEvents();
19 | }
20 |
21 | void FActionsEditorModule::ShutdownModule()
22 | {
23 | UE_LOG(LogActionsEd, Log, TEXT("UMapComponent: Log Ended"));
24 |
25 | CreatedAssetTypeActions.Empty();
26 |
27 | // Cleanup all information for auto generated default event nodes by this module
28 | FKismetEditorUtilities::UnregisterAutoBlueprintNodeCreation(this);
29 | }
30 |
31 |
32 | void FActionsEditorModule::RegisterPropertyTypeCustomizations() {}
33 |
34 | void FActionsEditorModule::PrepareAutoGeneratedDefaultEvents()
35 | {
36 | // Task events
37 | RegisterDefaultEvent(UAction, ReceiveActivate);
38 | RegisterDefaultEvent(UAction, ReceiveTick);
39 | RegisterDefaultEvent(UAction, ReceiveFinished);
40 | }
41 |
42 |
43 | void FActionsEditorModule::RegisterCustomPropertyTypeLayout(
44 | FName PropertyTypeName, FOnGetPropertyTypeCustomizationInstance PropertyTypeLayoutDelegate)
45 | {
46 | check(PropertyTypeName != NAME_None);
47 |
48 | static FName PropertyEditor("PropertyEditor");
49 | FPropertyEditorModule& PropertyModule =
50 | FModuleManager::GetModuleChecked(PropertyEditor);
51 | PropertyModule.RegisterCustomPropertyTypeLayout(PropertyTypeName, PropertyTypeLayoutDelegate);
52 | }
53 |
54 | #undef LOCTEXT_NAMESPACE
55 |
56 | IMPLEMENT_MODULE(FActionsEditorModule, ActionsEditor);
57 |
--------------------------------------------------------------------------------
/Source/Editor/Public/ActionsEditor.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 | #pragma once
3 |
4 | #include
5 | #include
6 | #include
7 |
8 |
9 | DECLARE_LOG_CATEGORY_EXTERN(LogActionsEd, All, All)
10 |
11 | class FActionsEditorModule : public IModuleInterface
12 | {
13 | public:
14 | virtual void StartupModule() override;
15 | virtual void ShutdownModule() override;
16 |
17 |
18 | private:
19 | void RegisterPropertyTypeCustomizations();
20 | void PrepareAutoGeneratedDefaultEvents();
21 |
22 | /**
23 | * Registers a custom struct
24 | *
25 | * @param StructName The name of the struct to register for property customization
26 | * @param StructLayoutDelegate The delegate to call to get the custom detail layout instance
27 | */
28 | void RegisterCustomPropertyTypeLayout(
29 | FName PropertyTypeName, FOnGetPropertyTypeCustomizationInstance PropertyTypeLayoutDelegate);
30 |
31 | void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef Action)
32 | {
33 | AssetTools.RegisterAssetTypeActions(Action);
34 | CreatedAssetTypeActions.Add(Action);
35 | }
36 |
37 | // Simplify Registering generated default events
38 | #define RegisterDefaultEventChecked(Class, FuncName) \
39 | (FKismetEditorUtilities::RegisterAutoGeneratedDefaultEvent( \
40 | this, Class::StaticClass(), GET_FUNCTION_NAME_CHECKED(Class, FuncName)))
41 |
42 | #define RegisterDefaultEvent(Class, FuncName) \
43 | (FKismetEditorUtilities::RegisterAutoGeneratedDefaultEvent( \
44 | this, Class::StaticClass(), FName(TEXT(#FuncName))))
45 |
46 |
47 | /** All created asset type actions. Cached here so that we can unregister them during shutdown. */
48 | TArray > CreatedAssetTypeActions;
49 | };
50 |
--------------------------------------------------------------------------------
/Source/Graph/ActionsGraph.Build.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2020 Piperift. All Rights Reserved.
2 |
3 | using UnrealBuildTool;
4 |
5 | public class ActionsGraph : ModuleRules
6 | {
7 | public ActionsGraph(ReadOnlyTargetRules TargetRules) : base(TargetRules)
8 | {
9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
10 |
11 | PublicDependencyModuleNames.AddRange(new string[] {
12 | });
13 |
14 | PrivateDependencyModuleNames.AddRange(new string[] {
15 | "Actions",
16 | "Core",
17 | "CoreUObject",
18 | "Engine",
19 | "Slate",
20 | "SlateCore",
21 | "ToolMenus",
22 | "UnrealEd",
23 | "KismetCompiler",
24 | "BlueprintGraph"
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Source/Graph/Private/ActionNodeHelpers.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "ActionNodeHelpers.h"
4 |
5 | #include "AssetRegistry/ARFilter.h"
6 | #include "AssetRegistry/AssetRegistryModule.h"
7 | #include "K2Node_Action.h"
8 |
9 | #include
10 |
11 |
12 | void FActionNodeHelpers::RegisterActionClassActions(
13 | FBlueprintActionDatabaseRegistrar& InActionRegister, UClass* NodeClass)
14 | {
15 | UClass* TaskType = UAction::StaticClass();
16 |
17 | int32 RegisteredCount = 0;
18 | if (const UObject* RegistrerTarget = InActionRegister.GetActionKeyFilter())
19 | {
20 | if (const auto* TargetClass = Cast(RegistrerTarget))
21 | {
22 | if (!TargetClass->HasAnyClassFlags(CLASS_Abstract) && !TargetClass->IsChildOf(TaskType))
23 | {
24 | UBlueprintNodeSpawner* NewAction = UBlueprintNodeSpawner::Create(NodeClass);
25 | check(NewAction != nullptr);
26 |
27 | TWeakObjectPtr TargetClassPtr = const_cast(TargetClass);
28 | NewAction->CustomizeNodeDelegate =
29 | UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(SetNodeFunc, TargetClassPtr);
30 |
31 | if (NewAction)
32 | {
33 | RegisteredCount += (int32) InActionRegister.AddBlueprintAction(TargetClass, NewAction);
34 | }
35 | }
36 | }
37 | }
38 | else
39 | {
40 | for (TObjectIterator ClassIt; ClassIt; ++ClassIt)
41 | {
42 | UClass* Class = *ClassIt;
43 | if (Class->HasAnyClassFlags(CLASS_Abstract) || !Class->IsChildOf(TaskType) || Class == TaskType)
44 | {
45 | continue;
46 | }
47 |
48 | RegisteredCount += RegistryActionClassAction(InActionRegister, NodeClass, Class);
49 | }
50 |
51 | // Registry blueprint classes
52 | /*TSet> BPClasses;
53 | GetAllBlueprintSubclasses(BPClasses, false, "");
54 | for (auto& BPClass : BPClasses)
55 | {
56 | if (!BPClass.IsValid())
57 | {
58 | UClass* Class = BPClass.LoadSynchronous();
59 | if (Class)
60 | {
61 | RegisteredCount += RegistryActionClassAction(InActionRegister, NodeClass, Class);
62 | }
63 | }
64 | }*/
65 | }
66 | return;
67 | }
68 |
69 | void FActionNodeHelpers::SetNodeFunc(
70 | UEdGraphNode* NewNode, bool /*bIsTemplateNode*/, TWeakObjectPtr ClassPtr)
71 | {
72 | UK2Node_Action* ActionNode = CastChecked(NewNode);
73 | if (ClassPtr.IsValid())
74 | {
75 | ActionNode->bShowClass = false;
76 | ActionNode->ActionClass = ClassPtr.Get();
77 | }
78 | }
79 |
80 | template
81 | void FActionNodeHelpers::GetAllBlueprintSubclasses(
82 | TSet>& Subclasses, bool bAllowAbstract, FString const& Path)
83 | {
84 | static const FName GeneratedClassTag = TEXT("GeneratedClass");
85 | static const FName ClassFlagsTag = TEXT("ClassFlags");
86 |
87 | UClass* Base = TBase::StaticClass();
88 | check(Base);
89 |
90 | // Load the asset registry module
91 | FAssetRegistryModule& AssetRegistryModule =
92 | FModuleManager::LoadModuleChecked(FName("AssetRegistry"));
93 | IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
94 |
95 | FName BaseClassName = Base->GetFName();
96 |
97 | // Use the asset registry to get the set of all class names deriving from Base
98 | TSet DerivedNames;
99 | {
100 | TArray BaseNames;
101 | BaseNames.Add(BaseClassName);
102 |
103 | TSet Excluded;
104 | AssetRegistry.GetDerivedClassNames(BaseNames, Excluded, DerivedNames);
105 | }
106 |
107 | // Set up a filter and then pull asset data for all blueprints in the specified path from the asset
108 | // registry. Note that this works in packaged builds too. Even though the blueprint itself cannot be
109 | // loaded, its asset data still exists and is tied to the UBlueprint type.
110 | FARFilter Filter;
111 | Filter.ClassNames.Add(UBlueprint::StaticClass()->GetFName());
112 | Filter.bRecursiveClasses = true;
113 | if (!Path.IsEmpty())
114 | {
115 | Filter.PackagePaths.Add(*Path);
116 | }
117 | Filter.bRecursivePaths = true;
118 |
119 | TArray AssetList;
120 | AssetRegistry.GetAssets(Filter, AssetList);
121 |
122 | // Iterate over retrieved blueprint assets
123 | for (auto const& Asset : AssetList)
124 | {
125 | // Get the the class this blueprint generates (this is stored as a full path)
126 | auto GeneratedClassPath = Asset.TagsAndValues.FindTag(GeneratedClassTag);
127 | if (GeneratedClassPath.IsSet())
128 | {
129 | // Convert path to just the name part
130 | const FString ClassObjectPath =
131 | FPackageName::ExportTextPathToObjectPath(GeneratedClassPath.GetValue());
132 | const FString ClassName = FPackageName::ObjectPathToObjectName(ClassObjectPath);
133 |
134 | // Check if this class is in the derived set
135 | if (!DerivedNames.Contains(*ClassName))
136 | {
137 | continue;
138 | }
139 |
140 | // Store using the path to the generated class
141 | Subclasses.Add(TAssetSubclassOf{FStringAssetReference(ClassObjectPath)});
142 | }
143 | }
144 | }
145 |
146 | int32 FActionNodeHelpers::RegistryActionClassAction(
147 | FBlueprintActionDatabaseRegistrar& InActionRegistar, UClass* NodeClass, UClass* Class)
148 | {
149 | UBlueprintNodeSpawner* NewAction = UBlueprintNodeSpawner::Create(NodeClass);
150 | check(NewAction != nullptr);
151 |
152 | NewAction->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(
153 | SetNodeFunc, TWeakObjectPtr{Class});
154 |
155 | if (NewAction)
156 | {
157 | return (int32) InActionRegistar.AddBlueprintAction(Class, NewAction);
158 | }
159 |
160 | return 0;
161 | }
162 |
--------------------------------------------------------------------------------
/Source/Graph/Private/ActionReflection.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "ActionReflection.h"
4 |
5 | #include
6 |
7 |
8 | void FBaseActionProperty::RefreshProperty()
9 | {
10 | if (Property.Get())
11 | {
12 | Name = Property->GetFName();
13 | const auto* K2Schema = GetDefault();
14 | K2Schema->ConvertPropertyToPinType(Property.Get(), Type);
15 | }
16 | }
17 |
18 |
19 | bool ActionReflection::GetVisibleProperties(UClass* Class, FActionProperties& OutProperties)
20 | {
21 | if (!Class)
22 | {
23 | return false;
24 | }
25 |
26 | OutProperties = {};
27 | for (TFieldIterator PropertyIt(Class, EFieldIteratorFlags::IncludeSuper); PropertyIt;
28 | ++PropertyIt)
29 | {
30 | FProperty* Property = *PropertyIt;
31 |
32 | // Delegate properties
33 | if (auto* DelegateProperty = CastField(Property))
34 | {
35 | if (DelegateProperty && Property->HasAllPropertyFlags(CPF_BlueprintAssignable))
36 | {
37 | FDelegateActionProperty ActionProperty{DelegateProperty};
38 | if (ActionProperty.HasParams())
39 | {
40 | OutProperties.ComplexDelegates.Add(ActionProperty);
41 | }
42 | else
43 | {
44 | OutProperties.SimpleDelegates.Add(ActionProperty);
45 | }
46 | }
47 | }
48 | // Variable properties
49 | else if (UEdGraphSchema_K2::IsPropertyExposedOnSpawn(Property) &&
50 | !Property->HasAnyPropertyFlags(CPF_Parm) &&
51 | !Property->HasAnyPropertyFlags(CPF_DisableEditOnInstance) &&
52 | Property->HasAllPropertyFlags(CPF_BlueprintVisible))
53 | {
54 | OutProperties.Variables.Add({Property});
55 | }
56 | }
57 | return true;
58 | }
59 |
60 | bool FActionProperties::operator==(const FActionProperties& Other) const
61 | {
62 | if (Variables.Num() != Other.Variables.Num() || SimpleDelegates.Num() != Other.SimpleDelegates.Num() ||
63 | ComplexDelegates.Num() != Other.ComplexDelegates.Num())
64 | {
65 | return false;
66 | }
67 |
68 | return Equals(Variables, Other.Variables) &&
69 | Equals(SimpleDelegates, Other.SimpleDelegates) &&
70 | Equals(ComplexDelegates, Other.ComplexDelegates);
71 | }
72 |
--------------------------------------------------------------------------------
/Source/Graph/Private/ActionsGraphModule.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2019 Piperift. All Rights Reserved.
2 |
3 | #include "ActionsGraphModule.h"
4 |
5 | #include
6 |
7 |
8 | DEFINE_LOG_CATEGORY(LogActionsGraph)
9 |
10 | IMPLEMENT_MODULE(FActionsGraphModule, ActionsGraph);
11 |
--------------------------------------------------------------------------------
/Source/Graph/Private/K2Node_Action.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "K2Node_Action.h"
4 |
5 | #include "ActionNodeHelpers.h"
6 | #include "ActionsGraphModule.h"
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 |
21 | #define LOCTEXT_NAMESPACE "ActionEditor"
22 |
23 | const FName UK2Node_Action::ClassPinName{"__Class"};
24 | const FName UK2Node_Action::OwnerPinName{UEdGraphSchema_K2::PN_Self};
25 |
26 |
27 | UK2Node_Action::UK2Node_Action() : Super()
28 | {
29 | NodeTooltip = LOCTEXT("NodeTooltip", "Creates a new action");
30 | }
31 |
32 |
33 | void UK2Node_Action::PostLoad()
34 | {
35 | Super::PostLoad();
36 | BindBlueprintCompile();
37 | }
38 |
39 | ///////////////////////////////////////////////////////////////////////////////
40 | // UEdGraphNode Interface
41 |
42 | void UK2Node_Action::AllocateDefaultPins()
43 | {
44 | const UEdGraphSchema_K2* K2Schema = GetDefault();
45 |
46 | // Add execution pins
47 | CreatePin(EGPD_Input, K2Schema->PC_Exec, K2Schema->PN_Execute);
48 | CreatePin(EGPD_Output, K2Schema->PC_Exec, K2Schema->PN_Then);
49 |
50 | // Action Owner
51 | UEdGraphPin* SelfPin = CreatePin(EGPD_Input, K2Schema->PC_Object, UObject::StaticClass(), OwnerPinName);
52 | SelfPin->PinFriendlyName = LOCTEXT("Owner", "Owner");
53 |
54 |
55 | // If we are not using a predefined class
56 | if (ShowClass())
57 | {
58 | CreateClassPin();
59 | }
60 |
61 | // Result pin
62 | UClass* ReturnClass = ActionClass ? ActionClass : GetClassPinBaseClass();
63 | UEdGraphPin* ResultPin =
64 | CreatePin(EGPD_Output, K2Schema->PC_Object, ReturnClass, K2Schema->PN_ReturnValue);
65 |
66 | // Update class pins if we have an assigned class
67 | if (ActionClass)
68 | {
69 | OnClassPinChanged();
70 | BindBlueprintCompile();
71 | }
72 |
73 | Super::AllocateDefaultPins();
74 | }
75 |
76 | FLinearColor UK2Node_Action::GetNodeTitleColor() const
77 | {
78 | return FColor{27, 240, 247};
79 | }
80 |
81 | FText UK2Node_Action::GetNodeTitle(ENodeTitleType::Type TitleType) const
82 | {
83 | if (ShowClass() && (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle))
84 | {
85 | return GetBaseNodeTitle();
86 | }
87 | else if (ActionClass)
88 | {
89 | if (CachedNodeTitle.IsOutOfDate(this))
90 | {
91 | FFormatNamedArguments Args;
92 | Args.Add(TEXT("ClassName"), ActionClass->GetDisplayNameText());
93 | // FText::Format() is slow, so we cache this to save on performance
94 | CachedNodeTitle.SetCachedText(FText::Format(GetNodeTitleFormat(), Args), this);
95 | }
96 | return CachedNodeTitle;
97 | }
98 | return NSLOCTEXT("K2Node", "Action_Title_None", "No Action");
99 | }
100 |
101 | void UK2Node_Action::PinDefaultValueChanged(UEdGraphPin* ChangedPin)
102 | {
103 | // Class changed
104 | if (ChangedPin && (ChangedPin->PinName == ClassPinName))
105 | {
106 | ActionClass = GetActionClassFromPin();
107 | OnClassPinChanged();
108 | BindBlueprintCompile();
109 | }
110 | }
111 |
112 | FText UK2Node_Action::GetTooltipText() const
113 | {
114 | return NodeTooltip;
115 | }
116 |
117 | bool UK2Node_Action::HasExternalDependencies(TArray* OptionalOutput) const
118 | {
119 | const UBlueprint* SourceBlueprint = GetBlueprint();
120 | const bool bResult = ActionClass && ActionClass->ClassGeneratedBy != SourceBlueprint;
121 | if (bResult && OptionalOutput)
122 | {
123 | OptionalOutput->AddUnique(ActionClass);
124 | }
125 | const bool bSuperResult = Super::HasExternalDependencies(OptionalOutput);
126 | return bSuperResult || bResult;
127 | }
128 |
129 | bool UK2Node_Action::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const
130 | {
131 | UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph);
132 | return Super::IsCompatibleWithGraph(TargetGraph) &&
133 | (!Blueprint || FBlueprintEditorUtils::FindUserConstructionScript(Blueprint) != TargetGraph);
134 | }
135 |
136 | void UK2Node_Action::PinConnectionListChanged(UEdGraphPin* Pin)
137 | {
138 | if (Pin && (Pin->PinName == ClassPinName))
139 | {
140 | ActionClass = GetActionClassFromPin();
141 | OnClassPinChanged();
142 | }
143 | }
144 |
145 | void UK2Node_Action::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const
146 | {
147 | UEdGraphPin* ClassPin = GetClassPin();
148 | if (ClassPin)
149 | {
150 | SetPinToolTip(*ClassPin, LOCTEXT("ClassPinDescription", "The object class you want to construct"));
151 | }
152 | UEdGraphPin* ResultPin = GetResultPin();
153 | if (ResultPin)
154 | {
155 | SetPinToolTip(*ResultPin, LOCTEXT("ResultPinDescription", "The constructed action"));
156 | }
157 |
158 | if (UEdGraphPin* OwnerPin = GetOwnerPin())
159 | {
160 | SetPinToolTip(*OwnerPin, LOCTEXT("OwnerPinDescription", "Parent of the action"));
161 | }
162 |
163 | return Super::GetPinHoverText(Pin, HoverTextOut);
164 | }
165 |
166 | // This will expand node for our custom object, with properties
167 | // which are set as EditAnywhere and meta=(ExposeOnSpawn), or equivalent in blueprint.
168 | void UK2Node_Action::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
169 | {
170 | Super::ExpandNode(CompilerContext, SourceGraph);
171 |
172 | const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
173 | check(SourceGraph && Schema);
174 |
175 | // Get static function
176 | static FName Create_FunctionName = GET_FUNCTION_NAME_CHECKED(UActionLibrary, CreateAction);
177 | static FName Activate_FunctionName = GET_FUNCTION_NAME_CHECKED(UAction, Activate);
178 |
179 | // Set function parameter names
180 | static FString ParamName_Type = FString(TEXT("Type"));
181 |
182 |
183 | /* Retrieve Pins */
184 | // Exec
185 | UEdGraphPin* ExecPin =
186 | GetExecPin(); // Exec pins are those big arrows, connected with thick white lines.
187 | UEdGraphPin* ThenPin =
188 | GetThenPin(); // Then pin is the same as exec pin, just on the other side (the out arrow).
189 |
190 | // Inputs
191 | UEdGraphPin* OwnerPin = GetOwnerPin();
192 | UEdGraphPin* ClassPin =
193 | GetClassPin(); // Get class pin which is used to determine which class to spawn.
194 |
195 | // Outputs
196 | UEdGraphPin* ResultPin = GetResultPin(); // Result pin, which will output our spawned object.
197 |
198 | if (!HasWorldContext())
199 | {
200 | CompilerContext.MessageLog.Error(
201 | *LOCTEXT("CreateActionNodeMissingClass_Error", "@@ node requires world context.").ToString(),
202 | this);
203 | // we break exec links so this is the only error we get, don't want the CreateItemData node being
204 | // considered and giving 'unexpected node' type warnings
205 | BreakAllNodeLinks();
206 | return;
207 | }
208 |
209 | if (!ActionClass)
210 | {
211 | CompilerContext.MessageLog.Error(*LOCTEXT("CreateActionNodeMissingClass_Error",
212 | "Create Action node @@ must have a class specified.")
213 | .ToString(),
214 | this);
215 | // we break exec links so this is the only error we get, don't want the CreateItemData node being
216 | // considered and giving 'unexpected node' type warnings
217 | BreakAllNodeLinks();
218 | return;
219 | }
220 |
221 | bool bIsErrorFree = true;
222 |
223 | //////////////////////////////////////////////////////////////////////////
224 | // create 'UActionFunctionLibrary::CreateAction' call node
225 | UK2Node_CallFunction* CreateActionNode =
226 | CompilerContext.SpawnIntermediateNode(this, SourceGraph);
227 | // Attach function
228 | CreateActionNode->FunctionReference.SetExternalMember(Create_FunctionName, UActionLibrary::StaticClass());
229 | CreateActionNode->AllocateDefaultPins();
230 |
231 | // allocate nodes for created widget.
232 | UEdGraphPin* CreateAction_Exec = CreateActionNode->GetExecPin();
233 | UEdGraphPin* CreateAction_Owner = CreateActionNode->FindPinChecked(TEXT("Owner"));
234 | UEdGraphPin* CreateAction_Type = CreateActionNode->FindPinChecked(ParamName_Type);
235 | UEdGraphPin* CreateAction_Result = CreateActionNode->GetReturnValuePin();
236 |
237 | // Move 'exec' pin to 'UActionFunctionLibrary::CreateAction'
238 | CompilerContext.MovePinLinksToIntermediate(*ExecPin, *CreateAction_Exec);
239 |
240 | // TODO: Create local variable for PrestatedClass
241 |
242 | // Move pin if connected else, copy the value
243 | if (ShowClass() && ClassPin->LinkedTo.Num() > 0)
244 | {
245 | // Copy the 'blueprint' connection from the spawn node to 'UActionLibrary::CreateAction'
246 | CompilerContext.MovePinLinksToIntermediate(*ClassPin, *CreateAction_Type);
247 | }
248 | else
249 | {
250 | // Copy blueprint literal onto 'UActionLibrary::CreateAction' call
251 | CreateAction_Type->DefaultObject = ActionClass;
252 | }
253 |
254 | // Copy Owner pin to 'UActionFunctionLibrary::CreateAction' if necessary
255 | if (OwnerPin)
256 | {
257 | CompilerContext.MovePinLinksToIntermediate(*OwnerPin, *CreateAction_Owner);
258 | }
259 |
260 | // Move Result pin to 'UActionFunctionLibrary::CreateAction'
261 | CreateAction_Result->PinType = ResultPin->PinType; // Copy type so it uses the right actor subclass
262 | CompilerContext.MovePinLinksToIntermediate(*ResultPin, *CreateAction_Result);
263 |
264 |
265 | //////////////////////////////////////////////////////////////////////////
266 | // create 'set var' nodes
267 |
268 | // Set all properties of the object
269 | UEdGraphPin* LastThenPin = FKismetCompilerUtilities::GenerateAssignmentNodes(
270 | CompilerContext, SourceGraph, CreateActionNode, this, CreateAction_Result, ActionClass);
271 |
272 | // For each delegate, define an event, bind it to delegate and implement a chain of assignments
273 | for (TFieldIterator PropertyIt(
274 | ActionClass, EFieldIteratorFlags::IncludeSuper);
275 | PropertyIt && bIsErrorFree; ++PropertyIt)
276 | {
277 | FMulticastDelegateProperty* Property = *PropertyIt;
278 |
279 | if (Property && Property->HasAllPropertyFlags(CPF_BlueprintAssignable) && Property->SignatureFunction)
280 | {
281 | const UFunction* DelegateSignatureFunction = Property->SignatureFunction;
282 | if (DelegateSignatureFunction->NumParms < 1)
283 | {
284 | bIsErrorFree &= FHelper::HandleDelegateImplementation(
285 | Property, CreateAction_Result, LastThenPin, this, SourceGraph, CompilerContext);
286 | }
287 | else
288 | {
289 | bIsErrorFree &= FHelper::HandleDelegateBindImplementation(
290 | Property, CreateAction_Result, LastThenPin, this, SourceGraph, CompilerContext);
291 | }
292 | }
293 | }
294 |
295 | if (!bIsErrorFree)
296 | {
297 | CompilerContext.MessageLog.Error(*LOCTEXT("CreateActionNodeMissingClass_Error",
298 | "There was a compile error while binding delegates.")
299 | .ToString(),
300 | this);
301 | // we break exec links so this is the only error we get, don't want the CreateAction node being
302 | // considered and giving 'unexpected node' type warnings
303 | BreakAllNodeLinks();
304 | return;
305 | }
306 |
307 |
308 | //////////////////////////////////////////////////////////////////////////
309 | // create 'UAction::Activate' call node
310 | UK2Node_CallFunction* ActivateActionNode =
311 | CompilerContext.SpawnIntermediateNode(this, SourceGraph);
312 | // Attach function
313 | ActivateActionNode->FunctionReference.SetExternalMember(Activate_FunctionName, UAction::StaticClass());
314 | ActivateActionNode->AllocateDefaultPins();
315 |
316 | // allocate nodes for created widget.
317 | UEdGraphPin* ActivateAction_Exec = ActivateActionNode->GetExecPin();
318 | UEdGraphPin* ActivateAction_Self = ActivateActionNode->FindPinChecked(Schema->PN_Self);
319 | UEdGraphPin* ActivateAction_Then = ActivateActionNode->GetThenPin();
320 |
321 | bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, ActivateAction_Exec);
322 | bIsErrorFree &= Schema->TryCreateConnection(CreateAction_Result, ActivateAction_Self);
323 |
324 | CompilerContext.MovePinLinksToIntermediate(*ThenPin, *ActivateAction_Then);
325 |
326 |
327 | if (!bIsErrorFree)
328 | {
329 | CompilerContext.MessageLog.Error(*LOCTEXT("CreateActionNodeMissingClass_Error",
330 | "There was a compile error while activating the action.")
331 | .ToString(),
332 | this);
333 | // we break exec links so this is the only error we get, don't want the CreateAction node being
334 | // considered and giving 'unexpected node' type warnings
335 | BreakAllNodeLinks();
336 | return;
337 | }
338 |
339 | // Break any links to the expanded node
340 | BreakAllNodeLinks();
341 | }
342 |
343 | ///////////////////////////////////////////////////////////////////////////////
344 | // UK2Node Interface
345 |
346 | void UK2Node_Action::ReallocatePinsDuringReconstruction(TArray& OldPins)
347 | {
348 | AllocateDefaultPins();
349 | if (ActionClass)
350 | {
351 | CreatePinsForClass(ActionClass);
352 | }
353 | RestoreSplitPins(OldPins);
354 | }
355 |
356 | void UK2Node_Action::GetNodeAttributes(TArray>& OutNodeAttributes) const
357 | {
358 | const FString ClassToSpawnStr = ActionClass ? ActionClass->GetName() : TEXT("InvalidClass");
359 | OutNodeAttributes.Add(TKeyValuePair(TEXT("Type"), TEXT("CreateAction")));
360 | OutNodeAttributes.Add(TKeyValuePair(TEXT("Class"), GetClass()->GetName()));
361 | OutNodeAttributes.Add(TKeyValuePair(TEXT("Name"), GetName()));
362 | OutNodeAttributes.Add(TKeyValuePair(TEXT("ObjectClass"), ClassToSpawnStr));
363 | }
364 |
365 | void UK2Node_Action::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
366 | {
367 | UClass* ActionKey = GetClass();
368 |
369 | FActionNodeHelpers::RegisterActionClassActions(ActionRegistrar, ActionKey);
370 |
371 | if (ActionRegistrar.IsOpenForRegistration(ActionKey))
372 | {
373 | UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
374 | check(NodeSpawner != nullptr);
375 |
376 | NodeSpawner->DefaultMenuSignature.Keywords = LOCTEXT("MenuKeywords", "Create Action");
377 | ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
378 | }
379 | }
380 |
381 | // Set context menu category in which our node will be present.
382 | FText UK2Node_Action::GetMenuCategory() const
383 | {
384 | return FText::FromString("Actions");
385 | }
386 |
387 | void UK2Node_Action::GetNodeContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
388 | {
389 | Super::GetNodeContextMenuActions(Menu, Context);
390 |
391 | if (!Context->bIsDebugging)
392 | {
393 | if (!Context->Pin || Context->Pin == GetClassPin())
394 | {
395 | FText Text;
396 | if (ShowClass())
397 | {
398 | Text = LOCTEXT("HideClass", "Hide Class pin");
399 | }
400 | else
401 | {
402 | Text = LOCTEXT("ShowClass", "Show Class pin");
403 | }
404 |
405 | auto& Section = Menu->AddSection("ActionNode", LOCTEXT("ActionNodeMenuSection", "Action Node"));
406 | Section.AddMenuEntry("ClassPinVisibility", Text,
407 | LOCTEXT("HideClassTooltip", "Hides the Class input pin"), FSlateIcon(),
408 | FUIAction(FExecuteAction::CreateUObject(
409 | const_cast(this), &UK2Node_Action::ToogleShowClass)));
410 | }
411 | }
412 | }
413 |
414 |
415 | ///////////////////////////////////////////////////////////////////////////////
416 | // UK2Node_Action
417 |
418 | void UK2Node_Action::CreatePinsForClass(UClass* InClass, TArray* OutClassPins)
419 | {
420 | check(InClass != nullptr);
421 |
422 | if (!ActionReflection::GetVisibleProperties(InClass, CurrentProperties))
423 | {
424 | return;
425 | }
426 |
427 | const UEdGraphSchema_K2* K2Schema = GetDefault();
428 |
429 | const UObject* const CDO = InClass->GetDefaultObject(false);
430 | for (const auto& Property : CurrentProperties.Variables)
431 | {
432 | UEdGraphPin* Pin = CreatePin(EGPD_Input, Property.GetType(), Property.GetFName());
433 | if (!Pin)
434 | {
435 | continue;
436 | }
437 |
438 | if (OutClassPins)
439 | {
440 | OutClassPins->Add(Pin);
441 | }
442 |
443 | if (CDO && K2Schema->PinDefaultValueIsEditable(*Pin))
444 | {
445 | FString DefaultValueAsString;
446 | const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(
447 | Property.GetProperty(), reinterpret_cast(CDO), DefaultValueAsString);
448 | check(bDefaultValueSet);
449 | K2Schema->TrySetDefaultValue(*Pin, DefaultValueAsString);
450 | }
451 |
452 | // Copy tooltip from the property.
453 | K2Schema->ConstructBasicPinTooltip(*Pin, Property.GetProperty()->GetToolTipText(), Pin->PinToolTip);
454 | }
455 |
456 | for (const auto& Property : CurrentProperties.ComplexDelegates)
457 | {
458 | if (!Property.GetFunction())
459 | {
460 | UE_LOG(LogActionsGraph, Error, TEXT("Delegate '%s' may be corrupted"),
461 | *Property.GetFName().ToString());
462 | }
463 |
464 | UEdGraphNode::FCreatePinParams Params{};
465 | Params.bIsConst = true;
466 | Params.bIsReference = true;
467 | UEdGraphPin* Pin = CreatePin(EGPD_Input, K2Schema->PC_Delegate, Property.GetFName(), Params);
468 | if (!Pin)
469 | {
470 | continue;
471 | }
472 |
473 | Pin->PinFriendlyName = FText::Format(
474 | NSLOCTEXT("K2Node", "PinFriendlyDelegateName", "{0}"), FText::FromName(Property.GetFName()));
475 |
476 | // Update PinType with the delegate's signature
477 | FMemberReference::FillSimpleMemberReference(
478 | Property.GetFunction(), Pin->PinType.PinSubCategoryMemberReference);
479 |
480 | if (OutClassPins)
481 | {
482 | OutClassPins->Add(Pin);
483 | }
484 | }
485 |
486 | for (const auto& Property : CurrentProperties.SimpleDelegates)
487 | {
488 | if (!Property.GetFunction())
489 | {
490 | UE_LOG(LogActionsGraph, Error, TEXT("Delegate '%s' may be corrupted"),
491 | *Property.GetFName().ToString());
492 | }
493 |
494 | UEdGraphPin* Pin = CreatePin(EGPD_Output, K2Schema->PC_Exec, Property.GetFName());
495 | if (!Pin)
496 | {
497 | continue;
498 | }
499 |
500 | if (OutClassPins)
501 | {
502 | OutClassPins->Add(Pin);
503 | }
504 | }
505 |
506 | // Change class of output pin
507 | UEdGraphPin* ResultPin = GetResultPin();
508 | ResultPin->PinType.PinSubCategoryObject = InClass;
509 | }
510 |
511 | bool UK2Node_Action::IsActionVarPin(UEdGraphPin* Pin)
512 | {
513 | const UEdGraphSchema_K2* K2Schema = GetDefault();
514 |
515 | return (Pin->PinName != K2Schema->PN_Execute && Pin->PinName != K2Schema->PN_Then &&
516 | Pin->PinName != K2Schema->PN_ReturnValue && Pin->PinName != ClassPinName &&
517 | Pin->PinName != OwnerPinName);
518 | }
519 |
520 | UEdGraphPin* UK2Node_Action::GetThenPin() const
521 | {
522 | const UEdGraphSchema_K2* K2Schema = GetDefault();
523 |
524 | UEdGraphPin* Pin = FindPinChecked(K2Schema->PN_Then);
525 | check(Pin->Direction == EGPD_Output);
526 | return Pin;
527 | }
528 |
529 | UEdGraphPin* UK2Node_Action::GetClassPin(const TArray* InPinsToSearch /*= nullptr*/) const
530 | {
531 | const TArray* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins;
532 |
533 | UEdGraphPin* Pin = nullptr;
534 | for (auto PinIt = PinsToSearch->CreateConstIterator(); PinIt; ++PinIt)
535 | {
536 | UEdGraphPin* TestPin = *PinIt;
537 | if (TestPin && TestPin->PinName == ClassPinName)
538 | {
539 | Pin = TestPin;
540 | break;
541 | }
542 | }
543 | check(!Pin || Pin->Direction == EGPD_Input);
544 | return Pin;
545 | }
546 |
547 | UEdGraphPin* UK2Node_Action::GetResultPin() const
548 | {
549 | const UEdGraphSchema_K2* K2Schema = GetDefault();
550 |
551 | UEdGraphPin* Pin = FindPinChecked(K2Schema->PN_ReturnValue);
552 | check(Pin->Direction == EGPD_Output);
553 | return Pin;
554 | }
555 |
556 | UEdGraphPin* UK2Node_Action::GetOwnerPin() const
557 | {
558 | UEdGraphPin* Pin = FindPin(OwnerPinName);
559 | ensure(nullptr == Pin || Pin->Direction == EGPD_Input);
560 | return Pin;
561 | }
562 |
563 | UClass* UK2Node_Action::GetActionClassFromPin() const
564 | {
565 | if (!ShowClass())
566 | {
567 | return ActionClass;
568 | }
569 |
570 | if (UEdGraphPin* ClassPin = GetClassPin())
571 | {
572 | if (ClassPin->DefaultObject && ClassPin->LinkedTo.Num() == 0)
573 | {
574 | return CastChecked(ClassPin->DefaultObject);
575 | }
576 | else if (ClassPin->LinkedTo.Num())
577 | {
578 | auto ClassSource = ClassPin->LinkedTo[0];
579 | return ClassSource ? Cast(ClassSource->PinType.PinSubCategoryObject.Get()) : nullptr;
580 | }
581 | }
582 | return nullptr;
583 | }
584 |
585 | UBlueprint* UK2Node_Action::GetActionBlueprint() const
586 | {
587 | if (!ActionClass)
588 | {
589 | return nullptr;
590 | }
591 |
592 | FString ClassPath = ActionClass->GetPathName();
593 | ClassPath.RemoveFromEnd(TEXT("_C"), ESearchCase::CaseSensitive);
594 | const FString BPPath = FString::Printf(TEXT("Blueprint'%s'"), *ClassPath);
595 |
596 | return Cast(StaticFindObject(UBlueprint::StaticClass(), nullptr, *BPPath));
597 | }
598 |
599 | bool UK2Node_Action::UseWorldContext() const
600 | {
601 | auto BP = GetBlueprint();
602 | const UClass* ParentClass = BP ? BP->ParentClass : nullptr;
603 | return ParentClass
604 | ? ParentClass->HasMetaDataHierarchical(FBlueprintMetadata::MD_ShowWorldContextPin) != nullptr
605 | : false;
606 | }
607 |
608 | bool UK2Node_Action::HasWorldContext() const
609 | {
610 | return FBlueprintEditorUtils::ImplementsGetWorld(GetBlueprint());
611 | }
612 |
613 | FText UK2Node_Action::GetBaseNodeTitle() const
614 | {
615 | return LOCTEXT("Action_BaseTitle", "Action");
616 | }
617 |
618 | FText UK2Node_Action::GetNodeTitleFormat() const
619 | {
620 | return LOCTEXT("Action_ClassTitle", "{ClassName}");
621 | }
622 |
623 | // which class can be used with this node to create objects. All childs of class can be used.
624 | UClass* UK2Node_Action::GetClassPinBaseClass() const
625 | {
626 | return UAction::StaticClass();
627 | }
628 |
629 | void UK2Node_Action::SetPinToolTip(UEdGraphPin& MutatablePin, const FText& PinDescription) const
630 | {
631 | MutatablePin.PinToolTip = UEdGraphSchema_K2::TypeToText(MutatablePin.PinType).ToString();
632 |
633 | if (const auto* K2Schema = Cast(GetSchema()))
634 | {
635 | MutatablePin.PinToolTip += TEXT(" ");
636 | MutatablePin.PinToolTip += K2Schema->GetPinDisplayName(&MutatablePin).ToString();
637 | }
638 |
639 | MutatablePin.PinToolTip += FString(TEXT("\n")) + PinDescription.ToString();
640 | }
641 |
642 | void UK2Node_Action::OnClassPinChanged()
643 | {
644 | bool bClassVisibilityChanged = false;
645 |
646 | UEdGraphPin* ClassPin = GetClassPin();
647 | if (ShowClass() && !ClassPin)
648 | {
649 | CreateClassPin();
650 | }
651 | else if (!ShowClass() && ClassPin)
652 | {
653 | // Class pin should exist, destroy it
654 | UBlueprint* Blueprint = GetBlueprint();
655 |
656 | ClassPin->Modify();
657 | Pins.Remove(ClassPin);
658 | ClassPin->BreakAllPinLinks(!Blueprint->bIsRegeneratingOnLoad);
659 |
660 | UEdGraphNode::DestroyPin(ClassPin);
661 | }
662 | else // Class pin visibility doesnt change
663 | {
664 | // Node will only refresh if the exposed variables and delegates changed
665 | FActionProperties Properties;
666 | const bool bFoundProperties = ActionReflection::GetVisibleProperties(ActionClass, Properties);
667 | if (!bFoundProperties || Properties == CurrentProperties)
668 | {
669 | return;
670 | }
671 | }
672 |
673 |
674 | // Remove all pins related to archetype variables
675 | TArray OldPins = Pins;
676 | TArray OldClassPins;
677 | for (UEdGraphPin* OldPin : OldPins)
678 | {
679 | if (IsActionVarPin(OldPin))
680 | {
681 | Pins.Remove(OldPin);
682 | OldClassPins.Add(OldPin);
683 | }
684 | }
685 |
686 | CachedNodeTitle.MarkDirty();
687 |
688 | TArray NewClassPins;
689 | if (ActionClass)
690 | {
691 | CreatePinsForClass(ActionClass, &NewClassPins);
692 | }
693 |
694 | RestoreSplitPins(OldPins);
695 |
696 | // Rewire return pin
697 | {
698 | UEdGraphPin* ResultPin = GetResultPin();
699 | // Cache all the pin connections to the ResultPin, we will attempt to recreate them
700 | TArray ResultPinConnectionList = ResultPin->LinkedTo;
701 | // Because the archetype has changed, we break the output link as the output pin type will change
702 | ResultPin->BreakAllPinLinks(true);
703 |
704 | // Recreate any pin links to the Result pin that are still valid
705 | for (UEdGraphPin* Connections : ResultPinConnectionList)
706 | {
707 | const auto* K2Schema = GetDefault();
708 | K2Schema->TryCreateConnection(ResultPin, Connections);
709 | }
710 | }
711 |
712 | // Rewire the old pins to the new pins so connections are maintained if possible
713 | RewireOldPinsToNewPins(OldClassPins, Pins, nullptr);
714 |
715 | // Refresh the UI for the graph so the pin changes show up
716 | GetGraph()->NotifyGraphChanged();
717 |
718 | // Mark dirty
719 | FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint());
720 | }
721 |
722 | void UK2Node_Action::OnBlueprintCompiled(UBlueprint* CompiledBP)
723 | {
724 | if (ActionBlueprint == CompiledBP)
725 | {
726 | OnClassPinChanged();
727 | }
728 | }
729 |
730 | void UK2Node_Action::BindBlueprintCompile()
731 | {
732 | if (ActionBlueprint)
733 | {
734 | ActionBlueprint->OnCompiled().RemoveAll(this);
735 | ActionBlueprint = nullptr;
736 | }
737 |
738 | if (UBlueprint* BPToSpawn = GetActionBlueprint())
739 | {
740 | BPToSpawn->OnCompiled().AddUObject(this, &UK2Node_Action::OnBlueprintCompiled);
741 | ActionBlueprint = BPToSpawn;
742 | }
743 | }
744 |
745 | void UK2Node_Action::ToogleShowClass()
746 | {
747 | if (!ShowClass())
748 | {
749 | FScopedTransaction Transaction(LOCTEXT("ShowClassPin", "Show class pin"));
750 | Modify();
751 |
752 | bShowClass = !bShowClass;
753 | OnClassPinChanged();
754 | }
755 | else
756 | {
757 | FScopedTransaction Transaction(LOCTEXT("HideClassPin", "Hide class pin"));
758 | Modify();
759 |
760 | bShowClass = !bShowClass;
761 | OnClassPinChanged();
762 | }
763 |
764 | FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint());
765 | }
766 |
767 | UEdGraphPin* UK2Node_Action::CreateClassPin()
768 | {
769 | const UEdGraphSchema_K2* K2Schema = GetDefault();
770 |
771 | // Add blueprint pin
772 | UEdGraphPin* ClassPin = CreatePin(EGPD_Input, K2Schema->PC_Class, GetClassPinBaseClass(), ClassPinName);
773 | ClassPin->PinFriendlyName = LOCTEXT("PinClassName", "Class");
774 | if (ActionClass)
775 | {
776 | ClassPin->DefaultObject = ActionClass;
777 | }
778 | return ClassPin;
779 | }
780 |
781 | bool UK2Node_Action::FHelper::ValidDataPin(
782 | const UEdGraphPin* Pin, EEdGraphPinDirection Direction, const UEdGraphSchema_K2* Schema)
783 | {
784 | check(Schema);
785 | const bool bValidDataPin = Pin && (Pin->PinName != Schema->PN_Execute) &&
786 | (Pin->PinName != Schema->PN_Then) &&
787 | (Pin->PinType.PinCategory != Schema->PC_Exec);
788 |
789 | const bool bProperDirection = Pin && (Pin->Direction == Direction);
790 |
791 | return bValidDataPin && bProperDirection;
792 | }
793 |
794 | bool UK2Node_Action::FHelper::CreateDelegateForNewFunction(UEdGraphPin* DelegateInputPin, FName FunctionName,
795 | UK2Node* CurrentNode, UEdGraph* SourceGraph, FKismetCompilerContext& CompilerContext)
796 | {
797 | const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
798 | check(DelegateInputPin && Schema && CurrentNode && SourceGraph && (FunctionName != NAME_None));
799 | bool bResult = true;
800 |
801 | // WORKAROUND, so we can create delegate from nonexistent function by avoiding check at expanding step
802 | // instead simply: Schema->TryCreateConnection(AddDelegateNode->GetDelegatePin(),
803 | // CustomEvent->FindPinChecked(UK2Node_CustomEvent::DelegateOutputName));
804 | UK2Node_Self* SelfNode = CompilerContext.SpawnIntermediateNode(CurrentNode, SourceGraph);
805 | SelfNode->AllocateDefaultPins();
806 |
807 | UK2Node_CreateDelegate* CreateDelegateNode =
808 | CompilerContext.SpawnIntermediateNode(CurrentNode, SourceGraph);
809 | CreateDelegateNode->AllocateDefaultPins();
810 | bResult &= Schema->TryCreateConnection(DelegateInputPin, CreateDelegateNode->GetDelegateOutPin());
811 | bResult &= Schema->TryCreateConnection(
812 | SelfNode->FindPinChecked(Schema->PN_Self), CreateDelegateNode->GetObjectInPin());
813 | CreateDelegateNode->SetFunction(FunctionName);
814 |
815 | return bResult;
816 | }
817 |
818 | bool UK2Node_Action::FHelper::CopyEventSignature(
819 | UK2Node_CustomEvent* CENode, UFunction* Function, const UEdGraphSchema_K2* Schema)
820 | {
821 | check(CENode && Function && Schema);
822 |
823 | bool bResult = true;
824 | for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
825 | {
826 | const FProperty* Param = *PropIt;
827 | if (!Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm))
828 | {
829 | FEdGraphPinType PinType;
830 | bResult &= Schema->ConvertPropertyToPinType(Param, /*out*/ PinType);
831 | bResult &= (nullptr != CENode->CreateUserDefinedPin(Param->GetFName(), PinType, EGPD_Output));
832 | }
833 | }
834 | return bResult;
835 | }
836 |
837 | bool UK2Node_Action::FHelper::HandleDelegateImplementation(FMulticastDelegateProperty* CurrentProperty,
838 | UEdGraphPin* ProxyObjectPin, UEdGraphPin*& InOutLastThenPin, UK2Node* CurrentNode, UEdGraph* SourceGraph,
839 | FKismetCompilerContext& CompilerContext)
840 | {
841 | bool bIsErrorFree = true;
842 | const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
843 | check(CurrentProperty && ProxyObjectPin && InOutLastThenPin && CurrentNode && SourceGraph && Schema);
844 |
845 | UEdGraphPin* PinForCurrentDelegateProperty = CurrentNode->FindPin(CurrentProperty->GetName());
846 | if (!PinForCurrentDelegateProperty ||
847 | (Schema->PC_Exec != PinForCurrentDelegateProperty->PinType.PinCategory))
848 | {
849 | FText ErrorMessage =
850 | FText::Format(LOCTEXT("WrongDelegateProperty",
851 | "BaseAsyncConstructObject: Cannot find execution pin for delegate "),
852 | FText::FromString(CurrentProperty->GetName()));
853 | CompilerContext.MessageLog.Error(*ErrorMessage.ToString(), CurrentNode);
854 | return false;
855 | }
856 |
857 | UK2Node_CustomEvent* CustomEvent =
858 | CompilerContext.SpawnIntermediateNode(CurrentNode, SourceGraph);
859 | CustomEvent->bInternalEvent = true;
860 | {
861 | UK2Node_AddDelegate* AddDelegateNode =
862 | CompilerContext.SpawnIntermediateNode(CurrentNode, SourceGraph);
863 |
864 | UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(CurrentNode);
865 | bool const bIsSelfContext =
866 | Blueprint->SkeletonGeneratedClass->IsChildOf(CurrentProperty->GetOwnerClass());
867 |
868 | AddDelegateNode->SetFromProperty(CurrentProperty, bIsSelfContext, CurrentProperty->GetOwnerClass());
869 | AddDelegateNode->AllocateDefaultPins();
870 |
871 | bIsErrorFree &=
872 | Schema->TryCreateConnection(AddDelegateNode->FindPinChecked(Schema->PN_Self), ProxyObjectPin);
873 | bIsErrorFree &= Schema->TryCreateConnection(
874 | InOutLastThenPin, AddDelegateNode->FindPinChecked(Schema->PN_Execute));
875 | InOutLastThenPin = AddDelegateNode->FindPinChecked(Schema->PN_Then);
876 | CustomEvent->CustomFunctionName = *FString::Printf(
877 | TEXT("%s_%s"), *CurrentProperty->GetName(), *CompilerContext.GetGuid(CurrentNode));
878 | CustomEvent->AllocateDefaultPins();
879 |
880 | bIsErrorFree &= FHelper::CreateDelegateForNewFunction(AddDelegateNode->GetDelegatePin(),
881 | CustomEvent->GetFunctionName(), CurrentNode, SourceGraph, CompilerContext);
882 | bIsErrorFree &=
883 | FHelper::CopyEventSignature(CustomEvent, AddDelegateNode->GetDelegateSignature(), Schema);
884 | }
885 |
886 | UEdGraphPin* LastActivatedNodeThen = CustomEvent->FindPinChecked(Schema->PN_Then);
887 |
888 | bIsErrorFree &=
889 | CompilerContext.MovePinLinksToIntermediate(*PinForCurrentDelegateProperty, *LastActivatedNodeThen)
890 | .CanSafeConnect();
891 | return bIsErrorFree;
892 | }
893 |
894 |
895 | bool UK2Node_Action::FHelper::HandleDelegateBindImplementation(FMulticastDelegateProperty* CurrentProperty,
896 | UEdGraphPin* ObjectPin, UEdGraphPin*& InOutLastThenPin, UK2Node* CurrentNode, UEdGraph* SourceGraph,
897 | FKismetCompilerContext& CompilerContext)
898 | {
899 | bool bIsErrorFree = true;
900 | const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
901 |
902 | check(CurrentProperty && Schema);
903 | UEdGraphPin* DelegateRefPin = CurrentNode->FindPinChecked(CurrentProperty->GetName());
904 | check(DelegateRefPin);
905 |
906 | UK2Node_AddDelegate* AddDelegateNode =
907 | CompilerContext.SpawnIntermediateNode(CurrentNode, SourceGraph);
908 | check(AddDelegateNode);
909 |
910 |
911 | AddDelegateNode->SetFromProperty(CurrentProperty, false, CurrentProperty->GetOwnerClass());
912 | AddDelegateNode->AllocateDefaultPins();
913 |
914 | bIsErrorFree &= Schema->TryCreateConnection(AddDelegateNode->FindPinChecked(Schema->PN_Self), ObjectPin);
915 | bIsErrorFree &=
916 | Schema->TryCreateConnection(InOutLastThenPin, AddDelegateNode->FindPinChecked(Schema->PN_Execute));
917 | InOutLastThenPin = AddDelegateNode->FindPinChecked(Schema->PN_Then);
918 |
919 | UEdGraphPin* AddDelegate_DelegatePin = AddDelegateNode->GetDelegatePin();
920 | bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*DelegateRefPin, *AddDelegate_DelegatePin)
921 | .CanSafeConnect();
922 | DelegateRefPin->PinType = AddDelegate_DelegatePin->PinType;
923 |
924 | return bIsErrorFree;
925 | }
926 |
927 | #undef LOCTEXT_NAMESPACE
928 |
--------------------------------------------------------------------------------
/Source/Graph/Public/ActionNodeHelpers.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "BlueprintActionDatabaseRegistrar.h"
6 | #include "BlueprintFunctionNodeSpawner.h"
7 | #include "BlueprintNodeSpawner.h"
8 | #include "CoreMinimal.h"
9 | #include "UObject/ObjectMacros.h"
10 |
11 |
12 | class UK2Node_Action;
13 |
14 | struct FActionNodeHelpers
15 | {
16 | static void RegisterActionClassActions(
17 | FBlueprintActionDatabaseRegistrar& InActionRegister, UClass* NodeClass);
18 |
19 | static void SetNodeFunc(UEdGraphNode* NewNode, bool /*bIsTemplateNode*/, TWeakObjectPtr ClassPtr);
20 |
21 |
22 | static int32 RegistryActionClassAction(
23 | FBlueprintActionDatabaseRegistrar& InActionRegistar, UClass* NodeClass, UClass* Class);
24 |
25 | template
26 | static void GetAllBlueprintSubclasses(
27 | TSet>& OutSubclasses, bool bAllowAbstract, FString const& Path);
28 | };
--------------------------------------------------------------------------------
/Source/Graph/Public/ActionReflection.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 | #include
7 |
8 | #include "ActionReflection.generated.h"
9 |
10 |
11 | USTRUCT()
12 | struct FBaseActionProperty
13 | {
14 | GENERATED_BODY()
15 |
16 | protected:
17 | UPROPERTY()
18 | TFieldPath Property;
19 |
20 | UPROPERTY()
21 | FName Name;
22 |
23 | UPROPERTY()
24 | FEdGraphPinType Type;
25 |
26 | public:
27 | FBaseActionProperty() {}
28 |
29 | FName GetFName() const
30 | {
31 | return Name;
32 | }
33 | const FEdGraphPinType& GetType() const
34 | {
35 | return Type;
36 | }
37 | FProperty* GetProperty() const
38 | {
39 | return Property.Get();
40 | }
41 |
42 | bool IsValid() const
43 | {
44 | return Property != nullptr;
45 | }
46 |
47 | bool operator==(const FBaseActionProperty& Other) const
48 | {
49 | return Name == Other.Name && Type == Other.Type;
50 | }
51 | bool operator!=(const FBaseActionProperty& Other) const
52 | {
53 | return !operator==(Other);
54 | }
55 |
56 | friend int32 GetTypeHash(const FBaseActionProperty& Item)
57 | {
58 | return GetTypeHash(Item.GetFName());
59 | }
60 |
61 | protected:
62 | FBaseActionProperty(FProperty* Property) : Property(Property)
63 | {
64 | RefreshProperty();
65 | }
66 |
67 | private:
68 | void RefreshProperty();
69 | };
70 |
71 |
72 | USTRUCT()
73 | struct FDelegateActionProperty : public FBaseActionProperty
74 | {
75 | GENERATED_BODY()
76 |
77 | FDelegateActionProperty(FMulticastDelegateProperty* Property = nullptr) : FBaseActionProperty(Property) {}
78 |
79 | FMulticastDelegateProperty* GetDelegate() const
80 | {
81 | return CastFieldChecked(Property.Get());
82 | }
83 |
84 | UFunction* GetFunction() const
85 | {
86 | return GetDelegate()->SignatureFunction;
87 | }
88 |
89 | bool HasParams() const
90 | {
91 | auto* Function = GetFunction();
92 | return Function ? Function->NumParms > 0 : false;
93 | }
94 | };
95 |
96 |
97 | USTRUCT()
98 | struct FVariableActionProperty : public FBaseActionProperty
99 | {
100 | GENERATED_BODY()
101 |
102 | FVariableActionProperty(FProperty* Property = nullptr) : FBaseActionProperty(Property) {}
103 | };
104 |
105 |
106 | USTRUCT()
107 | struct FActionProperties
108 | {
109 | GENERATED_BODY()
110 |
111 | UPROPERTY()
112 | TSet Variables;
113 |
114 | UPROPERTY()
115 | TSet SimpleDelegates;
116 |
117 | UPROPERTY()
118 | TSet ComplexDelegates;
119 |
120 |
121 | bool operator==(const FActionProperties& Other) const;
122 | bool operator!=(const FActionProperties& Other) const
123 | {
124 | return !operator==(Other);
125 | }
126 |
127 | private:
128 | template
129 | bool Equals(const TSet& One, const TSet& Other) const
130 | {
131 | for (const T& Value : One)
132 | {
133 | const T* const OtherValue = Other.Find(Value);
134 | if (!OtherValue || *OtherValue != Value)
135 | {
136 | // Element not found or not equal
137 | return false;
138 | }
139 | }
140 | return true;
141 | }
142 | };
143 |
144 |
145 | namespace ActionReflection
146 | {
147 | bool GetVisibleProperties(UClass* Class, FActionProperties& OutProperties);
148 | };
149 |
--------------------------------------------------------------------------------
/Source/Graph/Public/ActionsGraphModule.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 | #pragma once
3 |
4 | #include
5 |
6 |
7 | DECLARE_LOG_CATEGORY_EXTERN(LogActionsGraph, All, All)
8 |
9 | class FActionsGraphModule : public IModuleInterface
10 | {
11 | public:
12 | virtual void StartupModule() override {}
13 | virtual void ShutdownModule() override {}
14 | };
--------------------------------------------------------------------------------
/Source/Graph/Public/K2Node_Action.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "ActionReflection.h"
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 |
17 | #include "K2Node_Action.generated.h"
18 |
19 |
20 | UCLASS(Blueprintable)
21 | class ACTIONSGRAPH_API UK2Node_Action : public UK2Node
22 | {
23 | GENERATED_BODY()
24 |
25 | static const FName ClassPinName;
26 | static const FName OwnerPinName;
27 |
28 | public:
29 | UPROPERTY()
30 | UClass* ActionClass;
31 |
32 | UPROPERTY()
33 | bool bShowClass = true;
34 |
35 | protected:
36 | /** Output pin visibility control */
37 | UPROPERTY(EditAnywhere, Category = PinOptions, EditFixedSize)
38 | TArray ShowPinForProperties;
39 |
40 | /** Tooltip text for this node. */
41 | FText NodeTooltip;
42 |
43 | private:
44 | UPROPERTY()
45 | FActionProperties CurrentProperties;
46 |
47 | /** Blueprint that is binded OnCompile */
48 | UPROPERTY()
49 | UBlueprint* ActionBlueprint;
50 |
51 | /** Constructing FText strings can be costly, so we cache the node's title */
52 | FNodeTextCache CachedNodeTitle;
53 |
54 |
55 | public:
56 | UK2Node_Action();
57 |
58 | protected:
59 | virtual void PostLoad() override;
60 |
61 | //~ Begin UEdGraphNode Interface.
62 | virtual void AllocateDefaultPins() override;
63 | virtual FLinearColor GetNodeTitleColor() const override;
64 | virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
65 | virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override;
66 | virtual FText GetTooltipText() const override;
67 | virtual bool HasExternalDependencies(TArray* OptionalOutput) const override;
68 | virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override;
69 | virtual void PinConnectionListChanged(UEdGraphPin* Pin) override;
70 | virtual void GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const override;
71 | virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
72 | // End UEdGraphNode interface.
73 |
74 | //~ Begin UK2Node Interface
75 | virtual bool IsNodeSafeToIgnore() const override
76 | {
77 | return true;
78 | }
79 | virtual void ReallocatePinsDuringReconstruction(TArray& OldPins) override;
80 | virtual void GetNodeAttributes(TArray>& OutNodeAttributes) const override;
81 | virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
82 | virtual FText GetMenuCategory() const override;
83 | virtual void GetNodeContextMenuActions(
84 | UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const override;
85 | //~ End UK2Node Interface
86 |
87 |
88 | /** Create new pins to show properties on archetype */
89 | void CreatePinsForClass(UClass* InClass, TArray* OutClassPins = nullptr);
90 |
91 | /** See if this is a spawn variable pin, or a 'default' pin */
92 | virtual bool IsActionVarPin(UEdGraphPin* Pin);
93 |
94 | UEdGraphPin* GetThenPin() const;
95 | UEdGraphPin* GetClassPin(const TArray* InPinsToSearch = nullptr) const;
96 | UEdGraphPin* GetResultPin() const;
97 | UEdGraphPin* GetOwnerPin() const;
98 |
99 | /** Get the class that we are going to spawn, if it's defined as default value */
100 | UClass* GetActionClassFromPin() const;
101 |
102 | /** Get the class that we are going to spawn, if it's defined as default value */
103 | UBlueprint* GetActionBlueprint() const;
104 |
105 | /** Returns if the node uses World Object Context input */
106 | virtual bool UseWorldContext() const;
107 |
108 | /** Returns if the node has World Context */
109 | virtual bool HasWorldContext() const;
110 |
111 | /** Returns if the node uses Owner input */
112 | virtual bool UseOwner() const
113 | {
114 | return true;
115 | }
116 | virtual bool ShowClass() const
117 | {
118 | return bShowClass;
119 | }
120 |
121 | /** Gets the default node title when no class is selected */
122 | virtual FText GetBaseNodeTitle() const;
123 | /** Gets the node title when a class has been selected. */
124 | virtual FText GetNodeTitleFormat() const;
125 | /** Gets base class to use for the 'class' pin. UObject by default. */
126 | virtual UClass* GetClassPinBaseClass() const;
127 |
128 | /**
129 | * Takes the specified "MutatablePin" and sets its 'PinToolTip' field (according
130 | * to the specified description)
131 | *
132 | * @param MutatablePin The pin you want to set tool-tip text on
133 | * @param PinDescription A string describing the pin's purpose
134 | */
135 | void SetPinToolTip(UEdGraphPin& MutatablePin, const FText& PinDescription) const;
136 |
137 | /** Refresh pins when class was changed */
138 | void OnClassPinChanged();
139 |
140 | void OnBlueprintCompiled(UBlueprint*);
141 |
142 | private:
143 | void BindBlueprintCompile();
144 |
145 | void ToogleShowClass();
146 |
147 | UEdGraphPin* CreateClassPin();
148 |
149 | protected:
150 | struct ACTIONSGRAPH_API FHelper
151 | {
152 | struct FOutputPinAndLocalVariable
153 | {
154 | UEdGraphPin* OutputPin;
155 | UK2Node_TemporaryVariable* TempVar;
156 |
157 | FOutputPinAndLocalVariable(UEdGraphPin* Pin, UK2Node_TemporaryVariable* Var)
158 | : OutputPin(Pin)
159 | , TempVar(Var)
160 | {}
161 | };
162 |
163 | static bool ValidDataPin(
164 | const UEdGraphPin* Pin, EEdGraphPinDirection Direction, const UEdGraphSchema_K2* Schema);
165 |
166 | static bool CreateDelegateForNewFunction(UEdGraphPin* DelegateInputPin, FName FunctionName,
167 | UK2Node* CurrentNode, UEdGraph* SourceGraph, FKismetCompilerContext& CompilerContext);
168 |
169 | static bool CopyEventSignature(
170 | UK2Node_CustomEvent* CENode, UFunction* Function, const UEdGraphSchema_K2* Schema);
171 |
172 | static bool HandleDelegateImplementation(FMulticastDelegateProperty* CurrentProperty,
173 | UEdGraphPin* ProxyObjectPin, UEdGraphPin*& InOutLastThenPin, UK2Node* CurrentNode,
174 | UEdGraph* SourceGraph, FKismetCompilerContext& CompilerContext);
175 |
176 | static bool HandleDelegateBindImplementation(FMulticastDelegateProperty* CurrentProperty,
177 | UEdGraphPin* ProxyObjectPin, UEdGraphPin*& InOutLastThenPin, UK2Node* CurrentNode,
178 | UEdGraph* SourceGraph, FKismetCompilerContext& CompilerContext);
179 | };
180 | };
--------------------------------------------------------------------------------
/Source/Test/ActionsTest.Build.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2020 Piperift. All Rights Reserved.
2 |
3 | using UnrealBuildTool;
4 | using System.IO;
5 |
6 | namespace UnrealBuildTool.Rules
7 | {
8 | public class ActionsTest : ModuleRules
9 | {
10 | public ActionsTest(ReadOnlyTargetRules Target) : base(Target)
11 | {
12 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
13 | IWYUSupport = IWYUSupport.Full;
14 |
15 | PublicDependencyModuleNames.AddRange(new string[]
16 | {
17 | "Core",
18 | "Engine",
19 | "CoreUObject",
20 | "Actions"
21 | });
22 |
23 | PrivateDependencyModuleNames.AddRange(new string[]
24 | {
25 | });
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Source/Test/Private/Actions.spec.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "TestAction.h"
4 | #include "TestHelpers.h"
5 |
6 |
7 | constexpr const EAutomationTestFlags Flags_Product =
8 | EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask;
9 | constexpr const EAutomationTestFlags Flags_Smoke =
10 | EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::ProductFilter;
11 |
12 |
13 | #define BASE_SPEC FACESpec
14 |
15 |
16 | BEGIN_TESTSPEC(FSubsystemSpec, "ActionsExtension.Subsystem", Flags_Product)
17 | UWorld* World = nullptr;
18 | END_TESTSPEC(FSubsystemSpec)
19 | void FSubsystemSpec::Define()
20 | {
21 | BeforeEach([this]() {
22 | World = GetTestWorld();
23 | });
24 |
25 | It("Subsystem is valid", [this]() {
26 | TestNotNull("Subsystem", UActionsSubsystem::Get(World));
27 | });
28 | }
29 |
30 |
31 | BEGIN_TESTSPEC(FActionsSpec, "ActionsExtension.Actions", Flags_Smoke)
32 | UWorld* World = nullptr;
33 | END_TESTSPEC(FActionsSpec)
34 | void FActionsSpec::Define()
35 | {
36 | BeforeEach([this]() {
37 | World = GetTestWorld();
38 | });
39 |
40 | It("Can instantiate Action", [this]() {
41 | UTestAction* Action = CreateAction(World);
42 | TestNotNull("Action", Action);
43 | });
44 |
45 | It("Can activate Action", [this]() {
46 | UTestAction* Action = CreateAction(World);
47 | if (!Action)
48 | {
49 | Action->Activate();
50 | TestTrue("Activated", Action->IsRunning());
51 | }
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/Source/Test/Private/ActionsTest.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "ActionsTest.h"
4 |
--------------------------------------------------------------------------------
/Source/Test/Private/TestAction.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 | #include "Action.h"
5 |
6 | #include
7 |
8 | #include "TestAction.generated.h"
9 |
10 |
11 |
12 | UCLASS()
13 | class UTestAction : public UAction
14 | {
15 | GENERATED_BODY()
16 | };
17 |
--------------------------------------------------------------------------------
/Source/Test/Private/TestHelpers.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #include "TestHelpers.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 |
13 |
14 | UWorld* FACESpec::GetTestWorld() const
15 | {
16 | #if WITH_EDITOR
17 | const TIndirectArray& WorldContexts = GEngine->GetWorldContexts();
18 | for (const FWorldContext& Context : WorldContexts)
19 | {
20 | if (Context.World() != nullptr)
21 | {
22 | if (Context.WorldType == EWorldType::PIE /*&& Context.PIEInstance == 0*/)
23 | {
24 | return Context.World();
25 | }
26 |
27 | if (Context.WorldType == EWorldType::Game)
28 | {
29 | return Context.World();
30 | }
31 | }
32 | }
33 | #endif
34 |
35 | UWorld* TestWorld = GWorld;
36 | if (GIsEditor)
37 | {
38 | UE_LOG(LogTemp, Warning, TEXT("Test using GWorld. Not correct for PIE"));
39 | }
40 |
41 | return TestWorld;
42 | }
43 |
--------------------------------------------------------------------------------
/Source/Test/Public/ActionsTest.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 |
8 | class FActionsTest : public IModuleInterface
9 | {
10 | public:
11 | virtual void StartupModule() override {}
12 | virtual void ShutdownModule() override {}
13 |
14 | static inline FActionsTest& Get()
15 | {
16 | return FModuleManager::LoadModuleChecked("ActionsTest");
17 | }
18 |
19 | static inline bool IsAvailable()
20 | {
21 | return FModuleManager::Get().IsModuleLoaded("ActionsTest");
22 | }
23 | };
24 |
25 | IMPLEMENT_MODULE(FActionsTest, ActionsTest);
26 |
--------------------------------------------------------------------------------
/Source/Test/Public/TestHelpers.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-2023 Piperift. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 | #include
7 | #include
8 |
9 |
10 | class FACESpec : public FAutomationSpecBase
11 | {
12 | public:
13 | FACESpec(const FString& InName, const bool bInComplexTask) : FAutomationSpecBase(InName, bInComplexTask)
14 | {}
15 |
16 | protected:
17 | void TestNotImplemented()
18 | {
19 | AddWarning("Test not implemented.");
20 | }
21 |
22 | UWorld* GetTestWorld() const;
23 | };
24 |
25 |
26 | #ifndef BASE_SPEC
27 | # define BASE_SPEC FACESpec
28 | #endif
29 |
30 | #define BEGIN_TESTSPEC_PRIVATE(TClass, PrettyName, TFlags, FileName, LineNumber) \
31 | class TClass : public BASE_SPEC \
32 | { \
33 | using Super = BASE_SPEC; \
34 | \
35 | public: \
36 | TClass(const FString& InName) : Super(InName, false) \
37 | { \
38 | static_assert(!!((TFlags) &EAutomationTestFlags_ApplicationContextMask), \
39 | "AutomationTest has no application flag. It shouldn't run. See AutomationTest.h."); \
40 | static_assert( \
41 | !!(((TFlags) &EAutomationTestFlags_FilterMask) == EAutomationTestFlags::SmokeFilter) || \
42 | !!(((TFlags) &EAutomationTestFlags_FilterMask) == EAutomationTestFlags::EngineFilter) || \
43 | !!(((TFlags) &EAutomationTestFlags_FilterMask) == \
44 | EAutomationTestFlags::ProductFilter) || \
45 | !!(((TFlags) &EAutomationTestFlags_FilterMask) == EAutomationTestFlags::PerfFilter) || \
46 | !!(((TFlags) &EAutomationTestFlags_FilterMask) == EAutomationTestFlags::StressFilter) || \
47 | !!(((TFlags) &EAutomationTestFlags_FilterMask) == EAutomationTestFlags::NegativeFilter), \
48 | "All AutomationTests must have exactly 1 filter type specified. See AutomationTest.h."); \
49 | } \
50 | virtual EAutomationTestFlags GetTestFlags() const override \
51 | { \
52 | return TFlags; \
53 | } \
54 | using Super::GetTestSourceFileName; \
55 | virtual FString GetTestSourceFileName() const override \
56 | { \
57 | return FileName; \
58 | } \
59 | using Super::GetTestSourceFileLine; \
60 | virtual int32 GetTestSourceFileLine() const override \
61 | { \
62 | return LineNumber; \
63 | } \
64 | \
65 | protected: \
66 | virtual FString GetBeautifiedTestName() const override \
67 | { \
68 | return PrettyName; \
69 | } \
70 | virtual void Define() override;
71 |
72 |
73 | #if WITH_AUTOMATION_WORKER
74 | # define TESTSPEC(TClass, PrettyName, TFlags) \
75 | BEGIN_TESTSPEC_PRIVATE(TClass, PrettyName, TFlags, __FILE__, __LINE__) \
76 | } \
77 | ; \
78 | namespace \
79 | { \
80 | TClass TClass##AutomationSpecInstance(TEXT(#TClass)); \
81 | }
82 |
83 | # define BEGIN_TESTSPEC(TClass, PrettyName, TFlags) \
84 | BEGIN_TESTSPEC_PRIVATE(TClass, PrettyName, TFlags, __FILE__, __LINE__)
85 |
86 | # define END_TESTSPEC(TClass) \
87 | } \
88 | ; \
89 | namespace \
90 | { \
91 | TClass TClass##AutomationSpecInstance(TEXT(#TClass)); \
92 | }
93 | #endif
94 |
--------------------------------------------------------------------------------