summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/assimp/SCsub3
-rw-r--r--modules/assimp/editor_scene_importer_assimp.cpp1075
-rw-r--r--modules/assimp/editor_scene_importer_assimp.h50
-rw-r--r--modules/assimp/import_state.h53
-rw-r--r--modules/bmp/image_loader_bmp.cpp228
-rw-r--r--modules/bullet/btRayShape.h2
-rw-r--r--modules/bullet/collision_object_bullet.h12
-rw-r--r--modules/bullet/rigid_body_bullet.cpp18
-rw-r--r--modules/bullet/rigid_body_bullet.h4
-rw-r--r--modules/bullet/space_bullet.cpp21
-rw-r--r--modules/csg/csg.cpp2
-rw-r--r--modules/csg/csg_gizmos.cpp2
-rw-r--r--modules/csg/csg_shape.cpp12
-rw-r--r--modules/cvtt/SCsub19
-rw-r--r--modules/dds/texture_loader_dds.cpp6
-rw-r--r--modules/enet/doc_classes/NetworkedMultiplayerENet.xml2
-rw-r--r--modules/enet/networked_multiplayer_enet.cpp2
-rw-r--r--modules/etc/image_etc.cpp1
-rw-r--r--modules/etc/texture_loader_pkm.cpp4
-rw-r--r--modules/freetype/SCsub2
-rw-r--r--modules/gdnative/doc_classes/GDNativeLibrary.xml15
-rw-r--r--modules/gdnative/gdnative.cpp2
-rw-r--r--modules/gdnative/gdnative/array.cpp9
-rw-r--r--modules/gdnative/gdnative/pool_arrays.cpp35
-rw-r--r--modules/gdnative/gdnative_api.json60
-rw-r--r--modules/gdnative/include/gdnative/array.h2
-rw-r--r--modules/gdnative/include/gdnative/pool_arrays.h14
-rw-r--r--modules/gdnative/include/nativescript/godot_nativescript.h4
-rw-r--r--modules/gdnative/pluginscript/pluginscript_language.cpp4
-rw-r--r--modules/gdnative/pluginscript/pluginscript_script.cpp4
-rw-r--r--modules/gdnative/register_types.cpp6
-rw-r--r--modules/gdnative/videodecoder/video_stream_gdnative.cpp18
-rw-r--r--modules/gdnative/videodecoder/video_stream_gdnative.h3
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml18
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp28
-rw-r--r--modules/gdscript/gdscript.cpp17
-rw-r--r--modules/gdscript/gdscript_compiler.cpp10
-rw-r--r--modules/gdscript/gdscript_editor.cpp9
-rw-r--r--modules/gdscript/gdscript_function.cpp25
-rw-r--r--modules/gdscript/gdscript_functions.cpp48
-rw-r--r--modules/gdscript/gdscript_parser.cpp272
-rw-r--r--modules/gdscript/gdscript_parser.h30
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp83
-rw-r--r--modules/gdscript/gdscript_tokenizer.h10
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp115
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.h8
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp28
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp7
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp90
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.h4
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp41
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h4
-rw-r--r--modules/gdscript/language_server/lsp.hpp218
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml8
-rw-r--r--modules/gridmap/grid_map.cpp4
-rw-r--r--modules/gridmap/grid_map_editor_plugin.cpp209
-rw-r--r--modules/gridmap/grid_map_editor_plugin.h11
-rw-r--r--modules/jsonrpc/jsonrpc.cpp10
-rw-r--r--modules/mbedtls/crypto_mbedtls.cpp15
-rw-r--r--modules/mono/build_scripts/mono_configure.py2
-rw-r--r--modules/mono/class_db_api_json.cpp2
-rw-r--r--modules/mono/csharp_script.cpp6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs13
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs22
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs6
-rw-r--r--modules/mono/editor/bindings_generator.cpp80
-rw-r--r--modules/mono/editor/bindings_generator.h14
-rw-r--r--modules/mono/editor/csharp_project.cpp2
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp51
-rw-r--r--modules/mono/editor/godotsharp_export.cpp14
-rw-r--r--modules/mono/editor/godotsharp_export.h3
-rw-r--r--modules/mono/glue/Managed/Files/Plane.cs23
-rw-r--r--modules/mono/glue/Managed/Files/Rect2.cs8
-rw-r--r--modules/mono/godotsharp_dirs.cpp18
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp353
-rw-r--r--modules/mono/mono_gd/gd_mono.h68
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp2
-rw-r--r--modules/mono/utils/string_utils.cpp2
-rw-r--r--modules/opensimplex/doc_classes/NoiseTexture.xml1
-rw-r--r--modules/opus/audio_stream_opus.cpp8
-rw-r--r--modules/recast/navigation_mesh_generator.cpp24
-rw-r--r--modules/recast/navigation_mesh_generator.h2
-rw-r--r--modules/stb_vorbis/resource_importer_ogg_vorbis.cpp2
-rw-r--r--modules/theora/video_stream_theora.cpp4
-rw-r--r--modules/theora/video_stream_theora.h2
-rw-r--r--modules/visual_script/config.py2
-rw-r--r--modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml4
-rw-r--r--modules/visual_script/doc_classes/VisualScriptComposeArray.xml15
-rw-r--r--modules/visual_script/doc_classes/VisualScriptLists.xml95
-rw-r--r--modules/visual_script/register_types.cpp2
-rw-r--r--modules/visual_script/visual_script.cpp83
-rw-r--r--modules/visual_script/visual_script.h6
-rw-r--r--modules/visual_script/visual_script_editor.cpp2575
-rw-r--r--modules/visual_script/visual_script_editor.h70
-rw-r--r--modules/visual_script/visual_script_func_nodes.cpp17
-rw-r--r--modules/visual_script/visual_script_nodes.cpp455
-rw-r--r--modules/visual_script/visual_script_nodes.h97
-rw-r--r--modules/visual_script/visual_script_property_selector.cpp111
-rw-r--r--modules/visual_script/visual_script_property_selector.h18
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp9
-rw-r--r--modules/webm/video_stream_webm.cpp2
-rw-r--r--modules/webm/video_stream_webm.h2
-rw-r--r--modules/webrtc/doc_classes/WebRTCMultiplayer.xml4
-rw-r--r--modules/websocket/doc_classes/WebSocketClient.xml20
-rw-r--r--modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml4
-rw-r--r--modules/websocket/doc_classes/WebSocketServer.xml11
-rw-r--r--modules/websocket/emws_client.cpp23
-rw-r--r--modules/websocket/emws_client.h2
-rw-r--r--modules/websocket/emws_server.cpp2
-rw-r--r--modules/websocket/emws_server.h2
-rw-r--r--modules/websocket/websocket_client.cpp24
-rw-r--r--modules/websocket/websocket_client.h8
-rw-r--r--modules/websocket/websocket_server.cpp41
-rw-r--r--modules/websocket/websocket_server.h16
-rw-r--r--modules/websocket/wsl_client.cpp23
-rw-r--r--modules/websocket/wsl_client.h4
-rw-r--r--modules/websocket/wsl_peer.cpp11
-rw-r--r--modules/websocket/wsl_peer.h3
-rw-r--r--modules/websocket/wsl_server.cpp44
-rw-r--r--modules/websocket/wsl_server.h11
123 files changed, 5381 insertions, 2192 deletions
diff --git a/modules/assimp/SCsub b/modules/assimp/SCsub
index 275f1ff5e9..5e66b50de3 100644
--- a/modules/assimp/SCsub
+++ b/modules/assimp/SCsub
@@ -72,6 +72,9 @@ env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_X3D_IMPORTER'])
env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_GLTF_IMPORTER'])
env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_GLTF2_IMPORTER'])
env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_SINGLETHREADED'])
+env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_M3D_IMPORTER'])
+env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MMD_IMPORTER'])
+
if(env['platform'] == 'windows'):
env_assimp.Append(CPPDEFINES=['PLATFORM_WINDOWS'])
diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp
index e5439fd132..830e6e7e0c 100644
--- a/modules/assimp/editor_scene_importer_assimp.cpp
+++ b/modules/assimp/editor_scene_importer_assimp.cpp
@@ -29,34 +29,42 @@
/*************************************************************************/
#include "editor_scene_importer_assimp.h"
-#include "core/bind/core_bind.h"
#include "core/io/image_loader.h"
-#include "editor/editor_file_system.h"
#include "editor/import/resource_importer_scene.h"
-#include "editor_settings.h"
#include "import_utils.h"
#include "scene/3d/camera.h"
#include "scene/3d/light.h"
#include "scene/3d/mesh_instance.h"
-#include "scene/animation/animation_player.h"
#include "scene/main/node.h"
#include "scene/resources/material.h"
#include "scene/resources/surface_tool.h"
-#include <assimp/SceneCombiner.h>
-#include <assimp/cexport.h>
-#include <assimp/cimport.h>
#include <assimp/matrix4x4.h>
-#include <assimp/pbrmaterial.h>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
-#include <zutil.h>
-#include <assimp/DefaultLogger.hpp>
#include <assimp/Importer.hpp>
#include <assimp/LogStream.hpp>
-#include <assimp/Logger.hpp>
#include <string>
+// move into assimp
+aiBone *get_bone_by_name(const aiScene *scene, aiString bone_name) {
+ for (unsigned int mesh_id = 0; mesh_id < scene->mNumMeshes; ++mesh_id) {
+ aiMesh *mesh = scene->mMeshes[mesh_id];
+
+ // iterate over all the bones on the mesh for this node only!
+ for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) {
+
+ aiBone *bone = mesh->mBones[boneIndex];
+ if (bone->mName == bone_name) {
+ printf("matched bone by name: %s\n", bone->mName.C_Str());
+ return bone;
+ }
+ }
+ }
+
+ return NULL;
+}
+
void EditorSceneImporterAssimp::get_extensions(List<String> *r_extensions) const {
const String import_setting_string = "filesystem/import/open_asset_import/";
@@ -68,18 +76,15 @@ void EditorSceneImporterAssimp::get_extensions(List<String> *r_extensions) const
ImportFormat import = { exts, true };
import_format.insert("fbx", import);
}
- {
- Vector<String> exts;
- exts.push_back("pmx");
- ImportFormat import = { exts, true };
- import_format.insert("mmd", import);
- }
for (Map<String, ImportFormat>::Element *E = import_format.front(); E; E = E->next()) {
- _register_project_setting_import(E->key(), import_setting_string, E->get().extensions, r_extensions, E->get().is_default);
+ _register_project_setting_import(E->key(), import_setting_string, E->get().extensions, r_extensions,
+ E->get().is_default);
}
}
-void EditorSceneImporterAssimp::_register_project_setting_import(const String generic, const String import_setting_string, const Vector<String> &exts, List<String> *r_extensions, const bool p_enabled) const {
+void EditorSceneImporterAssimp::_register_project_setting_import(const String generic, const String import_setting_string,
+ const Vector<String> &exts, List<String> *r_extensions,
+ const bool p_enabled) const {
const String use_generic = "use_" + generic;
_GLOBAL_DEF(import_setting_string + use_generic, p_enabled, true);
if (ProjectSettings::get_singleton()->get(import_setting_string + use_generic)) {
@@ -96,7 +101,8 @@ uint32_t EditorSceneImporterAssimp::get_import_flags() const {
void EditorSceneImporterAssimp::_bind_methods() {
}
-Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) {
+Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps,
+ List<String> *r_missing_deps, Error *r_err) {
Assimp::Importer importer;
std::wstring w_path = ProjectSettings::get_singleton()->globalize_path(p_path).c_str();
std::string s_path(w_path.begin(), w_path.end());
@@ -114,9 +120,11 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f
//importer.SetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD, 1.0f);
int32_t post_process_Steps = aiProcess_CalcTangentSpace |
- aiProcess_GlobalScale | // imports models and listens to their file scale for CM to M conversions
+ aiProcess_GlobalScale |
+ // imports models and listens to their file scale for CM to M conversions
//aiProcess_FlipUVs |
- aiProcess_FlipWindingOrder | // very important for culling so that it is done in the correct order.
+ aiProcess_FlipWindingOrder |
+ // very important for culling so that it is done in the correct order.
//aiProcess_DropNormals |
//aiProcess_GenSmoothNormals |
//aiProcess_JoinIdenticalVertices |
@@ -131,8 +139,9 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f
aiProcess_TransformUVCoords |
aiProcess_FindInstances |
//aiProcess_FixInfacingNormals |
- aiProcess_ValidateDataStructure |
+ //aiProcess_ValidateDataStructure |
aiProcess_OptimizeMeshes |
+ aiProcess_PopulateArmatureData |
//aiProcess_OptimizeGraph |
//aiProcess_Debone |
// aiProcess_EmbedTextures |
@@ -157,7 +166,8 @@ struct EditorSceneImporterAssetImportInterpolate {
float t2 = t * t;
float t3 = t2 * t;
- return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
+ return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 +
+ (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
}
T bezier(T start, T control_1, T control_2, T end, float t) {
@@ -199,7 +209,8 @@ struct EditorSceneImporterAssetImportInterpolate<Quat> {
};
template <class T>
-T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp) {
+T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time,
+ AssetImportAnimation::Interpolation p_interp) {
//could use binary search, worth it?
int idx = -1;
for (int i = 0; i < p_times.size(); i++) {
@@ -271,48 +282,257 @@ T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, co
ERR_FAIL_V(p_values[0]);
}
-Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) {
+aiBone *EditorSceneImporterAssimp::get_bone_from_stack(ImportState &state, aiString name) {
+ List<aiBone *>::Element *iter;
+ aiBone *bone = NULL;
+ for (iter = state.bone_stack.front(); iter; iter = iter->next()) {
+ bone = (aiBone *)iter->get();
+
+ if (bone && bone->mName == name) {
+ state.bone_stack.erase(bone);
+ return bone;
+ }
+ }
+
+ return NULL;
+}
+
+Spatial *
+EditorSceneImporterAssimp::_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps,
+ const int32_t p_max_bone_weights) {
ERR_FAIL_COND_V(scene == NULL, NULL);
ImportState state;
state.path = p_path;
state.assimp_scene = scene;
state.max_bone_weights = p_max_bone_weights;
- state.root = memnew(Spatial);
- state.fbx = false;
state.animation_player = NULL;
- //fill light map cache
- for (size_t l = 0; l < scene->mNumLights; l++) {
+ // populate light map
+ for (unsigned int l = 0; l < scene->mNumLights; l++) {
aiLight *ai_light = scene->mLights[l];
ERR_CONTINUE(ai_light == NULL);
state.light_cache[AssimpUtils::get_assimp_string(ai_light->mName)] = l;
}
- //fill camera cache
- for (size_t c = 0; c < scene->mNumCameras; c++) {
+ // fill camera cache
+ for (unsigned int c = 0; c < scene->mNumCameras; c++) {
aiCamera *ai_camera = scene->mCameras[c];
ERR_CONTINUE(ai_camera == NULL);
state.camera_cache[AssimpUtils::get_assimp_string(ai_camera->mName)] = c;
}
if (scene->mRootNode) {
+ state.nodes.push_back(scene->mRootNode);
- //generate nodes
- for (uint32_t i = 0; i < scene->mRootNode->mNumChildren; i++) {
- _generate_node(state, NULL, scene->mRootNode->mChildren[i], state.root);
+ // make flat node tree - in order to make processing deterministic
+ for (unsigned int i = 0; i < scene->mRootNode->mNumChildren; i++) {
+ _generate_node(state, scene->mRootNode->mChildren[i]);
}
- // finalize skeleton
- for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
- Skeleton *skeleton = key_value_pair->key();
- // convert world to local for skeleton bone rests
- skeleton->localize_rests();
+ RegenerateBoneStack(state);
+
+ Node *last_valid_parent = NULL;
+
+ List<const aiNode *>::Element *iter;
+ for (iter = state.nodes.front(); iter; iter = iter->next()) {
+ const aiNode *element_assimp_node = iter->get();
+ const aiNode *parent_assimp_node = element_assimp_node->mParent;
+
+ String node_name = AssimpUtils::get_assimp_string(element_assimp_node->mName);
+ //print_verbose("node: " + node_name);
+
+ Spatial *spatial = NULL;
+ Transform transform = AssimpUtils::assimp_matrix_transform(element_assimp_node->mTransformation);
+
+ // retrieve this node bone
+ aiBone *bone = get_bone_from_stack(state, element_assimp_node->mName);
+
+ if (state.light_cache.has(node_name)) {
+ spatial = create_light(state, node_name, transform);
+ } else if (state.camera_cache.has(node_name)) {
+ spatial = create_camera(state, node_name, transform);
+ } else if (state.armature_nodes.find(element_assimp_node)) {
+ // create skeleton
+ print_verbose("Making skeleton: " + node_name);
+ Skeleton *skeleton = memnew(Skeleton);
+ spatial = skeleton;
+ if (!state.armature_skeletons.has(element_assimp_node)) {
+ state.armature_skeletons.insert(element_assimp_node, skeleton);
+ }
+ } else if (bone != NULL) {
+ continue;
+ } else if (element_assimp_node->mNumMeshes > 0) {
+ spatial = memnew(Spatial);
+ } else {
+ spatial = memnew(Spatial);
+ }
+
+ ERR_CONTINUE_MSG(spatial == NULL, "FBX Import - are we out of ram?");
+ // we on purpose set the transform and name after creating the node.
+
+ spatial->set_name(node_name);
+ spatial->set_global_transform(transform);
+
+ // first element is root
+ if (iter == state.nodes.front()) {
+ state.root = spatial;
+ }
+
+ // flat node map parent lookup tool
+ state.flat_node_map.insert(element_assimp_node, spatial);
+
+ Map<const aiNode *, Spatial *>::Element *parent_lookup = state.flat_node_map.find(parent_assimp_node);
+
+ // note: this always fails on the root node :) keep that in mind this is by design
+ if (parent_lookup) {
+ Spatial *parent_node = parent_lookup->value();
+
+ ERR_FAIL_COND_V_MSG(parent_node == NULL, state.root,
+ "Parent node invalid even though lookup successful, out of ram?")
+
+ if (parent_node && spatial != state.root) {
+ parent_node->add_child(spatial);
+ spatial->set_owner(state.root);
+ } else if (spatial == state.root) {
+ // required - think about it root never has a parent yet is valid, anything else without a parent is not valid.
+ } else // Safety for instances
+ {
+ WARN_PRINT(
+ "Failed to find parent node instance after lookup, serious warning report to godot with model");
+ memdelete(spatial); // this node is broken
+ }
+ } else if (spatial != state.root) {
+ // if the ainode is not in the tree
+ // parent it to the last good parent found
+ if (last_valid_parent) {
+ last_valid_parent->add_child(spatial);
+ spatial->set_owner(state.root);
+ } else {
+ // this is a serious error?
+ memdelete(spatial);
+ }
+ }
+
+ // update last valid parent
+ last_valid_parent = spatial;
+ }
+ print_verbose("node counts: " + itos(state.nodes.size()));
+
+ // make clean bone stack
+ RegenerateBoneStack(state);
+
+ print_verbose("generating godot bone data");
+
+ print_verbose("Godot bone stack count: " + itos(state.bone_stack.size()));
+
+ // This is a list of bones, duplicates are from other meshes and must be dealt with properly
+ for (List<aiBone *>::Element *element = state.bone_stack.front(); element; element = element->next()) {
+ aiBone *bone = element->get();
+
+ ERR_CONTINUE_MSG(!bone, "invalid bone read from assimp?");
+
+ // Utilities for armature lookup - for now only FBX makes these
+ aiNode *armature_for_bone = bone->mArmature;
+
+ // Utilities for bone node lookup - for now only FBX makes these
+ aiNode *bone_node = bone->mNode;
+ aiNode *parent_node = bone_node->mParent;
+
+ String bone_name = AssimpUtils::get_anim_string_from_assimp(bone->mName);
+ ERR_CONTINUE_MSG(armature_for_bone == NULL, "Armature for bone invalid: " + bone_name);
+ Skeleton *skeleton = state.armature_skeletons[armature_for_bone];
+
+ state.skeleton_bone_map[bone] = skeleton;
+
+ if (bone_name.empty()) {
+ bone_name = "untitled_bone_name";
+ WARN_PRINT("Untitled bone name detected... report with file please");
+ }
+
+ // todo: this is where skin support goes
+ if (skeleton && skeleton->find_bone(bone_name) == -1) {
+ print_verbose("[Godot Glue] Imported bone" + bone_name);
+ int boneIdx = skeleton->get_bone_count();
+
+ Transform pform = AssimpUtils::assimp_matrix_transform(bone->mNode->mTransformation);
+ skeleton->add_bone(bone_name);
+ skeleton->set_bone_rest(boneIdx, pform);
+ skeleton->set_bone_pose(boneIdx, pform);
+
+ if (parent_node != NULL) {
+ int parent_bone_id = skeleton->find_bone(AssimpUtils::get_anim_string_from_assimp(parent_node->mName));
+ int current_bone_id = boneIdx;
+ skeleton->set_bone_parent(current_bone_id, parent_bone_id);
+ }
+ }
}
print_verbose("generating mesh phase from skeletal mesh");
- generate_mesh_phase_from_skeletal_mesh(state);
+
+ List<Spatial *> cleanup_template_nodes;
+
+ for (Map<const aiNode *, Spatial *>::Element *key_value_pair = state.flat_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
+ const aiNode *assimp_node = key_value_pair->key();
+ Spatial *mesh_template = key_value_pair->value();
+ Node *parent_node = mesh_template->get_parent();
+
+ ERR_CONTINUE(assimp_node == NULL);
+ ERR_CONTINUE(mesh_template == NULL);
+
+ if (mesh_template == state.root) {
+ continue;
+ }
+
+ if (parent_node == NULL) {
+ print_error("Found invalid parent node!");
+ continue; // root node
+ }
+
+ String node_name = AssimpUtils::get_assimp_string(assimp_node->mName);
+ Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation);
+
+ if (assimp_node->mNumMeshes > 0) {
+ MeshInstance *mesh = create_mesh(state, assimp_node, node_name, parent_node, node_transform);
+ if (mesh) {
+
+ parent_node->remove_child(mesh_template);
+
+ // re-parent children
+ List<Node *> children;
+ // re-parent all children to new node
+ // note: since get_child_count will change during execution we must build a list first to be safe.
+ for (int childId = 0; childId < mesh_template->get_child_count(); childId++) {
+ // get child
+ Node *child = mesh_template->get_child(childId);
+ children.push_back(child);
+ }
+
+ for (List<Node *>::Element *element = children.front(); element; element = element->next()) {
+ // reparent the children to the real mesh node.
+ mesh_template->remove_child(element->get());
+ mesh->add_child(element->get());
+ element->get()->set_owner(state.root);
+ }
+
+ // update mesh in list so that each mesh node is available
+ // this makes the template unavailable which is the desired behaviour
+ state.flat_node_map[assimp_node] = mesh;
+
+ cleanup_template_nodes.push_back(mesh_template);
+
+ // clean up this list we don't need it
+ children.clear();
+ }
+ }
+ }
+
+ for (List<Spatial *>::Element *element = cleanup_template_nodes.front(); element; element = element->next()) {
+ if (element->get()) {
+ memdelete(element->get());
+ }
+ }
}
if (p_flags & IMPORT_ANIMATION && scene->mNumAnimations) {
@@ -326,29 +546,39 @@ Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, aiScen
}
}
+ //
+ // Cleanup operations
+ //
+
+ state.mesh_cache.clear();
+ state.material_cache.clear();
+ state.light_cache.clear();
+ state.camera_cache.clear();
+ state.assimp_node_map.clear();
+ state.path_to_image_cache.clear();
+ state.nodes.clear();
+ state.flat_node_map.clear();
+ state.armature_skeletons.clear();
+ state.bone_stack.clear();
return state.root;
}
-void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int p_track, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, Skeleton *p_skeleton, const NodePath &p_path, const String &p_name) {
-
- const aiNodeAnim *assimp_track = assimp_anim->mChannels[p_track];
+void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int track_id,
+ int anim_fps, Ref<Animation> animation, float ticks_per_second,
+ Skeleton *skeleton, const NodePath &node_path,
+ const String &node_name, aiBone *track_bone) {
+ const aiNodeAnim *assimp_track = assimp_anim->mChannels[track_id];
//make transform track
int track_idx = animation->get_track_count();
animation->add_track(Animation::TYPE_TRANSFORM);
- animation->track_set_path(track_idx, p_path);
+ animation->track_set_path(track_idx, node_path);
//first determine animation length
- float increment = 1.0 / float(p_bake_fps);
+ float increment = 1.0 / float(anim_fps);
float time = 0.0;
bool last = false;
- int skeleton_bone = -1;
-
- if (p_skeleton) {
- skeleton_bone = p_skeleton->find_bone(p_name);
- }
-
Vector<Vector3> pos_values;
Vector<float> pos_times;
Vector<Vector3> scale_values;
@@ -373,6 +603,7 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons
scale_values.push_back(Vector3(scale.x, scale.y, scale.z));
scale_times.push_back(assimp_track->mScalingKeys[sc].mTime / ticks_per_second);
}
+
while (true) {
Vector3 pos;
Quat rot;
@@ -383,26 +614,34 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons
}
if (rot_values.size()) {
- rot = _interpolate_track<Quat>(rot_times, rot_values, time, AssetImportAnimation::INTERP_LINEAR).normalized();
+ rot = _interpolate_track<Quat>(rot_times, rot_values, time,
+ AssetImportAnimation::INTERP_LINEAR)
+ .normalized();
}
if (scale_values.size()) {
scale = _interpolate_track<Vector3>(scale_times, scale_values, time, AssetImportAnimation::INTERP_LINEAR);
}
- if (skeleton_bone >= 0) {
- Transform xform;
- xform.basis.set_quat_scale(rot, scale);
- xform.origin = pos;
+ if (skeleton) {
+ int skeleton_bone = skeleton->find_bone(node_name);
- Transform rest_xform = p_skeleton->get_bone_rest(skeleton_bone);
- xform = rest_xform.affine_inverse() * xform;
- rot = xform.basis.get_rotation_quat();
- scale = xform.basis.get_scale();
- pos = xform.origin;
- }
+ if (skeleton_bone >= 0 && track_bone) {
+
+ Transform xform;
+ xform.basis.set_quat_scale(rot, scale);
+ xform.origin = pos;
- rot.normalize();
+ xform = skeleton->get_bone_pose(skeleton_bone).inverse() * xform;
+
+ rot = xform.basis.get_rotation_quat();
+ rot.normalize();
+ scale = xform.basis.get_scale();
+ pos = xform.origin;
+ } else {
+ ERR_FAIL_MSG("Skeleton bone lookup failed for skeleton: " + skeleton->get_name());
+ }
+ }
animation->track_set_interpolation_type(track_idx, Animation::INTERPOLATION_LINEAR);
animation->transform_track_insert_key(track_idx, time, pos, rot, scale);
@@ -417,6 +656,53 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons
}
}
+// I really do not like this but need to figure out a better way of removing it later.
+Node *EditorSceneImporterAssimp::get_node_by_name(ImportState &state, String name) {
+ for (Map<const aiNode *, Spatial *>::Element *key_value_pair = state.flat_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
+ const aiNode *assimp_node = key_value_pair->key();
+ Spatial *node = key_value_pair->value();
+
+ String node_name = AssimpUtils::get_assimp_string(assimp_node->mName);
+ if (name == node_name && node) {
+ return node;
+ }
+ }
+ return NULL;
+}
+
+/* Bone stack is a fifo handler for multiple armatures since armatures aren't a thing in assimp (yet) */
+void EditorSceneImporterAssimp::RegenerateBoneStack(ImportState &state) {
+
+ state.bone_stack.clear();
+ // build bone stack list
+ for (unsigned int mesh_id = 0; mesh_id < state.assimp_scene->mNumMeshes; ++mesh_id) {
+ aiMesh *mesh = state.assimp_scene->mMeshes[mesh_id];
+
+ // iterate over all the bones on the mesh for this node only!
+ for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) {
+ aiBone *bone = mesh->mBones[boneIndex];
+
+ // doubtful this is required right now but best to check
+ if (!state.bone_stack.find(bone)) {
+ //print_verbose("[assimp] bone stack added: " + String(bone->mName.C_Str()) );
+ state.bone_stack.push_back(bone);
+ }
+ }
+ }
+}
+
+/* Bone stack is a fifo handler for multiple armatures since armatures aren't a thing in assimp (yet) */
+void EditorSceneImporterAssimp::RegenerateBoneStack(ImportState &state, aiMesh *mesh) {
+ state.bone_stack.clear();
+ // iterate over all the bones on the mesh for this node only!
+ for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) {
+ aiBone *bone = mesh->mBones[boneIndex];
+ if (state.bone_stack.find(bone) == NULL) {
+ state.bone_stack.push_back(bone);
+ }
+ }
+}
+
// animation tracks are per bone
void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_animation_index, int p_bake_fps) {
@@ -428,7 +714,7 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim
if (name == String()) {
name = "Animation " + itos(p_animation_index + 1);
}
-
+ print_verbose("import animation: " + name);
float ticks_per_second = anim->mTicksPerSecond;
if (state.assimp_scene->mMetaData != NULL && Math::is_equal_approx(ticks_per_second, 0.0f)) {
@@ -451,34 +737,60 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim
animation->set_name(name);
animation->set_length(anim->mDuration / ticks_per_second);
- //regular tracks
+ // generate bone stack for animation import
+ RegenerateBoneStack(state);
+ //regular tracks
for (size_t i = 0; i < anim->mNumChannels; i++) {
const aiNodeAnim *track = anim->mChannels[i];
String node_name = AssimpUtils::get_assimp_string(track->mNodeName);
-
+ print_verbose("track name import: " + node_name);
if (track->mNumRotationKeys == 0 && track->mNumPositionKeys == 0 && track->mNumScalingKeys == 0) {
continue; //do not bother
}
- for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
- Skeleton *skeleton = key_value_pair->key();
-
- bool is_bone = skeleton->find_bone(node_name) != -1;
- //print_verbose("Bone " + node_name + " is bone? " + (is_bone ? "Yes" : "No"));
- NodePath node_path;
+ Skeleton *skeleton = NULL;
+ NodePath node_path;
+ aiBone *bone = NULL;
- if (is_bone) {
- String path = state.root->get_path_to(skeleton);
- path += ":" + node_name;
- node_path = path;
- } else {
- ERR_CONTINUE(!state.node_map.has(node_name));
- Node *node = state.node_map[node_name];
- node_path = state.root->get_path_to(node);
+ // Import skeleton bone animation for this track
+ // Any bone will do, no point in processing more than just what is in the skeleton
+ {
+ bone = get_bone_from_stack(state, track->mNodeName);
+
+ if (bone) {
+ // get skeleton by bone
+ skeleton = state.armature_skeletons[bone->mArmature];
+
+ if (skeleton) {
+ String path = state.root->get_path_to(skeleton);
+ path += ":" + node_name;
+ node_path = path;
+
+ if (node_path != NodePath()) {
+ _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton,
+ node_path, node_name, bone);
+ } else {
+ print_error("Failed to find valid node path for animation");
+ }
+ }
}
+ }
- _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name);
+ // not a bone
+ // note this is flaky it uses node names which is unreliable
+ Node *allocated_node = get_node_by_name(state, node_name);
+ // todo: implement skeleton grabbing for node based animations too :)
+ // check if node exists, if it does then also apply animation track for node and bones above are all handled.
+ // this is now inclusive animation handling so that
+ // we import all the data and do not miss anything.
+ if (allocated_node) {
+ node_path = state.root->get_path_to(allocated_node);
+
+ if (node_path != NodePath()) {
+ _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton,
+ node_path, node_name, nullptr);
+ }
}
}
@@ -493,10 +805,9 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim
ERR_CONTINUE(prop_name.split("*").size() != 2);
- ERR_CONTINUE(!state.node_map.has(mesh_name));
-
- const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(state.node_map[mesh_name]);
-
+ Node *item = get_node_by_name(state, mesh_name);
+ ERR_CONTINUE_MSG(!item, "failed to look up node by name");
+ const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(item);
ERR_CONTINUE(mesh_instance == NULL);
String base_path = state.root->get_path_to(mesh_instance);
@@ -527,20 +838,35 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim
state.animation_player->add_animation(name, animation);
}
}
-
//
-// Mesh Generation from indicies ? why do we need so much mesh code
+// Mesh Generation from indices ? why do we need so much mesh code
// [debt needs looked into]
-Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
- ImportState &state,
- const Vector<int> &p_surface_indices,
- const aiNode *assimp_node,
- Skeleton *p_skeleton) {
+Ref<Mesh>
+EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices,
+ const aiNode *assimp_node, Ref<Skin> &skin,
+ Skeleton *&skeleton_assigned) {
Ref<ArrayMesh> mesh;
mesh.instance();
bool has_uvs = false;
+ Map<String, uint32_t> morph_mesh_string_lookup;
+
+ for (int i = 0; i < p_surface_indices.size(); i++) {
+ const unsigned int mesh_idx = p_surface_indices[0];
+ const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_idx];
+ for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) {
+ String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName);
+ if (!morph_mesh_string_lookup.has(ai_anim_mesh_name)) {
+ morph_mesh_string_lookup.insert(ai_anim_mesh_name, j);
+ mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
+ if (ai_anim_mesh_name.empty()) {
+ ai_anim_mesh_name = String("morph_") + itos(j);
+ }
+ mesh->add_blend_shape(ai_anim_mesh_name);
+ }
+ }
+ }
//
// Process Vertex Weights
//
@@ -550,19 +876,30 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
Map<uint32_t, Vector<BoneInfo> > vertex_weights;
- if (p_skeleton) {
+ if (ai_mesh->mNumBones > 0) {
for (size_t b = 0; b < ai_mesh->mNumBones; b++) {
aiBone *bone = ai_mesh->mBones[b];
- String bone_name = AssimpUtils::get_assimp_string(bone->mName);
- int bone_index = p_skeleton->find_bone(bone_name);
- ERR_CONTINUE(bone_index == -1); //bone refers to an unexisting index, wtf.
+ if (!skeleton_assigned) {
+ print_verbose("Assigned mesh skeleton during mesh creation");
+ skeleton_assigned = state.skeleton_bone_map[bone];
+
+ if (!skin.is_valid()) {
+ print_verbose("Configured new skin");
+ skin.instance();
+ } else {
+ print_verbose("Reusing existing skin!");
+ }
+ }
+ // skeleton_assigned =
+ String bone_name = AssimpUtils::get_assimp_string(bone->mName);
+ int bone_index = skeleton_assigned->find_bone(bone_name);
+ ERR_CONTINUE(bone_index == -1);
for (size_t w = 0; w < bone->mNumWeights; w++) {
aiVertexWeight ai_weights = bone->mWeights[w];
BoneInfo bi;
-
uint32_t vertex_index = ai_weights.mVertexId;
bi.bone = bone_index;
bi.weight = ai_weights.mWeight;
@@ -599,7 +936,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
// Assign vertex colors
if (ai_mesh->HasVertexColors(0)) {
- Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b, ai_mesh->mColors[0]->a);
+ Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b,
+ ai_mesh->mColors[0]->a);
st->add_color(color);
}
@@ -665,6 +1003,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) {
if (mat_two_sided > 0) {
mat->set_cull_mode(SpatialMaterial::CULL_DISABLED);
+ } else {
+ mat->set_cull_mode(SpatialMaterial::CULL_BACK);
}
}
@@ -677,9 +1017,29 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
// Culling handling for meshes
// cull all back faces
- mat->set_cull_mode(SpatialMaterial::CULL_BACK);
+ mat->set_cull_mode(SpatialMaterial::CULL_DISABLED);
// Now process materials
+ aiTextureType base_color = aiTextureType_BASE_COLOR;
+ {
+ String filename, path;
+ AssimpImageData image_data;
+
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, base_color, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+
+ // anything transparent must be culled
+ if (image_data.raw_image->detect_alpha() != Image::ALPHA_NONE) {
+ mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+ mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+ mat->set_cull_mode(
+ SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode
+ }
+
+ mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, image_data.texture);
+ }
+ }
+
aiTextureType tex_diffuse = aiTextureType_DIFFUSE;
{
String filename, path;
@@ -692,7 +1052,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
if (image_data.raw_image->detect_alpha() != Image::ALPHA_NONE) {
mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode
+ mat->set_cull_mode(
+ SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode
}
mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, image_data.texture);
@@ -703,7 +1064,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) {
mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode
+ mat->set_cull_mode(
+ SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode
}
mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a));
}
@@ -731,6 +1093,60 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
}
}
+ aiTextureType tex_normal_camera = aiTextureType_NORMAL_CAMERA;
+ {
+ String filename, path;
+ Ref<ImageTexture> texture;
+ AssimpImageData image_data;
+
+ // Process texture normal map
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_normal_camera, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+ mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
+ mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture);
+ }
+ }
+
+ aiTextureType tex_emission_color = aiTextureType_EMISSION_COLOR;
+ {
+ String filename, path;
+ Ref<ImageTexture> texture;
+ AssimpImageData image_data;
+
+ // Process texture normal map
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_emission_color, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+ mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
+ mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture);
+ }
+ }
+
+ aiTextureType tex_metalness = aiTextureType_METALNESS;
+ {
+ String filename, path;
+ Ref<ImageTexture> texture;
+ AssimpImageData image_data;
+
+ // Process texture normal map
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_metalness, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+ mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, image_data.texture);
+ }
+ }
+
+ aiTextureType tex_roughness = aiTextureType_DIFFUSE_ROUGHNESS;
+ {
+ String filename, path;
+ Ref<ImageTexture> texture;
+ AssimpImageData image_data;
+
+ // Process texture normal map
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_roughness, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+ mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, image_data.texture);
+ }
+ }
+
aiTextureType tex_emissive = aiTextureType_EMISSIVE;
{
String filename = "";
@@ -745,7 +1161,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
} else {
// Process emission textures
aiString texture_emissive_path;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE, AI_PROPERTIES, texture_emissive_path)) {
+ if (AI_SUCCESS ==
+ ai_material->Get(AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE, AI_PROPERTIES, texture_emissive_path)) {
if (AssimpUtils::CreateAssimpTexture(state, texture_emissive_path, filename, path, image_data)) {
mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true);
mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, image_data.texture);
@@ -772,16 +1189,17 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
}
}
- aiTextureType tex_roughness = aiTextureType_SHININESS;
+ aiTextureType tex_ao_map = aiTextureType_AMBIENT_OCCLUSION;
{
String filename, path;
Ref<ImageTexture> texture;
AssimpImageData image_data;
// Process texture normal map
- if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_roughness, filename, path, image_data)) {
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_ao_map, filename, path, image_data)) {
AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
- mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, image_data.texture);
+ mat->set_feature(SpatialMaterial::FEATURE_AMBIENT_OCCLUSION, true);
+ mat->set_texture(SpatialMaterial::TEXTURE_AMBIENT_OCCLUSION, image_data.texture);
}
}
@@ -789,16 +1207,15 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
Array morphs;
morphs.resize(ai_mesh->mNumAnimMeshes);
Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES;
- Map<uint32_t, String> morph_mesh_idx_names;
+
for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) {
String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName);
- mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
+
if (ai_anim_mesh_name.empty()) {
ai_anim_mesh_name = String("morph_") + itos(j);
}
- mesh->add_blend_shape(ai_anim_mesh_name);
- morph_mesh_idx_names.insert(j, ai_anim_mesh_name);
+
Array array_copy;
array_copy.resize(VisualServer::ARRAY_MAX);
@@ -888,62 +1305,27 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
return mesh;
}
-/* to be moved into assimp */
-aiBone *get_bone_by_name(const aiScene *scene, aiString bone_name) {
- for (unsigned int mesh_id = 0; mesh_id < scene->mNumMeshes; ++mesh_id) {
- aiMesh *mesh = scene->mMeshes[mesh_id];
-
- // iterate over all the bones on the mesh for this node only!
- for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) {
-
- aiBone *bone = mesh->mBones[boneIndex];
- if (bone->mName == bone_name) {
- printf("matched bone by name: %s\n", bone->mName.C_Str());
- return bone;
- }
- }
- }
-
- return NULL;
-}
-
/**
* Create a new mesh for the node supplied
*/
-void EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform) {
+MeshInstance *
+EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *active_node, Transform node_transform) {
/* MESH NODE */
Ref<Mesh> mesh;
- Skeleton *skeleton = NULL;
+ Ref<Skin> skin;
// see if we have mesh cache for this.
Vector<int> surface_indices;
- for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) {
- int mesh_index = assimp_node->mMeshes[i];
- aiMesh *ai_mesh = state.assimp_scene->mMeshes[assimp_node->mMeshes[i]];
-
- // Map<aiBone*, Skeleton*> // this is what we need
- if (ai_mesh->mNumBones > 0) {
- // we only need the first bone to retrieve the skeleton
- const aiBone *first = ai_mesh->mBones[0];
- ERR_FAIL_COND(first == NULL);
+ RegenerateBoneStack(state);
- Map<const aiBone *, Skeleton *>::Element *match = state.bone_to_skeleton_lookup.find(first);
- if (match != NULL) {
- skeleton = match->value();
-
- if (skeleton == NULL) {
- print_error("failed to find bone skeleton for bone: " + AssimpUtils::get_assimp_string(first->mName));
- } else {
- print_verbose("successfully found skeleton for first bone on mesh, can properly handle animations now!");
- }
- // I really need the skeleton and bone to be known as this is something flaky in model exporters.
- ERR_FAIL_COND(skeleton == NULL); // should not happen if bone was successfully created in previous step.
- }
- }
+ // Configure indicies
+ for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) {
+ int mesh_index = assimp_node->mMeshes[i];
+ // create list of mesh indexes
surface_indices.push_back(mesh_index);
}
- surface_indices.sort();
+ //surface_indices.sort();
String mesh_key;
for (int i = 0; i < surface_indices.size(); i++) {
if (i > 0) {
@@ -952,263 +1334,154 @@ void EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *as
mesh_key += itos(surface_indices[i]);
}
+ Skeleton *skeleton = NULL;
+ aiNode *armature = NULL;
+
if (!state.mesh_cache.has(mesh_key)) {
- mesh = _generate_mesh_from_surface_indices(state, surface_indices, assimp_node, skeleton);
+ mesh = _generate_mesh_from_surface_indices(state, surface_indices, assimp_node, skin, skeleton);
state.mesh_cache[mesh_key] = mesh;
}
- //Transform transform = recursive_state.node_transform;
-
- // we must unfortunately overwrite mesh and skeleton transform with armature data
- if (skeleton != NULL) {
- print_verbose("Applying mesh and skeleton to armature");
- // required for blender, maya etc
- Map<Skeleton *, const Spatial *>::Element *match = state.armature_skeletons.find(skeleton);
- node_transform = match->value()->get_transform();
- }
-
MeshInstance *mesh_node = memnew(MeshInstance);
mesh = state.mesh_cache[mesh_key];
mesh_node->set_mesh(mesh);
- attach_new_node(state,
- mesh_node,
- assimp_node,
- parent_node,
- node_name,
- node_transform);
-
- // set this once and for all
- if (skeleton != NULL) {
- // root must be informed of its new child
- parent_node->add_child(skeleton);
+ // if we have a valid skeleton set it up
+ if (skin.is_valid()) {
+ for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) {
+ unsigned int mesh_index = assimp_node->mMeshes[i];
+ const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_index];
+
+ // please remember bone id relative to the skin is NOT the mesh relative index.
+ // it is the index relative to the skeleton that is why
+ // we have state.bone_id_map, it allows for duplicate bone id's too :)
+ // hope this makes sense
+
+ int bind_count = 0;
+ for (unsigned int boneId = 0; boneId < ai_mesh->mNumBones; ++boneId) {
+ aiBone *iterBone = ai_mesh->mBones[boneId];
+
+ // used to reparent mesh to the correct armature later on if assigned.
+ if (!armature) {
+ print_verbose("Configured mesh armature, will reparent later to armature");
+ armature = iterBone->mArmature;
+ }
- // owner must be set after adding to tree
- skeleton->set_owner(state.root);
+ if (skeleton) {
+ int id = skeleton->find_bone(AssimpUtils::get_assimp_string(iterBone->mName));
+ if (id != -1) {
+ print_verbose("Set bind bone: mesh: " + itos(mesh_index) + " bone index: " + itos(id));
+ Transform t = AssimpUtils::assimp_matrix_transform(iterBone->mOffsetMatrix);
- skeleton->set_transform(node_transform);
+ skin->add_bind(bind_count, t);
+ skin->set_bind_bone(bind_count, id);
+ bind_count++;
+ }
+ }
+ }
+ }
- // must be done after added to tree
- mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton));
+ print_verbose("Finished configuring bind pose for skin mesh");
}
-}
-
-/** generate_mesh_phase_from_skeletal_mesh
- * This must be executed after generate_nodes because the skeleton doesn't exist until that has completed the first pass
- */
-void EditorSceneImporterAssimp::generate_mesh_phase_from_skeletal_mesh(ImportState &state) {
- // prevent more than one skeleton existing per mesh
- // * multiple root bones have this
- // * this simply filters the node out if it has already been added then references the skeleton so we know the actual skeleton for this node
- for (Map<const aiNode *, const Node *>::Element *key_value_pair = state.assimp_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
- const aiNode *assimp_node = key_value_pair->key();
- Node *current_node = (Node *)key_value_pair->value();
- Node *parent_node = current_node->get_parent();
-
- ERR_CONTINUE(assimp_node == NULL);
- ERR_CONTINUE(parent_node == NULL);
- String node_name = AssimpUtils::get_assimp_string(assimp_node->mName);
- Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation);
+ // this code parents all meshes with bones to the armature they are for
+ // GLTF2 specification relies on this and we are enforcing it for FBX.
+ if (armature && state.flat_node_map[armature]) {
+ Node *armature_parent = state.flat_node_map[armature];
+ print_verbose("Parented mesh " + node_name + " to armature " + armature_parent->get_name());
+ // static mesh handling
+ armature_parent->add_child(mesh_node);
+ // transform must be identity
+ mesh_node->set_global_transform(Transform());
+ mesh_node->set_name(node_name);
+ mesh_node->set_owner(state.root);
+ } else {
+ // static mesh handling
+ active_node->add_child(mesh_node);
+ mesh_node->set_global_transform(node_transform);
+ mesh_node->set_name(node_name);
+ mesh_node->set_owner(state.root);
+ }
- if (assimp_node->mNumMeshes > 0) {
- create_mesh(state, assimp_node, node_name, current_node, parent_node, node_transform);
- }
+ if (skeleton) {
+ print_verbose("Attempted to set skeleton path!");
+ mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton));
+ mesh_node->set_skin(skin);
}
-}
-/**
- * attach_new_node
- * configures node, assigns parent node
-**/
-void EditorSceneImporterAssimp::attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform) {
- ERR_FAIL_COND(new_node == NULL);
- ERR_FAIL_COND(node == NULL);
- ERR_FAIL_COND(parent_node == NULL);
- ERR_FAIL_COND(state.root == NULL);
-
- // assign properties to new godot note
- new_node->set_name(Name);
- new_node->set_transform(transform);
-
- // add element as child to parent
- parent_node->add_child(new_node);
-
- // owner must be set after
- new_node->set_owner(state.root);
-
- // cache node mapping results by name and then by aiNode*
- state.node_map[Name] = new_node;
- state.assimp_node_map[node] = new_node;
+ return mesh_node;
}
/**
* Create a light for the scene
* Automatically caches lights for lookup later
*/
-void EditorSceneImporterAssimp::create_light(ImportState &state, RecursiveState &recursive_state) {
+Spatial *EditorSceneImporterAssimp::create_light(
+ ImportState &state,
+ const String &node_name,
+ Transform &look_at_transform) {
Light *light = NULL;
- aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[recursive_state.node_name]];
- ERR_FAIL_COND(!ai_light);
+ aiLight *assimp_light = state.assimp_scene->mLights[state.light_cache[node_name]];
+ ERR_FAIL_COND_V(!assimp_light, NULL);
- if (ai_light->mType == aiLightSource_DIRECTIONAL) {
+ if (assimp_light->mType == aiLightSource_DIRECTIONAL) {
light = memnew(DirectionalLight);
- Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z);
- dir.normalize();
- Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
- Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z);
- up.normalize();
-
- Transform light_transform;
- light_transform.set_look_at(pos, pos + dir, up);
-
- recursive_state.node_transform *= light_transform;
-
- } else if (ai_light->mType == aiLightSource_POINT) {
+ } else if (assimp_light->mType == aiLightSource_POINT) {
light = memnew(OmniLight);
- Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
- Transform xform;
- xform.origin = pos;
-
- recursive_state.node_transform *= xform;
-
- light->set_transform(xform);
-
- //light->set_param(Light::PARAM_ATTENUATION, 1);
- } else if (ai_light->mType == aiLightSource_SPOT) {
+ } else if (assimp_light->mType == aiLightSource_SPOT) {
light = memnew(SpotLight);
-
- Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z);
- dir.normalize();
- Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
- Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z);
- up.normalize();
-
- Transform light_transform;
- light_transform.set_look_at(pos, pos + dir, up);
- recursive_state.node_transform *= light_transform;
-
- //light->set_param(Light::PARAM_ATTENUATION, 0.0f);
}
- ERR_FAIL_COND(light == NULL);
+ ERR_FAIL_COND_V(light == NULL, NULL);
+
+ if (assimp_light->mType != aiLightSource_POINT) {
+ Vector3 pos = Vector3(
+ assimp_light->mPosition.x,
+ assimp_light->mPosition.y,
+ assimp_light->mPosition.z);
+ Vector3 look_at = Vector3(
+ assimp_light->mDirection.y,
+ assimp_light->mDirection.x,
+ assimp_light->mDirection.z)
+ .normalized();
+ Vector3 up = Vector3(
+ assimp_light->mUp.x,
+ assimp_light->mUp.y,
+ assimp_light->mUp.z);
+
+ look_at_transform.set_look_at(pos, look_at, up);
+ }
+ // properties for light variables should be put here.
+ // not really hugely important yet but we will need them in the future
- light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b));
- recursive_state.new_node = light;
+ light->set_color(
+ Color(assimp_light->mColorDiffuse.r, assimp_light->mColorDiffuse.g, assimp_light->mColorDiffuse.b));
- attach_new_node(state,
- recursive_state.new_node,
- recursive_state.assimp_node,
- recursive_state.parent_node,
- recursive_state.node_name,
- recursive_state.node_transform);
+ return light;
}
/**
* Create camera for the scene
*/
-void EditorSceneImporterAssimp::create_camera(ImportState &state, RecursiveState &recursive_state) {
- aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[recursive_state.node_name]];
- ERR_FAIL_COND(!ai_camera);
-
- Camera *camera = memnew(Camera);
-
- float near = ai_camera->mClipPlaneNear;
+Spatial *EditorSceneImporterAssimp::create_camera(
+ ImportState &state,
+ const String &node_name,
+ Transform &look_at_transform) {
+ aiCamera *camera = state.assimp_scene->mCameras[state.camera_cache[node_name]];
+ ERR_FAIL_COND_V(!camera, NULL);
+
+ Camera *camera_node = memnew(Camera);
+ ERR_FAIL_COND_V(!camera_node, NULL);
+ float near = camera->mClipPlaneNear;
if (Math::is_equal_approx(near, 0.0f)) {
near = 0.1f;
}
- camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar);
-
- Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z);
- Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized();
- Vector3 up = Vector3(ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z);
-
- Transform xform;
- xform.set_look_at(pos, look_at, up);
-
- recursive_state.new_node = camera;
-
- attach_new_node(state,
- recursive_state.new_node,
- recursive_state.assimp_node,
- recursive_state.parent_node,
- recursive_state.node_name,
- recursive_state.node_transform);
-}
-
-/**
- * Create Bone
- * Create a bone in the scene
- */
-void EditorSceneImporterAssimp::create_bone(ImportState &state, RecursiveState &recursive_state) {
- // for each armature node we must make a new skeleton but ensure it
- // has a bone in the child to ensure we don't make too many
- // the reason you must do this is because a skeleton exists per mesh?
- // and duplicate bone names are very bad for determining what is going on.
- aiBone *parent_bone_assimp = get_bone_by_name(state.assimp_scene, recursive_state.assimp_node->mParent->mName);
-
- // set to true when you want to use skeleton reference from cache.
- bool do_not_create_armature = false;
-
- // prevent more than one skeleton existing per mesh
- // * multiple root bones have this
- // * this simply filters the node out if it has already been added then references the skeleton so we know the actual skeleton for this node
- for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
- if (key_value_pair->value() == recursive_state.parent_node) {
- // apply the skeleton for this mesh
- recursive_state.skeleton = key_value_pair->key();
-
- // force this off
- do_not_create_armature = true;
- }
- }
-
- // check if parent was a bone
- // if parent was not a bone this is the first bone.
- // therefore parent is the 'armature'?
- // also for multi root bone support make sure we don't already have the skeleton cached.
- // if we do we must merge them - as this is all godot supports right now.
- if (!parent_bone_assimp && recursive_state.skeleton == NULL && !do_not_create_armature) {
- // create new skeleton on the root.
- recursive_state.skeleton = memnew(Skeleton);
-
- ERR_FAIL_COND(state.root == NULL);
- ERR_FAIL_COND(recursive_state.skeleton == NULL);
-
- print_verbose("Parent armature node is called " + recursive_state.parent_node->get_name());
- // store root node for this skeleton / used in animation playback and bone detection.
-
- state.armature_skeletons.insert(recursive_state.skeleton, Object::cast_to<Spatial>(recursive_state.parent_node));
-
- //skeleton->set_use_bones_in_world_transform(true);
- print_verbose("Created new FBX skeleton for armature node");
- }
-
- ERR_FAIL_COND_MSG(recursive_state.skeleton == NULL, "Mesh has invalid armature detection - report this");
-
- // this transform is a bone
- recursive_state.skeleton->add_bone(recursive_state.node_name);
+ camera_node->set_perspective(Math::rad2deg(camera->mHorizontalFOV) * 2.0f, near, camera->mClipPlaneFar);
+ Vector3 pos = Vector3(camera->mPosition.x, camera->mPosition.y, camera->mPosition.z);
+ Vector3 look_at = Vector3(camera->mLookAt.y, camera->mLookAt.x, camera->mLookAt.z).normalized();
+ Vector3 up = Vector3(camera->mUp.x, camera->mUp.y, camera->mUp.z);
- ERR_FAIL_COND(recursive_state.skeleton == NULL); // serious bug we must now exit.
- //ERR_FAIL_COND(recursive_state.skeleton->get_name() == "");
- print_verbose("Bone added to lookup: " + AssimpUtils::get_assimp_string(recursive_state.bone->mName));
- print_verbose("Skeleton attached to: " + recursive_state.skeleton->get_name());
- // make sure to write the bone lookup inverse so we can retrieve the mesh for this bone later
- state.bone_to_skeleton_lookup.insert(recursive_state.bone, recursive_state.skeleton);
-
- Transform xform = AssimpUtils::assimp_matrix_transform(recursive_state.bone->mOffsetMatrix);
- recursive_state.skeleton->set_bone_rest(recursive_state.skeleton->get_bone_count() - 1, xform.affine_inverse());
-
- // get parent node of assimp node
- const aiNode *parent_node_assimp = recursive_state.assimp_node->mParent;
-
- // ensure we have a parent
- if (parent_node_assimp != NULL) {
- int parent_bone_id = recursive_state.skeleton->find_bone(AssimpUtils::get_assimp_string(parent_node_assimp->mName));
- int current_bone_id = recursive_state.skeleton->find_bone(recursive_state.node_name);
- print_verbose("Parent bone id " + itos(parent_bone_id) + " current bone id" + itos(current_bone_id));
- print_verbose("Bone debug: " + AssimpUtils::get_assimp_string(parent_node_assimp->mName));
- recursive_state.skeleton->set_bone_parent(current_bone_id, parent_bone_id);
- }
+ look_at_transform.set_look_at(pos + look_at_transform.origin, look_at, up);
+ return camera_node;
}
/**
@@ -1217,46 +1490,30 @@ void EditorSceneImporterAssimp::create_bone(ImportState &state, RecursiveState &
*/
void EditorSceneImporterAssimp::_generate_node(
ImportState &state,
- Skeleton *skeleton,
- const aiNode *assimp_node, Node *parent_node) {
+ const aiNode *assimp_node) {
- // sanity check
- ERR_FAIL_COND(state.root == NULL);
- ERR_FAIL_COND(state.assimp_scene == NULL);
ERR_FAIL_COND(assimp_node == NULL);
- ERR_FAIL_COND(parent_node == NULL);
-
- Spatial *new_node = NULL;
+ state.nodes.push_back(assimp_node);
String node_name = AssimpUtils::get_assimp_string(assimp_node->mName);
- Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation);
-
- // can safely return null - is this node a bone?
- aiBone *bone = get_bone_by_name(state.assimp_scene, assimp_node->mName);
-
- // out arguments helper - for pushing state down into creation functions
- RecursiveState recursive_state(node_transform, skeleton, new_node, node_name, assimp_node, parent_node, bone);
-
- // Creation code
- if (state.light_cache.has(node_name)) {
- create_light(state, recursive_state);
- } else if (state.camera_cache.has(node_name)) {
- create_camera(state, recursive_state);
- } else if (bone != NULL) {
- create_bone(state, recursive_state);
- } else {
- //generic node
- recursive_state.new_node = memnew(Spatial);
- attach_new_node(state,
- recursive_state.new_node,
- recursive_state.assimp_node,
- recursive_state.parent_node,
- recursive_state.node_name,
- recursive_state.node_transform);
+ String parent_name = AssimpUtils::get_assimp_string(assimp_node->mParent->mName);
+
+ // please note
+ // duplicate bone names exist
+ // this is why we only check if the bone exists
+ // so everything else is useless but the name
+ // please do not copy any other values from get_bone_by_name.
+ aiBone *parent_bone = get_bone_by_name(state.assimp_scene, assimp_node->mParent->mName);
+ aiBone *current_bone = get_bone_by_name(state.assimp_scene, assimp_node->mName);
+
+ // is this an armature
+ // parent null
+ // and this is the first bone :)
+ if (parent_bone == NULL && current_bone) {
+ state.armature_nodes.push_back(assimp_node->mParent);
+ print_verbose("found valid armature: " + parent_name);
}
- // recurse into all child elements
- for (size_t i = 0; i < recursive_state.assimp_node->mNumChildren; i++) {
- _generate_node(state, recursive_state.skeleton, recursive_state.assimp_node->mChildren[i],
- recursive_state.new_node != NULL ? recursive_state.new_node : recursive_state.parent_node);
+ for (size_t i = 0; i < assimp_node->mNumChildren; i++) {
+ _generate_node(state, assimp_node->mChildren[i]);
}
-} \ No newline at end of file
+}
diff --git a/modules/assimp/editor_scene_importer_assimp.h b/modules/assimp/editor_scene_importer_assimp.h
index 787376c9af..a47d7ac46e 100644
--- a/modules/assimp/editor_scene_importer_assimp.h
+++ b/modules/assimp/editor_scene_importer_assimp.h
@@ -50,6 +50,7 @@
#include <assimp/DefaultLogger.hpp>
#include <assimp/LogStream.hpp>
#include <assimp/Logger.hpp>
+#include <map>
#include "import_state.h"
#include "import_utils.h"
@@ -72,7 +73,6 @@ public:
class EditorSceneImporterAssimp : public EditorSceneImporter {
private:
GDCLASS(EditorSceneImporterAssimp, EditorSceneImporter);
- const String ASSIMP_FBX_KEY = "_$AssimpFbx$";
struct AssetImportAnimation {
enum Interpolation {
@@ -88,40 +88,32 @@ private:
float weight;
};
- struct SkeletonHole { //nodes may be part of the skeleton by used by vertex
- String name;
- String parent;
- Transform pose;
- const aiNode *node;
- };
-
- void _calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w);
- void _set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture);
+ Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices,
+ const aiNode *assimp_node, Ref<Skin> &skin,
+ Skeleton *&skeleton_assigned);
- Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, const aiNode *assimp_node, Skeleton *p_skeleton = NULL);
-
- // utility for node creation
- void attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform);
// simple object creation functions
- void create_light(ImportState &state, RecursiveState &recursive_state);
- void create_camera(ImportState &state, RecursiveState &recursive_state);
- void create_bone(ImportState &state, RecursiveState &recursive_state);
+ Spatial *create_light(ImportState &state,
+ const String &node_name,
+ Transform &look_at_transform);
+ Spatial *create_camera(
+ ImportState &state,
+ const String &node_name,
+ Transform &look_at_transform);
// non recursive - linear so must not use recursive arguments
- void create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform);
-
+ MeshInstance *create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *active_node, Transform node_transform);
// recursive node generator
- void _generate_node(ImportState &state, Skeleton *skeleton, const aiNode *assimp_node, Node *parent_node);
- // runs after _generate_node as it must then use pre-created godot skeleton.
- void generate_mesh_phase_from_skeletal_mesh(ImportState &state);
- void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int p_track, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, Skeleton *p_skeleton, const NodePath &p_path, const String &p_name);
+ void _generate_node(ImportState &state, const aiNode *assimp_node);
+ void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int track_id,
+ int anim_fps, Ref<Animation> animation, float ticks_per_second,
+ Skeleton *skeleton, const NodePath &node_path,
+ const String &node_name, aiBone *track_bone);
void _import_animation(ImportState &state, int p_animation_index, int p_bake_fps);
-
+ Node *get_node_by_name(ImportState &state, String name);
+ aiBone *get_bone_from_stack(ImportState &state, aiString name);
Spatial *_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights);
- String _assimp_anim_string_to_string(const aiString &p_string) const;
- String _assimp_raw_string_to_string(const aiString &p_string) const;
- float _get_fbx_fps(int32_t time_mode, const aiScene *p_scene);
template <class T>
T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp);
void _register_project_setting_import(const String generic, const String import_setting_string, const Vector<String> &exts, List<String> *r_extensions, const bool p_enabled) const;
@@ -148,6 +140,10 @@ public:
virtual uint32_t get_import_flags() const;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = NULL);
Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path);
+
+ static void RegenerateBoneStack(ImportState &state);
+
+ void RegenerateBoneStack(ImportState &state, aiMesh *mesh);
};
#endif
#endif
diff --git a/modules/assimp/import_state.h b/modules/assimp/import_state.h
index 56d89ffea7..9859a88c1c 100644
--- a/modules/assimp/import_state.h
+++ b/modules/assimp/import_state.h
@@ -52,28 +52,42 @@
namespace AssimpImporter {
/** Import state is for global scene import data
- * This makes the code simpler and contains useful lookups.
- */
+ * This makes the code simpler and contains useful lookups.
+ */
struct ImportState {
String path;
+ Spatial *root;
const aiScene *assimp_scene;
uint32_t max_bone_weights;
- Spatial *root;
Map<String, Ref<Mesh> > mesh_cache;
Map<int, Ref<Material> > material_cache;
Map<String, int> light_cache;
Map<String, int> camera_cache;
- //Vector<Skeleton *> skeletons;
- Map<Skeleton *, const Spatial *> armature_skeletons; // maps skeletons based on their armature nodes.
- Map<const aiBone *, Skeleton *> bone_to_skeleton_lookup; // maps bones back into their skeleton
+
// very useful for when you need to ask assimp for the bone mesh
- Map<String, Node *> node_map;
- Map<const aiNode *, const Node *> assimp_node_map;
+
+ Map<const aiNode *, Node *> assimp_node_map;
Map<String, Ref<Image> > path_to_image_cache;
- bool fbx; //for some reason assimp does some things different for FBX
+
+ // Generation 3 - determinisitic iteration
+ // to lower potential recursion errors
+ List<const aiNode *> nodes;
+ Map<const aiNode *, Spatial *> flat_node_map;
AnimationPlayer *animation_player;
+
+ // Generation 3 - deterministic armatures
+ // list of armature nodes - flat and simple to parse
+ // assimp node, node in godot
+ List<aiNode *> armature_nodes;
+ Map<const aiNode *, Skeleton *> armature_skeletons;
+ Map<aiBone *, Skeleton *> skeleton_bone_map;
+ // Generation 3 - deterministic bone handling
+ // bones from the stack are popped when found
+ // this means we can detect
+ // what bones are for other armatures
+ List<aiBone *> bone_stack;
};
struct AssimpImageData {
@@ -86,14 +100,15 @@ struct AssimpImageData {
* This makes the code easier to handle too and add extra arguments without breaking things
*/
struct RecursiveState {
+ RecursiveState() {} // do not construct :)
RecursiveState(
Transform &_node_transform,
Skeleton *_skeleton,
Spatial *_new_node,
- const String &_node_name,
- const aiNode *_assimp_node,
+ String &_node_name,
+ aiNode *_assimp_node,
Node *_parent_node,
- const aiBone *_bone) :
+ aiBone *_bone) :
node_transform(_node_transform),
skeleton(_skeleton),
new_node(_new_node),
@@ -102,13 +117,13 @@ struct RecursiveState {
parent_node(_parent_node),
bone(_bone) {}
- Transform &node_transform;
- Skeleton *skeleton;
- Spatial *new_node;
- const String &node_name;
- const aiNode *assimp_node;
- Node *parent_node;
- const aiBone *bone;
+ Transform node_transform;
+ Skeleton *skeleton = NULL;
+ Spatial *new_node = NULL;
+ String node_name;
+ aiNode *assimp_node = NULL;
+ Node *parent_node = NULL;
+ aiBone *bone = NULL;
};
} // namespace AssimpImporter
diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp
index 5a32fa1c2c..8708430257 100644
--- a/modules/bmp/image_loader_bmp.cpp
+++ b/modules/bmp/image_loader_bmp.cpp
@@ -63,139 +63,137 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
ERR_FAIL_V(ERR_UNAVAILABLE);
}
- if (err == OK) {
- // Image data (might be indexed)
- PoolVector<uint8_t> data;
- int data_len = 0;
+ // Image data (might be indexed)
+ PoolVector<uint8_t> data;
+ int data_len = 0;
- if (bits_per_pixel <= 8) { // indexed
- data_len = width * height;
- } else { // color
- data_len = width * height * 4;
- }
- ERR_FAIL_COND_V(data_len == 0, ERR_BUG);
- err = data.resize(data_len);
-
- PoolVector<uint8_t>::Write data_w = data.write();
- uint8_t *write_buffer = data_w.ptr();
-
- const uint32_t width_bytes = width * bits_per_pixel / 8;
- const uint32_t line_width = (width_bytes + 3) & ~3;
-
- // The actual data traversal is determined by
- // the data width in case of 8/4/1 bit images
- const uint32_t w = bits_per_pixel >= 24 ? width : width_bytes;
- const uint8_t *line = p_buffer + (line_width * (height - 1));
-
- for (unsigned int i = 0; i < height; i++) {
- const uint8_t *line_ptr = line;
-
- for (unsigned int j = 0; j < w; j++) {
- switch (bits_per_pixel) {
- case 1: {
- uint8_t color_index = *line_ptr;
-
- write_buffer[index + 0] = (color_index >> 7) & 1;
- write_buffer[index + 1] = (color_index >> 6) & 1;
- write_buffer[index + 2] = (color_index >> 5) & 1;
- write_buffer[index + 3] = (color_index >> 4) & 1;
- write_buffer[index + 4] = (color_index >> 3) & 1;
- write_buffer[index + 5] = (color_index >> 2) & 1;
- write_buffer[index + 6] = (color_index >> 1) & 1;
- write_buffer[index + 7] = (color_index >> 0) & 1;
-
- index += 8;
- line_ptr += 1;
- } break;
- case 4: {
- uint8_t color_index = *line_ptr;
-
- write_buffer[index + 0] = (color_index >> 4) & 0x0f;
- write_buffer[index + 1] = color_index & 0x0f;
-
- index += 2;
- line_ptr += 1;
- } break;
- case 8: {
- uint8_t color_index = *line_ptr;
-
- write_buffer[index] = color_index;
-
- index += 1;
- line_ptr += 1;
- } break;
- case 24: {
- uint32_t color = *((uint32_t *)line_ptr);
-
- write_buffer[index + 2] = color & 0xff;
- write_buffer[index + 1] = (color >> 8) & 0xff;
- write_buffer[index + 0] = (color >> 16) & 0xff;
- write_buffer[index + 3] = 0xff;
-
- index += 4;
- line_ptr += 3;
- } break;
- case 32: {
- uint32_t color = *((uint32_t *)line_ptr);
-
- write_buffer[index + 2] = color & 0xff;
- write_buffer[index + 1] = (color >> 8) & 0xff;
- write_buffer[index + 0] = (color >> 16) & 0xff;
- write_buffer[index + 3] = color >> 24;
-
- index += 4;
- line_ptr += 4;
- } break;
- }
+ if (bits_per_pixel <= 8) { // indexed
+ data_len = width * height;
+ } else { // color
+ data_len = width * height * 4;
+ }
+ ERR_FAIL_COND_V(data_len == 0, ERR_BUG);
+ err = data.resize(data_len);
+
+ PoolVector<uint8_t>::Write data_w = data.write();
+ uint8_t *write_buffer = data_w.ptr();
+
+ const uint32_t width_bytes = width * bits_per_pixel / 8;
+ const uint32_t line_width = (width_bytes + 3) & ~3;
+
+ // The actual data traversal is determined by
+ // the data width in case of 8/4/1 bit images
+ const uint32_t w = bits_per_pixel >= 24 ? width : width_bytes;
+ const uint8_t *line = p_buffer + (line_width * (height - 1));
+
+ for (uint64_t i = 0; i < height; i++) {
+ const uint8_t *line_ptr = line;
+
+ for (unsigned int j = 0; j < w; j++) {
+ switch (bits_per_pixel) {
+ case 1: {
+ uint8_t color_index = *line_ptr;
+
+ write_buffer[index + 0] = (color_index >> 7) & 1;
+ write_buffer[index + 1] = (color_index >> 6) & 1;
+ write_buffer[index + 2] = (color_index >> 5) & 1;
+ write_buffer[index + 3] = (color_index >> 4) & 1;
+ write_buffer[index + 4] = (color_index >> 3) & 1;
+ write_buffer[index + 5] = (color_index >> 2) & 1;
+ write_buffer[index + 6] = (color_index >> 1) & 1;
+ write_buffer[index + 7] = (color_index >> 0) & 1;
+
+ index += 8;
+ line_ptr += 1;
+ } break;
+ case 4: {
+ uint8_t color_index = *line_ptr;
+
+ write_buffer[index + 0] = (color_index >> 4) & 0x0f;
+ write_buffer[index + 1] = color_index & 0x0f;
+
+ index += 2;
+ line_ptr += 1;
+ } break;
+ case 8: {
+ uint8_t color_index = *line_ptr;
+
+ write_buffer[index] = color_index;
+
+ index += 1;
+ line_ptr += 1;
+ } break;
+ case 24: {
+ uint32_t color = *((uint32_t *)line_ptr);
+
+ write_buffer[index + 2] = color & 0xff;
+ write_buffer[index + 1] = (color >> 8) & 0xff;
+ write_buffer[index + 0] = (color >> 16) & 0xff;
+ write_buffer[index + 3] = 0xff;
+
+ index += 4;
+ line_ptr += 3;
+ } break;
+ case 32: {
+ uint32_t color = *((uint32_t *)line_ptr);
+
+ write_buffer[index + 2] = color & 0xff;
+ write_buffer[index + 1] = (color >> 8) & 0xff;
+ write_buffer[index + 0] = (color >> 16) & 0xff;
+ write_buffer[index + 3] = color >> 24;
+
+ index += 4;
+ line_ptr += 4;
+ } break;
}
- line -= line_width;
}
+ line -= line_width;
+ }
- if (p_color_buffer == NULL || color_table_size == 0) { // regular pixels
+ if (p_color_buffer == NULL || color_table_size == 0) { // regular pixels
- p_image->create(width, height, 0, Image::FORMAT_RGBA8, data);
+ p_image->create(width, height, 0, Image::FORMAT_RGBA8, data);
- } else { // data is in indexed format, extend it
+ } else { // data is in indexed format, extend it
- // Palette data
- PoolVector<uint8_t> palette_data;
- palette_data.resize(color_table_size * 4);
+ // Palette data
+ PoolVector<uint8_t> palette_data;
+ palette_data.resize(color_table_size * 4);
- PoolVector<uint8_t>::Write palette_data_w = palette_data.write();
- uint8_t *pal = palette_data_w.ptr();
+ PoolVector<uint8_t>::Write palette_data_w = palette_data.write();
+ uint8_t *pal = palette_data_w.ptr();
- const uint8_t *cb = p_color_buffer;
+ const uint8_t *cb = p_color_buffer;
- for (unsigned int i = 0; i < color_table_size; ++i) {
- uint32_t color = *((uint32_t *)cb);
+ for (unsigned int i = 0; i < color_table_size; ++i) {
+ uint32_t color = *((uint32_t *)cb);
- pal[i * 4 + 0] = (color >> 16) & 0xff;
- pal[i * 4 + 1] = (color >> 8) & 0xff;
- pal[i * 4 + 2] = (color)&0xff;
- pal[i * 4 + 3] = 0xff;
+ pal[i * 4 + 0] = (color >> 16) & 0xff;
+ pal[i * 4 + 1] = (color >> 8) & 0xff;
+ pal[i * 4 + 2] = (color)&0xff;
+ pal[i * 4 + 3] = 0xff;
- cb += 4;
- }
- // Extend palette to image
- PoolVector<uint8_t> extended_data;
- extended_data.resize(data.size() * 4);
+ cb += 4;
+ }
+ // Extend palette to image
+ PoolVector<uint8_t> extended_data;
+ extended_data.resize(data.size() * 4);
- PoolVector<uint8_t>::Write ex_w = extended_data.write();
- uint8_t *dest = ex_w.ptr();
+ PoolVector<uint8_t>::Write ex_w = extended_data.write();
+ uint8_t *dest = ex_w.ptr();
- const int num_pixels = width * height;
+ const int num_pixels = width * height;
- for (int i = 0; i < num_pixels; i++) {
- dest[0] = pal[write_buffer[i] * 4 + 0];
- dest[1] = pal[write_buffer[i] * 4 + 1];
- dest[2] = pal[write_buffer[i] * 4 + 2];
- dest[3] = pal[write_buffer[i] * 4 + 3];
+ for (int i = 0; i < num_pixels; i++) {
+ dest[0] = pal[write_buffer[i] * 4 + 0];
+ dest[1] = pal[write_buffer[i] * 4 + 1];
+ dest[2] = pal[write_buffer[i] * 4 + 2];
+ dest[3] = pal[write_buffer[i] * 4 + 3];
- dest += 4;
- }
- p_image->create(width, height, 0, Image::FORMAT_RGBA8, extended_data);
+ dest += 4;
}
+ p_image->create(width, height, 0, Image::FORMAT_RGBA8, extended_data);
}
}
return err;
diff --git a/modules/bullet/btRayShape.h b/modules/bullet/btRayShape.h
index 7f3229b3e8..09c1f6c241 100644
--- a/modules/bullet/btRayShape.h
+++ b/modules/bullet/btRayShape.h
@@ -62,7 +62,7 @@ public:
virtual void setMargin(btScalar margin);
- void setSlipsOnSlope(bool p_slipOnSlope);
+ void setSlipsOnSlope(bool p_slipsOnSlope);
bool getSlipsOnSlope() const { return slipsOnSlope; }
const btTransform &getSupportPoint() const { return m_cacheSupportPoint; }
diff --git a/modules/bullet/collision_object_bullet.h b/modules/bullet/collision_object_bullet.h
index c9430bec18..04231b0814 100644
--- a/modules/bullet/collision_object_bullet.h
+++ b/modules/bullet/collision_object_bullet.h
@@ -167,14 +167,18 @@ public:
_FORCE_INLINE_ const VSet<RID> &get_exceptions() const { return exceptions; }
_FORCE_INLINE_ void set_collision_layer(uint32_t p_layer) {
- collisionLayer = p_layer;
- on_collision_filters_change();
+ if (collisionLayer != p_layer) {
+ collisionLayer = p_layer;
+ on_collision_filters_change();
+ }
}
_FORCE_INLINE_ uint32_t get_collision_layer() const { return collisionLayer; }
_FORCE_INLINE_ void set_collision_mask(uint32_t p_mask) {
- collisionMask = p_mask;
- on_collision_filters_change();
+ if (collisionMask != p_mask) {
+ collisionMask = p_mask;
+ on_collision_filters_change();
+ }
}
_FORCE_INLINE_ uint32_t get_collision_mask() const { return collisionMask; }
diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp
index 8d21b25b20..d611810bfa 100644
--- a/modules/bullet/rigid_body_bullet.cpp
+++ b/modules/bullet/rigid_body_bullet.cpp
@@ -126,16 +126,16 @@ void BulletPhysicsDirectBodyState::add_torque(const Vector3 &p_torque) {
body->apply_torque(p_torque);
}
-void BulletPhysicsDirectBodyState::apply_central_impulse(const Vector3 &p_j) {
- body->apply_central_impulse(p_j);
+void BulletPhysicsDirectBodyState::apply_central_impulse(const Vector3 &p_impulse) {
+ body->apply_central_impulse(p_impulse);
}
-void BulletPhysicsDirectBodyState::apply_impulse(const Vector3 &p_pos, const Vector3 &p_j) {
- body->apply_impulse(p_pos, p_j);
+void BulletPhysicsDirectBodyState::apply_impulse(const Vector3 &p_pos, const Vector3 &p_impulse) {
+ body->apply_impulse(p_pos, p_impulse);
}
-void BulletPhysicsDirectBodyState::apply_torque_impulse(const Vector3 &p_j) {
- body->apply_torque_impulse(p_j);
+void BulletPhysicsDirectBodyState::apply_torque_impulse(const Vector3 &p_impulse) {
+ body->apply_torque_impulse(p_impulse);
}
void BulletPhysicsDirectBodyState::set_sleep_state(bool p_enable) {
@@ -411,6 +411,8 @@ void RigidBodyBullet::on_collision_filters_change() {
if (space) {
space->reload_collision_filters(this);
}
+
+ set_activation_state(true);
}
void RigidBodyBullet::on_collision_checker_start() {
@@ -471,7 +473,7 @@ void RigidBodyBullet::assert_no_constraints() {
void RigidBodyBullet::set_activation_state(bool p_active) {
if (p_active) {
- btBody->setActivationState(ACTIVE_TAG);
+ btBody->activate();
} else {
btBody->setActivationState(WANTS_DEACTIVATION);
}
@@ -918,7 +920,7 @@ void RigidBodyBullet::reload_space_override_modificator() {
currentArea = areasWhereIam[i];
- if (PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED == currentArea->get_spOv_mode()) {
+ if (!currentArea || PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED == currentArea->get_spOv_mode()) {
continue;
}
diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h
index f63148092f..0b6dc997db 100644
--- a/modules/bullet/rigid_body_bullet.h
+++ b/modules/bullet/rigid_body_bullet.h
@@ -114,8 +114,8 @@ public:
virtual void add_force(const Vector3 &p_force, const Vector3 &p_pos);
virtual void add_torque(const Vector3 &p_torque);
virtual void apply_central_impulse(const Vector3 &p_impulse);
- virtual void apply_impulse(const Vector3 &p_pos, const Vector3 &p_j);
- virtual void apply_torque_impulse(const Vector3 &p_j);
+ virtual void apply_impulse(const Vector3 &p_pos, const Vector3 &p_impulse);
+ virtual void apply_torque_impulse(const Vector3 &p_impulse);
virtual void set_sleep_state(bool p_enable);
virtual bool is_sleeping() const;
diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp
index d2b16b0fd1..e74c29769f 100644
--- a/modules/bullet/space_bullet.cpp
+++ b/modules/bullet/space_bullet.cpp
@@ -42,6 +42,7 @@
#include "servers/physics_server.h"
#include "soft_body_bullet.h"
+#include <BulletCollision/BroadphaseCollision/btBroadphaseProxy.h>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <BulletCollision/NarrowPhaseCollision/btGjkEpaPenetrationDepthSolver.h>
@@ -459,9 +460,13 @@ void SpaceBullet::remove_area(AreaBullet *p_area) {
}
void SpaceBullet::reload_collision_filters(AreaBullet *p_area) {
- // This is necessary to change collision filter
- dynamicsWorld->removeCollisionObject(p_area->get_bt_ghost());
- dynamicsWorld->addCollisionObject(p_area->get_bt_ghost(), p_area->get_collision_layer(), p_area->get_collision_mask());
+ btGhostObject *ghost_object = p_area->get_bt_ghost();
+
+ btBroadphaseProxy *ghost_proxy = ghost_object->getBroadphaseHandle();
+ ghost_proxy->m_collisionFilterGroup = p_area->get_collision_layer();
+ ghost_proxy->m_collisionFilterMask = p_area->get_collision_mask();
+
+ dynamicsWorld->refreshBroadphaseProxy(ghost_object);
}
void SpaceBullet::add_rigid_body(RigidBodyBullet *p_body) {
@@ -482,9 +487,13 @@ void SpaceBullet::remove_rigid_body(RigidBodyBullet *p_body) {
}
void SpaceBullet::reload_collision_filters(RigidBodyBullet *p_body) {
- // This is necessary to change collision filter
- remove_rigid_body(p_body);
- add_rigid_body(p_body);
+ btRigidBody *rigid_body = p_body->get_bt_rigid_body();
+
+ btBroadphaseProxy *body_proxy = rigid_body->getBroadphaseProxy();
+ body_proxy->m_collisionFilterGroup = p_body->get_collision_layer();
+ body_proxy->m_collisionFilterMask = p_body->get_collision_mask();
+
+ dynamicsWorld->refreshBroadphaseProxy(rigid_body);
}
void SpaceBullet::add_soft_body(SoftBodyBullet *p_body) {
diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp
index f1b3fa2ac6..5a76f32977 100644
--- a/modules/csg/csg.cpp
+++ b/modules/csg/csg.cpp
@@ -114,7 +114,7 @@ void CSGBrush::_regen_face_aabbs() {
faces.write[i].aabb.position = faces[i].vertices[0];
faces.write[i].aabb.expand_to(faces[i].vertices[1]);
faces.write[i].aabb.expand_to(faces[i].vertices[2]);
- faces.write[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision erros
+ faces.write[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision errors
}
}
diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp
index e6bfa5525d..0d26943af6 100644
--- a/modules/csg/csg_gizmos.cpp
+++ b/modules/csg/csg_gizmos.cpp
@@ -377,7 +377,7 @@ void CSGShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
break;
}
- p_gizmo->add_mesh(mesh, false, RID(), solid_material);
+ p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), solid_material);
}
if (Object::cast_to<CSGSphere>(cs)) {
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 23725c4960..9409b33f24 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -282,7 +282,7 @@ void CSGShape::_update_shape() {
root_mesh.unref(); //byebye root mesh
CSGBrush *n = _get_brush();
- ERR_FAIL_COND(!n);
+ ERR_FAIL_COND_MSG(!n, "Cannot get CSGBrush.");
OAHashMap<Vector3, Vector3> vec_map;
@@ -1067,6 +1067,7 @@ void CSGSphere::set_radius(const float p_radius) {
radius = p_radius;
_make_dirty();
update_gizmo();
+ _change_notify("radius");
}
float CSGSphere::get_radius() const {
@@ -1251,6 +1252,7 @@ void CSGBox::set_width(const float p_width) {
width = p_width;
_make_dirty();
update_gizmo();
+ _change_notify("width");
}
float CSGBox::get_width() const {
@@ -1261,6 +1263,7 @@ void CSGBox::set_height(const float p_height) {
height = p_height;
_make_dirty();
update_gizmo();
+ _change_notify("height");
}
float CSGBox::get_height() const {
@@ -1271,6 +1274,7 @@ void CSGBox::set_depth(const float p_depth) {
depth = p_depth;
_make_dirty();
update_gizmo();
+ _change_notify("depth");
}
float CSGBox::get_depth() const {
@@ -1465,6 +1469,7 @@ void CSGCylinder::set_radius(const float p_radius) {
radius = p_radius;
_make_dirty();
update_gizmo();
+ _change_notify("radius");
}
float CSGCylinder::get_radius() const {
@@ -1475,6 +1480,7 @@ void CSGCylinder::set_height(const float p_height) {
height = p_height;
_make_dirty();
update_gizmo();
+ _change_notify("height");
}
float CSGCylinder::get_height() const {
@@ -1690,6 +1696,7 @@ void CSGTorus::set_inner_radius(const float p_inner_radius) {
inner_radius = p_inner_radius;
_make_dirty();
update_gizmo();
+ _change_notify("inner_radius");
}
float CSGTorus::get_inner_radius() const {
@@ -1700,6 +1707,7 @@ void CSGTorus::set_outer_radius(const float p_outer_radius) {
outer_radius = p_outer_radius;
_make_dirty();
update_gizmo();
+ _change_notify("outer_radius");
}
float CSGTorus::get_outer_radius() const {
@@ -2407,7 +2415,7 @@ NodePath CSGPolygon::get_path_node() const {
}
void CSGPolygon::set_path_interval(float p_interval) {
- ERR_FAIL_COND(p_interval < 0.001);
+ ERR_FAIL_COND_MSG(p_interval < 0.001, "Path interval cannot be smaller than 0.001.");
path_interval = p_interval;
_make_dirty();
update_gizmo();
diff --git a/modules/cvtt/SCsub b/modules/cvtt/SCsub
index 142af0c800..746b23ca28 100644
--- a/modules/cvtt/SCsub
+++ b/modules/cvtt/SCsub
@@ -6,19 +6,18 @@ Import('env_modules')
env_cvtt = env_modules.Clone()
# Thirdparty source files
-if env['builtin_squish']:
- thirdparty_dir = "#thirdparty/cvtt/"
- thirdparty_sources = [
- "ConvectionKernels.cpp"
- ]
+thirdparty_dir = "#thirdparty/cvtt/"
+thirdparty_sources = [
+ "ConvectionKernels.cpp"
+]
- thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
+thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
- env_cvtt.Prepend(CPPPATH=[thirdparty_dir])
+env_cvtt.Prepend(CPPPATH=[thirdparty_dir])
- env_thirdparty = env_cvtt.Clone()
- env_thirdparty.disable_warnings()
- env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources)
+env_thirdparty = env_cvtt.Clone()
+env_thirdparty.disable_warnings()
+env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources)
# Godot source files
env_cvtt.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp
index 0220dcae4a..16a0f9d6ee 100644
--- a/modules/dds/texture_loader_dds.cpp
+++ b/modules/dds/texture_loader_dds.cpp
@@ -108,7 +108,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path,
if (r_error)
*r_error = ERR_FILE_CORRUPT;
- ERR_FAIL_COND_V_MSG(err != OK, RES(), "Unable to open DDS texture file: " + p_path + ".");
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Unable to open DDS texture file '" + p_path + "'.");
uint32_t magic = f->get_32();
uint32_t hsize = f->get_32();
@@ -127,7 +127,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path,
if (magic != DDS_MAGIC || hsize != 124 || !(flags & DDSD_PIXELFORMAT) || !(flags & DDSD_CAPS)) {
- ERR_FAIL_V_MSG(RES(), "Invalid or unsupported DDS texture file: " + p_path + ".");
+ ERR_FAIL_V_MSG(RES(), "Invalid or unsupported DDS texture file '" + p_path + "'.");
}
/* uint32_t format_size = */ f->get_32();
@@ -216,7 +216,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path,
} else {
printf("unrecognized fourcc %x format_flags: %x - rgbbits %i - red_mask %x green mask %x blue mask %x alpha mask %x\n", format_fourcc, format_flags, format_rgb_bits, format_red_mask, format_green_mask, format_blue_mask, format_alpha_mask);
- ERR_FAIL_V_MSG(RES(), "Unrecognized or unsupported color layout in DDS: " + p_path + ".");
+ ERR_FAIL_V_MSG(RES(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'.");
}
if (!(flags & DDSD_MIPMAPCOUNT))
diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml
index 84ed5fd9ee..4c10588aa6 100644
--- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml
+++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml
@@ -115,9 +115,11 @@
<member name="compression_mode" type="int" setter="set_compression_mode" getter="get_compression_mode" enum="NetworkedMultiplayerENet.CompressionMode" default="0">
The compression method used for network packets. These have different tradeoffs of compression speed versus bandwidth, you may need to test which one works best for your use case if you use compression at all.
</member>
+ <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" />
<member name="transfer_channel" type="int" setter="set_transfer_channel" getter="get_transfer_channel" default="-1">
Set the default channel to be used to transfer data. By default, this value is [code]-1[/code] which means that ENet will only use 2 channels, one for reliable and one for unreliable packets. Channel [code]0[/code] is reserved, and cannot be used. Setting this member to any value between [code]0[/code] and [member channel_count] (excluded) will force ENet to use that channel for sending data.
</member>
+ <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="NetworkedMultiplayerPeer.TransferMode" default="2" />
</members>
<constants>
<constant name="COMPRESS_NONE" value="0" enum="CompressionMode">
diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp
index 44f69ca261..a787cd3b80 100644
--- a/modules/enet/networked_multiplayer_enet.cpp
+++ b/modules/enet/networked_multiplayer_enet.cpp
@@ -543,7 +543,7 @@ Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer
if (target_peer != 0) {
E = peer_map.find(ABS(target_peer));
- ERR_FAIL_COND_V_MSG(!E, ERR_INVALID_PARAMETER, "Invalid target peer: " + itos(target_peer) + ".");
+ ERR_FAIL_COND_V_MSG(!E, ERR_INVALID_PARAMETER, "Invalid target peer '" + itos(target_peer) + "'.");
}
ENetPacket *packet = enet_packet_create(NULL, p_buffer_size + 8, packet_flags);
diff --git a/modules/etc/image_etc.cpp b/modules/etc/image_etc.cpp
index 6f54436bf9..f0cbf6ae28 100644
--- a/modules/etc/image_etc.cpp
+++ b/modules/etc/image_etc.cpp
@@ -168,6 +168,7 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f
}
PoolVector<uint8_t>::Read r = img->get_data().read();
+ ERR_FAIL_COND(!r.ptr());
unsigned int target_size = Image::get_image_data_size(imgw, imgh, etc_format, p_img->has_mipmaps());
int mmc = 1 + (p_img->has_mipmaps() ? Image::get_image_required_mipmaps(imgw, imgh, etc_format) : 0);
diff --git a/modules/etc/texture_loader_pkm.cpp b/modules/etc/texture_loader_pkm.cpp
index dd61d816d4..4d8af6883f 100644
--- a/modules/etc/texture_loader_pkm.cpp
+++ b/modules/etc/texture_loader_pkm.cpp
@@ -56,14 +56,14 @@ RES ResourceFormatPKM::load(const String &p_path, const String &p_original_path,
if (r_error)
*r_error = ERR_FILE_CORRUPT;
- ERR_FAIL_COND_V_MSG(err != OK, RES(), "Unable to open PKM texture file: " + p_path + ".");
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Unable to open PKM texture file '" + p_path + "'.");
// big endian
f->set_endian_swap(true);
ETC1Header h;
f->get_buffer((uint8_t *)&h.tag, sizeof(h.tag));
- ERR_FAIL_COND_V_MSG(strncmp(h.tag, "PKM 10", sizeof(h.tag)), RES(), "Invalid or unsupported PKM texture file: " + p_path + ".");
+ ERR_FAIL_COND_V_MSG(strncmp(h.tag, "PKM 10", sizeof(h.tag)), RES(), "Invalid or unsupported PKM texture file '" + p_path + "'.");
h.format = f->get_16();
h.texWidth = f->get_16();
diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub
index b47377cbc4..8f4a8de895 100644
--- a/modules/freetype/SCsub
+++ b/modules/freetype/SCsub
@@ -66,7 +66,7 @@ if env['builtin_freetype']:
env.Prepend(CPPPATH=[thirdparty_dir + "/include"])
env_freetype.Append(CPPDEFINES=['FT2_BUILD_LIBRARY', 'FT_CONFIG_OPTION_USE_PNG'])
- if (env['target'] != 'release'):
+ if (env['target'] == 'debug'):
env_freetype.Append(CPPDEFINES=['ZLIB_DEBUG'])
# Also requires libpng headers
diff --git a/modules/gdnative/doc_classes/GDNativeLibrary.xml b/modules/gdnative/doc_classes/GDNativeLibrary.xml
index 7e1cac243a..aa48ab44f2 100644
--- a/modules/gdnative/doc_classes/GDNativeLibrary.xml
+++ b/modules/gdnative/doc_classes/GDNativeLibrary.xml
@@ -1,35 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GDNativeLibrary" inherits="Resource" category="Core" version="3.2">
<brief_description>
+ An external library containing functions or script classes to use in Godot.
</brief_description>
<description>
+ A GDNative library can implement [NativeScript]s, global functions to call with the [GDNative] class, or low-level engine extensions through interfaces such as [ARVRInterfaceGDNative]. The library must be compiled for each platform and architecture that the project will run on.
</description>
<tutorials>
+ <link>https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-c-example.html</link>
+ <link>https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-cpp-example.html</link>
</tutorials>
<methods>
<method name="get_current_dependencies" qualifiers="const">
<return type="PoolStringArray">
</return>
<description>
+ Returns paths to all dependency libraries for the current platform and architecture.
</description>
</method>
<method name="get_current_library_path" qualifiers="const">
<return type="String">
</return>
<description>
+ Returns the path to the dynamic library file for the current platform and architecture.
</description>
</method>
</methods>
<members>
<member name="config_file" type="ConfigFile" setter="set_config_file" getter="get_config_file">
+ This resource in INI-style [ConfigFile] format, as in [code].gdnlib[/code] files.
</member>
<member name="load_once" type="bool" setter="set_load_once" getter="should_load_once" default="true">
+ If [code]true[/code], Godot loads only one copy of the library and each script that references the library will share static data like static or global variables.
+ If [code]false[/code], Godot loads a separate copy of the library into memory for each script that references it.
</member>
<member name="reloadable" type="bool" setter="set_reloadable" getter="is_reloadable" default="true">
+ If [code]true[/code], the editor will temporarily unload the library whenever the user switches away from the editor window, allowing the user to recompile the library without restarting Godot.
+ [b]Note:[/b] If the library defines tool scripts that run inside the editor, [code]reloadable[/code] must be [code]false[/code]. Otherwise, the editor will attempt to unload the tool scripts while they're in use and crash.
</member>
<member name="singleton" type="bool" setter="set_singleton" getter="is_singleton" default="false">
+ If [code]true[/code], Godot loads the library at startup rather than the first time a script uses the library, calling [code]{prefix}gdnative_singleton[/code] after initializing the library (where [code]{prefix}[/code] is the value of [member symbol_prefix]). The library remains loaded as long as Godot is running.
+ [b]Note:[/b] A singleton library cannot be [member reloadable].
</member>
<member name="symbol_prefix" type="String" setter="set_symbol_prefix" getter="get_symbol_prefix" default="&quot;godot_&quot;">
+ The prefix this library's entry point functions begin with. For example, a GDNativeLibrary would declare its [code]gdnative_init[/code] function as [code]godot_gdnative_init[/code] by default.
+ On platforms that require statically linking libraries (currently only iOS), each library must have a different [code]symbol_prefix[/code].
</member>
</members>
<constants>
diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp
index 783ad4e147..ee9e71d4a0 100644
--- a/modules/gdnative/gdnative.cpp
+++ b/modules/gdnative/gdnative.cpp
@@ -339,7 +339,7 @@ bool GDNative::initialize() {
if (err || !library_init) {
OS::get_singleton()->close_dynamic_library(native_handle);
native_handle = NULL;
- ERR_PRINT("Failed to obtain godot_gdnative_init symbol");
+ ERR_PRINTS("Failed to obtain " + library->get_symbol_prefix() + "gdnative_init symbol");
return false;
}
diff --git a/modules/gdnative/gdnative/array.cpp b/modules/gdnative/gdnative/array.cpp
index 1ef8e9f900..e97a75cca8 100644
--- a/modules/gdnative/gdnative/array.cpp
+++ b/modules/gdnative/gdnative/array.cpp
@@ -327,6 +327,15 @@ godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_b
return res;
}
+godot_array GDAPI godot_array_slice(const godot_array *p_self, const godot_int p_begin, const godot_int p_end, const godot_int p_step, const godot_bool p_deep) {
+ const Array *self = (const Array *)p_self;
+ godot_array res;
+ Array *val = (Array *)&res;
+ memnew_placement(val, Array);
+ *val = self->slice(p_begin, p_end, p_step, p_deep);
+ return res;
+}
+
godot_variant GDAPI godot_array_max(const godot_array *p_self) {
const Array *self = (const Array *)p_self;
godot_variant v;
diff --git a/modules/gdnative/gdnative/pool_arrays.cpp b/modules/gdnative/gdnative/pool_arrays.cpp
index 74c540ca14..23791af67e 100644
--- a/modules/gdnative/gdnative/pool_arrays.cpp
+++ b/modules/gdnative/gdnative/pool_arrays.cpp
@@ -129,6 +129,11 @@ godot_int GDAPI godot_pool_byte_array_size(const godot_pool_byte_array *p_self)
return self->size();
}
+godot_bool GDAPI godot_pool_byte_array_empty(const godot_pool_byte_array *p_self) {
+ const PoolVector<uint8_t> *self = (const PoolVector<uint8_t> *)p_self;
+ return self->empty();
+}
+
void GDAPI godot_pool_byte_array_destroy(godot_pool_byte_array *p_self) {
((PoolVector<uint8_t> *)p_self)->~PoolVector();
}
@@ -218,6 +223,11 @@ godot_int GDAPI godot_pool_int_array_size(const godot_pool_int_array *p_self) {
return self->size();
}
+godot_bool GDAPI godot_pool_int_array_empty(const godot_pool_int_array *p_self) {
+ const PoolVector<godot_int> *self = (const PoolVector<godot_int> *)p_self;
+ return self->empty();
+}
+
void GDAPI godot_pool_int_array_destroy(godot_pool_int_array *p_self) {
((PoolVector<godot_int> *)p_self)->~PoolVector();
}
@@ -307,6 +317,11 @@ godot_int GDAPI godot_pool_real_array_size(const godot_pool_real_array *p_self)
return self->size();
}
+godot_bool GDAPI godot_pool_real_array_empty(const godot_pool_real_array *p_self) {
+ const PoolVector<godot_real> *self = (const PoolVector<godot_real> *)p_self;
+ return self->empty();
+}
+
void GDAPI godot_pool_real_array_destroy(godot_pool_real_array *p_self) {
((PoolVector<godot_real> *)p_self)->~PoolVector();
}
@@ -404,6 +419,11 @@ godot_int GDAPI godot_pool_string_array_size(const godot_pool_string_array *p_se
return self->size();
}
+godot_bool GDAPI godot_pool_string_array_empty(const godot_pool_string_array *p_self) {
+ const PoolVector<String> *self = (const PoolVector<String> *)p_self;
+ return self->empty();
+}
+
void GDAPI godot_pool_string_array_destroy(godot_pool_string_array *p_self) {
((PoolVector<String> *)p_self)->~PoolVector();
}
@@ -500,6 +520,11 @@ godot_int GDAPI godot_pool_vector2_array_size(const godot_pool_vector2_array *p_
return self->size();
}
+godot_bool GDAPI godot_pool_vector2_array_empty(const godot_pool_vector2_array *p_self) {
+ const PoolVector<Vector2> *self = (const PoolVector<Vector2> *)p_self;
+ return self->empty();
+}
+
void GDAPI godot_pool_vector2_array_destroy(godot_pool_vector2_array *p_self) {
((PoolVector<Vector2> *)p_self)->~PoolVector();
}
@@ -596,6 +621,11 @@ godot_int GDAPI godot_pool_vector3_array_size(const godot_pool_vector3_array *p_
return self->size();
}
+godot_bool GDAPI godot_pool_vector3_array_empty(const godot_pool_vector3_array *p_self) {
+ const PoolVector<Vector3> *self = (const PoolVector<Vector3> *)p_self;
+ return self->empty();
+}
+
void GDAPI godot_pool_vector3_array_destroy(godot_pool_vector3_array *p_self) {
((PoolVector<Vector3> *)p_self)->~PoolVector();
}
@@ -692,6 +722,11 @@ godot_int GDAPI godot_pool_color_array_size(const godot_pool_color_array *p_self
return self->size();
}
+godot_bool GDAPI godot_pool_color_array_empty(const godot_pool_color_array *p_self) {
+ const PoolVector<Color> *self = (const PoolVector<Color> *)p_self;
+ return self->empty();
+}
+
void GDAPI godot_pool_color_array_destroy(godot_pool_color_array *p_self) {
((PoolVector<Color> *)p_self)->~PoolVector();
}
diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json
index 03258584ce..9e5295a936 100644
--- a/modules/gdnative/gdnative_api.json
+++ b/modules/gdnative/gdnative_api.json
@@ -80,6 +80,66 @@
["const godot_vector2 *", "p_self"],
["const godot_vector2 *", "p_to"]
]
+ },
+ {
+ "name": "godot_array_slice",
+ "return_type": "godot_array",
+ "arguments": [
+ ["const godot_array *", "p_self"],
+ ["const godot_int", "p_begin"],
+ ["const godot_int", "p_end"],
+ ["const godot_int", "p_step"],
+ ["const godot_bool", "p_deep"]
+ ]
+ },
+ {
+ "name": "godot_pool_byte_array_empty",
+ "return_type": "godot_bool",
+ "arguments": [
+ ["const godot_pool_byte_array *", "p_self"]
+ ]
+ },
+ {
+ "name": "godot_pool_int_array_empty",
+ "return_type": "godot_bool",
+ "arguments": [
+ ["const godot_pool_int_array *", "p_self"]
+ ]
+ },
+ {
+ "name": "godot_pool_real_array_empty",
+ "return_type": "godot_bool",
+ "arguments": [
+ ["const godot_pool_real_array *", "p_self"]
+ ]
+ },
+ {
+ "name": "godot_pool_string_array_empty",
+ "return_type": "godot_bool",
+ "arguments": [
+ ["const godot_pool_string_array *", "p_self"]
+ ]
+ },
+ {
+ "name": "godot_pool_vector2_array_empty",
+ "return_type": "godot_bool",
+ "arguments": [
+ ["const godot_pool_vector2_array *", "p_self"]
+ ]
+ },
+ {
+ "name": "godot_pool_vector3_array_empty",
+ "return_type": "godot_bool",
+ "arguments": [
+ ["const godot_pool_vector3_array *", "p_self"]
+ ]
+ },
+ {
+ "name": "godot_pool_color_array_empty",
+ "return_type": "godot_bool",
+ "arguments": [
+ ["const godot_pool_color_array *", "p_self"]
+ ]
}
]
},
diff --git a/modules/gdnative/include/gdnative/array.h b/modules/gdnative/include/gdnative/array.h
index 10ef8a73d2..a27626325e 100644
--- a/modules/gdnative/include/gdnative/array.h
+++ b/modules/gdnative/include/gdnative/array.h
@@ -132,6 +132,8 @@ void GDAPI godot_array_destroy(godot_array *p_self);
godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_bool p_deep);
+godot_array GDAPI godot_array_slice(const godot_array *p_self, const godot_int p_begin, const godot_int p_end, const godot_int p_step, const godot_bool p_deep);
+
godot_variant GDAPI godot_array_max(const godot_array *p_self);
godot_variant GDAPI godot_array_min(const godot_array *p_self);
diff --git a/modules/gdnative/include/gdnative/pool_arrays.h b/modules/gdnative/include/gdnative/pool_arrays.h
index 96730ab085..63e8267f0e 100644
--- a/modules/gdnative/include/gdnative/pool_arrays.h
+++ b/modules/gdnative/include/gdnative/pool_arrays.h
@@ -191,6 +191,8 @@ uint8_t GDAPI godot_pool_byte_array_get(const godot_pool_byte_array *p_self, con
godot_int GDAPI godot_pool_byte_array_size(const godot_pool_byte_array *p_self);
+godot_bool GDAPI godot_pool_byte_array_empty(const godot_pool_byte_array *p_self);
+
void GDAPI godot_pool_byte_array_destroy(godot_pool_byte_array *p_self);
// int
@@ -222,6 +224,8 @@ godot_int GDAPI godot_pool_int_array_get(const godot_pool_int_array *p_self, con
godot_int GDAPI godot_pool_int_array_size(const godot_pool_int_array *p_self);
+godot_bool GDAPI godot_pool_int_array_empty(const godot_pool_int_array *p_self);
+
void GDAPI godot_pool_int_array_destroy(godot_pool_int_array *p_self);
// real
@@ -253,6 +257,8 @@ godot_real GDAPI godot_pool_real_array_get(const godot_pool_real_array *p_self,
godot_int GDAPI godot_pool_real_array_size(const godot_pool_real_array *p_self);
+godot_bool GDAPI godot_pool_real_array_empty(const godot_pool_real_array *p_self);
+
void GDAPI godot_pool_real_array_destroy(godot_pool_real_array *p_self);
// string
@@ -284,6 +290,8 @@ godot_string GDAPI godot_pool_string_array_get(const godot_pool_string_array *p_
godot_int GDAPI godot_pool_string_array_size(const godot_pool_string_array *p_self);
+godot_bool GDAPI godot_pool_string_array_empty(const godot_pool_string_array *p_self);
+
void GDAPI godot_pool_string_array_destroy(godot_pool_string_array *p_self);
// vector2
@@ -315,6 +323,8 @@ godot_vector2 GDAPI godot_pool_vector2_array_get(const godot_pool_vector2_array
godot_int GDAPI godot_pool_vector2_array_size(const godot_pool_vector2_array *p_self);
+godot_bool GDAPI godot_pool_vector2_array_empty(const godot_pool_vector2_array *p_self);
+
void GDAPI godot_pool_vector2_array_destroy(godot_pool_vector2_array *p_self);
// vector3
@@ -346,6 +356,8 @@ godot_vector3 GDAPI godot_pool_vector3_array_get(const godot_pool_vector3_array
godot_int GDAPI godot_pool_vector3_array_size(const godot_pool_vector3_array *p_self);
+godot_bool GDAPI godot_pool_vector3_array_empty(const godot_pool_vector3_array *p_self);
+
void GDAPI godot_pool_vector3_array_destroy(godot_pool_vector3_array *p_self);
// color
@@ -377,6 +389,8 @@ godot_color GDAPI godot_pool_color_array_get(const godot_pool_color_array *p_sel
godot_int GDAPI godot_pool_color_array_size(const godot_pool_color_array *p_self);
+godot_bool GDAPI godot_pool_color_array_empty(const godot_pool_color_array *p_self);
+
void GDAPI godot_pool_color_array_destroy(godot_pool_color_array *p_self);
//
diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h
index 7f52f5736c..8a05b6cfa3 100644
--- a/modules/gdnative/include/nativescript/godot_nativescript.h
+++ b/modules/gdnative/include/nativescript/godot_nativescript.h
@@ -64,9 +64,9 @@ typedef enum {
GODOT_PROPERTY_HINT_LAYERS_3D_RENDER,
GODOT_PROPERTY_HINT_LAYERS_3D_PHYSICS,
GODOT_PROPERTY_HINT_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,"
- GODOT_PROPERTY_HINT_DIR, ///< a directort path must be passed
+ GODOT_PROPERTY_HINT_DIR, ///< a directory path must be passed
GODOT_PROPERTY_HINT_GLOBAL_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,"
- GODOT_PROPERTY_HINT_GLOBAL_DIR, ///< a directort path must be passed
+ GODOT_PROPERTY_HINT_GLOBAL_DIR, ///< a directory path must be passed
GODOT_PROPERTY_HINT_RESOURCE_TYPE, ///< a resource object type
GODOT_PROPERTY_HINT_MULTILINE_TEXT, ///< used for string properties that can contain multiple lines
GODOT_PROPERTY_HINT_PLACEHOLDER_TEXT, ///< used to set a placeholder text for string properties
diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp
index 9de073fc8e..22898a73ce 100644
--- a/modules/gdnative/pluginscript/pluginscript_language.cpp
+++ b/modules/gdnative/pluginscript/pluginscript_language.cpp
@@ -200,7 +200,7 @@ void PluginScriptLanguage::get_recognized_extensions(List<String> *p_extensions)
}
void PluginScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const {
- // TODO: provid this statically in `godot_pluginscript_language_desc` ?
+ // TODO: provide this statically in `godot_pluginscript_language_desc` ?
if (_desc.get_public_functions) {
Array functions;
_desc.get_public_functions(_data, (godot_array *)&functions);
@@ -212,7 +212,7 @@ void PluginScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) c
}
void PluginScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_constants) const {
- // TODO: provid this statically in `godot_pluginscript_language_desc` ?
+ // TODO: provide this statically in `godot_pluginscript_language_desc` ?
if (_desc.get_public_constants) {
Dictionary constants;
_desc.get_public_constants(_data, (godot_dictionary *)&constants);
diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp
index 94d38e1be1..f7c961d38b 100644
--- a/modules/gdnative/pluginscript/pluginscript_script.cpp
+++ b/modules/gdnative/pluginscript/pluginscript_script.cpp
@@ -401,9 +401,7 @@ Error PluginScript::load_source_code(const String &p_path) {
PoolVector<uint8_t> sourcef;
Error err;
FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
- if (err) {
- ERR_FAIL_COND_V(err, err);
- }
+ ERR_FAIL_COND_V_MSG(err, err, "Cannot open file '" + p_path + "'.");
int len = f->get_len();
sourcef.resize(len + 1);
diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp
index 6ff6262b56..fa59c704d5 100644
--- a/modules/gdnative/register_types.cpp
+++ b/modules/gdnative/register_types.cpp
@@ -158,7 +158,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n"
"extern void add_ios_init_callback(void (*cb)());\n";
String linker_flags = "";
- for (unsigned int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) {
+ for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) {
String full_name = lib->get_symbol_prefix() + expected_symbols[i].name;
String code = declare_pattern.replace("$name", full_name);
code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))");
@@ -174,7 +174,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty
additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix());
String register_pattern = " if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n";
- for (unsigned int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) {
+ for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) {
String full_name = lib->get_symbol_prefix() + expected_symbols[i].name;
additional_code += register_pattern.replace("$name", full_name);
}
@@ -277,7 +277,7 @@ void register_gdnative_types() {
proc_ptr);
if (err != OK) {
- ERR_PRINT((String("No godot_gdnative_singleton in \"" + singleton->get_library()->get_current_library_path()) + "\" found").utf8().get_data());
+ ERR_PRINTS("No " + lib->get_symbol_prefix() + "gdnative_singleton in \"" + singleton->get_library()->get_current_library_path() + "\" found");
} else {
singleton_gdnatives.push_back(singleton);
((void (*)())proc_ptr)();
diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.cpp b/modules/gdnative/videodecoder/video_stream_gdnative.cpp
index be131c5402..14b7f9a2ef 100644
--- a/modules/gdnative/videodecoder/video_stream_gdnative.cpp
+++ b/modules/gdnative/videodecoder/video_stream_gdnative.cpp
@@ -149,7 +149,7 @@ void VideoStreamPlaybackGDNative::update(float p_delta) {
if (mix_callback) {
if (pcm_write_idx >= 0) {
// Previous remains
- int mixed = mix_callback(mix_udata, pcm, samples_decoded);
+ int mixed = mix_callback(mix_udata, pcm + pcm_write_idx * num_channels, samples_decoded);
if (mixed == samples_decoded) {
pcm_write_idx = -1;
} else {
@@ -168,8 +168,12 @@ void VideoStreamPlaybackGDNative::update(float p_delta) {
}
}
- while (interface->get_playback_position(data_struct) < time && playing) {
+ if (seek_backward) {
+ update_texture();
+ seek_backward = false;
+ }
+ while (interface->get_playback_position(data_struct) < time && playing) {
update_texture();
}
}
@@ -197,6 +201,7 @@ VideoStreamPlaybackGDNative::VideoStreamPlaybackGDNative() :
mix_callback(NULL),
num_channels(-1),
time(0),
+ seek_backward(false),
mix_rate(0),
delay_compensation(0),
pcm(NULL),
@@ -261,13 +266,20 @@ void VideoStreamPlaybackGDNative::stop() {
void VideoStreamPlaybackGDNative::seek(float p_time) {
ERR_FAIL_COND(interface == NULL);
interface->seek(data_struct, p_time);
+ if (p_time < time)
+ seek_backward = true;
+ time = p_time;
+ // reset audio buffers
+ memset(pcm, 0, num_channels * AUX_BUFFER_SIZE * sizeof(float));
+ pcm_write_idx = -1;
+ samples_decoded = 0;
}
void VideoStreamPlaybackGDNative::set_paused(bool p_paused) {
paused = p_paused;
}
-Ref<Texture> VideoStreamPlaybackGDNative::get_texture() {
+Ref<Texture> VideoStreamPlaybackGDNative::get_texture() const {
return texture;
}
diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.h b/modules/gdnative/videodecoder/video_stream_gdnative.h
index b9f1c8e4da..5ff7acb616 100644
--- a/modules/gdnative/videodecoder/video_stream_gdnative.h
+++ b/modules/gdnative/videodecoder/video_stream_gdnative.h
@@ -121,6 +121,7 @@ class VideoStreamPlaybackGDNative : public VideoStreamPlayback {
int num_channels;
float time;
+ bool seek_backward;
int mix_rate;
double delay_compensation;
@@ -167,7 +168,7 @@ public:
//virtual int mix(int16_t* p_buffer,int p_frames)=0;
- virtual Ref<Texture> get_texture();
+ virtual Ref<Texture> get_texture() const;
virtual void update(float p_delta);
virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata);
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 4efa90fd86..f96c4f28c8 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -21,7 +21,7 @@
<argument index="3" name="a8" type="int" default="255">
</argument>
<description>
- Returns a 32 bit color with red, green, blue and alpha channels. Each channel has 8 bits of information ranging from 0 to 255.
+ Returns a color constructed from integer red, green, blue, and alpha channels. Each channel should have 8 bits of information ranging from 0 to 255.
[code]r8[/code] red channel
[code]g8[/code] green channel
[code]b8[/code] blue channel
@@ -590,10 +590,10 @@
extends Sprite
var elapsed = 0.0
func _process(delta):
- var min_angle = deg2rad(0.0)
- var max_angle = deg2rad(90.0)
- rotation = lerp_angle(min_angle, max_angle, elapsed)
- elapsed += delta
+ var min_angle = deg2rad(0.0)
+ var max_angle = deg2rad(90.0)
+ rotation = lerp_angle(min_angle, max_angle, elapsed)
+ elapsed += delta
[/codeblock]
</description>
</method>
@@ -694,6 +694,14 @@
[/codeblock]
</description>
</method>
+ <method name="ord">
+ <return type="int">
+ </return>
+ <argument index="0" name="char" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
<method name="parse_json">
<return type="Variant">
</return>
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 963b40529d..4d6279074c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -247,7 +247,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
in_function_args = false;
}
- if (expect_type && prev_is_char) {
+ if (expect_type && (prev_is_char || str[j] == '=')) {
expect_type = false;
}
@@ -364,20 +364,28 @@ void GDScriptSyntaxHighlighter::_update_cache() {
number_color = text_editor->get_color("number_color");
member_color = text_editor->get_color("member_variable_color");
- EditorSettings *settings = EditorSettings::get_singleton();
- String text_editor_color_theme = settings->get("text_editor/theme/color_theme");
+ const String text_editor_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme");
+ const bool default_theme = text_editor_color_theme == "Default";
- bool default_theme = text_editor_color_theme == "Default";
- bool dark_theme = settings->is_dark_theme();
-
- function_definition_color = default_theme ? Color(0.0, 0.88, 1.0) : dark_theme ? Color(0.0, 0.88, 1.0) : Color(0.0, 0.65, 0.73);
- node_path_color = default_theme ? Color(0.39, 0.76, 0.35) : dark_theme ? Color(0.39, 0.76, 0.35) : Color(0.32, 0.55, 0.29);
+ if (default_theme || EditorSettings::get_singleton()->is_dark_theme()) {
+ function_definition_color = Color(0.4, 0.9, 1.0);
+ node_path_color = Color(0.39, 0.76, 0.35);
+ } else {
+ function_definition_color = Color(0.0, 0.65, 0.73);
+ node_path_color = Color(0.32, 0.55, 0.29);
+ }
EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color);
EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color);
if (text_editor_color_theme == "Adaptive" || default_theme) {
- settings->set_initial_value("text_editor/highlighting/gdscript/function_definition_color", function_definition_color, true);
- settings->set_initial_value("text_editor/highlighting/gdscript/node_path_color", node_path_color, true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/highlighting/gdscript/function_definition_color",
+ function_definition_color,
+ true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/highlighting/gdscript/node_path_color",
+ node_path_color,
+ true);
}
function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color");
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 5dab063061..edb296437b 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -99,7 +99,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
#endif
instance->owner->set_script_instance(instance);
- /* STEP 2, INITIALIZE AND CONSRTUCT */
+ /* STEP 2, INITIALIZE AND CONSTRUCT */
#ifndef NO_THREADS
GDScriptLanguage::singleton->lock->lock();
@@ -199,7 +199,7 @@ StringName GDScript::get_instance_base_type() const {
if (native.is_valid())
return native->get_name();
- if (base.is_valid())
+ if (base.is_valid() && base->is_valid())
return base->get_instance_base_type();
return StringName();
}
@@ -486,7 +486,7 @@ bool GDScript::_update_exports() {
placeholder_fallback_enabled = false;
- if (base_cache.is_valid()) {
+ if (base_cache.is_valid() && base_cache->is_valid()) {
if (base_cache->_update_exports()) {
changed = true;
}
@@ -2177,11 +2177,11 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori
script->set_script_path(p_original_path); // script needs this.
script->set_path(p_original_path);
Error err = script->load_byte_code(p_path);
- ERR_FAIL_COND_V(err != OK, RES());
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load byte code from file '" + p_path + "'.");
} else {
Error err = script->load_source_code(p_path);
- ERR_FAIL_COND_V(err != OK, RES());
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load source code from file '" + p_path + "'.");
script->set_script_path(p_original_path); // script needs this.
script->set_path(p_original_path);
@@ -2217,7 +2217,7 @@ String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) con
void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
FileAccessRef file = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND(!file);
+ ERR_FAIL_COND_MSG(!file, "Cannot open file '" + p_path + "'.");
String source = file->get_as_utf8_string();
if (source.empty()) {
@@ -2244,10 +2244,7 @@ Error ResourceFormatSaverGDScript::save(const String &p_path, const RES &p_resou
Error err;
FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- if (err) {
-
- ERR_FAIL_COND_V(err, err);
- }
+ ERR_FAIL_COND_V_MSG(err, err, "Cannot save GDScript file '" + p_path + "'.");
file->store_string(source);
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 7166189416..dea2225e91 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1520,8 +1520,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo
if (ret2 < 0)
return ERR_PARSE_ERROR;
+ int message_ret = 0;
+ if (as->message) {
+ message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false);
+ if (message_ret < 0)
+ return ERR_PARSE_ERROR;
+ }
+
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT);
codegen.opcodes.push_back(ret2);
+ codegen.opcodes.push_back(message_ret);
#endif
} break;
case GDScriptParser::Node::TYPE_BREAKPOINT: {
@@ -2064,7 +2072,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
}
instance->owner->set_script_instance(instance);
- /* STEP 2, INITIALIZE AND CONSRTUCT */
+ /* STEP 2, INITIALIZE AND CONSTRUCT */
Variant::CallError ce;
p_script->initializer->call(instance, NULL, 0, ce);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 9b3bf8ad5b..1d82735328 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -1944,11 +1944,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
r_result.insert(option.display, option);
}
- } else {
- for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) {
- ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER);
- r_result.insert(option.display, option);
- }
+ }
+ for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
}
}
if (!p_only_functions) {
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index 68f2a9473e..d8816726ce 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -1419,7 +1419,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (!container->iter_init(*counter, valid)) {
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "'.";
+ err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'.";
OPCODE_BREAK;
}
#endif
@@ -1432,7 +1432,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
*iterator = container->iter_get(*counter, valid);
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "'.";
+ err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'.";
OPCODE_BREAK;
}
#endif
@@ -1452,7 +1452,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (!container->iter_next(*counter, valid)) {
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?).";
+ err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?).";
OPCODE_BREAK;
}
#endif
@@ -1465,7 +1465,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
*iterator = container->iter_get(*counter, valid);
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?).";
+ err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?).";
OPCODE_BREAK;
}
#endif
@@ -1475,20 +1475,25 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
DISPATCH_OPCODE;
OPCODE(OPCODE_ASSERT) {
- CHECK_SPACE(2);
+ CHECK_SPACE(3);
#ifdef DEBUG_ENABLED
GET_VARIANT_PTR(test, 1);
+ GET_VARIANT_PTR(message, 2);
bool result = test->booleanize();
if (!result) {
-
- err_text = "Assertion failed.";
+ const String &message_str = *message;
+ if (message_str.empty()) {
+ err_text = "Assertion failed.";
+ } else {
+ err_text = "Assertion failed: " + message_str;
+ }
OPCODE_BREAK;
}
#endif
- ip += 2;
+ ip += 3;
}
DISPATCH_OPCODE;
@@ -1556,14 +1561,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
//error
// function, file, line, error, explanation
String err_file;
- if (p_instance && p_instance->script->is_valid() && p_instance->script->path != "")
+ if (p_instance && ObjectDB::instance_validate(p_instance->owner) && p_instance->script->is_valid() && p_instance->script->path != "")
err_file = p_instance->script->path;
else if (script)
err_file = script->path;
if (err_file == "")
err_file = "<built-in>";
String err_func = name;
- if (p_instance && p_instance->script->is_valid() && p_instance->script->name != "")
+ if (p_instance && ObjectDB::instance_validate(p_instance->owner) && p_instance->script->is_valid() && p_instance->script->name != "")
err_func = p_instance->script->name + "." + err_func;
int err_line = line;
if (err_text == "") {
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index 97790e00bb..bbafef68ed 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -572,37 +572,32 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
} break;
case OBJ_WEAKREF: {
VALIDATE_ARG_COUNT(1);
- if (p_args[0]->get_type() != Variant::OBJECT) {
-
+ if (p_args[0]->get_type() == Variant::OBJECT) {
+ if (p_args[0]->is_ref()) {
+ Ref<WeakRef> wref = memnew(WeakRef);
+ REF r = *p_args[0];
+ if (r.is_valid()) {
+ wref->set_ref(r);
+ }
+ r_ret = wref;
+ } else {
+ Ref<WeakRef> wref = memnew(WeakRef);
+ Object *obj = *p_args[0];
+ if (obj) {
+ wref->set_obj(obj);
+ }
+ r_ret = wref;
+ }
+ } else if (p_args[0]->get_type() == Variant::NIL) {
+ Ref<WeakRef> wref = memnew(WeakRef);
+ r_ret = wref;
+ } else {
r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
r_ret = Variant();
return;
}
-
- if (p_args[0]->is_ref()) {
-
- REF r = *p_args[0];
- if (!r.is_valid()) {
- r_ret = Variant();
- return;
- }
-
- Ref<WeakRef> wref = memnew(WeakRef);
- wref->set_ref(r);
- r_ret = wref;
- } else {
- Object *obj = *p_args[0];
- if (!obj) {
- r_ret = Variant();
- return;
- }
- Ref<WeakRef> wref = memnew(WeakRef);
- wref->set_obj(obj);
- r_ret = wref;
- }
-
} break;
case FUNC_FUNCREF: {
VALIDATE_ARG_COUNT(2);
@@ -1131,7 +1126,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
Dictionary d;
d["@subpath"] = cp;
- d["@path"] = p->path;
+ d["@path"] = p->get_path();
p = base.ptr();
@@ -1279,6 +1274,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
if (err != OK) {
r_ret = Variant();
+ ERR_PRINTS(vformat("Error parsing JSON at line %s: %s", errl, errs));
}
} break;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index e96bf0238a..9d229adb2a 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -89,8 +89,8 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) {
// be more python-like
- int current = tab_level.back()->get();
- tab_level.push_back(current);
+ IndentLevel current_level = indent_level.back()->get();
+ indent_level.push_back(current_level);
return true;
//_set_error("newline expected after ':'.");
//return false;
@@ -105,12 +105,19 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) {
} else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) {
int indent = tokenizer->get_token_line_indent();
- int current = tab_level.back()->get();
- if (indent <= current) {
+ int tabs = tokenizer->get_token_line_tab_indent();
+ IndentLevel current_level = indent_level.back()->get();
+ IndentLevel new_indent(indent, tabs);
+ if (new_indent.is_mixed(current_level)) {
+ _set_error("Mixed tabs and spaces in indentation.");
return false;
}
- tab_level.push_back(indent);
+ if (indent <= current_level.indent) {
+ return false;
+ }
+
+ indent_level.push_back(new_indent);
tokenizer->advance();
return true;
@@ -858,11 +865,23 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (current_function) {
int arg_idx = current_function->arguments.find(identifier);
if (arg_idx != -1) {
- if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
- // Assignment is not really usage
- current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] - 1;
- } else {
- current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1;
+ switch (tokenizer->get_token()) {
+ case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
+ case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
+ case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
+ case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SUB:
+ case GDScriptTokenizer::TK_OP_ASSIGN: {
+ // Assignment is not really usage
+ } break;
+ default: {
+ current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1;
+ }
}
}
}
@@ -1207,7 +1226,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (_get_completable_identifier(COMPLETION_INDEX, identifier)) {
if (identifier == StringName()) {
- identifier = "@temp"; //so it parses allright
+ identifier = "@temp"; //so it parses alright
}
completion_node = op;
@@ -1399,9 +1418,6 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
unary = true;
break;
case OperatorNode::OP_NEG:
- priority = 1;
- unary = true;
- break;
case OperatorNode::OP_POS:
priority = 1;
unary = true;
@@ -2216,7 +2232,7 @@ GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) {
}
void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) {
- int indent_level = tab_level.back()->get();
+ IndentLevel current_level = indent_level.back()->get();
p_block->has_return = true;
@@ -2231,13 +2247,11 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
if (error_set)
return;
- if (indent_level > tab_level.back()->get()) {
+ if (current_level.indent > indent_level.back()->get().indent) {
break; // go back a level
}
- if (pending_newline != -1) {
- pending_newline = -1;
- }
+ pending_newline = -1;
PatternBranchNode *branch = alloc_node<PatternBranchNode>();
branch->body = alloc_node<BlockNode>();
@@ -2688,7 +2702,7 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
- int indent_level = tab_level.back()->get();
+ IndentLevel current_level = indent_level.back()->get();
#ifdef DEBUG_ENABLED
@@ -2701,9 +2715,13 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
bool is_first_line = true;
while (true) {
- if (!is_first_line && tab_level.back()->prev() && tab_level.back()->prev()->get() == indent_level) {
+ if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) {
+ if (indent_level.back()->prev()->get().is_mixed(current_level)) {
+ _set_error("Mixed tabs and spaces in indentation.");
+ return;
+ }
// pythonic single-line expression, don't parse future lines
- tab_level.pop_back();
+ indent_level.pop_back();
p_block->end_line = tokenizer->get_token_line();
return;
}
@@ -2713,7 +2731,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (error_set)
return;
- if (indent_level > tab_level.back()->get()) {
+ if (current_level.indent > indent_level.back()->get().indent) {
p_block->end_line = tokenizer->get_token_line();
return; //go back a level
}
@@ -2846,15 +2864,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
assigned = subexpr;
} else {
- ConstantNode *c = alloc_node<ConstantNode>();
- if (lv->datatype.has_type && lv->datatype.kind == DataType::BUILTIN) {
- Variant::CallError err;
- c->value = Variant::construct(lv->datatype.builtin_type, NULL, 0, err);
- } else {
- c->value = Variant();
- }
- c->line = var_line;
- assigned = c;
+ assigned = _get_default_value_for_type(lv->datatype, var_line);
}
lv->assign = assigned;
//must be added later, to avoid self-referencing.
@@ -2925,14 +2935,14 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline())
;
- if (tab_level.back()->get() < indent_level) { //not at current indent level
+ if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level
p_block->end_line = tokenizer->get_token_line();
return;
}
if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELIF) {
- if (tab_level.back()->get() > indent_level) {
+ if (indent_level.back()->get().indent > current_level.indent) {
_set_error("Invalid indentation.");
return;
@@ -2980,7 +2990,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) {
- if (tab_level.back()->get() > indent_level) {
+ if (indent_level.back()->get().indent > current_level.indent) {
_set_error("Invalid indentation.");
return;
}
@@ -3280,15 +3290,36 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
case GDScriptTokenizer::TK_PR_ASSERT: {
tokenizer->advance();
- Node *condition = _parse_and_reduce_expression(p_block, p_static);
- if (!condition) {
- if (_recover_from_completion()) {
- break;
- }
+
+ if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
+ _set_error("Expected '(' after assert");
+ return;
+ }
+
+ tokenizer->advance();
+
+ Vector<Node *> args;
+ const bool result = _parse_arguments(p_block, args, p_static);
+ if (!result) {
return;
}
+
+ if (args.empty() || args.size() > 2) {
+ _set_error("Wrong number of arguments, expected 1 or 2");
+ return;
+ }
+
AssertNode *an = alloc_node<AssertNode>();
- an->condition = condition;
+ an->condition = _reduce_expression(args[0], p_static);
+
+ if (args.size() == 2) {
+ an->message = _reduce_expression(args[1], p_static);
+ } else {
+ ConstantNode *message_node = alloc_node<ConstantNode>();
+ message_node->value = String();
+ an->message = message_node;
+ }
+
p_block->statements.push_back(an);
if (!_end_statement()) {
@@ -3331,32 +3362,45 @@ bool GDScriptParser::_parse_newline() {
if (tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) {
+ IndentLevel current_level = indent_level.back()->get();
int indent = tokenizer->get_token_line_indent();
- int current_indent = tab_level.back()->get();
+ int tabs = tokenizer->get_token_line_tab_indent();
+ IndentLevel new_level(indent, tabs);
+
+ if (new_level.is_mixed(current_level)) {
+ _set_error("Mixed tabs and spaces in indentation.");
+ return false;
+ }
- if (indent > current_indent) {
+ if (indent > current_level.indent) {
_set_error("Unexpected indentation.");
return false;
}
- if (indent < current_indent) {
+ if (indent < current_level.indent) {
- while (indent < current_indent) {
+ while (indent < current_level.indent) {
//exit block
- if (tab_level.size() == 1) {
+ if (indent_level.size() == 1) {
_set_error("Invalid indentation. Bug?");
return false;
}
- tab_level.pop_back();
+ indent_level.pop_back();
- if (tab_level.back()->get() < indent) {
+ if (indent_level.back()->get().indent < indent) {
_set_error("Unindent does not match any outer indentation level.");
return false;
}
- current_indent = tab_level.back()->get();
+
+ if (indent_level.back()->get().is_mixed(current_level)) {
+ _set_error("Mixed tabs and spaces in indentation.");
+ return false;
+ }
+
+ current_level = indent_level.back()->get();
}
tokenizer->advance();
@@ -3454,7 +3498,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) {
void GDScriptParser::_parse_class(ClassNode *p_class) {
- int indent_level = tab_level.back()->get();
+ IndentLevel current_level = indent_level.back()->get();
while (true) {
@@ -3462,7 +3506,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (error_set)
return;
- if (indent_level > tab_level.back()->get()) {
+ if (current_level.indent > indent_level.back()->get().indent) {
p_class->end_line = tokenizer->get_token_line();
return; //go back a level
}
@@ -3533,6 +3577,15 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
return;
}
+ if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) {
+ const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name);
+ if (autoload_path.begins_with("*")) {
+ // It's a singleton, and not just a regular AutoLoad script.
+ _set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error.");
+ }
+ return;
+ }
+
tokenizer->advance(2);
if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
@@ -4839,35 +4892,29 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
return;
}
- Variant::Type initial_type = member.data_type.has_type ? member.data_type.builtin_type : member._export.type;
-
- if (initial_type != Variant::NIL && initial_type != Variant::OBJECT) {
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = member.identifier;
+ Node *expr;
- Node *expr;
+ if (member.data_type.has_type) {
+ expr = _get_default_value_for_type(member.data_type);
+ } else {
+ DataType exported_type;
+ exported_type.has_type = true;
+ exported_type.kind = DataType::BUILTIN;
+ exported_type.builtin_type = member._export.type;
+ expr = _get_default_value_for_type(exported_type);
+ }
- // Make sure arrays and dictionaries are not shared
- if (initial_type == Variant::ARRAY) {
- expr = alloc_node<ArrayNode>();
- } else if (initial_type == Variant::DICTIONARY) {
- expr = alloc_node<DictionaryNode>();
- } else {
- ConstantNode *cn = alloc_node<ConstantNode>();
- Variant::CallError ce2;
- cn->value = Variant::construct(initial_type, NULL, 0, ce2);
- expr = cn;
- }
+ IdentifierNode *id = alloc_node<IdentifierNode>();
+ id->name = member.identifier;
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_INIT_ASSIGN;
- op->arguments.push_back(id);
- op->arguments.push_back(expr);
+ OperatorNode *op = alloc_node<OperatorNode>();
+ op->op = OperatorNode::OP_INIT_ASSIGN;
+ op->arguments.push_back(id);
+ op->arguments.push_back(expr);
- p_class->initializer->statements.push_back(op);
+ p_class->initializer->statements.push_back(op);
- member.initial_assignment = op;
- }
+ member.initial_assignment = op;
}
if (autoexport && member.data_type.has_type) {
@@ -5727,28 +5774,35 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source,
}
}
- // Still look for class constants in parent script
+ // Still look for class constants in parent scripts
if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) {
Ref<Script> scr = base_type.script_type;
ERR_FAIL_COND_V(scr.is_null(), result);
- Map<StringName, Variant> constants;
- scr->get_constants(&constants);
+ while (scr.is_valid()) {
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
- if (constants.has(id)) {
- Ref<GDScript> gds = constants[id];
+ if (constants.has(id)) {
+ Ref<GDScript> gds = constants[id];
- if (gds.is_valid()) {
- result.kind = DataType::GDSCRIPT;
- result.script_type = gds;
- found = true;
- } else {
- Ref<Script> scr2 = constants[id];
- if (scr2.is_valid()) {
- result.kind = DataType::SCRIPT;
- result.script_type = scr2;
+ if (gds.is_valid()) {
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
found = true;
+ } else {
+ Ref<Script> scr2 = constants[id];
+ if (scr2.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = scr2;
+ found = true;
+ }
}
}
+ if (found) {
+ break;
+ } else {
+ scr = scr->get_base_script();
+ }
}
}
@@ -6091,12 +6145,18 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data
break;
}
+ // Some classes are prefixed with `_` internally
+ if (!ClassDB::class_exists(expr_native)) {
+ expr_native = "_" + expr_native;
+ }
+
switch (p_container.kind) {
case DataType::NATIVE: {
if (p_container.is_meta_type) {
return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static());
} else {
- return ClassDB::is_parent_class(expr_native, p_container.native_type);
+ StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type);
+ return ClassDB::is_parent_class(expr_native, container_native);
}
} break;
case DataType::SCRIPT:
@@ -6140,6 +6200,31 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data
return false;
}
+GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) {
+ Node *result;
+
+ if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) {
+ if (p_type.builtin_type == Variant::ARRAY) {
+ result = alloc_node<ArrayNode>();
+ } else if (p_type.builtin_type == Variant::DICTIONARY) {
+ result = alloc_node<DictionaryNode>();
+ } else {
+ ConstantNode *c = alloc_node<ConstantNode>();
+ Variant::CallError err;
+ c->value = Variant::construct(p_type.builtin_type, NULL, 0, err);
+ result = c;
+ }
+ } else {
+ ConstantNode *c = alloc_node<ConstantNode>();
+ c->value = Variant();
+ result = c;
+ }
+
+ result->line = p_line;
+
+ return result;
+}
+
GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
#ifdef DEBUG_ENABLED
if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) {
@@ -6655,7 +6740,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
}
}
- p_node->set_datatype(_resolve_type(node_type, p_node->line));
+ node_type = _resolve_type(node_type, p_node->line);
+ p_node->set_datatype(node_type);
return node_type;
}
@@ -8500,8 +8586,8 @@ void GDScriptParser::clear() {
validating = false;
for_completion = false;
error_set = false;
- tab_level.clear();
- tab_level.push_back(0);
+ indent_level.clear();
+ indent_level.push_back(IndentLevel(0, 0));
error_line = 0;
error_column = 0;
pending_newline = -1;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 72aa819a8c..93557d745d 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -481,7 +481,12 @@ public:
struct AssertNode : public Node {
Node *condition;
- AssertNode() { type = TYPE_ASSERT; }
+ Node *message;
+ AssertNode() :
+ condition(0),
+ message(0) {
+ type = TYPE_ASSERT;
+ }
};
struct BreakpointNode : public Node {
@@ -547,7 +552,27 @@ private:
int pending_newline;
- List<int> tab_level;
+ struct IndentLevel {
+ int indent;
+ int tabs;
+
+ bool is_mixed(IndentLevel other) {
+ return (
+ (indent == other.indent && tabs != other.tabs) ||
+ (indent > other.indent && tabs < other.tabs) ||
+ (indent < other.indent && tabs > other.tabs));
+ }
+
+ IndentLevel() :
+ indent(0),
+ tabs(0) {}
+
+ IndentLevel(int p_indent, int p_tabs) :
+ indent(p_indent),
+ tabs(p_tabs) {}
+ };
+
+ List<IndentLevel> indent_level;
String base_path;
String self_path;
@@ -610,6 +635,7 @@ private:
bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const;
bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const;
bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const;
+ Node *_get_default_value_for_type(const DataType &p_type, int p_line = -1);
DataType _reduce_node_type(Node *p_node);
DataType _reduce_function_call_type(const OperatorNode *p_call);
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 64b354bdb8..23a86f8d2b 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -450,11 +450,11 @@ void GDScriptTokenizerText::_make_error(const String &p_error) {
tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
}
-void GDScriptTokenizerText::_make_newline(int p_spaces) {
+void GDScriptTokenizerText::_make_newline(int p_indentation, int p_tabs) {
TokenData &tk = tk_rb[tk_rb_pos];
tk.type = TK_NEWLINE;
- tk.constant = p_spaces;
+ tk.constant = Vector2(p_indentation, p_tabs);
tk.line = line;
tk.col = column;
tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
@@ -511,33 +511,6 @@ void GDScriptTokenizerText::_advance() {
case ' ':
INCPOS(1);
continue;
- case '\n': {
- line++;
- INCPOS(1);
- column = 1;
- int i = 0;
- while (true) {
- if (GETCHAR(i) == ' ') {
- if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_SPACES;
- if (file_indent_type != INDENT_SPACES) {
- _make_error("Spaces used for indentation in tab-indented file!");
- return;
- }
- } else if (GETCHAR(i) == '\t') {
- if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_TABS;
- if (file_indent_type != INDENT_TABS) {
- _make_error("Tabs used for indentation in space-indented file!");
- return;
- }
- } else {
- break; // not indentation anymore
- }
- i++;
- }
-
- _make_newline(i);
- return;
- }
case '#': { // line comment skip
#ifdef DEBUG_ENABLED
String comment;
@@ -565,33 +538,34 @@ void GDScriptTokenizerText::_advance() {
ignore_warnings = true;
}
#endif // DEBUG_ENABLED
+ FALLTHROUGH;
+ }
+ case '\n': {
+ line++;
INCPOS(1);
+ bool used_spaces = false;
+ int tabs = 0;
column = 1;
- line++;
int i = 0;
while (true) {
if (GETCHAR(i) == ' ') {
- if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_SPACES;
- if (file_indent_type != INDENT_SPACES) {
- _make_error("Spaces used for indentation in tab-indented file!");
- return;
- }
+ i++;
+ used_spaces = true;
} else if (GETCHAR(i) == '\t') {
- if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_TABS;
- if (file_indent_type != INDENT_TABS) {
- _make_error("Tabs used for indentation in space-indented file!");
+ if (used_spaces) {
+ _make_error("Spaces used before tabs on a line");
return;
}
+ i++;
+ tabs++;
} else {
break; // not indentation anymore
}
- i++;
}
- _make_newline(i);
+ _make_newline(i, tabs);
return;
-
- } break;
+ }
case '/': {
switch (GETCHAR(1)) {
@@ -849,12 +823,8 @@ void GDScriptTokenizerText::_advance() {
_make_error("Unterminated String");
return;
}
- if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
- _make_error("Malformed hex constant in string");
- return;
- }
- CharType v;
+ CharType v = 0;
if (c >= '0' && c <= '9') {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
@@ -864,8 +834,8 @@ void GDScriptTokenizerText::_advance() {
v = c - 'A';
v += 10;
} else {
- ERR_PRINT("BUG");
- v = 0;
+ _make_error("Malformed hex constant in string");
+ return;
}
res <<= 4;
@@ -1112,7 +1082,6 @@ void GDScriptTokenizerText::set_code(const String &p_code) {
ignore_warnings = false;
#endif // DEBUG_ENABLED
last_error = "";
- file_indent_type = INDENT_NONE;
for (int i = 0; i < MAX_LOOKAHEAD + 1; i++)
_advance();
}
@@ -1187,7 +1156,17 @@ int GDScriptTokenizerText::get_token_line_indent(int p_offset) const {
int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0);
- return tk_rb[ofs].constant;
+ return tk_rb[ofs].constant.operator Vector2().x;
+}
+
+int GDScriptTokenizerText::get_token_line_tab_indent(int p_offset) const {
+
+ ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0);
+ ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0);
+
+ int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
+ ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0);
+ return tk_rb[ofs].constant.operator Vector2().y;
}
String GDScriptTokenizerText::get_token_error(int p_offset) const {
@@ -1425,7 +1404,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code)
int len;
// Objects cannot be constant, never encode objects
Error err = encode_variant(E->get(), NULL, len, false);
- ERR_FAIL_COND_V(err != OK, Vector<uint8_t>());
+ ERR_FAIL_COND_V_MSG(err != OK, Vector<uint8_t>(), "Error when trying to encode Variant.");
int pos = buf.size();
buf.resize(pos + len);
encode_variant(E->get(), &buf.write[pos], len, false);
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 89d586b912..58749012b7 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -168,6 +168,7 @@ public:
virtual int get_token_line(int p_offset = 0) const = 0;
virtual int get_token_column(int p_offset = 0) const = 0;
virtual int get_token_line_indent(int p_offset = 0) const = 0;
+ virtual int get_token_line_tab_indent(int p_offset = 0) const = 0;
virtual String get_token_error(int p_offset = 0) const = 0;
virtual void advance(int p_amount = 1) = 0;
#ifdef DEBUG_ENABLED
@@ -205,7 +206,7 @@ class GDScriptTokenizerText : public GDScriptTokenizer {
};
void _make_token(Token p_type);
- void _make_newline(int p_spaces = 0);
+ void _make_newline(int p_indentation = 0, int p_tabs = 0);
void _make_identifier(const StringName &p_identifier);
void _make_built_in_func(GDScriptFunctions::Function p_func);
void _make_constant(const Variant &p_constant);
@@ -222,11 +223,6 @@ class GDScriptTokenizerText : public GDScriptTokenizer {
int tk_rb_pos;
String last_error;
bool error_flag;
- enum {
- INDENT_NONE,
- INDENT_SPACES,
- INDENT_TABS,
- } file_indent_type;
#ifdef DEBUG_ENABLED
Vector<Pair<int, String> > warning_skips;
@@ -245,6 +241,7 @@ public:
virtual int get_token_line(int p_offset = 0) const;
virtual int get_token_column(int p_offset = 0) const;
virtual int get_token_line_indent(int p_offset = 0) const;
+ virtual int get_token_line_tab_indent(int p_offset = 0) const;
virtual const Variant &get_token_constant(int p_offset = 0) const;
virtual String get_token_error(int p_offset = 0) const;
virtual void advance(int p_amount = 1);
@@ -283,6 +280,7 @@ public:
virtual int get_token_line(int p_offset = 0) const;
virtual int get_token_column(int p_offset = 0) const;
virtual int get_token_line_indent(int p_offset = 0) const;
+ virtual int get_token_line_tab_indent(int p_offset = 0) const { return 0; }
virtual const Variant &get_token_constant(int p_offset = 0) const;
virtual String get_token_error(int p_offset = 0) const;
virtual void advance(int p_amount = 1);
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index 45f9ec9c6a..6b5c26ec81 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -105,6 +105,40 @@ void ExtendGDScriptParser::update_symbols() {
}
}
+void ExtendGDScriptParser::update_document_links(const String &p_code) {
+ document_links.clear();
+
+ GDScriptTokenizerText tokenizer;
+ FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);
+ tokenizer.set_code(p_code);
+ while (true) {
+ if (tokenizer.get_token() == GDScriptTokenizer::TK_EOF) {
+ break;
+ } else if (tokenizer.get_token() == GDScriptTokenizer::TK_CONSTANT) {
+ Variant const_val = tokenizer.get_token_constant();
+ if (const_val.get_type() == Variant::STRING) {
+ String path = const_val;
+ bool exists = fs->file_exists(path);
+ if (!exists) {
+ path = get_path().get_base_dir() + "/" + path;
+ exists = fs->file_exists(path);
+ }
+ if (exists) {
+ String value = const_val;
+ lsp::DocumentLink link;
+ link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
+ link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line());
+ link.range.end.line = link.range.start.line;
+ link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column());
+ link.range.start.character = link.range.end.character - value.length();
+ document_links.push_back(link);
+ }
+ }
+ }
+ tokenizer.advance();
+ }
+}
+
void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) {
const String uri = get_uri();
@@ -123,7 +157,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
r_symbol.selectionRange.start.line = r_symbol.range.start.line;
r_symbol.detail = "class " + r_symbol.name;
bool is_root_class = &r_symbol == &class_symbol;
- r_symbol.documentation = parse_documentation_as_markdown(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class);
+ r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class);
for (int i = 0; i < p_class->variables.size(); ++i) {
@@ -150,7 +184,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.detail += " = " + JSON::print(m.default_value);
}
- symbol.documentation = parse_documentation_as_markdown(line);
+ symbol.documentation = parse_documentation(line);
symbol.uri = uri;
symbol.script_path = path;
@@ -170,7 +204,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.range.end.line = symbol.range.start.line;
symbol.range.end.character = lines[line].length();
symbol.selectionRange.start.line = symbol.range.start.line;
- symbol.documentation = parse_documentation_as_markdown(line);
+ symbol.documentation = parse_documentation(line);
symbol.uri = uri;
symbol.script_path = path;
symbol.detail = "signal " + signal.name + "(";
@@ -189,6 +223,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
lsp::DocumentSymbol symbol;
const GDScriptParser::ClassNode::Constant &c = E->value();
const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
+ ERR_FAIL_COND(!node);
symbol.name = E->key();
symbol.kind = lsp::SymbolKind::Constant;
symbol.deprecated = false;
@@ -198,7 +233,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.range.end.line = symbol.range.start.line;
symbol.range.end.character = lines[line].length();
symbol.selectionRange.start.line = symbol.range.start.line;
- symbol.documentation = parse_documentation_as_markdown(line);
+ symbol.documentation = parse_documentation(line);
symbol.uri = uri;
symbol.script_path = path;
@@ -263,10 +298,10 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
const int line = LINE_NUMBER_TO_INDEX(p_func->line);
r_symbol.range.start.line = line;
r_symbol.range.start.character = p_func->column;
- r_symbol.range.end.line = MAX(p_func->body->end_line - 2, p_func->body->line);
+ r_symbol.range.end.line = MAX(p_func->body->end_line - 2, r_symbol.range.start.line);
r_symbol.range.end.character = lines[r_symbol.range.end.line].length();
r_symbol.selectionRange.start.line = r_symbol.range.start.line;
- r_symbol.documentation = parse_documentation_as_markdown(line);
+ r_symbol.documentation = parse_documentation(line);
r_symbol.uri = uri;
r_symbol.script_path = path;
@@ -324,67 +359,11 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) {
symbol.detail += ": " + var->datatype.to_string();
}
- symbol.documentation = parse_documentation_as_markdown(line);
+ symbol.documentation = parse_documentation(line);
r_symbol.children.push_back(symbol);
}
}
-String ExtendGDScriptParser::marked_documentation(const String &p_bbcode) {
-
- String markdown = p_bbcode.strip_edges();
-
- Vector<String> lines = markdown.split("\n");
- bool in_code_block = false;
- int code_block_indent = -1;
-
- markdown = "";
- for (int i = 0; i < lines.size(); i++) {
- String line = lines[i];
- int block_start = line.find("[codeblock]");
- if (block_start != -1) {
- code_block_indent = block_start;
- in_code_block = true;
- line = "'''gdscript";
- line = "\n";
- } else if (in_code_block) {
- line = "\t" + line.substr(code_block_indent, line.length());
- }
-
- if (in_code_block && line.find("[/codeblock]") != -1) {
- line = "'''\n";
- line = "\n";
- in_code_block = false;
- }
-
- if (!in_code_block) {
- line = line.strip_edges();
- line = line.replace("[code]", "`");
- line = line.replace("[/code]", "`");
- line = line.replace("[i]", "*");
- line = line.replace("[/i]", "*");
- line = line.replace("[b]", "**");
- line = line.replace("[/b]", "**");
- line = line.replace("[u]", "__");
- line = line.replace("[/u]", "__");
- line = line.replace("[method ", "`");
- line = line.replace("[member ", "`");
- line = line.replace("[signal ", "`");
- line = line.replace("[enum ", "`");
- line = line.replace("[constant ", "`");
- line = line.replace("[", "`");
- line = line.replace("]", "`");
- }
-
- if (!in_code_block && i < lines.size() - 1) {
- line += "\n\n";
- } else if (i < lines.size() - 1) {
- line += "\n";
- }
- markdown += line;
- }
- return markdown;
-}
-
String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {
ERR_FAIL_INDEX_V(p_line, lines.size(), String());
@@ -543,10 +522,6 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(i
return ret;
}
-String ExtendGDScriptParser::parse_documentation_as_markdown(int p_line, bool p_docs_down) {
- return marked_documentation(parse_documentation(p_line, p_docs_down));
-}
-
const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const {
if (p_line <= 0) {
return &class_symbol;
@@ -573,6 +548,10 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String
return NULL;
}
+const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const {
+ return document_links;
+}
+
const Array &ExtendGDScriptParser::get_member_completions() {
if (member_completions.empty()) {
@@ -674,6 +653,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
const GDScriptParser::ClassNode::Constant &c = E->value();
const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
+ ERR_FAIL_COND_V(!node, class_api);
Dictionary api;
api["name"] = E->key();
@@ -755,5 +735,6 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, NULL, false);
update_diagnostics();
update_symbols();
+ update_document_links(p_code);
return err;
}
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h
index dd0453d3ff..a6e0ca5534 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.h
+++ b/modules/gdscript/language_server/gdscript_extend_parser.h
@@ -56,12 +56,14 @@ class ExtendGDScriptParser : public GDScriptParser {
lsp::DocumentSymbol class_symbol;
Vector<lsp::Diagnostic> diagnostics;
+ List<lsp::DocumentLink> document_links;
ClassMembers members;
HashMap<String, ClassMembers> inner_classes;
void update_diagnostics();
void update_symbols();
+ void update_document_links(const String &p_code);
void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol);
void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol);
@@ -73,11 +75,6 @@ class ExtendGDScriptParser : public GDScriptParser {
Array member_completions;
- String parse_documentation_as_markdown(int p_line, bool p_docs_down = false);
-
-public:
- static String marked_documentation(const String &p_bbcode);
-
public:
_FORCE_INLINE_ const String &get_path() const { return path; }
_FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; }
@@ -93,6 +90,7 @@ public:
const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const;
const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const;
+ const List<lsp::DocumentLink> &get_document_links() const;
const Array &get_member_completions();
Dictionary generate_api() const;
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index afe461b68e..90646f73ba 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -100,9 +100,10 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
String root_uri = p_params["rootUri"];
String root = p_params["rootPath"];
- bool is_same_workspace = root == workspace->root;
+ bool is_same_workspace;
+#ifndef WINDOWS_ENABLED
is_same_workspace = root.to_lower() == workspace->root.to_lower();
-#ifdef WINDOWS_ENABLED
+#else
is_same_workspace = root.replace("\\", "/").to_lower() == workspace->root.to_lower();
#endif
@@ -133,6 +134,22 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
}
void GDScriptLanguageProtocol::initialized(const Variant &p_params) {
+
+ lsp::GodotCapabilities capabilities;
+
+ DocData *doc = EditorHelp::get_doc_data();
+ for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) {
+
+ lsp::GodotNativeClassInfo gdclass;
+ gdclass.name = E->get().name;
+ gdclass.class_doc = &(E->get());
+ if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E->get().name))) {
+ gdclass.class_info = ptr;
+ }
+ capabilities.native_classes.push_back(gdclass);
+ }
+
+ notify_client("gdscript/capabilities", capabilities.to_json());
}
void GDScriptLanguageProtocol::poll() {
@@ -142,6 +159,7 @@ void GDScriptLanguageProtocol::poll() {
Error GDScriptLanguageProtocol::start(int p_port) {
if (server == NULL) {
server = dynamic_cast<WebSocketServer *>(ClassDB::instance("WebSocketServer"));
+ ERR_FAIL_COND_V(!server, FAILED);
server->set_buffers(8192, 1024, 8192, 1024); // 8mb should be way more than enough
server->connect("data_received", this, "on_data_received");
server->connect("client_connected", this, "on_client_connected");
@@ -151,7 +169,13 @@ Error GDScriptLanguageProtocol::start(int p_port) {
}
void GDScriptLanguageProtocol::stop() {
+ const int *ptr = clients.next(NULL);
+ while (ptr) {
+ clients.get(*ptr)->close();
+ ptr = clients.next(ptr);
+ }
server->stop();
+ clients.clear();
}
void GDScriptLanguageProtocol::notify_all_clients(const String &p_method, const Variant &p_params) {
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index 9bea4557ac..8d58b99e02 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -56,15 +56,16 @@ void GDScriptLanguageServer::_notification(int p_what) {
void GDScriptLanguageServer::thread_main(void *p_userdata) {
GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
while (!self->thread_exit) {
+ // Poll 20 times per second
self->protocol.poll();
- OS::get_singleton()->delay_usec(10);
+ OS::get_singleton()->delay_usec(50000);
}
}
void GDScriptLanguageServer::start() {
int port = (int)_EDITOR_GET("network/language_server/remote_port");
if (protocol.start(port) == OK) {
- EditorNode::get_log()->add_message("** GDScript Language Server Started **");
+ EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR);
ERR_FAIL_COND(thread != NULL || thread_exit);
thread_exit = false;
thread = Thread::create(GDScriptLanguageServer::thread_main, this);
@@ -78,7 +79,7 @@ void GDScriptLanguageServer::stop() {
memdelete(thread);
thread = NULL;
protocol.stop();
- EditorNode::get_log()->add_message("** GDScript Language Server Stopped **");
+ EditorNode::get_log()->add_message("--- GDScript language server stopped ---", EditorLog::MSG_TYPE_EDITOR);
}
void register_lsp_types() {
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index f211fae526..b83db718b8 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -39,6 +39,7 @@
void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
+ ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol);
ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);
ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion);
ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve);
@@ -75,6 +76,11 @@ lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_
return doc;
}
+void GDScriptTextDocument::notify_client_show_symbol(const lsp::DocumentSymbol *symbol) {
+ ERR_FAIL_NULL(symbol);
+ GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true));
+}
+
void GDScriptTextDocument::initialize() {
if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
@@ -102,6 +108,21 @@ void GDScriptTextDocument::initialize() {
}
}
+Variant GDScriptTextDocument::nativeSymbol(const Dictionary &p_params) {
+
+ Variant ret;
+
+ lsp::NativeSymbolInspectParams params;
+ params.load(p_params);
+
+ if (const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_native_symbol(params)) {
+ ret = symbol->to_json(true);
+ notify_client_show_symbol(symbol);
+ }
+
+ return ret;
+}
+
Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) {
Dictionary params = p_params["textDocument"];
String uri = params["uri"];
@@ -271,8 +292,17 @@ Array GDScriptTextDocument::codeLens(const Dictionary &p_params) {
return arr;
}
-Variant GDScriptTextDocument::documentLink(const Dictionary &p_params) {
- Variant ret;
+Array GDScriptTextDocument::documentLink(const Dictionary &p_params) {
+ Array ret;
+
+ lsp::DocumentLinkParams params;
+ params.load(p_params);
+
+ List<lsp::DocumentLink> links;
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_document_links(params.textDocument.uri, links);
+ for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) {
+ ret.push_back(E->get().to_json());
+ }
return ret;
}
@@ -326,31 +356,35 @@ Array GDScriptTextDocument::definition(const Dictionary &p_params) {
const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri);
if (file_checker->file_exists(path)) {
arr.push_back(location.to_json());
- } else if (!symbol->native_class.empty() && GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) {
- String id;
- switch (symbol->kind) {
- case lsp::SymbolKind::Class:
- id = "class_name:" + symbol->name;
- break;
- case lsp::SymbolKind::Constant:
- id = "class_constant:" + symbol->native_class + ":" + symbol->name;
- break;
- case lsp::SymbolKind::Property:
- case lsp::SymbolKind::Variable:
- id = "class_property:" + symbol->native_class + ":" + symbol->name;
- break;
- case lsp::SymbolKind::Enum:
- id = "class_enum:" + symbol->native_class + ":" + symbol->name;
- break;
- case lsp::SymbolKind::Method:
- case lsp::SymbolKind::Function:
- id = "class_method:" + symbol->native_class + ":" + symbol->name;
- break;
- default:
- id = "class_global:" + symbol->native_class + ":" + symbol->name;
- break;
+ } else if (!symbol->native_class.empty()) {
+ if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) {
+ String id;
+ switch (symbol->kind) {
+ case lsp::SymbolKind::Class:
+ id = "class_name:" + symbol->name;
+ break;
+ case lsp::SymbolKind::Constant:
+ id = "class_constant:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Property:
+ case lsp::SymbolKind::Variable:
+ id = "class_property:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Enum:
+ id = "class_enum:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Method:
+ case lsp::SymbolKind::Function:
+ id = "class_method:" + symbol->native_class + ":" + symbol->name;
+ break;
+ default:
+ id = "class_global:" + symbol->native_class + ":" + symbol->name;
+ break;
+ }
+ call_deferred("show_native_symbol_in_editor", id);
+ } else {
+ notify_client_show_symbol(symbol);
}
- call_deferred("show_native_symbol_in_editor", id);
}
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
@@ -380,8 +414,8 @@ GDScriptTextDocument::~GDScriptTextDocument() {
memdelete(file_checker);
}
-void GDScriptTextDocument::sync_script_content(const String &p_uri, const String &p_content) {
- String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_uri);
+void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) {
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path);
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
}
diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h
index d15022d2c4..235e2c3f6e 100644
--- a/modules/gdscript/language_server/gdscript_text_document.h
+++ b/modules/gdscript/language_server/gdscript_text_document.h
@@ -52,14 +52,16 @@ protected:
private:
lsp::TextDocumentItem load_document_item(const Variant &p_param);
+ void notify_client_show_symbol(const lsp::DocumentSymbol *symbol);
public:
+ Variant nativeSymbol(const Dictionary &p_params);
Array documentSymbol(const Dictionary &p_params);
Array completion(const Dictionary &p_params);
Dictionary resolve(const Dictionary &p_params);
Array foldingRange(const Dictionary &p_params);
Array codeLens(const Dictionary &p_params);
- Variant documentLink(const Dictionary &p_params);
+ Array documentLink(const Dictionary &p_params);
Array colorPresentation(const Dictionary &p_params);
Variant hover(const Dictionary &p_params);
Array definition(const Dictionary &p_params);
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 1901daacff..c289ff6c07 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -198,7 +198,7 @@ Error GDScriptWorkspace::initialize() {
if (!class_data.inherits.empty()) {
class_symbol.detail += " extends " + class_data.inherits;
}
- class_symbol.documentation = ExtendGDScriptParser::marked_documentation(class_data.brief_description) + "\n" + ExtendGDScriptParser::marked_documentation(class_data.description);
+ class_symbol.documentation = class_data.brief_description + "\n" + class_data.description;
for (int i = 0; i < class_data.constants.size(); i++) {
const DocData::ConstantDoc &const_data = class_data.constants[i];
@@ -211,7 +211,7 @@ Error GDScriptWorkspace::initialize() {
symbol.detail += ": " + const_data.enumeration;
}
symbol.detail += " = " + const_data.value;
- symbol.documentation = ExtendGDScriptParser::marked_documentation(const_data.description);
+ symbol.documentation = const_data.description;
class_symbol.children.push_back(symbol);
}
@@ -232,7 +232,7 @@ Error GDScriptWorkspace::initialize() {
} else {
symbol.detail += ": " + data.type;
}
- symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description);
+ symbol.documentation = data.description;
class_symbol.children.push_back(symbol);
}
@@ -269,8 +269,12 @@ Error GDScriptWorkspace::initialize() {
params += params.empty() ? "..." : ", ...";
}
- symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + data.return_type;
- symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description);
+ String return_type = data.return_type;
+ if (return_type.empty()) {
+ return_type = "void";
+ }
+ symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type;
+ symbol.documentation = data.description;
class_symbol.children.push_back(symbol);
}
@@ -475,6 +479,33 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP
}
}
+const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) {
+
+ if (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(p_params.native_class)) {
+ const lsp::DocumentSymbol &symbol = E->get();
+ if (p_params.symbol_name.empty() || p_params.symbol_name == symbol.name) {
+ return &symbol;
+ }
+
+ for (int i = 0; i < symbol.children.size(); ++i) {
+ if (symbol.children[i].name == p_params.symbol_name) {
+ return &(symbol.children[i]);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) {
+ if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) {
+ const List<lsp::DocumentLink> &links = parser->get_document_links();
+ for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) {
+ r_list.push_back(E->get());
+ }
+ }
+}
+
Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {
Dictionary api;
if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) {
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index adce169d4b..a416ae1075 100644
--- a/modules/gdscript/language_server/gdscript_workspace.h
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -53,7 +53,6 @@ protected:
ExtendGDScriptParser *get_parse_successed_script(const String &p_path);
ExtendGDScriptParser *get_parse_result(const String &p_path);
- void strip_flat_symbols(const String &p_branch);
void list_script_files(const String &p_root_dir, List<String> &r_files);
public:
@@ -81,7 +80,8 @@ public:
const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false);
void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list);
-
+ const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params);
+ void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
Dictionary generate_script_api(const String &p_path);
GDScriptWorkspace();
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
index 3e57b6ee7e..a048af88bb 100644
--- a/modules/gdscript/language_server/lsp.hpp
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -31,12 +31,17 @@
#ifndef GODOT_LSP_H
#define GODOT_LSP_H
-#include "core/variant.h"
+#include "core/class_db.h"
+#include "core/list.h"
+#include "editor/doc/doc_data.h"
namespace lsp {
typedef String DocumentUri;
+/** Format BBCode documentation from DocData to markdown */
+static String marked_documentation(const String &p_bbcode);
+
/**
* Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
*/
@@ -199,6 +204,41 @@ struct TextDocumentPositionParams {
}
};
+struct DocumentLinkParams {
+ /**
+ * The document to provide document links for.
+ */
+ TextDocumentIdentifier textDocument;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ textDocument.load(p_params["textDocument"]);
+ }
+};
+
+/**
+ * A document link is a range in a text document that links to an internal or external resource, like another
+ * text document or a web site.
+ */
+struct DocumentLink {
+
+ /**
+ * The range this link applies to.
+ */
+ Range range;
+
+ /**
+ * The uri this link points to. If missing a resolve request is sent later.
+ */
+ DocumentUri target;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["range"] = range.to_json();
+ dict["target"] = target;
+ return dict;
+ }
+};
+
/**
* A textual edit applicable to a text document.
*/
@@ -247,6 +287,9 @@ struct Command {
}
};
+// Use namespace instead of enumeration to follow the LSP specifications
+// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
+
namespace TextDocumentSyncKind {
/**
* Documents should not be synced at all.
@@ -453,7 +496,7 @@ struct TextDocumentSyncOptions {
dict["willSave"] = willSave;
dict["openClose"] = openClose;
dict["change"] = change;
- dict["change"] = save.to_json();
+ dict["save"] = save.to_json();
return dict;
}
};
@@ -557,6 +600,7 @@ struct TextDocumentContentChangeEvent {
}
};
+// Use namespace instead of enumeration to follow the LSP specifications
namespace DiagnosticSeverity {
/**
* Reports an error.
@@ -657,6 +701,7 @@ struct Diagnostic {
}
};
+// Use namespace instead of enumeration to follow the LSP specifications
/**
* Describes the content type that a client supports in various
* result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
@@ -721,6 +766,9 @@ struct MarkupContent {
}
};
+// Use namespace instead of enumeration to follow the LSP specifications
+// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
+// And here C++ compilers are unhappy with our enumeration name like Color, File, Reference etc.
/**
* The kind of a completion entry.
*/
@@ -752,6 +800,7 @@ static const int Operator = 24;
static const int TypeParameter = 25;
}; // namespace CompletionItemKind
+// Use namespace instead of enumeration to follow the LSP specifications
/**
* Defines whether the insert text in a completion item should be interpreted as
* plain text or a snippet.
@@ -944,36 +993,39 @@ struct CompletionList {
Vector<CompletionItem> items;
};
+// Use namespace instead of enumeration to follow the LSP specifications
+// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
+// And here C++ compilers are unhappy with our enumeration name like String, Array, Object etc
/**
* A symbol kind.
*/
namespace SymbolKind {
-static const int File = 1;
-static const int Module = 2;
-static const int Namespace = 3;
-static const int Package = 4;
-static const int Class = 5;
-static const int Method = 6;
-static const int Property = 7;
-static const int Field = 8;
-static const int Constructor = 9;
-static const int Enum = 10;
-static const int Interface = 11;
-static const int Function = 12;
-static const int Variable = 13;
-static const int Constant = 14;
-static const int String = 15;
-static const int Number = 16;
-static const int Boolean = 17;
-static const int Array = 18;
-static const int Object = 19;
-static const int Key = 20;
-static const int Null = 21;
-static const int EnumMember = 22;
-static const int Struct = 23;
-static const int Event = 24;
-static const int Operator = 25;
-static const int TypeParameter = 26;
+static const int File = 0;
+static const int Module = 1;
+static const int Namespace = 2;
+static const int Package = 3;
+static const int Class = 4;
+static const int Method = 5;
+static const int Property = 6;
+static const int Field = 7;
+static const int Constructor = 8;
+static const int Enum = 9;
+static const int Interface = 10;
+static const int Function = 11;
+static const int Variable = 12;
+static const int Constant = 13;
+static const int String = 14;
+static const int Number = 15;
+static const int Boolean = 16;
+static const int Array = 17;
+static const int Object = 18;
+static const int Key = 19;
+static const int Null = 20;
+static const int EnumMember = 21;
+static const int Struct = 22;
+static const int Event = 23;
+static const int Operator = 24;
+static const int TypeParameter = 25;
}; // namespace SymbolKind
/**
@@ -1099,7 +1151,7 @@ struct DocumentSymbol {
*/
Vector<DocumentSymbol> children;
- _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary to_json(bool with_doc = false) const {
Dictionary dict;
dict["name"] = name;
dict["detail"] = detail;
@@ -1107,10 +1159,14 @@ struct DocumentSymbol {
dict["deprecated"] = deprecated;
dict["range"] = range.to_json();
dict["selectionRange"] = selectionRange.to_json();
+ if (with_doc) {
+ dict["documentation"] = documentation;
+ dict["native_class"] = native_class;
+ }
Array arr;
arr.resize(children.size());
for (int i = 0; i < children.size(); i++) {
- arr[i] = children[i].to_json();
+ arr[i] = children[i].to_json(with_doc);
}
dict["children"] = arr;
return dict;
@@ -1142,7 +1198,7 @@ struct DocumentSymbol {
markdown.value = "\t" + detail + "\n\n";
}
if (documentation.length()) {
- markdown.value += documentation + "\n\n";
+ markdown.value += marked_documentation(documentation) + "\n\n";
}
if (script_path.length()) {
markdown.value += "Defined in [" + script_path + "](" + uri + ")";
@@ -1194,6 +1250,17 @@ struct DocumentSymbol {
}
};
+struct NativeSymbolInspectParams {
+
+ String native_class;
+ String symbol_name;
+
+ void load(const Dictionary &p_params) {
+ native_class = p_params["native_class"];
+ symbol_name = p_params["symbol_name"];
+ }
+};
+
/**
* Enum of known range kinds
*/
@@ -1254,6 +1321,7 @@ struct FoldingRange {
}
};
+// Use namespace instead of enumeration to follow the LSP specifications
/**
* How a completion was triggered
*/
@@ -1501,6 +1569,94 @@ struct InitializeResult {
}
};
+struct GodotNativeClassInfo {
+
+ String name;
+ const DocData::ClassDoc *class_doc = NULL;
+ const ClassDB::ClassInfo *class_info = NULL;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["name"] = name;
+ dict["inherits"] = class_doc->inherits;
+ return dict;
+ }
+};
+
+/** Features not included in the standart lsp specifications */
+struct GodotCapabilities {
+
+ /**
+ * Native class list
+ */
+ List<GodotNativeClassInfo> native_classes;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ Array classes;
+ for (List<GodotNativeClassInfo>::Element *E = native_classes.front(); E; E = E->next()) {
+ classes.push_back(E->get().to_json());
+ }
+ dict["native_classes"] = classes;
+ return dict;
+ }
+};
+
+/** Format BBCode documentation from DocData to markdown */
+static String marked_documentation(const String &p_bbcode) {
+
+ String markdown = p_bbcode.strip_edges();
+
+ Vector<String> lines = markdown.split("\n");
+ bool in_code_block = false;
+ int code_block_indent = -1;
+
+ markdown = "";
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines[i];
+ int block_start = line.find("[codeblock]");
+ if (block_start != -1) {
+ code_block_indent = block_start;
+ in_code_block = true;
+ line = "\n";
+ } else if (in_code_block) {
+ line = "\t" + line.substr(code_block_indent, line.length());
+ }
+
+ if (in_code_block && line.find("[/codeblock]") != -1) {
+ line = "\n";
+ in_code_block = false;
+ }
+
+ if (!in_code_block) {
+ line = line.strip_edges();
+ line = line.replace("[code]", "`");
+ line = line.replace("[/code]", "`");
+ line = line.replace("[i]", "*");
+ line = line.replace("[/i]", "*");
+ line = line.replace("[b]", "**");
+ line = line.replace("[/b]", "**");
+ line = line.replace("[u]", "__");
+ line = line.replace("[/u]", "__");
+ line = line.replace("[method ", "`");
+ line = line.replace("[member ", "`");
+ line = line.replace("[signal ", "`");
+ line = line.replace("[enum ", "`");
+ line = line.replace("[constant ", "`");
+ line = line.replace("[", "`");
+ line = line.replace("]", "`");
+ }
+
+ if (!in_code_block && i < lines.size() - 1) {
+ line += "\n\n";
+ } else if (i < lines.size() - 1) {
+ line += "\n";
+ }
+ markdown += line;
+ }
+ return markdown;
+}
+
} // namespace lsp
#endif
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index 1bd3d72066..b762868f2c 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -217,6 +217,14 @@
Deprecated, use [member mesh_library] instead.
</member>
</members>
+ <signals>
+ <signal name="cell_size_changed">
+ <argument index="0" name="cell_size" type="Vector3">
+ </argument>
+ <description>
+ </description>
+ </signal>
+ </signals>
<constants>
<constant name="INVALID_CELL_ITEM" value="-1">
Invalid cell item that can be used in [method set_cell_item] to clear cells (or represent an empty cell in [method get_cell_item]).
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index b36afd4386..47ac0de7f9 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -227,10 +227,10 @@ Ref<MeshLibrary> GridMap::get_mesh_library() const {
}
void GridMap::set_cell_size(const Vector3 &p_size) {
-
ERR_FAIL_COND(p_size.x < 0.001 || p_size.y < 0.001 || p_size.z < 0.001);
cell_size = p_size;
_recreate_octant_data();
+ emit_signal("cell_size_changed", cell_size);
}
Vector3 GridMap::get_cell_size() const {
@@ -902,6 +902,8 @@ void GridMap::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
BIND_CONSTANT(INVALID_CELL_ITEM);
+
+ ADD_SIGNAL(MethodInfo("cell_size_changed", PropertyInfo(Variant::VECTOR3, "cell_size")));
}
void GridMap::set_clip(bool p_enabled, bool p_clip_above, int p_floor, Vector3::Axis p_axis) {
diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp
index 07b4f7f596..1bd570c55f 100644
--- a/modules/gridmap/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/grid_map_editor_plugin.cpp
@@ -40,11 +40,8 @@
void GridMapEditor::_node_removed(Node *p_node) {
- if (p_node == node) {
+ if (p_node == node)
node = NULL;
- hide();
- mesh_library_palette->hide();
- }
}
void GridMapEditor::_configure() {
@@ -279,6 +276,7 @@ void GridMapEditor::_update_cursor_transform() {
cursor_transform = Transform();
cursor_transform.origin = cursor_origin;
cursor_transform.basis.set_orthogonal_index(cursor_rot);
+ cursor_transform.basis *= node->get_cell_scale();
cursor_transform = node->get_global_transform() * cursor_transform;
if (cursor_instance.is_valid()) {
@@ -301,7 +299,7 @@ void GridMapEditor::_update_selection_transform() {
}
Transform xf;
- xf.scale(Vector3(1, 1, 1) * (Vector3(1, 1, 1) + (selection.end - selection.begin)) * node->get_cell_size());
+ xf.scale((Vector3(1, 1, 1) + (selection.end - selection.begin)) * node->get_cell_size());
xf.origin = selection.begin * node->get_cell_size();
VisualServer::get_singleton()->instance_set_transform(selection_instance, node->get_global_transform() * xf);
@@ -353,7 +351,14 @@ void GridMapEditor::_set_selection(bool p_active, const Vector3 &p_begin, const
selection.click = p_begin;
selection.current = p_end;
- _update_selection_transform();
+ if (is_visible_in_tree()) {
+ _update_selection_transform();
+ }
+
+ options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CLEAR), !selection.active);
+ options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CUT), !selection.active);
+ options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_DUPLICATE), !selection.active);
+ options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_FILL), !selection.active);
}
bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click) {
@@ -385,8 +390,8 @@ bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, boo
if (!p.intersects_segment(from, from + normal * settings_pick_distance->get_value(), &inters))
return false;
- //make sure the intersection is inside the frustum planes, to avoid
- //painting on invisible regions
+ // Make sure the intersection is inside the frustum planes, to avoid
+ // Painting on invisible regions.
for (int i = 0; i < planes.size(); i++) {
Plane fp = local_xform.xform(planes[i]);
@@ -397,8 +402,6 @@ bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, boo
int cell[3];
float cell_size[3] = { node->get_cell_size().x, node->get_cell_size().y, node->get_cell_size().z };
- last_mouseover = Vector3(-1, -1, -1);
-
for (int i = 0; i < 3; i++) {
if (i == edit_axis)
@@ -407,19 +410,11 @@ bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, boo
cell[i] = inters[i] / node->get_cell_size()[i];
if (inters[i] < 0)
- cell[i] -= 1; //compensate negative
+ cell[i] -= 1; // Compensate negative.
grid_ofs[i] = cell[i] * cell_size[i];
}
-
- /*if (cell[i]<0 || cell[i]>=grid_size[i]) {
-
- cursor_visible=false;
- _update_cursor_transform();
- return false;
- }*/
}
- last_mouseover = Vector3(cell[0], cell[1], cell[2]);
VS::get_singleton()->instance_set_transform(grid_instance[edit_axis], node->get_global_transform() * edit_grid_xform);
if (cursor_instance.is_valid()) {
@@ -601,7 +596,7 @@ void GridMapEditor::_update_paste_indicator() {
Basis item_rot;
item_rot.set_orthogonal_index(item.orientation);
- xf.basis = item_rot * xf.basis;
+ xf.basis = item_rot * xf.basis * node->get_cell_scale();
VisualServer::get_singleton()->instance_set_transform(item.instance, node->get_global_transform() * xf);
}
@@ -656,7 +651,7 @@ bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<Inpu
if (mb->is_pressed())
floor->set_value(floor->get_value() + mb->get_factor());
- return true; //eaten
+ return true; // Eaten.
} else if (mb->get_button_index() == BUTTON_WHEEL_DOWN && (mb->get_command() || mb->get_shift())) {
if (mb->is_pressed())
floor->set_value(floor->get_value() - mb->get_factor());
@@ -702,9 +697,7 @@ bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<Inpu
return do_input_action(p_camera, Point2(mb->get_position().x, mb->get_position().y), true);
} else {
- if (
- (mb->get_button_index() == BUTTON_RIGHT && input_action == INPUT_ERASE) ||
- (mb->get_button_index() == BUTTON_LEFT && input_action == INPUT_PAINT)) {
+ if ((mb->get_button_index() == BUTTON_RIGHT && input_action == INPUT_ERASE) || (mb->get_button_index() == BUTTON_LEFT && input_action == INPUT_PAINT)) {
if (set_items.size()) {
undo_redo->create_action(TTR("GridMap Paint"));
@@ -884,9 +877,15 @@ void GridMapEditor::update_palette() {
if (mesh_library.is_null()) {
last_mesh_library = NULL;
+ search_box->set_text("");
+ search_box->set_editable(false);
+ info_message->show();
return;
}
+ search_box->set_editable(true);
+ info_message->hide();
+
Vector<int> ids;
ids = mesh_library->get_item_list();
@@ -927,7 +926,7 @@ void GridMapEditor::update_palette() {
item++;
}
- if (selected != -1) {
+ if (selected != -1 && mesh_library_palette->get_item_count() > 0) {
mesh_library_palette->select(selected);
}
@@ -935,11 +934,11 @@ void GridMapEditor::update_palette() {
}
void GridMapEditor::edit(GridMap *p_gridmap) {
+ if (!p_gridmap && node)
+ node->disconnect("cell_size_changed", this, "_draw_grids");
node = p_gridmap;
- VS *vs = VS::get_singleton();
- last_mouseover = Vector3(-1, -1, -1);
input_action = INPUT_NONE;
selection.active = false;
_update_selection_transform();
@@ -964,75 +963,13 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
set_process(true);
- Vector3 edited_floor = p_gridmap->has_meta("_editor_floor_") ? p_gridmap->get_meta("_editor_floor_") : Variant();
clip_mode = p_gridmap->has_meta("_editor_clip_") ? ClipMode(p_gridmap->get_meta("_editor_clip_").operator int()) : CLIP_DISABLED;
- for (int i = 0; i < 3; i++) {
- if (vs->mesh_get_surface_count(grid[i]) > 0)
- vs->mesh_remove_surface(grid[i], 0);
- edit_floor[i] = edited_floor[i];
- }
-
- {
-
- //update grids
- indicator_mat.instance();
- indicator_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
- indicator_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- indicator_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
- indicator_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
- indicator_mat->set_albedo(Color(0.8, 0.5, 0.1));
-
- Vector<Vector3> grid_points[3];
- Vector<Color> grid_colors[3];
-
- float cell_size[3] = { p_gridmap->get_cell_size().x, p_gridmap->get_cell_size().y, p_gridmap->get_cell_size().z };
-
- for (int i = 0; i < 3; i++) {
-
- Vector3 axis;
- axis[i] = 1;
- Vector3 axis_n1;
- axis_n1[(i + 1) % 3] = cell_size[(i + 1) % 3];
- Vector3 axis_n2;
- axis_n2[(i + 2) % 3] = cell_size[(i + 2) % 3];
-
- for (int j = -GRID_CURSOR_SIZE; j <= GRID_CURSOR_SIZE; j++) {
-
- for (int k = -GRID_CURSOR_SIZE; k <= GRID_CURSOR_SIZE; k++) {
-
- Vector3 p = axis_n1 * j + axis_n2 * k;
- float trans = Math::pow(MAX(0, 1.0 - (Vector2(j, k).length() / GRID_CURSOR_SIZE)), 2);
-
- Vector3 pj = axis_n1 * (j + 1) + axis_n2 * k;
- float transj = Math::pow(MAX(0, 1.0 - (Vector2(j + 1, k).length() / GRID_CURSOR_SIZE)), 2);
-
- Vector3 pk = axis_n1 * j + axis_n2 * (k + 1);
- float transk = Math::pow(MAX(0, 1.0 - (Vector2(j, k + 1).length() / GRID_CURSOR_SIZE)), 2);
-
- grid_points[i].push_back(p);
- grid_points[i].push_back(pk);
- grid_colors[i].push_back(Color(1, 1, 1, trans));
- grid_colors[i].push_back(Color(1, 1, 1, transk));
-
- grid_points[i].push_back(p);
- grid_points[i].push_back(pj);
- grid_colors[i].push_back(Color(1, 1, 1, trans));
- grid_colors[i].push_back(Color(1, 1, 1, transj));
- }
- }
-
- Array d;
- d.resize(VS::ARRAY_MAX);
- d[VS::ARRAY_VERTEX] = grid_points[i];
- d[VS::ARRAY_COLOR] = grid_colors[i];
- VisualServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], VisualServer::PRIMITIVE_LINES, d);
- VisualServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid());
- }
- }
-
+ _draw_grids(node->get_cell_size());
update_grid();
_update_clip();
+
+ node->connect("cell_size_changed", this, "_draw_grids");
}
void GridMapEditor::_update_clip() {
@@ -1046,9 +983,7 @@ void GridMapEditor::_update_clip() {
void GridMapEditor::update_grid() {
- grid_xform.origin.x -= 1; //force update in hackish way.. what do i care
-
- //VS *vs = VS::get_singleton();
+ grid_xform.origin.x -= 1; // Force update in hackish way.
grid_ofs[edit_axis] = edit_floor[edit_axis] * node->get_cell_size()[edit_axis];
@@ -1064,11 +999,67 @@ void GridMapEditor::update_grid() {
updating = false;
}
+void GridMapEditor::_draw_grids(const Vector3 &cell_size) {
+ Vector3 edited_floor = node->has_meta("_editor_floor_") ? node->get_meta("_editor_floor_") : Variant();
+
+ for (int i = 0; i < 3; i++) {
+ if (VS::get_singleton()->mesh_get_surface_count(grid[i]) > 0)
+ VS::get_singleton()->mesh_remove_surface(grid[i], 0);
+ edit_floor[i] = edited_floor[i];
+ }
+
+ Vector<Vector3> grid_points[3];
+ Vector<Color> grid_colors[3];
+
+ for (int i = 0; i < 3; i++) {
+
+ Vector3 axis;
+ axis[i] = 1;
+ Vector3 axis_n1;
+ axis_n1[(i + 1) % 3] = cell_size[(i + 1) % 3];
+ Vector3 axis_n2;
+ axis_n2[(i + 2) % 3] = cell_size[(i + 2) % 3];
+
+ for (int j = -GRID_CURSOR_SIZE; j <= GRID_CURSOR_SIZE; j++) {
+
+ for (int k = -GRID_CURSOR_SIZE; k <= GRID_CURSOR_SIZE; k++) {
+
+ Vector3 p = axis_n1 * j + axis_n2 * k;
+ float trans = Math::pow(MAX(0, 1.0 - (Vector2(j, k).length() / GRID_CURSOR_SIZE)), 2);
+
+ Vector3 pj = axis_n1 * (j + 1) + axis_n2 * k;
+ float transj = Math::pow(MAX(0, 1.0 - (Vector2(j + 1, k).length() / GRID_CURSOR_SIZE)), 2);
+
+ Vector3 pk = axis_n1 * j + axis_n2 * (k + 1);
+ float transk = Math::pow(MAX(0, 1.0 - (Vector2(j, k + 1).length() / GRID_CURSOR_SIZE)), 2);
+
+ grid_points[i].push_back(p);
+ grid_points[i].push_back(pk);
+ grid_colors[i].push_back(Color(1, 1, 1, trans));
+ grid_colors[i].push_back(Color(1, 1, 1, transk));
+
+ grid_points[i].push_back(p);
+ grid_points[i].push_back(pj);
+ grid_colors[i].push_back(Color(1, 1, 1, trans));
+ grid_colors[i].push_back(Color(1, 1, 1, transj));
+ }
+ }
+
+ Array d;
+ d.resize(VS::ARRAY_MAX);
+ d[VS::ARRAY_VERTEX] = grid_points[i];
+ d[VS::ARRAY_COLOR] = grid_colors[i];
+ VisualServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], VisualServer::PRIMITIVE_LINES, d);
+ VisualServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid());
+ }
+}
+
void GridMapEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
+ get_tree()->connect("node_removed", this, "_node_removed");
mesh_library_palette->connect("item_selected", this, "_item_selected_cbk");
for (int i = 0; i < 3; i++) {
@@ -1085,6 +1076,7 @@ void GridMapEditor::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ get_tree()->disconnect("node_removed", this, "_node_removed");
_clear_clipboard_data();
for (int i = 0; i < 3; i++) {
@@ -1132,7 +1124,6 @@ void GridMapEditor::_notification(int p_what) {
SpatialEditorPlugin *sep = Object::cast_to<SpatialEditorPlugin>(editor->get_editor_plugin_screen());
if (sep)
sep->snap_cursor_to_plane(p);
- //editor->get_editor_plugin_screen()->call("snap_cursor_to_plane",p);
}
} break;
@@ -1198,8 +1189,10 @@ void GridMapEditor::_bind_methods() {
ClassDB::bind_method("_floor_changed", &GridMapEditor::_floor_changed);
ClassDB::bind_method("_floor_mouse_exited", &GridMapEditor::_floor_mouse_exited);
ClassDB::bind_method("_set_selection", &GridMapEditor::_set_selection);
+ ClassDB::bind_method("_node_removed", &GridMapEditor::_node_removed);
ClassDB::bind_method(D_METHOD("_set_display_mode", "mode"), &GridMapEditor::_set_display_mode);
+ ClassDB::bind_method("_draw_grids", &GridMapEditor::_draw_grids);
}
GridMapEditor::GridMapEditor(EditorNode *p_editor) {
@@ -1296,6 +1289,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
search_box = memnew(LineEdit);
search_box->set_h_size_flags(SIZE_EXPAND_FILL);
+ search_box->set_placeholder(TTR("Filter meshes"));
hb->add_child(search_box);
search_box->connect("text_changed", this, "_text_changed");
search_box->connect("gui_input", this, "_sbox_input");
@@ -1331,6 +1325,15 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
add_child(mesh_library_palette);
mesh_library_palette->set_v_size_flags(SIZE_EXPAND_FILL);
+ info_message = memnew(Label);
+ info_message->set_text(TTR("Give a MeshLibrary resource to this GridMap to use its meshes."));
+ info_message->set_valign(Label::VALIGN_CENTER);
+ info_message->set_align(Label::ALIGN_CENTER);
+ info_message->set_autowrap(true);
+ info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
+ info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
+ mesh_library_palette->add_child(info_message);
+
edit_axis = Vector3::AXIS_Y;
edit_floor[0] = -1;
edit_floor[1] = -1;
@@ -1340,13 +1343,12 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
selected_palette = -1;
lock_view = false;
cursor_rot = 0;
- last_mouseover = Vector3(-1, -1, -1);
selection_mesh = VisualServer::get_singleton()->mesh_create();
paste_mesh = VisualServer::get_singleton()->mesh_create();
{
- //selection mesh create
+ // Selection mesh create.
PoolVector<Vector3> lines;
PoolVector<Vector3> triangles;
@@ -1424,7 +1426,6 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
inner_mat.instance();
inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2));
- //inner_mat->set_flag(SpatialMaterial::FLAG_ONTOP, true);
inner_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
inner_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
@@ -1444,7 +1445,6 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
selection_floor_mat->set_on_top_of_alpha();
selection_floor_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
selection_floor_mat->set_line_width(3.0);
- //selection_floor_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
d[VS::ARRAY_VERTEX] = lines;
VisualServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, VS::PRIMITIVE_LINES, d);
@@ -1466,9 +1466,16 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
}
}
- selection.active = false;
+ _set_selection(false);
updating = false;
accumulated_floor_delta = 0.0;
+
+ indicator_mat.instance();
+ indicator_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
+ indicator_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+ indicator_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
+ indicator_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ indicator_mat->set_albedo(Color(0.8, 0.5, 0.1));
}
GridMapEditor::~GridMapEditor() {
diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h
index d174ac1035..42e62f2842 100644
--- a/modules/gridmap/grid_map_editor_plugin.h
+++ b/modules/gridmap/grid_map_editor_plugin.h
@@ -156,7 +156,6 @@ class GridMapEditor : public VBoxContainer {
Transform cursor_transform;
Vector3 cursor_origin;
- Vector3 last_mouseover;
int display_mode;
int selected_palette;
@@ -197,12 +196,17 @@ class GridMapEditor : public VBoxContainer {
RID instance;
};
- void update_grid();
+ ItemList *mesh_library_palette;
+ Label *info_message;
+
+ EditorNode *editor;
+
+ void update_grid(); // Change which and where the grid is displayed
+ void _draw_grids(const Vector3 &cell_size);
void _configure();
void _menu_option(int);
void update_palette();
void _set_display_mode(int p_mode);
- ItemList *mesh_library_palette;
void _item_selected_cbk(int idx);
void _update_cursor_transform();
void _update_cursor_instance();
@@ -227,7 +231,6 @@ class GridMapEditor : public VBoxContainer {
void _delete_selection();
void _fill_selection();
- EditorNode *editor;
bool do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click);
friend class GridMapEditorPlugin;
diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp
index e1bba60f2f..ea90cce83d 100644
--- a/modules/jsonrpc/jsonrpc.cpp
+++ b/modules/jsonrpc/jsonrpc.cpp
@@ -47,11 +47,11 @@ void JSONRPC::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_notification", "method", "params"), &JSONRPC::make_notification);
ClassDB::bind_method(D_METHOD("make_response_error", "code", "message", "id"), &JSONRPC::make_response_error, DEFVAL(Variant()));
- BIND_ENUM_CONSTANT(PARSE_ERROR)
- BIND_ENUM_CONSTANT(INVALID_REQUEST)
- BIND_ENUM_CONSTANT(METHOD_NOT_FOUND)
- BIND_ENUM_CONSTANT(INVALID_PARAMS)
- BIND_ENUM_CONSTANT(INTERNAL_ERROR)
+ BIND_ENUM_CONSTANT(PARSE_ERROR);
+ BIND_ENUM_CONSTANT(INVALID_REQUEST);
+ BIND_ENUM_CONSTANT(METHOD_NOT_FOUND);
+ BIND_ENUM_CONSTANT(INVALID_PARAMS);
+ BIND_ENUM_CONSTANT(INTERNAL_ERROR);
}
Dictionary JSONRPC::make_response_error(int p_code, const String &p_message, const Variant &p_id) const {
diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp
index 1e02084ae2..204f4e8905 100644
--- a/modules/mbedtls/crypto_mbedtls.cpp
+++ b/modules/mbedtls/crypto_mbedtls.cpp
@@ -55,7 +55,7 @@ Error CryptoKeyMbedTLS::load(String p_path) {
PoolByteArray out;
FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(!f, ERR_INVALID_PARAMETER, "Cannot open CryptoKeyMbedTLS file '" + p_path + "'.");
int flen = f->get_len();
out.resize(flen + 1);
@@ -69,14 +69,14 @@ Error CryptoKeyMbedTLS::load(String p_path) {
int ret = mbedtls_pk_parse_key(&pkey, out.read().ptr(), out.size(), NULL, 0);
// We MUST zeroize the memory for safety!
mbedtls_platform_zeroize(out.write().ptr(), out.size());
- ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing private key: " + itos(ret));
+ ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing private key '" + itos(ret) + "'.");
return OK;
}
Error CryptoKeyMbedTLS::save(String p_path) {
FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(!f, ERR_INVALID_PARAMETER, "Cannot save CryptoKeyMbedTLS file '" + p_path + "'.");
unsigned char w[16000];
memset(w, 0, sizeof(w));
@@ -85,7 +85,7 @@ Error CryptoKeyMbedTLS::save(String p_path) {
if (ret != 0) {
memdelete(f);
memset(w, 0, sizeof(w)); // Zeroize anything we might have written.
- ERR_FAIL_V_MSG(FAILED, "Error writing key: " + itos(ret));
+ ERR_FAIL_V_MSG(FAILED, "Error writing key '" + itos(ret) + "'.");
}
size_t len = strlen((char *)w);
@@ -104,7 +104,7 @@ Error X509CertificateMbedTLS::load(String p_path) {
PoolByteArray out;
FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(!f, ERR_INVALID_PARAMETER, "Cannot open X509CertificateMbedTLS file '" + p_path + "'.");
int flen = f->get_len();
out.resize(flen + 1);
@@ -131,7 +131,7 @@ Error X509CertificateMbedTLS::load_from_memory(const uint8_t *p_buffer, int p_le
Error X509CertificateMbedTLS::save(String p_path) {
FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(!f, ERR_INVALID_PARAMETER, "Cannot save X509CertificateMbedTLS file '" + p_path + "'.");
mbedtls_x509_crt *crt = &cert;
while (crt) {
@@ -140,7 +140,7 @@ Error X509CertificateMbedTLS::save(String p_path) {
int ret = mbedtls_pem_write_buffer(PEM_BEGIN_CRT, PEM_END_CRT, cert.raw.p, cert.raw.len, w, sizeof(w), &wrote);
if (ret != 0 || wrote == 0) {
memdelete(f);
- ERR_FAIL_V_MSG(FAILED, "Error writing certificate: " + itos(ret));
+ ERR_FAIL_V_MSG(FAILED, "Error writing certificate '" + itos(ret) + "'.");
}
f->store_buffer(w, wrote - 1); // don't write the string terminator
@@ -237,6 +237,7 @@ Ref<CryptoKey> CryptoMbedTLS::generate_rsa(int p_bytes) {
Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) {
Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS> >(p_key);
+ ERR_FAIL_COND_V_MSG(key.is_null(), NULL, "Invalid private key argument.");
mbedtls_x509write_cert crt;
mbedtls_x509write_crt_init(&crt);
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index f751719531..4c1ebd8d74 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -18,7 +18,7 @@ android_arch_dirs = {
def get_android_out_dir(env):
- return os.path.join(Dir('#platform/android/java/libs').abspath,
+ return os.path.join(Dir('#platform/android/java/lib/libs').abspath,
'release' if env['target'] == 'release' else 'debug',
android_arch_dirs[env['android_arch']])
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp
index 7580911a0a..bbc779601e 100644
--- a/modules/mono/class_db_api_json.cpp
+++ b/modules/mono/class_db_api_json.cpp
@@ -236,7 +236,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
}
FileAccessRef f = FileAccess::open(p_output_file, FileAccess::WRITE);
- ERR_FAIL_COND(!f);
+ ERR_FAIL_COND_MSG(!f, "Cannot open file '" + p_output_file + "'.");
f->store_string(JSON::print(classes_dict, /*indent: */ "\t"));
f->close();
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index e14e919f92..83be10dee3 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -1195,7 +1195,7 @@ void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<Mon
CSharpLanguage::CSharpLanguage() {
- ERR_FAIL_COND(singleton);
+ ERR_FAIL_COND_MSG(singleton, "C# singleton already exist.");
singleton = this;
finalizing = false;
@@ -3242,7 +3242,7 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
#if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED)
Error err = script->load_source_code(p_path);
- ERR_FAIL_COND_V(err != OK, RES());
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load C# script file '" + p_path + "'.");
#endif
script->set_path(p_original_path);
@@ -3325,7 +3325,7 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
Error err;
FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V(err, err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save C# script file '" + p_path + "'.");
file->store_string(source);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
index 4c1e47ecad..eb2c2dd77c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -34,7 +34,7 @@ namespace GodotTools.Build
if (_msbuildToolsPath.Empty())
{
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'.");
}
}
@@ -142,7 +142,7 @@ namespace GodotTools.Build
int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs,
blocking: true, output: (Godot.Collections.Array) outputArray);
- if (exitCode == 0)
+ if (exitCode != 0)
return string.Empty;
if (outputArray.Count == 0)
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
index 417032da54..ab37d89955 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
@@ -160,9 +160,16 @@ namespace GodotTools
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
- // Make sure to update the API assemblies if they happen to be missing. Just in
- // case the user decided to delete them at some point after they were loaded.
- Internal.UpdateApiAssembliesFromPrebuilt();
+ // Make sure the API assemblies are up to date before building the project.
+ // We may not have had the chance to update the release API assemblies, and the debug ones
+ // may have been deleted by the user at some point after they were loaded by the Godot editor.
+ string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "Release" ? "Release" : "Debug");
+
+ if (!string.IsNullOrEmpty(apiAssembliesUpdateError))
+ {
+ ShowBuildErrorDialog("Failed to update the Godot API assemblies");
+ return false;
+ }
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 7da7cff933..12edd651df 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -34,7 +34,7 @@ namespace GodotTools
private bool CreateProjectSolution()
{
- using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
+ using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3))
{
pr.Step("Generating C# project...".TTR());
@@ -73,9 +73,23 @@ namespace GodotTools
return false;
}
- // Make sure to update the API assemblies if they happen to be missing. Just in
- // case the user decided to delete them at some point after they were loaded.
- Internal.UpdateApiAssembliesFromPrebuilt();
+ pr.Step("Updating Godot API assemblies...".TTR());
+
+ string debugApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Debug");
+
+ if (!string.IsNullOrEmpty(debugApiAssembliesError))
+ {
+ ShowErrorDialog("Failed to update the Godot API assemblies: " + debugApiAssembliesError);
+ return false;
+ }
+
+ string releaseApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Release");
+
+ if (!string.IsNullOrEmpty(releaseApiAssembliesError))
+ {
+ ShowErrorDialog("Failed to update the Godot API assemblies: " + releaseApiAssembliesError);
+ return false;
+ }
pr.Step("Done".TTR());
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 9e24138143..01aa0d0ab1 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -40,8 +40,7 @@ namespace GodotTools.Ides
protected ILogger Logger
{
- get => logger ?? (logger = new ConsoleLogger());
- set => logger = value;
+ get => logger ?? (logger = new GodotLogger());
}
private void StartServer()
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 7783576910..836c9c11e4 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -10,8 +10,8 @@ namespace GodotTools.Internals
public const string CSharpLanguageType = "CSharpScript";
public const string CSharpLanguageExtension = "cs";
- public static string UpdateApiAssembliesFromPrebuilt() =>
- internal_UpdateApiAssembliesFromPrebuilt();
+ public static string UpdateApiAssembliesFromPrebuilt(string config) =>
+ internal_UpdateApiAssembliesFromPrebuilt(config);
public static string FullTemplatesDir =>
internal_FullTemplatesDir();
@@ -55,7 +55,7 @@ namespace GodotTools.Internals
// Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
- private static extern string internal_UpdateApiAssembliesFromPrebuilt();
+ private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_FullTemplatesDir();
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 1888bb3cb9..28cab2ab61 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -30,7 +30,7 @@
#include "bindings_generator.h"
-#ifdef DEBUG_METHODS_ENABLED
+#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
#include "core/engine.h"
#include "core/global_constants.h"
@@ -863,12 +863,14 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
if (!DirAccess::exists(p_proj_dir)) {
Error err = da->make_dir_recursive(p_proj_dir);
- ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create directory '" + p_proj_dir + "'.");
}
da->change_dir(p_proj_dir);
@@ -984,6 +986,8 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vect
Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
@@ -1064,6 +1068,8 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Ve
Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
String output_dir = path::abspath(path::realpath(p_output_dir));
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -1703,6 +1709,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
Error BindingsGenerator::generate_glue(const String &p_output_dir) {
+ ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
+
bool dir_exists = DirAccess::exists(p_output_dir);
ERR_FAIL_COND_V_MSG(!dir_exists, ERR_FILE_BAD_PATH, "The output directory does not exist.");
@@ -1785,6 +1793,9 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
output.append("uint32_t get_bindings_version() { return ");
output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n");
+ output.append("uint32_t get_cs_glue_version() { return ");
+ output.append(String::num_uint64(CS_GLUE_VERSION) + "; }\n");
+
output.append("\nvoid register_generated_icalls() " OPEN_BLOCK);
output.append("\tgodot_register_glue_header_icalls();\n");
@@ -2148,7 +2159,7 @@ StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Meta
}
}
-void BindingsGenerator::_populate_object_type_interfaces() {
+bool BindingsGenerator::_populate_object_type_interfaces() {
obj_types.clear();
@@ -2226,7 +2237,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
bool valid = false;
iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);
- ERR_FAIL_COND(!valid);
+ ERR_FAIL_COND_V(!valid, false);
iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
@@ -2290,7 +2301,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
imethod.is_vararg = m && m->is_vararg();
if (!m && !imethod.is_virtual) {
- ERR_FAIL_COND_MSG(!virtual_method_list.find(method_info),
+ ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,
"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");
// A virtual method without the virtual flag. This is a special case.
@@ -2307,9 +2318,9 @@ void BindingsGenerator::_populate_object_type_interfaces() {
// which could actually will return something different.
// Let's put this to notify us if that ever happens.
if (itype.cname != name_cache.type_Object || imethod.name != "free") {
- ERR_PRINTS("Notification: New unexpected virtual non-overridable method found."
- " We only expected Object.free, but found '" +
- itype.name + "." + imethod.name + "'.");
+ WARN_PRINTS("Notification: New unexpected virtual non-overridable method found."
+ " We only expected Object.free, but found '" +
+ itype.name + "." + imethod.name + "'.");
}
} else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
imethod.return_type.cname = return_info.class_name;
@@ -2321,7 +2332,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
ERR_PRINTS("Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'."
" Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");
/* clang-format on */
- ERR_FAIL();
+ ERR_FAIL_V(false);
}
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
imethod.return_type.cname = return_info.hint_string;
@@ -2342,8 +2353,10 @@ void BindingsGenerator::_populate_object_type_interfaces() {
for (int i = 0; i < argc; i++) {
PropertyInfo arginfo = method_info.arguments[i];
+ String orig_arg_name = arginfo.name;
+
ArgumentInterface iarg;
- iarg.name = arginfo.name;
+ iarg.name = orig_arg_name;
if (arginfo.type == Variant::INT && arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
iarg.type.cname = arginfo.class_name;
@@ -2367,7 +2380,9 @@ void BindingsGenerator::_populate_object_type_interfaces() {
iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));
if (m && m->has_default_argument(i)) {
- _default_argument_from_variant(m->get_default_argument(i), iarg);
+ bool defval_ok = _arg_default_value_from_variant(m->get_default_argument(i), iarg);
+ ERR_FAIL_COND_V_MSG(!defval_ok, false,
+ "Cannot determine default value for argument '" + orig_arg_name + "' of method '" + itype.name + "." + imethod.name + "'.");
}
imethod.add_argument(iarg);
@@ -2447,7 +2462,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
const StringName &constant_cname = E->get();
String constant_name = constant_cname.operator String();
int *value = class_info->constant_map.getptr(constant_cname);
- ERR_FAIL_NULL(value);
+ ERR_FAIL_NULL_V(value, false);
constants.erase(constant_name);
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
@@ -2483,7 +2498,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
for (const List<String>::Element *E = constants.front(); E; E = E->next()) {
const String &constant_name = E->get();
int *value = class_info->constant_map.getptr(StringName(E->get()));
- ERR_FAIL_NULL(value);
+ ERR_FAIL_NULL_V(value, false);
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
@@ -2504,9 +2519,11 @@ void BindingsGenerator::_populate_object_type_interfaces() {
class_list.pop_front();
}
+
+ return true;
}
-void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
+bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
r_iarg.default_argument = p_val;
@@ -2552,16 +2569,24 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::OBJECT:
- if (p_val.is_zero()) {
- r_iarg.default_argument = "null";
- break;
- }
- FALLTHROUGH;
+ ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
+
+ r_iarg.default_argument = "null";
+ break;
case Variant::DICTIONARY:
- case Variant::_RID:
r_iarg.default_argument = "new %s()";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
break;
+ case Variant::_RID:
+ ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'.");
+
+ ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
+ "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
+
+ r_iarg.default_argument = "null";
+ break;
case Variant::ARRAY:
case Variant::POOL_BYTE_ARRAY:
case Variant::POOL_INT_ARRAY:
@@ -2585,6 +2610,8 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg
if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null")
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
+
+ return true;
}
void BindingsGenerator::_populate_builtin_type_interfaces() {
@@ -2970,13 +2997,17 @@ void BindingsGenerator::_log(const char *p_format, ...) {
void BindingsGenerator::_initialize() {
+ initialized = false;
+
EditorHelp::generate_doc();
enum_types.clear();
_initialize_blacklisted_methods();
- _populate_object_type_interfaces();
+ bool obj_type_ok = _populate_object_type_interfaces();
+ ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");
+
_populate_builtin_type_interfaces();
_populate_global_constants();
@@ -2988,6 +3019,8 @@ void BindingsGenerator::_initialize() {
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next())
_generate_method_icalls(E.get());
+
+ initialized = true;
}
void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
@@ -3048,6 +3081,11 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
BindingsGenerator bindings_generator;
bindings_generator.set_log_print_enabled(true);
+ if (!bindings_generator.initialized) {
+ ERR_PRINTS("Failed to initialize the bindings generator");
+ ::exit(0);
+ }
+
if (glue_dir_path.length()) {
if (bindings_generator.generate_glue(glue_dir_path) != OK)
ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue.");
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 6f0c297575..8f3676940b 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -36,7 +36,7 @@
#include "editor/doc/doc_data.h"
#include "editor/editor_help.h"
-#ifdef DEBUG_METHODS_ENABLED
+#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
#include "core/ustring.h"
@@ -472,6 +472,7 @@ class BindingsGenerator {
};
bool log_print_enabled;
+ bool initialized;
OrderedHashMap<StringName, TypeInterface> obj_types;
@@ -502,6 +503,7 @@ class BindingsGenerator {
StringName type_VarArg;
StringName type_Object;
StringName type_Reference;
+ StringName type_RID;
StringName type_String;
StringName type_at_GlobalScope;
StringName enum_Error;
@@ -525,6 +527,7 @@ class BindingsGenerator {
type_VarArg = StaticCString::create("VarArg");
type_Object = StaticCString::create("Object");
type_Reference = StaticCString::create("Reference");
+ type_RID = StaticCString::create("RID");
type_String = StaticCString::create("String");
type_at_GlobalScope = StaticCString::create("@GlobalScope");
enum_Error = StaticCString::create("Error");
@@ -590,9 +593,9 @@ class BindingsGenerator {
StringName _get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
StringName _get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
- void _default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg);
+ bool _arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg);
- void _populate_object_type_interfaces();
+ bool _populate_object_type_interfaces();
void _populate_builtin_type_interfaces();
void _populate_global_constants();
@@ -621,12 +624,15 @@ public:
_FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; }
_FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; }
+ _FORCE_INLINE_ bool is_initialized() { return initialized; }
+
static uint32_t get_version();
static void handle_cmdline_args(const List<String> &p_cmdline_args);
BindingsGenerator() :
- log_print_enabled(true) {
+ log_print_enabled(true),
+ initialized(false) {
_initialize();
}
};
diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp
index 0e6c58c9d7..748447005f 100644
--- a/modules/mono/editor/csharp_project.cpp
+++ b/modules/mono/editor/csharp_project.cpp
@@ -75,7 +75,7 @@ bool generate_api_solution(const String &p_solution_dir, const String &p_core_pr
p_editor_proj_dir, p_editor_compile_items,
GDMono::get_singleton()->get_tools_project_editor_assembly());
} else {
- MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain");
+ MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ApiSolutionGeneration");
CRASH_COND(temp_domain == NULL);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 5a84d9e3b8..1564d73c2a 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -230,31 +230,9 @@ uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString
return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies);
}
-float godot_icall_Globals_EditorScale() {
- return EDSCALE;
-}
-
-MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
- String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
- Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
- Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
- return GDMonoMarshal::variant_to_mono_object(result);
-}
-
-MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
- String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
- Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
- Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
- return GDMonoMarshal::variant_to_mono_object(result);
-}
-
-MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
- String text = GDMonoMarshal::mono_string_to_godot(p_text);
- return GDMonoMarshal::mono_string_from_godot(TTR(text));
-}
-
-MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt() {
- String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt();
+MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) {
+ String config = GDMonoMarshal::mono_string_to_godot(p_config);
+ String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(config);
return GDMonoMarshal::mono_string_from_godot(error_str);
}
@@ -365,6 +343,29 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
}
}
+float godot_icall_Globals_EditorScale() {
+ return EDSCALE;
+}
+
+MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
+ String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
+ Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
+ Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
+ return GDMonoMarshal::variant_to_mono_object(result);
+}
+
+MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
+ String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
+ Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
+ Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
+ return GDMonoMarshal::variant_to_mono_object(result);
+}
+
+MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
+ String text = GDMonoMarshal::mono_string_to_godot(p_text);
+ return GDMonoMarshal::mono_string_from_godot(TTR(text));
+}
+
MonoString *godot_icall_Utils_OS_GetPlatformName() {
String os_name = OS::get_singleton()->get_name();
return GDMonoMarshal::mono_string_from_godot(os_name);
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index 80a7335b1d..e83152d668 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -32,9 +32,13 @@
#include <mono/metadata/image.h>
+#include "core/os/os.h"
+
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
+namespace GodotSharpExport {
+
String get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
@@ -45,7 +49,7 @@ String get_assemblyref_name(MonoImage *p_image, int index) {
return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
}
-Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
+Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
@@ -96,8 +100,8 @@ Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, co
return OK;
}
-Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) {
- MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
+Error get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) {
+ MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport");
ERR_FAIL_NULL_V(export_domain, FAILED);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
@@ -110,7 +114,9 @@ Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_proje
ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + p_project_dll_name + "'.");
Vector<String> search_dirs;
- GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir);
+ GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies);
}
+
+} // namespace GodotSharpExport
diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h
index 8d121a6bc3..58e46e2f2d 100644
--- a/modules/mono/editor/godotsharp_export.h
+++ b/modules/mono/editor/godotsharp_export.h
@@ -39,10 +39,11 @@
namespace GodotSharpExport {
+Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
+
Error get_exported_assembly_dependencies(const String &p_project_dll_name,
const String &p_project_dll_src_path, const String &p_build_config,
const String &p_custom_lib_dir, Dictionary &r_dependencies);
-Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
} // namespace GodotSharpExport
diff --git a/modules/mono/glue/Managed/Files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs
index a13161d2e6..26e717e089 100644
--- a/modules/mono/glue/Managed/Files/Plane.cs
+++ b/modules/mono/glue/Managed/Files/Plane.cs
@@ -82,12 +82,12 @@ namespace Godot
return Mathf.Abs(dist) <= epsilon;
}
- public Vector3 Intersect3(Plane b, Plane c)
+ public Vector3? Intersect3(Plane b, Plane c)
{
real_t denom = _normal.Cross(b._normal).Dot(c._normal);
- if (Mathf.Abs(denom) <= Mathf.Epsilon)
- return new Vector3();
+ if (Mathf.IsZeroApprox(denom))
+ return null;
Vector3 result = b._normal.Cross(c._normal) * D +
c._normal.Cross(_normal) * b.D +
@@ -96,34 +96,35 @@ namespace Godot
return result / denom;
}
- public Vector3 IntersectRay(Vector3 from, Vector3 dir)
+ public Vector3? IntersectRay(Vector3 from, Vector3 dir)
{
real_t den = _normal.Dot(dir);
- if (Mathf.Abs(den) <= Mathf.Epsilon)
- return new Vector3();
+ if (Mathf.IsZeroApprox(den))
+ return null;
real_t dist = (_normal.Dot(from) - D) / den;
// This is a ray, before the emitting pos (from) does not exist
if (dist > Mathf.Epsilon)
- return new Vector3();
+ return null;
return from + dir * -dist;
}
- public Vector3 IntersectSegment(Vector3 begin, Vector3 end)
+ public Vector3? IntersectSegment(Vector3 begin, Vector3 end)
{
Vector3 segment = begin - end;
real_t den = _normal.Dot(segment);
- if (Mathf.Abs(den) <= Mathf.Epsilon)
- return new Vector3();
+ if (Mathf.IsZeroApprox(den))
+ return null;
real_t dist = (_normal.Dot(begin) - D) / den;
+ // Only allow dist to be in the range of 0 to 1, with tolerance.
if (dist < -Mathf.Epsilon || dist > 1.0f + Mathf.Epsilon)
- return new Vector3();
+ return null;
return begin + segment * -dist;
}
diff --git a/modules/mono/glue/Managed/Files/Rect2.cs b/modules/mono/glue/Managed/Files/Rect2.cs
index f3dc9d8490..99542d0c0a 100644
--- a/modules/mono/glue/Managed/Files/Rect2.cs
+++ b/modules/mono/glue/Managed/Files/Rect2.cs
@@ -157,13 +157,13 @@ namespace Godot
public bool Intersects(Rect2 b)
{
- if (_position.x > b._position.x + b._size.x)
+ if (_position.x >= b._position.x + b._size.x)
return false;
- if (_position.x + _size.x < b._position.x)
+ if (_position.x + _size.x <= b._position.x)
return false;
- if (_position.y > b._position.y + b._size.y)
+ if (_position.y >= b._position.y + b._size.y)
return false;
- if (_position.y + _size.y < b._position.y)
+ if (_position.y + _size.y <= b._position.y)
return false;
return true;
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 4b2525c692..5fa8aed5a9 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -43,6 +43,8 @@
#include "utils/android_utils.h"
#endif
+#include "mono_gd/gd_mono.h"
+
namespace GodotSharpDirs {
String _get_expected_build_config() {
@@ -59,20 +61,6 @@ String _get_expected_build_config() {
#endif
}
-String _get_expected_api_build_config() {
-#ifdef TOOLS_ENABLED
- return "Debug";
-#else
-
-#ifdef DEBUG_ENABLED
- return "Debug";
-#else
- return "Release";
-#endif
-
-#endif
-}
-
String _get_mono_user_dir() {
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
@@ -134,7 +122,7 @@ private:
res_data_dir = "res://.mono";
res_metadata_dir = res_data_dir.plus_file("metadata");
res_assemblies_base_dir = res_data_dir.plus_file("assemblies");
- res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config());
+ res_assemblies_dir = res_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config());
res_config_dir = res_data_dir.plus_file("etc").plus_file("mono");
// TODO use paths from csproj
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index cd111abd4d..504b8d41d0 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -44,7 +44,6 @@
#include "core/project_settings.h"
#include "../csharp_script.h"
-#include "../glue/cs_glue_version.gen.h"
#include "../godotsharp_dirs.h"
#include "../utils/path_utils.h"
#include "gd_mono_class.h"
@@ -120,26 +119,29 @@ void gdmono_debug_init() {
mono_debug_init(MONO_DEBUG_FORMAT_MONO);
+ CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
+
+#ifdef TOOLS_ENABLED
int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685);
bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);
- CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
-
-#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() ||
ProjectSettings::get_singleton()->get_resource_path().empty() ||
Main::is_project_manager()) {
if (da_args.size() == 0)
return;
}
-#endif
if (da_args.length() == 0) {
da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) +
",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n"))
.utf8();
}
+#else
+ if (da_args.length() == 0)
+ return; // Exported games don't use the project settings to setup the debugger agent
+#endif
// --debugger-agent=help
const char *options[] = {
@@ -379,10 +381,10 @@ void GDMono::initialize_load_assemblies() {
}
bool GDMono::_are_api_assemblies_out_of_sync() {
- bool out_of_sync = core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated);
+ bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated);
#ifdef TOOLS_ENABLED
if (!out_of_sync)
- out_of_sync = editor_api_assembly && editor_api_assembly_out_of_sync;
+ out_of_sync = editor_api_assembly.assembly && editor_api_assembly.out_of_sync;
#endif
return out_of_sync;
}
@@ -395,23 +397,24 @@ uint64_t get_core_api_hash();
uint64_t get_editor_api_hash();
#endif
uint32_t get_bindings_version();
+uint32_t get_cs_glue_version();
void register_generated_icalls();
#else
uint64_t get_core_api_hash() {
- CRASH_NOW();
GD_UNREACHABLE();
}
#ifdef TOOLS_ENABLED
uint64_t get_editor_api_hash() {
- CRASH_NOW();
GD_UNREACHABLE();
}
#endif
uint32_t get_bindings_version() {
- CRASH_NOW();
+ GD_UNREACHABLE();
+}
+uint32_t get_cs_glue_version() {
GD_UNREACHABLE();
}
@@ -427,8 +430,8 @@ void GDMono::_register_internal_calls() {
}
void GDMono::_initialize_and_check_api_hashes() {
-
#ifdef MONO_GLUE_ENABLED
+#ifdef DEBUG_METHODS_ENABLED
if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) {
ERR_PRINT("Mono: Core API hash mismatch.");
}
@@ -438,6 +441,7 @@ void GDMono::_initialize_and_check_api_hashes() {
ERR_PRINT("Mono: Editor API hash mismatch.");
}
#endif // TOOLS_ENABLED
+#endif // DEBUG_METHODS_ENABLED
#endif // MONO_GLUE_ENABLED
}
@@ -519,10 +523,10 @@ bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMo
return true;
}
-APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) {
- APIAssembly::Version api_assembly_version;
+ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, ApiAssemblyInfo::Type p_api_type) {
+ ApiAssemblyInfo::Version api_assembly_version;
- const char *nativecalls_name = p_api_type == APIAssembly::API_CORE ?
+ const char *nativecalls_name = p_api_type == ApiAssemblyInfo::API_CORE ?
BINDINGS_CLASS_NATIVECALLS :
BINDINGS_CLASS_NATIVECALLS_EDITOR;
@@ -545,8 +549,8 @@ APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssemb
return api_assembly_version;
}
-String APIAssembly::to_string(APIAssembly::Type p_type) {
- return p_type == APIAssembly::API_CORE ? "API_CORE" : "API_EDITOR";
+String ApiAssemblyInfo::to_string(ApiAssemblyInfo::Type p_type) {
+ return p_type == ApiAssemblyInfo::API_CORE ? "API_CORE" : "API_EDITOR";
}
bool GDMono::_load_corlib_assembly() {
@@ -563,16 +567,12 @@ bool GDMono::_load_corlib_assembly() {
}
#ifdef TOOLS_ENABLED
-bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config) {
-
- bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ?
- GDMono::get_singleton()->core_api_assembly_out_of_sync :
- GDMono::get_singleton()->editor_api_assembly_out_of_sync;
+bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config) {
String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
- String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
+ String assembly_name = p_api_type == ApiAssemblyInfo::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
// Create destination directory if needed
if (!DirAccess::exists(dst_dir)) {
@@ -586,35 +586,102 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const Stri
}
}
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ String xml_file = assembly_name + ".xml";
+ if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
+ WARN_PRINTS("Failed to copy '" + xml_file + "'.");
+
+ String pdb_file = assembly_name + ".pdb";
+ if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
+ WARN_PRINTS("Failed to copy '" + pdb_file + "'.");
+
String assembly_file = assembly_name + ".dll";
- String assembly_src = src_dir.plus_file(assembly_file);
- String assembly_dst = dst_dir.plus_file(assembly_file);
+ if (da->copy(src_dir.plus_file(assembly_file), dst_dir.plus_file(assembly_file)) != OK) {
+ ERR_PRINTS("Failed to copy '" + assembly_file + "'.");
+ return false;
+ }
+
+ return true;
+}
- if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+static bool try_get_cached_api_hash_for(const String &p_api_assemblies_dir, bool &r_out_of_sync) {
+ String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+ String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
- String xml_file = assembly_name + ".xml";
- if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
- WARN_PRINTS("Failed to copy '" + xml_file + "'.");
+ if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path))
+ return false;
- String pdb_file = assembly_name + ".pdb";
- if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
- WARN_PRINTS("Failed to copy '" + pdb_file + "'.");
+ String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg");
- Error err = da->copy(assembly_src, assembly_dst);
+ if (!FileAccess::exists(cached_api_hash_path))
+ return false;
- if (err != OK) {
- ERR_PRINTS("Failed to copy '" + assembly_file + "'.");
- return false;
- }
+ Ref<ConfigFile> cfg;
+ cfg.instance();
+ Error cfg_err = cfg->load(cached_api_hash_path);
+ ERR_FAIL_COND_V(cfg_err != OK, false);
- api_assembly_out_of_sync = false;
+ // Checking the modified time is good enough
+ if (FileAccess::get_modified_time(core_api_assembly_path) != (uint64_t)cfg->get_value("core", "modified_time") ||
+ FileAccess::get_modified_time(editor_api_assembly_path) != (uint64_t)cfg->get_value("editor", "modified_time")) {
+ return false;
}
+ r_out_of_sync = GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("core", "bindings_version") ||
+ GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("core", "cs_glue_version") ||
+ GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("editor", "bindings_version") ||
+ GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("editor", "cs_glue_version") ||
+ GodotSharpBindings::get_core_api_hash() != (uint64_t)cfg->get_value("core", "api_hash") ||
+ GodotSharpBindings::get_editor_api_hash() != (uint64_t)cfg->get_value("editor", "api_hash");
+
return true;
}
-String GDMono::update_api_assemblies_from_prebuilt() {
+static void create_cached_api_hash_for(const String &p_api_assemblies_dir) {
+
+ String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+ String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+ String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg");
+
+ Ref<ConfigFile> cfg;
+ cfg.instance();
+
+ cfg->set_value("core", "modified_time", FileAccess::get_modified_time(core_api_assembly_path));
+ cfg->set_value("editor", "modified_time", FileAccess::get_modified_time(editor_api_assembly_path));
+
+ cfg->set_value("core", "bindings_version", GodotSharpBindings::get_bindings_version());
+ cfg->set_value("core", "cs_glue_version", GodotSharpBindings::get_cs_glue_version());
+ cfg->set_value("editor", "bindings_version", GodotSharpBindings::get_bindings_version());
+ cfg->set_value("editor", "cs_glue_version", GodotSharpBindings::get_cs_glue_version());
+
+ // This assumes the prebuilt api assemblies we copied to the project are not out of sync
+ cfg->set_value("core", "api_hash", GodotSharpBindings::get_core_api_hash());
+ cfg->set_value("editor", "api_hash", GodotSharpBindings::get_editor_api_hash());
+
+ Error err = cfg->save(cached_api_hash_path);
+ ERR_FAIL_COND(err != OK);
+}
+
+bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config) {
+ MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.CheckApiAssemblies");
+ ERR_FAIL_NULL_V(temp_domain, "Failed to create temporary domain to check API assemblies");
+ _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);
+
+ _GDMONO_SCOPE_DOMAIN_(temp_domain);
+
+ GDMono::LoadedApiAssembly temp_core_api_assembly;
+ GDMono::LoadedApiAssembly temp_editor_api_assembly;
+
+ if (!_try_load_api_assemblies(temp_core_api_assembly, temp_editor_api_assembly,
+ p_config, /* refonly: */ true, /* loaded_callback: */ NULL)) {
+ return temp_core_api_assembly.out_of_sync || temp_editor_api_assembly.out_of_sync;
+ }
+
+ return true; // Failed to load, assume they're outdated assemblies
+}
+
+String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync, const bool *p_editor_api_out_of_sync) {
#define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \
( \
@@ -625,46 +692,55 @@ String GDMono::update_api_assemblies_from_prebuilt() {
String("and the prebuilt assemblies are missing.") : \
String("and we failed to copy the prebuilt assemblies.")))
- bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync;
+ String dst_assemblies_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
- String core_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
- String editor_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+ String core_assembly_path = dst_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+ String editor_assembly_path = dst_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
- if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
- return String(); // No update needed
+ bool api_assemblies_out_of_sync = false;
- const int CONFIGS_LEN = 2;
- String configs[CONFIGS_LEN] = { String("Debug"), String("Release") };
+ if (p_core_api_out_of_sync && p_editor_api_out_of_sync) {
+ api_assemblies_out_of_sync = p_core_api_out_of_sync || p_editor_api_out_of_sync;
+ } else if (FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) {
+ // Determine if they're out of sync
+ if (!try_get_cached_api_hash_for(dst_assemblies_dir, api_assemblies_out_of_sync)) {
+ api_assemblies_out_of_sync = _temp_domain_load_are_assemblies_out_of_sync(p_config);
+ }
+ }
- for (int i = 0; i < CONFIGS_LEN; i++) {
- String config = configs[i];
+ // Note: Even if only one of the assemblies if missing or out of sync, we update both
- print_verbose("Updating '" + config + "' API assemblies");
+ if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
+ return String(); // No update needed
- String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(config);
- String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
- String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+ print_verbose("Updating '" + p_config + "' API assemblies");
- if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) {
- return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false);
- }
+ String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
+ String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+ String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
- // Copy the prebuilt Api
- if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) ||
- !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) {
- return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true);
- }
+ if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) {
+ return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ false);
}
+ // Copy the prebuilt Api
+ if (!copy_prebuilt_api_assembly(ApiAssemblyInfo::API_CORE, p_config) ||
+ !copy_prebuilt_api_assembly(ApiAssemblyInfo::API_EDITOR, p_config)) {
+ return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ true);
+ }
+
+ // Cache the api hash of the assemblies we just copied
+ create_cached_api_hash_for(dst_assemblies_dir);
+
return String(); // Updated successfully
#undef FAIL_REASON
}
#endif
-bool GDMono::_load_core_api_assembly() {
+bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) {
- if (core_api_assembly)
+ if (r_loaded_api_assembly.assembly)
return true;
#ifdef TOOLS_ENABLED
@@ -672,99 +748,115 @@ bool GDMono::_load_core_api_assembly() {
// If running the project manager, load it from the prebuilt API directory
String assembly_dir = !Main::is_project_manager() ?
- GodotSharpDirs::get_res_assemblies_dir() :
- GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
+ GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
+ GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
bool success = FileAccess::exists(assembly_path) &&
- load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly);
+ load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly);
#else
- bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
+ bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &r_loaded_api_assembly.assembly, p_refonly);
#endif
if (success) {
- APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE);
- core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash ||
- GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
- CS_GLUE_VERSION != api_assembly_ver.cs_glue_version;
- if (!core_api_assembly_out_of_sync) {
- GDMonoUtils::update_godot_api_cache();
-
- _install_trace_listener();
- }
+ ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_CORE);
+ r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash ||
+ GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
+ GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
} else {
- core_api_assembly_out_of_sync = false;
+ r_loaded_api_assembly.out_of_sync = false;
}
return success;
}
#ifdef TOOLS_ENABLED
-bool GDMono::_load_editor_api_assembly() {
+bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) {
- if (editor_api_assembly)
+ if (r_loaded_api_assembly.assembly)
return true;
// For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
// If running the project manager, load it from the prebuilt API directory
String assembly_dir = !Main::is_project_manager() ?
- GodotSharpDirs::get_res_assemblies_dir() :
- GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
+ GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
+ GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
bool success = FileAccess::exists(assembly_path) &&
- load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly);
+ load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly);
if (success) {
- APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR);
- editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash ||
- GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
- CS_GLUE_VERSION != api_assembly_ver.cs_glue_version;
+ ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_EDITOR);
+ r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash ||
+ GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
+ GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
} else {
- editor_api_assembly_out_of_sync = false;
+ r_loaded_api_assembly.out_of_sync = false;
}
return success;
}
#endif
-bool GDMono::_try_load_api_assemblies() {
-
- if (!_load_core_api_assembly()) {
+bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly,
+ const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback) {
+ if (!_load_core_api_assembly(r_core_api_assembly, p_config, p_refonly)) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load Core API assembly");
return false;
}
#ifdef TOOLS_ENABLED
- if (!_load_editor_api_assembly()) {
+ if (!_load_editor_api_assembly(r_editor_api_assembly, p_config, p_refonly)) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load Editor API assembly");
return false;
}
- if (editor_api_assembly_out_of_sync)
+ if (r_editor_api_assembly.out_of_sync)
return false;
#endif
// Check if the core API assembly is out of sync only after trying to load the
// editor API assembly. Otherwise, if both assemblies are out of sync, we would
// only update the former as we won't know the latter also needs to be updated.
- if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)
+ if (r_core_api_assembly.out_of_sync)
return false;
+ if (p_callback)
+ return p_callback();
+
return true;
}
+bool GDMono::_on_core_api_assembly_loaded() {
+ GDMonoUtils::update_godot_api_cache();
+
+ if (!GDMonoUtils::mono_cache.godot_api_cache_updated)
+ return false;
+
+ get_singleton()->_install_trace_listener();
+
+ return true;
+}
+
+bool GDMono::_try_load_api_assemblies_preset() {
+ return _try_load_api_assemblies(core_api_assembly, editor_api_assembly,
+ get_expected_api_build_config(), /* refonly: */ false, _on_core_api_assembly_loaded);
+}
+
void GDMono::_load_api_assemblies() {
- if (!_try_load_api_assemblies()) {
+ bool api_assemblies_loaded = _try_load_api_assemblies_preset();
+
+ if (!api_assemblies_loaded) {
#ifdef TOOLS_ENABLED
- // The API assemblies are out of sync. Fine, try one more time, but this time
- // update them from the prebuilt assemblies directory before trying to load them.
+ // The API assemblies are out of sync or some other error happened. Fine, try one more time, but
+ // this time update them from the prebuilt assemblies directory before trying to load them again.
// Shouldn't happen. The project manager loads the prebuilt API assemblies
CRASH_COND_MSG(Main::is_project_manager(), "Failed to load one of the prebuilt API assemblies.");
@@ -774,7 +866,7 @@ void GDMono::_load_api_assemblies() {
CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain.");
// 2. Update the API assemblies
- String update_error = update_api_assemblies_from_prebuilt();
+ String update_error = update_api_assemblies_from_prebuilt("Debug", &core_api_assembly.out_of_sync, &editor_api_assembly.out_of_sync);
CRASH_COND_MSG(!update_error.empty(), update_error);
// 3. Load the scripts domain again
@@ -782,28 +874,30 @@ void GDMono::_load_api_assemblies() {
CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain.");
// 4. Try loading the updated assemblies
- if (!_try_load_api_assemblies()) {
- // welp... too bad
-
- if (_are_api_assemblies_out_of_sync()) {
- if (core_api_assembly_out_of_sync) {
- ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync.");
- } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
- ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed.");
- }
-
- if (editor_api_assembly_out_of_sync) {
- ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync.");
- }
-
- CRASH_NOW();
- } else {
- CRASH_NOW_MSG("Failed to load one of the API assemblies.");
+ api_assemblies_loaded = _try_load_api_assemblies_preset();
+#endif
+ }
+
+ if (!api_assemblies_loaded) {
+ // welp... too bad
+
+ if (_are_api_assemblies_out_of_sync()) {
+ if (core_api_assembly.out_of_sync) {
+ ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync.");
+ } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
+ ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed.");
+ }
+
+#ifdef TOOLS_ENABLED
+ if (editor_api_assembly.out_of_sync) {
+ ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync.");
}
- }
-#else
- CRASH_NOW_MSG("Failed to load one of the API assemblies.");
#endif
+
+ CRASH_NOW();
+ } else {
+ CRASH_NOW_MSG("Failed to load one of the API assemblies.");
+ }
}
}
@@ -844,15 +938,14 @@ void GDMono::_install_trace_listener() {
#ifdef DEBUG_ENABLED
// Install the trace listener now before the project assembly is loaded
- typedef void (*DebuggingUtils_InstallTraceListener)(MonoObject **);
+ GDMonoClass *debug_utils = get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, "DebuggingUtils");
+ GDMonoMethod *install_func = debug_utils->get_method("InstallTraceListener");
+
MonoException *exc = NULL;
- GDMonoClass *debug_utils = core_api_assembly->get_class(BINDINGS_NAMESPACE, "DebuggingUtils");
- DebuggingUtils_InstallTraceListener install_func =
- (DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener");
- install_func((MonoObject **)&exc);
+ install_func->invoke_raw(NULL, NULL, &exc);
if (exc) {
- ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener.");
GDMonoUtils::debug_print_unhandled_exception(exc);
+ ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener.");
}
#endif
}
@@ -863,7 +956,7 @@ Error GDMono::_load_scripts_domain() {
print_verbose("Mono: Loading scripts domain...");
- scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain");
+ scripts_domain = GDMonoUtils::create_domain("GodotEngine.Domain.Scripts");
ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain.");
@@ -895,10 +988,13 @@ Error GDMono::_unload_scripts_domain() {
_domain_assemblies_cleanup(mono_domain_get_id(scripts_domain));
- core_api_assembly = NULL;
+ core_api_assembly.assembly = NULL;
+#ifdef TOOLS_ENABLED
+ editor_api_assembly.assembly = NULL;
+#endif
+
project_assembly = NULL;
#ifdef TOOLS_ENABLED
- editor_api_assembly = NULL;
tools_assembly = NULL;
tools_project_editor_assembly = NULL;
#endif
@@ -1068,16 +1164,9 @@ GDMono::GDMono() {
root_domain = NULL;
scripts_domain = NULL;
- core_api_assembly_out_of_sync = false;
-#ifdef TOOLS_ENABLED
- editor_api_assembly_out_of_sync = false;
-#endif
-
corlib_assembly = NULL;
- core_api_assembly = NULL;
project_assembly = NULL;
#ifdef TOOLS_ENABLED
- editor_api_assembly = NULL;
tools_assembly = NULL;
tools_project_editor_assembly = NULL;
#endif
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 4f7d3791f7..e14a0d8409 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -41,7 +41,7 @@
#include "../utils/mono_reg_utils.h"
#endif
-namespace APIAssembly {
+namespace ApiAssemblyInfo {
enum Type {
API_CORE,
API_EDITOR
@@ -76,7 +76,7 @@ struct Version {
};
String to_string(Type p_type);
-} // namespace APIAssembly
+} // namespace ApiAssemblyInfo
class GDMono {
@@ -86,44 +86,58 @@ public:
POLICY_LOG_ERROR
};
+ struct LoadedApiAssembly {
+ GDMonoAssembly *assembly;
+ bool out_of_sync;
+
+ LoadedApiAssembly() :
+ assembly(NULL),
+ out_of_sync(false) {
+ }
+ };
+
private:
bool runtime_initialized;
bool finalizing_scripts_domain;
+ UnhandledExceptionPolicy unhandled_exception_policy;
+
MonoDomain *root_domain;
MonoDomain *scripts_domain;
- bool core_api_assembly_out_of_sync;
-#ifdef TOOLS_ENABLED
- bool editor_api_assembly_out_of_sync;
-#endif
+ HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
GDMonoAssembly *corlib_assembly;
- GDMonoAssembly *core_api_assembly;
GDMonoAssembly *project_assembly;
#ifdef TOOLS_ENABLED
- GDMonoAssembly *editor_api_assembly;
GDMonoAssembly *tools_assembly;
GDMonoAssembly *tools_project_editor_assembly;
#endif
- HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
+ LoadedApiAssembly core_api_assembly;
+ LoadedApiAssembly editor_api_assembly;
- UnhandledExceptionPolicy unhandled_exception_policy;
-
- void _domain_assemblies_cleanup(uint32_t p_domain_id);
+ typedef bool (*CoreApiAssemblyLoadedCallback)();
bool _are_api_assemblies_out_of_sync();
+ bool _temp_domain_load_are_assemblies_out_of_sync(const String &p_config);
+
+ bool _load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly);
+#ifdef TOOLS_ENABLED
+ bool _load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly);
+#endif
+
+ static bool _on_core_api_assembly_loaded();
bool _load_corlib_assembly();
- bool _load_core_api_assembly();
#ifdef TOOLS_ENABLED
- bool _load_editor_api_assembly();
bool _load_tools_assemblies();
#endif
bool _load_project_assembly();
- bool _try_load_api_assemblies();
+ bool _try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly,
+ const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback);
+ bool _try_load_api_assemblies_preset();
void _load_api_assemblies();
void _install_trace_listener();
@@ -133,6 +147,8 @@ private:
Error _load_scripts_domain();
Error _unload_scripts_domain();
+ void _domain_assemblies_cleanup(uint32_t p_domain_id);
+
uint64_t api_core_hash;
#ifdef TOOLS_ENABLED
uint64_t api_editor_hash;
@@ -151,6 +167,7 @@ protected:
static GDMono *singleton;
public:
+#ifdef DEBUG_METHODS_ENABLED
uint64_t get_api_core_hash() {
if (api_core_hash == 0)
api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE);
@@ -162,11 +179,24 @@ public:
api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR);
return api_editor_hash;
}
+#endif // TOOLS_ENABLED
+#endif // DEBUG_METHODS_ENABLED
+
+ _FORCE_INLINE_ static String get_expected_api_build_config() {
+#ifdef TOOLS_ENABLED
+ return "Debug";
+#else
+#ifdef DEBUG_ENABLED
+ return "Debug";
+#else
+ return "Release";
#endif
+#endif
+ }
#ifdef TOOLS_ENABLED
- bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config);
- String update_api_assemblies_from_prebuilt();
+ bool copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config);
+ String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = NULL, const bool *p_editor_api_out_of_sync = NULL);
#endif
static GDMono *get_singleton() { return singleton; }
@@ -186,10 +216,10 @@ public:
_FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; }
_FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; }
- _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; }
+ _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly.assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; }
#ifdef TOOLS_ENABLED
- _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; }
+ _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly.assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; }
#endif
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index 6d91075ce3..7b3421fdb3 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -104,7 +104,7 @@ void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
ERR_FAIL_COND(!da);
Error err = da->change_dir(p_logs_dir);
- ERR_FAIL_COND(err != OK);
+ ERR_FAIL_COND_MSG(err != OK, "Cannot change directory to '" + p_logs_dir + "'.");
ERR_FAIL_COND(da->list_dir_begin() != OK);
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index e385f4c601..6504fbe423 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -550,6 +550,8 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
}
MonoDomain *create_domain(const String &p_friendly_name) {
+ print_verbose("Mono: Creating domain '" + p_friendly_name + "'...");
+
MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL);
if (domain) {
diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp
index 716c712ccc..e9efc7626d 100644
--- a/modules/mono/utils/string_utils.cpp
+++ b/modules/mono/utils/string_utils.cpp
@@ -165,7 +165,7 @@ Error read_all_file_utf8(const String &p_path, String &r_content) {
PoolVector<uint8_t> sourcef;
Error err;
FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V(err != OK, err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
int len = f->get_len();
sourcef.resize(len + 1);
diff --git a/modules/opensimplex/doc_classes/NoiseTexture.xml b/modules/opensimplex/doc_classes/NoiseTexture.xml
index 4b59a380f5..07d5eb27d6 100644
--- a/modules/opensimplex/doc_classes/NoiseTexture.xml
+++ b/modules/opensimplex/doc_classes/NoiseTexture.xml
@@ -17,6 +17,7 @@
</member>
<member name="bump_strength" type="float" setter="set_bump_strength" getter="get_bump_strength" default="8.0">
</member>
+ <member name="flags" type="int" setter="set_flags" getter="get_flags" override="true" default="7" />
<member name="height" type="int" setter="set_height" getter="get_height" default="512">
Height of the generated texture.
</member>
diff --git a/modules/opus/audio_stream_opus.cpp b/modules/opus/audio_stream_opus.cpp
index d3e8d3c9bb..43b0aecbf1 100644
--- a/modules/opus/audio_stream_opus.cpp
+++ b/modules/opus/audio_stream_opus.cpp
@@ -119,9 +119,7 @@ Error AudioStreamPlaybackOpus::_load_stream() {
Error err;
f = FileAccess::open(file, FileAccess::READ, &err);
- if (err) {
- ERR_FAIL_COND_V(err, err);
- }
+ ERR_FAIL_COND_V_MSG(err, err, "Cannot open file '" + file + "'.");
int _err = 0;
@@ -185,9 +183,7 @@ Error AudioStreamPlaybackOpus::set_file(const String &p_file) {
Error err;
f = FileAccess::open(file, FileAccess::READ, &err);
- if (err) {
- ERR_FAIL_COND_V(err, err);
- }
+ ERR_FAIL_COND_V_MSG(err, err, "Cannot open file '" + file + "'.");
int _err;
diff --git a/modules/recast/navigation_mesh_generator.cpp b/modules/recast/navigation_mesh_generator.cpp
index c5b60f2dca..320591cf7c 100644
--- a/modules/recast/navigation_mesh_generator.cpp
+++ b/modules/recast/navigation_mesh_generator.cpp
@@ -131,7 +131,7 @@ void EditorNavigationMeshGenerator::_add_faces(const PoolVector3Array &p_faces,
}
}
-void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask) {
+void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) {
if (Object::cast_to<MeshInstance>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
@@ -263,8 +263,10 @@ void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_tran
p_accumulated_transform = p_accumulated_transform * spatial->get_transform();
}
- for (int i = 0; i < p_node->get_child_count(); i++) {
- _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_verticies, p_indices, p_generate_from, p_collision_mask);
+ if (p_recurse_children) {
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_verticies, p_indices, p_generate_from, p_collision_mask, p_recurse_children);
+ }
}
}
@@ -439,7 +441,21 @@ void EditorNavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p
Vector<float> vertices;
Vector<int> indices;
- _parse_geometry(Object::cast_to<Spatial>(p_node)->get_transform().affine_inverse(), p_node, vertices, indices, p_nav_mesh->get_parsed_geometry_type(), p_nav_mesh->get_collision_mask());
+ List<Node *> parse_nodes;
+
+ if (p_nav_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_NAVMESH_CHILDREN) {
+ parse_nodes.push_back(p_node);
+ } else {
+ p_node->get_tree()->get_nodes_in_group(p_nav_mesh->get_source_group_name(), &parse_nodes);
+ }
+
+ Transform navmesh_xform = Object::cast_to<Spatial>(p_node)->get_transform().affine_inverse();
+ for (const List<Node *>::Element *E = parse_nodes.front(); E; E = E->next()) {
+ int geometry_type = p_nav_mesh->get_parsed_geometry_type();
+ uint32_t collision_mask = p_nav_mesh->get_collision_mask();
+ bool recurse_children = p_nav_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT;
+ _parse_geometry(navmesh_xform, E->get(), vertices, indices, geometry_type, collision_mask, recurse_children);
+ }
if (vertices.size() > 0 && indices.size() > 0) {
diff --git a/modules/recast/navigation_mesh_generator.h b/modules/recast/navigation_mesh_generator.h
index 30a6e3c835..f19622a4a9 100644
--- a/modules/recast/navigation_mesh_generator.h
+++ b/modules/recast/navigation_mesh_generator.h
@@ -47,7 +47,7 @@ protected:
static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies);
static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices);
static void _add_faces(const PoolVector3Array &p_faces, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices);
- static void _parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask);
+ static void _parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask, bool p_recurse_children);
static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh);
static void _build_recast_navigation_mesh(Ref<NavigationMesh> p_nav_mesh, EditorProgress *ep,
diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp b/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp
index e10f29e310..977ff064bc 100644
--- a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp
+++ b/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp
@@ -83,7 +83,7 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin
FileAccess *f = FileAccess::open(p_source_file, FileAccess::READ);
- ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
+ ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, "Cannot open file '" + p_source_file + "'.");
size_t len = f->get_len();
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index 6a1b463305..ed1a7f682b 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -175,7 +175,7 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
memdelete(file);
}
file = FileAccess::open(p_file, FileAccess::READ);
- ERR_FAIL_COND(!file);
+ ERR_FAIL_COND_MSG(!file, "Cannot open file '" + p_file + "'.");
#ifdef THEORA_USE_THREAD_STREAMING
thread_exit = false;
@@ -368,7 +368,7 @@ float VideoStreamPlaybackTheora::get_time() const {
return time - AudioServer::get_singleton()->get_output_latency() - delay_compensation; //-((get_total())/(float)vi.rate);
};
-Ref<Texture> VideoStreamPlaybackTheora::get_texture() {
+Ref<Texture> VideoStreamPlaybackTheora::get_texture() const {
return texture;
}
diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h
index 0c37d33358..b241722cd1 100644
--- a/modules/theora/video_stream_theora.h
+++ b/modules/theora/video_stream_theora.h
@@ -147,7 +147,7 @@ public:
void set_file(const String &p_file);
- virtual Ref<Texture> get_texture();
+ virtual Ref<Texture> get_texture() const;
virtual void update(float p_delta);
virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata);
diff --git a/modules/visual_script/config.py b/modules/visual_script/config.py
index 04e1a40b81..087a13a200 100644
--- a/modules/visual_script/config.py
+++ b/modules/visual_script/config.py
@@ -11,6 +11,7 @@ def get_doc_classes():
"VisualScriptBuiltinFunc",
"VisualScriptClassConstant",
"VisualScriptComment",
+ "VisualScriptComposeArray",
"VisualScriptCondition",
"VisualScriptConstant",
"VisualScriptConstructor",
@@ -28,6 +29,7 @@ def get_doc_classes():
"VisualScriptIndexSet",
"VisualScriptInputAction",
"VisualScriptIterator",
+ "VisualScriptLists",
"VisualScriptLocalVarSet",
"VisualScriptLocalVar",
"VisualScriptMathConstant",
diff --git a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml
index 9e3670ec35..b5b452ee47 100644
--- a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml
+++ b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml
@@ -217,7 +217,9 @@
</constant>
<constant name="MATH_LERP_ANGLE" value="66" enum="BuiltinFunc">
</constant>
- <constant name="FUNC_MAX" value="67" enum="BuiltinFunc">
+ <constant name="TEXT_ORD" value="67" enum="BuiltinFunc">
+ </constant>
+ <constant name="FUNC_MAX" value="68" enum="BuiltinFunc">
Represents the size of the [enum BuiltinFunc] enum.
</constant>
</constants>
diff --git a/modules/visual_script/doc_classes/VisualScriptComposeArray.xml b/modules/visual_script/doc_classes/VisualScriptComposeArray.xml
new file mode 100644
index 0000000000..92efbc51d1
--- /dev/null
+++ b/modules/visual_script/doc_classes/VisualScriptComposeArray.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualScriptComposeArray" inherits="VisualScriptLists" category="Core" version="3.2">
+ <brief_description>
+ A Visual Script Node used to create array from a list of items.
+ </brief_description>
+ <description>
+ A Visual Script Node used to compose array from the list of elements provided with custom in-graph UI hard coded in the VisualScript Editor.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ </methods>
+ <constants>
+ </constants>
+</class>
diff --git a/modules/visual_script/doc_classes/VisualScriptLists.xml b/modules/visual_script/doc_classes/VisualScriptLists.xml
new file mode 100644
index 0000000000..8cf3eb1d38
--- /dev/null
+++ b/modules/visual_script/doc_classes/VisualScriptLists.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualScriptLists" inherits="VisualScriptNode" category="Core" version="3.2">
+ <brief_description>
+ A Visual Script virtual class for in-graph editable nodes.
+ </brief_description>
+ <description>
+ A Visual Script virtual class that defines the shape and the default behaviour of the nodes that have to be in-graph editable nodes.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="add_input_data_port">
+ <return type="void">
+ </return>
+ <argument index="0" name="type" type="int" enum="Variant.Type">
+ </argument>
+ <argument index="1" name="name" type="String">
+ </argument>
+ <argument index="2" name="index" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="add_output_data_port">
+ <return type="void">
+ </return>
+ <argument index="0" name="type" type="int" enum="Variant.Type">
+ </argument>
+ <argument index="1" name="name" type="String">
+ </argument>
+ <argument index="2" name="index" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="remove_input_data_port">
+ <return type="void">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="remove_output_data_port">
+ <return type="void">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_input_data_port_name">
+ <return type="void">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <argument index="1" name="name" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_input_data_port_type">
+ <return type="void">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <argument index="1" name="type" type="int" enum="Variant.Type">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_output_data_port_name">
+ <return type="void">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <argument index="1" name="name" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_output_data_port_type">
+ <return type="void">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <argument index="1" name="type" type="int" enum="Variant.Type">
+ </argument>
+ <description>
+ </description>
+ </method>
+ </methods>
+ <constants>
+ </constants>
+</class>
diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp
index 24b96223d7..49272345fe 100644
--- a/modules/visual_script/register_types.cpp
+++ b/modules/visual_script/register_types.cpp
@@ -56,6 +56,8 @@ void register_visual_script_types() {
ClassDB::register_virtual_class<VisualScriptNode>();
ClassDB::register_class<VisualScriptFunctionState>();
ClassDB::register_class<VisualScriptFunction>();
+ ClassDB::register_virtual_class<VisualScriptLists>();
+ ClassDB::register_class<VisualScriptComposeArray>();
ClassDB::register_class<VisualScriptOperator>();
ClassDB::register_class<VisualScriptVariableSet>();
ClassDB::register_class<VisualScriptVariableGet>();
diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp
index 70389b6729..bb8612af6f 100644
--- a/modules/visual_script/visual_script.cpp
+++ b/modules/visual_script/visual_script.cpp
@@ -578,6 +578,10 @@ void VisualScript::get_data_connection_list(const StringName &p_func, List<DataC
}
}
+void VisualScript::set_tool_enabled(bool p_enabled) {
+ is_tool_script = p_enabled;
+}
+
void VisualScript::add_variable(const StringName &p_name, const Variant &p_default_value, bool p_export) {
ERR_FAIL_COND(instances.size());
@@ -894,7 +898,7 @@ ScriptInstance *VisualScript::instance_create(Object *p_this) {
#ifdef TOOLS_ENABLED
- if (!ScriptServer::is_scripting_enabled()) {
+ if (!ScriptServer::is_scripting_enabled() && !is_tool_script) {
PlaceHolderScriptInstance *sins = memnew(PlaceHolderScriptInstance(VisualScriptLanguage::singleton, Ref<Script>((Script *)this), p_this));
placeholders.insert(sins);
@@ -958,7 +962,7 @@ Error VisualScript::reload(bool p_keep_state) {
bool VisualScript::is_tool() const {
- return false;
+ return is_tool_script;
}
bool VisualScript::is_valid() const {
@@ -1010,17 +1014,16 @@ void VisualScript::get_script_method_list(List<MethodInfo> *p_list) const {
Ref<VisualScriptFunction> func = E->get().nodes[E->get().function_id].node;
if (func.is_valid()) {
-
for (int i = 0; i < func->get_argument_count(); i++) {
PropertyInfo arg;
arg.name = func->get_argument_name(i);
arg.type = func->get_argument_type(i);
mi.arguments.push_back(arg);
}
+
+ p_list->push_back(mi);
}
}
-
- p_list->push_back(mi);
}
}
@@ -1133,6 +1136,9 @@ void VisualScript::_set_data(const Dictionary &p_data) {
Array funcs = d["functions"];
functions.clear();
+ Vector2 last_pos = Vector2(-100 * funcs.size(), -100 * funcs.size()); // this is the center of the last fn box
+ Vector2 last_size = Vector2(0.0, 0.0);
+
for (int i = 0; i < funcs.size(); i++) {
Dictionary func = funcs[i];
@@ -1145,11 +1151,42 @@ void VisualScript::_set_data(const Dictionary &p_data) {
Array nodes = func["nodes"];
- for (int j = 0; j < nodes.size(); j += 3) {
+ if (!d.has("vs_unify") && nodes.size() > 0) {
+ Vector2 top_left = nodes[1];
+ Vector2 bottom_right = nodes[1];
- add_node(name, nodes[j], nodes[j + 2], nodes[j + 1]);
- }
+ for (int j = 0; j < nodes.size(); j += 3) {
+ Point2 pos = nodes[j + 1];
+ if (pos.y > top_left.y) {
+ top_left.y = pos.y;
+ }
+ if (pos.y < bottom_right.y) {
+ bottom_right.y = pos.y;
+ }
+ if (pos.x > bottom_right.x) {
+ bottom_right.x = pos.x;
+ }
+ if (pos.x < top_left.x) {
+ top_left.x = pos.x;
+ }
+ }
+
+ Vector2 size = Vector2(bottom_right.x - top_left.x, top_left.y - bottom_right.y);
+
+ Vector2 offset = last_pos + (last_size / 2.0) + (size / 2.0); // dunno I might just keep it in one axis but diagonal feels better....
+
+ last_pos = offset;
+ last_size = size;
+
+ for (int j = 0; j < nodes.size(); j += 3) {
+ add_node(name, nodes[j], nodes[j + 2], offset + nodes[j + 1]); // also add an additional buffer if you want to
+ }
+ } else {
+ for (int j = 0; j < nodes.size(); j += 3) {
+ add_node(name, nodes[j], nodes[j + 2], nodes[j + 1]);
+ }
+ }
Array sequence_connections = func["sequence_connections"];
for (int j = 0; j < sequence_connections.size(); j += 3) {
@@ -1164,6 +1201,11 @@ void VisualScript::_set_data(const Dictionary &p_data) {
data_connect(name, data_connections[j + 0], data_connections[j + 1], data_connections[j + 2], data_connections[j + 3]);
}
}
+
+ if (d.has("is_tool_script"))
+ is_tool_script = d["is_tool_script"];
+ else
+ is_tool_script = false;
}
Dictionary VisualScript::_get_data() const {
@@ -1245,6 +1287,8 @@ Dictionary VisualScript::_get_data() const {
}
d["functions"] = funcs;
+ d["is_tool_script"] = is_tool_script;
+ d["vs_unify"] = true;
return d;
}
@@ -1317,6 +1361,11 @@ void VisualScript::_bind_methods() {
VisualScript::VisualScript() {
base_type = "Object";
+ is_tool_script = false;
+}
+
+StringName VisualScript::get_default_func() const {
+ return StringName("f_312843592");
}
Set<int> VisualScript::get_output_sequence_ports_connected(const String &edited_func, int from_node) {
@@ -1392,6 +1441,10 @@ void VisualScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
for (const Map<StringName, VisualScript::Function>::Element *E = script->functions.front(); E; E = E->next()) {
+ if (E->key() == script->get_default_func()) {
+ continue;
+ }
+
MethodInfo mi;
mi.name = E->key();
if (E->get().function_id >= 0 && E->get().nodes.has(E->get().function_id)) {
@@ -1410,8 +1463,6 @@ void VisualScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
if (!vsf->is_sequenced()) { //assumed constant if not sequenced
mi.flags |= METHOD_FLAG_CONST;
}
-
- //vsf->Get_ for now at least it does not return..
}
}
@@ -1420,6 +1471,9 @@ void VisualScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
}
bool VisualScriptInstance::has_method(const StringName &p_method) const {
+ if (p_method == script->get_default_func())
+ return false;
+
return script->functions.has(p_method);
}
@@ -1991,6 +2045,9 @@ Ref<Script> VisualScriptInstance::get_script() const {
MultiplayerAPI::RPCMode VisualScriptInstance::get_rpc_mode(const StringName &p_method) const {
+ if (p_method == script->get_default_func())
+ return MultiplayerAPI::RPC_MODE_DISABLED;
+
const Map<StringName, VisualScript::Function>::Element *E = script->functions.find(p_method);
if (!E) {
return MultiplayerAPI::RPC_MODE_DISABLED;
@@ -2039,11 +2096,14 @@ void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_o
for (const Map<StringName, VisualScript::Variable>::Element *E = script->variables.front(); E; E = E->next()) {
variables[E->key()] = E->get().default_value;
- //no hacer que todo exporte, solo las que queres!
}
for (const Map<StringName, VisualScript::Function>::Element *E = script->functions.front(); E; E = E->next()) {
+ if (E->key() == script->get_default_func()) {
+ continue;
+ }
+
Function function;
function.node = E->get().function_id;
function.max_stack = 0;
@@ -2080,6 +2140,7 @@ void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_o
for (const Map<int, VisualScript::Function::NodeData>::Element *F = E->get().nodes.front(); F; F = F->next()) {
Ref<VisualScriptNode> node = F->get().node;
+
VisualScriptNodeInstance *instance = node->instance(this); //create instance
ERR_FAIL_COND(!instance);
diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h
index 098c28370d..a035f6d42d 100644
--- a/modules/visual_script/visual_script.h
+++ b/modules/visual_script/visual_script.h
@@ -239,6 +239,7 @@ private:
PropertyInfo info;
Variant default_value;
bool _export;
+ // add getter & setter options here
};
Map<StringName, Function> functions;
@@ -247,6 +248,8 @@ private:
Map<Object *, VisualScriptInstance *> instances;
+ bool is_tool_script;
+
#ifdef TOOLS_ENABLED
Set<PlaceHolderScriptInstance *> placeholders;
//void _update_placeholder(PlaceHolderScriptInstance *p_placeholder);
@@ -265,6 +268,8 @@ protected:
static void _bind_methods();
public:
+ // TODO: Remove it in future when breaking changes are acceptable
+ StringName get_default_func() const;
void add_function(const StringName &p_name);
bool has_function(const StringName &p_name) const;
void remove_function(const StringName &p_name);
@@ -273,6 +278,7 @@ public:
Vector2 get_function_scroll(const StringName &p_name) const;
void get_function_list(List<StringName> *r_functions) const;
int get_function_node_id(const StringName &p_name) const;
+ void set_tool_enabled(bool p_enabled);
void add_node(const StringName &p_func, int p_id, const Ref<VisualScriptNode> &p_node, const Point2 &p_pos = Point2());
void remove_node(const StringName &p_func, int p_id);
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index 8faa342bbe..0db771f7c0 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -421,31 +421,42 @@ void VisualScriptEditor::_update_graph_connections() {
graph->clear_connections();
- List<VisualScript::SequenceConnection> sequence_conns;
- script->get_sequence_connection_list(edited_func, &sequence_conns);
-
- for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
+ List<StringName> funcs;
+ script->get_function_list(&funcs);
- graph->connect_node(itos(E->get().from_node), E->get().from_output, itos(E->get().to_node), 0);
+ if (funcs.size() <= 0) {
+ updating_graph = false;
+ return;
}
- List<VisualScript::DataConnection> data_conns;
- script->get_data_connection_list(edited_func, &data_conns);
-
- for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) {
+ for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) {
- VisualScript::DataConnection dc = E->get();
+ List<VisualScript::SequenceConnection> sequence_conns;
+ script->get_sequence_connection_list(F->get(), &sequence_conns);
- Ref<VisualScriptNode> from_node = script->get_node(edited_func, E->get().from_node);
- Ref<VisualScriptNode> to_node = script->get_node(edited_func, E->get().to_node);
+ for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
- if (to_node->has_input_sequence_port()) {
- dc.to_port++;
+ graph->connect_node(itos(E->get().from_node), E->get().from_output, itos(E->get().to_node), 0);
}
- dc.from_port += from_node->get_output_sequence_port_count();
+ List<VisualScript::DataConnection> data_conns;
+ script->get_data_connection_list(F->get(), &data_conns);
- graph->connect_node(itos(E->get().from_node), dc.from_port, itos(E->get().to_node), dc.to_port);
+ for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) {
+
+ VisualScript::DataConnection dc = E->get();
+
+ Ref<VisualScriptNode> from_node = script->get_node(F->get(), E->get().from_node);
+ Ref<VisualScriptNode> to_node = script->get_node(F->get(), E->get().to_node);
+
+ if (to_node->has_input_sequence_port()) {
+ dc.to_port++;
+ }
+
+ dc.from_port += from_node->get_output_sequence_port_count();
+
+ graph->connect_node(itos(E->get().from_node), dc.from_port, itos(E->get().to_node), dc.to_port);
+ }
}
}
@@ -474,7 +485,10 @@ void VisualScriptEditor::_update_graph(int p_only_id) {
}
}
- if (!script->has_function(edited_func)) {
+ List<StringName> funcs;
+ script->get_function_list(&funcs);
+
+ if (funcs.size() <= 0) {
graph->hide();
select_func_text->show();
updating_graph = false;
@@ -516,254 +530,390 @@ void VisualScriptEditor::_update_graph(int p_only_id) {
Ref<Texture> seq_port = Control::get_icon("VisualShaderPort", "EditorIcons");
- List<int> ids;
- script->get_node_list(edited_func, &ids);
- StringName editor_icons = "EditorIcons";
+ for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) { // loop through all the functions
- for (List<int>::Element *E = ids.front(); E; E = E->next()) {
+ List<int> ids;
+ script->get_node_list(F->get(), &ids);
+ StringName editor_icons = "EditorIcons";
- if (p_only_id >= 0 && p_only_id != E->get())
- continue;
+ for (List<int>::Element *E = ids.front(); E; E = E->next()) {
- Ref<VisualScriptNode> node = script->get_node(edited_func, E->get());
- Vector2 pos = script->get_node_position(edited_func, E->get());
+ if (p_only_id >= 0 && p_only_id != E->get())
+ continue;
- GraphNode *gnode = memnew(GraphNode);
- gnode->set_title(node->get_caption());
- gnode->set_offset(pos * EDSCALE);
- if (error_line == E->get()) {
- gnode->set_overlay(GraphNode::OVERLAY_POSITION);
- } else if (node->is_breakpoint()) {
- gnode->set_overlay(GraphNode::OVERLAY_BREAKPOINT);
- }
+ Ref<VisualScriptNode> node = script->get_node(F->get(), E->get());
+ Vector2 pos = script->get_node_position(F->get(), E->get());
- gnode->set_meta("__vnode", node);
- gnode->set_name(itos(E->get()));
- gnode->connect("dragged", this, "_node_moved", varray(E->get()));
- gnode->connect("close_request", this, "_remove_node", varray(E->get()), CONNECT_DEFERRED);
+ GraphNode *gnode = memnew(GraphNode);
+ gnode->set_title(node->get_caption());
+ gnode->set_offset(pos * EDSCALE);
+ if (error_line == E->get()) {
+ gnode->set_overlay(GraphNode::OVERLAY_POSITION);
+ } else if (node->is_breakpoint()) {
+ gnode->set_overlay(GraphNode::OVERLAY_BREAKPOINT);
+ }
- if (E->get() != script->get_function_node_id(edited_func)) {
- //function can't be erased
- gnode->set_show_close_button(true);
- }
+ gnode->set_meta("__vnode", node);
+ gnode->set_name(itos(E->get()));
+ gnode->connect("dragged", this, "_node_moved", varray(E->get()));
+ gnode->connect("close_request", this, "_remove_node", varray(E->get()), CONNECT_DEFERRED);
- bool has_gnode_text = false;
+ if (E->get() != script->get_function_node_id(F->get())) {
+ //function can't be erased
+ gnode->set_show_close_button(true);
+ }
- if (Object::cast_to<VisualScriptExpression>(node.ptr())) {
- has_gnode_text = true;
- LineEdit *line_edit = memnew(LineEdit);
- line_edit->set_text(node->get_text());
- line_edit->set_expand_to_text_length(true);
- line_edit->add_font_override("font", get_font("source", "EditorFonts"));
- gnode->add_child(line_edit);
- line_edit->connect("text_changed", this, "_expression_text_changed", varray(E->get()));
- } else {
- String text = node->get_text();
- if (!text.empty()) {
+ bool has_gnode_text = false;
+
+ Ref<VisualScriptLists> nd_list = node;
+ bool is_vslist = nd_list.is_valid();
+ if (is_vslist) {
+ HBoxContainer *hbnc = memnew(HBoxContainer);
+ if (nd_list->is_input_port_editable()) {
+ has_gnode_text = true;
+ Button *btn = memnew(Button);
+ btn->set_text("Add Input Port");
+ hbnc->add_child(btn);
+ btn->connect("pressed", this, "_add_input_port", varray(E->get()));
+ }
+ if (nd_list->is_output_port_editable()) {
+ if (nd_list->is_input_port_editable())
+ hbnc->add_spacer();
+ has_gnode_text = true;
+ Button *btn = memnew(Button);
+ btn->set_text("Add Output Port");
+ hbnc->add_child(btn);
+ btn->connect("pressed", this, "_add_output_port", varray(E->get()));
+ }
+ gnode->add_child(hbnc);
+ } else if (Object::cast_to<VisualScriptExpression>(node.ptr())) {
has_gnode_text = true;
- Label *label = memnew(Label);
- label->set_text(text);
- gnode->add_child(label);
+ LineEdit *line_edit = memnew(LineEdit);
+ line_edit->set_text(node->get_text());
+ line_edit->set_expand_to_text_length(true);
+ line_edit->add_font_override("font", get_font("source", "EditorFonts"));
+ gnode->add_child(line_edit);
+ line_edit->connect("text_changed", this, "_expression_text_changed", varray(E->get()));
+ } else {
+ String text = node->get_text();
+ if (!text.empty()) {
+ has_gnode_text = true;
+ Label *label = memnew(Label);
+ label->set_text(text);
+ gnode->add_child(label);
+ }
}
- }
-
- if (Object::cast_to<VisualScriptComment>(node.ptr())) {
- Ref<VisualScriptComment> vsc = node;
- gnode->set_comment(true);
- gnode->set_resizable(true);
- gnode->set_custom_minimum_size(vsc->get_size() * EDSCALE);
- gnode->connect("resize_request", this, "_comment_node_resized", varray(E->get()));
- }
- if (node_styles.has(node->get_category())) {
- Ref<StyleBoxFlat> sbf = node_styles[node->get_category()];
- if (gnode->is_comment())
- sbf = EditorNode::get_singleton()->get_theme_base()->get_theme()->get_stylebox("comment", "GraphNode");
-
- Color c = sbf->get_border_color();
- c.a = 1;
- if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) {
- Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0);
- mono_color.a = 0.85;
- c = mono_color;
+ if (Object::cast_to<VisualScriptComment>(node.ptr())) {
+ Ref<VisualScriptComment> vsc = node;
+ gnode->set_comment(true);
+ gnode->set_resizable(true);
+ gnode->set_custom_minimum_size(vsc->get_size() * EDSCALE);
+ gnode->connect("resize_request", this, "_comment_node_resized", varray(E->get()));
}
- gnode->add_color_override("title_color", c);
- c.a = 0.7;
- gnode->add_color_override("close_color", c);
- gnode->add_color_override("resizer_color", c);
- gnode->add_style_override("frame", sbf);
- }
-
- const Color mono_color = get_color("mono_color", "Editor");
-
- int slot_idx = 0;
-
- bool single_seq_output = node->get_output_sequence_port_count() == 1 && node->get_output_sequence_port_text(0) == String();
- if ((node->has_input_sequence_port() || single_seq_output) || has_gnode_text) {
- // IF has_gnode_text is true BUT we have no sequence ports to draw (in here),
- // we still draw the disabled default ones to shift up the slots by one,
- // so the slots DON'T start with the content text.
-
- // IF has_gnode_text is false, but we DO want to draw default sequence ports,
- // we draw a dummy text to take up the position of the sequence nodes, so all the other ports are still aligned correctly.
- if (!has_gnode_text) {
- Label *dummy = memnew(Label);
- dummy->set_text(" ");
- gnode->add_child(dummy);
+ if (node_styles.has(node->get_category())) {
+ Ref<StyleBoxFlat> sbf = node_styles[node->get_category()];
+ if (gnode->is_comment())
+ sbf = EditorNode::get_singleton()->get_theme_base()->get_theme()->get_stylebox("comment", "GraphNode");
+
+ Color c = sbf->get_border_color();
+ c.a = 1;
+ if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) {
+ Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0);
+ mono_color.a = 0.85;
+ c = mono_color;
+ }
+ gnode->add_color_override("title_color", c);
+ c.a = 0.7;
+ gnode->add_color_override("close_color", c);
+ gnode->add_color_override("resizer_color", c);
+ gnode->add_style_override("frame", sbf);
}
- gnode->set_slot(0, node->has_input_sequence_port(), TYPE_SEQUENCE, mono_color, single_seq_output, TYPE_SEQUENCE, mono_color, seq_port, seq_port);
- slot_idx++;
- }
- int mixed_seq_ports = 0;
+ const Color mono_color = get_color("mono_color", "Editor");
- if (!single_seq_output) {
+ int slot_idx = 0;
- if (node->has_mixed_input_and_sequence_ports()) {
- mixed_seq_ports = node->get_output_sequence_port_count();
- } else {
- for (int i = 0; i < node->get_output_sequence_port_count(); i++) {
+ bool single_seq_output = node->get_output_sequence_port_count() == 1 && node->get_output_sequence_port_text(0) == String();
+ if ((node->has_input_sequence_port() || single_seq_output) || has_gnode_text) {
+ // IF has_gnode_text is true BUT we have no sequence ports to draw (in here),
+ // we still draw the disabled default ones to shift up the slots by one,
+ // so the slots DON'T start with the content text.
- Label *text2 = memnew(Label);
- text2->set_text(node->get_output_sequence_port_text(i));
- text2->set_align(Label::ALIGN_RIGHT);
- gnode->add_child(text2);
- gnode->set_slot(slot_idx, false, 0, Color(), true, TYPE_SEQUENCE, mono_color, seq_port, seq_port);
- slot_idx++;
+ // IF has_gnode_text is false, but we DO want to draw default sequence ports,
+ // we draw a dummy text to take up the position of the sequence nodes, so all the other ports are still aligned correctly.
+ if (!has_gnode_text) {
+ Label *dummy = memnew(Label);
+ dummy->set_text(" ");
+ gnode->add_child(dummy);
}
+ gnode->set_slot(0, node->has_input_sequence_port(), TYPE_SEQUENCE, mono_color, single_seq_output, TYPE_SEQUENCE, mono_color, seq_port, seq_port);
+ slot_idx++;
}
- }
- for (int i = 0; i < MAX(node->get_output_value_port_count(), MAX(mixed_seq_ports, node->get_input_value_port_count())); i++) {
+ int mixed_seq_ports = 0;
- bool left_ok = false;
- Variant::Type left_type = Variant::NIL;
- String left_name;
+ if (!single_seq_output) {
- if (i < node->get_input_value_port_count()) {
- PropertyInfo pi = node->get_input_value_port_info(i);
- left_ok = true;
- left_type = pi.type;
- left_name = pi.name;
+ if (node->has_mixed_input_and_sequence_ports()) {
+ mixed_seq_ports = node->get_output_sequence_port_count();
+ } else {
+ for (int i = 0; i < node->get_output_sequence_port_count(); i++) {
+
+ Label *text2 = memnew(Label);
+ text2->set_text(node->get_output_sequence_port_text(i));
+ text2->set_align(Label::ALIGN_RIGHT);
+ gnode->add_child(text2);
+ gnode->set_slot(slot_idx, false, 0, Color(), true, TYPE_SEQUENCE, mono_color, seq_port, seq_port);
+ slot_idx++;
+ }
+ }
}
- bool right_ok = false;
- Variant::Type right_type = Variant::NIL;
- String right_name;
+ for (int i = 0; i < MAX(node->get_output_value_port_count(), MAX(mixed_seq_ports, node->get_input_value_port_count())); i++) {
- if (i >= mixed_seq_ports && i < node->get_output_value_port_count() + mixed_seq_ports) {
- PropertyInfo pi = node->get_output_value_port_info(i - mixed_seq_ports);
- right_ok = true;
- right_type = pi.type;
- right_name = pi.name;
- }
+ bool left_ok = false;
+ Variant::Type left_type = Variant::NIL;
+ String left_name;
- HBoxContainer *hbc = memnew(HBoxContainer);
+ if (i < node->get_input_value_port_count()) {
+ PropertyInfo pi = node->get_input_value_port_info(i);
+ left_ok = true;
+ left_type = pi.type;
+ left_name = pi.name;
+ }
- if (left_ok) {
+ bool right_ok = false;
+ Variant::Type right_type = Variant::NIL;
+ String right_name;
- Ref<Texture> t;
- if (left_type >= 0 && left_type < Variant::VARIANT_MAX) {
- t = type_icons[left_type];
- }
- if (t.is_valid()) {
- TextureRect *tf = memnew(TextureRect);
- tf->set_texture(t);
- tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
- hbc->add_child(tf);
+ if (i >= mixed_seq_ports && i < node->get_output_value_port_count() + mixed_seq_ports) {
+ PropertyInfo pi = node->get_output_value_port_info(i - mixed_seq_ports);
+ right_ok = true;
+ right_type = pi.type;
+ right_name = pi.name;
}
+ VBoxContainer *vbc = memnew(VBoxContainer);
+ HBoxContainer *hbc = memnew(HBoxContainer);
+ HBoxContainer *hbc2 = memnew(HBoxContainer);
+ vbc->add_child(hbc);
+ vbc->add_child(hbc2);
+ if (left_ok) {
+
+ Ref<Texture> t;
+ if (left_type >= 0 && left_type < Variant::VARIANT_MAX) {
+ t = type_icons[left_type];
+ }
+ if (t.is_valid()) {
+ TextureRect *tf = memnew(TextureRect);
+ tf->set_texture(t);
+ tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
+ hbc->add_child(tf);
+ }
- hbc->add_child(memnew(Label(left_name)));
+ if (is_vslist) {
+ if (nd_list->is_input_port_name_editable()) {
+ LineEdit *name_box = memnew(LineEdit);
+ hbc->add_child(name_box);
+ name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0));
+ name_box->set_text(left_name);
+ name_box->set_expand_to_text_length(true);
+ name_box->connect("resized", this, "_update_node_size", varray(E->get()));
+ name_box->connect("focus_exited", this, "_port_name_focus_out", varray(name_box, E->get(), i, true));
+ } else {
+ hbc->add_child(memnew(Label(left_name)));
+ }
- if (left_type != Variant::NIL && !script->is_input_value_port_connected(edited_func, E->get(), i)) {
+ if (nd_list->is_input_port_type_editable()) {
+ OptionButton *opbtn = memnew(OptionButton);
+ for (int j = Variant::NIL; j < Variant::VARIANT_MAX; j++) {
+ opbtn->add_item(Variant::get_type_name(Variant::Type(j)));
+ }
+ opbtn->select(left_type);
+ opbtn->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
+ hbc->add_child(opbtn);
+ opbtn->connect("item_selected", this, "_change_port_type", varray(E->get(), i, true), CONNECT_DEFERRED);
+ }
- PropertyInfo pi = node->get_input_value_port_info(i);
- Button *button = memnew(Button);
- Variant value = node->get_default_input_value(i);
- if (value.get_type() != left_type) {
- //different type? for now convert
- //not the same, reconvert
- Variant::CallError ce;
- const Variant *existingp = &value;
- value = Variant::construct(left_type, &existingp, 1, ce, false);
+ Button *rmbtn = memnew(Button);
+ rmbtn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Remove", "EditorIcons"));
+ hbc->add_child(rmbtn);
+ rmbtn->connect("pressed", this, "_remove_input_port", varray(E->get(), i), CONNECT_DEFERRED);
+ } else {
+ hbc->add_child(memnew(Label(left_name)));
}
- if (left_type == Variant::COLOR) {
- button->set_custom_minimum_size(Size2(30, 0) * EDSCALE);
- button->connect("draw", this, "_draw_color_over_button", varray(button, value));
- } else if (left_type == Variant::OBJECT && Ref<Resource>(value).is_valid()) {
+ if (left_type != Variant::NIL && !script->is_input_value_port_connected(F->get(), E->get(), i)) {
+
+ PropertyInfo pi = node->get_input_value_port_info(i);
+ Button *button = memnew(Button);
+ Variant value = node->get_default_input_value(i);
+ if (value.get_type() != left_type) {
+ //different type? for now convert
+ //not the same, reconvert
+ Variant::CallError ce;
+ const Variant *existingp = &value;
+ value = Variant::construct(left_type, &existingp, 1, ce, false);
+ }
- Ref<Resource> res = value;
- Array arr;
- arr.push_back(button->get_instance_id());
- arr.push_back(String(value));
- EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res, this, "_button_resource_previewed", arr);
+ if (left_type == Variant::COLOR) {
+ button->set_custom_minimum_size(Size2(30, 0) * EDSCALE);
+ button->connect("draw", this, "_draw_color_over_button", varray(button, value));
+ } else if (left_type == Variant::OBJECT && Ref<Resource>(value).is_valid()) {
- } else if (pi.type == Variant::INT && pi.hint == PROPERTY_HINT_ENUM) {
+ Ref<Resource> res = value;
+ Array arr;
+ arr.push_back(button->get_instance_id());
+ arr.push_back(String(value));
+ EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res, this, "_button_resource_previewed", arr);
- button->set_text(pi.hint_string.get_slice(",", value));
- } else {
+ } else if (pi.type == Variant::INT && pi.hint == PROPERTY_HINT_ENUM) {
+
+ button->set_text(pi.hint_string.get_slice(",", value));
+ } else {
- button->set_text(value);
+ button->set_text(value);
+ }
+ button->connect("pressed", this, "_default_value_edited", varray(button, E->get(), i));
+ hbc2->add_child(button);
}
- button->connect("pressed", this, "_default_value_edited", varray(button, E->get(), i));
- hbc->add_child(button);
+ } else {
+ Control *c = memnew(Control);
+ c->set_custom_minimum_size(Size2(10, 0) * EDSCALE);
+ hbc->add_child(c);
}
- } else {
- Control *c = memnew(Control);
- c->set_custom_minimum_size(Size2(10, 0) * EDSCALE);
- hbc->add_child(c);
- }
- hbc->add_spacer();
+ hbc->add_spacer();
+ hbc2->add_spacer();
- if (i < mixed_seq_ports) {
+ if (i < mixed_seq_ports) {
- Label *text2 = memnew(Label);
- text2->set_text(node->get_output_sequence_port_text(i));
- text2->set_align(Label::ALIGN_RIGHT);
- hbc->add_child(text2);
- }
+ Label *text2 = memnew(Label);
+ text2->set_text(node->get_output_sequence_port_text(i));
+ text2->set_align(Label::ALIGN_RIGHT);
+ hbc->add_child(text2);
+ }
- if (right_ok) {
+ if (right_ok) {
+
+ if (is_vslist) {
+ Button *rmbtn = memnew(Button);
+ rmbtn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Remove", "EditorIcons"));
+ hbc->add_child(rmbtn);
+ rmbtn->connect("pressed", this, "_remove_output_port", varray(E->get(), i), CONNECT_DEFERRED);
+
+ if (nd_list->is_output_port_type_editable()) {
+ OptionButton *opbtn = memnew(OptionButton);
+ for (int j = Variant::NIL; j < Variant::VARIANT_MAX; j++) {
+ opbtn->add_item(Variant::get_type_name(Variant::Type(j)));
+ }
+ opbtn->select(right_type);
+ opbtn->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
+ hbc->add_child(opbtn);
+ opbtn->connect("item_selected", this, "_change_port_type", varray(E->get(), i, false), CONNECT_DEFERRED);
+ }
- hbc->add_child(memnew(Label(right_name)));
+ if (nd_list->is_output_port_name_editable()) {
+ LineEdit *name_box = memnew(LineEdit);
+ hbc->add_child(name_box);
+ name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0));
+ name_box->set_text(right_name);
+ name_box->set_expand_to_text_length(true);
+ name_box->connect("resized", this, "_update_node_size", varray(E->get()));
+ name_box->connect("focus_exited", this, "_port_name_focus_out", varray(name_box, E->get(), i, false));
+ } else {
+ hbc->add_child(memnew(Label(right_name)));
+ }
+ } else {
+ hbc->add_child(memnew(Label(right_name)));
+ }
- Ref<Texture> t;
- if (right_type >= 0 && right_type < Variant::VARIANT_MAX) {
- t = type_icons[right_type];
+ Ref<Texture> t;
+ if (right_type >= 0 && right_type < Variant::VARIANT_MAX) {
+ t = type_icons[right_type];
+ }
+ if (t.is_valid()) {
+ TextureRect *tf = memnew(TextureRect);
+ tf->set_texture(t);
+ tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
+ hbc->add_child(tf);
+ }
}
- if (t.is_valid()) {
- TextureRect *tf = memnew(TextureRect);
- tf->set_texture(t);
- tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
- hbc->add_child(tf);
+
+ gnode->add_child(vbc);
+
+ bool dark_theme = get_constant("dark_theme", "Editor");
+ if (i < mixed_seq_ports) {
+ gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), true, TYPE_SEQUENCE, mono_color, Ref<Texture>(), seq_port);
+ } else {
+ gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), right_ok, right_type, _color_from_type(right_type, dark_theme));
}
+
+ slot_idx++;
}
- gnode->add_child(hbc);
+ graph->add_child(gnode);
- bool dark_theme = get_constant("dark_theme", "Editor");
- if (i < mixed_seq_ports) {
- gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), true, TYPE_SEQUENCE, mono_color, Ref<Texture>(), seq_port);
- } else {
- gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), right_ok, right_type, _color_from_type(right_type, dark_theme));
+ if (gnode->is_comment()) {
+ graph->move_child(gnode, 0);
}
-
- slot_idx++;
}
+ }
+ _update_graph_connections();
+ // use default_func instead of default_func for now I think that should be good stop gap solution to ensure not breaking anything
+ graph->call_deferred("set_scroll_ofs", script->get_function_scroll(default_func) * EDSCALE);
+ updating_graph = false;
+}
- graph->add_child(gnode);
+void VisualScriptEditor::_change_port_type(int p_select, int p_id, int p_port, bool is_input) {
- if (gnode->is_comment()) {
- graph->move_child(gnode, 0);
- }
+ StringName func = _get_function_of_node(p_id);
+
+ Ref<VisualScriptLists> vsn = script->get_node(func, p_id);
+ if (!vsn.is_valid())
+ return;
+
+ undo_redo->create_action("Change Port Type");
+ if (is_input) {
+ undo_redo->add_do_method(vsn.ptr(), "set_input_data_port_type", p_port, Variant::Type(p_select));
+ undo_redo->add_undo_method(vsn.ptr(), "set_input_data_port_type", p_port, vsn->get_input_value_port_info(p_port).type);
+ } else {
+ undo_redo->add_do_method(vsn.ptr(), "set_output_data_port_type", p_port, Variant::Type(p_select));
+ undo_redo->add_undo_method(vsn.ptr(), "set_output_data_port_type", p_port, vsn->get_output_value_port_info(p_port).type);
}
+ undo_redo->commit_action();
+}
- _update_graph_connections();
- graph->call_deferred("set_scroll_ofs", script->get_function_scroll(edited_func) * EDSCALE); //may need to adapt a bit, let it do so
- updating_graph = false;
+void VisualScriptEditor::_update_node_size(int p_id) {
+
+ Node *node = graph->get_node(itos(p_id));
+ if (Object::cast_to<Control>(node))
+ Object::cast_to<Control>(node)->set_size(Vector2(1, 1)); //shrink if text is smaller
+}
+void VisualScriptEditor::_port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input) {
+ StringName func = _get_function_of_node(p_id);
+
+ Ref<VisualScriptLists> vsn = script->get_node(func, p_id);
+ if (!vsn.is_valid())
+ return;
+
+ String text;
+
+ if (Object::cast_to<LineEdit>(p_name_box))
+ text = Object::cast_to<LineEdit>(p_name_box)->get_text();
+ else
+ return;
+
+ undo_redo->create_action("Change Port Name");
+ if (is_input) {
+ undo_redo->add_do_method(vsn.ptr(), "set_input_data_port_name", p_port, text);
+ undo_redo->add_undo_method(vsn.ptr(), "set_input_data_port_name", p_port, vsn->get_input_value_port_info(p_port).name);
+ } else {
+ undo_redo->add_do_method(vsn.ptr(), "set_output_data_port_name", p_port, text);
+ undo_redo->add_undo_method(vsn.ptr(), "set_output_data_port_name", p_port, vsn->get_output_value_port_info(p_port).name);
+ }
+ undo_redo->commit_action();
}
void VisualScriptEditor::_update_members() {
@@ -784,11 +934,16 @@ void VisualScriptEditor::_update_members() {
List<StringName> func_names;
script->get_function_list(&func_names);
for (List<StringName>::Element *E = func_names.front(); E; E = E->next()) {
+
+ if (E->get() == default_func) {
+ continue;
+ }
+
TreeItem *ti = members->create_item(functions);
ti->set_text(0, E->get());
ti->set_selectable(0, true);
- ti->set_editable(0, true);
ti->set_metadata(0, E->get());
+ ti->add_button(0, Control::get_icon("Edit", "EditorIcons"), 0);
if (selected == E->get())
ti->select(0);
}
@@ -888,15 +1043,15 @@ void VisualScriptEditor::_member_selected() {
if (ti->get_parent() == members->get_root()->get_children()) {
- if (edited_func != selected) {
-
- revert_on_drag = edited_func;
- edited_func = selected;
- _update_members();
- _update_graph();
+#ifdef OSX_ENABLED
+ bool held_ctrl = Input::get_singleton()->is_key_pressed(KEY_META);
+#else
+ bool held_ctrl = Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+#endif
+ if (held_ctrl) {
+ ERR_FAIL_COND(!script->has_function(selected));
+ _center_on_node(selected, script->get_function_node_id(selected));
}
-
- return; //or crash because it will become invalid
}
}
@@ -936,9 +1091,6 @@ void VisualScriptEditor::_member_edited() {
if (ti->get_parent() == root->get_children()) {
- if (edited_func == selected) {
- edited_func = new_name;
- }
selected = new_name;
int node_id = script->get_function_node_id(name);
@@ -950,10 +1102,27 @@ void VisualScriptEditor::_member_edited() {
undo_redo->add_do_method(script.ptr(), "rename_function", name, new_name);
undo_redo->add_undo_method(script.ptr(), "rename_function", new_name, name);
if (func.is_valid()) {
-
undo_redo->add_do_method(func.ptr(), "set_name", new_name);
undo_redo->add_undo_method(func.ptr(), "set_name", name);
}
+
+ // also fix all function calls
+ List<StringName> flst;
+ script->get_function_list(&flst);
+ for (List<StringName>::Element *E = flst.front(); E; E = E->next()) {
+ List<int> lst;
+ script->get_node_list(E->get(), &lst);
+ for (List<int>::Element *F = lst.front(); F; F = F->next()) {
+ Ref<VisualScriptFunctionCall> fncall = script->get_node(E->get(), F->get());
+ if (!fncall.is_valid())
+ continue;
+ if (fncall->get_function() == name) {
+ undo_redo->add_do_method(fncall.ptr(), "set_function", new_name);
+ undo_redo->add_undo_method(fncall.ptr(), "set_function", name);
+ }
+ }
+ }
+
undo_redo->add_do_method(this, "_update_members");
undo_redo->add_undo_method(this, "_update_members");
undo_redo->add_do_method(this, "_update_graph");
@@ -962,8 +1131,6 @@ void VisualScriptEditor::_member_edited() {
undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed");
undo_redo->commit_action();
- // _update_graph();
-
return; //or crash because it will become invalid
}
@@ -998,6 +1165,112 @@ void VisualScriptEditor::_member_edited() {
}
}
+void VisualScriptEditor::_create_function_dialog() {
+ function_create_dialog->popup_centered();
+ func_name_box->set_text("");
+ func_name_box->grab_focus();
+ for (int i = 0; i < func_input_vbox->get_child_count(); i++) {
+ Node *nd = func_input_vbox->get_child(i);
+ nd->queue_delete();
+ }
+}
+
+void VisualScriptEditor::_create_function() {
+ String name = _validate_name((func_name_box->get_text() == "") ? "new_func" : func_name_box->get_text());
+ selected = name;
+ Vector2 ofs = _get_available_pos();
+
+ Ref<VisualScriptFunction> func_node;
+ func_node.instance();
+ func_node->set_name(name);
+
+ for (int i = 0; i < func_input_vbox->get_child_count(); i++) {
+ OptionButton *opbtn = Object::cast_to<OptionButton>(func_input_vbox->get_child(i)->get_child(3));
+ LineEdit *lne = Object::cast_to<LineEdit>(func_input_vbox->get_child(i)->get_child(1));
+ if (!opbtn || !lne)
+ continue;
+ Variant::Type arg_type = Variant::Type(opbtn->get_selected());
+ String arg_name = lne->get_text();
+ func_node->add_argument(arg_type, arg_name);
+ }
+
+ undo_redo->create_action(TTR("Add Function"));
+ undo_redo->add_do_method(script.ptr(), "add_function", name);
+ undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_function", name);
+ undo_redo->add_do_method(this, "_update_members");
+ undo_redo->add_undo_method(this, "_update_members");
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->add_do_method(this, "emit_signal", "edited_script_changed");
+ undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed");
+ undo_redo->commit_action();
+
+ _update_graph();
+}
+
+void VisualScriptEditor::_add_node_dialog() {
+ _generic_search(script->get_instance_base_type(), graph->get_global_position() + Vector2(55, 80), true);
+}
+
+void VisualScriptEditor::_add_func_input() {
+ HBoxContainer *hbox = memnew(HBoxContainer);
+ hbox->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ Label *name_label = memnew(Label);
+ name_label->set_text(TTR("Name:"));
+ hbox->add_child(name_label);
+
+ LineEdit *name_box = memnew(LineEdit);
+ name_box->set_h_size_flags(SIZE_EXPAND_FILL);
+ name_box->set_text("input");
+ name_box->connect("focus_entered", this, "_deselect_input_names");
+ hbox->add_child(name_box);
+
+ Label *type_label = memnew(Label);
+ type_label->set_text(TTR("Type:"));
+ hbox->add_child(type_label);
+
+ OptionButton *type_box = memnew(OptionButton);
+ type_box->set_custom_minimum_size(Size2(120 * EDSCALE, 0));
+ for (int i = Variant::NIL; i < Variant::VARIANT_MAX; i++)
+ type_box->add_item(Variant::get_type_name(Variant::Type(i)));
+ type_box->select(1);
+ hbox->add_child(type_box);
+
+ Button *delete_button = memnew(Button);
+ delete_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Remove", "EditorIcons"));
+ delete_button->set_tooltip(vformat(TTR("Delete input port")));
+ hbox->add_child(delete_button);
+
+ for (int i = 0; i < func_input_vbox->get_child_count(); i++) {
+ LineEdit *line_edit = (LineEdit *)func_input_vbox->get_child(i)->get_child(1);
+ line_edit->deselect();
+ }
+
+ func_input_vbox->add_child(hbox);
+ hbox->set_meta("id", hbox->get_position_in_parent());
+
+ delete_button->connect("pressed", this, "_remove_func_input", varray(hbox));
+
+ name_box->select_all();
+ name_box->grab_focus();
+}
+
+void VisualScriptEditor::_remove_func_input(Node *p_node) {
+ func_input_vbox->remove_child(p_node);
+ p_node->queue_delete();
+}
+
+void VisualScriptEditor::_deselect_input_names() {
+ int cn = func_input_vbox->get_child_count();
+ for (int i = 0; i < cn; i++) {
+ LineEdit *lne = Object::cast_to<LineEdit>(func_input_vbox->get_child(i)->get_child(1));
+ if (lne)
+ lne->deselect();
+ }
+}
+
void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_button) {
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
@@ -1010,7 +1283,6 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt
//add function, this one uses menu
if (p_button == 1) {
-
new_virtual_method_select->select_method_from_base_type(script->get_instance_base_type(), String(), true);
return;
@@ -1018,7 +1290,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt
String name = _validate_name("new_function");
selected = name;
- edited_func = selected;
+ Vector2 ofs = _get_available_pos();
Ref<VisualScriptFunction> func_node;
func_node.instance();
@@ -1026,7 +1298,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt
undo_redo->create_action(TTR("Add Function"));
undo_redo->add_do_method(script.ptr(), "add_function", name);
- undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node);
+ undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node, ofs);
undo_redo->add_undo_method(script.ptr(), "remove_function", name);
undo_redo->add_do_method(this, "_update_members");
undo_redo->add_undo_method(this, "_update_members");
@@ -1073,135 +1345,193 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt
undo_redo->commit_action();
return; //or crash because it will become invalid
}
+ } else if (ti->get_parent() == root->get_children()) {
+ selected = ti->get_text(0);
+ function_name_edit->set_position(Input::get_singleton()->get_mouse_position() - Vector2(60, -10));
+ function_name_edit->popup();
+ function_name_box->set_text(selected);
+ function_name_box->select_all();
}
}
-void VisualScriptEditor::_expression_text_changed(const String &p_text, int p_id) {
+void VisualScriptEditor::_add_input_port(int p_id) {
- Ref<VisualScriptExpression> vse = script->get_node(edited_func, p_id);
- if (!vse.is_valid())
+ StringName func = _get_function_of_node(p_id);
+
+ Ref<VisualScriptLists> vsn = script->get_node(func, p_id);
+ if (!vsn.is_valid())
return;
updating_graph = true;
- undo_redo->create_action(TTR("Change Expression"), UndoRedo::MERGE_ENDS);
- undo_redo->add_do_property(vse.ptr(), "expression", p_text);
- undo_redo->add_undo_property(vse.ptr(), "expression", vse->get("expression"));
+ undo_redo->create_action(TTR("Add Input Port"), UndoRedo::MERGE_ENDS);
+ undo_redo->add_do_method(vsn.ptr(), "add_input_data_port", Variant::NIL, "arg", -1);
undo_redo->add_do_method(this, "_update_graph", p_id);
+
+ undo_redo->add_undo_method(vsn.ptr(), "remove_input_data_port", vsn->get_input_value_port_count());
undo_redo->add_undo_method(this, "_update_graph", p_id);
+
+ updating_graph = false;
+
undo_redo->commit_action();
+}
- Node *node = graph->get_node(itos(p_id));
- if (Object::cast_to<Control>(node))
- Object::cast_to<Control>(node)->set_size(Vector2(1, 1)); //shrink if text is smaller
+void VisualScriptEditor::_add_output_port(int p_id) {
+
+ StringName func = _get_function_of_node(p_id);
+
+ Ref<VisualScriptLists> vsn = script->get_node(func, p_id);
+ if (!vsn.is_valid())
+ return;
+
+ updating_graph = true;
+
+ undo_redo->create_action(TTR("Add Output Port"), UndoRedo::MERGE_ENDS);
+ undo_redo->add_do_method(vsn.ptr(), "add_output_data_port", Variant::NIL, "arg", -1);
+ undo_redo->add_do_method(this, "_update_graph", p_id);
+
+ undo_redo->add_undo_method(vsn.ptr(), "remove_output_data_port", vsn->get_output_value_port_count());
+ undo_redo->add_undo_method(this, "_update_graph", p_id);
updating_graph = false;
+
+ undo_redo->commit_action();
}
-void VisualScriptEditor::_available_node_doubleclicked() {
+void VisualScriptEditor::_remove_input_port(int p_id, int p_port) {
+
+ StringName func = _get_function_of_node(p_id);
- if (edited_func == String())
+ Ref<VisualScriptLists> vsn = script->get_node(func, p_id);
+ if (!vsn.is_valid())
return;
- TreeItem *item = nodes->get_selected();
+ updating_graph = true;
- if (!item)
- return;
+ undo_redo->create_action(TTR("Remove Input Port"), UndoRedo::MERGE_ENDS);
+
+ int conn_from = -1, conn_port = -1;
+ script->get_input_value_port_connection_source(func, p_id, p_port, &conn_from, &conn_port);
+
+ if (conn_from != -1)
+ undo_redo->add_do_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, p_id, p_port);
+
+ undo_redo->add_do_method(vsn.ptr(), "remove_input_data_port", p_port);
+ undo_redo->add_do_method(this, "_update_graph", p_id);
+
+ if (conn_from != -1)
+ undo_redo->add_undo_method(script.ptr(), "data_connect", func, conn_from, conn_port, p_id, p_port);
+
+ undo_redo->add_undo_method(vsn.ptr(), "add_input_data_port", vsn->get_input_value_port_info(p_port).type, vsn->get_input_value_port_info(p_port).name, p_port);
+ undo_redo->add_undo_method(this, "_update_graph", p_id);
+
+ updating_graph = false;
+
+ undo_redo->commit_action();
+}
+
+void VisualScriptEditor::_remove_output_port(int p_id, int p_port) {
+
+ StringName func = _get_function_of_node(p_id);
- String which = item->get_metadata(0);
- if (which == String())
+ Ref<VisualScriptLists> vsn = script->get_node(func, p_id);
+ if (!vsn.is_valid())
return;
- Vector2 ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
+ updating_graph = true;
- ofs /= EDSCALE;
+ undo_redo->create_action(TTR("Remove Output Port"), UndoRedo::MERGE_ENDS);
- while (true) {
- bool exists = false;
- List<int> existing;
- script->get_node_list(edited_func, &existing);
- for (List<int>::Element *E = existing.front(); E; E = E->next()) {
- Point2 pos = script->get_node_position(edited_func, E->get());
- if (pos.distance_to(ofs) < 15) {
- ofs += Vector2(graph->get_snap(), graph->get_snap());
- exists = true;
- break;
- }
+ List<VisualScript::DataConnection> data_connections;
+ script->get_data_connection_list(func, &data_connections);
+
+ HashMap<int, Set<int> > conn_map;
+ for (const List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
+ if (E->get().from_node == p_id && E->get().from_port == p_port) {
+ // push into the connections map
+ if (!conn_map.has(E->get().to_node))
+ conn_map.set(E->get().to_node, Set<int>());
+ conn_map[E->get().to_node].insert(E->get().to_port);
}
+ }
- if (exists)
- continue;
- break;
+ undo_redo->add_do_method(vsn.ptr(), "remove_output_data_port", p_port);
+ undo_redo->add_do_method(this, "_update_graph", p_id);
+
+ List<int> keys;
+ conn_map.get_key_list(&keys);
+ for (const List<int>::Element *E = keys.front(); E; E = E->next()) {
+ for (const Set<int>::Element *F = conn_map[E->get()].front(); F; F = F->next()) {
+ undo_redo->add_undo_method(script.ptr(), "data_connect", func, p_id, p_port, E->get(), F->get());
+ }
}
- Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(which);
- int new_id = script->get_available_id();
+ undo_redo->add_undo_method(vsn.ptr(), "add_output_data_port", vsn->get_output_value_port_info(p_port).type, vsn->get_output_value_port_info(p_port).name, p_port);
+ undo_redo->add_undo_method(this, "_update_graph", p_id);
- undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
- undo_redo->add_do_method(this, "_update_graph");
- undo_redo->add_undo_method(this, "_update_graph");
- undo_redo->commit_action();
+ updating_graph = false;
- Node *node = graph->get_node(itos(new_id));
- if (node) {
- graph->set_selected(node);
- _node_selected(node);
- }
+ undo_redo->commit_action();
}
-void VisualScriptEditor::_update_available_nodes() {
+void VisualScriptEditor::_expression_text_changed(const String &p_text, int p_id) {
+
+ StringName func = _get_function_of_node(p_id);
- nodes->clear();
+ Ref<VisualScriptExpression> vse = script->get_node(func, p_id);
+ if (!vse.is_valid())
+ return;
- TreeItem *root = nodes->create_item();
+ updating_graph = true;
- Map<String, TreeItem *> path_cache;
+ undo_redo->create_action(TTR("Change Expression"), UndoRedo::MERGE_ENDS);
+ undo_redo->add_do_property(vse.ptr(), "expression", p_text);
+ undo_redo->add_undo_property(vse.ptr(), "expression", vse->get("expression"));
+ undo_redo->add_do_method(this, "_update_graph", p_id);
+ undo_redo->add_undo_method(this, "_update_graph", p_id);
+ undo_redo->commit_action();
- String filter = node_filter->get_text();
+ Node *node = graph->get_node(itos(p_id));
+ if (Object::cast_to<Control>(node))
+ Object::cast_to<Control>(node)->set_size(Vector2(1, 1)); //shrink if text is smaller
- List<String> fnodes;
- VisualScriptLanguage::singleton->get_registered_node_names(&fnodes);
+ updating_graph = false;
+}
- for (List<String>::Element *E = fnodes.front(); E; E = E->next()) {
+Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const {
+ if (centered)
+ ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5;
- Vector<String> path = E->get().split("/");
+ if (graph->is_using_snap()) {
+ int snap = graph->get_snap();
+ ofs = ofs.snapped(Vector2(snap, snap));
+ }
- if (filter != String() && path.size() && path[path.size() - 1].findn(filter) == -1)
- continue;
+ ofs /= EDSCALE;
- String sp;
- TreeItem *parent = root;
-
- for (int i = 0; i < path.size() - 1; i++) {
-
- if (i > 0)
- sp += ",";
- sp += path[i];
- if (!path_cache.has(sp)) {
- TreeItem *pathn = nodes->create_item(parent);
- pathn->set_selectable(0, false);
- pathn->set_text(0, path[i].capitalize());
- path_cache[sp] = pathn;
- parent = pathn;
- if (filter == String()) {
- pathn->set_collapsed(true); //should remember state
+ while (true) {
+ bool exists = false;
+ List<StringName> all_fn;
+ script->get_function_list(&all_fn);
+ for (List<StringName>::Element *F = all_fn.front(); F; F = F->next()) {
+ StringName curr_fn = F->get();
+ List<int> existing;
+ script->get_node_list(curr_fn, &existing);
+ for (List<int>::Element *E = existing.front(); E; E = E->next()) {
+ Point2 pos = script->get_node_position(curr_fn, E->get());
+ if (pos.distance_to(ofs) < 50) {
+ ofs += Vector2(graph->get_snap(), graph->get_snap());
+ exists = true;
+ break;
}
- } else {
- parent = path_cache[sp];
}
}
-
- TreeItem *item = nodes->create_item(parent);
- item->set_text(0, path[path.size() - 1].capitalize());
- item->set_selectable(0, true);
- item->set_metadata(0, E->get());
+ if (exists)
+ continue;
+ break;
}
+
+ return ofs;
}
String VisualScriptEditor::_validate_name(const String &p_name) const {
@@ -1227,6 +1557,8 @@ String VisualScriptEditor::_validate_name(const String &p_name) const {
void VisualScriptEditor::_on_nodes_delete() {
+ // delete all the selected nodes
+
List<int> to_erase;
for (int i = 0; i < graph->get_child_count(); i++) {
@@ -1245,26 +1577,30 @@ void VisualScriptEditor::_on_nodes_delete() {
for (List<int>::Element *F = to_erase.front(); F; F = F->next()) {
- undo_redo->add_do_method(script.ptr(), "remove_node", edited_func, F->get());
- undo_redo->add_undo_method(script.ptr(), "add_node", edited_func, F->get(), script->get_node(edited_func, F->get()), script->get_node_position(edited_func, F->get()));
+ int cr_node = F->get();
+
+ StringName func = _get_function_of_node(cr_node);
+
+ undo_redo->add_do_method(script.ptr(), "remove_node", func, cr_node);
+ undo_redo->add_undo_method(script.ptr(), "add_node", func, cr_node, script->get_node(func, cr_node), script->get_node_position(func, cr_node));
List<VisualScript::SequenceConnection> sequence_conns;
- script->get_sequence_connection_list(edited_func, &sequence_conns);
+ script->get_sequence_connection_list(func, &sequence_conns);
for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
- if (E->get().from_node == F->get() || E->get().to_node == F->get()) {
- undo_redo->add_undo_method(script.ptr(), "sequence_connect", edited_func, E->get().from_node, E->get().from_output, E->get().to_node);
+ if (E->get().from_node == cr_node || E->get().to_node == cr_node) {
+ undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, E->get().from_node, E->get().from_output, E->get().to_node);
}
}
List<VisualScript::DataConnection> data_conns;
- script->get_data_connection_list(edited_func, &data_conns);
+ script->get_data_connection_list(func, &data_conns);
for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) {
if (E->get().from_node == F->get() || E->get().to_node == F->get()) {
- undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ undo_redo->add_undo_method(script.ptr(), "data_connect", func, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
}
}
}
@@ -1276,13 +1612,16 @@ void VisualScriptEditor::_on_nodes_delete() {
void VisualScriptEditor::_on_nodes_duplicate() {
- List<int> to_duplicate;
+ Set<int> to_duplicate;
+ List<StringName> funcs;
for (int i = 0; i < graph->get_child_count(); i++) {
GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
if (gn) {
if (gn->is_selected() && gn->is_close_button_visible()) {
- to_duplicate.push_back(gn->get_name().operator String().to_int());
+ int id = gn->get_name().operator String().to_int();
+ to_duplicate.insert(id);
+ funcs.push_back(_get_function_of_node(id));
}
}
}
@@ -1294,18 +1633,42 @@ void VisualScriptEditor::_on_nodes_duplicate() {
int idc = script->get_available_id() + 1;
Set<int> to_select;
+ HashMap<int, int> remap;
- for (List<int>::Element *F = to_duplicate.front(); F; F = F->next()) {
+ for (Set<int>::Element *F = to_duplicate.front(); F; F = F->next()) {
- Ref<VisualScriptNode> node = script->get_node(edited_func, F->get());
+ // duplicate from the specifc function but place it into the default func as it would lack the connections
+ StringName func = _get_function_of_node(F->get());
+ Ref<VisualScriptNode> node = script->get_node(func, F->get());
Ref<VisualScriptNode> dupe = node->duplicate(true);
int new_id = idc++;
+ remap.set(F->get(), new_id);
+
to_select.insert(new_id);
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, dupe, script->get_node_position(edited_func, F->get()) + Vector2(20, 20));
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, dupe, script->get_node_position(func, F->get()) + Vector2(20, 20));
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
}
+
+ for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) {
+ List<VisualScript::SequenceConnection> seqs;
+ script->get_sequence_connection_list(F->get(), &seqs);
+ for (List<VisualScript::SequenceConnection>::Element *E = seqs.front(); E; E = E->next()) {
+ if (to_duplicate.has(E->get().from_node) && to_duplicate.has(E->get().to_node)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", default_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]);
+ }
+ }
+
+ List<VisualScript::DataConnection> data;
+ script->get_data_connection_list(F->get(), &data);
+ for (List<VisualScript::DataConnection>::Element *E = data.front(); E; E = E->next()) {
+ if (to_duplicate.has(E->get().from_node) && to_duplicate.has(E->get().to_node)) {
+ undo_redo->add_do_method(script.ptr(), "data_connect", default_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port);
+ }
+ }
+ }
+
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
@@ -1320,22 +1683,45 @@ void VisualScriptEditor::_on_nodes_duplicate() {
}
if (to_select.size()) {
- EditorNode::get_singleton()->push_item(script->get_node(edited_func, to_select.front()->get()).ptr());
+ EditorNode::get_singleton()->push_item(script->get_node(default_func, to_select.front()->get()).ptr());
}
}
-void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) {
+void VisualScriptEditor::_generic_search(String p_base_type, Vector2 pos, bool node_centered) {
+ if (node_centered)
+ port_action_pos = graph->get_size() / 2.0f;
+ else
+ port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position();
+
+ new_connect_node_select->select_from_visual_script(p_base_type, false, false); // neither connecting nor reset text
+
+ // ensure that the dialog fits inside the graph
+ Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size();
+ pos.x = pos.x > bounds.x ? bounds.x : pos.x;
+ pos.y = pos.y > bounds.y ? bounds.y : pos.y;
- Ref<InputEventMouseButton> mb = p_event;
+ if (pos != Vector2())
+ new_connect_node_select->set_position(pos);
+}
+
+void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) {
+ // GUI input for VS Editor Plugin
+ Ref<InputEventMouseButton> key = p_event;
- if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
- revert_on_drag = String(); //so we can still drag functions
+ if (key.is_valid() && !key->is_pressed()) {
+ mouse_up_position = Input::get_singleton()->get_mouse_position();
}
}
-void VisualScriptEditor::_generic_search(String p_base_type) {
- port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position();
- new_connect_node_select->select_from_visual_script(p_base_type, false);
+void VisualScriptEditor::_graph_gui_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseButton> key = p_event;
+
+ if (key.is_valid() && key->is_pressed() && key->get_button_mask() == BUTTON_RIGHT) {
+ saved_position = graph->get_local_mouse_position();
+
+ Point2 gpos = Input::get_singleton()->get_mouse_position();
+ _generic_search(script->get_instance_base_type(), gpos);
+ }
}
void VisualScriptEditor::_members_gui_input(const Ref<InputEvent> &p_event) {
@@ -1365,28 +1751,82 @@ void VisualScriptEditor::_members_gui_input(const Ref<InputEvent> &p_event) {
}
}
}
+
+ Ref<InputEventMouseButton> btn = p_event;
+ if (btn.is_valid() && btn->is_doubleclick()) {
+ TreeItem *ti = members->get_selected();
+ if (ti && ti->get_parent() == members->get_root()->get_children()) // to check if it's a function
+ _center_on_node(ti->get_metadata(0), script->get_function_node_id(ti->get_metadata(0)));
+ }
}
-Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
+void VisualScriptEditor::_rename_function(const String &name, const String &new_name) {
- if (p_from == nodes) {
+ if (!new_name.is_valid_identifier()) {
- TreeItem *it = nodes->get_item_at_position(p_point);
- if (!it)
- return Variant();
- String type = it->get_metadata(0);
- if (type == String())
- return Variant();
+ EditorNode::get_singleton()->show_warning(TTR("Name is not a valid identifier:") + " " + new_name);
+ return;
+ }
- Dictionary dd;
- dd["type"] = "visual_script_node_drag";
- dd["node_type"] = type;
+ if (script->has_function(new_name) || script->has_variable(new_name) || script->has_custom_signal(new_name)) {
- Label *label = memnew(Label);
- label->set_text(it->get_text(0));
- set_drag_preview(label);
- return dd;
+ EditorNode::get_singleton()->show_warning(TTR("Name already in use by another func/var/signal:") + " " + new_name);
+ return;
+ }
+
+ int node_id = script->get_function_node_id(name);
+ Ref<VisualScriptFunction> func;
+ if (script->has_node(name, node_id)) {
+ func = script->get_node(name, node_id);
+ }
+ undo_redo->create_action(TTR("Rename Function"));
+ undo_redo->add_do_method(script.ptr(), "rename_function", name, new_name);
+ undo_redo->add_undo_method(script.ptr(), "rename_function", new_name, name);
+ if (func.is_valid()) {
+ undo_redo->add_do_method(func.ptr(), "set_name", new_name);
+ undo_redo->add_undo_method(func.ptr(), "set_name", name);
+ }
+
+ // also fix all function calls
+ List<StringName> flst;
+ script->get_function_list(&flst);
+ for (List<StringName>::Element *E = flst.front(); E; E = E->next()) {
+ List<int> lst;
+ script->get_node_list(E->get(), &lst);
+ for (List<int>::Element *F = lst.front(); F; F = F->next()) {
+ Ref<VisualScriptFunctionCall> fncall = script->get_node(E->get(), F->get());
+ if (!fncall.is_valid())
+ continue;
+ if (fncall->get_function() == name) {
+ undo_redo->add_do_method(fncall.ptr(), "set_function", new_name);
+ undo_redo->add_undo_method(fncall.ptr(), "set_function", name);
+ }
+ }
+ }
+
+ undo_redo->add_do_method(this, "_update_members");
+ undo_redo->add_undo_method(this, "_update_members");
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->add_do_method(this, "emit_signal", "edited_script_changed");
+ undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed");
+ undo_redo->commit_action();
+}
+
+void VisualScriptEditor::_fn_name_box_input(const Ref<InputEvent> &p_event) {
+
+ if (!function_name_edit->is_visible())
+ return;
+
+ Ref<InputEventKey> key = p_event;
+ if (key.is_valid() && key->is_pressed() && key->get_scancode() == KEY_ENTER) {
+ function_name_edit->hide();
+ _rename_function(selected, function_name_box->get_text());
+ function_name_box->clear();
}
+}
+
+Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
if (p_from == members) {
@@ -1406,11 +1846,6 @@ Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_f
dd["type"] = "visual_script_function_drag";
dd["function"] = type;
- if (revert_on_drag != String()) {
- edited_func = revert_on_drag; //revert so function does not change
- revert_on_drag = String();
- _update_graph();
- }
} else if (it->get_parent() == root->get_children()->get_next()) {
dd["type"] = "visual_script_variable_drag";
@@ -1481,8 +1916,6 @@ bool VisualScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &
return false;
}
-#ifdef TOOLS_ENABLED
-
static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const Ref<Script> &script) {
if (p_edited_scene != p_current_node && p_current_node->get_owner() != p_edited_scene)
@@ -1502,8 +1935,6 @@ static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const
return NULL;
}
-#endif
-
void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
if (p_from != graph) {
@@ -1530,15 +1961,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
ofs /= EDSCALE;
- Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(d["node_type"]);
- int new_id = script->get_available_id();
-
- undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
- undo_redo->add_do_method(this, "_update_graph");
- undo_redo->add_undo_method(this, "_update_graph");
- undo_redo->commit_action();
+ int new_id = _create_new_node_from_name(d["node_type"], ofs, default_func);
Node *node = graph->get_node(itos(new_id));
if (node) {
@@ -1579,8 +2002,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, vnode, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
undo_redo->commit_action();
@@ -1609,11 +2032,11 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, vnode, ofs);
undo_redo->add_do_method(vnode.ptr(), "set_base_type", script->get_instance_base_type());
undo_redo->add_do_method(vnode.ptr(), "set_function", d["function"]);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
undo_redo->commit_action();
@@ -1642,8 +2065,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, vnode, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
undo_redo->commit_action();
@@ -1672,8 +2095,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Preload Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, prnode, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, prnode, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
undo_redo->commit_action();
@@ -1713,8 +2136,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
prnode.instance();
prnode->set_preload(res);
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, prnode, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, prnode, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
new_ids.push_back(new_id);
new_id++;
ofs += Vector2(20, 20) * EDSCALE;
@@ -1740,7 +2163,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script);
if (!sn) {
- EditorNode::get_singleton()->show_warning("Can't drop nodes because script '" + get_name() + "' is not used in this scene.");
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop nodes because script '%s' is not used in this scene."), get_name()));
return;
}
@@ -1782,20 +2205,20 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
scene_node.instance();
scene_node->set_node_path(sn->get_path_to(node));
n = scene_node;
-
} else {
+ // ! Doesn't work properly
Ref<VisualScriptFunctionCall> call;
call.instance();
call->set_call_mode(VisualScriptFunctionCall::CALL_MODE_NODE_PATH);
call->set_base_path(sn->get_path_to(node));
call->set_base_type(node->get_class());
n = call;
- method_select->select_from_instance(node);
+ method_select->select_from_instance(node, "", true, node->get_class());
selecting_method_id = base_id;
}
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, base_id, n, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, base_id);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, base_id, n, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, base_id);
base_id++;
ofs += Vector2(25, 25);
@@ -1810,7 +2233,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script);
if (!sn && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
- EditorNode::get_singleton()->show_warning("Can't drop properties because script '" + get_name() + "' is not used in this scene.\nDrop holding 'Shift' to just copy the signature.");
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop properties because script '%s' is not used in this scene.\nDrop holding 'Shift' to just copy the signature."), get_name()));
return;
}
@@ -1866,13 +2289,13 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
vnode = pget;
}
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, base_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, base_id, vnode, ofs);
undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]);
if (!use_get) {
undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]);
}
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, base_id);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, base_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
@@ -1913,12 +2336,12 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
}
vnode = pget;
}
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, base_id, vnode, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, base_id, vnode, ofs);
undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]);
if (!use_get) {
undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]);
}
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, base_id);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, base_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
@@ -1929,7 +2352,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
void VisualScriptEditor::_selected_method(const String &p_method, const String &p_type, const bool p_connecting) {
- Ref<VisualScriptFunctionCall> vsfc = script->get_node(edited_func, selecting_method_id);
+ Ref<VisualScriptFunctionCall> vsfc = script->get_node(default_func, selecting_method_id);
if (!vsfc.is_valid())
return;
vsfc->set_function(p_method);
@@ -1986,8 +2409,16 @@ void VisualScriptEditor::set_edited_resource(const RES &p_res) {
script->connect("node_ports_changed", this, "_node_ports_changed");
+ default_func = script->get_default_func();
+
+ if (!script->has_function(default_func)) // this is the supposed default function
+ {
+ script->add_function(default_func);
+ script->set_edited(true); //so that if a function was added it's saved
+ }
+
+ _update_graph();
_update_members();
- _update_available_nodes();
}
Vector<String> VisualScriptEditor::get_functions() {
@@ -2021,18 +2452,14 @@ Ref<Texture> VisualScriptEditor::get_icon() {
}
bool VisualScriptEditor::is_unsaved() {
-#ifdef TOOLS_ENABLED
return script->is_edited() || script->are_subnodes_edited();
-#else
- return false;
-#endif
}
Variant VisualScriptEditor::get_edit_state() {
Dictionary d;
- d["function"] = edited_func;
+ d["function"] = default_func;
d["scroll"] = graph->get_scroll_ofs();
d["zoom"] = graph->get_zoom();
d["using_snap"] = graph->is_using_snap();
@@ -2044,8 +2471,7 @@ void VisualScriptEditor::set_edit_state(const Variant &p_state) {
Dictionary d = p_state;
if (d.has("function")) {
- edited_func = d["function"];
- selected = edited_func;
+ selected = default_func;
}
_update_graph();
@@ -2065,16 +2491,24 @@ void VisualScriptEditor::set_edit_state(const Variant &p_state) {
}
}
-void VisualScriptEditor::_center_on_node(int p_id) {
+void VisualScriptEditor::_center_on_node(const StringName &p_func, int p_id) {
Node *n = graph->get_node(itos(p_id));
GraphNode *gn = Object::cast_to<GraphNode>(n);
+
+ // clear selection
+ for (int i = 0; i < graph->get_child_count(); i++) {
+ GraphNode *gnd = Object::cast_to<GraphNode>(graph->get_child(i));
+ if (gnd)
+ gnd->set_selected(false);
+ }
+
if (gn) {
gn->set_selected(true);
Vector2 new_scroll = gn->get_offset() - graph->get_size() * 0.5 + gn->get_size() * 0.5;
graph->set_scroll_ofs(new_scroll);
- script->set_function_scroll(edited_func, new_scroll / EDSCALE);
- script->set_edited(true); //so it's saved
+ script->set_function_scroll(p_func, new_scroll / EDSCALE);
+ script->set_edited(true);
}
}
@@ -2091,13 +2525,10 @@ void VisualScriptEditor::goto_line(int p_line, bool p_with_error) {
if (script->has_node(E->get(), p_line)) {
- edited_func = E->get();
- selected = edited_func;
_update_graph();
_update_members();
- call_deferred("call_deferred", "_center_on_node", p_line); //editor might be just created and size might not exist yet
-
+ call_deferred("call_deferred", "_center_on_node", E->get(), p_line); //editor might be just created and size might not exist yet
return;
}
}
@@ -2132,6 +2563,7 @@ void VisualScriptEditor::tag_saved_version() {
}
void VisualScriptEditor::reload(bool p_soft) {
+ _update_graph();
}
void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) {
@@ -2155,10 +2587,9 @@ void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) {
void VisualScriptEditor::add_callback(const String &p_function, PoolStringArray p_args) {
if (script->has_function(p_function)) {
- edited_func = p_function;
- selected = edited_func;
_update_members();
_update_graph();
+ _center_on_node(p_function, script->get_function_node_id(p_function));
return;
}
@@ -2189,13 +2620,10 @@ void VisualScriptEditor::add_callback(const String &p_function, PoolStringArray
script->add_function(p_function);
script->add_node(p_function, script->get_available_id(), func);
- edited_func = p_function;
- selected = edited_func;
_update_members();
_update_graph();
- graph->call_deferred("set_scroll_ofs", script->get_function_scroll(edited_func)); //for first time it might need to be later
- //undo_redo->clear_history();
+ _center_on_node(p_function, script->get_function_node_id(p_function));
}
bool VisualScriptEditor::show_members_overview() {
@@ -2227,9 +2655,13 @@ void VisualScriptEditor::_change_base_type() {
select_base_type->popup_create(true, true);
}
+void VisualScriptEditor::_toggle_tool_script() {
+ script->set_tool_enabled(!script->is_tool());
+}
+
void VisualScriptEditor::clear_edit_menu() {
memdelete(edit_menu);
- memdelete(left_vsplit);
+ memdelete(members_section);
}
void VisualScriptEditor::_change_base_type_callback() {
@@ -2293,46 +2725,66 @@ void VisualScriptEditor::_end_node_move() {
undo_redo->commit_action();
}
-void VisualScriptEditor::_move_node(String func, int p_id, const Vector2 &p_to) {
+void VisualScriptEditor::_move_node(const StringName &p_func, int p_id, const Vector2 &p_to) {
+
+ if (!script->has_function(p_func))
+ return;
+
+ Node *node = graph->get_node(itos(p_id));
- if (func == String(edited_func)) {
- Node *node = graph->get_node(itos(p_id));
- if (Object::cast_to<GraphNode>(node))
- Object::cast_to<GraphNode>(node)->set_offset(p_to);
+ if (Object::cast_to<GraphNode>(node))
+ Object::cast_to<GraphNode>(node)->set_offset(p_to);
+
+ script->set_node_position(p_func, p_id, p_to / EDSCALE);
+}
+
+StringName VisualScriptEditor::_get_function_of_node(int p_id) const {
+
+ List<StringName> funcs;
+ script->get_function_list(&funcs);
+ for (List<StringName>::Element *E = funcs.front(); E; E = E->next()) {
+ if (script->has_node(E->get(), p_id)) {
+ return E->get();
+ }
}
- script->set_node_position(edited_func, p_id, p_to / EDSCALE);
+
+ return ""; // this is passed to avoid crash and is tested against later
}
void VisualScriptEditor::_node_moved(Vector2 p_from, Vector2 p_to, int p_id) {
- undo_redo->add_do_method(this, "_move_node", String(edited_func), p_id, p_to);
- undo_redo->add_undo_method(this, "_move_node", String(edited_func), p_id, p_from);
+ StringName func = _get_function_of_node(p_id);
+
+ undo_redo->add_do_method(this, "_move_node", func, p_id, p_to);
+ undo_redo->add_undo_method(this, "_move_node", func, p_id, p_from);
}
void VisualScriptEditor::_remove_node(int p_id) {
undo_redo->create_action(TTR("Remove VisualScript Node"));
- undo_redo->add_do_method(script.ptr(), "remove_node", edited_func, p_id);
- undo_redo->add_undo_method(script.ptr(), "add_node", edited_func, p_id, script->get_node(edited_func, p_id), script->get_node_position(edited_func, p_id));
+ StringName func = _get_function_of_node(p_id);
+
+ undo_redo->add_do_method(script.ptr(), "remove_node", func, p_id);
+ undo_redo->add_undo_method(script.ptr(), "add_node", func, p_id, script->get_node(func, p_id), script->get_node_position(func, p_id));
List<VisualScript::SequenceConnection> sequence_conns;
- script->get_sequence_connection_list(edited_func, &sequence_conns);
+ script->get_sequence_connection_list(func, &sequence_conns);
for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
if (E->get().from_node == p_id || E->get().to_node == p_id) {
- undo_redo->add_undo_method(script.ptr(), "sequence_connect", edited_func, E->get().from_node, E->get().from_output, E->get().to_node);
+ undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, E->get().from_node, E->get().from_output, E->get().to_node);
}
}
List<VisualScript::DataConnection> data_conns;
- script->get_data_connection_list(edited_func, &data_conns);
+ script->get_data_connection_list(func, &data_conns);
for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) {
if (E->get().from_node == p_id || E->get().to_node == p_id) {
- undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ undo_redo->add_undo_method(script.ptr(), "data_connect", func, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
}
}
@@ -2344,15 +2796,29 @@ void VisualScriptEditor::_remove_node(int p_id) {
void VisualScriptEditor::_node_ports_changed(const String &p_func, int p_id) {
- if (p_func != String(edited_func))
- return;
-
_update_graph(p_id);
}
+bool VisualScriptEditor::node_has_sequence_connections(const StringName &p_func, int p_id) {
+ List<VisualScript::SequenceConnection> sequence_conns;
+ script->get_sequence_connection_list(p_func, &sequence_conns);
+
+ for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
+ int from = E->get().from_node;
+ int to = E->get().to_node;
+
+ if (to == p_id || from == p_id)
+ return true;
+ }
+
+ return false;
+}
+
void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot) {
- Ref<VisualScriptNode> from_node = script->get_node(edited_func, p_from.to_int());
+ StringName from_func = _get_function_of_node(p_from.to_int());
+
+ Ref<VisualScriptNode> from_node = script->get_node(from_func, p_from.to_int());
ERR_FAIL_COND(!from_node.is_valid());
bool from_seq;
@@ -2361,7 +2827,9 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot,
if (!_get_out_slot(from_node, p_from_slot, from_port, from_seq))
return; //can't connect this, it's invalid
- Ref<VisualScriptNode> to_node = script->get_node(edited_func, p_to.to_int());
+ StringName to_func = _get_function_of_node(p_to.to_int());
+
+ Ref<VisualScriptNode> to_node = script->get_node(to_func, p_to.to_int());
ERR_FAIL_COND(!to_node.is_valid());
bool to_seq;
@@ -2372,29 +2840,170 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot,
ERR_FAIL_COND(from_seq != to_seq);
+ // Do all the checks here
+ StringName func; // this the func where we store the one the nodes at the end of the resolution on having multiple nodes
+
undo_redo->create_action(TTR("Connect Nodes"));
+ if (from_func == to_func) {
+ func = to_func;
+ } else if (from_seq) {
+ // this is a sequence connection
+ _move_nodes_with_rescan(to_func, from_func, p_to.to_int()); // this function moves the nodes from func1 to func2
+ func = from_func;
+ } else {
+ if (node_has_sequence_connections(to_func, p_to.to_int())) {
+ if (node_has_sequence_connections(from_func, p_from.to_int())) {
+ ERR_PRINT("Trying to connect between different sequence node trees");
+ return;
+ } else {
+ _move_nodes_with_rescan(from_func, to_func, p_from.to_int());
+ func = to_func;
+ }
+ } else if (node_has_sequence_connections(from_func, p_from.to_int())) {
+ if (from_func == default_func) {
+ _move_nodes_with_rescan(from_func, to_func, p_from.to_int());
+ func = to_func;
+ } else {
+ _move_nodes_with_rescan(to_func, from_func, p_to.to_int());
+ func = from_func;
+ }
+ } else {
+ if (to_func == default_func) {
+ _move_nodes_with_rescan(to_func, from_func, p_to.to_int());
+ func = from_func;
+ } else {
+ _move_nodes_with_rescan(from_func, to_func, p_from.to_int());
+ func = to_func;
+ }
+ }
+ }
+
if (from_seq) {
- undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, p_from.to_int(), from_port, p_to.to_int());
- undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int());
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", func, p_from.to_int(), from_port, p_to.to_int());
+ // this undo error on undo after move can't be removed without painful gymnastics
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, p_from.to_int(), from_port, p_to.to_int());
} else {
+ bool converted = false;
+ int conv_node = -1;
+
+ Ref<VisualScriptOperator> oper = to_node;
+ if (oper.is_valid() && oper->get_typed() == Variant::NIL) {
+ // it's an operator Node and if the type is already nil
+ if (from_node->get_output_value_port_info(from_port).type != Variant::NIL) {
+ oper->set_typed(from_node->get_output_value_port_info(from_port).type);
+ }
+ }
- // disconnect current, and connect the new one
- if (script->is_input_value_port_connected(edited_func, p_to.to_int(), to_port)) {
- int conn_from;
- int conn_port;
- script->get_input_value_port_connection_source(edited_func, p_to.to_int(), to_port, &conn_from, &conn_port);
- undo_redo->add_do_method(script.ptr(), "data_disconnect", edited_func, conn_from, conn_port, p_to.to_int(), to_port);
- undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, conn_from, conn_port, p_to.to_int(), to_port);
+ Ref<VisualScriptOperator> operf = from_node;
+ if (operf.is_valid() && operf->get_typed() == Variant::NIL) {
+ // it's an operator Node and if the type is already nil
+ if (to_node->get_input_value_port_info(to_port).type != Variant::NIL) {
+ operf->set_typed(to_node->get_input_value_port_info(to_port).type);
+ }
}
- undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port);
- undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port);
- //update nodes in sgraph
- undo_redo->add_do_method(this, "_update_graph", p_from.to_int());
- undo_redo->add_do_method(this, "_update_graph", p_to.to_int());
- undo_redo->add_undo_method(this, "_update_graph", p_from.to_int());
- undo_redo->add_undo_method(this, "_update_graph", p_to.to_int());
+ Variant::Type to_type = to_node->get_input_value_port_info(to_port).type;
+ Variant::Type from_type = from_node->get_output_value_port_info(from_port).type;
+
+ if (to_type != Variant::NIL && from_type != Variant::NIL && to_type != from_type) {
+ // add a constructor node between the ports
+ bool exceptions = false; // true if there are any exceptions
+ exceptions = exceptions || (to_type == Variant::INT && from_type == Variant::REAL);
+ exceptions = exceptions || (to_type == Variant::REAL && from_type == Variant::INT);
+ if (Variant::can_convert(from_type, to_type) && !exceptions) {
+ MethodInfo mi;
+ mi.name = Variant::get_type_name(to_type);
+ PropertyInfo pi;
+ pi.name = "from";
+ pi.type = from_type;
+ mi.arguments.push_back(pi);
+ mi.return_val.type = to_type;
+ // we know that this is allowed so create a new constructor node
+ Ref<VisualScriptConstructor> constructor;
+ constructor.instance();
+ constructor->set_constructor_type(to_type);
+ constructor->set_constructor(mi);
+ // add the new constructor node
+
+ GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(p_from));
+ GraphNode *gn2 = Object::cast_to<GraphNode>(graph->get_node(p_to));
+ if (gn && gn2) {
+ Vector2 from_node_size = gn->get_rect().get_size();
+ Vector2 to_node_size = gn2->get_rect().get_size();
+ Vector2 to_node_pos = script->get_node_position(func, p_to.to_int());
+ Vector2 from_node_pos = script->get_node_position(func, p_from.to_int());
+ Vector2 new_to_node_pos = from_node_pos;
+ Vector2 constructor_pos;
+ if ((to_node_pos.x - from_node_pos.x) < 0) {
+ // to is behind from node
+ if (to_node_pos.x > (from_node_pos.x - to_node_size.x - 240))
+ new_to_node_pos.x = from_node_pos.x - to_node_size.x - 240; // approx size of construtor node + padding
+ else
+ new_to_node_pos.x = to_node_pos.x;
+ new_to_node_pos.y = to_node_pos.y;
+ constructor_pos.x = from_node_pos.x - 210;
+ constructor_pos.y = to_node_pos.y;
+ } else {
+ // to is ahead of from node
+ if (to_node_pos.x < (from_node_size.x + from_node_pos.x + 240))
+ new_to_node_pos.x = from_node_size.x + from_node_pos.x + 240; // approx size of construtor node + padding
+ else
+ new_to_node_pos.x = to_node_pos.x;
+ new_to_node_pos.y = to_node_pos.y;
+ constructor_pos.x = from_node_size.x + from_node_pos.x + 10;
+ constructor_pos.y = to_node_pos.y;
+ }
+ undo_redo->add_do_method(this, "_move_node", func, p_to.to_int(), new_to_node_pos);
+ undo_redo->add_undo_method(this, "_move_node", func, p_to.to_int(), to_node_pos);
+ conv_node = script->get_available_id();
+ undo_redo->add_do_method(script.ptr(), "add_node", func, conv_node, constructor, _get_available_pos(false, constructor_pos));
+ undo_redo->add_undo_method(script.ptr(), "remove_node", func, conv_node);
+ converted = true;
+ }
+ }
+ }
+
+ // disconnect current, and connect the new one
+ if (script->is_input_value_port_connected(func, p_to.to_int(), to_port)) {
+ if (can_swap && data_disconnect_node == p_to.to_int()) {
+ int conn_from;
+ int conn_port;
+ script->get_input_value_port_connection_source(func, p_to.to_int(), to_port, &conn_from, &conn_port);
+ undo_redo->add_do_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, p_to.to_int(), to_port);
+ undo_redo->add_do_method(script.ptr(), "data_connect", func, conn_from, conn_port, data_disconnect_node, data_disconnect_port);
+ undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, data_disconnect_node, data_disconnect_port);
+ undo_redo->add_undo_method(script.ptr(), "data_connect", func, conn_from, conn_port, p_to.to_int(), to_port);
+ can_swap = false; // swapped
+ } else {
+ int conn_from;
+ int conn_port;
+ script->get_input_value_port_connection_source(func, p_to.to_int(), to_port, &conn_from, &conn_port);
+ undo_redo->add_do_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, p_to.to_int(), to_port);
+ undo_redo->add_undo_method(script.ptr(), "data_connect", func, conn_from, conn_port, p_to.to_int(), to_port);
+ }
+ }
+ if (!converted) {
+ undo_redo->add_do_method(script.ptr(), "data_connect", func, p_from.to_int(), from_port, p_to.to_int(), to_port);
+ undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, p_from.to_int(), from_port, p_to.to_int(), to_port);
+ } else {
+ // this is noice
+ undo_redo->add_do_method(script.ptr(), "data_connect", func, p_from.to_int(), from_port, conv_node, 0);
+ undo_redo->add_do_method(script.ptr(), "data_connect", func, conv_node, 0, p_to.to_int(), to_port);
+ // I don't think this is needed but gonna leave it here for now... until I need to finalise it all
+ undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, p_from.to_int(), from_port, conv_node, 0);
+ undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, conv_node, 0, p_to.to_int(), to_port);
+ }
+ //update nodes in graph
+ if (!converted) {
+ undo_redo->add_do_method(this, "_update_graph", p_from.to_int());
+ undo_redo->add_do_method(this, "_update_graph", p_to.to_int());
+ undo_redo->add_undo_method(this, "_update_graph", p_from.to_int());
+ undo_redo->add_undo_method(this, "_update_graph", p_to.to_int());
+ } else {
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ }
}
undo_redo->add_do_method(this, "_update_graph_connections");
@@ -2405,7 +3014,10 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot,
void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot) {
- Ref<VisualScriptNode> from_node = script->get_node(edited_func, p_from.to_int());
+ StringName func = _get_function_of_node(p_from.to_int());
+ ERR_FAIL_COND(func != _get_function_of_node(p_to.to_int()));
+
+ Ref<VisualScriptNode> from_node = script->get_node(func, p_from.to_int());
ERR_FAIL_COND(!from_node.is_valid());
bool from_seq;
@@ -2414,7 +3026,7 @@ void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_sl
if (!_get_out_slot(from_node, p_from_slot, from_port, from_seq))
return; //can't connect this, it's invalid
- Ref<VisualScriptNode> to_node = script->get_node(edited_func, p_to.to_int());
+ Ref<VisualScriptNode> to_node = script->get_node(func, p_to.to_int());
ERR_FAIL_COND(!to_node.is_valid());
bool to_seq;
@@ -2425,15 +3037,20 @@ void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_sl
ERR_FAIL_COND(from_seq != to_seq);
- undo_redo->create_action(TTR("Connect Nodes"));
+ undo_redo->create_action(TTR("Disconnect Nodes"));
if (from_seq) {
- undo_redo->add_do_method(script.ptr(), "sequence_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int());
- undo_redo->add_undo_method(script.ptr(), "sequence_connect", edited_func, p_from.to_int(), from_port, p_to.to_int());
+ undo_redo->add_do_method(script.ptr(), "sequence_disconnect", func, p_from.to_int(), from_port, p_to.to_int());
+ undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, p_from.to_int(), from_port, p_to.to_int());
} else {
- undo_redo->add_do_method(script.ptr(), "data_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port);
- undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port);
- //update nodes in sgraph
+
+ can_swap = true;
+ data_disconnect_node = p_to.to_int();
+ data_disconnect_port = to_port;
+
+ undo_redo->add_do_method(script.ptr(), "data_disconnect", func, p_from.to_int(), from_port, p_to.to_int(), to_port);
+ undo_redo->add_undo_method(script.ptr(), "data_connect", func, p_from.to_int(), from_port, p_to.to_int(), to_port);
+ //update relevant nodes in the graph
undo_redo->add_do_method(this, "_update_graph", p_from.to_int());
undo_redo->add_do_method(this, "_update_graph", p_to.to_int());
undo_redo->add_undo_method(this, "_update_graph", p_from.to_int());
@@ -2445,6 +3062,216 @@ void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_sl
undo_redo->commit_action();
}
+void VisualScriptEditor::_move_nodes_with_rescan(const StringName &p_func_from, const StringName &p_func_to, int p_id) {
+
+ Set<int> nodes_to_move;
+ HashMap<int, Map<int, int> > seqconns_to_move; // from => List(outp, to)
+ HashMap<int, Map<int, Pair<int, int> > > dataconns_to_move; // to => List(inp_p => from, outp)
+
+ nodes_to_move.insert(p_id);
+ Set<int> sequence_connections;
+ {
+ List<VisualScript::SequenceConnection> sequence_conns;
+ script->get_sequence_connection_list(p_func_from, &sequence_conns);
+
+ HashMap<int, Map<int, int> > seqcons; // from => List(out_p => to)
+
+ for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
+ int from = E->get().from_node;
+ int to = E->get().to_node;
+ int out_p = E->get().from_output;
+ if (!seqcons.has(from))
+ seqcons.set(from, Map<int, int>());
+ seqcons[from].insert(out_p, to);
+ sequence_connections.insert(to);
+ sequence_connections.insert(from);
+ }
+
+ int conn = p_id;
+ List<int> stack;
+ HashMap<int, Set<int> > seen; // from, outp
+ while (seqcons.has(conn)) {
+ for (auto E = seqcons[conn].front(); E; E = E->next()) {
+ if (seen.has(conn) && seen[conn].has(E->key())) {
+ if (!E->next()) {
+ if (stack.size() > 0) {
+ conn = stack.back()->get();
+ stack.pop_back();
+ break;
+ }
+ conn = -101;
+ break;
+ }
+ continue;
+ }
+ if (!seen.has(conn))
+ seen.set(conn, Set<int>());
+ seen[conn].insert(E->key());
+ stack.push_back(conn);
+ if (!seqconns_to_move.has(conn))
+ seqconns_to_move.set(conn, Map<int, int>());
+ seqconns_to_move[conn].insert(E->key(), E->get());
+ conn = E->get();
+ nodes_to_move.insert(conn);
+ break;
+ }
+ if (!seqcons.has(conn) && stack.size() > 0) {
+ conn = stack.back()->get();
+ stack.pop_back();
+ }
+ }
+ }
+
+ {
+ List<VisualScript::DataConnection> data_connections;
+ script->get_data_connection_list(p_func_from, &data_connections);
+
+ HashMap<int, Map<int, Pair<int, int> > > connections;
+
+ for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
+ int from = E->get().from_node;
+ int to = E->get().to_node;
+ int out_p = E->get().from_port;
+ int in_p = E->get().to_port;
+
+ if (!connections.has(to))
+ connections.set(to, Map<int, Pair<int, int> >());
+ connections[to].insert(in_p, Pair<int, int>(from, out_p));
+ }
+
+ // go through the HashMap and do all sorts of crazy ass stuff now...
+ Set<int> nodes_to_be_added;
+ for (Set<int>::Element *F = nodes_to_move.front(); F; F = F->next()) {
+ HashMap<int, Set<int> > seen;
+ List<int> stack;
+ int id = F->get();
+ while (connections.has(id)) {
+ for (auto E = connections[id].front(); E; E = E->next()) {
+ if (seen.has(id) && seen[id].has(E->key())) {
+ if (!E->next()) {
+ if (stack.size() > 0) {
+ id = stack.back()->get();
+ stack.pop_back();
+ break;
+ }
+ id = -11; // I assume ids can't be negative should confirm it...
+ break;
+ }
+ continue;
+ }
+
+ if (sequence_connections.has(E->get().first)) {
+ if (!nodes_to_move.has(E->get().first)) {
+ if (stack.size() > 0) {
+ id = stack.back()->get();
+ stack.pop_back();
+ break;
+ }
+ id = -11; // I assume ids can't be negative should confirm it...
+ break;
+ }
+ }
+
+ if (!seen.has(id))
+ seen.set(id, Set<int>());
+ seen[id].insert(E->key());
+ stack.push_back(id);
+ if (!dataconns_to_move.has(id))
+ dataconns_to_move.set(id, Map<int, Pair<int, int> >());
+ dataconns_to_move[id].insert(E->key(), Pair<int, int>(E->get().first, E->get().second));
+ id = E->get().first;
+ nodes_to_be_added.insert(id);
+ break;
+ }
+ if (!connections.has(id) && stack.size() > 0) {
+ id = stack.back()->get();
+ stack.pop_back();
+ }
+ }
+ }
+ for (Set<int>::Element *E = nodes_to_be_added.front(); E; E = E->next()) {
+ nodes_to_move.insert(E->get());
+ }
+ }
+
+ // * this is primarily for the sake of the having proper undo
+ List<VisualScript::SequenceConnection> seqext;
+ List<VisualScript::DataConnection> dataext;
+
+ List<VisualScript::SequenceConnection> seq_connections;
+ script->get_sequence_connection_list(p_func_from, &seq_connections);
+
+ for (List<VisualScript::SequenceConnection>::Element *E = seq_connections.front(); E; E = E->next()) {
+ if (!nodes_to_move.has(E->get().from_node) && nodes_to_move.has(E->get().to_node)) {
+ seqext.push_back(E->get());
+ } else if (nodes_to_move.has(E->get().from_node) && !nodes_to_move.has(E->get().to_node)) {
+ seqext.push_back(E->get());
+ }
+ }
+
+ List<VisualScript::DataConnection> data_connections;
+ script->get_data_connection_list(p_func_from, &data_connections);
+
+ for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
+ if (!nodes_to_move.has(E->get().from_node) && nodes_to_move.has(E->get().to_node)) {
+ dataext.push_back(E->get());
+ } else if (nodes_to_move.has(E->get().from_node) && !nodes_to_move.has(E->get().to_node)) {
+ dataext.push_back(E->get());
+ }
+ }
+
+ // undo_redo->create_action("Rescan Functions");
+
+ for (Set<int>::Element *E = nodes_to_move.front(); E; E = E->next()) {
+ int id = E->get();
+
+ undo_redo->add_do_method(script.ptr(), "remove_node", p_func_from, id);
+ undo_redo->add_do_method(script.ptr(), "add_node", p_func_to, id, script->get_node(p_func_from, id), script->get_node_position(p_func_from, id));
+
+ undo_redo->add_undo_method(script.ptr(), "remove_node", p_func_to, id);
+ undo_redo->add_undo_method(script.ptr(), "add_node", p_func_from, id, script->get_node(p_func_from, id), script->get_node_position(p_func_from, id));
+ }
+
+ List<int> skeys;
+ seqconns_to_move.get_key_list(&skeys);
+ for (List<int>::Element *E = skeys.front(); E; E = E->next()) {
+ int from_node = E->get();
+ for (Map<int, int>::Element *F = seqconns_to_move[from_node].front(); F; F = F->next()) {
+ int from_port = F->key();
+ int to_node = F->get();
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", p_func_to, from_node, from_port, to_node);
+ undo_redo->add_undo_method(script.ptr(), "sequence_connect", p_func_from, from_node, from_port, to_node);
+ }
+ }
+
+ List<int> keys;
+ dataconns_to_move.get_key_list(&keys);
+ for (List<int>::Element *E = keys.front(); E; E = E->next()) {
+ int to_node = E->get(); // to_node
+ for (Map<int, Pair<int, int> >::Element *F = dataconns_to_move[E->get()].front(); F; F = F->next()) {
+ int inp_p = F->key();
+ Pair<int, int> fro = F->get();
+
+ undo_redo->add_do_method(script.ptr(), "data_connect", p_func_to, fro.first, fro.second, to_node, inp_p);
+ undo_redo->add_undo_method(script.ptr(), "data_connect", p_func_from, fro.first, fro.second, to_node, inp_p);
+ }
+ }
+
+ // this to have proper undo operations
+ for (List<VisualScript::SequenceConnection>::Element *E = seqext.front(); E; E = E->next()) {
+ undo_redo->add_undo_method(script.ptr(), "sequence_connect", p_func_from, E->get().from_node, E->get().from_output, E->get().to_node);
+ }
+ for (List<VisualScript::DataConnection>::Element *E = dataext.front(); E; E = E->next()) {
+ undo_redo->add_undo_method(script.ptr(), "data_connect", p_func_from, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ }
+ // this doesn't need do methods as they are handled by the subsequent do calls implicitly
+
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+
+ // undo_redo->commit_action();
+}
+
void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_pos) {
Node *node = graph->get_node(p_from);
@@ -2452,7 +3279,9 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro
if (!gn)
return;
- Ref<VisualScriptNode> vsn = script->get_node(edited_func, p_from.to_int());
+ StringName func = _get_function_of_node(p_from.to_int());
+
+ Ref<VisualScriptNode> vsn = script->get_node(func, p_from.to_int());
if (!vsn.is_valid())
return;
@@ -2462,12 +3291,11 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro
port_action_node = p_from.to_int();
port_action_output = p_from_slot;
- _port_action_menu(CREATE_ACTION);
+ _port_action_menu(CREATE_ACTION, func);
} else {
-
port_action_output = p_from_slot - vsn->get_output_sequence_port_count();
port_action_node = p_from.to_int();
- _port_action_menu(CREATE_CALL_SET_GET);
+ _port_action_menu(CREATE_CALL_SET_GET, func);
}
}
@@ -2481,7 +3309,9 @@ VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_ac
visited_nodes.insert(p_port_action_node);
- Ref<VisualScriptNode> node = script->get_node(edited_func, p_port_action_node);
+ StringName func = _get_function_of_node(p_port_action_node);
+
+ Ref<VisualScriptNode> node = script->get_node(func, p_port_action_node);
if (!node.is_valid()) {
@@ -2500,7 +3330,7 @@ VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_ac
int from_node;
int from_port;
- if (script->get_input_value_port_connection_source(edited_func, p_port_action_node, i, &from_node, &from_port)) {
+ if (script->get_input_value_port_connection_source(func, p_port_action_node, i, &from_node, &from_port)) {
g = _guess_output_type(from_node, from_port, visited_nodes);
} else {
@@ -2525,7 +3355,7 @@ VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_ac
return node->guess_output_type(in_guesses.ptrw(), p_port_action_output);
}
-void VisualScriptEditor::_port_action_menu(int p_option) {
+void VisualScriptEditor::_port_action_menu(int p_option, const StringName &func) {
Vector2 ofs = graph->get_scroll_ofs() + port_action_pos;
if (graph->is_using_snap()) {
@@ -2549,8 +3379,10 @@ void VisualScriptEditor::_port_action_menu(int p_option) {
} else {
n->set_base_type("Object");
}
-
- String type_string = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+ String type_string;
+ if (script->get_node(func, port_action_node)->get_output_value_port_count() > 0) {
+ type_string = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+ }
if (tg.type == Variant::OBJECT) {
if (tg.script.is_valid()) {
new_connect_node_select->select_from_script(tg.script, "");
@@ -2564,10 +3396,19 @@ void VisualScriptEditor::_port_action_menu(int p_option) {
} else {
new_connect_node_select->select_from_basic_type(tg.type);
}
+ // ensure that the dialog fits inside the graph
+ Vector2 pos = mouse_up_position;
+ Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size();
+ pos.x = pos.x > bounds.x ? bounds.x : pos.x;
+ pos.y = pos.y > bounds.y ? bounds.y : pos.y;
+ new_connect_node_select->set_position(pos);
} break;
case CREATE_ACTION: {
VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn);
- PropertyInfo property_info = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output);
+ PropertyInfo property_info;
+ if (script->get_node(func, port_action_node)->get_output_value_port_count() > 0) {
+ property_info = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output);
+ }
if (tg.type == Variant::OBJECT) {
if (property_info.type == Variant::OBJECT && property_info.hint_string != String()) {
new_connect_node_select->select_from_action(property_info.hint_string);
@@ -2579,25 +3420,18 @@ void VisualScriptEditor::_port_action_menu(int p_option) {
} else {
new_connect_node_select->select_from_action(Variant::get_type_name(tg.type));
}
+ // ensure that the dialog fits inside the graph
+ Vector2 pos = mouse_up_position;
+ Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size();
+ pos.x = pos.x > bounds.x ? bounds.x : pos.x;
+ pos.y = pos.y > bounds.y ? bounds.y : pos.y;
+ new_connect_node_select->set_position(pos);
} break;
}
}
-void VisualScriptEditor::new_node(Ref<VisualScriptNode> vnode, Vector2 ofs) {
- Set<int> vn;
- Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node);
- int new_id = script->get_available_id();
- undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
- undo_redo->add_do_method(this, "_update_graph", new_id);
- undo_redo->add_undo_method(this, "_update_graph", new_id);
- undo_redo->commit_action();
-
- port_action_new_node = new_id;
-}
-
void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id) {
+
undo_redo->create_action(TTR("Connect Node Data"));
VisualScriptReturn *vnode_return = Object::cast_to<VisualScriptReturn>(vnode.ptr());
if (vnode_return != NULL && vnode_old->get_output_value_port_count() > 0) {
@@ -2616,12 +3450,14 @@ void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<Visua
if (port >= value_count) {
port = 0;
}
- undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, port_action_node, port, new_id, 0);
- undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, port_action_node, port, new_id, 0);
+ StringName func = _get_function_of_node(port_action_node);
+ undo_redo->add_do_method(script.ptr(), "data_connect", func, port_action_node, port, new_id, 0);
+ undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, port_action_node, port, new_id, 0);
undo_redo->commit_action();
}
void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting) {
+
Vector2 ofs = graph->get_scroll_ofs() + port_action_pos;
if (graph->is_using_snap()) {
int snap = graph->get_snap();
@@ -2631,19 +3467,29 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
Set<int> vn;
+ bool port_node_exists = true;
+
+ StringName func = _get_function_of_node(port_action_node);
+ if (func == StringName()) {
+ func = default_func;
+ port_node_exists = false;
+ }
+
if (p_category == "visualscript") {
Ref<VisualScriptNode> vnode_new = VisualScriptLanguage::singleton->create_node_from_name(p_text);
- Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node);
+ Ref<VisualScriptNode> vnode_old;
+ if (port_node_exists)
+ vnode_old = script->get_node(func, port_action_node);
int new_id = script->get_available_id();
- if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr()) && script->get_node(edited_func, port_action_node).is_valid()) {
- Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type;
+ if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr()) && vnode_old.is_valid()) {
+ Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type;
Object::cast_to<VisualScriptOperator>(vnode_new.ptr())->set_typed(type);
}
- if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr()) && script->get_node(edited_func, port_action_node).is_valid()) {
- Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type;
- String hint_name = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+ if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr()) && vnode_old.is_valid()) {
+ Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type;
+ String hint_name = vnode_old->get_output_value_port_info(port_action_output).hint_string;
if (type == Variant::OBJECT) {
Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(hint_name);
@@ -2653,14 +3499,15 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(Variant::get_type_name(type));
}
}
+
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode_new, ofs);
+ undo_redo->add_do_method(script.ptr(), "add_node", func, new_id, vnode_new, ofs);
if (vnode_old.is_valid() && p_connecting) {
connect_seq(vnode_old, vnode_new, new_id);
connect_data(vnode_old, vnode_new, new_id);
}
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", func, new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
undo_redo->commit_action();
@@ -2723,16 +3570,24 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
}
}
- new_node(vnode, ofs);
+ int new_id = script->get_available_id();
+ undo_redo->create_action(TTR("Add Node"));
+ undo_redo->add_do_method(script.ptr(), "add_node", func, new_id, vnode, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", func, new_id);
+ undo_redo->add_do_method(this, "_update_graph", new_id);
+ undo_redo->add_undo_method(this, "_update_graph", new_id);
+ undo_redo->commit_action();
+
+ port_action_new_node = new_id;
- Ref<VisualScriptNode> vsn = script->get_node(edited_func, port_action_new_node);
+ Ref<VisualScriptNode> vsn = script->get_node(func, port_action_new_node);
if (Object::cast_to<VisualScriptFunctionCall>(vsn.ptr())) {
Ref<VisualScriptFunctionCall> vsfc = vsn;
vsfc->set_function(p_text);
- if (p_connecting) {
+ if (port_node_exists && p_connecting) {
VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn);
if (tg.type == Variant::OBJECT) {
@@ -2741,9 +3596,9 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
if (tg.gdclass != StringName()) {
vsfc->set_base_type(tg.gdclass);
- } else if (script->get_node(edited_func, port_action_node).is_valid()) {
- PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint;
- String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+ } else if (script->get_node(func, port_action_node).is_valid()) {
+ PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint;
+ String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) {
vsfc->set_base_type(base_type);
@@ -2765,8 +3620,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
}
}
- // if connecting from another node the call mode shouldn't be self
- if (p_connecting) {
+ if (port_node_exists && p_connecting) {
if (Object::cast_to<VisualScriptPropertySet>(vsn.ptr())) {
Ref<VisualScriptPropertySet> vsp = vsn;
@@ -2777,9 +3631,9 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
if (tg.gdclass != StringName()) {
vsp->set_base_type(tg.gdclass);
- } else if (script->get_node(edited_func, port_action_node).is_valid()) {
- PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint;
- String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+ } else if (script->get_node(func, port_action_node).is_valid()) {
+ PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint;
+ String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) {
vsp->set_base_type(base_type);
@@ -2807,9 +3661,9 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
if (tg.gdclass != StringName()) {
vsp->set_base_type(tg.gdclass);
- } else if (script->get_node(edited_func, port_action_node).is_valid()) {
- PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint;
- String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
+ } else if (script->get_node(func, port_action_node).is_valid()) {
+ PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint;
+ String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) {
vsp->set_base_type(base_type);
}
@@ -2826,16 +3680,20 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri
}
}
}
- Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node);
- if (vnode_old.is_valid() && p_connecting) {
- connect_seq(vnode_old, vnode, port_action_new_node);
- connect_data(vnode_old, vnode, port_action_new_node);
+ if (port_node_exists) {
+ Ref<VisualScriptNode> vnode_old = script->get_node(func, port_action_node);
+ if (vnode_old.is_valid() && p_connecting) {
+ connect_seq(vnode_old, vnode, port_action_new_node);
+ connect_data(vnode_old, vnode, port_action_new_node);
+ }
}
_update_graph(port_action_new_node);
- _update_graph_connections();
+ if (port_node_exists)
+ _update_graph_connections();
}
void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id) {
+
VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(vnode_new.ptr());
if (vnode_operator != NULL && !vnode_operator->has_input_sequence_port()) {
return;
@@ -2851,27 +3709,29 @@ void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<Visual
return;
}
+ StringName func = _get_function_of_node(port_action_node);
+
undo_redo->create_action(TTR("Connect Node Sequence"));
int pass_port = -vnode_old->get_output_sequence_port_count() + 1;
int return_port = port_action_output - 1;
if (vnode_old->get_output_value_port_info(port_action_output).name == String("pass") &&
- !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(pass_port)) {
- undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, pass_port, new_id);
- undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, pass_port, new_id);
+ !script->get_output_sequence_ports_connected(func, port_action_node).has(pass_port)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, pass_port, new_id);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, pass_port, new_id);
} else if (vnode_old->get_output_value_port_info(port_action_output).name == String("return") &&
- !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(return_port)) {
- undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, return_port, new_id);
- undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, return_port, new_id);
+ !script->get_output_sequence_ports_connected(func, port_action_node).has(return_port)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, return_port, new_id);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, return_port, new_id);
} else {
for (int port = 0; port < vnode_old->get_output_sequence_port_count(); port++) {
int count = vnode_old->get_output_sequence_port_count();
- if (port_action_output < count && !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port_action_output)) {
- undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port_action_output, new_id);
- undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port_action_output, new_id);
+ if (port_action_output < count && !script->get_output_sequence_ports_connected(func, port_action_node).has(port_action_output)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, port_action_output, new_id);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, port_action_output, new_id);
break;
- } else if (!script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port)) {
- undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port, new_id);
- undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port, new_id);
+ } else if (!script->get_output_sequence_ports_connected(func, port_action_node).has(port)) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, port, new_id);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, port, new_id);
break;
}
}
@@ -2904,7 +3764,6 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons
}
selected = name;
- edited_func = selected;
Ref<VisualScriptFunction> func_node;
func_node.instance();
func_node->set_name(name);
@@ -2916,14 +3775,16 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons
func_node->add_argument(minfo.arguments[i].type, minfo.arguments[i].name, -1, minfo.arguments[i].hint, minfo.arguments[i].hint_string);
}
- undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node);
+ Vector2 ofs = _get_available_pos();
+
+ undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node, ofs);
if (minfo.return_val.type != Variant::NIL || minfo.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
Ref<VisualScriptReturn> ret_node;
ret_node.instance();
ret_node->set_return_type(minfo.return_val.type);
ret_node->set_enable_return_value(true);
ret_node->set_name(name);
- undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id() + 1, ret_node, Vector2(500, 0));
+ undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id() + 1, ret_node, _get_available_pos(false, ofs + Vector2(500, 0)));
}
undo_redo->add_undo_method(script.ptr(), "remove_function", name);
@@ -2938,31 +3799,30 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons
}
void VisualScriptEditor::_cancel_connect_node() {
- // Causes crashes
- //script->remove_node(edited_func, port_action_new_node);
- _update_graph();
+ // ensure the cancel is done
+ port_action_new_node = -1;
}
-void VisualScriptEditor::_create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point) {
- Vector2 ofs = graph->get_scroll_ofs() + p_point;
- if (graph->is_using_snap()) {
- int snap = graph->get_snap();
- ofs = ofs.snapped(Vector2(snap, snap));
- }
- ofs /= EDSCALE;
+int VisualScriptEditor::_create_new_node_from_name(const String &p_text, const Vector2 &p_point, const StringName &p_func) {
+
+ StringName func = default_func;
+ if (p_func != StringName())
+ func = p_func;
+
Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(p_text);
int new_id = script->get_available_id();
undo_redo->create_action(TTR("Add Node"));
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(script.ptr(), "add_node", func, new_id, vnode, p_point);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", func, new_id);
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
undo_redo->commit_action();
+ return new_id;
}
void VisualScriptEditor::_default_value_changed() {
- Ref<VisualScriptNode> vsn = script->get_node(edited_func, editing_id);
+ Ref<VisualScriptNode> vsn = script->get_node(_get_function_of_node(editing_id), editing_id);
if (vsn.is_null())
return;
@@ -2977,7 +3837,7 @@ void VisualScriptEditor::_default_value_changed() {
void VisualScriptEditor::_default_value_edited(Node *p_button, int p_id, int p_input_port) {
- Ref<VisualScriptNode> vsn = script->get_node(edited_func, p_id);
+ Ref<VisualScriptNode> vsn = script->get_node(_get_function_of_node(p_id), p_id);
if (vsn.is_null())
return;
@@ -2996,16 +3856,18 @@ void VisualScriptEditor::_default_value_edited(Node *p_button, int p_id, int p_i
if (pinfo.type == Variant::NODE_PATH) {
Node *edited_scene = get_tree()->get_edited_scene_root();
- Node *script_node = _find_script_node(edited_scene, edited_scene, script);
+ if (edited_scene) { // Fixing an old crash bug ( Visual Script Crashes on editing NodePath with an empty scene open)
+ Node *script_node = _find_script_node(edited_scene, edited_scene, script);
- if (script_node) {
- //pick a node relative to the script, IF the script exists
- pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE;
- pinfo.hint_string = script_node->get_path();
- } else {
- //pick a path relative to edited scene
- pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE;
- pinfo.hint_string = get_tree()->get_edited_scene_root()->get_path();
+ if (script_node) {
+ //pick a node relative to the script, IF the script exists
+ pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE;
+ pinfo.hint_string = script_node->get_path();
+ } else {
+ //pick a path relative to edited scene
+ pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE;
+ pinfo.hint_string = get_tree()->get_edited_scene_root()->get_path();
+ }
}
}
@@ -3032,62 +3894,64 @@ void VisualScriptEditor::_hide_timer() {
hint_text->hide();
}
-void VisualScriptEditor::_node_filter_changed(const String &p_text) {
-
- _update_available_nodes();
-}
-
void VisualScriptEditor::_notification(int p_what) {
- if (p_what == NOTIFICATION_READY || (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree())) {
-
- node_filter->set_right_icon(Control::get_icon("Search", "EditorIcons"));
- node_filter->set_clear_button_enabled(true);
-
- if (p_what == NOTIFICATION_READY) {
+ switch (p_what) {
+ case NOTIFICATION_READY: {
variable_editor->connect("changed", this, "_update_members");
signal_editor->connect("changed", this, "_update_members");
+ FALLTHROUGH;
}
+ case NOTIFICATION_THEME_CHANGED: {
+ if (p_what != NOTIFICATION_READY && !is_visible_in_tree()) {
+ return;
+ }
- Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme();
+ edit_variable_edit->add_style_override("bg", get_stylebox("bg", "Tree"));
+ edit_signal_edit->add_style_override("bg", get_stylebox("bg", "Tree"));
+ func_input_scroll->add_style_override("bg", get_stylebox("bg", "Tree"));
- bool dark_theme = tm->get_constant("dark_theme", "Editor");
+ Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme();
- List<Pair<String, Color> > colors;
- if (dark_theme) {
- colors.push_back(Pair<String, Color>("flow_control", Color(0.96, 0.96, 0.96)));
- colors.push_back(Pair<String, Color>("functions", Color(0.96, 0.52, 0.51)));
- colors.push_back(Pair<String, Color>("data", Color(0.5, 0.96, 0.81)));
- colors.push_back(Pair<String, Color>("operators", Color(0.67, 0.59, 0.87)));
- colors.push_back(Pair<String, Color>("custom", Color(0.5, 0.73, 0.96)));
- colors.push_back(Pair<String, Color>("constants", Color(0.96, 0.5, 0.69)));
- } else {
- colors.push_back(Pair<String, Color>("flow_control", Color(0.26, 0.26, 0.26)));
- colors.push_back(Pair<String, Color>("functions", Color(0.95, 0.4, 0.38)));
- colors.push_back(Pair<String, Color>("data", Color(0.07, 0.73, 0.51)));
- colors.push_back(Pair<String, Color>("operators", Color(0.51, 0.4, 0.82)));
- colors.push_back(Pair<String, Color>("custom", Color(0.31, 0.63, 0.95)));
- colors.push_back(Pair<String, Color>("constants", Color(0.94, 0.18, 0.49)));
- }
+ bool dark_theme = tm->get_constant("dark_theme", "Editor");
- for (List<Pair<String, Color> >::Element *E = colors.front(); E; E = E->next()) {
- Ref<StyleBoxFlat> sb = tm->get_stylebox("frame", "GraphNode");
- if (!sb.is_null()) {
- Ref<StyleBoxFlat> frame_style = sb->duplicate();
- Color c = sb->get_border_color();
- Color cn = E->get().second;
- cn.a = c.a;
- frame_style->set_border_color(cn);
- node_styles[E->get().first] = frame_style;
+ List<Pair<String, Color> > colors;
+ if (dark_theme) {
+ colors.push_back(Pair<String, Color>("flow_control", Color(0.96, 0.96, 0.96)));
+ colors.push_back(Pair<String, Color>("functions", Color(0.96, 0.52, 0.51)));
+ colors.push_back(Pair<String, Color>("data", Color(0.5, 0.96, 0.81)));
+ colors.push_back(Pair<String, Color>("operators", Color(0.67, 0.59, 0.87)));
+ colors.push_back(Pair<String, Color>("custom", Color(0.5, 0.73, 0.96)));
+ colors.push_back(Pair<String, Color>("constants", Color(0.96, 0.5, 0.69)));
+ } else {
+ colors.push_back(Pair<String, Color>("flow_control", Color(0.26, 0.26, 0.26)));
+ colors.push_back(Pair<String, Color>("functions", Color(0.95, 0.4, 0.38)));
+ colors.push_back(Pair<String, Color>("data", Color(0.07, 0.73, 0.51)));
+ colors.push_back(Pair<String, Color>("operators", Color(0.51, 0.4, 0.82)));
+ colors.push_back(Pair<String, Color>("custom", Color(0.31, 0.63, 0.95)));
+ colors.push_back(Pair<String, Color>("constants", Color(0.94, 0.18, 0.49)));
}
- }
- if (is_visible_in_tree() && script.is_valid()) {
- _update_members();
- _update_graph();
- }
- } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
- left_vsplit->set_visible(is_visible_in_tree());
+ for (List<Pair<String, Color> >::Element *E = colors.front(); E; E = E->next()) {
+ Ref<StyleBoxFlat> sb = tm->get_stylebox("frame", "GraphNode");
+ if (!sb.is_null()) {
+ Ref<StyleBoxFlat> frame_style = sb->duplicate();
+ Color c = sb->get_border_color();
+ Color cn = E->get().second;
+ cn.a = c.a;
+ frame_style->set_border_color(cn);
+ node_styles[E->get().first] = frame_style;
+ }
+ }
+
+ if (is_visible_in_tree() && script.is_valid()) {
+ _update_members();
+ _update_graph();
+ }
+ } break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ members_section->set_visible(is_visible_in_tree());
+ } break;
}
}
@@ -3098,8 +3962,9 @@ void VisualScriptEditor::_graph_ofs_changed(const Vector2 &p_ofs) {
updating_graph = true;
- if (script->has_function(edited_func)) {
- script->set_function_scroll(edited_func, graph->get_scroll_ofs() / EDSCALE);
+ // Just use the default func for all the properties that need to be handled for drawing rather than adding to the Visual Script Class
+ if (script->has_function(default_func)) {
+ script->set_function_scroll(default_func, graph->get_scroll_ofs() / EDSCALE);
script->set_edited(true);
}
updating_graph = false;
@@ -3110,7 +3975,9 @@ void VisualScriptEditor::_comment_node_resized(const Vector2 &p_new_size, int p_
if (updating_graph)
return;
- Ref<VisualScriptComment> vsc = script->get_node(edited_func, p_node);
+ StringName func = _get_function_of_node(p_node);
+
+ Ref<VisualScriptComment> vsc = script->get_node(func, p_node);
if (vsc.is_null())
return;
@@ -3128,7 +3995,7 @@ void VisualScriptEditor::_comment_node_resized(const Vector2 &p_new_size, int p_
undo_redo->add_undo_method(vsc.ptr(), "set_size", vsc->get_size());
undo_redo->commit_action();
- gn->set_custom_minimum_size(p_new_size); //for this time since graph update is blocked
+ gn->set_custom_minimum_size(p_new_size);
gn->set_size(Size2(1, 1));
graph->set_block_minimum_size_adjust(false);
updating_graph = false;
@@ -3148,7 +4015,8 @@ void VisualScriptEditor::_menu_option(int p_what) {
if (gn) {
if (gn->is_selected()) {
int id = String(gn->get_name()).to_int();
- Ref<VisualScriptNode> vsn = script->get_node(edited_func, id);
+ StringName func = _get_function_of_node(id);
+ Ref<VisualScriptNode> vsn = script->get_node(func, id);
if (vsn.is_valid()) {
vsn->set_breakpoint(!vsn->is_breakpoint());
reselect.push_back(gn->get_name());
@@ -3170,28 +4038,30 @@ void VisualScriptEditor::_menu_option(int p_what) {
} break;
case EDIT_COPY_NODES:
case EDIT_CUT_NODES: {
-
- if (!script->has_function(edited_func))
+ if (!script->has_function(default_func))
break;
clipboard->nodes.clear();
clipboard->data_connections.clear();
clipboard->sequence_connections.clear();
+ Set<String> funcs;
for (int i = 0; i < graph->get_child_count(); i++) {
GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
if (gn) {
if (gn->is_selected()) {
int id = String(gn->get_name()).to_int();
- Ref<VisualScriptNode> node = script->get_node(edited_func, id);
+ StringName func = _get_function_of_node(id);
+ Ref<VisualScriptNode> node = script->get_node(func, id);
if (Object::cast_to<VisualScriptFunction>(*node)) {
EditorNode::get_singleton()->show_warning(TTR("Can't copy the function node."));
return;
}
if (node.is_valid()) {
clipboard->nodes[id] = node->duplicate(true);
- clipboard->nodes_positions[id] = script->get_node_position(edited_func, id);
+ clipboard->nodes_positions[id] = script->get_node_position(func, id);
+ funcs.insert(String(func));
}
}
}
@@ -3200,37 +4070,38 @@ void VisualScriptEditor::_menu_option(int p_what) {
if (clipboard->nodes.empty())
break;
- List<VisualScript::SequenceConnection> sequence_connections;
+ for (Set<String>::Element *F = funcs.front(); F; F = F->next()) {
+ List<VisualScript::SequenceConnection> sequence_connections;
- script->get_sequence_connection_list(edited_func, &sequence_connections);
+ script->get_sequence_connection_list(F->get(), &sequence_connections);
- for (List<VisualScript::SequenceConnection>::Element *E = sequence_connections.front(); E; E = E->next()) {
+ for (List<VisualScript::SequenceConnection>::Element *E = sequence_connections.front(); E; E = E->next()) {
- if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) {
+ if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) {
- clipboard->sequence_connections.insert(E->get());
+ clipboard->sequence_connections.insert(E->get());
+ }
}
- }
- List<VisualScript::DataConnection> data_connections;
+ List<VisualScript::DataConnection> data_connections;
- script->get_data_connection_list(edited_func, &data_connections);
+ script->get_data_connection_list(F->get(), &data_connections);
- for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
+ for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
- if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) {
+ if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) {
- clipboard->data_connections.insert(E->get());
+ clipboard->data_connections.insert(E->get());
+ }
}
}
-
if (p_what == EDIT_CUT_NODES) {
_on_nodes_delete(); // oh yeah, also delete on cut
}
} break;
case EDIT_PASTE_NODES: {
- if (!script->has_function(edited_func))
+ if (!script->has_function(default_func))
break;
if (clipboard->nodes.empty()) {
@@ -3248,11 +4119,15 @@ void VisualScriptEditor::_menu_option(int p_what) {
Set<Vector2> existing_positions;
{
- List<int> nodes;
- script->get_node_list(edited_func, &nodes);
- for (List<int>::Element *E = nodes.front(); E; E = E->next()) {
- Vector2 pos = script->get_node_position(edited_func, E->get()).snapped(Vector2(2, 2));
- existing_positions.insert(pos);
+ List<StringName> functions;
+ script->get_function_list(&functions);
+ for (List<StringName>::Element *F = functions.front(); F; F = F->next()) {
+ List<int> nodes;
+ script->get_node_list(F->get(), &nodes);
+ for (List<int>::Element *E = nodes.front(); E; E = E->next()) {
+ Vector2 pos = script->get_node_position(F->get(), E->get()).snapped(Vector2(2, 2));
+ existing_positions.insert(pos);
+ }
}
}
@@ -3271,20 +4146,20 @@ void VisualScriptEditor::_menu_option(int p_what) {
paste_pos += Vector2(20, 20) * EDSCALE;
}
- undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, node, paste_pos);
- undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id);
+ undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, node, paste_pos);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
}
for (Set<VisualScript::SequenceConnection>::Element *E = clipboard->sequence_connections.front(); E; E = E->next()) {
- undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]);
- undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]);
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", default_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]);
+ undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", default_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]);
}
for (Set<VisualScript::DataConnection>::Element *E = clipboard->data_connections.front(); E; E = E->next()) {
- undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port);
- undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port);
+ undo_redo->add_do_method(script.ptr(), "data_connect", default_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port);
+ undo_redo->add_undo_method(script.ptr(), "data_disconnect", default_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port);
}
undo_redo->add_do_method(this, "_update_graph");
@@ -3300,6 +4175,275 @@ void VisualScriptEditor::_menu_option(int p_what) {
}
}
} break;
+ case EDIT_CREATE_FUNCTION: {
+
+ StringName function = "";
+ Map<int, Ref<VisualScriptNode> > nodes;
+ Set<int> selections;
+ for (int i = 0; i < graph->get_child_count(); i++) {
+ GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
+ if (gn) {
+ if (gn->is_selected()) {
+ int id = String(gn->get_name()).to_int();
+ StringName func = _get_function_of_node(id);
+ Ref<VisualScriptNode> node = script->get_node(func, id);
+ if (Object::cast_to<VisualScriptFunction>(*node)) {
+ EditorNode::get_singleton()->show_warning(TTR("Can't create function with a function node."));
+ return;
+ }
+ if (node.is_valid()) {
+ if (func != function && function != StringName("")) {
+ EditorNode::get_singleton()->show_warning(TTR("Can't create function of nodes from nodes of multiple functions."));
+ return;
+ }
+ nodes.insert(id, node);
+ selections.insert(id);
+ function = func;
+ }
+ }
+ }
+ }
+
+ if (nodes.size() == 0) {
+ return; // nothing to be done if there are no valid nodes selected
+ }
+
+ Set<VisualScript::SequenceConnection> seqmove;
+ Set<VisualScript::DataConnection> datamove;
+
+ Set<VisualScript::SequenceConnection> seqext;
+ Set<VisualScript::DataConnection> dataext;
+
+ int start_node = -1;
+ Set<int> end_nodes;
+ if (nodes.size() == 1) {
+ Ref<VisualScriptNode> nd = script->get_node(function, nodes.front()->key());
+ if (nd.is_valid() && nd->has_input_sequence_port())
+ start_node = nodes.front()->key();
+ else {
+ EditorNode::get_singleton()->show_warning(TTR("Select at least one node with sequence port."));
+ return;
+ }
+ } else {
+ List<VisualScript::SequenceConnection> seqs;
+ script->get_sequence_connection_list(function, &seqs);
+
+ if (seqs.size() == 0) {
+ // in case there are no sequence connections
+ // select the top most node cause that's probably how
+ // the user wants to connect the nodes
+ int top_nd = -1;
+ Vector2 top;
+ for (Map<int, Ref<VisualScriptNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ Ref<VisualScriptNode> nd = script->get_node(function, E->key());
+ if (nd.is_valid() && nd->has_input_sequence_port()) {
+ if (top_nd < 0) {
+ top_nd = E->key();
+ top = script->get_node_position(function, top_nd);
+ }
+ Vector2 pos = script->get_node_position(function, E->key());
+ if (top.y > pos.y) {
+ top_nd = E->key();
+ top = pos;
+ }
+ }
+ }
+ Ref<VisualScriptNode> nd = script->get_node(function, top_nd);
+ if (nd.is_valid() && nd->has_input_sequence_port())
+ start_node = top_nd;
+ else {
+ EditorNode::get_singleton()->show_warning(TTR("Select at least one node with sequence port."));
+ return;
+ }
+ } else {
+ // pick the node with input sequence
+ Set<int> nodes_from;
+ Set<int> nodes_to;
+ for (List<VisualScript::SequenceConnection>::Element *E = seqs.front(); E; E = E->next()) {
+ if (nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) {
+ seqmove.insert(E->get());
+ nodes_from.insert(E->get().from_node);
+ } else if (nodes.has(E->get().from_node) && !nodes.has(E->get().to_node)) {
+ seqext.insert(E->get());
+ } else if (!nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) {
+ if (start_node == -1) {
+ seqext.insert(E->get());
+ start_node = E->get().to_node;
+ } else {
+ EditorNode::get_singleton()->show_warning(TTR("Try to only have one sequence input in selection."));
+ return;
+ }
+ }
+ nodes_to.insert(E->get().to_node);
+ }
+
+ // to use to add return nodes
+ _get_ends(start_node, seqs, selections, end_nodes);
+
+ if (start_node == -1) {
+ // if we still don't have a start node then
+ // run through the nodes and select the first tree node
+ // ie node without any input sequence but output sequence
+ for (Set<int>::Element *E = nodes_from.front(); E; E = E->next()) {
+ if (!nodes_to.has(E->get())) {
+ start_node = E->get();
+ }
+ }
+ }
+ }
+ }
+
+ if (start_node == -1) {
+ return; // this should not happen, but just in case something goes wrong
+ }
+
+ List<Variant::Type> inputs; // input types
+ List<Pair<int, int> > input_connections;
+ {
+ List<VisualScript::DataConnection> dats;
+ script->get_data_connection_list(function, &dats);
+ for (List<VisualScript::DataConnection>::Element *E = dats.front(); E; E = E->next()) {
+ if (nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) {
+ datamove.insert(E->get());
+ } else if (!nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) {
+ // add all these as inputs for the Function
+ Ref<VisualScriptNode> node = script->get_node(function, E->get().to_node);
+ if (node.is_valid()) {
+ dataext.insert(E->get());
+ PropertyInfo pi = node->get_input_value_port_info(E->get().to_port);
+ inputs.push_back(pi.type);
+ input_connections.push_back(Pair<int, int>(E->get().to_node, E->get().to_port));
+ }
+ } else if (nodes.has(E->get().from_node) && !nodes.has(E->get().to_node)) {
+ dataext.insert(E->get());
+ }
+ }
+ }
+
+ String new_fn = _validate_name("new_function");
+
+ Vector2 ofs = _get_available_pos(false, script->get_node_position(function, start_node) - Vector2(80, 150));
+
+ Ref<VisualScriptFunction> func_node;
+ func_node.instance();
+ func_node->set_name(new_fn);
+
+ undo_redo->create_action(TTR("Create Function"));
+
+ undo_redo->add_do_method(script.ptr(), "add_function", new_fn);
+ int fn_id = script->get_available_id();
+ undo_redo->add_do_method(script.ptr(), "add_node", new_fn, fn_id, func_node, ofs);
+ undo_redo->add_undo_method(script.ptr(), "remove_function", new_fn);
+ undo_redo->add_do_method(this, "_update_members");
+ undo_redo->add_undo_method(this, "_update_members");
+ undo_redo->add_do_method(this, "emit_signal", "edited_script_changed");
+ undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed");
+
+ // Move the nodes
+
+ for (Map<int, Ref<VisualScriptNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ undo_redo->add_do_method(script.ptr(), "remove_node", function, E->key());
+ undo_redo->add_do_method(script.ptr(), "add_node", new_fn, E->key(), E->get(), script->get_node_position(function, E->key()));
+
+ // undo_redo->add_undo_method(script.ptr(), "remove_node", new_fn, E->key()); not needed cause we already remove the function :P
+ undo_redo->add_undo_method(script.ptr(), "add_node", function, E->key(), E->get(), script->get_node_position(function, E->key()));
+ }
+
+ for (Set<VisualScript::SequenceConnection>::Element *E = seqmove.front(); E; E = E->next()) {
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", new_fn, E->get().from_node, E->get().from_output, E->get().to_node);
+ undo_redo->add_undo_method(script.ptr(), "sequence_connect", function, E->get().from_node, E->get().from_output, E->get().to_node);
+ }
+
+ for (Set<VisualScript::DataConnection>::Element *E = datamove.front(); E; E = E->next()) {
+ undo_redo->add_do_method(script.ptr(), "data_connect", new_fn, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ undo_redo->add_undo_method(script.ptr(), "data_connect", function, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ }
+
+ // Add undo for external connections as well so that it's easier to revert back and forth
+ // these didn't require do methods as it's already handled internally by other do calls
+ for (Set<VisualScript::SequenceConnection>::Element *E = seqext.front(); E; E = E->next()) {
+ undo_redo->add_undo_method(script.ptr(), "sequence_connect", function, E->get().from_node, E->get().from_output, E->get().to_node);
+ }
+ for (Set<VisualScript::DataConnection>::Element *E = dataext.front(); E; E = E->next()) {
+ undo_redo->add_undo_method(script.ptr(), "data_connect", function, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ }
+
+ // I don't really think we need support for non sequenced functions at this moment
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", new_fn, fn_id, 0, start_node);
+
+ // end nodes are mapped to the return nodes with data connections if possible
+ int m = 1;
+ for (Set<int>::Element *G = end_nodes.front(); G; G = G->next()) {
+ Ref<VisualScriptReturn> ret_node;
+ ret_node.instance();
+
+ int ret_id = fn_id + (m++);
+ selections.insert(ret_id);
+ Vector2 ofsi = _get_available_pos(false, script->get_node_position(function, G->get()) + Vector2(80, -100));
+ undo_redo->add_do_method(script.ptr(), "add_node", new_fn, ret_id, ret_node, ofsi);
+ undo_redo->add_undo_method(script.ptr(), "remove_node", new_fn, ret_id);
+
+ undo_redo->add_do_method(script.ptr(), "sequence_connect", new_fn, G->get(), 0, ret_id);
+ // add data outputs from each of the end_nodes
+ Ref<VisualScriptNode> vsn = script->get_node(function, G->get());
+ if (vsn.is_valid() && vsn->get_output_value_port_count() > 0) {
+ ret_node->set_enable_return_value(true);
+ // use the zeroth data port cause that's the likely one that is planned to be used
+ ret_node->set_return_type(vsn->get_output_value_port_info(0).type);
+ undo_redo->add_do_method(script.ptr(), "data_connect", new_fn, G->get(), 0, ret_id, 0);
+ }
+ }
+
+ // * might make the system more intelligent by checking port from info.
+ int i = 0;
+ List<Pair<int, int> >::Element *F = input_connections.front();
+ for (List<Variant::Type>::Element *E = inputs.front(); E && F; E = E->next(), F = F->next()) {
+ func_node->add_argument(E->get(), "arg_" + String::num_int64(i), i);
+ undo_redo->add_do_method(script.ptr(), "data_connect", new_fn, fn_id, i, F->get().first, F->get().second);
+ i++; // increment i
+ }
+
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+
+ undo_redo->commit_action();
+
+ // make sure all Nodes get marked for selection so that they can be moved together
+ selections.insert(fn_id);
+ for (int k = 0; k < graph->get_child_count(); k++) {
+ GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(k));
+ if (gn) {
+ int id = gn->get_name().operator String().to_int();
+ gn->set_selected(selections.has(id));
+ }
+ }
+
+ // Ensure Preview Selection is of newly created function node
+ if (selections.size()) {
+ EditorNode::get_singleton()->push_item(func_node.ptr());
+ }
+
+ } break;
+ case REFRESH_GRAPH: {
+ _update_graph();
+ } break;
+ }
+}
+
+// this is likely going to be very slow and I am not sure if I should keep it
+// but I hope that it will not be a problem considering that we won't be creating functions so frequently
+// and cyclic connections would be a problem but hopefully we won't let them get to this point
+void VisualScriptEditor::_get_ends(int p_node, const List<VisualScript::SequenceConnection> &p_seqs, const Set<int> &p_selected, Set<int> &r_end_nodes) {
+ for (const List<VisualScript::SequenceConnection>::Element *E = p_seqs.front(); E; E = E->next()) {
+ int from = E->get().from_node;
+ int to = E->get().to_node;
+
+ if (from == p_node && p_selected.has(to)) {
+ // this is an interior connection move forward to the to node
+ _get_ends(to, p_seqs, p_selected, r_end_nodes);
+ } else if (from == p_node && !p_selected.has(to)) {
+ r_end_nodes.insert(from);
+ }
}
}
@@ -3312,6 +4456,9 @@ void VisualScriptEditor::_member_rmb_selected(const Vector2 &p_pos) {
member_popup->set_position(members->get_global_position() + p_pos);
member_popup->set_size(Vector2());
+ function_name_edit->set_position(members->get_global_position() + p_pos);
+ function_name_edit->set_size(Vector2());
+
TreeItem *root = members->get_root();
Ref<Texture> del_icon = Control::get_icon("Remove", "EditorIcons");
@@ -3322,6 +4469,8 @@ void VisualScriptEditor::_member_rmb_selected(const Vector2 &p_pos) {
member_type = MEMBER_FUNCTION;
member_name = ti->get_text(0);
+ member_popup->add_icon_shortcut(edit_icon, ED_GET_SHORTCUT("visual_script_editor/edit_member"), MEMBER_EDIT);
+ member_popup->add_separator();
member_popup->add_icon_shortcut(del_icon, ED_GET_SHORTCUT("visual_script_editor/delete_selected"), MEMBER_REMOVE);
member_popup->popup();
return;
@@ -3389,6 +4538,11 @@ void VisualScriptEditor::_member_option(int p_option) {
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
undo_redo->commit_action();
+ } else if (p_option == MEMBER_EDIT) {
+ selected = members->get_selected()->get_text(0);
+ function_name_edit->popup();
+ function_name_box->set_text(selected);
+ function_name_box->select_all();
}
} break;
case MEMBER_VARIABLE: {
@@ -3425,7 +4579,6 @@ void VisualScriptEditor::_member_option(int p_option) {
undo_redo->add_undo_method(this, "_update_members");
undo_redo->commit_action();
} else if (p_option == MEMBER_EDIT) {
-
signal_editor->edit(name);
edit_signal_dialog->set_title(TTR("Editing Signal:") + " " + name);
edit_signal_dialog->popup_centered_minsize(Size2(400, 300) * EDSCALE);
@@ -3446,8 +4599,14 @@ void VisualScriptEditor::_bind_methods() {
ClassDB::bind_method("_member_edited", &VisualScriptEditor::_member_edited);
ClassDB::bind_method("_member_selected", &VisualScriptEditor::_member_selected);
ClassDB::bind_method("_update_members", &VisualScriptEditor::_update_members);
+ ClassDB::bind_method("_members_gui_input", &VisualScriptEditor::_members_gui_input);
+ ClassDB::bind_method("_member_rmb_selected", &VisualScriptEditor::_member_rmb_selected);
+ ClassDB::bind_method("_member_option", &VisualScriptEditor::_member_option);
+ ClassDB::bind_method("_fn_name_box_input", &VisualScriptEditor::_fn_name_box_input);
+
ClassDB::bind_method("_change_base_type", &VisualScriptEditor::_change_base_type);
ClassDB::bind_method("_change_base_type_callback", &VisualScriptEditor::_change_base_type_callback);
+ ClassDB::bind_method("_toggle_tool_script", &VisualScriptEditor::_toggle_tool_script);
ClassDB::bind_method("_node_selected", &VisualScriptEditor::_node_selected);
ClassDB::bind_method("_node_moved", &VisualScriptEditor::_node_moved);
ClassDB::bind_method("_move_node", &VisualScriptEditor::_move_node);
@@ -3456,7 +4615,14 @@ void VisualScriptEditor::_bind_methods() {
ClassDB::bind_method("_remove_node", &VisualScriptEditor::_remove_node);
ClassDB::bind_method("_update_graph", &VisualScriptEditor::_update_graph, DEFVAL(-1));
ClassDB::bind_method("_node_ports_changed", &VisualScriptEditor::_node_ports_changed);
- ClassDB::bind_method("_available_node_doubleclicked", &VisualScriptEditor::_available_node_doubleclicked);
+
+ ClassDB::bind_method("_create_function_dialog", &VisualScriptEditor::_create_function_dialog);
+ ClassDB::bind_method("_create_function", &VisualScriptEditor::_create_function);
+ ClassDB::bind_method("_add_node_dialog", &VisualScriptEditor::_add_node_dialog);
+ ClassDB::bind_method("_add_func_input", &VisualScriptEditor::_add_func_input);
+ ClassDB::bind_method("_remove_func_input", &VisualScriptEditor::_remove_func_input);
+ ClassDB::bind_method("_deselect_input_names", &VisualScriptEditor::_deselect_input_names);
+
ClassDB::bind_method("_default_value_edited", &VisualScriptEditor::_default_value_edited);
ClassDB::bind_method("_default_value_changed", &VisualScriptEditor::_default_value_changed);
ClassDB::bind_method("_menu_option", &VisualScriptEditor::_menu_option);
@@ -3469,15 +4635,23 @@ void VisualScriptEditor::_bind_methods() {
ClassDB::bind_method("_selected_new_virtual_method", &VisualScriptEditor::_selected_new_virtual_method);
ClassDB::bind_method("_cancel_connect_node", &VisualScriptEditor::_cancel_connect_node);
- ClassDB::bind_method("_create_new_node", &VisualScriptEditor::_create_new_node);
+ ClassDB::bind_method("_create_new_node_from_name", &VisualScriptEditor::_create_new_node_from_name);
ClassDB::bind_method("_expression_text_changed", &VisualScriptEditor::_expression_text_changed);
+ ClassDB::bind_method("_add_input_port", &VisualScriptEditor::_add_input_port);
+ ClassDB::bind_method("_add_output_port", &VisualScriptEditor::_add_output_port);
+ ClassDB::bind_method("_remove_input_port", &VisualScriptEditor::_remove_input_port);
+ ClassDB::bind_method("_remove_output_port", &VisualScriptEditor::_remove_output_port);
+ ClassDB::bind_method("_change_port_type", &VisualScriptEditor::_change_port_type);
+ ClassDB::bind_method("_update_node_size", &VisualScriptEditor::_update_node_size);
+ ClassDB::bind_method("_port_name_focus_out", &VisualScriptEditor::_port_name_focus_out);
ClassDB::bind_method("get_drag_data_fw", &VisualScriptEditor::get_drag_data_fw);
ClassDB::bind_method("can_drop_data_fw", &VisualScriptEditor::can_drop_data_fw);
ClassDB::bind_method("drop_data_fw", &VisualScriptEditor::drop_data_fw);
ClassDB::bind_method("_input", &VisualScriptEditor::_input);
- ClassDB::bind_method("_members_gui_input", &VisualScriptEditor::_members_gui_input);
+ ClassDB::bind_method("_graph_gui_input", &VisualScriptEditor::_graph_gui_input);
+
ClassDB::bind_method("_on_nodes_delete", &VisualScriptEditor::_on_nodes_delete);
ClassDB::bind_method("_on_nodes_duplicate", &VisualScriptEditor::_on_nodes_duplicate);
@@ -3488,17 +4662,10 @@ void VisualScriptEditor::_bind_methods() {
ClassDB::bind_method("_graph_connect_to_empty", &VisualScriptEditor::_graph_connect_to_empty);
ClassDB::bind_method("_update_graph_connections", &VisualScriptEditor::_update_graph_connections);
- ClassDB::bind_method("_node_filter_changed", &VisualScriptEditor::_node_filter_changed);
ClassDB::bind_method("_selected_method", &VisualScriptEditor::_selected_method);
ClassDB::bind_method("_draw_color_over_button", &VisualScriptEditor::_draw_color_over_button);
- ClassDB::bind_method("_member_rmb_selected", &VisualScriptEditor::_member_rmb_selected);
-
- ClassDB::bind_method("_member_option", &VisualScriptEditor::_member_option);
-
- ClassDB::bind_method("_update_available_nodes", &VisualScriptEditor::_update_available_nodes);
-
ClassDB::bind_method("_generic_search", &VisualScriptEditor::_generic_search);
}
@@ -3508,6 +4675,8 @@ VisualScriptEditor::VisualScriptEditor() {
clipboard = memnew(Clipboard);
}
updating_graph = false;
+ saved_pos_dirty = false;
+ saved_position = Vector2(0, 0);
edit_menu = memnew(MenuButton);
edit_menu->set_text(TTR("Edit"));
@@ -3519,56 +4688,50 @@ VisualScriptEditor::VisualScriptEditor() {
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/copy_nodes"), EDIT_COPY_NODES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/cut_nodes"), EDIT_CUT_NODES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/paste_nodes"), EDIT_PASTE_NODES);
-
+ edit_menu->get_popup()->add_separator();
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/create_function"), EDIT_CREATE_FUNCTION);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/refresh_nodes"), REFRESH_GRAPH);
edit_menu->get_popup()->connect("id_pressed", this, "_menu_option");
- left_vsplit = memnew(VSplitContainer);
- ScriptEditor::get_singleton()->get_left_list_split()->call_deferred("add_child", left_vsplit); //add but wait until done settig up this
- left_vsplit->set_v_size_flags(SIZE_EXPAND_FILL);
- left_vsplit->set_stretch_ratio(2);
- left_vsplit->hide();
+ members_section = memnew(VBoxContainer);
+ // Add but wait until done setting up this.
+ ScriptEditor::get_singleton()->get_left_list_split()->call_deferred("add_child", members_section);
+ members_section->set_v_size_flags(SIZE_EXPAND_FILL);
- VBoxContainer *left_vb = memnew(VBoxContainer);
- left_vsplit->add_child(left_vb);
- left_vb->set_v_size_flags(SIZE_EXPAND_FILL);
- //left_vb->set_custom_minimum_size(Size2(230, 1) * EDSCALE);
+ CheckButton *tool_script_check = memnew(CheckButton);
+ tool_script_check->set_text(TTR("Make Tool:"));
+ members_section->add_child(tool_script_check);
+ tool_script_check->connect("pressed", this, "_toggle_tool_script");
- base_type_select = memnew(Button);
- left_vb->add_margin_child(TTR("Base Type:"), base_type_select);
- base_type_select->connect("pressed", this, "_change_base_type");
+ /// Members ///
members = memnew(Tree);
- left_vb->add_margin_child(TTR("Members:"), members, true);
+ members_section->add_margin_child(TTR("Members:"), members, true);
+ members->set_custom_minimum_size(Size2(0, 50 * EDSCALE));
members->set_hide_root(true);
members->connect("button_pressed", this, "_member_button");
members->connect("item_edited", this, "_member_edited");
members->connect("cell_selected", this, "_member_selected", varray(), CONNECT_DEFERRED);
members->connect("gui_input", this, "_members_gui_input");
+ members->connect("item_rmb_selected", this, "_member_rmb_selected");
+ members->set_allow_rmb_select(true);
members->set_allow_reselect(true);
members->set_hide_folding(true);
members->set_drag_forwarding(this);
- VBoxContainer *left_vb2 = memnew(VBoxContainer);
- left_vsplit->add_child(left_vb2);
- left_vb2->set_v_size_flags(SIZE_EXPAND_FILL);
-
- VBoxContainer *vbc_nodes = memnew(VBoxContainer);
- HBoxContainer *hbc_nodes = memnew(HBoxContainer);
- node_filter = memnew(LineEdit);
- node_filter->connect("text_changed", this, "_node_filter_changed");
- hbc_nodes->add_child(node_filter);
- node_filter->set_h_size_flags(SIZE_EXPAND_FILL);
- vbc_nodes->add_child(hbc_nodes);
-
- nodes = memnew(Tree);
- vbc_nodes->add_child(nodes);
- nodes->set_v_size_flags(SIZE_EXPAND_FILL);
+ member_popup = memnew(PopupMenu);
+ add_child(member_popup);
+ member_popup->connect("id_pressed", this, "_member_option");
- left_vb2->add_margin_child(TTR("Available Nodes:"), vbc_nodes, true);
+ function_name_edit = memnew(PopupDialog);
+ function_name_box = memnew(LineEdit);
+ function_name_edit->add_child(function_name_box);
+ function_name_edit->set_h_size_flags(SIZE_EXPAND);
+ function_name_box->connect("gui_input", this, "_fn_name_box_input");
+ function_name_box->set_expand_to_text_length(true);
+ add_child(function_name_edit);
- nodes->set_hide_root(true);
- nodes->connect("item_activated", this, "_available_node_doubleclicked");
- nodes->set_drag_forwarding(this);
+ /// Actual Graph ///
graph = memnew(GraphEdit);
add_child(graph);
@@ -3579,10 +4742,77 @@ VisualScriptEditor::VisualScriptEditor() {
graph->connect("_end_node_move", this, "_end_node_move");
graph->connect("delete_nodes_request", this, "_on_nodes_delete");
graph->connect("duplicate_nodes_request", this, "_on_nodes_duplicate");
+ graph->connect("gui_input", this, "_graph_gui_input");
graph->set_drag_forwarding(this);
graph->hide();
graph->connect("scroll_offset_changed", this, "_graph_ofs_changed");
+ /// Add Buttons to Top Bar/Zoom bar.
+ HBoxContainer *graph_hbc = graph->get_zoom_hbox();
+
+ Label *base_lbl = memnew(Label);
+ base_lbl->set_text("Change Base Type: ");
+ graph_hbc->add_child(base_lbl);
+
+ base_type_select = memnew(Button);
+ base_type_select->connect("pressed", this, "_change_base_type");
+ graph_hbc->add_child(base_type_select);
+
+ Button *add_nds = memnew(Button);
+ add_nds->set_text("Add Nodes...");
+ graph_hbc->add_child(add_nds);
+ add_nds->connect("pressed", this, "_add_node_dialog");
+
+ Button *fn_btn = memnew(Button);
+ fn_btn->set_text("Add Function...");
+ graph_hbc->add_child(fn_btn);
+ fn_btn->connect("pressed", this, "_create_function_dialog");
+
+ // Add Function Dialog.
+ VBoxContainer *function_vb = memnew(VBoxContainer);
+ function_vb->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ HBoxContainer *func_name_hbox = memnew(HBoxContainer);
+ function_vb->add_child(func_name_hbox);
+
+ Label *func_name_label = memnew(Label);
+ func_name_label->set_text(TTR("Name:"));
+ func_name_hbox->add_child(func_name_label);
+
+ func_name_box = memnew(LineEdit);
+ func_name_box->set_h_size_flags(SIZE_EXPAND_FILL);
+ func_name_box->set_placeholder(TTR("function_name"));
+ func_name_box->set_text("");
+ func_name_box->connect("focus_entered", this, "_deselect_input_names");
+ func_name_hbox->add_child(func_name_box);
+
+ // Add minor setting for function if needed, here!
+
+ function_vb->add_child(memnew(HSeparator));
+
+ Button *add_input_button = memnew(Button);
+ add_input_button->set_h_size_flags(SIZE_EXPAND_FILL);
+ add_input_button->set_text(TTR("Add Input"));
+ add_input_button->connect("pressed", this, "_add_func_input");
+ function_vb->add_child(add_input_button);
+
+ func_input_scroll = memnew(ScrollContainer);
+ func_input_scroll->set_v_size_flags(SIZE_EXPAND_FILL);
+ function_vb->add_child(func_input_scroll);
+
+ func_input_vbox = memnew(VBoxContainer);
+ func_input_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ func_input_scroll->add_child(func_input_vbox);
+
+ function_create_dialog = memnew(ConfirmationDialog);
+ function_create_dialog->set_custom_minimum_size(Size2(450, 300) * EDSCALE);
+ function_create_dialog->set_v_size_flags(SIZE_EXPAND_FILL);
+ function_create_dialog->set_title(TTR("Create Function"));
+ function_create_dialog->add_child(function_vb);
+ function_create_dialog->get_ok()->set_text(TTR("Create"));
+ function_create_dialog->get_ok()->connect("pressed", this, "_create_function");
+ add_child(function_create_dialog);
+
select_func_text = memnew(Label);
select_func_text->set_text(TTR("Select or create a function to edit its graph."));
select_func_text->set_align(Label::ALIGN_CENTER);
@@ -3603,7 +4833,7 @@ VisualScriptEditor::VisualScriptEditor() {
hint_text_timer->connect("timeout", this, "_hide_timer");
add_child(hint_text_timer);
- //allowed casts (connections)
+ // Allowed casts (connections).
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
graph->add_valid_connection_type(Variant::NIL, i);
graph->add_valid_connection_type(i, Variant::NIL);
@@ -3643,7 +4873,7 @@ VisualScriptEditor::VisualScriptEditor() {
edit_variable_edit->edit(variable_editor);
select_base_type = memnew(CreateDialog);
- select_base_type->set_base_type("Object"); //anything goes
+ select_base_type->set_base_type("Object"); // Anything goes.
select_base_type->connect("create", this, "_change_base_type_callback");
add_child(select_base_type);
@@ -3651,8 +4881,8 @@ VisualScriptEditor::VisualScriptEditor() {
updating_members = false;
- set_process_input(true); //for revert on drag
- set_process_unhandled_input(true); //for revert on drag
+ set_process_input(true);
+ set_process_unhandled_input(true);
default_value_edit = memnew(CustomPropertyEditor);
add_child(default_value_edit);
@@ -3665,25 +4895,18 @@ VisualScriptEditor::VisualScriptEditor() {
new_connect_node_select = memnew(VisualScriptPropertySelector);
add_child(new_connect_node_select);
+ new_connect_node_select->set_resizable(true);
new_connect_node_select->connect("selected", this, "_selected_connect_node");
new_connect_node_select->get_cancel()->connect("pressed", this, "_cancel_connect_node");
new_virtual_method_select = memnew(VisualScriptPropertySelector);
add_child(new_virtual_method_select);
new_virtual_method_select->connect("selected", this, "_selected_new_virtual_method");
-
- member_popup = memnew(PopupMenu);
- add_child(member_popup);
- members->connect("item_rmb_selected", this, "_member_rmb_selected");
- members->set_allow_rmb_select(true);
- member_popup->connect("id_pressed", this, "_member_option");
-
- _VisualScriptEditor::get_singleton()->connect("custom_nodes_updated", this, "_update_available_nodes");
}
VisualScriptEditor::~VisualScriptEditor() {
- undo_redo->clear_history(); //avoid crashes
+ undo_redo->clear_history(); // Avoid crashes.
memdelete(signal_editor);
memdelete(variable_editor);
}
@@ -3714,12 +4937,14 @@ static void register_editor_callback() {
ED_SHORTCUT("visual_script_editor/copy_nodes", TTR("Copy Nodes"), KEY_MASK_CMD + KEY_C);
ED_SHORTCUT("visual_script_editor/cut_nodes", TTR("Cut Nodes"), KEY_MASK_CMD + KEY_X);
ED_SHORTCUT("visual_script_editor/paste_nodes", TTR("Paste Nodes"), KEY_MASK_CMD + KEY_V);
+ ED_SHORTCUT("visual_script_editor/create_function", TTR("Make Function"), KEY_MASK_CMD + KEY_G);
+ ED_SHORTCUT("visual_script_editor/refresh_nodes", TTR("Refresh Graph"), KEY_MASK_CMD + KEY_R);
ED_SHORTCUT("visual_script_editor/edit_member", TTR("Edit Member"), KEY_MASK_CMD + KEY_E);
}
void VisualScriptEditor::register_editor() {
- //too early to register stuff here, request a callback
+ // Too early to register stuff here, request a callback.
EditorNode::add_plugin_init_callback(register_editor_callback);
}
diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h
index 4f302d1d72..fbf021e7b6 100644
--- a/modules/visual_script/visual_script_editor.h
+++ b/modules/visual_script/visual_script_editor.h
@@ -59,6 +59,8 @@ class VisualScriptEditor : public ScriptEditorBase {
EDIT_COPY_NODES,
EDIT_CUT_NODES,
EDIT_PASTE_NODES,
+ EDIT_CREATE_FUNCTION,
+ REFRESH_GRAPH
};
enum PortAction {
@@ -79,17 +81,19 @@ class VisualScriptEditor : public ScriptEditorBase {
MEMBER_SIGNAL
};
- VSplitContainer *left_vsplit;
+ VBoxContainer *members_section;
MenuButton *edit_menu;
Ref<VisualScript> script;
Button *base_type_select;
- GraphEdit *graph;
+ LineEdit *func_name_box;
+ ScrollContainer *func_input_scroll;
+ VBoxContainer *func_input_vbox;
+ ConfirmationDialog *function_create_dialog;
- LineEdit *node_filter;
- TextureRect *node_filter_icon;
+ GraphEdit *graph;
VisualScriptEditorSignalEdit *signal_editor;
@@ -110,7 +114,8 @@ class VisualScriptEditor : public ScriptEditorBase {
UndoRedo *undo_redo;
Tree *members;
- Tree *nodes;
+ PopupDialog *function_name_edit;
+ LineEdit *function_name_box;
Label *hint_text;
Timer *hint_text_timer;
@@ -133,6 +138,7 @@ class VisualScriptEditor : public ScriptEditorBase {
HashMap<StringName, Ref<StyleBox> > node_styles;
StringName edited_func;
+ StringName default_func;
void _update_graph_connections();
void _update_graph(int p_only_id = -1);
@@ -165,9 +171,13 @@ class VisualScriptEditor : public ScriptEditorBase {
int port_action_output;
Vector2 port_action_pos;
int port_action_new_node;
- void _port_action_menu(int p_option);
- void new_node(Ref<VisualScriptNode> vnode, Vector2 ofs);
+ bool saved_pos_dirty;
+ Vector2 saved_position;
+
+ Vector2 mouse_up_position;
+
+ void _port_action_menu(int p_option, const StringName &p_func);
void connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id);
@@ -175,23 +185,26 @@ class VisualScriptEditor : public ScriptEditorBase {
void connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id);
void _cancel_connect_node();
- void _create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point);
+ int _create_new_node_from_name(const String &p_text, const Vector2 &p_point, const StringName &p_func = StringName());
void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting);
int error_line;
void _node_selected(Node *p_node);
- void _center_on_node(int p_id);
+ void _center_on_node(const StringName &p_func, int p_id);
void _node_filter_changed(const String &p_text);
void _change_base_type_callback();
void _change_base_type();
+ void _toggle_tool_script();
void _member_selected();
void _member_edited();
void _begin_node_move();
void _end_node_move();
- void _move_node(String func, int p_id, const Vector2 &p_to);
+ void _move_node(const StringName &p_func, int p_id, const Vector2 &p_to);
+
+ void _get_ends(int p_node, const List<VisualScript::SequenceConnection> &p_seqs, const Set<int> &p_selected, Set<int> &r_end_nodes);
void _node_moved(Vector2 p_from, Vector2 p_to, int p_id);
void _remove_node(int p_id);
@@ -200,21 +213,44 @@ class VisualScriptEditor : public ScriptEditorBase {
void _graph_connect_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_pos);
void _node_ports_changed(const String &p_func, int p_id);
- void _available_node_doubleclicked();
+ void _node_create();
void _update_available_nodes();
void _member_button(Object *p_item, int p_column, int p_button);
void _expression_text_changed(const String &p_text, int p_id);
+ void _add_input_port(int p_id);
+ void _add_output_port(int p_id);
+ void _remove_input_port(int p_id, int p_port);
+ void _remove_output_port(int p_id, int p_port);
+ void _change_port_type(int p_select, int p_id, int p_port, bool is_input);
+ void _update_node_size(int p_id);
+ void _port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input);
- String revert_on_drag;
+ Vector2 _get_available_pos(bool centered = true, Vector2 ofs = Vector2()) const;
+ StringName _get_function_of_node(int p_id) const;
- void _input(const Ref<InputEvent> &p_event);
+ void _move_nodes_with_rescan(const StringName &p_func_from, const StringName &p_func_to, int p_id);
+ bool node_has_sequence_connections(const StringName &p_func, int p_id);
- void _generic_search(String p_base_type = "");
+ void _generic_search(String p_base_type = "", Vector2 pos = Vector2(), bool node_centered = false);
+ void _input(const Ref<InputEvent> &p_event);
+ void _graph_gui_input(const Ref<InputEvent> &p_event);
void _members_gui_input(const Ref<InputEvent> &p_event);
+ void _fn_name_box_input(const Ref<InputEvent> &p_event);
+ void _rename_function(const String &p_name, const String &p_new_name);
+
+ void _create_function_dialog();
+ void _create_function();
+ void _add_func_input();
+ void _remove_func_input(Node *p_node);
+ void _deselect_input_names();
+ void _add_node_dialog();
+ void _node_item_selected();
+ void _node_item_unselected();
+
void _on_nodes_delete();
void _on_nodes_duplicate();
@@ -225,6 +261,10 @@ class VisualScriptEditor : public ScriptEditorBase {
int editing_id;
int editing_input;
+ bool can_swap;
+ int data_disconnect_node;
+ int data_disconnect_port;
+
void _default_value_changed();
void _default_value_edited(Node *p_button, int p_id, int p_input_port);
@@ -239,7 +279,7 @@ class VisualScriptEditor : public ScriptEditorBase {
void _draw_color_over_button(Object *obj, Color p_color);
void _button_resource_previewed(const String &p_path, const Ref<Texture> &p_preview, const Ref<Texture> &p_small_preview, Variant p_ud);
- VisualScriptNode::TypeGuess _guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &visited_nodes);
+ VisualScriptNode::TypeGuess _guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &p_visited_nodes);
void _member_rmb_selected(const Vector2 &p_pos);
void _member_option(int p_option);
diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp
index c330fa1bc0..4e90a08009 100644
--- a/modules/visual_script/visual_script_func_nodes.cpp
+++ b/modules/visual_script/visual_script_func_nodes.cpp
@@ -133,10 +133,12 @@ int VisualScriptFunctionCall::get_input_value_port_count() const {
MethodBind *mb = ClassDB::get_method(_get_base_type(), function);
if (mb) {
- return mb->get_argument_count() + (call_mode == CALL_MODE_INSTANCE ? 1 : 0) + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) - use_default_args;
+ int defaulted_args = mb->get_argument_count() < use_default_args ? mb->get_argument_count() : use_default_args;
+ return mb->get_argument_count() + (call_mode == CALL_MODE_INSTANCE ? 1 : 0) + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) - defaulted_args;
}
- return method_cache.arguments.size() + (call_mode == CALL_MODE_INSTANCE ? 1 : 0) + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) - use_default_args;
+ int defaulted_args = method_cache.arguments.size() < use_default_args ? method_cache.arguments.size() : use_default_args;
+ return method_cache.arguments.size() + (call_mode == CALL_MODE_INSTANCE ? 1 : 0) + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) - defaulted_args;
}
}
int VisualScriptFunctionCall::get_output_value_port_count() const {
@@ -1037,7 +1039,7 @@ PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const
}
List<PropertyInfo> props;
- ClassDB::get_property_list(_get_base_type(), &props, true);
+ ClassDB::get_property_list(_get_base_type(), &props, false);
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
if (E->get().name == property) {
PropertyInfo pinfo = PropertyInfo(E->get().type, "value", PROPERTY_HINT_TYPE_STRING, E->get().hint_string);
@@ -1056,13 +1058,6 @@ PropertyInfo VisualScriptPropertySet::get_output_value_port_info(int p_idx) cons
if (call_mode == CALL_MODE_BASIC_TYPE) {
return PropertyInfo(basic_type, "out");
} else if (call_mode == CALL_MODE_INSTANCE) {
- List<PropertyInfo> props;
- ClassDB::get_property_list(_get_base_type(), &props, true);
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- if (E->get().name == property) {
- return PropertyInfo(E->get().type, "pass", PROPERTY_HINT_TYPE_STRING, E->get().hint_string);
- }
- }
return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type());
} else {
return PropertyInfo();
@@ -1813,7 +1808,7 @@ PropertyInfo VisualScriptPropertyGet::get_input_value_port_info(int p_idx) const
PropertyInfo VisualScriptPropertyGet::get_output_value_port_info(int p_idx) const {
List<PropertyInfo> props;
- ClassDB::get_property_list(_get_base_type(), &props, true);
+ ClassDB::get_property_list(_get_base_type(), &props, false);
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
if (E->get().name == property) {
return PropertyInfo(E->get().type, "value." + String(index));
diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp
index 65820b4c15..957127fe61 100644
--- a/modules/visual_script/visual_script_nodes.cpp
+++ b/modules/visual_script/visual_script_nodes.cpp
@@ -356,6 +356,441 @@ int VisualScriptFunction::get_stack_size() const {
}
//////////////////////////////////////////
+/////////////////LISTS////////////////////
+//////////////////////////////////////////
+
+int VisualScriptLists::get_output_sequence_port_count() const {
+ if (sequenced)
+ return 1;
+ return 0;
+}
+bool VisualScriptLists::has_input_sequence_port() const {
+ return sequenced;
+}
+
+String VisualScriptLists::get_output_sequence_port_text(int p_port) const {
+ return "";
+}
+
+int VisualScriptLists::get_input_value_port_count() const {
+ return inputports.size();
+}
+int VisualScriptLists::get_output_value_port_count() const {
+ return outputports.size();
+}
+
+PropertyInfo VisualScriptLists::get_input_value_port_info(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, inputports.size(), PropertyInfo());
+
+ PropertyInfo pi;
+ pi.name = inputports[p_idx].name;
+ pi.type = inputports[p_idx].type;
+ return pi;
+}
+PropertyInfo VisualScriptLists::get_output_value_port_info(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, outputports.size(), PropertyInfo());
+
+ PropertyInfo pi;
+ pi.name = outputports[p_idx].name;
+ pi.type = outputports[p_idx].type;
+ return pi;
+}
+
+bool VisualScriptLists::is_input_port_editable() const {
+ return ((flags & INPUT_EDITABLE) == INPUT_EDITABLE);
+}
+bool VisualScriptLists::is_input_port_name_editable() const {
+ return ((flags & INPUT_NAME_EDITABLE) == INPUT_NAME_EDITABLE);
+}
+bool VisualScriptLists::is_input_port_type_editable() const {
+ return ((flags & INPUT_TYPE_EDITABLE) == INPUT_TYPE_EDITABLE);
+}
+
+bool VisualScriptLists::is_output_port_editable() const {
+ return ((flags & OUTPUT_EDITABLE) == OUTPUT_EDITABLE);
+}
+bool VisualScriptLists::is_output_port_name_editable() const {
+ return ((flags & INPUT_NAME_EDITABLE) == INPUT_NAME_EDITABLE);
+}
+bool VisualScriptLists::is_output_port_type_editable() const {
+ return ((flags & INPUT_TYPE_EDITABLE) == INPUT_TYPE_EDITABLE);
+}
+
+// for the inspector
+bool VisualScriptLists::_set(const StringName &p_name, const Variant &p_value) {
+
+ if (p_name == "input_count" && is_input_port_editable()) {
+
+ int new_argc = p_value;
+ int argc = inputports.size();
+ if (argc == new_argc)
+ return true;
+
+ inputports.resize(new_argc);
+
+ for (int i = argc; i < new_argc; i++) {
+ inputports.write[i].name = "arg" + itos(i + 1);
+ inputports.write[i].type = Variant::NIL;
+ }
+ ports_changed_notify();
+ _change_notify();
+ return true;
+ }
+ if (String(p_name).begins_with("input_") && is_input_port_editable()) {
+ int idx = String(p_name).get_slicec('_', 1).get_slicec('/', 0).to_int() - 1;
+ ERR_FAIL_INDEX_V(idx, inputports.size(), false);
+ String what = String(p_name).get_slice("/", 1);
+ if (what == "type") {
+
+ Variant::Type new_type = Variant::Type(int(p_value));
+ inputports.write[idx].type = new_type;
+ ports_changed_notify();
+
+ return true;
+ }
+
+ if (what == "name") {
+
+ inputports.write[idx].name = p_value;
+ ports_changed_notify();
+ return true;
+ }
+ }
+
+ if (p_name == "output_count" && is_output_port_editable()) {
+
+ int new_argc = p_value;
+ int argc = outputports.size();
+ if (argc == new_argc)
+ return true;
+
+ outputports.resize(new_argc);
+
+ for (int i = argc; i < new_argc; i++) {
+ outputports.write[i].name = "arg" + itos(i + 1);
+ outputports.write[i].type = Variant::NIL;
+ }
+ ports_changed_notify();
+ _change_notify();
+ return true;
+ }
+ if (String(p_name).begins_with("output_") && is_output_port_editable()) {
+ int idx = String(p_name).get_slicec('_', 1).get_slicec('/', 0).to_int() - 1;
+ ERR_FAIL_INDEX_V(idx, outputports.size(), false);
+ String what = String(p_name).get_slice("/", 1);
+ if (what == "type") {
+
+ Variant::Type new_type = Variant::Type(int(p_value));
+ outputports.write[idx].type = new_type;
+ ports_changed_notify();
+
+ return true;
+ }
+
+ if (what == "name") {
+
+ outputports.write[idx].name = p_value;
+ ports_changed_notify();
+ return true;
+ }
+ }
+
+ if (p_name == "sequenced/sequenced") {
+ sequenced = p_value;
+ ports_changed_notify();
+ return true;
+ }
+
+ return false;
+}
+bool VisualScriptLists::_get(const StringName &p_name, Variant &r_ret) const {
+
+ if (p_name == "input_count" && is_input_port_editable()) {
+ r_ret = inputports.size();
+ return true;
+ }
+ if (String(p_name).begins_with("input_") && is_input_port_editable()) {
+ int idx = String(p_name).get_slicec('_', 1).get_slicec('/', 0).to_int() - 1;
+ ERR_FAIL_INDEX_V(idx, inputports.size(), false);
+ String what = String(p_name).get_slice("/", 1);
+ if (what == "type") {
+ r_ret = inputports[idx].type;
+ return true;
+ }
+ if (what == "name") {
+ r_ret = inputports[idx].name;
+ return true;
+ }
+ }
+
+ if (p_name == "output_count" && is_output_port_editable()) {
+ r_ret = outputports.size();
+ return true;
+ }
+ if (String(p_name).begins_with("output_") && is_output_port_editable()) {
+ int idx = String(p_name).get_slicec('_', 1).get_slicec('/', 0).to_int() - 1;
+ ERR_FAIL_INDEX_V(idx, outputports.size(), false);
+ String what = String(p_name).get_slice("/", 1);
+ if (what == "type") {
+ r_ret = outputports[idx].type;
+ return true;
+ }
+ if (what == "name") {
+ r_ret = outputports[idx].name;
+ return true;
+ }
+ }
+
+ if (p_name == "sequenced/sequenced") {
+ r_ret = sequenced;
+ return true;
+ }
+
+ return false;
+}
+void VisualScriptLists::_get_property_list(List<PropertyInfo> *p_list) const {
+
+ if (is_input_port_editable()) {
+ p_list->push_back(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_RANGE, "0,256"));
+ String argt = "Any";
+ for (int i = 1; i < Variant::VARIANT_MAX; i++) {
+ argt += "," + Variant::get_type_name(Variant::Type(i));
+ }
+
+ for (int i = 0; i < inputports.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::INT, "input_" + itos(i + 1) + "/type", PROPERTY_HINT_ENUM, argt));
+ p_list->push_back(PropertyInfo(Variant::STRING, "input_" + itos(i + 1) + "/name"));
+ }
+ }
+
+ if (is_output_port_editable()) {
+ p_list->push_back(PropertyInfo(Variant::INT, "output_count", PROPERTY_HINT_RANGE, "0,256"));
+ String argt = "Any";
+ for (int i = 1; i < Variant::VARIANT_MAX; i++) {
+ argt += "," + Variant::get_type_name(Variant::Type(i));
+ }
+
+ for (int i = 0; i < outputports.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::INT, "output_" + itos(i + 1) + "/type", PROPERTY_HINT_ENUM, argt));
+ p_list->push_back(PropertyInfo(Variant::STRING, "output_" + itos(i + 1) + "/name"));
+ }
+ }
+ p_list->push_back(PropertyInfo(Variant::BOOL, "sequenced/sequenced"));
+}
+
+// input data port interaction
+void VisualScriptLists::add_input_data_port(Variant::Type p_type, const String &p_name, int p_index) {
+
+ if (!is_input_port_editable())
+ return;
+
+ Port inp;
+ inp.name = p_name;
+ inp.type = p_type;
+ if (p_index >= 0)
+ inputports.insert(p_index, inp);
+ else
+ inputports.push_back(inp);
+
+ ports_changed_notify();
+ _change_notify();
+}
+void VisualScriptLists::set_input_data_port_type(int p_idx, Variant::Type p_type) {
+
+ if (!is_input_port_type_editable())
+ return;
+
+ ERR_FAIL_INDEX(p_idx, inputports.size());
+
+ inputports.write[p_idx].type = p_type;
+ ports_changed_notify();
+ _change_notify();
+}
+void VisualScriptLists::set_input_data_port_name(int p_idx, const String &p_name) {
+
+ if (!is_input_port_name_editable())
+ return;
+
+ ERR_FAIL_INDEX(p_idx, inputports.size());
+
+ inputports.write[p_idx].name = p_name;
+ ports_changed_notify();
+ _change_notify();
+}
+void VisualScriptLists::remove_input_data_port(int p_argidx) {
+
+ if (!is_input_port_editable())
+ return;
+
+ ERR_FAIL_INDEX(p_argidx, inputports.size());
+
+ inputports.remove(p_argidx);
+
+ ports_changed_notify();
+ _change_notify();
+}
+
+// output data port interaction
+void VisualScriptLists::add_output_data_port(Variant::Type p_type, const String &p_name, int p_index) {
+
+ if (!is_output_port_editable())
+ return;
+
+ Port out;
+ out.name = p_name;
+ out.type = p_type;
+ if (p_index >= 0)
+ outputports.insert(p_index, out);
+ else
+ outputports.push_back(out);
+
+ ports_changed_notify();
+ _change_notify();
+}
+void VisualScriptLists::set_output_data_port_type(int p_idx, Variant::Type p_type) {
+
+ if (!is_output_port_type_editable())
+ return;
+
+ ERR_FAIL_INDEX(p_idx, outputports.size());
+
+ outputports.write[p_idx].type = p_type;
+ ports_changed_notify();
+ _change_notify();
+}
+void VisualScriptLists::set_output_data_port_name(int p_idx, const String &p_name) {
+
+ if (!is_output_port_name_editable())
+ return;
+
+ ERR_FAIL_INDEX(p_idx, outputports.size());
+
+ outputports.write[p_idx].name = p_name;
+ ports_changed_notify();
+ _change_notify();
+}
+void VisualScriptLists::remove_output_data_port(int p_argidx) {
+
+ if (!is_output_port_editable())
+ return;
+
+ ERR_FAIL_INDEX(p_argidx, outputports.size());
+
+ outputports.remove(p_argidx);
+
+ ports_changed_notify();
+ _change_notify();
+}
+
+// sequences
+void VisualScriptLists::set_sequenced(bool p_enable) {
+ if (sequenced == p_enable)
+ return;
+ sequenced = p_enable;
+ ports_changed_notify();
+}
+bool VisualScriptLists::is_sequenced() const {
+ return sequenced;
+}
+
+VisualScriptLists::VisualScriptLists() {
+ // initialize
+ sequenced = false;
+ flags = 0;
+}
+
+void VisualScriptLists::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("add_input_data_port", "type", "name", "index"), &VisualScriptLists::add_input_data_port);
+ ClassDB::bind_method(D_METHOD("set_input_data_port_name", "index", "name"), &VisualScriptLists::set_input_data_port_name);
+ ClassDB::bind_method(D_METHOD("set_input_data_port_type", "index", "type"), &VisualScriptLists::set_input_data_port_type);
+ ClassDB::bind_method(D_METHOD("remove_input_data_port", "index"), &VisualScriptLists::remove_input_data_port);
+
+ ClassDB::bind_method(D_METHOD("add_output_data_port", "type", "name", "index"), &VisualScriptLists::add_output_data_port);
+ ClassDB::bind_method(D_METHOD("set_output_data_port_name", "index", "name"), &VisualScriptLists::set_output_data_port_name);
+ ClassDB::bind_method(D_METHOD("set_output_data_port_type", "index", "type"), &VisualScriptLists::set_output_data_port_type);
+ ClassDB::bind_method(D_METHOD("remove_output_data_port", "index"), &VisualScriptLists::remove_output_data_port);
+}
+
+//////////////////////////////////////////
+//////////////COMPOSEARRAY////////////////
+//////////////////////////////////////////
+
+int VisualScriptComposeArray::get_output_sequence_port_count() const {
+ if (sequenced)
+ return 1;
+ return 0;
+}
+bool VisualScriptComposeArray::has_input_sequence_port() const {
+ return sequenced;
+}
+
+String VisualScriptComposeArray::get_output_sequence_port_text(int p_port) const {
+ return "";
+}
+
+int VisualScriptComposeArray::get_input_value_port_count() const {
+ return inputports.size();
+}
+int VisualScriptComposeArray::get_output_value_port_count() const {
+ return 1;
+}
+
+PropertyInfo VisualScriptComposeArray::get_input_value_port_info(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, inputports.size(), PropertyInfo());
+
+ PropertyInfo pi;
+ pi.name = inputports[p_idx].name;
+ pi.type = inputports[p_idx].type;
+ return pi;
+}
+PropertyInfo VisualScriptComposeArray::get_output_value_port_info(int p_idx) const {
+ PropertyInfo pi;
+ pi.name = "out";
+ pi.type = Variant::ARRAY;
+ return pi;
+}
+
+String VisualScriptComposeArray::get_caption() const {
+ return "Compose Array";
+}
+String VisualScriptComposeArray::get_text() const {
+ return "";
+}
+
+class VisualScriptComposeArrayNode : public VisualScriptNodeInstance {
+public:
+ int input_count = 0;
+ virtual int get_working_memory_size() const { return 0; }
+
+ virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Variant::CallError &r_error, String &r_error_str) {
+
+ if (input_count > 0) {
+ Array arr;
+ for (int i = 0; i < input_count; i++)
+ arr.push_back((*p_inputs[i]));
+ Variant va = Variant(arr);
+
+ *p_outputs[0] = va;
+ }
+
+ return 0;
+ }
+};
+
+VisualScriptNodeInstance *VisualScriptComposeArray::instance(VisualScriptInstance *p_instance) {
+
+ VisualScriptComposeArrayNode *instance = memnew(VisualScriptComposeArrayNode);
+ instance->input_count = inputports.size();
+ return instance;
+}
+
+VisualScriptComposeArray::VisualScriptComposeArray() {
+ // initialize stuff here
+ sequenced = false;
+ flags = INPUT_EDITABLE;
+}
+
+//////////////////////////////////////////
////////////////OPERATOR//////////////////
//////////////////////////////////////////
@@ -3640,6 +4075,14 @@ VisualScriptDeconstruct::VisualScriptDeconstruct() {
type = Variant::NIL;
}
+template <Variant::Type T>
+static Ref<VisualScriptNode> create_node_deconst_typed(const String &p_name) {
+ Ref<VisualScriptDeconstruct> node;
+ node.instance();
+ node->set_deconstruct_type(T);
+ return node;
+}
+
void register_visual_script_nodes() {
VisualScriptLanguage::singleton->add_register_func("data/set_variable", create_node_generic<VisualScriptVariableSet>);
@@ -3697,7 +4140,17 @@ void register_visual_script_nodes() {
VisualScriptLanguage::singleton->add_register_func("operators/logic/in", create_op_node<Variant::OP_IN>);
VisualScriptLanguage::singleton->add_register_func("operators/logic/select", create_node_generic<VisualScriptSelect>);
- VisualScriptLanguage::singleton->add_register_func("functions/deconstruct", create_node_generic<VisualScriptDeconstruct>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::VECTOR2), create_node_deconst_typed<Variant::Type::VECTOR2>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::VECTOR3), create_node_deconst_typed<Variant::Type::VECTOR3>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::COLOR), create_node_deconst_typed<Variant::Type::COLOR>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::RECT2), create_node_deconst_typed<Variant::Type::RECT2>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::TRANSFORM2D), create_node_deconst_typed<Variant::Type::TRANSFORM2D>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::PLANE), create_node_deconst_typed<Variant::Type::PLANE>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::QUAT), create_node_deconst_typed<Variant::Type::QUAT>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::AABB), create_node_deconst_typed<Variant::Type::AABB>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::BASIS), create_node_deconst_typed<Variant::Type::BASIS>);
+ VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::TRANSFORM), create_node_deconst_typed<Variant::Type::TRANSFORM>);
+ VisualScriptLanguage::singleton->add_register_func("functions/compose_array", create_node_generic<VisualScriptComposeArray>);
for (int i = 1; i < Variant::VARIANT_MAX; i++) {
diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h
index 762a1bdfb6..c7354cb0d8 100644
--- a/modules/visual_script/visual_script_nodes.h
+++ b/modules/visual_script/visual_script_nodes.h
@@ -103,6 +103,103 @@ public:
VisualScriptFunction();
};
+class VisualScriptLists : public VisualScriptNode {
+
+ GDCLASS(VisualScriptLists, VisualScriptNode)
+
+ struct Port {
+ String name;
+ Variant::Type type;
+ };
+
+protected:
+ Vector<Port> inputports;
+ Vector<Port> outputports;
+
+ enum {
+ OUTPUT_EDITABLE = 0x0001,
+ OUTPUT_NAME_EDITABLE = 0x0002,
+ OUTPUT_TYPE_EDITABLE = 0x0004,
+ INPUT_EDITABLE = 0x0008,
+ INPUT_NAME_EDITABLE = 0x000F,
+ INPUT_TYPE_EDITABLE = 0x0010,
+ };
+
+ int flags;
+
+ bool sequenced;
+
+ 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;
+
+ static void _bind_methods();
+
+public:
+ virtual bool is_output_port_editable() const;
+ virtual bool is_output_port_name_editable() const;
+ virtual bool is_output_port_type_editable() const;
+
+ virtual bool is_input_port_editable() const;
+ virtual bool is_input_port_name_editable() const;
+ virtual bool is_input_port_type_editable() const;
+
+ virtual int get_output_sequence_port_count() const;
+ virtual bool has_input_sequence_port() const;
+
+ virtual String get_output_sequence_port_text(int p_port) const;
+
+ virtual int get_input_value_port_count() const;
+ virtual int get_output_value_port_count() const;
+
+ virtual PropertyInfo get_input_value_port_info(int p_idx) const;
+ virtual PropertyInfo get_output_value_port_info(int p_idx) const;
+
+ virtual String get_caption() const = 0;
+ virtual String get_text() const = 0;
+ virtual String get_category() const = 0;
+
+ void add_input_data_port(Variant::Type p_type, const String &p_name, int p_index = -1);
+ void set_input_data_port_type(int p_idx, Variant::Type p_type);
+ void set_input_data_port_name(int p_idx, const String &p_name);
+ void remove_input_data_port(int p_argidx);
+
+ void add_output_data_port(Variant::Type p_type, const String &p_name, int p_index = -1);
+ void set_output_data_port_type(int p_idx, Variant::Type p_type);
+ void set_output_data_port_name(int p_idx, const String &p_name);
+ void remove_output_data_port(int p_argidx);
+
+ void set_sequenced(bool p_enable);
+ bool is_sequenced() const;
+
+ VisualScriptLists();
+};
+
+class VisualScriptComposeArray : public VisualScriptLists {
+
+ GDCLASS(VisualScriptComposeArray, VisualScriptLists)
+
+public:
+ virtual int get_output_sequence_port_count() const;
+ virtual bool has_input_sequence_port() const;
+
+ virtual String get_output_sequence_port_text(int p_port) const;
+
+ virtual int get_input_value_port_count() const;
+ virtual int get_output_value_port_count() const;
+
+ virtual PropertyInfo get_input_value_port_info(int p_idx) const;
+ virtual PropertyInfo get_output_value_port_info(int p_idx) const;
+
+ virtual String get_caption() const;
+ virtual String get_text() const;
+ virtual String get_category() const { return "functions"; }
+
+ virtual VisualScriptNodeInstance *instance(VisualScriptInstance *p_instance);
+
+ VisualScriptComposeArray();
+};
+
class VisualScriptOperator : public VisualScriptNode {
GDCLASS(VisualScriptOperator, VisualScriptNode);
diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp
index 764807cffd..62b818150c 100644
--- a/modules/visual_script/visual_script_property_selector.cpp
+++ b/modules/visual_script/visual_script_property_selector.cpp
@@ -32,7 +32,7 @@
#include "core/os/keyboard.h"
#include "editor/editor_node.h"
-#include "editor_scale.h"
+#include "editor/editor_scale.h"
#include "modules/visual_script/visual_script.h"
#include "modules/visual_script/visual_script_builtin_funcs.h"
#include "modules/visual_script/visual_script_flow_control.h"
@@ -200,13 +200,10 @@ void VisualScriptPropertySelector::_update_search() {
Object *obj = ObjectDB::get_instance(script);
if (Object::cast_to<Script>(obj)) {
- methods.push_back(MethodInfo("*Script Methods"));
Object::cast_to<Script>(obj)->get_script_method_list(&methods);
-
- } else {
- methods.push_back(MethodInfo("*" + String(E->get())));
- ClassDB::get_method_list(E->get(), &methods, true, true);
}
+
+ ClassDB::get_method_list(E->get(), &methods, true, true);
}
}
for (List<MethodInfo>::Element *M = methods.front(); M; M = M->next()) {
@@ -274,6 +271,7 @@ void VisualScriptPropertySelector::_update_search() {
get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box);
get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box);
get_visual_node_names("functions/by_type/" + Variant::get_type_name(type), Set<String>(), found, root, search_box);
+ get_visual_node_names("functions/deconstruct/" + Variant::get_type_name(type), Set<String>(), found, root, search_box);
get_visual_node_names("operators/compare/", Set<String>(), found, root, search_box);
if (type == Variant::INT) {
get_visual_node_names("operators/bitwise/", Set<String>(), found, root, search_box);
@@ -327,7 +325,7 @@ void VisualScriptPropertySelector::create_visualscript_item(const String &name,
}
}
-void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box) {
+void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box) {
Map<String, TreeItem *> path_cache;
List<String> fnodes;
@@ -338,37 +336,59 @@ void VisualScriptPropertySelector::get_visual_node_names(const String &root_filt
continue;
}
Vector<String> path = E->get().split("/");
- bool is_filter = false;
- for (Set<String>::Element *F = filter.front(); F; F = F->next()) {
- if (path.size() >= 2 && path[1].findn(F->get()) != -1) {
- is_filter = true;
+
+ // check if the name has the filter
+ bool in_filter = false;
+ Vector<String> tx_filters = search_box->get_text().split(" ");
+ for (int i = 0; i < tx_filters.size(); i++) {
+ if (tx_filters[i] == "") {
+ in_filter = true;
+ } else {
+ in_filter = false;
+ }
+ if (E->get().findn(tx_filters[i]) != -1) {
+ in_filter = true;
break;
}
}
- if (is_filter) {
+ if (!in_filter) {
continue;
}
- if (search_box->get_text() != String() && E->get().findn(search_box->get_text()) == -1) {
+ bool in_modifier = false | p_modifiers.empty();
+ for (Set<String>::Element *F = p_modifiers.front(); F && in_modifier; F = F->next()) {
+ if (E->get().findn(F->get()) != -1)
+ in_modifier = true;
+ }
+ if (!in_modifier) {
continue;
}
+
TreeItem *item = search_options->create_item(root);
- VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(*VisualScriptLanguage::singleton->create_node_from_name(E->get()));
+ Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(E->get());
+ Ref<VisualScriptOperator> vnode_operator = vnode;
String type_name;
- if (vnode_operator != NULL) {
+ if (vnode_operator.is_valid()) {
String type;
if (path.size() >= 2) {
type = path[1];
}
type_name = type.capitalize() + " ";
}
- VisualScriptFunctionCall *vnode_function_call = Object::cast_to<VisualScriptFunctionCall>(*VisualScriptLanguage::singleton->create_node_from_name(E->get()));
- if (vnode_function_call != NULL) {
+ Ref<VisualScriptFunctionCall> vnode_function_call = vnode;
+ if (vnode_function_call.is_valid()) {
String basic_type = Variant::get_type_name(vnode_function_call->get_basic_type());
type_name = basic_type.capitalize() + " ";
}
-
- Vector<String> desc = path[path.size() - 1].replace("(", "( ").replace(")", " )").replace(",", ", ").split(" ");
+ Ref<VisualScriptConstructor> vnode_constructor = vnode;
+ if (vnode_constructor.is_valid()) {
+ type_name = "Construct ";
+ }
+ Ref<VisualScriptDeconstruct> vnode_deconstruct = vnode;
+ if (vnode_deconstruct.is_valid()) {
+ type_name = "Deconstruct ";
+ }
+ Vector<String> desc = path[path.size() - 1].replace("(", " ").replace(")", " ").replace(",", " ").split(" ");
for (int i = 0; i < desc.size(); i++) {
desc.write[i] = desc[i].capitalize();
if (desc[i].ends_with(",")) {
@@ -504,7 +524,7 @@ void VisualScriptPropertySelector::_notification(int p_what) {
}
}
-void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, const bool p_virtuals_only, const bool p_connecting) {
+void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, const bool p_virtuals_only, const bool p_connecting, bool clear_text) {
base_type = p_base;
selected = p_current;
@@ -515,7 +535,10 @@ void VisualScriptPropertySelector::select_method_from_base_type(const String &p_
virtuals_only = p_virtuals_only;
show_window(.5f);
- search_box->set_text("");
+ if (clear_text)
+ search_box->set_text("");
+ else
+ search_box->select_all();
search_box->grab_focus();
connecting = p_connecting;
@@ -526,7 +549,7 @@ void VisualScriptPropertySelector::set_type_filter(const Vector<Variant::Type> &
type_filter = p_type_filter;
}
-void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only, bool p_seq_connect, const bool p_connecting) {
+void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only, bool p_seq_connect, const bool p_connecting, bool clear_text) {
base_type = p_base;
selected = p_current;
@@ -538,7 +561,10 @@ void VisualScriptPropertySelector::select_from_base_type(const String &p_base, c
virtuals_only = p_virtuals_only;
show_window(.5f);
- search_box->set_text("");
+ if (clear_text)
+ search_box->set_text("");
+ else
+ search_box->select_all();
search_box->grab_focus();
seq_connect = p_seq_connect;
connecting = p_connecting;
@@ -546,7 +572,7 @@ void VisualScriptPropertySelector::select_from_base_type(const String &p_base, c
_update_search();
}
-void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current, const bool p_connecting) {
+void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current, const bool p_connecting, bool clear_text) {
ERR_FAIL_COND(p_script.is_null());
base_type = p_script->get_instance_base_type();
@@ -559,7 +585,10 @@ void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_scrip
virtuals_only = false;
show_window(.5f);
- search_box->set_text("");
+ if (clear_text)
+ search_box->set_text("");
+ else
+ search_box->select_all();
search_box->grab_focus();
seq_connect = false;
connecting = p_connecting;
@@ -567,7 +596,7 @@ void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_scrip
_update_search();
}
-void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current, const bool p_connecting) {
+void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current, const bool p_connecting, bool clear_text) {
ERR_FAIL_COND(p_type == Variant::NIL);
base_type = "";
selected = p_current;
@@ -579,7 +608,10 @@ void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type,
virtuals_only = false;
show_window(.5f);
- search_box->set_text("");
+ if (clear_text)
+ search_box->set_text("");
+ else
+ search_box->select_all();
search_box->grab_focus();
seq_connect = false;
connecting = p_connecting;
@@ -587,7 +619,7 @@ void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type,
_update_search();
}
-void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current, const bool p_connecting) {
+void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current, const bool p_connecting, bool clear_text) {
base_type = p_type;
selected = p_current;
type = Variant::NIL;
@@ -598,7 +630,10 @@ void VisualScriptPropertySelector::select_from_action(const String &p_type, cons
virtuals_only = false;
show_window(.5f);
- search_box->set_text("");
+ if (clear_text)
+ search_box->set_text("");
+ else
+ search_box->select_all();
search_box->grab_focus();
seq_connect = true;
connecting = p_connecting;
@@ -606,8 +641,8 @@ void VisualScriptPropertySelector::select_from_action(const String &p_type, cons
_update_search();
}
-void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current, const bool p_connecting) {
- base_type = "";
+void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current, const bool p_connecting, const String &p_basetype, bool clear_text) {
+ base_type = p_basetype;
selected = p_current;
type = Variant::NIL;
script = 0;
@@ -617,7 +652,10 @@ void VisualScriptPropertySelector::select_from_instance(Object *p_instance, cons
virtuals_only = false;
show_window(.5f);
- search_box->set_text("");
+ if (clear_text)
+ search_box->set_text("");
+ else
+ search_box->select_all();
search_box->grab_focus();
seq_connect = false;
connecting = p_connecting;
@@ -625,7 +663,7 @@ void VisualScriptPropertySelector::select_from_instance(Object *p_instance, cons
_update_search();
}
-void VisualScriptPropertySelector::select_from_visual_script(const String &p_base, const bool p_connecting) {
+void VisualScriptPropertySelector::select_from_visual_script(const String &p_base, const bool p_connecting, bool clear_text) {
base_type = p_base;
selected = "";
type = Variant::NIL;
@@ -635,7 +673,10 @@ void VisualScriptPropertySelector::select_from_visual_script(const String &p_bas
instance = NULL;
virtuals_only = false;
show_window(.5f);
- search_box->set_text("");
+ if (clear_text)
+ search_box->set_text("");
+ else
+ search_box->select_all();
search_box->grab_focus();
connecting = p_connecting;
@@ -646,7 +687,7 @@ void VisualScriptPropertySelector::show_window(float p_screen_ratio) {
Rect2 rect;
Point2 window_size = get_viewport_rect().size;
rect.size = (window_size * p_screen_ratio).floor();
- rect.size.x = rect.size.x / 1.25f;
+ rect.size.x = rect.size.x / 2.2f;
rect.position = ((window_size - rect.size) / 2.0f).floor();
popup(rect);
}
diff --git a/modules/visual_script/visual_script_property_selector.h b/modules/visual_script/visual_script_property_selector.h
index 6235e4ba1d..3a7c8de6a2 100644
--- a/modules/visual_script/visual_script_property_selector.h
+++ b/modules/visual_script/visual_script_property_selector.h
@@ -31,8 +31,8 @@
#ifndef VISUALSCRIPT_PROPERTYSELECTOR_H
#define VISUALSCRIPT_PROPERTYSELECTOR_H
+#include "editor/editor_help.h"
#include "editor/property_editor.h"
-#include "editor_help.h"
#include "scene/gui/rich_text_label.h"
class VisualScriptPropertySelector : public ConfirmationDialog {
@@ -45,7 +45,7 @@ class VisualScriptPropertySelector : public ConfirmationDialog {
void create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text);
- void get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box);
+ void get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box);
void _sbox_input(const Ref<InputEvent> &p_ie);
@@ -74,13 +74,13 @@ protected:
static void _bind_methods();
public:
- void select_method_from_base_type(const String &p_base, const String &p_current = "", const bool p_virtuals_only = false, const bool p_connecting = true);
- void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false, const bool p_connecting = true);
- void select_from_script(const Ref<Script> &p_script, const String &p_current = "", const bool p_connecting = true);
- void select_from_basic_type(Variant::Type p_type, const String &p_current = "", const bool p_connecting = true);
- void select_from_action(const String &p_type, const String &p_current = "", const bool p_connecting = true);
- void select_from_instance(Object *p_instance, const String &p_current = "", const bool p_connecting = true);
- void select_from_visual_script(const String &p_base, const bool p_connecting = true);
+ void select_method_from_base_type(const String &p_base, const String &p_current = "", const bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true);
+ void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false, const bool p_connecting = true, bool clear_text = true);
+ void select_from_script(const Ref<Script> &p_script, const String &p_current = "", const bool p_connecting = true, bool clear_text = true);
+ void select_from_basic_type(Variant::Type p_type, const String &p_current = "", const bool p_connecting = true, bool clear_text = true);
+ void select_from_action(const String &p_type, const String &p_current = "", const bool p_connecting = true, bool clear_text = true);
+ void select_from_instance(Object *p_instance, const String &p_current = "", const bool p_connecting = true, const String &p_basetype = "", bool clear_text = true);
+ void select_from_visual_script(const String &p_base, const bool p_connecting = true, bool clear_text = true);
void show_window(float p_screen_ratio);
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index 2f56e778b9..c330af60a4 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -242,10 +242,7 @@ Error AudioStreamPlaybackOGGVorbis::set_file(const String &p_file) {
stream_valid = false;
Error err;
f = FileAccess::open(file, FileAccess::READ, &err);
-
- if (err) {
- ERR_FAIL_COND_V(err, err);
- }
+ ERR_FAIL_COND_V_MSG(err, err, "Cannot open file '" + p_file + "'.");
int errv = ov_open_callbacks(f, &vf, NULL, 0, _ov_callbacks);
switch (errv) {
@@ -294,9 +291,7 @@ Error AudioStreamPlaybackOGGVorbis::_load_stream() {
Error err;
f = FileAccess::open(file, FileAccess::READ, &err);
- if (err) {
- ERR_FAIL_COND_V(err, err);
- }
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + file + "'.");
int errv = ov_open_callbacks(f, &vf, NULL, 0, _ov_callbacks);
switch (errv) {
diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp
index fa3602ad27..4ce0db3746 100644
--- a/modules/webm/video_stream_webm.cpp
+++ b/modules/webm/video_stream_webm.cpp
@@ -230,7 +230,7 @@ void VideoStreamPlaybackWebm::set_audio_track(int p_idx) {
audio_track = p_idx;
}
-Ref<Texture> VideoStreamPlaybackWebm::get_texture() {
+Ref<Texture> VideoStreamPlaybackWebm::get_texture() const {
return texture;
}
diff --git a/modules/webm/video_stream_webm.h b/modules/webm/video_stream_webm.h
index ddcbb1eb08..4f79d46cce 100644
--- a/modules/webm/video_stream_webm.h
+++ b/modules/webm/video_stream_webm.h
@@ -90,7 +90,7 @@ public:
virtual void set_audio_track(int p_idx);
- virtual Ref<Texture> get_texture();
+ virtual Ref<Texture> get_texture() const;
virtual void update(float p_delta);
virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata);
diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayer.xml
index 2b0622fffa..605b1ef082 100644
--- a/modules/webrtc/doc_classes/WebRTCMultiplayer.xml
+++ b/modules/webrtc/doc_classes/WebRTCMultiplayer.xml
@@ -80,6 +80,10 @@
</description>
</method>
</methods>
+ <members>
+ <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" />
+ <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="NetworkedMultiplayerPeer.TransferMode" default="2" />
+ </members>
<constants>
</constants>
</class>
diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml
index c3baf9de83..705e3485f5 100644
--- a/modules/websocket/doc_classes/WebSocketClient.xml
+++ b/modules/websocket/doc_classes/WebSocketClient.xml
@@ -21,10 +21,13 @@
</argument>
<argument index="2" name="gd_mp_api" type="bool" default="false">
</argument>
+ <argument index="3" name="custom_headers" type="PoolStringArray" default="PoolStringArray( )">
+ </argument>
<description>
Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested.
If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted.
If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]).
+ You can optionally pass a list of [code]custom_headers[/code] to be added to the handshake HTTP request (not supported in HTML5 platform).
</description>
</method>
<method name="disconnect_from_host">
@@ -38,8 +41,25 @@
Disconnects this client from the connected host. See [method WebSocketPeer.close] for more information.
</description>
</method>
+ <method name="get_connected_host" qualifiers="const">
+ <return type="String">
+ </return>
+ <description>
+ Return the IP address of the currently connected host.
+ </description>
+ </method>
+ <method name="get_connected_port" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ Return the IP port of the currently connected host.
+ </description>
+ </method>
</methods>
<members>
+ <member name="trusted_ssl_certificate" type="X509Certificate" setter="set_trusted_ssl_certificate" getter="get_trusted_ssl_certificate">
+ If specified, this [X509Certificate] will be the only one accepted when connecting to an SSL host. Any other certificate provided by the server will be regarded as invalid.
+ </member>
<member name="verify_ssl" type="bool" setter="set_verify_ssl_enabled" getter="is_verify_ssl_enabled">
If [code]true[/code], SSL certificate verification is enabled.
[b]Note:[/b] You must specify the certificates to be used in the Project Settings for it to work when exported.
diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
index b80a28e648..7070cfbdab 100644
--- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
@@ -37,6 +37,10 @@
</description>
</method>
</methods>
+ <members>
+ <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" />
+ <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="NetworkedMultiplayerPeer.TransferMode" default="2" />
+ </members>
<signals>
<signal name="peer_packet">
<argument index="0" name="peer_source" type="int">
diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml
index 63318e5874..86f2dae64f 100644
--- a/modules/websocket/doc_classes/WebSocketServer.xml
+++ b/modules/websocket/doc_classes/WebSocketServer.xml
@@ -82,6 +82,17 @@
</description>
</method>
</methods>
+ <members>
+ <member name="ca_chain" type="X509Certificate" setter="set_ca_chain" getter="get_ca_chain">
+ When using SSL (see [member private_key] and [member ssl_certificate]), you can set this to a valid [X509Certificate] to be provided as additional CA chain information during the SSL handshake.
+ </member>
+ <member name="private_key" type="CryptoKey" setter="set_private_key" getter="get_private_key">
+ When set to a valid [CryptoKey] (along with [member ssl_certificate]) will cause the server to require SSL instead of regular TCP (i.e. the [code]wss://[/code] protocol).
+ </member>
+ <member name="ssl_certificate" type="X509Certificate" setter="set_ssl_certificate" getter="get_ssl_certificate">
+ When set to a valid [X509Certificate] (along with [member private_key]) will cause the server to require SSL instead of regular TCP (i.e. the [code]wss://[/code] protocol).
+ </member>
+ </members>
<signals>
<signal name="client_close_request">
<argument index="0" name="id" type="int">
diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp
index 409cc9f699..983db60d5e 100644
--- a/modules/websocket/emws_client.cpp
+++ b/modules/websocket/emws_client.cpp
@@ -64,13 +64,26 @@ EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int
}
}
-Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) {
+Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) {
+
+ String proto_string;
+ for (int i = 0; i < p_protocols.size(); i++) {
+ if (i != 0)
+ proto_string += ",";
+ proto_string += p_protocols[i];
+ }
- String proto_string = p_protocols.join(",");
String str = "ws://";
- if (p_ssl)
+ if (p_custom_headers.size()) {
+ WARN_PRINT_ONCE("Custom headers are not supported in in HTML5 platform.");
+ }
+ if (p_ssl) {
str = "wss://";
+ if (ssl_cert.is_valid()) {
+ WARN_PRINT_ONCE("Custom SSL certificate is not supported in HTML5 platform.");
+ }
+ }
str += p_host + ":" + itos(p_port) + p_path;
_is_connecting = true;
@@ -193,12 +206,12 @@ void EMWSClient::disconnect_from_host(int p_code, String p_reason) {
IP_Address EMWSClient::get_connected_host() const {
- return IP_Address();
+ ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export.");
};
uint16_t EMWSClient::get_connected_port() const {
- return 1025;
+ ERR_FAIL_V_MSG(0, "Not supported in HTML5 export.");
};
int EMWSClient::get_max_packet_size() const {
diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h
index 1811d05eea..67705891b2 100644
--- a/modules/websocket/emws_client.h
+++ b/modules/websocket/emws_client.h
@@ -50,7 +50,7 @@ public:
bool _is_connecting;
Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets);
- Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>());
+ Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>());
Ref<WebSocketPeer> get_peer(int p_peer_id) const;
void disconnect_from_host(int p_code = 1000, String p_reason = "");
IP_Address get_connected_host() const;
diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp
index c4bb459ad0..9a6a30d613 100644
--- a/modules/websocket/emws_server.cpp
+++ b/modules/websocket/emws_server.cpp
@@ -33,7 +33,7 @@
#include "emws_server.h"
#include "core/os/os.h"
-Error EMWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) {
+Error EMWSServer::listen(int p_port, Vector<String> p_protocols, bool gd_mp_api) {
return FAILED;
}
diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h
index a5e5b4090e..e8da8c26b4 100644
--- a/modules/websocket/emws_server.h
+++ b/modules/websocket/emws_server.h
@@ -43,7 +43,7 @@ class EMWSServer : public WebSocketServer {
public:
Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets);
- Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false);
+ Error listen(int p_port, Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false);
void stop();
bool is_listening() const;
bool has_peer(int p_id) const;
diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp
index 4ff5404c61..8bbd5aa37f 100644
--- a/modules/websocket/websocket_client.cpp
+++ b/modules/websocket/websocket_client.cpp
@@ -40,7 +40,7 @@ WebSocketClient::WebSocketClient() {
WebSocketClient::~WebSocketClient() {
}
-Error WebSocketClient::connect_to_url(String p_url, PoolVector<String> p_protocols, bool gd_mp_api) {
+Error WebSocketClient::connect_to_url(String p_url, const Vector<String> p_protocols, bool gd_mp_api, const Vector<String> p_custom_headers) {
_is_multiplayer = gd_mp_api;
String host = p_url;
@@ -72,7 +72,7 @@ Error WebSocketClient::connect_to_url(String p_url, PoolVector<String> p_protoco
host = host.substr(0, p_len);
}
- return connect_to_host(host, path, port, ssl, p_protocols);
+ return connect_to_host(host, path, port, ssl, p_protocols, p_custom_headers);
}
void WebSocketClient::set_verify_ssl_enabled(bool p_verify_ssl) {
@@ -85,6 +85,17 @@ bool WebSocketClient::is_verify_ssl_enabled() const {
return verify_ssl;
}
+Ref<X509Certificate> WebSocketClient::get_trusted_ssl_certificate() const {
+
+ return ssl_cert;
+}
+
+void WebSocketClient::set_trusted_ssl_certificate(Ref<X509Certificate> p_cert) {
+
+ ERR_FAIL_COND(get_connection_status() != CONNECTION_DISCONNECTED);
+ ssl_cert = p_cert;
+}
+
bool WebSocketClient::is_server() const {
return false;
@@ -132,13 +143,20 @@ void WebSocketClient::_on_error() {
}
void WebSocketClient::_bind_methods() {
- ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api"), &WebSocketClient::connect_to_url, DEFVAL(PoolVector<String>()), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api", "custom_headers"), &WebSocketClient::connect_to_url, DEFVAL(Vector<String>()), DEFVAL(false), DEFVAL(Vector<String>()));
ClassDB::bind_method(D_METHOD("disconnect_from_host", "code", "reason"), &WebSocketClient::disconnect_from_host, DEFVAL(1000), DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_connected_host"), &WebSocketClient::get_connected_host);
+ ClassDB::bind_method(D_METHOD("get_connected_port"), &WebSocketClient::get_connected_port);
ClassDB::bind_method(D_METHOD("set_verify_ssl_enabled", "enabled"), &WebSocketClient::set_verify_ssl_enabled);
ClassDB::bind_method(D_METHOD("is_verify_ssl_enabled"), &WebSocketClient::is_verify_ssl_enabled);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "verify_ssl", PROPERTY_HINT_NONE, "", 0), "set_verify_ssl_enabled", "is_verify_ssl_enabled");
+ ClassDB::bind_method(D_METHOD("get_trusted_ssl_certificate"), &WebSocketClient::get_trusted_ssl_certificate);
+ ClassDB::bind_method(D_METHOD("set_trusted_ssl_certificate"), &WebSocketClient::set_trusted_ssl_certificate);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trusted_ssl_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_trusted_ssl_certificate", "get_trusted_ssl_certificate");
+
ADD_SIGNAL(MethodInfo("data_received"));
ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol")));
ADD_SIGNAL(MethodInfo("server_close_request", PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason")));
diff --git a/modules/websocket/websocket_client.h b/modules/websocket/websocket_client.h
index 7ddb9468a5..08fd6798fe 100644
--- a/modules/websocket/websocket_client.h
+++ b/modules/websocket/websocket_client.h
@@ -31,6 +31,7 @@
#ifndef WEBSOCKET_CLIENT_H
#define WEBSOCKET_CLIENT_H
+#include "core/crypto/crypto.h"
#include "core/error_list.h"
#include "websocket_multiplayer_peer.h"
#include "websocket_peer.h"
@@ -43,17 +44,20 @@ class WebSocketClient : public WebSocketMultiplayerPeer {
protected:
Ref<WebSocketPeer> _peer;
bool verify_ssl;
+ Ref<X509Certificate> ssl_cert;
static void _bind_methods();
public:
- Error connect_to_url(String p_url, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false);
+ Error connect_to_url(String p_url, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false, const Vector<String> p_custom_headers = Vector<String>());
void set_verify_ssl_enabled(bool p_verify_ssl);
bool is_verify_ssl_enabled() const;
+ Ref<X509Certificate> get_trusted_ssl_certificate() const;
+ void set_trusted_ssl_certificate(Ref<X509Certificate> p_cert);
virtual void poll() = 0;
- virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()) = 0;
+ virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()) = 0;
virtual void disconnect_from_host(int p_code = 1000, String p_reason = "") = 0;
virtual IP_Address get_connected_host() const = 0;
virtual uint16_t get_connected_port() const = 0;
diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp
index ef5f6f5c20..c7414075ed 100644
--- a/modules/websocket/websocket_server.cpp
+++ b/modules/websocket/websocket_server.cpp
@@ -42,19 +42,58 @@ WebSocketServer::~WebSocketServer() {
void WebSocketServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_listening"), &WebSocketServer::is_listening);
- ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(PoolVector<String>()), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(Vector<String>()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("stop"), &WebSocketServer::stop);
ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer);
ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketServer::get_peer_address);
ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketServer::get_peer_port);
ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketServer::disconnect_peer, DEFVAL(1000), DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_private_key"), &WebSocketServer::get_private_key);
+ ClassDB::bind_method(D_METHOD("set_private_key"), &WebSocketServer::set_private_key);
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "private_key", PROPERTY_HINT_RESOURCE_TYPE, "CryptoKey", 0), "set_private_key", "get_private_key");
+
+ ClassDB::bind_method(D_METHOD("get_ssl_certificate"), &WebSocketServer::get_ssl_certificate);
+ ClassDB::bind_method(D_METHOD("set_ssl_certificate"), &WebSocketServer::set_ssl_certificate);
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ssl_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_ssl_certificate", "get_ssl_certificate");
+
+ ClassDB::bind_method(D_METHOD("get_ca_chain"), &WebSocketServer::get_ca_chain);
+ ClassDB::bind_method(D_METHOD("set_ca_chain"), &WebSocketServer::set_ca_chain);
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ca_chain", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_ca_chain", "get_ca_chain");
+
ADD_SIGNAL(MethodInfo("client_close_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason")));
ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::BOOL, "was_clean_close")));
ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol")));
ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id")));
}
+Ref<CryptoKey> WebSocketServer::get_private_key() const {
+ return private_key;
+}
+
+void WebSocketServer::set_private_key(Ref<CryptoKey> p_key) {
+ ERR_FAIL_COND(is_listening());
+ private_key = p_key;
+}
+
+Ref<X509Certificate> WebSocketServer::get_ssl_certificate() const {
+ return ssl_cert;
+}
+
+void WebSocketServer::set_ssl_certificate(Ref<X509Certificate> p_cert) {
+ ERR_FAIL_COND(is_listening());
+ ssl_cert = p_cert;
+}
+
+Ref<X509Certificate> WebSocketServer::get_ca_chain() const {
+ return ca_chain;
+}
+
+void WebSocketServer::set_ca_chain(Ref<X509Certificate> p_ca_chain) {
+ ERR_FAIL_COND(is_listening());
+ ca_chain = p_ca_chain;
+}
+
NetworkedMultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const {
if (is_listening())
return CONNECTION_CONNECTED;
diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h
index 83c0c10419..0b39f94473 100644
--- a/modules/websocket/websocket_server.h
+++ b/modules/websocket/websocket_server.h
@@ -31,6 +31,7 @@
#ifndef WEBSOCKET_H
#define WEBSOCKET_H
+#include "core/crypto/crypto.h"
#include "core/reference.h"
#include "websocket_multiplayer_peer.h"
#include "websocket_peer.h"
@@ -43,9 +44,13 @@ class WebSocketServer : public WebSocketMultiplayerPeer {
protected:
static void _bind_methods();
+ Ref<CryptoKey> private_key;
+ Ref<X509Certificate> ssl_cert;
+ Ref<X509Certificate> ca_chain;
+
public:
virtual void poll() = 0;
- virtual Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false) = 0;
+ virtual Error listen(int p_port, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false) = 0;
virtual void stop() = 0;
virtual bool is_listening() const = 0;
virtual bool has_peer(int p_id) const = 0;
@@ -62,6 +67,15 @@ public:
void _on_disconnect(int32_t p_peer_id, bool p_was_clean);
void _on_close_request(int32_t p_peer_id, int p_code, String p_reason);
+ Ref<CryptoKey> get_private_key() const;
+ void set_private_key(Ref<CryptoKey> p_key);
+
+ Ref<X509Certificate> get_ssl_certificate() const;
+ void set_ssl_certificate(Ref<X509Certificate> p_cert);
+
+ Ref<X509Certificate> get_ca_chain() const;
+ void set_ca_chain(Ref<X509Certificate> p_ca_chain);
+
virtual Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) = 0;
WebSocketServer();
diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp
index 0006a057e0..ad70c9c0e1 100644
--- a/modules/websocket/wsl_client.cpp
+++ b/modules/websocket/wsl_client.cpp
@@ -86,6 +86,7 @@ void WSLClient::_do_handshake() {
WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData);
data->obj = this;
data->conn = _connection;
+ data->tcp = _tcp;
data->is_server = false;
data->id = 1;
_peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size);
@@ -151,7 +152,7 @@ bool WSLClient::_verify_headers(String &r_protocol) {
return true;
}
-Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) {
+Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) {
ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE);
@@ -180,7 +181,12 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
_connection = _tcp;
_use_ssl = p_ssl;
_host = p_host;
- _protocols = p_protocols;
+ // Strip edges from protocols.
+ _protocols.resize(p_protocols.size());
+ String *pw = _protocols.ptrw();
+ for (int i = 0; i < p_protocols.size(); i++) {
+ pw[i] = p_protocols[i].strip_edges();
+ }
_key = WSLPeer::generate_key();
// TODO custom extra headers (allow overriding this too?)
@@ -199,6 +205,9 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
}
request += "\r\n";
}
+ for (int i = 0; i < p_custom_headers.size(); i++) {
+ request += p_custom_headers[i] + "\r\n";
+ }
request += "\r\n";
_request = request.utf8();
@@ -236,7 +245,7 @@ void WSLClient::poll() {
ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create());
ERR_FAIL_COND_MSG(ssl.is_null(), "SSL is not available in this build.");
ssl->set_blocking_handshake_enabled(false);
- if (ssl->connect_to_stream(_tcp, verify_ssl, _host) != OK) {
+ if (ssl->connect_to_stream(_tcp, verify_ssl, _host, ssl_cert) != OK) {
disconnect_from_host();
_on_error();
return;
@@ -293,7 +302,7 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) {
_key = "";
_host = "";
- _protocols.resize(0);
+ _protocols.clear();
_use_ssl = false;
_request = "";
@@ -305,12 +314,14 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) {
IP_Address WSLClient::get_connected_host() const {
- return IP_Address();
+ ERR_FAIL_COND_V(!_peer->is_connected_to_host(), IP_Address());
+ return _peer->get_connected_host();
}
uint16_t WSLClient::get_connected_port() const {
- return 1025;
+ ERR_FAIL_COND_V(!_peer->is_connected_to_host(), 0);
+ return _peer->get_connected_port();
}
Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) {
diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h
index 57dfd635b7..870be94a87 100644
--- a/modules/websocket/wsl_client.h
+++ b/modules/websocket/wsl_client.h
@@ -64,7 +64,7 @@ private:
String _key;
String _host;
- PoolVector<String> _protocols;
+ Vector<String> _protocols;
bool _use_ssl;
void _do_handshake();
@@ -72,7 +72,7 @@ private:
public:
Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets);
- Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>());
+ Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>());
int get_max_packet_size() const;
Ref<WebSocketPeer> get_peer(int p_peer_id) const;
void disconnect_from_host(int p_code = 1000, String p_reason = "");
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index 74fb901232..32beccde5d 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -208,7 +208,6 @@ void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigne
_data = p_data;
_data->peer = this;
_data->valid = true;
- _connection = Ref<StreamPeer>(_data->conn);
if (_data->is_server)
wslay_event_context_server_init(&(_data->ctx), &wsl_callbacks, _data);
@@ -302,18 +301,16 @@ void WSLPeer::close(int p_code, String p_reason) {
IP_Address WSLPeer::get_connected_host() const {
- ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address());
+ ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), IP_Address());
- IP_Address ip;
- return ip;
+ return _data->tcp->get_connected_host();
}
uint16_t WSLPeer::get_connected_port() const {
- ERR_FAIL_COND_V(!is_connected_to_host(), 0);
+ ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), 0);
- uint16_t port = 0;
- return port;
+ return _data->tcp->get_connected_port();
}
void WSLPeer::invalidate() {
diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h
index d51b304fe1..01ad250468 100644
--- a/modules/websocket/wsl_peer.h
+++ b/modules/websocket/wsl_peer.h
@@ -35,6 +35,7 @@
#include "core/error_list.h"
#include "core/io/packet_peer.h"
+#include "core/io/stream_peer_tcp.h"
#include "core/ring_buffer.h"
#include "packet_buffer.h"
#include "websocket_peer.h"
@@ -55,6 +56,7 @@ public:
void *obj;
void *peer;
Ref<StreamPeer> conn;
+ Ref<StreamPeerTCP> tcp;
int id;
wslay_event_context_ptr ctx;
@@ -77,7 +79,6 @@ private:
static bool _wsl_poll(struct PeerData *p_data);
static void _wsl_destroy(struct PeerData **p_data);
- Ref<StreamPeer> _connection;
struct PeerData *_data;
uint8_t _is_string;
// Our packet info is just a boolean (is_string), using uint8_t for it.
diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp
index efb526eed1..2181775b99 100644
--- a/modules/websocket/wsl_server.cpp
+++ b/modules/websocket/wsl_server.cpp
@@ -35,6 +35,7 @@
#include "core/project_settings.h"
WSLServer::PendingPeer::PendingPeer() {
+ use_ssl = false;
time = 0;
has_request = false;
response_sent = 0;
@@ -42,7 +43,7 @@ WSLServer::PendingPeer::PendingPeer() {
memset(req_buf, 0, sizeof(req_buf));
}
-bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) {
+bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) {
Vector<String> psa = String((char *)req_buf).split("\r\n");
int len = psa.size();
ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4.");
@@ -79,11 +80,12 @@ bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) {
if (headers.has("sec-websocket-protocol")) {
Vector<String> protos = headers["sec-websocket-protocol"].split(",");
for (int i = 0; i < protos.size(); i++) {
+ String proto = protos[i].strip_edges();
// Check if we have the given protocol
for (int j = 0; j < p_protocols.size(); j++) {
- if (protos[i] != p_protocols[j])
+ if (proto != p_protocols[j])
continue;
- protocol = protos[i];
+ protocol = proto;
break;
}
// Found a protocol
@@ -97,9 +99,19 @@ bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) {
return true;
}
-Error WSLServer::PendingPeer::do_handshake(PoolStringArray p_protocols) {
+Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols) {
if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT)
return ERR_TIMEOUT;
+ if (use_ssl) {
+ Ref<StreamPeerSSL> ssl = static_cast<Ref<StreamPeerSSL> >(connection);
+ if (ssl.is_null())
+ return FAILED;
+ ssl->poll();
+ if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING)
+ return ERR_BUSY;
+ else if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED)
+ return FAILED;
+ }
if (!has_request) {
int read = 0;
while (true) {
@@ -143,11 +155,16 @@ Error WSLServer::PendingPeer::do_handshake(PoolStringArray p_protocols) {
return OK;
}
-Error WSLServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) {
+Error WSLServer::listen(int p_port, const Vector<String> p_protocols, bool gd_mp_api) {
ERR_FAIL_COND_V(is_listening(), ERR_ALREADY_IN_USE);
_is_multiplayer = gd_mp_api;
- _protocols = p_protocols;
+ // Strip edges from protocols.
+ _protocols.resize(p_protocols.size());
+ String *pw = _protocols.ptrw();
+ for (int i = 0; i < p_protocols.size(); i++) {
+ pw[i] = p_protocols[i].strip_edges();
+ }
_server->listen(p_port);
return OK;
@@ -185,6 +202,7 @@ void WSLServer::poll() {
WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData);
data->obj = this;
data->conn = ppeer->connection;
+ data->tcp = ppeer->tcp;
data->is_server = true;
data->id = id;
@@ -204,12 +222,21 @@ void WSLServer::poll() {
return;
while (_server->is_connection_available()) {
- Ref<StreamPeer> conn = _server->take_connection();
+ Ref<StreamPeerTCP> conn = _server->take_connection();
if (is_refusing_new_connections())
continue; // Conn will go out-of-scope and be closed.
Ref<PendingPeer> peer = memnew(PendingPeer);
- peer->connection = conn;
+ if (private_key.is_valid() && ssl_cert.is_valid()) {
+ Ref<StreamPeerSSL> ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create());
+ ssl->set_blocking_handshake_enabled(false);
+ ssl->accept_stream(conn, private_key, ssl_cert, ca_chain);
+ peer->connection = ssl;
+ peer->use_ssl = true;
+ } else {
+ peer->connection = conn;
+ }
+ peer->tcp = conn;
peer->time = OS::get_singleton()->get_ticks_msec();
_pending.push_back(peer);
}
@@ -231,6 +258,7 @@ void WSLServer::stop() {
}
_pending.clear();
_peer_map.clear();
+ _protocols.clear();
}
bool WSLServer::has_peer(int p_id) const {
diff --git a/modules/websocket/wsl_server.h b/modules/websocket/wsl_server.h
index 2ceb941073..aae563355e 100644
--- a/modules/websocket/wsl_server.h
+++ b/modules/websocket/wsl_server.h
@@ -36,6 +36,7 @@
#include "websocket_server.h"
#include "wsl_peer.h"
+#include "core/io/stream_peer_ssl.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"
@@ -49,10 +50,12 @@ private:
class PendingPeer : public Reference {
private:
- bool _parse_request(const PoolStringArray p_protocols);
+ bool _parse_request(const Vector<String> p_protocols);
public:
+ Ref<StreamPeerTCP> tcp;
Ref<StreamPeer> connection;
+ bool use_ssl;
int time;
uint8_t req_buf[WSL_MAX_HEADER_SIZE];
@@ -65,7 +68,7 @@ private:
PendingPeer();
- Error do_handshake(const PoolStringArray p_protocols);
+ Error do_handshake(const Vector<String> p_protocols);
};
int _in_buf_size;
@@ -75,11 +78,11 @@ private:
List<Ref<PendingPeer> > _pending;
Ref<TCP_Server> _server;
- PoolStringArray _protocols;
+ Vector<String> _protocols;
public:
Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets);
- Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false);
+ Error listen(int p_port, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false);
void stop();
bool is_listening() const;
int get_max_packet_size() const;