mirror of
https://github.com/godotengine/godot.git
synced 2024-11-25 21:52:51 +00:00
Rework StateMachine and nested StateMachine process
Breaking compatibility: If a StateMachineTransition is connected to a nested StateMachine prior to this, it is removed. Also, there was a feature to connect another StateMachine as the End of a StateMachine, which is also removed to avoid reference confusion. It was like a GoTo Statement, also further passing the same reference to the blending process, causing the blending calculation to break or causing some StateMachines to not work.
This commit is contained in:
parent
a7d0e18a31
commit
991e6e90ba
@ -61,6 +61,7 @@
|
||||
<param index="0" name="time" type="float" />
|
||||
<param index="1" name="seek" type="bool" />
|
||||
<param index="2" name="is_external_seeking" type="bool" />
|
||||
<param index="3" name="test_only" type="bool" />
|
||||
<description>
|
||||
When inheriting from [AnimationRootNode], implement this virtual method to run some code when this node is processed. The [param time] parameter is a relative delta, unless [param seek] is [code]true[/code], in which case it is absolute.
|
||||
Here, call the [method blend_input], [method blend_node] or [method blend_animation] functions. You can also use [method get_parameter] and [method set_parameter] to modify local memory.
|
||||
@ -97,6 +98,7 @@
|
||||
<param index="4" name="blend" type="float" />
|
||||
<param index="5" name="filter" type="int" enum="AnimationNode.FilterAction" default="0" />
|
||||
<param index="6" name="sync" type="bool" default="true" />
|
||||
<param index="7" name="test_only" type="bool" default="false" />
|
||||
<description>
|
||||
Blend an input. This is only useful for nodes created for an [AnimationNodeBlendTree]. The [param time] parameter is a relative delta, unless [param seek] is [code]true[/code], in which case it is absolute. A filter mode may be optionally passed (see [enum FilterAction] for options).
|
||||
</description>
|
||||
@ -111,6 +113,7 @@
|
||||
<param index="5" name="blend" type="float" />
|
||||
<param index="6" name="filter" type="int" enum="AnimationNode.FilterAction" default="0" />
|
||||
<param index="7" name="sync" type="bool" default="true" />
|
||||
<param index="8" name="test_only" type="bool" default="false" />
|
||||
<description>
|
||||
Blend another animation node (in case this node contains children animation nodes). This function is only useful if you inherit from [AnimationRootNode] instead, else editors will not display your node for addition.
|
||||
</description>
|
||||
|
@ -165,5 +165,23 @@
|
||||
<member name="allow_transition_to_self" type="bool" setter="set_allow_transition_to_self" getter="is_allow_transition_to_self" default="false">
|
||||
If [code]true[/code], allows teleport to the self state with [method AnimationNodeStateMachinePlayback.travel]. When the reset option is enabled in [method AnimationNodeStateMachinePlayback.travel], the animation is restarted. If [code]false[/code], nothing happens on the teleportation to the self state.
|
||||
</member>
|
||||
<member name="reset_ends" type="bool" setter="set_reset_ends" getter="are_ends_reset" default="false">
|
||||
If [code]true[/code], treat the cross-fade to the start and end nodes as a blend with the RESET animation.
|
||||
In most cases, when additional cross-fades are performed in the parent [AnimationNode] of the state machine, setting this property to [code]false[/code] and matching the cross-fade time of the parent [AnimationNode] and the state machine's start node and end node gives good results.
|
||||
</member>
|
||||
<member name="state_machine_type" type="int" setter="set_state_machine_type" getter="get_state_machine_type" enum="AnimationNodeStateMachine.StateMachineType" default="0">
|
||||
This property can define the process of transitions for different use cases. See also [enum AnimationNodeStateMachine.StateMachineType].
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
<constant name="STATE_MACHINE_TYPE_ROOT" value="0" enum="StateMachineType">
|
||||
Seeking to the beginning is treated as playing from the start state. Transition to the end state is treated as exiting the state machine.
|
||||
</constant>
|
||||
<constant name="STATE_MACHINE_TYPE_NESTED" value="1" enum="StateMachineType">
|
||||
Seeking to the beginning is treated as seeking to the beginning of the animation in the current state. Transition to the end state, or the absence of transitions in each state, is treated as exiting the state machine.
|
||||
</constant>
|
||||
<constant name="STATE_MACHINE_TYPE_GROUPED" value="2" enum="StateMachineType">
|
||||
This is a grouped state machine that can be controlled from a parent state machine. It does not work on standalone. There must be a state machine with [member state_machine_type] of [constant STATE_MACHINE_TYPE_ROOT] or [constant STATE_MACHINE_TYPE_NESTED] in the parent or ancestor.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include "scene/gui/tree.h"
|
||||
#include "scene/main/viewport.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/scene_string_names.h"
|
||||
|
||||
bool AnimationNodeStateMachineEditor::can_edit(const Ref<AnimationNode> &p_node) {
|
||||
Ref<AnimationNodeStateMachine> ansm = p_node;
|
||||
@ -67,7 +68,6 @@ void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) {
|
||||
selected_transition_from = StringName();
|
||||
selected_transition_to = StringName();
|
||||
selected_transition_index = -1;
|
||||
selected_multi_transition = TransitionLine();
|
||||
selected_node = StringName();
|
||||
selected_nodes.clear();
|
||||
_update_mode();
|
||||
@ -78,14 +78,60 @@ void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) {
|
||||
tool_connect->set_disabled(read_only);
|
||||
}
|
||||
|
||||
String AnimationNodeStateMachineEditor::_get_root_playback_path(String &r_node_directory) {
|
||||
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
|
||||
Vector<String> edited_path = AnimationTreeEditor::get_singleton()->get_edited_path();
|
||||
|
||||
String base_path;
|
||||
Vector<String> node_directory_path;
|
||||
|
||||
bool is_playable_anodesm_found = false;
|
||||
|
||||
if (edited_path.size()) {
|
||||
while (!is_playable_anodesm_found) {
|
||||
base_path = String("/").join(edited_path);
|
||||
Ref<AnimationNodeStateMachine> anodesm = !edited_path.size() ? tree->get_tree_root() : tree->get_tree_root()->find_node_by_path(base_path);
|
||||
if (!anodesm.is_valid()) {
|
||||
break;
|
||||
} else {
|
||||
if (anodesm->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
|
||||
is_playable_anodesm_found = true;
|
||||
} else {
|
||||
int idx = edited_path.size() - 1;
|
||||
node_directory_path.push_back(edited_path[idx]);
|
||||
edited_path.remove_at(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_playable_anodesm_found) {
|
||||
// Return Root/Nested state machine playback.
|
||||
node_directory_path.reverse();
|
||||
r_node_directory = String("/").join(node_directory_path);
|
||||
if (node_directory_path.size()) {
|
||||
r_node_directory += "/";
|
||||
}
|
||||
base_path = !edited_path.size() ? String(SceneStringNames::get_singleton()->parameters_base_path) + "playback" : String(SceneStringNames::get_singleton()->parameters_base_path) + base_path + "/playback";
|
||||
} else {
|
||||
// Hmmm, we have to return Grouped state machine playback...
|
||||
// It will give the user the error that Root/Nested state machine should be retrieved, that would be kind :-)
|
||||
r_node_directory = String();
|
||||
base_path = AnimationTreeEditor::get_singleton()->get_base_path() + "playback";
|
||||
}
|
||||
|
||||
return base_path;
|
||||
}
|
||||
|
||||
void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEvent> &p_event) {
|
||||
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback");
|
||||
if (playback.is_null()) {
|
||||
String node_directory;
|
||||
Ref<AnimationNodeStateMachinePlayback> playback = tree->get(_get_root_playback_path(node_directory));
|
||||
if (!playback.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -99,16 +145,6 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
|
||||
}
|
||||
}
|
||||
|
||||
// Group selected nodes on a state machine
|
||||
if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->is_ctrl_pressed() && !k->is_shift_pressed() && k->get_keycode() == Key::G && !k->is_echo()) {
|
||||
_group_selected_nodes();
|
||||
}
|
||||
|
||||
// Ungroup state machine
|
||||
if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->is_ctrl_pressed() && k->is_shift_pressed() && k->get_keycode() == Key::G && !k->is_echo()) {
|
||||
_ungroup_selected_nodes();
|
||||
}
|
||||
|
||||
Ref<InputEventMouseButton> mb = p_event;
|
||||
|
||||
// Add new node
|
||||
@ -124,17 +160,16 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
|
||||
selected_transition_from = StringName();
|
||||
selected_transition_to = StringName();
|
||||
selected_transition_index = -1;
|
||||
selected_multi_transition = TransitionLine();
|
||||
selected_node = StringName();
|
||||
|
||||
for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
|
||||
if (node_rects[i].play.has_point(mb->get_position())) { //edit name
|
||||
if (play_mode->get_selected() == 1 || !playback->is_playing()) {
|
||||
//start
|
||||
playback->start(node_rects[i].node_name);
|
||||
// Start
|
||||
playback->start(node_directory + String(node_rects[i].node_name));
|
||||
} else {
|
||||
//travel
|
||||
playback->travel(node_rects[i].node_name);
|
||||
// Travel
|
||||
playback->travel(node_directory + String(node_rects[i].node_name));
|
||||
}
|
||||
state_machine_draw->queue_redraw();
|
||||
return;
|
||||
@ -213,26 +248,11 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
|
||||
selected_transition_index = closest;
|
||||
|
||||
Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(closest);
|
||||
EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
|
||||
|
||||
if (!transition_lines[closest].multi_transitions.is_empty()) {
|
||||
selected_transition_index = -1;
|
||||
selected_multi_transition = transition_lines[closest];
|
||||
|
||||
Ref<EditorAnimationMultiTransitionEdit> multi;
|
||||
multi.instantiate();
|
||||
multi->add_transition(selected_transition_from, selected_transition_to, tr);
|
||||
|
||||
for (int i = 0; i < transition_lines[closest].multi_transitions.size(); i++) {
|
||||
int index = transition_lines[closest].multi_transitions[i].transition_index;
|
||||
|
||||
Ref<AnimationNodeStateMachineTransition> transition = state_machine->get_transition(index);
|
||||
StringName from = transition_lines[closest].multi_transitions[i].from_node;
|
||||
StringName to = transition_lines[closest].multi_transitions[i].to_node;
|
||||
|
||||
multi->add_transition(from, to, transition);
|
||||
}
|
||||
EditorNode::get_singleton()->push_item(multi.ptr(), "", true);
|
||||
if (!state_machine->is_transition_across_group(closest)) {
|
||||
EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
|
||||
} else {
|
||||
EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
|
||||
EditorNode::get_singleton()->push_item(nullptr, "", true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,11 +316,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
|
||||
EditorNode::get_singleton()->show_warning(TTR("Transition exists!"));
|
||||
connecting = false;
|
||||
} else {
|
||||
if (anodesm.is_valid() || end_node.is_valid()) {
|
||||
_open_connect_menu(mb->get_position());
|
||||
} else {
|
||||
_add_transition();
|
||||
}
|
||||
_add_transition();
|
||||
}
|
||||
} else {
|
||||
_open_menu(mb->get_position());
|
||||
@ -481,12 +497,6 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
|
||||
String from = String(transition_lines[closest].from_node);
|
||||
String to = String(transition_lines[closest].to_node);
|
||||
String tooltip = from + " -> " + to;
|
||||
|
||||
for (int i = 0; i < transition_lines[closest].multi_transitions.size(); i++) {
|
||||
from = String(transition_lines[closest].multi_transitions[i].from_node);
|
||||
to = String(transition_lines[closest].multi_transitions[i].to_node);
|
||||
tooltip += "\n" + from + " -> " + to;
|
||||
}
|
||||
state_machine_draw->set_tooltip_text(tooltip);
|
||||
} else {
|
||||
state_machine_draw->set_tooltip_text("");
|
||||
@ -522,224 +532,6 @@ Control::CursorShape AnimationNodeStateMachineEditor::get_cursor_shape(const Poi
|
||||
return cursor_shape;
|
||||
}
|
||||
|
||||
void AnimationNodeStateMachineEditor::_group_selected_nodes() {
|
||||
if (!selected_nodes.is_empty()) {
|
||||
if (selected_nodes.size() == 1 && (*selected_nodes.begin() == state_machine->start_node || *selected_nodes.begin() == state_machine->end_node))
|
||||
return;
|
||||
|
||||
Ref<AnimationNodeStateMachine> group_sm = memnew(AnimationNodeStateMachine);
|
||||
Vector2 group_position;
|
||||
|
||||
Vector<NodeUR> nodes_ur;
|
||||
Vector<TransitionUR> transitions_ur;
|
||||
|
||||
int base = 1;
|
||||
String base_name = group_sm->get_caption();
|
||||
String group_name = base_name;
|
||||
|
||||
while (state_machine->has_node(group_name) && !selected_nodes.has(group_name)) {
|
||||
base++;
|
||||
group_name = base_name + " " + itos(base);
|
||||
}
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action("Group");
|
||||
|
||||
// Move selected nodes to the new state machine
|
||||
for (const StringName &E : selected_nodes) {
|
||||
if (!state_machine->can_edit_node(E)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<AnimationNode> node = state_machine->get_node(E);
|
||||
Vector2 node_position = state_machine->get_node_position(E);
|
||||
group_position += node_position;
|
||||
|
||||
NodeUR new_node;
|
||||
new_node.name = E;
|
||||
new_node.node = node;
|
||||
new_node.position = node_position;
|
||||
|
||||
nodes_ur.push_back(new_node);
|
||||
}
|
||||
|
||||
// Add the transitions to the new state machine
|
||||
for (int i = 0; i < state_machine->get_transition_count(); i++) {
|
||||
String from = state_machine->get_transition_from(i);
|
||||
String to = state_machine->get_transition_to(i);
|
||||
|
||||
String local_from = from.get_slicec('/', 0);
|
||||
String local_to = to.get_slicec('/', 0);
|
||||
|
||||
String old_from = from;
|
||||
String old_to = to;
|
||||
|
||||
bool from_selected = false;
|
||||
bool to_selected = false;
|
||||
|
||||
if (selected_nodes.has(local_from) && local_from != state_machine->start_node) {
|
||||
from_selected = true;
|
||||
}
|
||||
if (selected_nodes.has(local_to) && local_to != state_machine->end_node) {
|
||||
to_selected = true;
|
||||
}
|
||||
if (!from_selected && !to_selected) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i);
|
||||
|
||||
if (!from_selected) {
|
||||
from = "../" + old_from;
|
||||
}
|
||||
if (!to_selected) {
|
||||
to = "../" + old_to;
|
||||
}
|
||||
|
||||
TransitionUR new_tr;
|
||||
new_tr.new_from = from;
|
||||
new_tr.new_to = to;
|
||||
new_tr.old_from = old_from;
|
||||
new_tr.old_to = old_to;
|
||||
new_tr.transition = tr;
|
||||
|
||||
transitions_ur.push_back(new_tr);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodes_ur.size(); i++) {
|
||||
undo_redo->add_do_method(state_machine.ptr(), "remove_node", nodes_ur[i].name);
|
||||
undo_redo->add_undo_method(group_sm.ptr(), "remove_node", nodes_ur[i].name);
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(state_machine.ptr(), "add_node", group_name, group_sm, group_position / nodes_ur.size());
|
||||
undo_redo->add_undo_method(state_machine.ptr(), "remove_node", group_name);
|
||||
|
||||
for (int i = 0; i < nodes_ur.size(); i++) {
|
||||
undo_redo->add_do_method(group_sm.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position);
|
||||
undo_redo->add_undo_method(state_machine.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position);
|
||||
}
|
||||
|
||||
for (int i = 0; i < transitions_ur.size(); i++) {
|
||||
undo_redo->add_do_method(group_sm.ptr(), "add_transition", transitions_ur[i].new_from, transitions_ur[i].new_to, transitions_ur[i].transition);
|
||||
undo_redo->add_undo_method(state_machine.ptr(), "add_transition", transitions_ur[i].old_from, transitions_ur[i].old_to, transitions_ur[i].transition);
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(this, "_update_graph");
|
||||
undo_redo->add_undo_method(this, "_update_graph");
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
|
||||
selected_nodes.clear();
|
||||
selected_nodes.insert(group_name);
|
||||
state_machine_draw->queue_redraw();
|
||||
accept_event();
|
||||
_update_mode();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeStateMachineEditor::_ungroup_selected_nodes() {
|
||||
bool find = false;
|
||||
HashSet<StringName> new_selected_nodes;
|
||||
|
||||
for (const StringName &E : selected_nodes) {
|
||||
Ref<AnimationNodeStateMachine> group_sm = state_machine->get_node(E);
|
||||
|
||||
if (group_sm.is_valid()) {
|
||||
find = true;
|
||||
|
||||
Vector2 group_position = state_machine->get_node_position(E);
|
||||
StringName group_name = E;
|
||||
|
||||
List<AnimationNode::ChildNode> nodes;
|
||||
group_sm->get_child_nodes(&nodes);
|
||||
|
||||
Vector<NodeUR> nodes_ur;
|
||||
Vector<TransitionUR> transitions_ur;
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action("Ungroup");
|
||||
|
||||
// Move all child nodes to current state machine
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
if (!group_sm->can_edit_node(nodes[i].name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 node_position = group_sm->get_node_position(nodes[i].name);
|
||||
|
||||
NodeUR new_node;
|
||||
new_node.name = nodes[i].name;
|
||||
new_node.position = node_position;
|
||||
new_node.node = nodes[i].node;
|
||||
|
||||
nodes_ur.push_back(new_node);
|
||||
}
|
||||
|
||||
for (int i = 0; i < group_sm->get_transition_count(); i++) {
|
||||
String from = group_sm->get_transition_from(i);
|
||||
String to = group_sm->get_transition_to(i);
|
||||
Ref<AnimationNodeStateMachineTransition> tr = group_sm->get_transition(i);
|
||||
|
||||
TransitionUR new_tr;
|
||||
new_tr.new_from = from.replace_first("../", "");
|
||||
new_tr.new_to = to.replace_first("../", "");
|
||||
new_tr.old_from = from;
|
||||
new_tr.old_to = to;
|
||||
new_tr.transition = tr;
|
||||
|
||||
transitions_ur.push_back(new_tr);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodes_ur.size(); i++) {
|
||||
undo_redo->add_do_method(group_sm.ptr(), "remove_node", nodes_ur[i].name);
|
||||
undo_redo->add_undo_method(state_machine.ptr(), "remove_node", nodes_ur[i].name);
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(state_machine.ptr(), "remove_node", group_name);
|
||||
undo_redo->add_undo_method(state_machine.ptr(), "add_node", group_name, group_sm, group_position);
|
||||
|
||||
for (int i = 0; i < nodes_ur.size(); i++) {
|
||||
new_selected_nodes.insert(nodes_ur[i].name);
|
||||
undo_redo->add_do_method(state_machine.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position);
|
||||
undo_redo->add_undo_method(group_sm.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position);
|
||||
}
|
||||
|
||||
for (int i = 0; i < transitions_ur.size(); i++) {
|
||||
if (transitions_ur[i].old_from != state_machine->start_node && transitions_ur[i].old_to != state_machine->end_node) {
|
||||
undo_redo->add_do_method(state_machine.ptr(), "add_transition", transitions_ur[i].new_from, transitions_ur[i].new_to, transitions_ur[i].transition);
|
||||
}
|
||||
|
||||
undo_redo->add_undo_method(group_sm.ptr(), "add_transition", transitions_ur[i].old_from, transitions_ur[i].old_to, transitions_ur[i].transition);
|
||||
}
|
||||
|
||||
for (int i = 0; i < state_machine->get_transition_count(); i++) {
|
||||
String from = state_machine->get_transition_from(i);
|
||||
String to = state_machine->get_transition_to(i);
|
||||
Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i);
|
||||
|
||||
if (from == group_name || to == group_name) {
|
||||
undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, tr);
|
||||
}
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(this, "_update_graph");
|
||||
undo_redo->add_undo_method(this, "_update_graph");
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (find) {
|
||||
selected_nodes = new_selected_nodes;
|
||||
selected_node = StringName();
|
||||
state_machine_draw->queue_redraw();
|
||||
accept_event();
|
||||
_update_mode();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) {
|
||||
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
|
||||
if (!tree) {
|
||||
@ -790,83 +582,8 @@ void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) {
|
||||
add_node_pos = p_position / EDSCALE + state_machine->get_graph_offset();
|
||||
}
|
||||
|
||||
void AnimationNodeStateMachineEditor::_open_connect_menu(const Vector2 &p_position) {
|
||||
ERR_FAIL_COND(connecting_to_node == StringName());
|
||||
|
||||
Ref<AnimationNode> node = state_machine->get_node(connecting_to_node);
|
||||
Ref<AnimationNodeStateMachine> anodesm = node;
|
||||
Ref<AnimationNodeEndState> end_node = node;
|
||||
ERR_FAIL_COND(!anodesm.is_valid() && !end_node.is_valid());
|
||||
|
||||
connect_menu->clear();
|
||||
state_machine_menu->clear();
|
||||
end_menu->clear();
|
||||
nodes_to_connect.clear();
|
||||
|
||||
for (int i = connect_menu->get_child_count() - 1; i >= 0; i--) {
|
||||
Node *child = connect_menu->get_child(i);
|
||||
|
||||
if (child->is_class("PopupMenu")) {
|
||||
connect_menu->remove_child(child);
|
||||
}
|
||||
}
|
||||
|
||||
connect_menu->reset_size();
|
||||
state_machine_menu->reset_size();
|
||||
end_menu->reset_size();
|
||||
|
||||
if (anodesm.is_valid()) {
|
||||
_create_submenu(connect_menu, anodesm, connecting_to_node, connecting_to_node);
|
||||
} else {
|
||||
_create_submenu(connect_menu, state_machine, connecting_to_node, connecting_to_node, true);
|
||||
}
|
||||
|
||||
connect_menu->add_submenu_item(TTR("To") + " Animation", connecting_to_node);
|
||||
|
||||
if (state_machine_menu->get_item_count() > 0 || !end_node.is_valid()) {
|
||||
connect_menu->add_submenu_item(TTR("To") + " StateMachine", "state_machines");
|
||||
connect_menu->add_child(state_machine_menu);
|
||||
}
|
||||
|
||||
if (end_node.is_valid()) {
|
||||
connect_menu->add_submenu_item(TTR("To") + " End", "end_nodes");
|
||||
connect_menu->add_child(end_menu);
|
||||
} else {
|
||||
state_machine_menu->add_item(connecting_to_node, nodes_to_connect.size());
|
||||
}
|
||||
|
||||
nodes_to_connect.push_back(connecting_to_node);
|
||||
|
||||
if (nodes_to_connect.size() == 1) {
|
||||
_add_transition();
|
||||
return;
|
||||
}
|
||||
|
||||
connect_menu->set_position(state_machine_draw->get_screen_transform().xform(p_position));
|
||||
connect_menu->popup();
|
||||
}
|
||||
|
||||
bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Ref<AnimationNodeStateMachine> p_nodesm, const StringName &p_name, const StringName &p_path, bool from_root, Vector<Ref<AnimationNodeStateMachine>> p_parents) {
|
||||
bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Ref<AnimationNodeStateMachine> p_nodesm, const StringName &p_name, const StringName &p_path) {
|
||||
String prev_path;
|
||||
Vector<Ref<AnimationNodeStateMachine>> parents = p_parents;
|
||||
|
||||
if (from_root && p_nodesm->get_prev_state_machine() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (from_root) {
|
||||
AnimationNodeStateMachine *prev = p_nodesm->get_prev_state_machine();
|
||||
|
||||
while (prev != nullptr) {
|
||||
parents.push_back(prev);
|
||||
p_nodesm = Ref<AnimationNodeStateMachine>(prev);
|
||||
prev_path += "../";
|
||||
prev = prev->get_prev_state_machine();
|
||||
}
|
||||
end_menu->add_item("Root", nodes_to_connect.size());
|
||||
nodes_to_connect.push_back(prev_path + state_machine->end_node);
|
||||
prev_path.remove_at(prev_path.size() - 1);
|
||||
}
|
||||
|
||||
List<StringName> nodes;
|
||||
p_nodesm->get_node_list(&nodes);
|
||||
@ -881,12 +598,7 @@ bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Ref<Ani
|
||||
if (p_nodesm->can_edit_node(E)) {
|
||||
Ref<AnimationNodeStateMachine> ansm = p_nodesm->get_node(E);
|
||||
|
||||
String path;
|
||||
if (from_root) {
|
||||
path = prev_path + "/" + E;
|
||||
} else {
|
||||
path = String(p_path) + "/" + E;
|
||||
}
|
||||
String path = String(p_path) + "/" + E;
|
||||
|
||||
if (ansm == state_machine) {
|
||||
end_menu->add_item(E, nodes_to_connect.size());
|
||||
@ -895,25 +607,10 @@ bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Ref<Ani
|
||||
}
|
||||
|
||||
if (ansm.is_valid()) {
|
||||
bool parent_found = false;
|
||||
state_machine_menu->add_item(E, nodes_to_connect.size());
|
||||
nodes_to_connect.push_back(path);
|
||||
|
||||
for (int i = 0; i < parents.size(); i++) {
|
||||
if (parents[i] == ansm) {
|
||||
path = path.replace_first("/../" + E, "");
|
||||
parent_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent_found) {
|
||||
end_menu->add_item(E, nodes_to_connect.size());
|
||||
nodes_to_connect.push_back(path + "/" + state_machine->end_node);
|
||||
} else {
|
||||
state_machine_menu->add_item(E, nodes_to_connect.size());
|
||||
nodes_to_connect.push_back(path);
|
||||
}
|
||||
|
||||
if (_create_submenu(nodes_menu, ansm, E, path, false, parents)) {
|
||||
if (_create_submenu(nodes_menu, ansm, E, path)) {
|
||||
nodes_menu->add_submenu_item(E, E);
|
||||
node_added = true;
|
||||
}
|
||||
@ -939,7 +636,6 @@ void AnimationNodeStateMachineEditor::_delete_selected() {
|
||||
while (item) {
|
||||
if (!updating) {
|
||||
updating = true;
|
||||
selected_multi_transition = TransitionLine();
|
||||
undo_redo->create_action("Transition(s) Removed");
|
||||
}
|
||||
|
||||
@ -959,18 +655,10 @@ void AnimationNodeStateMachineEditor::_delete_selected() {
|
||||
}
|
||||
|
||||
void AnimationNodeStateMachineEditor::_delete_all() {
|
||||
Vector<TransitionLine> multi_transitions = selected_multi_transition.multi_transitions;
|
||||
selected_multi_transition = TransitionLine();
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action("Transition(s) Removed");
|
||||
_erase_selected(true);
|
||||
for (int i = 0; i < multi_transitions.size(); i++) {
|
||||
selected_transition_from = multi_transitions[i].from_node;
|
||||
selected_transition_to = multi_transitions[i].to_node;
|
||||
_erase_selected(true);
|
||||
}
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
|
||||
@ -1120,14 +808,19 @@ void AnimationNodeStateMachineEditor::_add_transition(const bool p_nested_action
|
||||
selected_transition_to = connecting_to_node;
|
||||
selected_transition_index = transition_lines.size();
|
||||
|
||||
EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
|
||||
if (!state_machine->is_transition_across_group(selected_transition_index)) {
|
||||
EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
|
||||
} else {
|
||||
EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
|
||||
EditorNode::get_singleton()->push_item(nullptr, "", true);
|
||||
}
|
||||
_update_mode();
|
||||
}
|
||||
|
||||
connecting = false;
|
||||
}
|
||||
|
||||
void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_multi_transitions) {
|
||||
void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group) {
|
||||
Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label"));
|
||||
Color icon_color(1, 1, 1);
|
||||
Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
|
||||
@ -1173,11 +866,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co
|
||||
xf.columns[2] = (p_from + p_to) * 0.5 - xf.columns[1] * icon->get_height() * 0.5 - xf.columns[0] * icon->get_height() * 0.5;
|
||||
|
||||
state_machine_draw->draw_set_transform_matrix(xf);
|
||||
if (p_multi_transitions) {
|
||||
state_machine_draw->draw_texture(icons[0], Vector2(-icon->get_width(), 0), icon_color);
|
||||
state_machine_draw->draw_texture(icons[0], Vector2(), icon_color);
|
||||
state_machine_draw->draw_texture(icons[0], Vector2(icon->get_width(), 0), icon_color);
|
||||
} else {
|
||||
if (!p_is_across_group) {
|
||||
state_machine_draw->draw_texture(icon, Vector2(), icon_color);
|
||||
}
|
||||
state_machine_draw->draw_set_transform_matrix(Transform2D());
|
||||
@ -1344,17 +1033,14 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
|
||||
for (int i = 0; i < state_machine->get_transition_count(); i++) {
|
||||
TransitionLine tl;
|
||||
tl.transition_index = i;
|
||||
|
||||
tl.from_node = state_machine->get_transition_from(i);
|
||||
StringName local_from = String(tl.from_node).get_slicec('/', 0);
|
||||
local_from = local_from == ".." ? state_machine->start_node : local_from;
|
||||
Vector2 ofs_from = (dragging_selected && selected_nodes.has(local_from)) ? drag_ofs : Vector2();
|
||||
tl.from = (state_machine->get_node_position(local_from) * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE;
|
||||
Vector2 ofs_from = (dragging_selected && selected_nodes.has(tl.from_node)) ? drag_ofs : Vector2();
|
||||
tl.from = (state_machine->get_node_position(tl.from_node) * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE;
|
||||
|
||||
tl.to_node = state_machine->get_transition_to(i);
|
||||
StringName local_to = String(tl.to_node).get_slicec('/', 0);
|
||||
local_to = local_to == ".." ? state_machine->end_node : local_to;
|
||||
Vector2 ofs_to = (dragging_selected && selected_nodes.has(local_to)) ? drag_ofs : Vector2();
|
||||
tl.to = (state_machine->get_node_position(local_to) * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE;
|
||||
Vector2 ofs_to = (dragging_selected && selected_nodes.has(tl.to_node)) ? drag_ofs : Vector2();
|
||||
tl.to = (state_machine->get_node_position(tl.to_node) * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE;
|
||||
|
||||
Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i);
|
||||
tl.disabled = bool(tr->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED);
|
||||
@ -1366,35 +1052,36 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
|
||||
tl.travel = false;
|
||||
tl.fade_ratio = 0.0;
|
||||
tl.hidden = false;
|
||||
tl.is_across_group = state_machine->is_transition_across_group(i);
|
||||
|
||||
if (state_machine->has_local_transition(local_to, local_from)) { //offset if same exists
|
||||
if (state_machine->has_transition(tl.to_node, tl.from_node)) { //offset if same exists
|
||||
Vector2 offset = -(tl.from - tl.to).normalized().orthogonal() * tr_bidi_offset;
|
||||
tl.from += offset;
|
||||
tl.to += offset;
|
||||
}
|
||||
|
||||
for (int j = 0; j < node_rects.size(); j++) {
|
||||
if (node_rects[j].node_name == local_from) {
|
||||
if (node_rects[j].node_name == tl.from_node) {
|
||||
_clip_src_line_to_rect(tl.from, tl.to, node_rects[j].node);
|
||||
}
|
||||
if (node_rects[j].node_name == local_to) {
|
||||
if (node_rects[j].node_name == tl.to_node) {
|
||||
_clip_dst_line_to_rect(tl.from, tl.to, node_rects[j].node);
|
||||
}
|
||||
}
|
||||
|
||||
tl.selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node;
|
||||
|
||||
if (blend_from == local_from && current == local_to) {
|
||||
if (blend_from == tl.from_node && current == tl.to_node) {
|
||||
tl.travel = true;
|
||||
tl.fade_ratio = MIN(1.0, fading_pos / fading_time);
|
||||
}
|
||||
|
||||
if (travel_path.size()) {
|
||||
if (current == local_from && travel_path[0] == local_to) {
|
||||
if (current == tl.from_node && travel_path[0] == tl.to_node) {
|
||||
tl.travel = true;
|
||||
} else {
|
||||
for (int j = 0; j < travel_path.size() - 1; j++) {
|
||||
if (travel_path[j] == local_from && travel_path[j + 1] == local_to) {
|
||||
if (travel_path[j] == tl.from_node && travel_path[j + 1] == tl.to_node) {
|
||||
tl.travel = true;
|
||||
break;
|
||||
}
|
||||
@ -1408,17 +1095,11 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
|
||||
tl.auto_advance = true;
|
||||
}
|
||||
|
||||
// check if already have this local transition
|
||||
// check if already have this transition
|
||||
for (int j = 0; j < transition_lines.size(); j++) {
|
||||
StringName from = String(transition_lines[j].from_node).get_slicec('/', 0);
|
||||
StringName to = String(transition_lines[j].to_node).get_slicec('/', 0);
|
||||
from = from == ".." ? state_machine->start_node : from;
|
||||
to = to == ".." ? state_machine->end_node : to;
|
||||
|
||||
if (from == local_from && to == local_to) {
|
||||
if (transition_lines[j].from_node == tl.from_node && transition_lines[j].to_node == tl.to_node) {
|
||||
tl.hidden = true;
|
||||
transition_lines.write[j].disabled = transition_lines[j].disabled && tl.disabled;
|
||||
transition_lines.write[j].multi_transitions.push_back(tl);
|
||||
}
|
||||
}
|
||||
transition_lines.push_back(tl);
|
||||
@ -1427,7 +1108,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
|
||||
for (int i = 0; i < transition_lines.size(); i++) {
|
||||
TransitionLine tl = transition_lines[i];
|
||||
if (!tl.hidden) {
|
||||
_connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, !tl.multi_transitions.is_empty());
|
||||
_connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, tl.is_across_group);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1481,6 +1162,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
|
||||
state_machine_draw->draw_string(font, nr.name.position + Vector2(0, font->get_ascent(font_size)), name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
|
||||
offset.x += strsize + sep;
|
||||
|
||||
nr.can_edit = needs_editor;
|
||||
if (needs_editor) {
|
||||
nr.edit.position = offset + Vector2(0, (h - edit->get_height()) / 2).floor();
|
||||
nr.edit.size = edit->get_size();
|
||||
@ -1546,6 +1228,10 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_individual(String
|
||||
|
||||
const NodeRect &nr = node_rects[idx];
|
||||
|
||||
if (nr.can_edit) {
|
||||
return; // It is not AnimationNodeAnimation.
|
||||
}
|
||||
|
||||
Vector2 from;
|
||||
from.x = nr.play.position.x;
|
||||
from.y = (nr.play.position.y + nr.play.size.y + nr.node.position.y + nr.node.size.y) * 0.5;
|
||||
@ -1584,14 +1270,14 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_all() {
|
||||
{
|
||||
float len = MAX(0.0001, current_length);
|
||||
float pos = CLAMP(current_play_pos, 0, len);
|
||||
float c = pos / len;
|
||||
float c = current_length == HUGE_LENGTH ? 1 : (pos / len);
|
||||
_state_machine_pos_draw_individual(playback->get_current_node(), c);
|
||||
}
|
||||
|
||||
{
|
||||
float len = MAX(0.0001, fade_from_length);
|
||||
float pos = CLAMP(fade_from_current_play_pos, 0, len);
|
||||
float c = pos / len;
|
||||
float c = fade_from_length == HUGE_LENGTH ? 1 : (pos / len);
|
||||
_state_machine_pos_draw_individual(playback->get_fading_from_node(), c);
|
||||
}
|
||||
}
|
||||
@ -1630,8 +1316,6 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
|
||||
auto_advance->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons")));
|
||||
|
||||
tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
|
||||
tool_group->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons")));
|
||||
tool_ungroup->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons")));
|
||||
|
||||
play_mode->clear();
|
||||
play_mode->add_icon_item(get_theme_icon(SNAME("PlayTravel"), SNAME("EditorIcons")), TTR("Travel"));
|
||||
@ -1655,10 +1339,6 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
|
||||
error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
|
||||
} else if (tree->is_state_invalid()) {
|
||||
error = tree->get_invalid_state_reason();
|
||||
/*} else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) {
|
||||
if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) {
|
||||
error = TTR("Start and end nodes are needed for a sub-transition.");
|
||||
}*/
|
||||
} else if (playback.is_null()) {
|
||||
error = vformat(TTR("No playback resource set at path: %s."), AnimationTreeEditor::get_singleton()->get_base_path() + "playback");
|
||||
}
|
||||
@ -1780,8 +1460,12 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
|
||||
|
||||
while (anodesm.is_valid()) {
|
||||
current_node_playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + next + "/playback");
|
||||
next += "/" + current_node_playback->get_current_node();
|
||||
anodesm = anodesm->get_node(current_node_playback->get_current_node());
|
||||
StringName cnode = current_node_playback->get_current_node();
|
||||
next += "/" + cnode;
|
||||
if (!anodesm->has_node(cnode)) {
|
||||
break;
|
||||
}
|
||||
anodesm = anodesm->get_node(cnode);
|
||||
}
|
||||
|
||||
// when current_node is a state machine, use playback of current_node to set play_pos
|
||||
@ -1883,10 +1567,7 @@ void AnimationNodeStateMachineEditor::_erase_selected(const bool p_nested_action
|
||||
for (int j = 0; j < state_machine->get_transition_count(); j++) {
|
||||
String from = state_machine->get_transition_from(j);
|
||||
String to = state_machine->get_transition_to(j);
|
||||
String local_from = from.get_slicec('/', 0);
|
||||
String local_to = to.get_slicec('/', 0);
|
||||
|
||||
if (local_from == node_rects[i].node_name || local_to == node_rects[i].node_name) {
|
||||
if (from == node_rects[i].node_name || to == node_rects[i].node_name) {
|
||||
undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, state_machine->get_transition(j));
|
||||
}
|
||||
}
|
||||
@ -1903,30 +1584,6 @@ void AnimationNodeStateMachineEditor::_erase_selected(const bool p_nested_action
|
||||
selected_nodes.clear();
|
||||
}
|
||||
|
||||
if (!selected_multi_transition.multi_transitions.is_empty()) {
|
||||
delete_tree->clear();
|
||||
|
||||
TreeItem *root = delete_tree->create_item();
|
||||
|
||||
TreeItem *item = delete_tree->create_item(root);
|
||||
item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
item->set_text(0, String(selected_transition_from) + " -> " + selected_transition_to);
|
||||
item->set_editable(0, true);
|
||||
|
||||
for (int i = 0; i < selected_multi_transition.multi_transitions.size(); i++) {
|
||||
String from = selected_multi_transition.multi_transitions[i].from_node;
|
||||
String to = selected_multi_transition.multi_transitions[i].to_node;
|
||||
|
||||
item = delete_tree->create_item(root);
|
||||
item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
item->set_text(0, from + " -> " + to);
|
||||
item->set_editable(0, true);
|
||||
}
|
||||
|
||||
delete_window->popup_centered(Vector2(400, 200));
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected_transition_to != StringName() && selected_transition_from != StringName() && state_machine->has_transition(selected_transition_from, selected_transition_to)) {
|
||||
Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(state_machine->find_transition(selected_transition_from, selected_transition_to));
|
||||
if (!p_nested_action) {
|
||||
@ -1945,7 +1602,6 @@ void AnimationNodeStateMachineEditor::_erase_selected(const bool p_nested_action
|
||||
selected_transition_from = StringName();
|
||||
selected_transition_to = StringName();
|
||||
selected_transition_index = -1;
|
||||
selected_multi_transition = TransitionLine();
|
||||
}
|
||||
|
||||
state_machine_draw->queue_redraw();
|
||||
@ -1957,24 +1613,6 @@ void AnimationNodeStateMachineEditor::_update_mode() {
|
||||
bool nothing_selected = selected_nodes.is_empty() && selected_transition_from == StringName() && selected_transition_to == StringName();
|
||||
bool start_end_selected = selected_nodes.size() == 1 && (*selected_nodes.begin() == state_machine->start_node || *selected_nodes.begin() == state_machine->end_node);
|
||||
tool_erase->set_disabled(nothing_selected || start_end_selected || read_only);
|
||||
|
||||
if (selected_nodes.is_empty() || start_end_selected || read_only) {
|
||||
tool_group->set_disabled(true);
|
||||
tool_group->set_visible(true);
|
||||
tool_ungroup->set_visible(false);
|
||||
} else {
|
||||
Ref<AnimationNodeStateMachine> ansm = state_machine->get_node(*selected_nodes.begin());
|
||||
|
||||
if (selected_nodes.size() == 1 && ansm.is_valid()) {
|
||||
tool_group->set_disabled(true);
|
||||
tool_group->set_visible(false);
|
||||
tool_ungroup->set_visible(true);
|
||||
} else {
|
||||
tool_group->set_disabled(false);
|
||||
tool_group->set_visible(true);
|
||||
tool_ungroup->set_visible(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selection_tools_hb->hide();
|
||||
}
|
||||
@ -2037,20 +1675,6 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
|
||||
top_hb->add_child(selection_tools_hb);
|
||||
selection_tools_hb->add_child(memnew(VSeparator));
|
||||
|
||||
tool_group = memnew(Button);
|
||||
tool_group->set_flat(true);
|
||||
tool_group->set_tooltip_text(TTR("Group Selected Node(s)") + " (Ctrl+G)");
|
||||
tool_group->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_group_selected_nodes));
|
||||
tool_group->set_disabled(true);
|
||||
selection_tools_hb->add_child(tool_group);
|
||||
|
||||
tool_ungroup = memnew(Button);
|
||||
tool_ungroup->set_flat(true);
|
||||
tool_ungroup->set_tooltip_text(TTR("Ungroup Selected Node") + " (Ctrl+Shift+G)");
|
||||
tool_ungroup->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_ungroup_selected_nodes));
|
||||
tool_ungroup->set_visible(false);
|
||||
selection_tools_hb->add_child(tool_ungroup);
|
||||
|
||||
tool_erase = memnew(Button);
|
||||
tool_erase->set_flat(true);
|
||||
tool_erase->set_tooltip_text(TTR("Remove selected node or transition."));
|
||||
|
@ -56,8 +56,6 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
|
||||
LineEdit *name_edit = nullptr;
|
||||
|
||||
HBoxContainer *selection_tools_hb = nullptr;
|
||||
Button *tool_group = nullptr;
|
||||
Button *tool_ungroup = nullptr;
|
||||
Button *tool_erase = nullptr;
|
||||
|
||||
HBoxContainer *transition_tools_hb = nullptr;
|
||||
@ -85,7 +83,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
|
||||
static AnimationNodeStateMachineEditor *singleton;
|
||||
|
||||
void _state_machine_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_multi_transitions);
|
||||
void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group);
|
||||
|
||||
void _state_machine_draw();
|
||||
|
||||
@ -136,6 +134,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
|
||||
Rect2 play;
|
||||
Rect2 name;
|
||||
Rect2 edit;
|
||||
bool can_edit;
|
||||
};
|
||||
|
||||
Vector<NodeRect> node_rects;
|
||||
@ -156,7 +155,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
|
||||
float fade_ratio;
|
||||
bool hidden;
|
||||
int transition_index;
|
||||
Vector<TransitionLine> multi_transitions;
|
||||
bool is_across_group = false;
|
||||
};
|
||||
|
||||
Vector<TransitionLine> transition_lines;
|
||||
@ -178,7 +177,6 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
|
||||
StringName selected_transition_from;
|
||||
StringName selected_transition_to;
|
||||
int selected_transition_index;
|
||||
TransitionLine selected_multi_transition;
|
||||
void _add_transition(const bool p_nested_action = false);
|
||||
|
||||
StringName over_node;
|
||||
@ -190,19 +188,17 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
|
||||
void _open_editor(const String &p_name);
|
||||
void _scroll_changed(double);
|
||||
|
||||
String _get_root_playback_path(String &r_node_directory);
|
||||
|
||||
void _clip_src_line_to_rect(Vector2 &r_from, const Vector2 &p_to, const Rect2 &p_rect);
|
||||
void _clip_dst_line_to_rect(const Vector2 &p_from, Vector2 &r_to, const Rect2 &p_rect);
|
||||
|
||||
void _erase_selected(const bool p_nested_action = false);
|
||||
void _update_mode();
|
||||
void _open_menu(const Vector2 &p_position);
|
||||
void _open_connect_menu(const Vector2 &p_position);
|
||||
bool _create_submenu(PopupMenu *p_menu, Ref<AnimationNodeStateMachine> p_nodesm, const StringName &p_name, const StringName &p_path, bool from_root = false, Vector<Ref<AnimationNodeStateMachine>> p_parents = Vector<Ref<AnimationNodeStateMachine>>());
|
||||
bool _create_submenu(PopupMenu *p_menu, Ref<AnimationNodeStateMachine> p_nodesm, const StringName &p_name, const StringName &p_path);
|
||||
void _stop_connecting();
|
||||
|
||||
void _group_selected_nodes();
|
||||
void _ungroup_selected_nodes();
|
||||
|
||||
void _delete_selected();
|
||||
void _delete_all();
|
||||
void _delete_tree_draw();
|
||||
|
@ -66,11 +66,6 @@ void AnimationTreeEditor::edit(AnimationTree *p_tree) {
|
||||
|
||||
Vector<String> path;
|
||||
if (tree) {
|
||||
if (tree->has_meta("_tree_edit_path")) {
|
||||
path = tree->get_meta("_tree_edit_path");
|
||||
} else {
|
||||
current_root = ObjectID();
|
||||
}
|
||||
edit_path(path);
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) {
|
||||
Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) const {
|
||||
return get_blend_point_node(p_name.operator String().to_int());
|
||||
}
|
||||
|
||||
@ -272,14 +272,14 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<Animatio
|
||||
}
|
||||
}
|
||||
|
||||
double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeBlendSpace1D::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
if (blend_points_used == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (blend_points_used == 1) {
|
||||
// only one point available, just play that animation
|
||||
return blend_node(blend_points[0].name, blend_points[0].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
return blend_node(blend_points[0].name, blend_points[0].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
double blend_pos = get_parameter(blend_position);
|
||||
@ -351,10 +351,10 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_is_
|
||||
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i == point_lower || i == point_higher) {
|
||||
double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true);
|
||||
double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true, p_test_only);
|
||||
max_time_remaining = MAX(max_time_remaining, remaining);
|
||||
} else if (sync) {
|
||||
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
|
||||
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -379,22 +379,22 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_is_
|
||||
na_n->set_backward(na_c->is_backward());
|
||||
}
|
||||
//see how much animation remains
|
||||
from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true);
|
||||
from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
cur_length_internal = from + max_time_remaining;
|
||||
|
||||
cur_closest = new_closest;
|
||||
|
||||
} else {
|
||||
max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i != cur_closest) {
|
||||
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
|
||||
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,10 +114,10 @@ public:
|
||||
void set_use_sync(bool p_sync);
|
||||
bool is_using_sync() const;
|
||||
|
||||
double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
String get_caption() const override;
|
||||
|
||||
Ref<AnimationNode> get_child_by_name(const StringName &p_name) override;
|
||||
Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
|
||||
AnimationNodeBlendSpace1D();
|
||||
~AnimationNodeBlendSpace1D();
|
||||
|
@ -442,7 +442,7 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect
|
||||
r_weights[2] = w;
|
||||
}
|
||||
|
||||
double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
_update_triangles();
|
||||
|
||||
Vector2 blend_pos = get_parameter(blend_position);
|
||||
@ -512,7 +512,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_is_
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (i == triangle_points[j]) {
|
||||
//blend with the given weight
|
||||
double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, blend_weights[j], FILTER_IGNORE, true);
|
||||
double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, blend_weights[j], FILTER_IGNORE, true, p_test_only);
|
||||
if (first || t < mind) {
|
||||
mind = t;
|
||||
first = false;
|
||||
@ -523,7 +523,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_is_
|
||||
}
|
||||
|
||||
if (sync && !found) {
|
||||
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
|
||||
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -548,22 +548,22 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_is_
|
||||
na_n->set_backward(na_c->is_backward());
|
||||
}
|
||||
//see how much animation remains
|
||||
from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true);
|
||||
from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
cur_length_internal = from + mind;
|
||||
|
||||
cur_closest = new_closest;
|
||||
|
||||
} else {
|
||||
mind = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
mind = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i != cur_closest) {
|
||||
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
|
||||
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -604,7 +604,7 @@ bool AnimationNodeBlendSpace2D::get_auto_triangles() const {
|
||||
return auto_triangles;
|
||||
}
|
||||
|
||||
Ref<AnimationNode> AnimationNodeBlendSpace2D::get_child_by_name(const StringName &p_name) {
|
||||
Ref<AnimationNode> AnimationNodeBlendSpace2D::get_child_by_name(const StringName &p_name) const {
|
||||
return get_blend_point_node(p_name.operator String().to_int());
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ public:
|
||||
void set_y_label(const String &p_label);
|
||||
String get_y_label() const;
|
||||
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
virtual String get_caption() const override;
|
||||
|
||||
Vector2 get_closest_point(const Vector2 &p_point);
|
||||
@ -143,7 +143,7 @@ public:
|
||||
void set_use_sync(bool p_sync);
|
||||
bool is_using_sync() const;
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) override;
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
|
||||
AnimationNodeBlendSpace2D();
|
||||
~AnimationNodeBlendSpace2D();
|
||||
|
@ -64,7 +64,7 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const
|
||||
}
|
||||
}
|
||||
|
||||
double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
AnimationPlayer *ap = state->player;
|
||||
ERR_FAIL_COND_V(!ap, 0);
|
||||
|
||||
@ -99,6 +99,7 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext
|
||||
step = p_time;
|
||||
}
|
||||
|
||||
bool is_looping = false;
|
||||
if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) {
|
||||
if (!Math::is_zero_approx(anim_size)) {
|
||||
if (prev_time >= 0 && cur_time < 0) {
|
||||
@ -111,6 +112,7 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext
|
||||
}
|
||||
cur_time = Math::pingpong(cur_time, anim_size);
|
||||
}
|
||||
is_looping = true;
|
||||
} else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) {
|
||||
if (!Math::is_zero_approx(anim_size)) {
|
||||
if (prev_time >= 0 && cur_time < 0) {
|
||||
@ -122,6 +124,7 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext
|
||||
cur_time = Math::fposmod(cur_time, anim_size);
|
||||
}
|
||||
backward = false;
|
||||
is_looping = true;
|
||||
} else {
|
||||
if (cur_time < 0) {
|
||||
step += cur_time;
|
||||
@ -159,14 +162,16 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext
|
||||
}
|
||||
}
|
||||
|
||||
if (play_mode == PLAY_MODE_FORWARD) {
|
||||
blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag);
|
||||
} else {
|
||||
blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag);
|
||||
if (!p_test_only) {
|
||||
if (play_mode == PLAY_MODE_FORWARD) {
|
||||
blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag);
|
||||
} else {
|
||||
blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag);
|
||||
}
|
||||
}
|
||||
set_parameter(time, cur_time);
|
||||
|
||||
return anim_size - cur_time;
|
||||
return is_looping ? HUGE_LENGTH : anim_size - cur_time;
|
||||
}
|
||||
|
||||
String AnimationNodeAnimation::get_caption() const {
|
||||
@ -311,7 +316,7 @@ bool AnimationNodeOneShot::has_filter() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
OneShotRequest cur_request = static_cast<OneShotRequest>((int)get_parameter(request));
|
||||
bool cur_active = get_parameter(active);
|
||||
double cur_time = get_parameter(time);
|
||||
@ -324,7 +329,7 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter
|
||||
if (cur_request == ONE_SHOT_REQUEST_ABORT) {
|
||||
set_parameter(active, false);
|
||||
set_parameter(time_to_restart, -1);
|
||||
return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
|
||||
return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
|
||||
} else if (!do_start && !cur_active) {
|
||||
if (cur_time_to_restart >= 0.0 && !p_seek) {
|
||||
cur_time_to_restart -= p_time;
|
||||
@ -334,7 +339,7 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter
|
||||
set_parameter(time_to_restart, cur_time_to_restart);
|
||||
}
|
||||
if (!do_start) {
|
||||
return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
|
||||
return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,11 +375,11 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter
|
||||
|
||||
double main_rem = 0.0;
|
||||
if (mix == MIX_MODE_ADD) {
|
||||
main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
|
||||
main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
|
||||
} else {
|
||||
main_rem = blend_input(0, p_time, use_blend && p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync); // Unlike below, processing this edge is a corner case.
|
||||
main_rem = blend_input(0, p_time, use_blend && p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
|
||||
}
|
||||
double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_PASS, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
|
||||
double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
|
||||
|
||||
if (do_start) {
|
||||
cur_remaining = os_rem;
|
||||
@ -459,10 +464,10 @@ bool AnimationNodeAdd2::has_filter() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
double AnimationNodeAdd2::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeAdd2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
double amount = get_parameter(add_amount);
|
||||
double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
|
||||
blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync);
|
||||
double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
|
||||
blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync, p_test_only);
|
||||
|
||||
return rem0;
|
||||
}
|
||||
@ -493,11 +498,11 @@ bool AnimationNodeAdd3::has_filter() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
double AnimationNodeAdd3::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeAdd3::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
double amount = get_parameter(add_amount);
|
||||
blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_PASS, sync);
|
||||
double rem0 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
|
||||
blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_PASS, sync);
|
||||
blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_PASS, sync, p_test_only);
|
||||
double rem0 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
|
||||
blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_PASS, sync, p_test_only);
|
||||
|
||||
return rem0;
|
||||
}
|
||||
@ -525,11 +530,11 @@ String AnimationNodeBlend2::get_caption() const {
|
||||
return "Blend2";
|
||||
}
|
||||
|
||||
double AnimationNodeBlend2::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeBlend2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
double amount = get_parameter(blend_amount);
|
||||
|
||||
double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - amount, FILTER_BLEND, sync);
|
||||
double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync);
|
||||
double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - amount, FILTER_BLEND, sync, p_test_only);
|
||||
double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync, p_test_only);
|
||||
|
||||
return amount > 0.5 ? rem1 : rem0; // Hacky but good enough.
|
||||
}
|
||||
@ -560,11 +565,11 @@ String AnimationNodeBlend3::get_caption() const {
|
||||
return "Blend3";
|
||||
}
|
||||
|
||||
double AnimationNodeBlend3::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeBlend3::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
double amount = get_parameter(blend_amount);
|
||||
double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_IGNORE, sync);
|
||||
double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0 - ABS(amount), FILTER_IGNORE, sync);
|
||||
double rem2 = blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_IGNORE, sync);
|
||||
double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_IGNORE, sync, p_test_only);
|
||||
double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0 - ABS(amount), FILTER_IGNORE, sync, p_test_only);
|
||||
double rem2 = blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_IGNORE, sync, p_test_only);
|
||||
|
||||
return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough.
|
||||
}
|
||||
@ -592,12 +597,12 @@ String AnimationNodeTimeScale::get_caption() const {
|
||||
return "TimeScale";
|
||||
}
|
||||
|
||||
double AnimationNodeTimeScale::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeTimeScale::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
double cur_scale = get_parameter(scale);
|
||||
if (p_seek) {
|
||||
return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
} else {
|
||||
return blend_input(0, p_time * cur_scale, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
return blend_input(0, p_time * cur_scale, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
|
||||
@ -622,16 +627,16 @@ String AnimationNodeTimeSeek::get_caption() const {
|
||||
return "TimeSeek";
|
||||
}
|
||||
|
||||
double AnimationNodeTimeSeek::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeTimeSeek::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
double cur_seek_pos = get_parameter(seek_pos_request);
|
||||
if (p_seek) {
|
||||
return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
} else if (cur_seek_pos >= 0) {
|
||||
double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true);
|
||||
double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
set_parameter(seek_pos_request, -1.0); // Reset.
|
||||
return ret;
|
||||
} else {
|
||||
return blend_input(0, p_time, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
return blend_input(0, p_time, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
|
||||
@ -815,7 +820,7 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const {
|
||||
return allow_transition_to_self;
|
||||
}
|
||||
|
||||
double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
String cur_transition_request = get_parameter(transition_request);
|
||||
int cur_current_index = get_parameter(current_index);
|
||||
int cur_prev_index = get_parameter(prev_index);
|
||||
@ -881,7 +886,7 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
|
||||
// Special case for restart.
|
||||
if (restart) {
|
||||
set_parameter(time, 0);
|
||||
return blend_input(cur_current_index, 0, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
return blend_input(cur_current_index, 0, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
if (switched) {
|
||||
@ -898,14 +903,14 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
|
||||
if (sync) {
|
||||
for (int i = 0; i < get_input_count(); i++) {
|
||||
if (i != cur_current_index && i != cur_prev_index) {
|
||||
blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
|
||||
blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cur_prev_index < 0) { // Process current animation, check for transition.
|
||||
|
||||
rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
|
||||
if (p_seek) {
|
||||
cur_time = p_time;
|
||||
@ -935,12 +940,12 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
|
||||
|
||||
// Blend values must be more than CMP_EPSILON to process discrete keys in edge.
|
||||
if (input_data[cur_current_index].reset && !p_seek && switched) { // Just switched, seek to start of current.
|
||||
rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, blend_inv, FILTER_IGNORE, true);
|
||||
rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, blend_inv, FILTER_IGNORE, true, p_test_only);
|
||||
} else {
|
||||
rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, blend_inv, FILTER_IGNORE, true);
|
||||
rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, blend_inv, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
blend_input(cur_prev_index, p_time, use_blend && p_seek, p_is_external_seeking, blend, FILTER_IGNORE, true);
|
||||
blend_input(cur_prev_index, p_time, use_blend && p_seek, p_is_external_seeking, blend, FILTER_IGNORE, true, p_test_only);
|
||||
if (p_seek) {
|
||||
cur_time = p_time;
|
||||
} else {
|
||||
@ -999,8 +1004,8 @@ String AnimationNodeOutput::get_caption() const {
|
||||
return "Output";
|
||||
}
|
||||
|
||||
double AnimationNodeOutput::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
double AnimationNodeOutput::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
AnimationNodeOutput::AnimationNodeOutput() {
|
||||
@ -1218,9 +1223,9 @@ String AnimationNodeBlendTree::get_caption() const {
|
||||
return "BlendTree";
|
||||
}
|
||||
|
||||
double AnimationNodeBlendTree::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNodeBlendTree::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output].node;
|
||||
return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
|
||||
return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, nullptr, p_test_only);
|
||||
}
|
||||
|
||||
void AnimationNodeBlendTree::get_node_list(List<StringName> *r_list) {
|
||||
@ -1237,7 +1242,7 @@ Vector2 AnimationNodeBlendTree::get_graph_offset() const {
|
||||
return graph_offset;
|
||||
}
|
||||
|
||||
Ref<AnimationNode> AnimationNodeBlendTree::get_child_by_name(const StringName &p_name) {
|
||||
Ref<AnimationNode> AnimationNodeBlendTree::get_child_by_name(const StringName &p_name) const {
|
||||
return get_node(p_name);
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ public:
|
||||
static Vector<String> (*get_editable_animation_list)();
|
||||
|
||||
virtual String get_caption() const override;
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
void set_animation(const StringName &p_name);
|
||||
StringName get_animation() const;
|
||||
@ -150,7 +150,7 @@ public:
|
||||
MixMode get_mix_mode() const;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeOneShot();
|
||||
};
|
||||
@ -173,7 +173,7 @@ public:
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeAdd2();
|
||||
};
|
||||
@ -193,7 +193,7 @@ public:
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeAdd3();
|
||||
};
|
||||
@ -211,7 +211,7 @@ public:
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
AnimationNodeBlend2();
|
||||
@ -231,7 +231,7 @@ public:
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
AnimationNodeBlend3();
|
||||
};
|
||||
|
||||
@ -249,7 +249,7 @@ public:
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeTimeScale();
|
||||
};
|
||||
@ -268,7 +268,7 @@ public:
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeTimeSeek();
|
||||
};
|
||||
@ -332,7 +332,7 @@ public:
|
||||
void set_allow_transition_to_self(bool p_enable);
|
||||
bool is_allow_transition_to_self() const;
|
||||
|
||||
double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeTransition();
|
||||
};
|
||||
@ -342,7 +342,7 @@ class AnimationNodeOutput : public AnimationNode {
|
||||
|
||||
public:
|
||||
virtual String get_caption() const override;
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
AnimationNodeOutput();
|
||||
};
|
||||
|
||||
@ -414,14 +414,14 @@ public:
|
||||
void get_node_connections(List<NodeConnection> *r_connections) const;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
|
||||
void get_node_list(List<StringName> *r_list);
|
||||
|
||||
void set_graph_offset(const Vector2 &p_graph_offset);
|
||||
Vector2 get_graph_offset() const;
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) override;
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
|
||||
AnimationNodeBlendTree();
|
||||
~AnimationNodeBlendTree();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -100,88 +100,23 @@ public:
|
||||
VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode)
|
||||
VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::AdvanceMode)
|
||||
|
||||
class AnimationNodeStateMachine;
|
||||
|
||||
class AnimationNodeStateMachinePlayback : public Resource {
|
||||
GDCLASS(AnimationNodeStateMachinePlayback, Resource);
|
||||
|
||||
friend class AnimationNodeStateMachine;
|
||||
|
||||
struct AStarCost {
|
||||
float distance = 0.0;
|
||||
StringName prev;
|
||||
};
|
||||
|
||||
struct Transition {
|
||||
StringName from;
|
||||
StringName to;
|
||||
StringName next;
|
||||
};
|
||||
|
||||
double len_fade_from = 0.0;
|
||||
double pos_fade_from = 0.0;
|
||||
|
||||
double len_current = 0.0;
|
||||
double pos_current = 0.0;
|
||||
bool end_loop = false;
|
||||
|
||||
StringName current;
|
||||
Transition current_transition;
|
||||
Ref<Curve> current_curve;
|
||||
bool force_auto_advance = false;
|
||||
|
||||
StringName fading_from;
|
||||
float fading_time = 0.0;
|
||||
float fading_pos = 0.0;
|
||||
|
||||
Vector<StringName> path;
|
||||
bool playing = false;
|
||||
|
||||
StringName start_request;
|
||||
StringName travel_request;
|
||||
bool reset_request = false;
|
||||
bool reset_request_on_teleport = false;
|
||||
bool next_request = false;
|
||||
bool stop_request = false;
|
||||
|
||||
bool _travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel);
|
||||
void _start(const StringName &p_state);
|
||||
double _process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking);
|
||||
|
||||
double process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking);
|
||||
|
||||
bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void travel(const StringName &p_state, bool p_reset_on_teleport = true);
|
||||
void start(const StringName &p_state, bool p_reset = true);
|
||||
void next();
|
||||
void stop();
|
||||
bool is_playing() const;
|
||||
StringName get_current_node() const;
|
||||
StringName get_fading_from_node() const;
|
||||
Vector<StringName> get_travel_path() const;
|
||||
float get_current_play_pos() const;
|
||||
float get_current_length() const;
|
||||
|
||||
float get_fade_from_play_pos() const;
|
||||
float get_fade_from_length() const;
|
||||
|
||||
float get_fading_time() const;
|
||||
float get_fading_pos() const;
|
||||
|
||||
AnimationNodeStateMachinePlayback();
|
||||
};
|
||||
class AnimationNodeStateMachinePlayback;
|
||||
|
||||
class AnimationNodeStateMachine : public AnimationRootNode {
|
||||
GDCLASS(AnimationNodeStateMachine, AnimationRootNode);
|
||||
|
||||
public:
|
||||
enum StateMachineType {
|
||||
STATE_MACHINE_TYPE_ROOT,
|
||||
STATE_MACHINE_TYPE_NESTED,
|
||||
STATE_MACHINE_TYPE_GROUPED,
|
||||
};
|
||||
|
||||
private:
|
||||
friend class AnimationNodeStateMachinePlayback;
|
||||
|
||||
StateMachineType state_machine_type = STATE_MACHINE_TYPE_ROOT;
|
||||
|
||||
struct State {
|
||||
Ref<AnimationRootNode> node;
|
||||
Vector2 position;
|
||||
@ -189,28 +124,24 @@ private:
|
||||
|
||||
HashMap<StringName, State> states;
|
||||
bool allow_transition_to_self = false;
|
||||
bool reset_ends = false;
|
||||
|
||||
struct Transition {
|
||||
StringName from;
|
||||
StringName to;
|
||||
StringName local_from;
|
||||
StringName local_to;
|
||||
Ref<AnimationNodeStateMachineTransition> transition;
|
||||
};
|
||||
|
||||
Vector<Transition> transitions;
|
||||
|
||||
StringName playback = "playback";
|
||||
StringName state_machine_name;
|
||||
AnimationNodeStateMachine *prev_state_machine = nullptr;
|
||||
bool updating_transitions = false;
|
||||
|
||||
Vector2 graph_offset;
|
||||
|
||||
void _remove_transition(const Ref<AnimationNodeStateMachineTransition> p_transition);
|
||||
void _rename_transitions(const StringName &p_name, const StringName &p_new_name);
|
||||
bool _can_connect(const StringName &p_name, Vector<AnimationNodeStateMachine *> p_parents = Vector<AnimationNodeStateMachine *>());
|
||||
StringName _get_shortest_path(const StringName &p_path) const;
|
||||
bool _can_connect(const StringName &p_name);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
@ -218,6 +149,8 @@ protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
|
||||
|
||||
virtual void _tree_changed() override;
|
||||
@ -232,6 +165,7 @@ public:
|
||||
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
virtual bool is_parameter_read_only(const StringName &p_parameter) const override;
|
||||
|
||||
void add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position = Vector2());
|
||||
void replace_node(const StringName &p_name, Ref<AnimationNode> p_node);
|
||||
@ -248,32 +182,164 @@ public:
|
||||
virtual void get_child_nodes(List<ChildNode> *r_child_nodes) override;
|
||||
|
||||
bool has_transition(const StringName &p_from, const StringName &p_to) const;
|
||||
bool has_local_transition(const StringName &p_from, const StringName &p_to) const;
|
||||
bool has_transition_from(const StringName &p_from) const;
|
||||
bool has_transition_to(const StringName &p_to) const;
|
||||
int find_transition(const StringName &p_from, const StringName &p_to) const;
|
||||
Vector<int> find_transition_from(const StringName &p_from) const;
|
||||
Vector<int> find_transition_to(const StringName &p_to) const;
|
||||
void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition);
|
||||
Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const;
|
||||
StringName get_transition_from(int p_transition) const;
|
||||
StringName get_transition_to(int p_transition) const;
|
||||
int get_transition_count() const;
|
||||
bool is_transition_across_group(int p_transition) const;
|
||||
void remove_transition_by_index(const int p_transition);
|
||||
void remove_transition(const StringName &p_from, const StringName &p_to);
|
||||
|
||||
void set_state_machine_type(StateMachineType p_state_machine_type);
|
||||
StateMachineType get_state_machine_type() const;
|
||||
|
||||
void set_allow_transition_to_self(bool p_enable);
|
||||
bool is_allow_transition_to_self() const;
|
||||
|
||||
bool can_edit_node(const StringName &p_name) const;
|
||||
void set_reset_ends(bool p_enable);
|
||||
bool are_ends_reset() const;
|
||||
|
||||
AnimationNodeStateMachine *get_prev_state_machine() const;
|
||||
bool can_edit_node(const StringName &p_name) const;
|
||||
|
||||
void set_graph_offset(const Vector2 &p_offset);
|
||||
Vector2 get_graph_offset() const;
|
||||
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) override;
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
|
||||
AnimationNodeStateMachine();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeStateMachine::StateMachineType);
|
||||
|
||||
class AnimationNodeStateMachinePlayback : public Resource {
|
||||
GDCLASS(AnimationNodeStateMachinePlayback, Resource);
|
||||
|
||||
friend class AnimationNodeStateMachine;
|
||||
|
||||
struct AStarCost {
|
||||
float distance = 0.0;
|
||||
StringName prev;
|
||||
};
|
||||
|
||||
struct TransitionInfo {
|
||||
StringName from;
|
||||
StringName to;
|
||||
StringName next;
|
||||
};
|
||||
|
||||
struct NextInfo {
|
||||
StringName node;
|
||||
double xfade;
|
||||
Ref<Curve> curve;
|
||||
AnimationNodeStateMachineTransition::SwitchMode switch_mode;
|
||||
bool is_reset;
|
||||
};
|
||||
|
||||
struct ChildStateMachineInfo {
|
||||
Ref<AnimationNodeStateMachinePlayback> playback;
|
||||
Vector<StringName> path;
|
||||
bool is_reset = false;
|
||||
};
|
||||
|
||||
Ref<AnimationNodeStateMachineTransition> default_transition;
|
||||
String base_path;
|
||||
|
||||
double len_fade_from = 0.0;
|
||||
double pos_fade_from = 0.0;
|
||||
|
||||
double len_current = 0.0;
|
||||
double pos_current = 0.0;
|
||||
|
||||
StringName current;
|
||||
Ref<Curve> current_curve;
|
||||
|
||||
Ref<AnimationNodeStateMachineTransition> group_start_transition;
|
||||
Ref<AnimationNodeStateMachineTransition> group_end_transition;
|
||||
|
||||
StringName fading_from;
|
||||
float fading_time = 0.0;
|
||||
float fading_pos = 0.0;
|
||||
|
||||
Vector<StringName> path;
|
||||
bool playing = false;
|
||||
|
||||
StringName start_request;
|
||||
StringName travel_request;
|
||||
bool reset_request = false;
|
||||
bool reset_request_on_teleport = false;
|
||||
bool _reset_request_for_fading_from = false;
|
||||
bool next_request = false;
|
||||
bool stop_request = false;
|
||||
bool teleport_request = false;
|
||||
|
||||
bool is_grouped = false;
|
||||
|
||||
void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true);
|
||||
void _start_main(const StringName &p_state, bool p_reset = true);
|
||||
void _next_main();
|
||||
void _stop_main();
|
||||
|
||||
bool _make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector<StringName> &r_path, bool p_test_only);
|
||||
String _validate_path(AnimationNodeStateMachine *p_state_machine, const String &p_path);
|
||||
bool _travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only);
|
||||
void _start(AnimationNodeStateMachine *p_state_machine);
|
||||
|
||||
void _clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only);
|
||||
bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only);
|
||||
void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only);
|
||||
|
||||
double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only);
|
||||
double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only);
|
||||
|
||||
bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
|
||||
bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only);
|
||||
NextInfo _find_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const;
|
||||
Ref<AnimationNodeStateMachineTransition> _check_group_transition(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const AnimationNodeStateMachine::Transition &p_transition, Ref<AnimationNodeStateMachine> &r_state_machine, bool &r_bypass) const;
|
||||
bool _can_transition_to_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only);
|
||||
|
||||
void _set_current(AnimationNodeStateMachine *p_state_machine, const StringName &p_state);
|
||||
void _set_grouped(bool p_is_grouped);
|
||||
void _set_base_path(const String &p_base_path);
|
||||
Ref<AnimationNodeStateMachinePlayback> _get_parent_playback(AnimationTree *p_tree) const;
|
||||
Ref<AnimationNodeStateMachine> _get_parent_state_machine(AnimationTree *p_tree) const;
|
||||
Ref<AnimationNodeStateMachineTransition> _get_group_start_transition() const;
|
||||
Ref<AnimationNodeStateMachineTransition> _get_group_end_transition() const;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void travel(const StringName &p_state, bool p_reset_on_teleport = true);
|
||||
void start(const StringName &p_state, bool p_reset = true);
|
||||
void next();
|
||||
void stop();
|
||||
bool is_playing() const;
|
||||
bool is_end() const;
|
||||
StringName get_current_node() const;
|
||||
StringName get_fading_from_node() const;
|
||||
Vector<StringName> get_travel_path() const;
|
||||
float get_current_play_pos() const;
|
||||
float get_current_length() const;
|
||||
|
||||
float get_fade_from_play_pos() const;
|
||||
float get_fade_from_length() const;
|
||||
|
||||
float get_fading_time() const;
|
||||
float get_fading_pos() const;
|
||||
|
||||
void clear_path();
|
||||
void push_path(const StringName &p_state);
|
||||
|
||||
AnimationNodeStateMachinePlayback();
|
||||
};
|
||||
|
||||
#endif // ANIMATION_NODE_STATE_MACHINE_H
|
||||
|
@ -61,6 +61,9 @@ bool AnimationNode::is_parameter_read_only(const StringName &p_parameter) const
|
||||
}
|
||||
|
||||
void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) {
|
||||
if (is_testing) {
|
||||
return;
|
||||
}
|
||||
ERR_FAIL_COND(!state);
|
||||
ERR_FAIL_COND(!state->tree->property_parent_map.has(base_path));
|
||||
ERR_FAIL_COND(!state->tree->property_parent_map[base_path].has(p_name));
|
||||
@ -124,13 +127,13 @@ void AnimationNode::blend_animation(const StringName &p_animation, double p_time
|
||||
state->animation_states.push_back(anim_state);
|
||||
}
|
||||
|
||||
double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector<StringName> &p_connections) {
|
||||
double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector<StringName> &p_connections, bool p_test_only) {
|
||||
base_path = p_base_path;
|
||||
parent = p_parent;
|
||||
connections = p_connections;
|
||||
state = p_state;
|
||||
|
||||
double t = process(p_time, p_seek, p_is_external_seeking);
|
||||
double t = process(p_time, p_seek, p_is_external_seeking, p_test_only);
|
||||
|
||||
state = nullptr;
|
||||
parent = nullptr;
|
||||
@ -154,7 +157,7 @@ void AnimationNode::make_invalid(const String &p_reason) {
|
||||
state->invalid_reasons += String::utf8("• ") + p_reason;
|
||||
}
|
||||
|
||||
double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync) {
|
||||
double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) {
|
||||
ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
|
||||
ERR_FAIL_COND_V(!state, 0);
|
||||
|
||||
@ -173,7 +176,7 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool
|
||||
|
||||
//inputs.write[p_input].last_pass = state->last_pass;
|
||||
real_t activity = 0.0;
|
||||
double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, &activity);
|
||||
double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, &activity, p_test_only);
|
||||
|
||||
Vector<AnimationTree::Activity> *activity_ptr = state->tree->input_activity_map.getptr(base_path);
|
||||
|
||||
@ -184,11 +187,11 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool
|
||||
return ret;
|
||||
}
|
||||
|
||||
double AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync) {
|
||||
return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync);
|
||||
double AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) {
|
||||
return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, nullptr, p_test_only);
|
||||
}
|
||||
|
||||
double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, real_t *r_max) {
|
||||
double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, real_t *r_max, bool p_test_only) {
|
||||
ERR_FAIL_COND_V(!p_node.is_valid(), 0);
|
||||
ERR_FAIL_COND_V(!state, 0);
|
||||
|
||||
@ -298,9 +301,9 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri
|
||||
// This process, which depends on p_sync is needed to process sync correctly in the case of
|
||||
// that a synced AnimationNodeSync exists under the un-synced AnimationNodeSync.
|
||||
if (!p_seek && !p_sync && !any_valid) {
|
||||
return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_is_external_seeking, p_connections);
|
||||
return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_is_external_seeking, p_connections, p_test_only);
|
||||
}
|
||||
return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_is_external_seeking, p_connections);
|
||||
return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_is_external_seeking, p_connections, p_test_only);
|
||||
}
|
||||
|
||||
String AnimationNode::get_caption() const {
|
||||
@ -354,9 +357,14 @@ int AnimationNode::find_input(const String &p_name) const {
|
||||
return idx;
|
||||
}
|
||||
|
||||
double AnimationNode::process(double p_time, bool p_seek, bool p_is_external_seeking) {
|
||||
double AnimationNode::process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
is_testing = p_test_only;
|
||||
return _process(p_time, p_seek, p_is_external_seeking, p_test_only);
|
||||
}
|
||||
|
||||
double AnimationNode::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
|
||||
double ret = 0;
|
||||
GDVIRTUAL_CALL(_process, p_time, p_seek, p_is_external_seeking, ret);
|
||||
GDVIRTUAL_CALL(_process, p_time, p_seek, p_is_external_seeking, p_test_only, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -410,12 +418,24 @@ void AnimationNode::_validate_property(PropertyInfo &p_property) const {
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) {
|
||||
Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) const {
|
||||
Ref<AnimationNode> ret;
|
||||
GDVIRTUAL_CALL(_get_child_by_name, p_name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ref<AnimationNode> AnimationNode::find_node_by_path(const String &p_name) const {
|
||||
Vector<String> split = p_name.split("/");
|
||||
Ref<AnimationNode> ret = const_cast<AnimationNode *>(this);
|
||||
for (int i = 0; i < split.size(); i++) {
|
||||
ret = ret->get_child_by_name(split[i]);
|
||||
if (!ret.is_valid()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AnimationNode::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input);
|
||||
ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input);
|
||||
@ -434,8 +454,8 @@ void AnimationNode::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "is_external_seeking", "blend", "looped_flag"), &AnimationNode::blend_animation, DEFVAL(Animation::LOOPED_FLAG_NONE));
|
||||
ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter);
|
||||
ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter);
|
||||
@ -448,7 +468,7 @@ void AnimationNode::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_get_child_by_name, "name");
|
||||
GDVIRTUAL_BIND(_get_parameter_default_value, "parameter");
|
||||
GDVIRTUAL_BIND(_is_parameter_read_only, "parameter");
|
||||
GDVIRTUAL_BIND(_process, "time", "seek", "is_external_seeking");
|
||||
GDVIRTUAL_BIND(_process, "time", "seek", "is_external_seeking", "test_only");
|
||||
GDVIRTUAL_BIND(_get_caption);
|
||||
GDVIRTUAL_BIND(_has_filter);
|
||||
|
||||
|
@ -37,6 +37,8 @@
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/resources/audio_stream_polyphonic.h"
|
||||
|
||||
#define HUGE_LENGTH 31540000 // 31540000 seconds mean 1 year... is it too long? It must be longer than any Animation length and Transition xfade time to prevent time inversion.
|
||||
|
||||
class AnimationNodeBlendTree;
|
||||
class AnimationNodeStartState;
|
||||
class AnimationNodeEndState;
|
||||
@ -87,7 +89,9 @@ public:
|
||||
Vector<real_t> blends;
|
||||
State *state = nullptr;
|
||||
|
||||
double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector<StringName> &p_connections);
|
||||
bool is_testing = false;
|
||||
|
||||
double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector<StringName> &p_connections, bool p_test_only = false);
|
||||
|
||||
//all this is temporary
|
||||
StringName base_path;
|
||||
@ -100,12 +104,15 @@ public:
|
||||
Array _get_filters() const;
|
||||
void _set_filters(const Array &p_filters);
|
||||
friend class AnimationNodeBlendTree;
|
||||
double _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr);
|
||||
double _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr, bool p_test_only = false);
|
||||
|
||||
protected:
|
||||
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false);
|
||||
double process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false);
|
||||
|
||||
void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
|
||||
double blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true);
|
||||
double blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true);
|
||||
double blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
|
||||
double blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
|
||||
|
||||
void make_invalid(const String &p_reason);
|
||||
AnimationTree *get_animation_tree() const;
|
||||
@ -119,7 +126,7 @@ protected:
|
||||
GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName)
|
||||
GDVIRTUAL1RC(Variant, _get_parameter_default_value, StringName)
|
||||
GDVIRTUAL1RC(bool, _is_parameter_read_only, StringName)
|
||||
GDVIRTUAL3RC(double, _process, double, bool, bool)
|
||||
GDVIRTUAL4RC(double, _process, double, bool, bool, bool)
|
||||
GDVIRTUAL0RC(String, _get_caption)
|
||||
GDVIRTUAL0RC(bool, _has_filter)
|
||||
|
||||
@ -138,7 +145,6 @@ public:
|
||||
|
||||
virtual void get_child_nodes(List<ChildNode> *r_child_nodes);
|
||||
|
||||
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking);
|
||||
virtual String get_caption() const;
|
||||
|
||||
virtual bool add_input(const String &p_name);
|
||||
@ -156,7 +162,8 @@ public:
|
||||
|
||||
virtual bool has_filter() const;
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name);
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const;
|
||||
Ref<AnimationNode> find_node_by_path(const String &p_name) const;
|
||||
|
||||
AnimationNode();
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user