diff options
author | George L. Albany <Megacake1234@gmail.com> | 2024-11-15 20:50:25 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-15 20:50:25 +0000 |
commit | 9767837a7ec40697788765e581131cb2cf172567 (patch) | |
tree | d58eaa8daad3e30c8b84a50e70a21f93b05525c5 | |
parent | ac1a49725fc038ae11ef9060fecb2b0f9c6333b2 (diff) | |
parent | 4a5836e5462554a738b502aa8bbde5e4a051eb56 (diff) | |
download | redot-engine-9767837a7ec40697788765e581131cb2cf172567.tar.gz |
Merge pull request #864 from Spartan322/merge/6c05ec3
Merge commit godotengine/godot@6c05ec3
143 files changed, 1787 insertions, 1096 deletions
diff --git a/.github/actions/godot-build/action.yml b/.github/actions/godot-build/action.yml index 93d6f076b7..ebc301dd0f 100644 --- a/.github/actions/godot-build/action.yml +++ b/.github/actions/godot-build/action.yml @@ -18,12 +18,12 @@ inputs: required: false scons-cache: description: The SCons cache path. - default: ${{ github.workspace }}/.scons-cache/ + default: ${{ github.workspace }}/.scons_cache/ scons-cache-limit: description: The SCons cache size limit. # actions/cache has 10 GiB limit, and GitHub runners have a 14 GiB disk. # Limit to 7 GiB to avoid having the extracted cache fill the disk. - default: 7168 + default: 7 runs: using: composite @@ -32,10 +32,8 @@ runs: shell: sh env: SCONSFLAGS: ${{ inputs.sconsflags }} - SCONS_CACHE: ${{ inputs.scons-cache }} - SCONS_CACHE_LIMIT: ${{ inputs.scons-cache-limit }} run: | - echo "Building with flags:" platform=${{ inputs.platform }} target=${{ inputs.target }} tests=${{ inputs.tests }} ${{ env.SCONSFLAGS }} + echo "Building with flags:" platform=${{ inputs.platform }} target=${{ inputs.target }} tests=${{ inputs.tests }} ${{ env.SCONSFLAGS }} "cache_path=${{ inputs.scons-cache }}" cache_limit=${{ inputs.scons-cache-limit }} if [ "${{ inputs.target }}" != "editor" ]; then # Ensure we don't include editor code in export template builds. @@ -49,5 +47,5 @@ runs: export BUILD_NAME="gh" fi - scons platform=${{ inputs.platform }} target=${{ inputs.target }} tests=${{ inputs.tests }} ${{ env.SCONSFLAGS }} + scons platform=${{ inputs.platform }} target=${{ inputs.target }} tests=${{ inputs.tests }} ${{ env.SCONSFLAGS }} "cache_path=${{ inputs.scons-cache }}" cache_limit=${{ inputs.scons-cache-limit }} ls -l bin/ diff --git a/.github/actions/godot-cache-restore/action.yml b/.github/actions/godot-cache-restore/action.yml index 7abec20a28..e2a1b97019 100644 --- a/.github/actions/godot-cache-restore/action.yml +++ b/.github/actions/godot-cache-restore/action.yml @@ -6,7 +6,7 @@ inputs: default: ${{ github.job }} scons-cache: description: The SCons cache path. - default: ${{ github.workspace }}/.scons-cache/ + default: ${{ github.workspace }}/.scons_cache/ runs: using: composite @@ -29,7 +29,6 @@ runs: # 4. A partial match for the same base branch only (not ideal, matches any PR with the same base branch). restore-keys: | - ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }} ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-refs/heads/${{ env.GODOT_BASE_BRANCH }} ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }} diff --git a/.github/actions/godot-cache-save/action.yml b/.github/actions/godot-cache-save/action.yml index df877cec67..42aa836406 100644 --- a/.github/actions/godot-cache-save/action.yml +++ b/.github/actions/godot-cache-save/action.yml @@ -6,7 +6,7 @@ inputs: default: ${{ github.job }} scons-cache: description: The SCons cache path. - default: ${{ github.workspace }}/.scons-cache/ + default: ${{ github.workspace }}/.scons_cache/ runs: using: composite diff --git a/.github/actions/godot-deps/action.yml b/.github/actions/godot-deps/action.yml index eb9bdef1e7..bd9a1f55ed 100644 --- a/.github/actions/godot-deps/action.yml +++ b/.github/actions/godot-deps/action.yml @@ -27,6 +27,5 @@ runs: shell: bash run: | python -c "import sys; print(sys.version)" - python -m pip install wheel python -m pip install scons==${{ inputs.scons-version }} scons --version diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml index dc82a7cb3c..af99a4b035 100644 --- a/.github/workflows/godot_cpp_test.yml +++ b/.github/workflows/godot_cpp_test.yml @@ -52,9 +52,6 @@ jobs: # continue-on-error: true - name: Build godot-cpp test extension - env: # Keep synced with godot-build. - SCONS_CACHE: ${{ github.workspace }}/.scons-cache/ - SCONS_CACHE_LIMIT: 7168 run: scons --directory=./godot-cpp/test target=template_debug dev_build=yes verbose=yes # - name: Save Godot build cache diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index ffd39c866f..b594ec47c9 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -17,8 +17,8 @@ concurrency: jobs: build-linux: - # If unspecified, stay one LTS before latest to increase portability of Linux artifacts. - runs-on: ${{ matrix.os || 'ubuntu-22.04' }} + # Stay one LTS before latest to increase portability of Linux artifacts. + runs-on: ubuntu-22.04 name: ${{ matrix.name }} strategy: fail-fast: false @@ -61,8 +61,6 @@ jobs: artifact: false # Test our oldest supported SCons/Python versions on one arbitrary editor build. legacy-scons: true - # Python 3.6 unavailable on 22.04. - os: ubuntu-20.04 - name: Editor with ThreadSanitizer (target=editor, tests=yes, dev_build=yes, use_tsan=yes, use_llvm=yes, linker=lld) cache-name: linux-editor-thread-sanitizer @@ -132,8 +130,8 @@ jobs: uses: ./.github/actions/godot-deps with: # Sync with Ensure*Version in SConstruct. - python-version: 3.6 - scons-version: 3.1.2 + python-version: 3.8 + scons-version: 4.0 - name: Setup GCC problem matcher uses: ammaraskar/gcc-problem-matcher@master diff --git a/.gitignore b/.gitignore index 32a43b8c63..0dcf79c460 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,9 @@ venv __pycache__/ *.pyc +# Python modules +.*_cache/ + # Documentation doc/_build/ @@ -164,9 +167,6 @@ gmon.out # Kdevelop *.kdev4 -# Mypy -.mypy_cache - # Qt Creator *.config *.creator diff --git a/SConstruct b/SConstruct index c9eb6b2eef..1c1a654b7a 100644 --- a/SConstruct +++ b/SConstruct @@ -1,8 +1,8 @@ #!/usr/bin/env python from misc.utility.scons_hints import * -EnsureSConsVersion(3, 1, 2) -EnsurePythonVersion(3, 6) +EnsureSConsVersion(4, 0) +EnsurePythonVersion(3, 8) # System import atexit @@ -59,7 +59,7 @@ import glsl_builders import methods import scu_builders from methods import print_error, print_warning -from platform_methods import architecture_aliases, architectures +from platform_methods import architecture_aliases, architectures, compatibility_platform_aliases if ARGUMENTS.get("target", "editor") == "editor": _helper_module("editor.editor_builders", "editor/editor_builders.py") @@ -271,6 +271,8 @@ opts.Add(BoolVariable("scu_build", "Use single compilation unit build", False)) opts.Add("scu_limit", "Max includes per SCU file when using scu_build (determines RAM use)", "0") opts.Add(BoolVariable("engine_update_check", "Enable engine update checks in the Project Manager", True)) opts.Add(BoolVariable("steamapi", "Enable minimal SteamAPI integration for usage time tracking (editor only)", False)) +opts.Add("cache_path", "Path to a directory where SCons cache files will be stored. No value disables the cache.", "") +opts.Add("cache_limit", "Max size (in GiB) for the SCons cache. 0 means no limit.", "0") # Thirdparty libraries opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True)) @@ -321,6 +323,9 @@ opts.Add("rcflags", "Custom flags for Windows resource compiler") # in following code (especially platform and custom_modules). opts.Update(env) +# Setup caching logic early to catch everything. +methods.prepare_cache(env) + # Copy custom environment variables if set. if env["import_env_vars"]: for env_var in str(env["import_env_vars"]).split(","): @@ -350,27 +355,18 @@ if env["platform"] == "": if env["platform"] != "": print(f'Automatically detected platform: {env["platform"]}') -if env["platform"] == "osx": - # Deprecated alias kept for compatibility. - print_warning('Platform "osx" has been renamed to "macos" in Redot 4. Building for platform "macos".') - env["platform"] = "macos" - -if env["platform"] == "iphone": - # Deprecated alias kept for compatibility. - print_warning('Platform "iphone" has been renamed to "ios" in Redot 4. Building for platform "ios".') - env["platform"] = "ios" - -if env["platform"] in ["linux", "bsd", "x11"]: - if env["platform"] == "x11": - # Deprecated alias kept for compatibility. - print_warning('Platform "x11" has been renamed to "linuxbsd" in Redot 4. Building for platform "linuxbsd".') - # Alias for convenience. - env["platform"] = "linuxbsd" +# Deprecated aliases kept for compatibility. +if env["platform"] in compatibility_platform_aliases: + alias = env["platform"] + platform = compatibility_platform_aliases[alias] + print_warning( + f'Platform "{alias}" has been renamed to "{platform}" in Redot 4. Building for platform "{platform}".' + ) + env["platform"] = platform -if env["platform"] == "javascript": - # Deprecated alias kept for compatibility. - print_warning('Platform "javascript" has been renamed to "web" in Redot 4. Building for platform "web".') - env["platform"] = "web" +# Alias for convenience. +if env["platform"] in ["linux", "bsd"]: + env["platform"] = "linuxbsd" if env["platform"] not in platform_list: text = "The following platforms are available:\n\t{}\n".format("\n\t".join(platform_list)) @@ -667,40 +663,32 @@ elif methods.using_gcc(env): "to switch to posix threads." ) Exit(255) - if env["debug_paths_relative"] and cc_version_major < 8: - print_warning("GCC < 8 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option.") - env["debug_paths_relative"] = False elif methods.using_clang(env): # Apple LLVM versions differ from upstream LLVM version \o/, compare # in https://en.wikipedia.org/wiki/Xcode#Toolchain_versions - if env["platform"] == "macos" or env["platform"] == "ios": - vanilla = methods.is_vanilla_clang(env) - if vanilla and cc_version_major < 6: - print_error( - "Detected Clang version older than 6, which does not fully support " - "C++17. Supported versions are Clang 6 and later." - ) - Exit(255) - elif not vanilla and cc_version_major < 10: + if methods.is_apple_clang(env): + if cc_version_major < 10: print_error( "Detected Apple Clang version older than 10, which does not fully " "support C++17. Supported versions are Apple Clang 10 and later." ) Exit(255) - if env["debug_paths_relative"] and not vanilla and cc_version_major < 12: + elif env["debug_paths_relative"] and cc_version_major < 12: print_warning( "Apple Clang < 12 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option." ) env["debug_paths_relative"] = False - elif cc_version_major < 6: - print_error( - "Detected Clang version older than 6, which does not fully support " - "C++17. Supported versions are Clang 6 and later." - ) - Exit(255) - if env["debug_paths_relative"] and cc_version_major < 10: - print_warning("Clang < 10 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option.") - env["debug_paths_relative"] = False + else: + if cc_version_major < 6: + print_error( + "Detected Clang version older than 6, which does not fully support " + "C++17. Supported versions are Clang 6 and later." + ) + Exit(255) + elif env["debug_paths_relative"] and cc_version_major < 10: + print_warning("Clang < 10 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option.") + env["debug_paths_relative"] = False + elif env.msvc: # Ensure latest minor builds of Visual Studio 2017/2019. # https://github.com/godotengine/godot/pull/94995#issuecomment-2336464574 @@ -764,7 +752,7 @@ else: project_path = Dir("#").abspath env.Append(CCFLAGS=[f"-ffile-prefix-map={project_path}=."]) else: - if methods.using_clang(env) and not methods.is_vanilla_clang(env): + if methods.is_apple_clang(env): # Apple Clang, its linker doesn't like -s. env.Append(LINKFLAGS=["-Wl,-S", "-Wl,-x", "-Wl,-dead_strip"]) else: @@ -1050,23 +1038,7 @@ GLSL_BUILDERS = { } env.Append(BUILDERS=GLSL_BUILDERS) -scons_cache_path = os.environ.get("SCONS_CACHE") -if scons_cache_path is not None: - CacheDir(scons_cache_path) - print("Scons cache enabled... (path: '" + scons_cache_path + "')") - -if env["vsproj"]: - env.vs_incs = [] - env.vs_srcs = [] - if env["compiledb"]: - if env.scons_version < (4, 0, 0): - # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. - print_error( - "The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version - ) - Exit(255) - env.Tool("compilation_db") env.Alias("compiledb", env.CompilationDatabase()) @@ -1148,5 +1120,3 @@ def purge_flaky_files(): atexit.register(purge_flaky_files) - -methods.clean_cache(env) diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 08ce6c0e07..fa5c30c4ec 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1410,7 +1410,7 @@ void ProjectSettings::_add_builtin_input_map() { } Dictionary action; - action["deadzone"] = Variant(0.2f); + action["deadzone"] = Variant(InputMap::DEFAULT_DEADZONE); action["events"] = events; String action_name = "input/" + E.key; diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 248b5a8bf7..e580ea185d 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -1099,7 +1099,7 @@ JoyAxis InputEventJoypadMotion::get_axis() const { void InputEventJoypadMotion::set_axis_value(float p_value) { axis_value = p_value; - pressed = Math::abs(axis_value) >= 0.5f; + pressed = Math::abs(axis_value) >= InputMap::DEFAULT_DEADZONE; emit_changed(); } diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 553de4af9c..3073935f96 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -44,7 +44,7 @@ InputMap *InputMap::singleton = nullptr; void InputMap::_bind_methods() { ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action); ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions); - ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.2f)); + ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(DEFAULT_DEADZONE)); ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action); ClassDB::bind_method(D_METHOD("action_set_deadzone", "action", "deadzone"), &InputMap::action_set_deadzone); @@ -307,7 +307,7 @@ void InputMap::load_from_project_settings() { String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length()); Dictionary action = GLOBAL_GET(pi.name); - float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.2f; + float deadzone = action.has("deadzone") ? (float)action["deadzone"] : DEFAULT_DEADZONE; Array events = action["events"]; add_action(name, deadzone); diff --git a/core/input/input_map.h b/core/input/input_map.h index 1a2e378d79..319ce98ebd 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -51,6 +51,8 @@ public: List<Ref<InputEvent>> inputs; }; + static constexpr float DEFAULT_DEADZONE = 0.2f; + private: static InputMap *singleton; @@ -76,7 +78,7 @@ public: bool has_action(const StringName &p_action) const; List<StringName> get_actions() const; - void add_action(const StringName &p_action, float p_deadzone = 0.2); + void add_action(const StringName &p_action, float p_deadzone = DEFAULT_DEADZONE); void erase_action(const StringName &p_action); float action_get_deadzone(const StringName &p_action); diff --git a/core/io/file_access.compat.inc b/core/io/file_access.compat.inc new file mode 100644 index 0000000000..0ecafb7d9d --- /dev/null +++ b/core/io/file_access.compat.inc @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* file_access.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +Ref<FileAccess> FileAccess::_open_encrypted_bind_compat_98918(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key) { + return open_encrypted(p_path, p_mode_flags, p_key, Vector<uint8_t>()); +} + +void FileAccess::_bind_compatibility_methods() { + ClassDB::bind_compatibility_static_method("FileAccess", D_METHOD("open_encrypted", "path", "mode_flags", "key"), &FileAccess::_open_encrypted_bind_compat_98918); +} + +#endif // DISABLE_DEPRECATED diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index e10462adcd..0b004ce1e3 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -31,6 +31,7 @@ /**************************************************************************/ #include "file_access.h" +#include "file_access.compat.inc" #include "core/config/project_settings.h" #include "core/crypto/crypto_core.h" @@ -126,7 +127,7 @@ Ref<FileAccess> FileAccess::_open(const String &p_path, ModeFlags p_mode_flags) return fa; } -Ref<FileAccess> FileAccess::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key) { +Ref<FileAccess> FileAccess::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key, const Vector<uint8_t> &p_iv) { Ref<FileAccess> fa = _open(p_path, p_mode_flags); if (fa.is_null()) { return fa; @@ -134,7 +135,7 @@ Ref<FileAccess> FileAccess::open_encrypted(const String &p_path, ModeFlags p_mod Ref<FileAccessEncrypted> fae; fae.instantiate(); - Error err = fae->open_and_parse(fa, p_key, (p_mode_flags == WRITE) ? FileAccessEncrypted::MODE_WRITE_AES256 : FileAccessEncrypted::MODE_READ); + Error err = fae->open_and_parse(fa, p_key, (p_mode_flags == WRITE) ? FileAccessEncrypted::MODE_WRITE_AES256 : FileAccessEncrypted::MODE_READ, true, p_iv); last_file_open_error = err; if (err) { return Ref<FileAccess>(); @@ -808,7 +809,7 @@ String FileAccess::get_sha256(const String &p_file) { void FileAccess::_bind_methods() { ClassDB::bind_static_method("FileAccess", D_METHOD("open", "path", "flags"), &FileAccess::_open); - ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted", "path", "mode_flags", "key"), &FileAccess::open_encrypted); + ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted", "path", "mode_flags", "key", "iv"), &FileAccess::open_encrypted, DEFVAL(Vector<uint8_t>())); ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted_with_pass", "path", "mode_flags", "pass"), &FileAccess::open_encrypted_pass); ClassDB::bind_static_method("FileAccess", D_METHOD("open_compressed", "path", "mode_flags", "compression_mode"), &FileAccess::open_compressed, DEFVAL(0)); ClassDB::bind_static_method("FileAccess", D_METHOD("get_open_error"), &FileAccess::get_open_error); diff --git a/core/io/file_access.h b/core/io/file_access.h index 61970b2d1a..20b5f26d9f 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -111,6 +111,12 @@ protected: static FileCloseFailNotify close_fail_notify; +#ifndef DISABLE_DEPRECATED + static Ref<FileAccess> _open_encrypted_bind_compat_98918(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key); + + static void _bind_compatibility_methods(); +#endif + private: static bool backup_save; thread_local static Error last_file_open_error; @@ -201,7 +207,7 @@ public: static Ref<FileAccess> create_for_path(const String &p_path); static Ref<FileAccess> open(const String &p_path, int p_mode_flags, Error *r_error = nullptr); /// Create a file access (for the current platform) this is the only portable way of accessing files. - static Ref<FileAccess> open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key); + static Ref<FileAccess> open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key, const Vector<uint8_t> &p_iv = Vector<uint8_t>()); static Ref<FileAccess> open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass); static Ref<FileAccess> open_compressed(const String &p_path, ModeFlags p_mode_flags, CompressionMode p_compress_mode = COMPRESSION_FASTLZ); static Error get_open_error(); diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp index a84aa4c800..5fe7d99c1c 100644 --- a/core/io/file_access_encrypted.cpp +++ b/core/io/file_access_encrypted.cpp @@ -38,7 +38,7 @@ #include <stdio.h> -Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) { +Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic, const Vector<uint8_t> &p_iv) { ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, vformat("Can't open file while another file from path '%s' is open.", file->get_path_absolute())); ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER); @@ -51,6 +51,16 @@ Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<u writing = true; file = p_base; key = p_key; + if (p_iv.is_empty()) { + iv.resize(16); + CryptoCore::RandomGenerator rng; + ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator."); + Error err = rng.get_random_bytes(iv.ptrw(), 16); + ERR_FAIL_COND_V(err != OK, err); + } else { + ERR_FAIL_COND_V(p_iv.size() != 16, ERR_INVALID_PARAMETER); + iv = p_iv; + } } else if (p_mode == MODE_READ) { writing = false; @@ -65,10 +75,8 @@ Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<u p_base->get_buffer(md5d, 16); length = p_base->get_64(); - unsigned char iv[16]; - for (int i = 0; i < 16; i++) { - iv[i] = p_base->get_8(); - } + iv.resize(16); + p_base->get_buffer(iv.ptrw(), 16); base = p_base->get_position(); ERR_FAIL_COND_V(p_base->get_length() < base + length, ERR_FILE_CORRUPT); @@ -85,7 +93,7 @@ Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<u CryptoCore::AESContext ctx; ctx.set_encode_key(key.ptrw(), 256); // Due to the nature of CFB, same key schedule is used for both encryption and decryption! - ctx.decrypt_cfb(ds, iv, data.ptrw(), data.ptrw()); + ctx.decrypt_cfb(ds, iv.ptrw(), data.ptrw(), data.ptrw()); } data.resize(length); @@ -147,14 +155,9 @@ void FileAccessEncrypted::_close() { file->store_buffer(hash, 16); file->store_64(data.size()); + file->store_buffer(iv.ptr(), 16); - unsigned char iv[16]; - for (int i = 0; i < 16; i++) { - iv[i] = Math::rand() % 256; - file->store_8(iv[i]); - } - - ctx.encrypt_cfb(len, iv, compressed.ptrw(), compressed.ptrw()); + ctx.encrypt_cfb(len, iv.ptrw(), compressed.ptrw(), compressed.ptrw()); file->store_buffer(compressed.ptr(), compressed.size()); data.clear(); diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h index c9d0c458c1..0a8c19ea37 100644 --- a/core/io/file_access_encrypted.h +++ b/core/io/file_access_encrypted.h @@ -46,6 +46,7 @@ public: }; private: + Vector<uint8_t> iv; Vector<uint8_t> key; bool writing = false; Ref<FileAccess> file; @@ -59,9 +60,11 @@ private: void _close(); public: - Error open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic = true); + Error open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic = true, const Vector<uint8_t> &p_iv = Vector<uint8_t>()); Error open_and_parse_password(Ref<FileAccess> p_base, const String &p_key, Mode p_mode); + Vector<uint8_t> get_iv() const { return iv; } + virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file virtual bool is_open() const override; ///< true when file is open diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 124c08fe07..ff02d3f6fa 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -207,7 +207,7 @@ Ref<FileAccess> PackedData::try_open_path(const String &p_path) { } bool PackedData::has_path(const String &p_path) { - return files.has(PathMD5(p_path.simplify_path().md5_buffer())); + return files.has(PathMD5(p_path.simplify_path().trim_prefix("res://").md5_buffer())); } bool PackedData::has_directory(const String &p_path) { diff --git a/core/io/image.cpp b/core/io/image.cpp index 3e5ae837f5..a69794c931 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -2613,23 +2613,25 @@ Image::AlphaMode Image::detect_alpha() const { } Error Image::load(const String &p_path) { + String path = ResourceUID::ensure_path(p_path); #ifdef DEBUG_ENABLED - if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) { - WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", p_path)); + if (path.begins_with("res://") && ResourceLoader::exists(path)) { + WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", path)); } #endif - return ImageLoader::load_image(p_path, this); + return ImageLoader::load_image(ResourceUID::ensure_path(p_path), this); } Ref<Image> Image::load_from_file(const String &p_path) { + String path = ResourceUID::ensure_path(p_path); #ifdef DEBUG_ENABLED - if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) { - WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", p_path)); + if (path.begins_with("res://") && ResourceLoader::exists(path)) { + WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", path)); } #endif Ref<Image> image; image.instantiate(); - Error err = ImageLoader::load_image(p_path, image); + Error err = ImageLoader::load_image(path, image); if (err != OK) { ERR_FAIL_V_MSG(Ref<Image>(), vformat("Failed to load image. Error %d", err)); } diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index 0c564ce33b..83cde97331 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -36,6 +36,7 @@ #include "core/crypto/crypto_core.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" +#include "core/io/resource_loader.h" // These constants are off by 1, causing the 'z' and '9' characters never to be used. // This cannot be fixed without breaking compatibility; see GH-83843. @@ -141,6 +142,21 @@ void ResourceUID::remove_id(ID p_id) { unique_ids.erase(p_id); } +String ResourceUID::uid_to_path(const String &p_uid) { + return singleton->get_id_path(singleton->text_to_id(p_uid)); +} + +String ResourceUID::path_to_uid(const String &p_path) { + return singleton->id_to_text(ResourceLoader::get_resource_uid(p_path)); +} + +String ResourceUID::ensure_path(const String &p_uid_or_path) { + if (p_uid_or_path.begins_with("uid://")) { + return uid_to_path(p_uid_or_path); + } + return p_uid_or_path; +} + Error ResourceUID::save_to_cache() { String cache_file = get_cache_file(); if (!FileAccess::exists(cache_file)) { diff --git a/core/io/resource_uid.h b/core/io/resource_uid.h index 19ccf90e8c..3ab25cb9d4 100644 --- a/core/io/resource_uid.h +++ b/core/io/resource_uid.h @@ -75,6 +75,10 @@ public: String get_id_path(ID p_id) const; void remove_id(ID p_id); + static String uid_to_path(const String &p_uid); + static String path_to_uid(const String &p_path); + static String ensure_path(const String &p_uid_or_path); + Error load_from_cache(bool p_reset); Error save_to_cache(); Error update_cache(); diff --git a/core/templates/a_hash_map.h b/core/templates/a_hash_map.h index 81b061deb4..e831a65c67 100644 --- a/core/templates/a_hash_map.h +++ b/core/templates/a_hash_map.h @@ -624,10 +624,11 @@ public: } // Inserts an element without checking if it already exists. - void insert_new(const TKey &p_key, const TValue &p_value) { + Iterator insert_new(const TKey &p_key, const TValue &p_value) { DEV_ASSERT(!has(p_key)); uint32_t hash = _hash(p_key); - _insert_element(p_key, p_value, hash); + uint32_t pos = _insert_element(p_key, p_value, hash); + return Iterator(elements + pos, elements, elements + num_elements); } /* Array methods. */ diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 3a26cac7a9..12e1dfa55f 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2981,7 +2981,7 @@ Editing the property prompts the user for restarting the editor. </constant> <constant name="PROPERTY_USAGE_SCRIPT_VARIABLE" value="4096" enum="PropertyUsageFlags" is_bitfield="true"> - The property is a script variable which should be serialized and saved in the scene file. + The property is a script variable. [constant PROPERTY_USAGE_SCRIPT_VARIABLE] can be used to distinguish between exported script variables from built-in variables (which don't have this usage flag). By default, [constant PROPERTY_USAGE_SCRIPT_VARIABLE] is [b]not[/b] applied to variables that are created by overriding [method Object._get_property_list] in a script. </constant> <constant name="PROPERTY_USAGE_STORE_IF_NULL" value="8192" enum="PropertyUsageFlags" is_bitfield="true"> The property value of type [Object] will be stored even if its value is [code]null[/code]. diff --git a/doc/classes/AnimationNodeAnimation.xml b/doc/classes/AnimationNodeAnimation.xml index 70c3e5a26e..f4490bc167 100644 --- a/doc/classes/AnimationNodeAnimation.xml +++ b/doc/classes/AnimationNodeAnimation.xml @@ -12,6 +12,10 @@ <link title="Third Person Shooter (TPS) Demo">https://godotengine.org/asset-library/asset/2710</link> </tutorials> <members> + <member name="advance_on_start" type="bool" setter="set_advance_on_start" getter="is_advance_on_start" default="false"> + If [code]true[/code], on receiving a request to play an animation from the start, the first frame is not drawn, but only processed, and playback starts from the next frame. + See also the notes of [method AnimationPlayer.play]. + </member> <member name="animation" type="StringName" setter="set_animation" getter="get_animation" default="&"""> Animation to use as an output. It is one of the animations provided by [member AnimationTree.anim_player]. </member> diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml index dcd2d527e2..0f5844fd63 100644 --- a/doc/classes/DirAccess.xml +++ b/doc/classes/DirAccess.xml @@ -14,7 +14,7 @@ # Static DirAccess.make_dir_absolute("user://levels/world1") [/codeblock] - [b]Note:[/b] Many resources types are imported (e.g. textures or sound files), and their source asset will not be included in the exported game, as only the imported version is used. Use [ResourceLoader] to access imported resources. + [b]Note:[/b] Accessing project ("res://") directories once exported may behave unexpectedly as some files are converted to engine-specific formats and their original source files may not be present in the expected PCK package. Because of this, to access resources in an exported project, it is recommended to use [ResourceLoader] instead of [FileAccess]. Here is an example on how to iterate through the files of a directory: [codeblocks] [gdscript] @@ -116,6 +116,7 @@ <param index="0" name="path" type="String" /> <description> Returns whether the target directory exists. The argument can be relative to the current directory, or an absolute path. + [b]Note:[/b] The returned [bool] in the editor and after exporting when used on a path in the [code]res://[/code] directory may be different. Some files are converted to engine-specific formats when exported, potentially changing the directory structure. </description> </method> <method name="dir_exists_absolute" qualifiers="static"> @@ -123,6 +124,7 @@ <param index="0" name="path" type="String" /> <description> Static version of [method dir_exists]. Supports only absolute paths. + [b]Note:[/b] The returned [bool] in the editor and after exporting when used on a path in the [code]res://[/code] directory may be different. Some files are converted to engine-specific formats when exported, potentially changing the directory structure. </description> </method> <method name="file_exists"> @@ -131,6 +133,7 @@ <description> Returns whether the target file exists. The argument can be relative to the current directory, or an absolute path. For a static equivalent, use [method FileAccess.file_exists]. + [b]Note:[/b] Many resources types are imported (e.g. textures or sound files), and their source asset will not be included in the exported game, as only the imported version is used. See [method ResourceLoader.exists] for an alternative approach that takes resource remapping into account. </description> </method> <method name="get_current_dir" qualifiers="const"> @@ -151,6 +154,7 @@ <description> Returns a [PackedStringArray] containing filenames of the directory contents, excluding files. The array is sorted alphabetically. Affected by [member include_hidden] and [member include_navigational]. + [b]Note:[/b] The returned directories in the editor and after exporting in the [code]res://[/code] directory may differ as some files are converted to engine-specific formats when exported. </description> </method> <method name="get_directories_at" qualifiers="static"> @@ -159,6 +163,7 @@ <description> Returns a [PackedStringArray] containing filenames of the directory contents, excluding files, at the given [param path]. The array is sorted alphabetically. Use [method get_directories] if you want more control of what gets included. + [b]Note:[/b] The returned directories in the editor and after exporting in the [code]res://[/code] directory may differ as some files are converted to engine-specific formats when exported. </description> </method> <method name="get_drive_count" qualifiers="static"> @@ -194,6 +199,7 @@ <description> Returns a [PackedStringArray] containing filenames of the directory contents, excluding directories, at the given [param path]. The array is sorted alphabetically. Use [method get_files] if you want more control of what gets included. + [b]Note:[/b] When used on a [code]res://[/code] path in an exported project, only the files included in the PCK at the given folder level are returned. In practice, this means that since imported resources are stored in a top-level [code].godot/[/code] folder, only paths to [code].gd[/code] and [code].import[/code] files are returned (plus a few other files, such as [code]project.godot[/code] or [code]project.binary[/code] and the project icon). In an exported project, the list of returned files will also vary depending on [member ProjectSettings.editor/export/convert_text_resources_to_binary]. </description> </method> <method name="get_next"> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index e69748baff..fb4f901829 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -237,7 +237,7 @@ <return type="int" /> <param index="0" name="rect" type="Rect2" /> <description> - Returns index of the screen which contains specified rectangle. + Returns the index of the screen that overlaps the most with the given rectangle. Returns [code]-1[/code] if the rectangle doesn't overlap with any screen or has no area. </description> </method> <method name="get_swap_cancel_ok"> diff --git a/doc/classes/EditorTranslationParserPlugin.xml b/doc/classes/EditorTranslationParserPlugin.xml index 43bbeaefa7..b9dc654de3 100644 --- a/doc/classes/EditorTranslationParserPlugin.xml +++ b/doc/classes/EditorTranslationParserPlugin.xml @@ -99,6 +99,14 @@ <tutorials> </tutorials> <methods> + <method name="_get_comments" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="msgids_comment" type="String[]" /> + <param index="1" name="msgids_context_plural_comment" type="String[]" /> + <description> + If overridden, called after [method _parse_file] to get comments for the parsed entries. This method should fill the arrays with the same number of elements and in the same order as [method _parse_file]. + </description> + </method> <method name="_get_recognized_extensions" qualifiers="virtual const"> <return type="PackedStringArray" /> <description> diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 235f7eb819..79c3f0c3f1 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -308,6 +308,7 @@ <param index="0" name="path" type="String" /> <param index="1" name="mode_flags" type="int" enum="FileAccess.ModeFlags" /> <param index="2" name="key" type="PackedByteArray" /> + <param index="3" name="iv" type="PackedByteArray" default="PackedByteArray()" /> <description> Creates a new [FileAccess] object and opens an encrypted file in write or read mode. You need to pass a binary key to encrypt/decrypt it. [b]Note:[/b] The provided key must be 32 bytes long. diff --git a/doc/classes/GeometryInstance3D.xml b/doc/classes/GeometryInstance3D.xml index f4075af186..0c45154c8a 100644 --- a/doc/classes/GeometryInstance3D.xml +++ b/doc/classes/GeometryInstance3D.xml @@ -39,8 +39,12 @@ <member name="extra_cull_margin" type="float" setter="set_extra_cull_margin" getter="get_extra_cull_margin" default="0.0"> The extra distance added to the GeometryInstance3D's bounding box ([AABB]) to increase its cull box. </member> - <member name="gi_lightmap_scale" type="int" setter="set_lightmap_scale" getter="get_lightmap_scale" enum="GeometryInstance3D.LightmapScale" default="0"> + <member name="gi_lightmap_scale" type="int" setter="set_lightmap_scale" getter="get_lightmap_scale" enum="GeometryInstance3D.LightmapScale" default="0" deprecated="Use [member gi_lightmap_texel_scale] instead."> + The texel density to use for lightmapping in [LightmapGI]. + </member> + <member name="gi_lightmap_texel_scale" type="float" setter="set_lightmap_texel_scale" getter="get_lightmap_texel_scale" default="1.0"> The texel density to use for lightmapping in [LightmapGI]. Greater scale values provide higher resolution in the lightmap, which can result in sharper shadows for lights that have both direct and indirect light baked. However, greater scale values will also increase the space taken by the mesh in the lightmap texture, which increases the memory, storage, and bake time requirements. When using a single mesh at different scales, consider adjusting this value to keep the lightmap texel density consistent across meshes. + For example, doubling [member gi_lightmap_texel_scale] doubles the lightmap texture resolution for this object [i]on each axis[/i], so it will [i]quadruple[/i] the texel count. </member> <member name="gi_mode" type="int" setter="set_gi_mode" getter="get_gi_mode" enum="GeometryInstance3D.GIMode" default="1" keywords="global_illumination_mode, light_bake_mode"> The global illumination mode to use for the whole geometry. To avoid inconsistent results, use a mode that matches the purpose of the mesh during gameplay (static/dynamic). @@ -111,19 +115,19 @@ <constant name="GI_MODE_DYNAMIC" value="2" enum="GIMode"> Dynamic global illumination mode. Use for dynamic objects that contribute to global illumination. This GI mode is only effective when using [VoxelGI], but it has a higher performance impact than [constant GI_MODE_STATIC]. When using other GI methods, this will act the same as [constant GI_MODE_DISABLED]. When using [LightmapGI], the object will receive indirect lighting using lightmap probes instead of using the baked lightmap texture. </constant> - <constant name="LIGHTMAP_SCALE_1X" value="0" enum="LightmapScale"> + <constant name="LIGHTMAP_SCALE_1X" value="0" enum="LightmapScale" deprecated="Use [member gi_lightmap_texel_scale] instead."> The standard texel density for lightmapping with [LightmapGI]. </constant> - <constant name="LIGHTMAP_SCALE_2X" value="1" enum="LightmapScale"> + <constant name="LIGHTMAP_SCALE_2X" value="1" enum="LightmapScale" deprecated="Use [member gi_lightmap_texel_scale] instead."> Multiplies texel density by 2× for lightmapping with [LightmapGI]. To ensure consistency in texel density, use this when scaling a mesh by a factor between 1.5 and 3.0. </constant> - <constant name="LIGHTMAP_SCALE_4X" value="2" enum="LightmapScale"> + <constant name="LIGHTMAP_SCALE_4X" value="2" enum="LightmapScale" deprecated="Use [member gi_lightmap_texel_scale] instead."> Multiplies texel density by 4× for lightmapping with [LightmapGI]. To ensure consistency in texel density, use this when scaling a mesh by a factor between 3.0 and 6.0. </constant> - <constant name="LIGHTMAP_SCALE_8X" value="3" enum="LightmapScale"> + <constant name="LIGHTMAP_SCALE_8X" value="3" enum="LightmapScale" deprecated="Use [member gi_lightmap_texel_scale] instead."> Multiplies texel density by 8× for lightmapping with [LightmapGI]. To ensure consistency in texel density, use this when scaling a mesh by a factor greater than 6.0. </constant> - <constant name="LIGHTMAP_SCALE_MAX" value="4" enum="LightmapScale"> + <constant name="LIGHTMAP_SCALE_MAX" value="4" enum="LightmapScale" deprecated="Use [member gi_lightmap_texel_scale] instead."> Represents the size of the [enum LightmapScale] enum. </constant> <constant name="VISIBILITY_RANGE_FADE_DISABLED" value="0" enum="VisibilityRangeFadeMode"> diff --git a/doc/classes/LightmapGI.xml b/doc/classes/LightmapGI.xml index 51e0b8a575..f3ef010c59 100644 --- a/doc/classes/LightmapGI.xml +++ b/doc/classes/LightmapGI.xml @@ -70,6 +70,7 @@ </member> <member name="texel_scale" type="float" setter="set_texel_scale" getter="get_texel_scale" default="1.0"> Scales the lightmap texel density of all meshes for the current bake. This is a multiplier that builds upon the existing lightmap texel size defined in each imported 3D scene, along with the per-mesh density multiplier (which is designed to be used when the same mesh is used at different scales). Lower values will result in faster bake times. + For example, doubling [member texel_scale] doubles the lightmap texture resolution for all objects [i]on each axis[/i], so it will [i]quadruple[/i] the texel count. </member> <member name="use_denoiser" type="bool" setter="set_use_denoiser" getter="is_using_denoiser" default="true"> If [code]true[/code], uses a GPU-based denoising algorithm on the generated lightmap. This eliminates most noise within the generated lightmap at the cost of longer bake times. File sizes are generally not impacted significantly by the use of a denoiser, although lossless compression may do a better job at compressing a denoised image. diff --git a/doc/classes/MainLoop.xml b/doc/classes/MainLoop.xml index 0a00345a47..713dbd6681 100644 --- a/doc/classes/MainLoop.xml +++ b/doc/classes/MainLoop.xml @@ -5,7 +5,7 @@ </brief_description> <description> [MainLoop] is the abstract base class for a Redot project's game loop. It is inherited by [SceneTree], which is the default game loop implementation used in Redot projects, though it is also possible to write and use one's own [MainLoop] subclass instead of the scene tree. - Upon the application start, a [MainLoop] implementation must be provided to the OS; otherwise, the application will exit. This happens automatically (and a [SceneTree] is created) unless a [MainLoop] [Script] is provided from the command line (with e.g. [code]redot -s my_loop.gd[/code]) or the "Main Loop Type" project setting is overwritten. + Upon the application start, a [MainLoop] implementation must be provided to the OS; otherwise, the application will exit. This happens automatically (and a [SceneTree] is created) unless a [MainLoop] [Script] is provided from the command line (with e.g. [code]redot -s my_loop.gd[/code]) or the [member ProjectSettings.application/run/main_loop_type] project setting is overwritten. Here is an example script implementing a simple [MainLoop]: [codeblocks] [gdscript] diff --git a/doc/classes/PhysicalBone3D.xml b/doc/classes/PhysicalBone3D.xml index eda9fd6af5..e2ad3db0a5 100644 --- a/doc/classes/PhysicalBone3D.xml +++ b/doc/classes/PhysicalBone3D.xml @@ -57,7 +57,7 @@ </methods> <members> <member name="angular_damp" type="float" setter="set_angular_damp" getter="get_angular_damp" default="0.0"> - Damps the body's rotation. By default, the body will use the [b]Default Angular Damp[/b] in [b]Project > Project Settings > Physics > 3d[/b] or any value override set by an [Area3D] the body is in. Depending on [member angular_damp_mode], you can set [member angular_damp] to be added to or to replace the body's damping value. + Damps the body's rotation. By default, the body will use the [member ProjectSettings.physics/3d/default_angular_damp] project setting or any value override set by an [Area3D] the body is in. Depending on [member angular_damp_mode], you can set [member angular_damp] to be added to or to replace the body's damping value. See [member ProjectSettings.physics/3d/default_angular_damp] for more details about damping. </member> <member name="angular_damp_mode" type="int" setter="set_angular_damp_mode" getter="get_angular_damp_mode" enum="PhysicalBone3D.DampMode" default="0"> diff --git a/doc/classes/Range.xml b/doc/classes/Range.xml index 820ff04b70..a76676489f 100644 --- a/doc/classes/Range.xml +++ b/doc/classes/Range.xml @@ -54,7 +54,7 @@ Minimum value. Range is clamped if [member value] is less than [member min_value]. </member> <member name="page" type="float" setter="set_page" getter="get_page" default="0.0"> - Page size. Used mainly for [ScrollBar]. ScrollBar's length is its size multiplied by [member page] over the difference between [member min_value] and [member max_value]. + Page size. Used mainly for [ScrollBar]. A [ScrollBar]'s grabber length is the [ScrollBar]'s size multiplied by [member page] over the difference between [member min_value] and [member max_value]. </member> <member name="ratio" type="float" setter="set_as_ratio" getter="get_as_ratio"> The value mapped between 0 and 1. diff --git a/doc/classes/ResourceImporterTexture.xml b/doc/classes/ResourceImporterTexture.xml index 60329ad1fb..b3e893f687 100644 --- a/doc/classes/ResourceImporterTexture.xml +++ b/doc/classes/ResourceImporterTexture.xml @@ -90,9 +90,13 @@ <member name="process/size_limit" type="int" setter="" getter="" default="0"> If set to a value greater than [code]0[/code], the size of the texture is limited on import to a value smaller than or equal to the value specified here. For non-square textures, the size limit affects the longer dimension, with the shorter dimension scaled to preserve aspect ratio. Resizing is performed using cubic interpolation. This can be used to reduce memory usage without affecting the source images, or avoid issues with textures not displaying on mobile/web platforms (as these usually can't display textures larger than 4096×4096). + [b]Note:[/b] Even if this is set to [code]0[/code], import size is limited to the following dimensions for technical reasons. Depending on [member compress/mode], textures will be downsampled on import if necessary: + - [b]Lossy:[/b] 16383 pixels width or height, whichever is larger; + - [b]Basis Universal:[/b] 16384 pixels width or height, whichever is larger; + - [b]All other modes:[/b] 32768 pixels width or height, whichever is larger. </member> <member name="roughness/mode" type="int" setter="" getter="" default="0"> - The color channel to consider as a roughness map in this texture. Only effective if Roughness > Src Normal is not empty. + The color channel to consider as a roughness map in this texture. Only effective if [member roughness/src_normal] is not empty. </member> <member name="roughness/src_normal" type="String" setter="" getter="" default=""""> The path to the texture to consider as a normal map for roughness filtering on import. Specifying this can help decrease specular aliasing slightly in 3D. diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml index 5661d1a276..1977b238e4 100644 --- a/doc/classes/RigidBody2D.xml +++ b/doc/classes/RigidBody2D.xml @@ -123,7 +123,7 @@ </methods> <members> <member name="angular_damp" type="float" setter="set_angular_damp" getter="get_angular_damp" default="0.0"> - Damps the body's rotation. By default, the body will use the [b]Default Angular Damp[/b] in [b]Project > Project Settings > Physics > 2d[/b] or any value override set by an [Area2D] the body is in. Depending on [member angular_damp_mode], you can set [member angular_damp] to be added to or to replace the body's damping value. + Damps the body's rotation. By default, the body will use the [member ProjectSettings.physics/2d/default_angular_damp] setting or any value override set by an [Area2D] the body is in. Depending on [member angular_damp_mode], you can set [member angular_damp] to be added to or to replace the body's damping value. See [member ProjectSettings.physics/2d/default_angular_damp] for more details about damping. </member> <member name="angular_damp_mode" type="int" setter="set_angular_damp_mode" getter="get_angular_damp_mode" enum="RigidBody2D.DampMode" default="0"> @@ -172,7 +172,7 @@ For a body that is always frozen, use [StaticBody2D] or [AnimatableBody2D] instead. </member> <member name="gravity_scale" type="float" setter="set_gravity_scale" getter="get_gravity_scale" default="1.0"> - Multiplies the gravity applied to the body. The body's gravity is calculated from the [b]Default Gravity[/b] value in [b]Project > Project Settings > Physics > 2d[/b] and/or any additional gravity vector applied by [Area2D]s. + Multiplies the gravity applied to the body. The body's gravity is calculated from the [member ProjectSettings.physics/2d/default_gravity] project setting and/or any additional gravity vector applied by [Area2D]s. </member> <member name="inertia" type="float" setter="set_inertia" getter="get_inertia" default="0.0"> The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body. The moment of inertia is usually computed automatically from the mass and the shapes, but this property allows you to set a custom value. @@ -201,7 +201,7 @@ [/codeblocks] </member> <member name="linear_damp" type="float" setter="set_linear_damp" getter="get_linear_damp" default="0.0"> - Damps the body's movement. By default, the body will use the [b]Default Linear Damp[/b] in [b]Project > Project Settings > Physics > 2d[/b] or any value override set by an [Area2D] the body is in. Depending on [member linear_damp_mode], you can set [member linear_damp] to be added to or to replace the body's damping value. + Damps the body's movement. By default, the body will use the [member ProjectSettings.physics/2d/default_linear_damp] setting or any value override set by an [Area2D] the body is in. Depending on [member linear_damp_mode], you can set [member linear_damp] to be added to or to replace the body's damping value. See [member ProjectSettings.physics/2d/default_linear_damp] for more details about damping. </member> <member name="linear_damp_mode" type="int" setter="set_linear_damp_mode" getter="get_linear_damp_mode" enum="RigidBody2D.DampMode" default="0"> diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml index 9a299ade57..de6d5cde3d 100644 --- a/doc/classes/RigidBody3D.xml +++ b/doc/classes/RigidBody3D.xml @@ -130,7 +130,7 @@ </methods> <members> <member name="angular_damp" type="float" setter="set_angular_damp" getter="get_angular_damp" default="0.0"> - Damps the body's rotation. By default, the body will use the [b]Default Angular Damp[/b] in [b]Project > Project Settings > Physics > 3d[/b] or any value override set by an [Area3D] the body is in. Depending on [member angular_damp_mode], you can set [member angular_damp] to be added to or to replace the body's damping value. + Damps the body's rotation. By default, the body will use the [member ProjectSettings.physics/3d/default_angular_damp] project setting or any value override set by an [Area3D] the body is in. Depending on [member angular_damp_mode], you can set [member angular_damp] to be added to or to replace the body's damping value. See [member ProjectSettings.physics/3d/default_angular_damp] for more details about damping. </member> <member name="angular_damp_mode" type="int" setter="set_angular_damp_mode" getter="get_angular_damp_mode" enum="RigidBody3D.DampMode" default="0"> @@ -208,7 +208,7 @@ [/codeblocks] </member> <member name="linear_damp" type="float" setter="set_linear_damp" getter="get_linear_damp" default="0.0"> - Damps the body's movement. By default, the body will use the [b]Default Linear Damp[/b] in [b]Project > Project Settings > Physics > 3d[/b] or any value override set by an [Area3D] the body is in. Depending on [member linear_damp_mode], you can set [member linear_damp] to be added to or to replace the body's damping value. + Damps the body's movement. By default, the body will use the [member ProjectSettings.physics/3d/default_linear_damp] project setting or any value override set by an [Area3D] the body is in. Depending on [member linear_damp_mode], you can set [member linear_damp] to be added to or to replace the body's damping value. See [member ProjectSettings.physics/3d/default_linear_damp] for more details about damping. </member> <member name="linear_damp_mode" type="int" setter="set_linear_damp_mode" getter="get_linear_damp_mode" enum="RigidBody3D.DampMode" default="0"> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 7e393ca3ec..1ff9efdc94 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -248,7 +248,7 @@ <param index="1" name="placeholder" type="String" default=""{_}"" /> <description> Formats the string by replacing all occurrences of [param placeholder] with the elements of [param values]. - [param values] can be a [Dictionary], an [Array] or an [Object]. Any underscores in [param placeholder] will be replaced with the corresponding keys in advance. Array elements use their index as keys. + [param values] can be a [Dictionary], an [Array], or an [Object]. Any underscores in [param placeholder] will be replaced with the corresponding keys in advance. Array elements use their index as keys. [codeblock] # Prints "Waiting for Redot is a play by Samuel Beckett, and Redot Engine is named after it." var use_array_values = "Waiting for {0} is a play by {1}, and {0} Engine is named after it." @@ -265,7 +265,7 @@ [/codeblock] When passing an [Object], the property names from [method Object.get_property_list] are used as keys. [codeblock] - # Prints: Visible true, position (0, 0). + # Prints "Visible true, position (0, 0)" var node = Node2D.new() print("Visible {visible}, position {position}".format(node)) [/codeblock] diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index 4705677b07..2ac589587e 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -231,7 +231,7 @@ <param index="1" name="placeholder" type="String" default=""{_}"" /> <description> Formats the string by replacing all occurrences of [param placeholder] with the elements of [param values]. - [param values] can be a [Dictionary] or an [Array]. Any underscores in [param placeholder] will be replaced with the corresponding keys in advance. Array elements use their index as keys. + [param values] can be a [Dictionary], an [Array], or an [Object]. Any underscores in [param placeholder] will be replaced with the corresponding keys in advance. Array elements use their index as keys. [codeblock] # Prints "Waiting for Redot is a play by Samuel Beckett, and Redot Engine is named after it." var use_array_values = "Waiting for {0} is a play by {1}, and {0} Engine is named after it." @@ -246,6 +246,12 @@ print("User {} is {}.".format([42, "Redot"], "{}")) print("User {id} is {name}.".format([["id", 42], ["name", "Redot"]])) [/codeblock] + When passing an [Object], the property names from [method Object.get_property_list] are used as keys. + [codeblock] + # Prints "Visible true, position (0, 0)" + var node = Node2D.new() + print("Visible {visible}, position {position}".format(node)) + [/codeblock] See also the [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_format_string.html]GDScript format string[/url] tutorial. [b]Note:[/b] Each replacement is done sequentially for each element of [param values], [b]not[/b] all at once. This means that if any element is inserted and it contains another placeholder, it may be changed by the next replacement. While this can be very useful, it often causes unexpected results. If not necessary, make sure [param values]'s elements do not contain placeholders. [codeblock] diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index 29187b5967..053f98b395 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -2162,6 +2162,7 @@ RDD::FenceID RenderingDeviceDriverD3D12::fence_create() { Error RenderingDeviceDriverD3D12::fence_wait(FenceID p_fence) { FenceInfo *fence = (FenceInfo *)(p_fence.id); + fence->d3d_fence->SetEventOnCompletion(fence->fence_value, fence->event_handle); DWORD res = WaitForSingleObjectEx(fence->event_handle, INFINITE, FALSE); #ifdef PIX_ENABLED PIXNotifyWakeFromFenceSignal(fence->event_handle); @@ -2256,7 +2257,6 @@ Error RenderingDeviceDriverD3D12::command_queue_execute_and_present(CommandQueue FenceInfo *fence = (FenceInfo *)(p_cmd_fence.id); fence->fence_value++; command_queue->d3d_queue->Signal(fence->d3d_fence.Get(), fence->fence_value); - fence->d3d_fence->SetEventOnCompletion(fence->fence_value, fence->event_handle); } } diff --git a/drivers/metal/rendering_context_driver_metal.h b/drivers/metal/rendering_context_driver_metal.h index 293b06d974..bc01975d12 100644 --- a/drivers/metal/rendering_context_driver_metal.h +++ b/drivers/metal/rendering_context_driver_metal.h @@ -109,6 +109,7 @@ public: uint32_t height = 0; DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED; bool needs_resize = false; + double present_minimum_duration = 0.0; Surface( #ifdef __OBJC__ @@ -125,6 +126,7 @@ public: virtual Error resize(uint32_t p_desired_framebuffer_count) = 0; virtual RDD::FramebufferID acquire_next_frame_buffer() = 0; virtual void present(MDCommandBuffer *p_cmd_buffer) = 0; + void set_max_fps(int p_max_fps) { present_minimum_duration = p_max_fps ? 1.0 / p_max_fps : 0.0; } }; #ifdef __OBJC__ diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm index 07f6736e5d..a3022e0bdb 100644 --- a/drivers/metal/rendering_context_driver_metal.mm +++ b/drivers/metal/rendering_context_driver_metal.mm @@ -174,7 +174,7 @@ public: count--; front = (front + 1) % frame_buffers.size(); - [p_cmd_buffer->get_command_buffer() presentDrawable:drawable]; + [p_cmd_buffer->get_command_buffer() presentDrawable:drawable afterMinimumDuration:present_minimum_duration]; } }; diff --git a/drivers/metal/rendering_device_driver_metal.h b/drivers/metal/rendering_device_driver_metal.h index 7b3f47c2a6..9715107041 100644 --- a/drivers/metal/rendering_device_driver_metal.h +++ b/drivers/metal/rendering_device_driver_metal.h @@ -222,6 +222,7 @@ public: virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override final; virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override final; virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final; + virtual void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) override final; virtual void swap_chain_free(SwapChainID p_swap_chain) override final; #pragma mark - Frame Buffer diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index 517044a47f..5a30473735 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -984,6 +984,12 @@ RDD::DataFormat RenderingDeviceDriverMetal::swap_chain_get_format(SwapChainID p_ return swap_chain->data_format; } +void RenderingDeviceDriverMetal::swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) { + SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id); + RenderingContextDriverMetal::Surface *metal_surface = (RenderingContextDriverMetal::Surface *)(swap_chain->surface); + metal_surface->set_max_fps(p_max_fps); +} + void RenderingDeviceDriverMetal::swap_chain_free(SwapChainID p_swap_chain) { SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id); _swap_chain_release(swap_chain); @@ -1028,7 +1034,7 @@ void RenderingDeviceDriverMetal::framebuffer_free(FramebufferID p_framebuffer) { #pragma mark - Shader -const uint32_t SHADER_BINARY_VERSION = 2; +const uint32_t SHADER_BINARY_VERSION = 3; // region Serialization @@ -1332,23 +1338,32 @@ struct ComputeSize { struct ShaderStageData { RD::ShaderStage stage = RD::ShaderStage::SHADER_STAGE_MAX; + uint32_t is_position_invariant = UINT32_MAX; + uint32_t supports_fast_math = UINT32_MAX; CharString entry_point_name; CharString source; size_t serialize_size() const { int comp_size = Compression::get_max_compressed_buffer_size(source.length(), Compression::MODE_ZSTD); return sizeof(uint32_t) // Stage. - + sizeof(uint32_t) /* entry_point_name.utf8().length */ + entry_point_name.length() + sizeof(uint32_t) /* uncompressed size */ + sizeof(uint32_t) /* compressed size */ + comp_size; + + sizeof(uint32_t) // is_position_invariant + + sizeof(uint32_t) // supports_fast_math + + sizeof(uint32_t) /* entry_point_name.utf8().length */ + + entry_point_name.length() + sizeof(uint32_t) /* uncompressed size */ + sizeof(uint32_t) /* compressed size */ + comp_size; } void serialize(BufWriter &p_writer) const { p_writer.write((uint32_t)stage); + p_writer.write(is_position_invariant); + p_writer.write(supports_fast_math); p_writer.write(entry_point_name); p_writer.write_compressed(source); } void deserialize(BufReader &p_reader) { p_reader.read((uint32_t &)stage); + p_reader.read(is_position_invariant); + p_reader.read(supports_fast_math); p_reader.read(entry_point_name); p_reader.read_compressed(source); } @@ -2289,6 +2304,8 @@ Vector<uint8_t> RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec ShaderStageData stage_data; stage_data.stage = v.shader_stage; + stage_data.is_position_invariant = compiler.is_position_invariant(); + stage_data.supports_fast_math = !entry_point.flags.get(spv::ExecutionModeSignedZeroInfNanPreserve); stage_data.entry_point_name = entry_point.name.c_str(); stage_data.source = source.c_str(); bin_data.stages.push_back(stage_data); @@ -2361,7 +2378,8 @@ RDD::ShaderID RenderingDeviceDriverMetal::shader_create_from_bytecode(const Vect ShaderCacheEntry *cd = memnew(ShaderCacheEntry(*this, key)); cd->name = binary_data.shader_name; cd->stage = shader_data.stage; - + options.preserveInvariance = shader_data.is_position_invariant; + options.fastMathEnabled = YES; MDLibrary *library = [MDLibrary newLibraryWithCacheEntry:cd device:device source:source diff --git a/drivers/unix/net_socket_unix.cpp b/drivers/unix/net_socket_unix.cpp index 22417a5fe1..2230a6cc58 100644 --- a/drivers/unix/net_socket_unix.cpp +++ b/drivers/unix/net_socket_unix.cpp @@ -62,7 +62,7 @@ #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP #endif -size_t NetSocketPosix::_set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type) { +size_t NetSocketUnix::_set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type) { memset(p_addr, 0, sizeof(struct sockaddr_storage)); if (p_ip_type == IP::TYPE_IPV6 || p_ip_type == IP::TYPE_ANY) { // IPv6 socket. @@ -97,7 +97,7 @@ size_t NetSocketPosix::_set_addr_storage(struct sockaddr_storage *p_addr, const } } -void NetSocketPosix::_set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port) { +void NetSocketUnix::_set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port) { if (p_addr->ss_family == AF_INET) { struct sockaddr_in *addr4 = (struct sockaddr_in *)p_addr; if (r_ip) { @@ -117,21 +117,21 @@ void NetSocketPosix::_set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ } } -NetSocket *NetSocketPosix::_create_func() { - return memnew(NetSocketPosix); +NetSocket *NetSocketUnix::_create_func() { + return memnew(NetSocketUnix); } -void NetSocketPosix::make_default() { +void NetSocketUnix::make_default() { _create = _create_func; } -void NetSocketPosix::cleanup() { +void NetSocketUnix::cleanup() { } -NetSocketPosix::NetSocketPosix() { +NetSocketUnix::NetSocketUnix() { } -NetSocketPosix::~NetSocketPosix() { +NetSocketUnix::~NetSocketUnix() { close(); } @@ -142,7 +142,7 @@ NetSocketPosix::~NetSocketPosix() { #pragma GCC diagnostic ignored "-Wlogical-op" #endif -NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const { +NetSocketUnix::NetError NetSocketUnix::_get_socket_error() const { if (errno == EISCONN) { return ERR_NET_IS_CONNECTED; } @@ -169,7 +169,7 @@ NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const { #pragma GCC diagnostic pop #endif -bool NetSocketPosix::_can_use_ip(const IPAddress &p_ip, const bool p_for_bind) const { +bool NetSocketUnix::_can_use_ip(const IPAddress &p_ip, const bool p_for_bind) const { if (p_for_bind && !(p_ip.is_valid() || p_ip.is_wildcard())) { return false; } else if (!p_for_bind && !p_ip.is_valid()) { @@ -180,7 +180,7 @@ bool NetSocketPosix::_can_use_ip(const IPAddress &p_ip, const bool p_for_bind) c return !(_ip_type != IP::TYPE_ANY && !p_ip.is_wildcard() && _ip_type != type); } -_FORCE_INLINE_ Error NetSocketPosix::_change_multicast_group(IPAddress p_ip, String p_if_name, bool p_add) { +_FORCE_INLINE_ Error NetSocketUnix::_change_multicast_group(IPAddress p_ip, String p_if_name, bool p_add) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); ERR_FAIL_COND_V(!_can_use_ip(p_ip, false), ERR_INVALID_PARAMETER); @@ -234,7 +234,7 @@ _FORCE_INLINE_ Error NetSocketPosix::_change_multicast_group(IPAddress p_ip, Str return OK; } -void NetSocketPosix::_set_socket(int p_sock, IP::Type p_ip_type, bool p_is_stream) { +void NetSocketUnix::_set_socket(int p_sock, IP::Type p_ip_type, bool p_is_stream) { _sock = p_sock; _ip_type = p_ip_type; _is_stream = p_is_stream; @@ -242,13 +242,13 @@ void NetSocketPosix::_set_socket(int p_sock, IP::Type p_ip_type, bool p_is_strea _set_close_exec_enabled(true); } -void NetSocketPosix::_set_close_exec_enabled(bool p_enabled) { +void NetSocketUnix::_set_close_exec_enabled(bool p_enabled) { // Enable close on exec to avoid sharing with subprocesses. Off by default on Windows. int opts = fcntl(_sock, F_GETFD); fcntl(_sock, F_SETFD, opts | FD_CLOEXEC); } -Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) { +Error NetSocketUnix::open(Type p_sock_type, IP::Type &ip_type) { ERR_FAIL_COND_V(is_open(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(ip_type > IP::TYPE_ANY || ip_type < IP::TYPE_NONE, ERR_INVALID_PARAMETER); @@ -301,7 +301,7 @@ Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) { return OK; } -void NetSocketPosix::close() { +void NetSocketUnix::close() { if (_sock != -1) { ::close(_sock); } @@ -311,7 +311,7 @@ void NetSocketPosix::close() { _is_stream = false; } -Error NetSocketPosix::bind(IPAddress p_addr, uint16_t p_port) { +Error NetSocketUnix::bind(IPAddress p_addr, uint16_t p_port) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); ERR_FAIL_COND_V(!_can_use_ip(p_addr, true), ERR_INVALID_PARAMETER); @@ -328,7 +328,7 @@ Error NetSocketPosix::bind(IPAddress p_addr, uint16_t p_port) { return OK; } -Error NetSocketPosix::listen(int p_max_pending) { +Error NetSocketUnix::listen(int p_max_pending) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); if (::listen(_sock, p_max_pending) != 0) { @@ -341,7 +341,7 @@ Error NetSocketPosix::listen(int p_max_pending) { return OK; } -Error NetSocketPosix::connect_to_host(IPAddress p_host, uint16_t p_port) { +Error NetSocketUnix::connect_to_host(IPAddress p_host, uint16_t p_port) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); ERR_FAIL_COND_V(!_can_use_ip(p_host, false), ERR_INVALID_PARAMETER); @@ -369,7 +369,7 @@ Error NetSocketPosix::connect_to_host(IPAddress p_host, uint16_t p_port) { return OK; } -Error NetSocketPosix::poll(PollType p_type, int p_timeout) const { +Error NetSocketUnix::poll(PollType p_type, int p_timeout) const { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); struct pollfd pfd; @@ -403,7 +403,7 @@ Error NetSocketPosix::poll(PollType p_type, int p_timeout) const { return OK; } -Error NetSocketPosix::recv(uint8_t *p_buffer, int p_len, int &r_read) { +Error NetSocketUnix::recv(uint8_t *p_buffer, int p_len, int &r_read) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); r_read = ::recv(_sock, p_buffer, p_len, 0); @@ -424,7 +424,7 @@ Error NetSocketPosix::recv(uint8_t *p_buffer, int p_len, int &r_read) { return OK; } -Error NetSocketPosix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek) { +Error NetSocketUnix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); struct sockaddr_storage from; @@ -462,7 +462,7 @@ Error NetSocketPosix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddr return OK; } -Error NetSocketPosix::send(const uint8_t *p_buffer, int p_len, int &r_sent) { +Error NetSocketUnix::send(const uint8_t *p_buffer, int p_len, int &r_sent) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); int flags = 0; @@ -488,7 +488,7 @@ Error NetSocketPosix::send(const uint8_t *p_buffer, int p_len, int &r_sent) { return OK; } -Error NetSocketPosix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) { +Error NetSocketUnix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); struct sockaddr_storage addr; @@ -510,7 +510,7 @@ Error NetSocketPosix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP return OK; } -Error NetSocketPosix::set_broadcasting_enabled(bool p_enabled) { +Error NetSocketUnix::set_broadcasting_enabled(bool p_enabled) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); // IPv6 has no broadcast support. if (_ip_type == IP::TYPE_IPV6) { @@ -525,7 +525,7 @@ Error NetSocketPosix::set_broadcasting_enabled(bool p_enabled) { return OK; } -void NetSocketPosix::set_blocking_enabled(bool p_enabled) { +void NetSocketUnix::set_blocking_enabled(bool p_enabled) { ERR_FAIL_COND(!is_open()); int ret = 0; @@ -541,7 +541,7 @@ void NetSocketPosix::set_blocking_enabled(bool p_enabled) { } } -void NetSocketPosix::set_ipv6_only_enabled(bool p_enabled) { +void NetSocketUnix::set_ipv6_only_enabled(bool p_enabled) { ERR_FAIL_COND(!is_open()); // This option is only available in IPv6 sockets. ERR_FAIL_COND(_ip_type == IP::TYPE_IPV4); @@ -552,7 +552,7 @@ void NetSocketPosix::set_ipv6_only_enabled(bool p_enabled) { } } -void NetSocketPosix::set_tcp_no_delay_enabled(bool p_enabled) { +void NetSocketUnix::set_tcp_no_delay_enabled(bool p_enabled) { ERR_FAIL_COND(!is_open()); ERR_FAIL_COND(!_is_stream); // Not TCP. @@ -562,7 +562,7 @@ void NetSocketPosix::set_tcp_no_delay_enabled(bool p_enabled) { } } -void NetSocketPosix::set_reuse_address_enabled(bool p_enabled) { +void NetSocketUnix::set_reuse_address_enabled(bool p_enabled) { ERR_FAIL_COND(!is_open()); int par = p_enabled ? 1 : 0; @@ -571,11 +571,11 @@ void NetSocketPosix::set_reuse_address_enabled(bool p_enabled) { } } -bool NetSocketPosix::is_open() const { +bool NetSocketUnix::is_open() const { return _sock != -1; } -int NetSocketPosix::get_available_bytes() const { +int NetSocketUnix::get_available_bytes() const { ERR_FAIL_COND_V(!is_open(), -1); int len; @@ -588,7 +588,7 @@ int NetSocketPosix::get_available_bytes() const { return len; } -Error NetSocketPosix::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const { +Error NetSocketUnix::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const { ERR_FAIL_COND_V(!is_open(), FAILED); struct sockaddr_storage saddr; @@ -602,7 +602,7 @@ Error NetSocketPosix::get_socket_address(IPAddress *r_ip, uint16_t *r_port) cons return OK; } -Ref<NetSocket> NetSocketPosix::accept(IPAddress &r_ip, uint16_t &r_port) { +Ref<NetSocket> NetSocketUnix::accept(IPAddress &r_ip, uint16_t &r_port) { Ref<NetSocket> out; ERR_FAIL_COND_V(!is_open(), out); @@ -617,17 +617,17 @@ Ref<NetSocket> NetSocketPosix::accept(IPAddress &r_ip, uint16_t &r_port) { _set_ip_port(&their_addr, &r_ip, &r_port); - NetSocketPosix *ns = memnew(NetSocketPosix); + NetSocketUnix *ns = memnew(NetSocketUnix); ns->_set_socket(fd, _ip_type, _is_stream); ns->set_blocking_enabled(false); return Ref<NetSocket>(ns); } -Error NetSocketPosix::join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) { +Error NetSocketUnix::join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) { return _change_multicast_group(p_multi_address, p_if_name, true); } -Error NetSocketPosix::leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) { +Error NetSocketUnix::leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) { return _change_multicast_group(p_multi_address, p_if_name, false); } diff --git a/drivers/unix/net_socket_unix.h b/drivers/unix/net_socket_unix.h index f50b774109..54a2bdf2a7 100644 --- a/drivers/unix/net_socket_unix.h +++ b/drivers/unix/net_socket_unix.h @@ -39,7 +39,7 @@ #include <sys/socket.h> -class NetSocketPosix : public NetSocket { +class NetSocketUnix : public NetSocket { private: int _sock = -1; IP::Type _ip_type = IP::TYPE_NONE; @@ -95,8 +95,8 @@ public: virtual Error join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) override; virtual Error leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) override; - NetSocketPosix(); - ~NetSocketPosix() override; + NetSocketUnix(); + ~NetSocketUnix() override; }; #endif // UNIX_ENABLED && !UNIX_SOCKET_UNAVAILABLE diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 8ef2ea895e..8df91d1500 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -169,7 +169,9 @@ void OS_Unix::initialize_core() { DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA); DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM); - NetSocketPosix::make_default(); +#ifndef UNIX_SOCKET_UNAVAILABLE + NetSocketUnix::make_default(); +#endif IPUnix::make_default(); process_map = memnew((HashMap<ProcessID, ProcessInfo>)); @@ -178,7 +180,9 @@ void OS_Unix::initialize_core() { void OS_Unix::finalize_core() { memdelete(process_map); - NetSocketPosix::cleanup(); +#ifndef UNIX_SOCKET_UNAVAILABLE + NetSocketUnix::cleanup(); +#endif } Vector<String> OS_Unix::get_video_adapter_driver_info() const { diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 3643f599d9..38c4f4ae07 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -3034,13 +3034,10 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue, #if defined(SWAPPY_FRAME_PACING_ENABLED) if (swappy_frame_pacer_enable) { - const double max_fps = Engine::get_singleton()->get_max_fps(); - const uint64_t max_time = max_fps > 0 ? uint64_t((1000.0 * 1000.0 * 1000.0) / max_fps) : 0; - SwappyVk_initAndGetRefreshCycleDuration(get_jni_env(), static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity(), physical_device, vk_device, swap_chain->vk_swapchain, &swap_chain->refresh_duration); SwappyVk_setWindow(vk_device, swap_chain->vk_swapchain, static_cast<OS_Android *>(OS::get_singleton())->get_native_window()); - SwappyVk_setSwapIntervalNS(vk_device, swap_chain->vk_swapchain, MAX(swap_chain->refresh_duration, max_time)); + SwappyVk_setSwapIntervalNS(vk_device, swap_chain->vk_swapchain, swap_chain->refresh_duration); enum SwappyModes { PIPELINE_FORCED_ON, diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index b731e8959c..f1ef341d51 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1518,6 +1518,9 @@ void EditorFileSystem::_delete_internal_files(const String &p_file) { } da->remove(p_file + ".import"); } + if (FileAccess::exists(p_file + ".uid")) { + DirAccess::remove_absolute(p_file + ".uid"); + } } int EditorFileSystem::_insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir) { diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index bcfd4936f7..d702e50edc 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -1168,7 +1168,7 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const if (p_matching_keyword.is_empty()) { item->set_text(0, p_doc->name); } else { - item->set_text(0, p_doc->name + " - " + TTR(vformat("Matches the \"%s\" keyword.", p_matching_keyword))); + item->set_text(0, p_doc->name + " - " + vformat(TTR("Matches the \"%s\" keyword."), p_matching_keyword)); } if (!term.is_empty()) { @@ -1274,7 +1274,7 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons text = p_class_name + "." + p_text; } if (!p_matching_keyword.is_empty()) { - text += " - " + TTR(vformat("Matches the \"%s\" keyword.", p_matching_keyword)); + text += " - " + vformat(TTR("Matches the \"%s\" keyword."), p_matching_keyword); } item->set_text(0, text); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index eda65fd0c5..ad730b372f 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2726,7 +2726,7 @@ void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorIn for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) { EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor); - if (ep && current_favorites.has(F.properties[0])) { + if (ep && !F.properties.is_empty() && current_favorites.has(F.properties[0])) { ep->favorited = true; favorites_vbox->add_child(F.property_editor); } else { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b3a2ba04a3..a610c6ec42 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1456,6 +1456,16 @@ void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String file->popup_file_dialog(); } +void EditorNode::ensure_uid_file(const String &p_new_resource_path) { + if (ResourceLoader::exists(p_new_resource_path) && !ResourceLoader::has_custom_uid_support(p_new_resource_path) && !FileAccess::exists(p_new_resource_path + ".uid")) { + Ref<FileAccess> f = FileAccess::open(p_new_resource_path + ".uid", FileAccess::WRITE); + if (f.is_valid()) { + const ResourceUID::ID id = ResourceUID::get_singleton()->create_id(); + f->store_line(ResourceUID::get_singleton()->id_to_text(id)); + } + } +} + void EditorNode::_menu_option(int p_option) { _menu_option_confirm(p_option, false); } @@ -2175,6 +2185,12 @@ void EditorNode::_dialog_action(String p_file) { case RESOURCE_SAVE_AS: { ERR_FAIL_COND(saving_resource.is_null()); save_resource_in_path(saving_resource, p_file); + + if (current_menu_option == RESOURCE_SAVE_AS) { + // Create .uid file when making new Resource. + ensure_uid_file(p_file); + } + saving_resource = Ref<Resource>(); ObjectID current_id = editor_history.get_current(); Object *current_obj = current_id.is_valid() ? ObjectDB::get_instance(current_id) : nullptr; @@ -7696,24 +7712,28 @@ EditorNode::EditorNode() { disk_changed = memnew(ConfirmationDialog); { - disk_changed->set_title(TTR("Files have been modified on disk")); + disk_changed->set_title(TTR("Files have been modified outside Godot")); VBoxContainer *vbc = memnew(VBoxContainer); disk_changed->add_child(vbc); Label *dl = memnew(Label); - dl->set_text(TTR("The following files are newer on disk.\nWhat action should be taken?")); + dl->set_text(TTR("The following files are newer on disk:")); vbc->add_child(dl); disk_changed_list = memnew(Tree); vbc->add_child(disk_changed_list); disk_changed_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + Label *what_action_label = memnew(Label); + what_action_label->set_text(TTR("What action should be taken?")); + vbc->add_child(what_action_label); + disk_changed->connect(SceneStringName(confirmed), callable_mp(this, &EditorNode::_reload_modified_scenes)); disk_changed->connect(SceneStringName(confirmed), callable_mp(this, &EditorNode::_reload_project_settings)); - disk_changed->set_ok_button_text(TTR("Discard local changes and reload")); + disk_changed->set_ok_button_text(TTR("Reload from disk")); - disk_changed->add_button(TTR("Keep local changes and overwrite"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->add_button(TTR("Ignore external changes"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); disk_changed->connect("custom_action", callable_mp(this, &EditorNode::_resave_scenes)); } diff --git a/editor/editor_node.h b/editor/editor_node.h index 3bd8b511bb..4bf892579f 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -749,6 +749,7 @@ public: void save_resource_in_path(const Ref<Resource> &p_resource, const String &p_path); void save_resource(const Ref<Resource> &p_resource); void save_resource_as(const Ref<Resource> &p_resource, const String &p_at_path = String()); + void ensure_uid_file(const String &p_new_resource_path); void show_about() { _menu_option_confirm(HELP_ABOUT, false); } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index c0fb72fd10..49595e6fb5 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -478,7 +478,7 @@ void EditorPropertyPath::_path_selected(const String &p_path) { String EditorPropertyPath::_get_path_text() { String full_path = get_edited_property_value(); if (full_path.begins_with("uid://")) { - full_path = ResourceUID::get_singleton()->get_id_path(ResourceUID::get_singleton()->text_to_id(full_path)); + full_path = ResourceUID::uid_to_path(full_path); } return full_path; diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index d03e0a24fb..3eaf5f4c29 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -1175,6 +1175,11 @@ void EditorPropertyDictionary::update_property() { new_prop->set_h_size_flags(SIZE_EXPAND_FILL); new_prop->set_read_only(is_read_only()); slot.set_prop(new_prop); + } else if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { + Variant key = dict.get_key_at_index(slot.index); + String cs = key.get_construct_string(); + slot.prop->set_label(cs); + slot.prop->set_tooltip_text(cs); } // We need to grab the focus of the property that is being changed, even if the type didn't actually changed. @@ -1202,7 +1207,8 @@ void EditorPropertyDictionary::update_property() { void EditorPropertyDictionary::_remove_pressed(int p_slot_index) { Dictionary dict = object->get_dict().duplicate(); - dict.erase(dict.get_key_at_index(p_slot_index)); + int index = slots[p_slot_index].index; + dict.erase(dict.get_key_at_index(index)); emit_changed(get_edited_property(), dict); } diff --git a/editor/editor_translation_parser.cpp b/editor/editor_translation_parser.cpp index b020f30356..df7b383011 100644 --- a/editor/editor_translation_parser.cpp +++ b/editor/editor_translation_parser.cpp @@ -33,7 +33,6 @@ #include "editor_translation_parser.h" #include "core/error/error_macros.h" -#include "core/io/file_access.h" #include "core/object/script_language.h" #include "core/templates/hash_set.h" @@ -67,6 +66,21 @@ Error EditorTranslationParserPlugin::parse_file(const String &p_path, Vector<Str } } +void EditorTranslationParserPlugin::get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) { + TypedArray<String> ids_comment; + TypedArray<String> ids_ctx_plural_comment; + + if (GDVIRTUAL_CALL(_get_comments, ids_comment, ids_ctx_plural_comment)) { + for (int i = 0; i < ids_comment.size(); i++) { + r_ids_comment->append(ids_comment[i]); + } + + for (int i = 0; i < ids_ctx_plural_comment.size(); i++) { + r_ids_ctx_plural_comment->append(ids_ctx_plural_comment[i]); + } + } +} + void EditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const { Vector<String> extensions; if (GDVIRTUAL_CALL(_get_recognized_extensions, extensions)) { @@ -80,6 +94,7 @@ void EditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_ex void EditorTranslationParserPlugin::_bind_methods() { GDVIRTUAL_BIND(_parse_file, "path", "msgids", "msgids_context_plural"); + GDVIRTUAL_BIND(_get_comments, "msgids_comment", "msgids_context_plural_comment"); GDVIRTUAL_BIND(_get_recognized_extensions); } diff --git a/editor/editor_translation_parser.h b/editor/editor_translation_parser.h index 843224e932..d9c0b5a4dc 100644 --- a/editor/editor_translation_parser.h +++ b/editor/editor_translation_parser.h @@ -45,10 +45,12 @@ protected: static void _bind_methods(); GDVIRTUAL3(_parse_file, String, TypedArray<String>, TypedArray<Array>) + GDVIRTUAL2(_get_comments, TypedArray<String>, TypedArray<String>) GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions) public: virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural); + virtual void get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment); virtual void get_recognized_extensions(List<String> *r_extensions) const; }; diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index b7facc1531..3b15d6978b 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -89,6 +89,8 @@ void EditorExport::_save() { config->set_value(section, "encryption_include_filters", preset->get_enc_in_filter()); config->set_value(section, "encryption_exclude_filters", preset->get_enc_ex_filter()); + config->set_value(section, "seed", preset->get_seed()); + config->set_value(section, "encrypt_pck", preset->get_enc_pck()); config->set_value(section, "encrypt_directory", preset->get_enc_directory()); config->set_value(section, "script_export_mode", preset->get_script_export_mode()); @@ -309,6 +311,9 @@ void EditorExport::load_config() { preset->set_script_export_mode(config->get_value(section, "script_export_mode", EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED)); preset->set_patches(config->get_value(section, "patches", Vector<String>())); + if (config->has_section_key(section, "seed")) { + preset->set_seed(config->get_value(section, "seed")); + } if (config->has_section_key(section, "encrypt_pck")) { preset->set_enc_pck(config->get_value(section, "encrypt_pck")); } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index a9ae28f57e..da12bfdbe0 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -218,7 +218,7 @@ void EditorExportPlatform::_unload_patches() { PackedData::get_singleton()->clear(); } -Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { +Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); PackData *pd = (PackData *)p_userdata; @@ -249,10 +249,27 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa Ref<FileAccess> ftmp = pd->f; if (sd.encrypted) { + Vector<uint8_t> iv; + if (p_seed != 0) { + uint64_t seed = p_seed; + + const uint8_t *ptr = p_data.ptr(); + int64_t len = p_data.size(); + for (int64_t i = 0; i < len; i++) { + seed = ((seed << 5) + seed) ^ ptr[i]; + } + + RandomPCG rng = RandomPCG(seed, RandomPCG::DEFAULT_INC); + iv.resize(16); + for (int i = 0; i < 16; i++) { + iv.write[i] = rng.rand() % 256; + } + } + fae.instantiate(); ERR_FAIL_COND_V(fae.is_null(), ERR_SKIP); - Error err = fae->open_and_parse(ftmp, p_key, FileAccessEncrypted::MODE_WRITE_AES256, false); + Error err = fae->open_and_parse(ftmp, p_key, FileAccessEncrypted::MODE_WRITE_AES256, false, iv); ERR_FAIL_COND_V(err != OK, ERR_SKIP); ftmp = fae; } @@ -290,15 +307,15 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa return OK; } -Error EditorExportPlatform::_save_pack_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { +Error EditorExportPlatform::_save_pack_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { if (_check_hash(PackedData::get_singleton()->get_file_hash(p_path), p_data)) { return OK; } - return _save_pack_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key); + return _save_pack_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key, p_seed); } -Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { +Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); String path = p_path.replace_first("res://", ""); @@ -330,12 +347,12 @@ Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_pat return OK; } -Error EditorExportPlatform::_save_zip_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { +Error EditorExportPlatform::_save_zip_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { if (_check_hash(PackedData::get_singleton()->get_file_hash(p_path), p_data)) { return OK; } - return _save_zip_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key); + return _save_zip_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key, p_seed); } Ref<ImageTexture> EditorExportPlatform::get_option_icon(int p_index) const { @@ -934,7 +951,7 @@ Vector<String> EditorExportPlatform::get_forced_export_files() { return files; } -Error EditorExportPlatform::_script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { +Error EditorExportPlatform::_script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { Callable cb = ((ScriptCallbackData *)p_userdata)->file_cb; ERR_FAIL_COND_V(!cb.is_valid(), FAILED); @@ -1045,8 +1062,10 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & Vector<String> enc_in_filters; Vector<String> enc_ex_filters; Vector<uint8_t> key; + uint64_t seed = 0; if (enc_pck) { + seed = p_preset->get_seed(); Vector<String> enc_in_split = p_preset->get_enc_in_filter().split(","); for (int i = 0; i < enc_in_split.size(); i++) { String f = enc_in_split[i].strip_edges(); @@ -1118,7 +1137,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } } for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) { - err = p_save_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size(), enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size(), enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1236,7 +1255,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) { - err = p_save_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1268,7 +1287,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & if (importer_type == "keep") { // Just keep file as-is. Vector<uint8_t> array = FileAccess::get_file_as_bytes(path); - err = p_save_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; @@ -1311,13 +1330,13 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & sarr.resize(cs.size()); memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); - err = p_save_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } // Now actual remapped file: sarr = FileAccess::get_file_as_bytes(export_path); - err = p_save_func(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1347,14 +1366,14 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & if (remap == "path") { String remapped_path = config->get_value("remap", remap); Vector<uint8_t> array = FileAccess::get_file_as_bytes(remapped_path); - err = p_save_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); } else if (remap.begins_with("path.")) { String feature = remap.get_slice(".", 1); if (remap_features.has(feature)) { String remapped_path = config->get_value("remap", remap); Vector<uint8_t> array = FileAccess::get_file_as_bytes(remapped_path); - err = p_save_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); } else { // Remove paths if feature not enabled. config->erase_section_key("remap", remap); @@ -1380,7 +1399,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & sarr.resize(cs.size()); memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); - err = p_save_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; @@ -1401,7 +1420,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } Vector<uint8_t> array = FileAccess::get_file_as_bytes(export_path); - err = p_save_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1465,7 +1484,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & new_file.write[j] = utf8[j]; } - err = p_save_func(p_udata, from + ".remap", new_file, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, from + ".remap", new_file, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1479,7 +1498,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & Vector<String> forced_export = get_forced_export_files(); for (int i = 0; i < forced_export.size(); i++) { Vector<uint8_t> array = FileAccess::get_file_as_bytes(forced_export[i]); - err = p_save_func(p_udata, forced_export[i], array, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, forced_export[i], array, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1491,7 +1510,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & Vector<uint8_t> data = FileAccess::get_file_as_bytes(engine_cfb); DirAccess::remove_file_or_error(engine_cfb); - err = p_save_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); + err = p_save_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1874,6 +1893,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b Ref<FileAccess> fhead = f; if (enc_pck && enc_directory) { + uint64_t seed = p_preset->get_seed(); String script_key = _get_script_encryption_key(p_preset); Vector<uint8_t> key; key.resize(32); @@ -1908,7 +1928,27 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b return ERR_CANT_CREATE; } - err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_WRITE_AES256, false); + Vector<uint8_t> iv; + if (seed != 0) { + for (int i = 0; i < pd.file_ofs.size(); i++) { + for (int64_t j = 0; j < pd.file_ofs[i].path_utf8.length(); j++) { + seed = ((seed << 5) + seed) ^ pd.file_ofs[i].path_utf8.get_data()[j]; + } + for (int64_t j = 0; j < pd.file_ofs[i].md5.size(); j++) { + seed = ((seed << 5) + seed) ^ pd.file_ofs[i].md5[j]; + } + seed = ((seed << 5) + seed) ^ pd.file_ofs[i].ofs; + seed = ((seed << 5) + seed) ^ pd.file_ofs[i].size; + } + + RandomPCG rng = RandomPCG(seed, RandomPCG::DEFAULT_INC); + iv.resize(16); + for (int i = 0; i < 16; i++) { + iv.write[i] = rng.rand() % 256; + } + } + + err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_WRITE_AES256, false, iv); if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("Can't open encrypted file to write.")); return ERR_CANT_CREATE; diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 054419a82b..230eaa12fc 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -55,7 +55,7 @@ protected: static void _bind_methods(); public: - typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); typedef Error (*EditorExportRemoveFunction)(void *p_userdata, const String &p_path); typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so); @@ -116,14 +116,14 @@ private: static bool _check_hash(const uint8_t *p_hash, const Vector<uint8_t> &p_data); - static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); - static Error _save_pack_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); + static Error _save_pack_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); static Error _pack_add_shared_object(void *p_userdata, const SharedObject &p_so); static Error _remove_pack_file(void *p_userdata, const String &p_path); - static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); - static Error _save_zip_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); + static Error _save_zip_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); static Error _zip_add_shared_object(void *p_userdata, const SharedObject &p_so); struct ScriptCallbackData { @@ -131,7 +131,7 @@ private: Callable so_cb; }; - static Error _script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); static Error _script_add_shared_object(void *p_userdata, const SharedObject &p_so); void _edit_files_with_filter(Ref<DirAccess> &da, const Vector<String> &p_filters, HashSet<String> &r_list, bool exclude); diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index f0131e5907..2eb3c52dd7 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -225,13 +225,13 @@ Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> if (err == OK) { err = da->copy_dir(src_path, target_path, -1, true); if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("GDExtension"), TTR(vformat("Failed to copy shared object \"%s\".", src_path))); + add_message(EXPORT_MESSAGE_ERROR, TTR("GDExtension"), vformat(TTR("Failed to copy shared object \"%s\"."), src_path)); } } } else { err = da->copy(src_path, target_path); if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("GDExtension"), TTR(vformat("Failed to copy shared object \"%s\".", src_path))); + add_message(EXPORT_MESSAGE_ERROR, TTR("GDExtension"), vformat(TTR("Failed to copy shared object \"%s\"."), src_path)); } if (err == OK) { err = sign_shared_object(p_preset, p_debug, target_path); diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp index 1362a05b11..17452d01bd 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -453,6 +453,15 @@ String EditorExportPreset::get_enc_ex_filter() const { return enc_ex_filters; } +void EditorExportPreset::set_seed(uint64_t p_seed) { + seed = p_seed; + EditorExport::singleton->save_presets(); +} + +uint64_t EditorExportPreset::get_seed() const { + return seed; +} + void EditorExportPreset::set_enc_pck(bool p_enabled) { enc_pck = p_enabled; EditorExport::singleton->save_presets(); diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h index cebd37acc4..0594678e91 100644 --- a/editor/export/editor_export_preset.h +++ b/editor/export/editor_export_preset.h @@ -94,6 +94,7 @@ private: String enc_ex_filters; bool enc_pck = false; bool enc_directory = false; + uint64_t seed = 0; String script_key; int script_mode = MODE_SCRIPT_BINARY_TOKENS_COMPRESSED; @@ -167,6 +168,9 @@ public: void set_enc_ex_filter(const String &p_filter); String get_enc_ex_filter() const; + void set_seed(uint64_t p_seed); + uint64_t get_seed() const; + void set_enc_pck(bool p_enabled); bool get_enc_pck() const; diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index f7f792aa4b..e8024c456f 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -384,10 +384,16 @@ void ProjectExportDialog::_edit_preset(int p_index) { bool enc_pck_mode = current->get_enc_pck(); enc_pck->set_pressed(enc_pck_mode); + uint64_t seed = current->get_seed(); + if (!updating_seed) { + seed_input->set_text(itos(seed)); + } + enc_directory->set_disabled(!enc_pck_mode); enc_in_filters->set_editable(enc_pck_mode); enc_ex_filters->set_editable(enc_pck_mode); script_key->set_editable(enc_pck_mode); + seed_input->set_editable(enc_pck_mode); bool enc_directory_mode = current->get_enc_directory(); enc_directory->set_pressed(enc_directory_mode); @@ -593,6 +599,21 @@ void ProjectExportDialog::_enc_pck_changed(bool p_pressed) { _update_current_preset(); } +void ProjectExportDialog::_seed_input_changed(const String &p_text) { + if (updating) { + return; + } + + Ref<EditorExportPreset> current = get_current_preset(); + ERR_FAIL_COND(current.is_null()); + + current->set_seed(seed_input->get_text().to_int()); + + updating_seed = true; + _update_current_preset(); + updating_seed = false; +} + void ProjectExportDialog::_enc_directory_changed(bool p_pressed) { if (updating) { return; @@ -1625,6 +1646,10 @@ ProjectExportDialog::ProjectExportDialog() { sec_vb->add_child(script_key_error); sections->add_child(sec_scroll_container); + seed_input = memnew(LineEdit); + seed_input->connect(SceneStringName(text_changed), callable_mp(this, &ProjectExportDialog::_seed_input_changed)); + sec_vb->add_margin_child(TTR("Initialization vector seed"), seed_input); + Label *sec_info = memnew(Label); sec_info->set_text(TTR("Note: Encryption key needs to be stored in the binary,\nyou need to build the export templates from source.")); sec_vb->add_child(sec_info); diff --git a/editor/export/project_export.h b/editor/export/project_export.h index a8b5ecbffc..d3ea30bbc0 100644 --- a/editor/export/project_export.h +++ b/editor/export/project_export.h @@ -174,6 +174,7 @@ class ProjectExportDialog : public ConfirmationDialog { CheckButton *enc_directory = nullptr; LineEdit *enc_in_filters = nullptr; LineEdit *enc_ex_filters = nullptr; + LineEdit *seed_input = nullptr; OptionButton *script_mode = nullptr; @@ -194,9 +195,11 @@ class ProjectExportDialog : public ConfirmationDialog { bool updating_script_key = false; bool updating_enc_filters = false; + bool updating_seed = false; void _enc_pck_changed(bool p_pressed); void _enc_directory_changed(bool p_pressed); void _enc_filters_changed(const String &p_text); + void _seed_input_changed(const String &p_text); void _script_encryption_key_changed(const String &p_key); bool _validate_script_encryption_key(const String &p_key); diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index b1e837ffae..6d0390ab99 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -67,7 +67,7 @@ void EditorFileDialog::_native_popup() { } else if (access == ACCESS_USERDATA) { root = OS::get_singleton()->get_user_data_dir(); } - DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &EditorFileDialog::_native_dialog_cb)); + DisplayServer::get_singleton()->file_dialog_with_options_show(get_translated_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, _get_options(), callable_mp(this, &EditorFileDialog::_native_dialog_cb)); } void EditorFileDialog::popup(const Rect2i &p_rect) { @@ -1150,37 +1150,54 @@ void EditorFileDialog::_filter_selected(int) { void EditorFileDialog::update_filters() { filter->clear(); + processed_filters.clear(); if (filters.size() > 1) { String all_filters; + String all_filters_full; const int max_filters = 5; for (int i = 0; i < MIN(max_filters, filters.size()); i++) { - String flt = filters[i].get_slice(";", 0).strip_edges(); + String flt = filters[i].get_slicec(';', 0).strip_edges(); if (i > 0) { all_filters += ", "; } all_filters += flt; } + for (int i = 0; i < filters.size(); i++) { + String flt = filters[i].get_slicec(';', 0).strip_edges(); + if (i > 0) { + all_filters_full += ","; + } + all_filters_full += flt; + } if (max_filters < filters.size()) { all_filters += ", ..."; } - filter->add_item(TTR("All Recognized") + " (" + all_filters + ")"); + String f = TTR("All Recognized") + " (" + all_filters + ")"; + filter->add_item(f); + processed_filters.push_back(all_filters_full + ";" + f); } for (int i = 0; i < filters.size(); i++) { - String flt = filters[i].get_slice(";", 0).strip_edges(); + String flt = filters[i].get_slicec(';', 0).strip_edges(); String desc = filters[i].get_slice(";", 1).strip_edges(); if (desc.length()) { - filter->add_item(desc + " (" + flt + ")"); + String f = desc + " (" + flt + ")"; + filter->add_item(f); + processed_filters.push_back(flt + ";" + f); } else { - filter->add_item("(" + flt + ")"); + String f = "(" + flt + ")"; + filter->add_item(f); + processed_filters.push_back(flt + ";" + f); } } - filter->add_item(TTR("All Files (*)")); + String f = TTR("All Files (*)"); + filter->add_item(f); + processed_filters.push_back("*.*;" + f); } void EditorFileDialog::clear_filters() { diff --git a/editor/gui/editor_file_dialog.h b/editor/gui/editor_file_dialog.h index c74e3aae5b..52b5686cc0 100644 --- a/editor/gui/editor_file_dialog.h +++ b/editor/gui/editor_file_dialog.h @@ -147,6 +147,7 @@ private: void _push_history(); Vector<String> filters; + Vector<String> processed_filters; bool previews_enabled = true; bool preview_waiting = false; diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 36ddfa757d..70ff927bd3 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -1100,8 +1100,19 @@ void SceneTreeEditor::rename_node(Node *p_node, const String &p_name, TreeItem * // Trim leading/trailing whitespace to prevent node names from containing accidental whitespace, which would make it more difficult to get the node via `get_node()`. new_name = new_name.strip_edges(); + if (new_name.is_empty() && p_node->get_owner() != nullptr && !p_node->get_scene_file_path().is_empty()) { + // If name is empty and node is root of an instance, revert to the original name. + const Ref<PackedScene> node_scene = ResourceLoader::load(p_node->get_scene_file_path()); + if (node_scene.is_valid()) { + const Ref<SceneState> &state = node_scene->get_state(); + if (state->get_node_count() > 0) { + new_name = state->get_node_name(0); // Root's name. + } + } + } + if (new_name.is_empty()) { - // If name is empty, fallback to class name. + // If name is still empty, fallback to class name. if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) { new_name = Node::adjust_name_casing(p_node->get_class()); } else { diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index 240321225b..a4c7089dcd 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -243,7 +243,10 @@ void ResourceImporterTexture::get_import_options(const String &p_path, List<Impo r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/normal_map_invert_y"), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/hdr_as_srgb"), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/hdr_clamp_exposure"), false)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "process/size_limit", PROPERTY_HINT_RANGE, "0,4096,1"), 0)); + + // Maximum bound is the highest allowed value for lossy compression (the lowest common denominator). + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "process/size_limit", PROPERTY_HINT_RANGE, "0,16383,1"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "detect_3d/compress_to", PROPERTY_HINT_ENUM, "Disabled,VRAM Compressed,Basis Universal"), (p_preset == PRESET_DETECT) ? 1 : 0)); // Do path based customization only if a path was passed. @@ -456,7 +459,28 @@ Error ResourceImporterTexture::import(ResourceUID::ID p_source_id, const String const bool normal_map_invert_y = p_options["process/normal_map_invert_y"]; // Support for texture streaming is not implemented yet. const bool stream = false; - const int size_limit = p_options["process/size_limit"]; + + int size_limit = p_options["process/size_limit"]; + bool using_fallback_size_limit = false; + if (size_limit == 0) { + using_fallback_size_limit = true; + // If no size limit is defined, use a fallback size limit to prevent textures from looking incorrect or failing to import. + switch (compress_mode) { + case COMPRESS_LOSSY: + // Maximum WebP size on either axis. + size_limit = 16383; + break; + case COMPRESS_BASIS_UNIVERSAL: + // Maximum Basis Universal size on either axis. + size_limit = 16384; + break; + default: + // As of June 2024, no GPU can correctly display a texture larger than 32768 pixels on either axis. + size_limit = 32768; + break; + } + } + const bool hdr_as_srgb = p_options["process/hdr_as_srgb"]; if (hdr_as_srgb) { loader_flags |= ImageFormatLoader::FLAG_FORCE_LINEAR; @@ -525,11 +549,19 @@ Error ResourceImporterTexture::import(ResourceUID::ID p_source_id, const String int new_width = size_limit; int new_height = target_image->get_height() * new_width / target_image->get_width(); + if (using_fallback_size_limit) { + // Only warn if downsizing occurred when the user did not explicitly request it. + WARN_PRINT(vformat("%s: Texture was downsized on import as its width (%d pixels) exceeded the importable size limit (%d pixels).", p_source_file, target_image->get_width(), size_limit)); + } target_image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); } else { int new_height = size_limit; int new_width = target_image->get_width() * new_height / target_image->get_height(); + if (using_fallback_size_limit) { + // Only warn if downsizing occurred when the user did not explicitly request it. + WARN_PRINT(vformat("%s: Texture was downsized on import as its height (%d pixels) exceeded the importable size limit (%d pixels).", p_source_file, target_image->get_height(), size_limit)); + } target_image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 0d439c4311..51679cca9a 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1932,37 +1932,50 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { // Drag resize handles if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && + ((tool == TOOL_SELECT && b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) { bool has_locked_items = false; List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items); - if (selection.size() == 1) { + + // Remove non-movable nodes. + for (CanvasItem *ci : selection) { + if (!_is_node_movable(ci, true)) { + selection.erase(ci); + } + } + + if (!selection.is_empty()) { CanvasItem *ci = selection.front()->get(); - if (_is_node_movable(ci)) { - Transform2D xform = transform * ci->get_global_transform_with_canvas(); - Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); - Transform2D simple_xform = viewport->get_transform() * unscaled_transform; + Transform2D edit_transform; + if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) { + edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot); + } else { + edit_transform = ci->_edit_get_transform(); + } - drag_type = DRAG_SCALE_BOTH; + Transform2D xform = transform * ci->get_global_transform_with_canvas(); + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * edit_transform).orthonormalized(); + Transform2D simple_xform = viewport->get_transform() * unscaled_transform; - if (show_transformation_gizmos) { - Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE); - Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); - if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) { - drag_type = DRAG_SCALE_X; - } - Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); - if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) { - drag_type = DRAG_SCALE_Y; - } - } + drag_type = DRAG_SCALE_BOTH; - drag_from = transform.affine_inverse().xform(b->get_position()); - drag_selection = List<CanvasItem *>(); - drag_selection.push_back(ci); - _save_canvas_item_state(drag_selection); - return true; + if (show_transformation_gizmos) { + Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE); + Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); + if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) { + drag_type = DRAG_SCALE_X; + } + Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); + if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) { + drag_type = DRAG_SCALE_Y; + } } + + drag_from = transform.affine_inverse().xform(b->get_position()); + drag_selection = selection; + _save_canvas_item_state(drag_selection); + return true; } else { if (has_locked_items) { EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING); @@ -1970,66 +1983,87 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { return has_locked_items; } } - } - - if (drag_type == DRAG_SCALE_BOTH || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) { + } else if (drag_type == DRAG_SCALE_BOTH || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) { // Resize the node if (m.is_valid()) { _restore_canvas_item_state(drag_selection); - CanvasItem *ci = drag_selection.front()->get(); drag_to = transform.affine_inverse().xform(m->get_position()); - Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse(); - Transform2D unscaled_transform = (transform * parent_xform * ci->_edit_get_transform()).orthonormalized(); - Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform; - - bool uniform = m->is_shift_pressed(); - bool is_ctrl = m->is_command_or_control_pressed(); - - Point2 drag_from_local = simple_xform.xform(drag_from); - Point2 drag_to_local = simple_xform.xform(drag_to); - Point2 offset = drag_to_local - drag_from_local; + Size2 scale_max; + if (drag_type != DRAG_SCALE_BOTH) { + for (CanvasItem *ci : drag_selection) { + scale_max = scale_max.max(ci->_edit_get_scale()); + } + } - Size2 scale = ci->_edit_get_scale(); - Size2 original_scale = scale; - real_t ratio = scale.y / scale.x; - if (drag_type == DRAG_SCALE_BOTH) { - Size2 scale_factor = drag_to_local / drag_from_local; - if (uniform) { - scale *= (scale_factor.x + scale_factor.y) / 2.0; + for (CanvasItem *ci : drag_selection) { + Transform2D edit_transform; + bool using_temp_pivot = !Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y); + if (using_temp_pivot) { + edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot); } else { - scale *= scale_factor; + edit_transform = ci->_edit_get_transform(); } - } else { - Size2 scale_factor = Vector2(offset.x, -offset.y) / SCALE_HANDLE_DISTANCE; - Size2 parent_scale = parent_xform.get_scale(); - scale_factor *= Vector2(1.0 / parent_scale.x, 1.0 / parent_scale.y); - if (drag_type == DRAG_SCALE_X) { - scale.x += scale_factor.x; + Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse(); + Transform2D unscaled_transform = (transform * parent_xform * edit_transform).orthonormalized(); + Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform; + + bool uniform = m->is_shift_pressed(); + bool is_ctrl = m->is_command_or_control_pressed(); + + Point2 drag_from_local = simple_xform.xform(drag_from); + Point2 drag_to_local = simple_xform.xform(drag_to); + Point2 offset = drag_to_local - drag_from_local; + + Size2 scale = ci->_edit_get_scale(); + Size2 original_scale = scale; + real_t ratio = scale.y / scale.x; + if (drag_type == DRAG_SCALE_BOTH) { + Size2 scale_factor = drag_to_local / drag_from_local; if (uniform) { - scale.y = scale.x * ratio; + scale *= (scale_factor.x + scale_factor.y) / 2.0; + } else { + scale *= scale_factor; } - } else if (drag_type == DRAG_SCALE_Y) { - scale.y -= scale_factor.y; - if (uniform) { - scale.x = scale.y / ratio; + } else { + Size2 scale_factor = Vector2(offset.x, -offset.y) / SCALE_HANDLE_DISTANCE; + Size2 parent_scale = parent_xform.get_scale(); + // Take into account the biggest scale, so all nodes are scaled uniformly. + scale_factor *= Vector2(1.0 / parent_scale.x, 1.0 / parent_scale.y) / (scale_max / original_scale); + + if (drag_type == DRAG_SCALE_X) { + scale.x += scale_factor.x; + if (uniform) { + scale.y = scale.x * ratio; + } + } else if (drag_type == DRAG_SCALE_Y) { + scale.y -= scale_factor.y; + if (uniform) { + scale.x = scale.y / ratio; + } } } - } - if (snap_scale && !is_ctrl) { - if (snap_relative) { - scale.x = original_scale.x * (roundf((scale.x / original_scale.x) / snap_scale_step) * snap_scale_step); - scale.y = original_scale.y * (roundf((scale.y / original_scale.y) / snap_scale_step) * snap_scale_step); - } else { - scale.x = roundf(scale.x / snap_scale_step) * snap_scale_step; - scale.y = roundf(scale.y / snap_scale_step) * snap_scale_step; + if (snap_scale && !is_ctrl) { + if (snap_relative) { + scale.x = original_scale.x * (Math::round((scale.x / original_scale.x) / snap_scale_step) * snap_scale_step); + scale.y = original_scale.y * (Math::round((scale.y / original_scale.y) / snap_scale_step) * snap_scale_step); + } else { + scale.x = Math::round(scale.x / snap_scale_step) * snap_scale_step; + scale.y = Math::round(scale.y / snap_scale_step) * snap_scale_step; + } + } + + ci->_edit_set_scale(scale); + + if (using_temp_pivot) { + Point2 ci_origin = ci->_edit_get_transform().get_origin(); + ci->_edit_set_position(ci_origin + (ci_origin - temp_pivot) * ((scale - original_scale) / original_scale)); } } - ci->_edit_set_scale(scale); return true; } @@ -2077,7 +2111,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_NONE) { //Start moving the nodes if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { - if ((b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) { + if ((tool == TOOL_SELECT && b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) { bool has_locked_items = false; List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items); @@ -2137,7 +2171,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } Point2 drag_delta = drag_to - drag_from; - if (drag_selection.size() == 1 && (drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y)) { + if (drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) { const CanvasItem *selected = drag_selection.front()->get(); Transform2D parent_xform = selected->get_global_transform_with_canvas() * selected->get_transform().affine_inverse(); Transform2D unscaled_transform = (transform * parent_xform * selected->_edit_get_transform()).orthonormalized(); @@ -3470,16 +3504,14 @@ void CanvasItemEditor::_draw_selection() { Ref<Texture2D> previous_position_icon = get_editor_theme_icon(SNAME("EditorPositionPrevious")); RID vp_ci = viewport->get_canvas_item(); - List<CanvasItem *> selection = _get_edited_canvas_items(true, false); - bool single = selection.size() == 1; + bool transform_tool = tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT; + for (CanvasItem *E : selection) { CanvasItem *ci = Object::cast_to<CanvasItem>(E); CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); - bool item_locked = ci->has_meta("_edit_lock_"); - // Draw the previous position if we are dragging the node if (show_helpers && (drag_type == DRAG_MOVE || drag_type == DRAG_ROTATE || @@ -3504,6 +3536,7 @@ void CanvasItemEditor::_draw_selection() { } } + bool item_locked = ci->has_meta("_edit_lock_"); Transform2D xform = transform * ci->get_global_transform_with_canvas(); // Draw the selected items position / surrounding boxes @@ -3533,7 +3566,7 @@ void CanvasItemEditor::_draw_selection() { viewport->draw_set_transform_matrix(viewport->get_transform()); } - if (single && !item_locked && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT)) { //kind of sucks + if (single && !item_locked && transform_tool) { // Draw the pivot if (ci->_edit_use_pivot()) { // Draw the node's pivot @@ -3576,73 +3609,88 @@ void CanvasItemEditor::_draw_selection() { select_handle->draw(vp_ci, (ofs - (select_handle->get_size() / 2)).floor()); } } + } + } - // Draw the move handles - bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); - bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT); - if (tool == TOOL_MOVE && show_transformation_gizmos) { - if (_is_node_movable(ci)) { - Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); - Transform2D simple_xform = viewport->get_transform() * unscaled_transform; + // Remove non-movable nodes. + for (CanvasItem *ci : selection) { + if (!_is_node_movable(ci, true)) { + selection.erase(ci); + } + } - Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE); - viewport->draw_set_transform_matrix(simple_xform); + if (!selection.is_empty() && transform_tool && show_transformation_gizmos) { + CanvasItem *ci = selection.front()->get(); - Vector<Point2> points = { - Vector2(move_factor.x * EDSCALE, 5 * EDSCALE), - Vector2(move_factor.x * EDSCALE, -5 * EDSCALE), - Vector2((move_factor.x + 10) * EDSCALE, 0) - }; + Transform2D xform = transform * ci->get_global_transform_with_canvas(); + bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); + bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT); - viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor))); - viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE)); + // Draw the move handles. + if ((tool == TOOL_SELECT && is_alt && !is_ctrl) || tool == TOOL_MOVE) { + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); + Transform2D simple_xform = viewport->get_transform() * unscaled_transform; - points.clear(); - points.push_back(Vector2(5 * EDSCALE, move_factor.y * EDSCALE)); - points.push_back(Vector2(-5 * EDSCALE, move_factor.y * EDSCALE)); - points.push_back(Vector2(0, (move_factor.y + 10) * EDSCALE)); + Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE); + viewport->draw_set_transform_matrix(simple_xform); - viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor))); - viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE)); + Vector<Point2> points = { + Vector2(move_factor.x * EDSCALE, 5 * EDSCALE), + Vector2(move_factor.x * EDSCALE, -5 * EDSCALE), + Vector2((move_factor.x + 10) * EDSCALE, 0) + }; - viewport->draw_set_transform_matrix(viewport->get_transform()); - } - } + viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor))); + viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE)); - // Draw the rescale handles - if (show_transformation_gizmos && ((is_alt && is_ctrl) || tool == TOOL_SCALE || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y)) { - if (_is_node_movable(ci)) { - Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); - Transform2D simple_xform = viewport->get_transform() * unscaled_transform; + points.clear(); + points.push_back(Vector2(5 * EDSCALE, move_factor.y * EDSCALE)); + points.push_back(Vector2(-5 * EDSCALE, move_factor.y * EDSCALE)); + points.push_back(Vector2(0, (move_factor.y + 10) * EDSCALE)); - Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE); - bool uniform = Input::get_singleton()->is_key_pressed(Key::SHIFT); - Point2 offset = (simple_xform.affine_inverse().xform(drag_to) - simple_xform.affine_inverse().xform(drag_from)) * zoom; + viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor))); + viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE)); - if (drag_type == DRAG_SCALE_X) { - scale_factor.x += offset.x; - if (uniform) { - scale_factor.y += offset.x; - } - } else if (drag_type == DRAG_SCALE_Y) { - scale_factor.y += offset.y; - if (uniform) { - scale_factor.x += offset.y; - } - } + viewport->draw_set_transform_matrix(viewport->get_transform()); + } - viewport->draw_set_transform_matrix(simple_xform); - Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); - viewport->draw_rect(x_handle_rect, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor))); - viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE)); + // Draw the rescale handles. + if ((tool == TOOL_SELECT && is_alt && is_ctrl) || tool == TOOL_SCALE || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) { + Transform2D edit_transform; + if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) { + edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot); + } else { + edit_transform = ci->_edit_get_transform(); + } + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * edit_transform).orthonormalized(); + Transform2D simple_xform = viewport->get_transform() * unscaled_transform; - Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); - viewport->draw_rect(y_handle_rect, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor))); - viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE)); + Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE); + bool uniform = Input::get_singleton()->is_key_pressed(Key::SHIFT); + Point2 offset = (simple_xform.affine_inverse().xform(drag_to) - simple_xform.affine_inverse().xform(drag_from)) * zoom; - viewport->draw_set_transform_matrix(viewport->get_transform()); + if (drag_type == DRAG_SCALE_X) { + scale_factor.x += offset.x; + if (uniform) { + scale_factor.y += offset.x; + } + } else if (drag_type == DRAG_SCALE_Y) { + scale_factor.y += offset.y; + if (uniform) { + scale_factor.x += offset.y; } } + + viewport->draw_set_transform_matrix(simple_xform); + Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); + viewport->draw_rect(x_handle_rect, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor))); + viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE)); + + Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); + viewport->draw_rect(y_handle_rect, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor))); + viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE)); + + viewport->draw_set_transform_matrix(viewport->get_transform()); } } @@ -5358,7 +5406,7 @@ CanvasItemEditor::CanvasItemEditor() { main_menu_hbox->add_child(pivot_button); pivot_button->set_toggle_mode(true); pivot_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT)); - pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot.") + "\n" + TTR("Shift: Set temporary rotation pivot.") + "\n" + TTR("Click this button while holding Shift to put the temporary rotation pivot in the center of the selected nodes.")); + pivot_button->set_tooltip_text(TTR("Click to change object's pivot.") + "\n" + TTR("Shift: Set temporary pivot.") + "\n" + TTR("Click this button while holding Shift to put the temporary pivot in the center of the selected nodes.")); pan_button = memnew(Button); pan_button->set_theme_type_variation("FlatButton"); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index f7d52306c5..d2098de99a 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -4393,28 +4393,28 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { disk_changed = memnew(ConfirmationDialog); { - disk_changed->set_title(TTR("Files have been modified on disk")); + disk_changed->set_title(TTR("Files have been modified outside Godot")); VBoxContainer *vbc = memnew(VBoxContainer); disk_changed->add_child(vbc); Label *files_are_newer_label = memnew(Label); - files_are_newer_label->set_text(TTR("The following files are newer on disk.")); + files_are_newer_label->set_text(TTR("The following files are newer on disk:")); vbc->add_child(files_are_newer_label); - Label *what_action_label = memnew(Label); - what_action_label->set_text(TTR("What action should be taken?:")); - vbc->add_child(what_action_label); - disk_changed_list = memnew(Tree); vbc->add_child(disk_changed_list); disk_changed_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); disk_changed_list->set_v_size_flags(SIZE_EXPAND_FILL); + Label *what_action_label = memnew(Label); + what_action_label->set_text(TTR("What action should be taken?")); + vbc->add_child(what_action_label); + disk_changed->connect(SceneStringName(confirmed), callable_mp(this, &ScriptEditor::reload_scripts).bind(false)); - disk_changed->set_ok_button_text(TTR("Discard local changes and reload")); + disk_changed->set_ok_button_text(TTR("Reload from disk")); - disk_changed->add_button(TTR("Keep local changes and overwrite"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->add_button(TTR("Ignore external changes"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); disk_changed->connect("custom_action", callable_mp(this, &ScriptEditor::_resave_scripts)); } diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 94c54b69d7..32ea811c3a 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -953,7 +953,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c } else if (p_symbol.is_resource_file() || p_symbol.begins_with("uid://")) { String symbol = p_symbol; if (symbol.begins_with("uid://")) { - symbol = ResourceUID::get_singleton()->get_id_path(ResourceUID::get_singleton()->text_to_id(symbol)); + symbol = ResourceUID::uid_to_path(symbol); } List<String> scene_extensions; diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 649c2d849d..e714e9b4cc 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -1987,6 +1987,67 @@ bool VisualShaderEditor::_update_preview_parameter_tree() { return found; } +void VisualShaderEditor::_preview_tools_menu_option(int p_idx) { + ShaderMaterial *src_mat = nullptr; + + if (p_idx == COPY_PARAMS_FROM_MATERIAL || p_idx == PASTE_PARAMS_TO_MATERIAL) { + for (int i = EditorNode::get_singleton()->get_editor_selection_history()->get_path_size() - 1; i >= 0; i--) { + Object *object = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_selection_history()->get_path_object(i)); + ShaderMaterial *src_mat2; + if (!object) { + continue; + } + if (object->has_method("get_material_override")) { // Trying to get material from MeshInstance. + src_mat2 = Object::cast_to<ShaderMaterial>(object->call("get_material_override")); + } else if (object->has_method("get_material")) { // From CanvasItem/Node2D. + src_mat2 = Object::cast_to<ShaderMaterial>(object->call("get_material")); + } else { + src_mat2 = Object::cast_to<ShaderMaterial>(object); + } + + if (src_mat2 && src_mat2->get_shader().is_valid() && src_mat2->get_shader() == visual_shader) { + src_mat = src_mat2; + break; + } + } + } + + switch (p_idx) { + case COPY_PARAMS_FROM_MATERIAL: + if (src_mat) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Copy Preview Shader Parameters From Material")); + + List<PropertyInfo> params; + preview_material->get_shader()->get_shader_uniform_list(¶ms); + for (const PropertyInfo &E : params) { + undo_redo->add_do_method(visual_shader.ptr(), "_set_preview_shader_parameter", E.name, src_mat->get_shader_parameter(E.name)); + undo_redo->add_undo_method(visual_shader.ptr(), "_set_preview_shader_parameter", E.name, preview_material->get_shader_parameter(E.name)); + } + + undo_redo->commit_action(); + } + break; + case PASTE_PARAMS_TO_MATERIAL: + if (src_mat) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Paste Preview Shader Parameters To Material")); + + List<PropertyInfo> params; + preview_material->get_shader()->get_shader_uniform_list(¶ms); + for (const PropertyInfo &E : params) { + undo_redo->add_do_method(src_mat, "set_shader_parameter", E.name, preview_material->get_shader_parameter(E.name)); + undo_redo->add_undo_method(src_mat, "set_shader_parameter", E.name, src_mat->get_shader_parameter(E.name)); + } + + undo_redo->commit_action(); + } + break; + default: + break; + } +} + void VisualShaderEditor::_clear_preview_param() { selected_param_id = ""; current_prop = nullptr; @@ -4066,6 +4127,7 @@ void VisualShaderEditor::_connection_request(const String &p_from, int p_from_in int from = p_from.to_int(); int to = p_to.to_int(); + bool swap = last_to_node != -1 && Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); if (!visual_shader->can_connect_nodes(type, from, p_from_index, to, p_to_index)) { return; @@ -4083,6 +4145,14 @@ void VisualShaderEditor::_connection_request(const String &p_from, int p_from_in undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + + if (swap) { + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, last_to_node, last_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, last_to_node, last_to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, last_to_node, last_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, last_to_node, last_to_port); + } + break; } } @@ -4096,6 +4166,9 @@ void VisualShaderEditor::_connection_request(const String &p_from, int p_from_in undo_redo->add_do_method(graph_plugin.ptr(), "update_node", (int)type, to); undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", (int)type, to); undo_redo->commit_action(); + + last_to_node = -1; + last_to_port = -1; } void VisualShaderEditor::_disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { @@ -4106,6 +4179,11 @@ void VisualShaderEditor::_disconnection_request(const String &p_from, int p_from int from = p_from.to_int(); int to = p_to.to_int(); + last_to_node = to; + last_to_port = p_to_index; + + info_label->show(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Nodes Disconnected")); undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from, p_from_index, to, p_to_index); @@ -4117,6 +4195,10 @@ void VisualShaderEditor::_disconnection_request(const String &p_from, int p_from undo_redo->commit_action(); } +void VisualShaderEditor::_connection_drag_ended() { + info_label->hide(); +} + void VisualShaderEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { from_node = p_from.to_int(); from_slot = p_from_slot; @@ -5021,8 +5103,11 @@ void VisualShaderEditor::_param_property_changed(const String &p_property, const void VisualShaderEditor::_update_current_param() { if (current_prop != nullptr) { String name = current_prop->get_meta("id"); - preview_material->set("shader_parameter/" + name, visual_shader->_get_preview_shader_parameter(name)); - + if (visual_shader->_has_preview_shader_parameter(name)) { + preview_material->set("shader_parameter/" + name, visual_shader->_get_preview_shader_parameter(name)); + } else { + preview_material->set("shader_parameter/" + name, Variant()); + } current_prop->update_property(); current_prop->update_editor_property_status(); current_prop->update_cache(); @@ -5161,6 +5246,7 @@ void VisualShaderEditor::_notification(int p_what) { } tools->set_button_icon(get_editor_theme_icon(SNAME("Tools"))); + preview_tools->set_button_icon(get_editor_theme_icon(SNAME("Tools"))); if (is_visible_in_tree()) { _update_graph(); @@ -6307,6 +6393,7 @@ VisualShaderEditor::VisualShaderEditor() { graph->connect(SceneStringName(gui_input), callable_mp(this, &VisualShaderEditor::_graph_gui_input)); graph->connect("connection_to_empty", callable_mp(this, &VisualShaderEditor::_connection_to_empty)); graph->connect("connection_from_empty", callable_mp(this, &VisualShaderEditor::_connection_from_empty)); + graph->connect("connection_drag_ended", callable_mp(this, &VisualShaderEditor::_connection_drag_ended)); graph->connect(SceneStringName(visibility_changed), callable_mp(this, &VisualShaderEditor::_visibility_changed)); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR_INT); @@ -6367,6 +6454,13 @@ VisualShaderEditor::VisualShaderEditor() { graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_TRANSFORM, VisualShaderNode::PORT_TYPE_TRANSFORM); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SAMPLER, VisualShaderNode::PORT_TYPE_SAMPLER); + info_label = memnew(Label); + info_label->set_text(vformat(TTR("Hold %s Key To Swap Connections"), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL))); + info_label->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_WIDE, PRESET_MODE_MINSIZE, 20); + info_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + info_label->hide(); + graph->get_top_layer()->add_child(info_label); + PanelContainer *toolbar_panel = static_cast<PanelContainer *>(graph->get_menu_hbox()->get_parent()); toolbar_panel->set_anchors_and_offsets_preset(Control::PRESET_TOP_WIDE, PRESET_MODE_MINSIZE, 10); toolbar_panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); @@ -6557,11 +6651,21 @@ VisualShaderEditor::VisualShaderEditor() { VBoxContainer *params_vbox = memnew(VBoxContainer); preview_split->add_child(params_vbox); + HBoxContainer *filter_hbox = memnew(HBoxContainer); + params_vbox->add_child(filter_hbox); + param_filter = memnew(LineEdit); + filter_hbox->add_child(param_filter); param_filter->connect(SceneStringName(text_changed), callable_mp(this, &VisualShaderEditor::_param_filter_changed)); param_filter->set_h_size_flags(SIZE_EXPAND_FILL); param_filter->set_placeholder(TTR("Filter Parameters")); - params_vbox->add_child(param_filter); + + preview_tools = memnew(MenuButton); + filter_hbox->add_child(preview_tools); + preview_tools->set_tooltip_text(TTR("Options")); + preview_tools->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &VisualShaderEditor::_preview_tools_menu_option)); + preview_tools->get_popup()->add_item(TTR("Copy Parameters From Material"), COPY_PARAMS_FROM_MATERIAL); + preview_tools->get_popup()->add_item(TTR("Paste Parameters To Material"), PASTE_PARAMS_TO_MATERIAL); ScrollContainer *sc = memnew(ScrollContainer); sc->set_v_size_flags(SIZE_EXPAND_FILL); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 1877bd094e..51cab987cb 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -223,6 +223,10 @@ class VisualShaderEditor : public ShaderEditor { Button *code_preview_button = nullptr; Button *shader_preview_button = nullptr; + int last_to_node = -1; + int last_to_port = -1; + Label *info_label = nullptr; + OptionButton *edit_type = nullptr; OptionButton *edit_type_standard = nullptr; OptionButton *edit_type_particles = nullptr; @@ -278,6 +282,7 @@ class VisualShaderEditor : public ShaderEditor { bool shader_preview_showed = true; LineEdit *param_filter = nullptr; + MenuButton *preview_tools = nullptr; String selected_param_id; Tree *parameters = nullptr; HashMap<String, PropertyInfo> parameter_props; @@ -320,6 +325,11 @@ class VisualShaderEditor : public ShaderEditor { COLLAPSE_ALL }; + enum PreviewToolsMenuOptions { + COPY_PARAMS_FROM_MATERIAL, + PASTE_PARAMS_TO_MATERIAL, + }; + #ifdef MINGW_ENABLED #undef DELETE #endif @@ -369,6 +379,7 @@ class VisualShaderEditor : public ShaderEditor { void _show_add_varying_dialog(); void _show_remove_varying_dialog(); + void _preview_tools_menu_option(int p_idx); void _clear_preview_param(); void _update_preview_parameter_list(); bool _update_preview_parameter_tree(); @@ -497,6 +508,7 @@ class VisualShaderEditor : public ShaderEditor { void _unlink_node_from_parent_frame(int p_node_id); + void _connection_drag_ended(); void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); bool _check_node_drop_on_connection(const Vector2 &p_position, Ref<GraphEdit::Connection> *r_closest_connection, int *r_node_id = nullptr, int *r_to_port = nullptr); diff --git a/editor/pot_generator.cpp b/editor/pot_generator.cpp index 57a0183e67..8d23f9ad6c 100644 --- a/editor/pot_generator.cpp +++ b/editor/pot_generator.cpp @@ -71,11 +71,16 @@ void POTGenerator::generate_pot(const String &p_file) { for (int i = 0; i < files.size(); i++) { Vector<String> msgids; Vector<Vector<String>> msgids_context_plural; + + Vector<String> msgids_comment; + Vector<String> msgids_context_plural_comment; + const String &file_path = files[i]; String file_extension = file_path.get_extension(); if (EditorTranslationParser::get_singleton()->can_parse(file_extension)) { EditorTranslationParser::get_singleton()->get_parser(file_extension)->parse_file(file_path, &msgids, &msgids_context_plural); + EditorTranslationParser::get_singleton()->get_parser(file_extension)->get_comments(&msgids_comment, &msgids_context_plural_comment); } else { ERR_PRINT("Unrecognized file extension " + file_extension + " in generate_pot()"); return; @@ -83,16 +88,18 @@ void POTGenerator::generate_pot(const String &p_file) { for (int j = 0; j < msgids_context_plural.size(); j++) { const Vector<String> &entry = msgids_context_plural[j]; - _add_new_msgid(entry[0], entry[1], entry[2], file_path); + const String &comment = (j < msgids_context_plural_comment.size()) ? msgids_context_plural_comment[j] : String(); + _add_new_msgid(entry[0], entry[1], entry[2], file_path, comment); } for (int j = 0; j < msgids.size(); j++) { - _add_new_msgid(msgids[j], "", "", file_path); + const String &comment = (j < msgids_comment.size()) ? msgids_comment[j] : String(); + _add_new_msgid(msgids[j], "", "", file_path, comment); } } if (GLOBAL_GET("internationalization/locale/translation_add_builtin_strings_to_pot")) { for (const Vector<String> &extractable_msgids : get_extractable_message_list()) { - _add_new_msgid(extractable_msgids[0], extractable_msgids[1], extractable_msgids[2], ""); + _add_new_msgid(extractable_msgids[0], extractable_msgids[1], extractable_msgids[2], "", ""); } } @@ -138,15 +145,25 @@ void POTGenerator::_write_to_pot(const String &p_file) { String context = v_msgid_data[i].ctx; String plural = v_msgid_data[i].plural; const HashSet<String> &locations = v_msgid_data[i].locations; + const HashSet<String> &comments = v_msgid_data[i].comments; // Put the blank line at the start, to avoid a double at the end when closing the file. file->store_line(""); + // Write comments. + bool is_first_comment = true; + for (const String &E : comments) { + if (is_first_comment) { + file->store_line("#. TRANSLATORS: " + E.replace("\n", "\n#. ")); + } else { + file->store_line("#. " + E.replace("\n", "\n#. ")); + } + is_first_comment = false; + } + // Write file locations. for (const String &E : locations) { - if (!E.is_empty()) { - file->store_line("#: " + E.trim_prefix("res://").replace("\n", "\\n")); - } + file->store_line("#: " + E.trim_prefix("res://").replace("\n", "\\n")); } // Write context. @@ -201,7 +218,7 @@ void POTGenerator::_write_msgid(Ref<FileAccess> r_file, const String &p_id, bool } } -void POTGenerator::_add_new_msgid(const String &p_msgid, const String &p_context, const String &p_plural, const String &p_location) { +void POTGenerator::_add_new_msgid(const String &p_msgid, const String &p_context, const String &p_plural, const String &p_location, const String &p_comment) { // Insert new location if msgid under same context exists already. if (all_translation_strings.has(p_msgid)) { Vector<MsgidData> &v_mdata = all_translation_strings[p_msgid]; @@ -210,18 +227,27 @@ void POTGenerator::_add_new_msgid(const String &p_msgid, const String &p_context if (!v_mdata[i].plural.is_empty() && !p_plural.is_empty() && v_mdata[i].plural != p_plural) { WARN_PRINT("Redefinition of plural message (msgid_plural), under the same message (msgid) and context (msgctxt)"); } - v_mdata.write[i].locations.insert(p_location); + if (!p_location.is_empty()) { + v_mdata.write[i].locations.insert(p_location); + } + if (!p_comment.is_empty()) { + v_mdata.write[i].comments.insert(p_comment); + } return; } } } - // Add a new entry of msgid, context, plural and location - context and plural might be empty if the inserted msgid doesn't associated - // context or plurals. + // Add a new entry. MsgidData mdata; mdata.ctx = p_context; mdata.plural = p_plural; - mdata.locations.insert(p_location); + if (!p_location.is_empty()) { + mdata.locations.insert(p_location); + } + if (!p_comment.is_empty()) { + mdata.comments.insert(p_comment); + } all_translation_strings[p_msgid].push_back(mdata); } diff --git a/editor/pot_generator.h b/editor/pot_generator.h index 8a18c7b49e..7639f98159 100644 --- a/editor/pot_generator.h +++ b/editor/pot_generator.h @@ -46,13 +46,14 @@ class POTGenerator { String ctx; String plural; HashSet<String> locations; + HashSet<String> comments; }; // Store msgid as key and the additional data around the msgid - if it's under a context, has plurals and its file locations. HashMap<String, Vector<MsgidData>> all_translation_strings; void _write_to_pot(const String &p_file); void _write_msgid(Ref<FileAccess> r_file, const String &p_id, bool p_plural); - void _add_new_msgid(const String &p_msgid, const String &p_context, const String &p_plural, const String &p_location); + void _add_new_msgid(const String &p_msgid, const String &p_context, const String &p_plural, const String &p_location, const String &p_comment); #ifdef DEBUG_POT void _print_all_translation_strings(); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index ce38ca0a1d..c6f040c44d 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -36,8 +36,6 @@ #include "core/io/config_file.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" -#include "core/io/resource_saver.h" -#include "core/io/stream_peer_tls.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/version.h" @@ -53,12 +51,9 @@ #include "editor/project_manager/project_list.h" #include "editor/project_manager/project_tag.h" #include "editor/project_manager/quick_settings_dialog.h" -#include "editor/themes/editor_icons.h" #include "editor/themes/editor_scale.h" #include "editor/themes/editor_theme_manager.h" #include "main/main.h" -#include "scene/gui/check_box.h" -#include "scene/gui/color_rect.h" #include "scene/gui/flow_container.h" #include "scene/gui/line_edit.h" #include "scene/gui/margin_container.h" @@ -66,7 +61,6 @@ #include "scene/gui/panel_container.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/separator.h" -#include "scene/gui/texture_rect.h" #include "scene/main/window.h" #include "scene/theme/theme_db.h" #include "servers/display_server.h" @@ -1302,15 +1296,10 @@ ProjectManager::ProjectManager() { filter_option->connect(SceneStringName(item_selected), callable_mp(this, &ProjectManager::_on_order_option_changed)); hb->add_child(filter_option); - Vector<String> sort_filter_titles; - sort_filter_titles.push_back(TTR("Last Edited")); - sort_filter_titles.push_back(TTR("Name")); - sort_filter_titles.push_back(TTR("Path")); - sort_filter_titles.push_back(TTR("Tags")); - - for (int i = 0; i < sort_filter_titles.size(); i++) { - filter_option->add_item(sort_filter_titles[i]); - } + filter_option->add_item(TTR("Last Edited")); + filter_option->add_item(TTR("Name")); + filter_option->add_item(TTR("Path")); + filter_option->add_item(TTR("Tags")); } // Project list and its sidebar. diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 7c2b151f2e..9849f04ee6 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -33,6 +33,7 @@ #include "project_settings_editor.h" #include "core/config/project_settings.h" +#include "core/input/input_map.h" #include "editor/editor_inspector.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" @@ -392,7 +393,7 @@ void ProjectSettingsEditor::_action_added(const String &p_name) { Dictionary action; action["events"] = Array(); - action["deadzone"] = 0.2f; + action["deadzone"] = InputMap::DEFAULT_DEADZONE; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Input Action")); diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 97b9be6fe9..ca91f8357a 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -361,6 +361,7 @@ void ScriptCreateDialog::_create_new() { alert->popup_centered(); return; } + EditorNode::get_singleton()->ensure_uid_file(lpath); } emit_signal(SNAME("script_created"), scr); diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp index 165b2d3e3f..fc9285a67e 100644 --- a/editor/shader_create_dialog.cpp +++ b/editor/shader_create_dialog.cpp @@ -33,6 +33,7 @@ #include "shader_create_dialog.h" #include "core/config/project_settings.h" +#include "editor/editor_node.h" #include "editor/gui/editor_file_dialog.h" #include "editor/gui/editor_validation_panel.h" #include "editor/themes/editor_scale.h" @@ -242,6 +243,7 @@ void fog() { alert->popup_centered(); return; } + EditorNode::get_singleton()->ensure_uid_file(lpath); emit_signal(SNAME("shader_include_created"), shader_inc); } else { @@ -260,6 +262,7 @@ void fog() { alert->popup_centered(); return; } + EditorNode::get_singleton()->ensure_uid_file(lpath); } emit_signal(SNAME("shader_created"), shader); diff --git a/main/main.cpp b/main/main.cpp index 8c5e062436..f328d5ebf7 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2556,7 +2556,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph Engine::get_singleton()->set_physics_ticks_per_second(GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "physics/common/physics_ticks_per_second", PROPERTY_HINT_RANGE, "1,1000,1"), 60)); Engine::get_singleton()->set_max_physics_steps_per_frame(GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "physics/common/max_physics_steps_per_frame", PROPERTY_HINT_RANGE, "1,100,1"), 8)); Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5)); - Engine::get_singleton()->set_max_fps(GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/max_fps", PROPERTY_HINT_RANGE, "0,1000,1"), 0)); GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "audio/driver/output_latency", PROPERTY_HINT_RANGE, "1,100,1"), 15); // Use a safer default output_latency for web to avoid audio cracking on low-end devices, especially mobile. @@ -2577,10 +2576,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->set_environment("MVK_CONFIG_LOG_LEVEL", OS::get_singleton()->_verbose_stdout ? "3" : "1"); // 1 = Errors only, 3 = Info #endif - if (max_fps >= 0) { - Engine::get_singleton()->set_max_fps(max_fps); - } - if (frame_delay == 0) { frame_delay = GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/frame_delay_msec", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), 0); if (Engine::get_singleton()->is_editor_hint()) { @@ -3026,6 +3021,13 @@ Error Main::setup2(bool p_show_boot_logo) { OS::get_singleton()->benchmark_end_measure("Servers", "Display"); } + // Max FPS needs to be set after the DisplayServer is created. + Engine::get_singleton()->set_max_fps(GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/max_fps", PROPERTY_HINT_RANGE, "0,1000,1"), 0)); + + if (max_fps >= 0) { + Engine::get_singleton()->set_max_fps(max_fps); + } + #ifdef TOOLS_ENABLED // If the editor is running in windowed mode, ensure the window rect fits // the screen in case screen count or position has changed. diff --git a/methods.py b/methods.py index 623c2df800..8c0de8ec9d 100644 --- a/methods.py +++ b/methods.py @@ -1,5 +1,7 @@ +import atexit import contextlib import glob +import math import os import re import subprocess @@ -8,7 +10,7 @@ from collections import OrderedDict from enum import Enum from io import StringIO, TextIOWrapper from pathlib import Path -from typing import Generator, List, Optional, Union +from typing import Generator, List, Optional, Union, cast # Get the "Redot" folder name ahead of time base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/" @@ -421,8 +423,7 @@ def use_windows_spawn_fix(self, platform=None): "shell": False, "env": env, } - if sys.version_info >= (3, 7, 0): - popen_args["text"] = True + popen_args["text"] = True proc = subprocess.Popen(cmdline, **popen_args) _, err = proc.communicate() rv = proc.wait() @@ -605,23 +606,6 @@ def glob_recursive(pattern, node="."): return results -def add_to_vs_project(env, sources): - for x in sources: - fname = env.File(x).path if isinstance(x, str) else env.File(x)[0].path - pieces = fname.split(".") - if len(pieces) > 0: - basename = pieces[0] - basename = basename.replace("\\\\", "/") - if os.path.isfile(basename + ".h"): - env.vs_incs += [basename + ".h"] - elif os.path.isfile(basename + ".hpp"): - env.vs_incs += [basename + ".hpp"] - if os.path.isfile(basename + ".c"): - env.vs_srcs += [basename + ".c"] - elif os.path.isfile(basename + ".cpp"): - env.vs_srcs += [basename + ".cpp"] - - def precious_program(env, program, sources, **args): program = env.ProgramOriginal(program, sources, **args) env.Precious(program) @@ -682,7 +666,9 @@ def detect_darwin_sdk_path(platform, env): raise -def is_vanilla_clang(env): +def is_apple_clang(env): + if env["platform"] not in ["macos", "ios"]: + return False if not using_clang(env): return False try: @@ -690,7 +676,7 @@ def is_vanilla_clang(env): except (subprocess.CalledProcessError, OSError): print_warning("Couldn't parse CXX environment variable to infer compiler version.") return False - return not version.startswith("Apple") + return version.startswith("Apple") def get_compiler_version(env): @@ -814,159 +800,160 @@ def using_emcc(env): def show_progress(env): - if env["ninja"]: - # Has its own progress/tracking tool that clashes with ours + # Progress reporting is not available in non-TTY environments since it messes with the output + # (for example, when writing to a file). Ninja has its own progress/tracking tool that clashes + # with ours. + if not env["progress"] or not sys.stdout.isatty() or env["ninja"]: return - import sys - - from SCons.Script import AlwaysBuild, Command, Progress - - screen = sys.stdout - # Progress reporting is not available in non-TTY environments since it - # messes with the output (for example, when writing to a file) - show_progress = env["progress"] and sys.stdout.isatty() - node_count = 0 - node_count_max = 0 - node_count_interval = 1 - node_count_fname = str(env.Dir("#")) + "/.scons_node_count" - - import math - - class cache_progress: - # The default is 1 GB cache - def __init__(self, path=None, limit=pow(1024, 3)): - self.path = path - self.limit = limit - if env["verbose"] and path is not None: - screen.write( - "Current cache limit is {} (used: {})\n".format( - self.convert_size(limit), self.convert_size(self.get_size(path)) - ) - ) + NODE_COUNT_FILENAME = f"{base_folder_path}.scons_node_count" + + class ShowProgress: + def __init__(self): + self.count = 0 + self.max = 0 + try: + with open(NODE_COUNT_FILENAME, "r", encoding="utf-8") as f: + self.max = int(f.readline()) + except OSError: + pass + if self.max == 0: + print("NOTE: Performing initial build, progress percentage unavailable!") def __call__(self, node, *args, **kw): - nonlocal node_count, node_count_max, node_count_interval, node_count_fname, show_progress - if show_progress: - # Print the progress percentage - node_count += node_count_interval - if node_count_max > 0 and node_count <= node_count_max: - screen.write("\r[%3d%%] " % (node_count * 100 / node_count_max)) - screen.flush() - elif node_count_max > 0 and node_count > node_count_max: - screen.write("\r[100%] ") - screen.flush() - else: - screen.write("\r[Initial build] ") - screen.flush() - - def convert_size(self, size_bytes): - if size_bytes == 0: - return "0 bytes" - size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") - i = int(math.floor(math.log(size_bytes, 1024))) - p = math.pow(1024, i) - s = round(size_bytes / p, 2) - return "%s %s" % (int(s) if i == 0 else s, size_name[i]) - - def get_size(self, start_path="."): - total_size = 0 - for dirpath, dirnames, filenames in os.walk(start_path): - for f in filenames: - fp = os.path.join(dirpath, f) - total_size += os.path.getsize(fp) - return total_size + self.count += 1 + if self.max != 0: + percent = int(min(self.count * 100 / self.max, 100)) + sys.stdout.write(f"\r[{percent:3d}%] ") + sys.stdout.flush() + + from SCons.Script import Progress + + progressor = ShowProgress() + Progress(progressor) def progress_finish(target, source, env): - nonlocal node_count, progressor try: - with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f: - f.write("%d\n" % node_count) - except Exception: + with open(NODE_COUNT_FILENAME, "w", encoding="utf-8", newline="\n") as f: + f.write(f"{progressor.count}\n") + except OSError: pass - try: - with open(node_count_fname, "r", encoding="utf-8") as f: - node_count_max = int(f.readline()) - except Exception: - pass + env.AlwaysBuild( + env.CommandNoCache( + "progress_finish", [], env.Action(progress_finish, "Building node count database .scons_node_count") + ) + ) + + +def convert_size(size_bytes: int) -> str: + if size_bytes == 0: + return "0 bytes" + SIZE_NAMES = ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] + index = math.floor(math.log(size_bytes, 1024)) + power = math.pow(1024, index) + size = round(size_bytes / power, 2) + return f"{size} {SIZE_NAMES[index]}" + + +def get_size(start_path: str = ".") -> int: + total_size = 0 + for dirpath, _, filenames in os.walk(start_path): + for file in filenames: + path = os.path.join(dirpath, file) + total_size += os.path.getsize(path) + return total_size - cache_directory = os.environ.get("SCONS_CACHE") - # Simple cache pruning, attached to SCons' progress callback. Trim the - # cache directory to a size not larger than cache_limit. - cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024 - progressor = cache_progress(cache_directory, cache_limit) - Progress(progressor, interval=node_count_interval) - - progress_finish_command = Command("progress_finish", [], progress_finish) - AlwaysBuild(progress_finish_command) - - -def clean_cache(env): - import atexit - import time - - class cache_clean: - def __init__(self, path=None, limit=pow(1024, 3)): - self.path = path - self.limit = limit - - def clean(self): - self.delete(self.file_list()) - - def delete(self, files): - if len(files) == 0: - return - if env["verbose"]: - # Utter something - print("Purging %d %s from cache..." % (len(files), "files" if len(files) > 1 else "file")) - [os.remove(f) for f in files] - - def file_list(self): - if self.path is None: - # Nothing to do - return [] - # Gather a list of (filename, (size, atime)) within the - # cache directory - file_stat = [(x, os.stat(x)[6:8]) for x in glob.glob(os.path.join(self.path, "*", "*"))] - if file_stat == []: - # Nothing to do - return [] - # Weight the cache files by size (assumed to be roughly - # proportional to the recompilation time) times an exponential - # decay since the ctime, and return a list with the entries - # (filename, size, weight). - current_time = time.time() - file_stat = [(x[0], x[1][0], (current_time - x[1][1])) for x in file_stat] - # Sort by the most recently accessed files (most sensible to keep) first - file_stat.sort(key=lambda x: x[2]) - # Search for the first entry where the storage limit is - # reached - sum, mark = 0, None - for i, x in enumerate(file_stat): - sum += x[1] - if sum > self.limit: - mark = i - break - if mark is None: - return [] - else: - return [x[0] for x in file_stat[mark:]] - def cache_finally(): - nonlocal cleaner +def clean_cache(cache_path: str, cache_limit: int, verbose: bool): + files = glob.glob(os.path.join(cache_path, "*", "*")) + if not files: + return + + # Remove all text files, store binary files in list of (filename, size, atime). + purge = [] + texts = [] + stats = [] + for file in files: + # Failing a utf-8 decode is the easiest way to determine if a file is binary. try: - cleaner.clean() - except Exception: - pass + with open(file, encoding="utf-8") as out: + out.read(1024) + except UnicodeDecodeError: + stats.append((file, *os.stat(file)[6:8])) + except OSError: + print_error(f'Failed to access cache file "{file}"; skipping.') + else: + texts.append(file) + + if texts: + count = len(texts) + for file in texts: + try: + os.remove(file) + except OSError: + print_error(f'Failed to remove cache file "{file}"; skipping.') + count -= 1 + if verbose: + print("Purging %d text %s from cache..." % (count, "files" if count > 1 else "file")) + + if cache_limit: + # Sort by most recent access (most sensible to keep) first. Search for the first entry where + # the cache limit is reached. + stats.sort(key=lambda x: x[2], reverse=True) + sum = 0 + for index, stat in enumerate(stats): + sum += stat[1] + if sum > cache_limit: + purge.extend([x[0] for x in stats[index:]]) + break + + if purge: + count = len(purge) + for file in purge: + try: + os.remove(file) + except OSError: + print_error(f'Failed to remove cache file "{file}"; skipping.') + count -= 1 + if verbose: + print("Purging %d %s from cache..." % (count, "files" if count > 1 else "file")) + + +def prepare_cache(env) -> None: + if env.GetOption("clean"): + return + + cache_path = "" + if env["cache_path"]: + cache_path = cast(str, env["cache_path"]) + elif os.environ.get("SCONS_CACHE"): + print_warning("Environment variable `SCONS_CACHE` is deprecated; use `cache_path` argument instead.") + cache_path = cast(str, os.environ.get("SCONS_CACHE")) - cache_directory = os.environ.get("SCONS_CACHE") - # Simple cache pruning, attached to SCons' progress callback. Trim the - # cache directory to a size not larger than cache_limit. - cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024 - cleaner = cache_clean(cache_directory, cache_limit) + if not cache_path: + return - atexit.register(cache_finally) + env.CacheDir(cache_path) + print(f'SCons cache enabled... (path: "{cache_path}")') + + if env["cache_limit"]: + cache_limit = float(env["cache_limit"]) + elif os.environ.get("SCONS_CACHE_LIMIT"): + print_warning("Environment variable `SCONS_CACHE_LIMIT` is deprecated; use `cache_limit` argument instead.") + cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", "0")) / 1024 # Old method used MiB, convert to GiB + + # Convert GiB to bytes; treat negative numbers as 0 (unlimited). + cache_limit = max(0, int(cache_limit * 1024 * 1024 * 1024)) + if env["verbose"]: + print( + "Current cache limit is {} (used: {})".format( + convert_size(cache_limit) if cache_limit else "∞", + convert_size(get_size(cache_path)), + ) + ) + + atexit.register(clean_cache, cache_path, cache_limit, env["verbose"]) def dump(env): @@ -1124,9 +1111,7 @@ def generate_vs_project(env, original_args, project_name="redot"): import json md5 = hashlib.md5( - json.dumps(headers + headers_dirs + sources + sources_dirs + others + others_dirs, sort_keys=True).encode( - "utf-8" - ) + json.dumps(sorted(headers + headers_dirs + sources + sources_dirs + others + others_dirs)).encode("utf-8") ).hexdigest() if os.path.exists(f"{project_name}.vcxproj.filters"): diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index 4f5ebc7a68..75e81b5ff4 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -115,3 +115,10 @@ GH-91201 Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/OS/methods/read_string_from_stdin': arguments Added optional argument. Compatibility method registered. + + +GH-98918 +-------- +Validate extension JSON: Error: Field 'classes/FileAccess/methods/open_encrypted/arguments': size changed value in new API, from 3 to 4. + +Optional argument added to allow setting initialization vector. Compatibility method registered. diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 8bba8b3fd5..b736b68b08 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -53,6 +53,10 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve ids = r_ids; ids_ctx_plural = r_ids_ctx_plural; + + ids_comment.clear(); + ids_ctx_plural_comment.clear(); + Ref<GDScript> gdscript = loaded_res; String source_code = gdscript->get_source_code(); @@ -64,18 +68,90 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve err = analyzer.analyze(); ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer."); + comment_data = &parser.comment_data; + // Traverse through the parsed tree from GDScriptParser. GDScriptParser::ClassNode *c = parser.get_tree(); _traverse_class(c); + comment_data = nullptr; + return OK; } +void GDScriptEditorTranslationParserPlugin::get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) { + r_ids_comment->append_array(ids_comment); + r_ids_ctx_plural_comment->append_array(ids_ctx_plural_comment); +} + bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) { ERR_FAIL_NULL_V(p_expression, false); return p_expression->is_constant && p_expression->reduced_value.is_string(); } +String GDScriptEditorTranslationParserPlugin::_parse_comment(int p_line, bool &r_skip) const { + // Parse inline comment. + if (comment_data->has(p_line)) { + const String stripped_comment = comment_data->get(p_line).comment.trim_prefix("#").strip_edges(); + + if (stripped_comment.begins_with("TRANSLATORS:")) { + return stripped_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false); + } + if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) { + r_skip = true; + return String(); + } + } + + // Parse multiline comment. + String multiline_comment; + for (int line = p_line - 1; comment_data->has(line) && comment_data->get(line).new_line; line--) { + const String stripped_comment = comment_data->get(line).comment.trim_prefix("#").strip_edges(); + + if (stripped_comment.is_empty()) { + continue; + } + + if (multiline_comment.is_empty()) { + multiline_comment = stripped_comment; + } else { + multiline_comment = stripped_comment + "\n" + multiline_comment; + } + + if (stripped_comment.begins_with("TRANSLATORS:")) { + return multiline_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false); + } + if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) { + r_skip = true; + return String(); + } + } + + return String(); +} + +void GDScriptEditorTranslationParserPlugin::_add_id(const String &p_id, int p_line) { + bool skip = false; + const String comment = _parse_comment(p_line, skip); + if (skip) { + return; + } + + ids->push_back(p_id); + ids_comment.push_back(comment); +} + +void GDScriptEditorTranslationParserPlugin::_add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line) { + bool skip = false; + const String comment = _parse_comment(p_line, skip); + if (skip) { + return; + } + + ids_ctx_plural->push_back(p_id_ctx_plural); + ids_ctx_plural_comment.push_back(comment); +} + void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) { for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &m = p_class->members[i]; @@ -255,7 +331,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) { // If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string. - ids->push_back(p_assignment->assigned_value->reduced_value); + _add_id(p_assignment->assigned_value->reduced_value, p_assignment->assigned_value->start_line); } else if (assignee_name == fd_filters) { // Extract from `get_node("FileDialog").filters = <filter array>`. _extract_fd_filter_array(p_assignment->assigned_value); @@ -289,7 +365,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C } } if (extract_id_ctx_plural) { - ids_ctx_plural->push_back(id_ctx_plural); + _add_id_ctx_plural(id_ctx_plural, p_call->start_line); } } else if (function_name == trn_func || function_name == atrn_func) { // Extract from `tr_n(id, plural, n, ctx)` or `atr_n(id, plural, n, ctx)`. @@ -309,20 +385,20 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C } } if (extract_id_ctx_plural) { - ids_ctx_plural->push_back(id_ctx_plural); + _add_id_ctx_plural(id_ctx_plural, p_call->start_line); } } else if (first_arg_patterns.has(function_name)) { if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) { - ids->push_back(p_call->arguments[0]->reduced_value); + _add_id(p_call->arguments[0]->reduced_value, p_call->arguments[0]->start_line); } } else if (second_arg_patterns.has(function_name)) { if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) { - ids->push_back(p_call->arguments[1]->reduced_value); + _add_id(p_call->arguments[1]->reduced_value, p_call->arguments[1]->start_line); } } else if (function_name == fd_add_filter) { // Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images"). if (!p_call->arguments.is_empty()) { - _extract_fd_filter_string(p_call->arguments[0]); + _extract_fd_filter_string(p_call->arguments[0], p_call->arguments[0]->start_line); } } else if (function_name == fd_set_filter) { // Extract from `get_node("FileDialog").set_filters(<filter array>)`. @@ -332,12 +408,12 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C } } -void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) { +void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line) { // Extract the name in "extension ; name". if (_is_constant_string(p_expression)) { PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true); ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format."); - ids->push_back(arr[1].strip_edges()); + _add_id(arr[1].strip_edges(), p_line); } } @@ -357,7 +433,7 @@ void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScr if (array_node) { for (int i = 0; i < array_node->elements.size(); i++) { - _extract_fd_filter_string(array_node->elements[i]); + _extract_fd_filter_string(array_node->elements[i], array_node->elements[i]->start_line); } } } diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index f7634505f2..987301fc22 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -34,16 +34,23 @@ #define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H #include "../gdscript_parser.h" +#include "../gdscript_tokenizer.h" +#include "core/templates/hash_map.h" #include "core/templates/hash_set.h" #include "editor/editor_translation_parser.h" class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin { GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin); + const HashMap<int, GDScriptTokenizer::CommentData> *comment_data = nullptr; + Vector<String> *ids = nullptr; Vector<Vector<String>> *ids_ctx_plural = nullptr; + Vector<String> ids_comment; + Vector<String> ids_ctx_plural_comment; + // List of patterns used for extracting translation strings. StringName tr_func = "tr"; StringName trn_func = "tr_n"; @@ -59,6 +66,11 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug static bool _is_constant_string(const GDScriptParser::ExpressionNode *p_expression); + String _parse_comment(int p_line, bool &r_skip) const; + + void _add_id(const String &p_id, int p_line); + void _add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line); + void _traverse_class(const GDScriptParser::ClassNode *p_class); void _traverse_function(const GDScriptParser::FunctionNode *p_func); void _traverse_block(const GDScriptParser::SuiteNode *p_suite); @@ -67,11 +79,12 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment); void _assess_call(const GDScriptParser::CallNode *p_call); - void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression); + void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line); void _extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression); public: virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override; + virtual void get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) override; virtual void get_recognized_extensions(List<String> *r_extensions) const override; GDScriptEditorTranslationParserPlugin(); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 7ff36f94fb..eebabe9fe0 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -411,6 +411,10 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ parse_program(); pop_multiline(); +#ifdef TOOLS_ENABLED + comment_data = tokenizer->get_comments(); +#endif + memdelete(text_tokenizer); tokenizer = nullptr; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index ae5890294b..3d85cbd6fe 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1603,6 +1603,8 @@ public: #ifdef TOOLS_ENABLED static HashMap<String, String> theme_color_names; + + HashMap<int, GDScriptTokenizer::CommentData> comment_data; #endif // TOOLS_ENABLED GDScriptParser(); diff --git a/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn b/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn index d3dea6b12b..082c87e708 100644 --- a/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn +++ b/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn @@ -1,3 +1,16 @@ [gd_scene load_steps=1 format=3 uid="uid://dl28pdkxcjvym"] +[sub_resource type="Animation" id="Animation_d1pub"] +resource_name = "bounce" + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_gs7mj"] +_data = { +"bounce": SubResource("Animation_d1pub") +} + [node name="GetNode" type="Node"] + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +"": SubResource("AnimationLibrary_gs7mj") +} diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg new file mode 100644 index 0000000000..ca108a18c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg @@ -0,0 +1,6 @@ +[input] +scene="res://completion/argument_options/argument_options.tscn" +[output] +include=[ + {"display": "\"bounce\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd new file mode 100644 index 0000000000..abeadbe5ee --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd @@ -0,0 +1,5 @@ +@onready var anim := $AnimationPlayer + +func test(): + anim.play(➡) + pass diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg new file mode 100644 index 0000000000..ca108a18c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg @@ -0,0 +1,6 @@ +[input] +scene="res://completion/argument_options/argument_options.tscn" +[output] +include=[ + {"display": "\"bounce\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd new file mode 100644 index 0000000000..d11f81e985 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd @@ -0,0 +1,5 @@ +@onready var anim: AnimationPlayer = $AnimationPlayer + +func test(): + anim.play(➡) + pass diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg new file mode 100644 index 0000000000..ca108a18c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg @@ -0,0 +1,6 @@ +[input] +scene="res://completion/argument_options/argument_options.tscn" +[output] +include=[ + {"display": "\"bounce\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd new file mode 100644 index 0000000000..4ddfd21ac6 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd @@ -0,0 +1,5 @@ +@onready var anim = $AnimationPlayer + +func test(): + anim.play(➡) + pass diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index 84be3cd5be..bf2a544e59 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -648,6 +648,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D Ref<InputEventKey> k = p_event; if (k.is_valid() && k->is_pressed() && !k->is_echo()) { + // Transform mode (toggle button): // If we are in Transform mode we pass the events to the 3D editor, // but if the Transform mode shortcut is pressed again, we go back to Selection mode. if (mode_buttons_group->get_pressed_button() == transform_mode_button) { @@ -658,7 +659,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D } return EditorPlugin::AFTER_GUI_INPUT_PASS; } - + // Tool modes and tool actions: for (BaseButton *b : viewport_shortcut_buttons) { if (b->is_disabled()) { continue; @@ -675,9 +676,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D return EditorPlugin::AFTER_GUI_INPUT_STOP; } } - } - - if (k.is_valid() && k->is_pressed() && !k->is_echo()) { + // Hard key actions: if (k->get_keycode() == Key::ESCAPE) { if (input_action == INPUT_PASTE) { _clear_clipboard_data(); @@ -694,7 +693,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D return EditorPlugin::AFTER_GUI_INPUT_STOP; } } - + // Options menu shortcuts: Ref<Shortcut> ed_shortcut = ED_GET_SHORTCUT("grid_map/previous_floor"); if (ed_shortcut.is_valid() && ed_shortcut->matches_event(p_event)) { accept_event(); @@ -1398,6 +1397,7 @@ GridMapEditor::GridMapEditor() { fill_action_button->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_FILL)); action_buttons->add_child(fill_action_button); + viewport_shortcut_buttons.push_back(fill_action_button); move_action_button = memnew(Button); move_action_button->set_theme_type_variation("FlatButton"); @@ -1405,6 +1405,7 @@ GridMapEditor::GridMapEditor() { move_action_button->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_CUT)); action_buttons->add_child(move_action_button); + viewport_shortcut_buttons.push_back(move_action_button); duplicate_action_button = memnew(Button); duplicate_action_button->set_theme_type_variation("FlatButton"); @@ -1412,6 +1413,7 @@ GridMapEditor::GridMapEditor() { duplicate_action_button->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_DUPLICATE)); action_buttons->add_child(duplicate_action_button); + viewport_shortcut_buttons.push_back(duplicate_action_button); delete_action_button = memnew(Button); delete_action_button->set_theme_type_variation("FlatButton"); @@ -1419,6 +1421,7 @@ GridMapEditor::GridMapEditor() { delete_action_button->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_CLEAR)); action_buttons->add_child(delete_action_button); + viewport_shortcut_buttons.push_back(delete_action_button); vsep = memnew(VSeparator); toolbar->add_child(vsep); @@ -1432,6 +1435,7 @@ GridMapEditor::GridMapEditor() { rotate_x_button->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_X)); rotation_buttons->add_child(rotate_x_button); + viewport_shortcut_buttons.push_back(rotate_x_button); rotate_y_button = memnew(Button); rotate_y_button->set_theme_type_variation("FlatButton"); @@ -1439,6 +1443,7 @@ GridMapEditor::GridMapEditor() { rotate_y_button->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_Y)); rotation_buttons->add_child(rotate_y_button); + viewport_shortcut_buttons.push_back(rotate_y_button); rotate_z_button = memnew(Button); rotate_z_button->set_theme_type_variation("FlatButton"); @@ -1446,6 +1451,7 @@ GridMapEditor::GridMapEditor() { rotate_z_button->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_Z)); rotation_buttons->add_child(rotate_z_button); + viewport_shortcut_buttons.push_back(rotate_z_button); // Wide empty separation control. (like BoxContainer::add_spacer()) Control *c = memnew(Control); @@ -1458,9 +1464,9 @@ GridMapEditor::GridMapEditor() { floor->set_max(32767); floor->set_step(1); floor->set_tooltip_text( - TTR(vformat("Change Grid Floor:\nPrevious Plane (%s)\nNext Plane (%s)", + vformat(TTR("Change Grid Floor:\nPrevious Plane (%s)\nNext Plane (%s)"), ED_GET_SHORTCUT("grid_map/previous_floor")->get_as_text(), - ED_GET_SHORTCUT("grid_map/next_floor")->get_as_text()))); + ED_GET_SHORTCUT("grid_map/next_floor")->get_as_text())); toolbar->add_child(floor); floor->get_line_edit()->add_theme_constant_override("minimum_character_width", 2); floor->get_line_edit()->set_context_menu_enabled(false); diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml index c2d879962c..edcaa3baef 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml @@ -53,7 +53,7 @@ </methods> <members> <member name="delta_interval" type="float" setter="set_delta_interval" getter="get_delta_interval" default="0.0"> - Time interval between delta synchronizations. When set to [code]0.0[/code] (the default), delta synchronizations happen every network process frame. + Time interval between delta synchronizations. Used when the replication is set to [constant SceneReplicationConfig.REPLICATION_MODE_ON_CHANGE]. If set to [code]0.0[/code] (the default), delta synchronizations happen every network process frame. </member> <member name="public_visibility" type="bool" setter="set_visibility_public" getter="is_visibility_public" default="true"> Whether synchronization should be visible to all peers by default. See [method set_visibility_for] and [method add_visibility_filter] for ways of configuring fine-grained visibility options. @@ -62,7 +62,7 @@ Resource containing which properties to synchronize. </member> <member name="replication_interval" type="float" setter="set_replication_interval" getter="get_replication_interval" default="0.0"> - Time interval between synchronizations. When set to [code]0.0[/code] (the default), synchronizations happen every network process frame. + Time interval between synchronizations. Used when the replication is set to [constant SceneReplicationConfig.REPLICATION_MODE_ALWAYS]. If set to [code]0.0[/code] (the default), synchronizations happen every network process frame. </member> <member name="root_path" type="NodePath" setter="set_root_path" getter="get_root_path" default="NodePath("..")"> Node path that replicated properties are relative to. diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 99ecb416ed..968241ab47 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -852,7 +852,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation using namespace Clipper2Lib; PathsD traversable_polygon_paths; PathsD obstruction_polygon_paths; - int obstruction_polygon_path_size = 0; + bool empty_projected_obstructions = true; { RWLockRead read_lock(p_source_geometry_data->geometry_rwlock); @@ -888,7 +888,8 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation traversable_polygon_paths.push_back(std::move(subject_path)); } - if (!projected_obstructions.is_empty()) { + empty_projected_obstructions = projected_obstructions.is_empty(); + if (!empty_projected_obstructions) { for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { if (projected_obstruction.carve) { continue; @@ -909,7 +910,6 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation } } - obstruction_polygon_path_size = obstruction_polygon_paths.size(); for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) { PathD clip_path; clip_path.reserve(obstruction_outline.size()); @@ -921,7 +921,6 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation } Rect2 baking_rect = p_navigation_mesh->get_baking_rect(); - PathsD area_obstruction_polygon_paths; if (baking_rect.has_area()) { Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset(); @@ -933,27 +932,48 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation RectD clipper_rect = RectD(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y); traversable_polygon_paths = RectClip(clipper_rect, traversable_polygon_paths); - area_obstruction_polygon_paths = RectClip(clipper_rect, obstruction_polygon_paths); - } else { - area_obstruction_polygon_paths = obstruction_polygon_paths; + obstruction_polygon_paths = RectClip(clipper_rect, obstruction_polygon_paths); } // first merge all traversable polygons according to user specified fill rule PathsD dummy_clip_path; traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, FillRule::NonZero); // merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry - area_obstruction_polygon_paths = Union(area_obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero); + obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero); - PathsD path_solution = Difference(traversable_polygon_paths, area_obstruction_polygon_paths, FillRule::NonZero); + PathsD path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero); real_t agent_radius_offset = p_navigation_mesh->get_agent_radius(); if (agent_radius_offset > 0.0) { path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon); } - if (obstruction_polygon_path_size > 0) { - obstruction_polygon_paths.resize(obstruction_polygon_path_size); - path_solution = Difference(path_solution, obstruction_polygon_paths, FillRule::NonZero); + // Apply obstructions that are not affected by agent radius, the ones with carve enabled. + if (!empty_projected_obstructions) { + RWLockRead read_lock(p_source_geometry_data->geometry_rwlock); + const Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_projected_obstructions; + obstruction_polygon_paths.resize(0); + for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (!projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) { + continue; + } + + PathD clip_path; + clip_path.reserve(projected_obstruction.vertices.size() / 2); + for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { + clip_path.emplace_back(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + } + if (!IsPositive(clip_path)) { + std::reverse(clip_path.begin(), clip_path.end()); + } + obstruction_polygon_paths.push_back(std::move(clip_path)); + } + if (obstruction_polygon_paths.size() > 0) { + path_solution = Difference(path_solution, obstruction_polygon_paths, FillRule::NonZero); + } } //path_solution = RamerDouglasPeucker(path_solution, 0.025); // diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 6ce62e20d1..125460cb3b 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -2038,8 +2038,9 @@ bool OpenXRAPI::poll_events() { if (local_floor_emulation.enabled) { local_floor_emulation.should_reset_floor_height = true; } - if (event->poseValid && xr_interface) { - xr_interface->on_pose_recentered(); + + if (xr_interface) { + xr_interface->on_reference_space_change_pending(); } } break; case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index f600891ec8..f4b15b4960 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -1136,6 +1136,12 @@ void OpenXRInterface::process() { if (head.is_valid()) { head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity, head_confidence); } + + if (reference_stage_changing) { + // Now that we have updated tracking information in our updated reference space, trigger our pose recentered signal. + emit_signal(SNAME("pose_recentered")); + reference_stage_changing = false; + } } void OpenXRInterface::pre_render() { @@ -1317,8 +1323,8 @@ void OpenXRInterface::on_state_exiting() { emit_signal(SNAME("instance_exiting")); } -void OpenXRInterface::on_pose_recentered() { - emit_signal(SNAME("pose_recentered")); +void OpenXRInterface::on_reference_space_change_pending() { + reference_stage_changing = true; } void OpenXRInterface::on_refresh_rate_changes(float p_new_rate) { diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 71af2f8176..c7e516ee55 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -72,6 +72,7 @@ class OpenXRInterface : public XRInterface { private: OpenXRAPI *openxr_api = nullptr; bool initialized = false; + bool reference_stage_changing = false; XRInterface::TrackingStatus tracking_state; // At a minimum we need a tracker for our head @@ -209,7 +210,7 @@ public: void on_state_stopping(); void on_state_loss_pending(); void on_state_exiting(); - void on_pose_recentered(); + void on_reference_space_change_pending(); void on_refresh_rate_changes(float p_new_rate); void tracker_profile_changed(RID p_tracker, RID p_interaction_profile); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 6449b6ab6e..666c8d634a 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -790,7 +790,7 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj return OK; } -Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { +Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { APKExportData *ed = static_cast<APKExportData *>(p_userdata); String dst_path = p_path.replace_first("res://", "assets/"); @@ -798,7 +798,7 @@ Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String return OK; } -Error EditorExportPlatformAndroid::ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { +Error EditorExportPlatformAndroid::ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { return OK; } diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 6a9e58216e..7200fe768c 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -144,9 +144,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { static Error save_apk_so(void *p_userdata, const SharedObject &p_so); - static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); - static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so); @@ -188,7 +188,7 @@ protected: void _notification(int p_what); public: - typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 5091a514b3..7cf55c62d1 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -171,7 +171,7 @@ Error store_string_at_path(const String &p_path, const String &p_data) { // It is used by the export_project_files method to save all the asset files into the gradle project. // It's functionality mirrors that of the method save_apk_file. // This method will be called ONLY when gradle build is enabled. -Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { +Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { CustomExportData *export_data = static_cast<CustomExportData *>(p_userdata); String dst_path = p_path.replace_first("res://", export_data->assets_directory + "/"); print_verbose("Saving project files from " + p_path + " into " + dst_path); diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index d56365e1b7..13da13b29f 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -95,7 +95,7 @@ Error store_string_at_path(const String &p_path, const String &p_data); // It is used by the export_project_files method to save all the asset files into the gradle project. // It's functionality mirrors that of the method save_apk_file. // This method will be called ONLY when gradle build is enabled. -Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); +Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed); // Creates strings.xml files inside the gradle project for different locales. Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name, const String &p_gradle_build_dir); diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index 45222ca3b0..276d74b75b 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -173,7 +173,7 @@ dependencies { implementation "androidx.window:window:1.3.0" implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation "org.bouncycastle:bcprov-jdk15to18:1.77" + implementation "org.bouncycastle:bcprov-jdk15to18:1.78" // Meta dependencies horizonosImplementation "org.godotengine:godot-openxr-vendors-meta:3.0.0-stable" diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index 2a9fc65ac3..2aabf0961a 100644 --- a/platform/android/net_socket_android.cpp +++ b/platform/android/net_socket_android.cpp @@ -86,7 +86,7 @@ NetSocketAndroid::~NetSocketAndroid() { } void NetSocketAndroid::close() { - NetSocketPosix::close(); + NetSocketUnix::close(); if (wants_broadcast) { multicast_lock_release(); } @@ -98,7 +98,7 @@ void NetSocketAndroid::close() { } Error NetSocketAndroid::set_broadcasting_enabled(bool p_enabled) { - Error err = NetSocketPosix::set_broadcasting_enabled(p_enabled); + Error err = NetSocketUnix::set_broadcasting_enabled(p_enabled); if (err != OK) { return err; } @@ -117,7 +117,7 @@ Error NetSocketAndroid::set_broadcasting_enabled(bool p_enabled) { } Error NetSocketAndroid::join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) { - Error err = NetSocketPosix::join_multicast_group(p_multi_address, p_if_name); + Error err = NetSocketUnix::join_multicast_group(p_multi_address, p_if_name); if (err != OK) { return err; } @@ -131,7 +131,7 @@ Error NetSocketAndroid::join_multicast_group(const IPAddress &p_multi_address, c } Error NetSocketAndroid::leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) { - Error err = NetSocketPosix::leave_multicast_group(p_multi_address, p_if_name); + Error err = NetSocketUnix::leave_multicast_group(p_multi_address, p_if_name); if (err != OK) { return err; } diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h index 8f0e64ea44..68c0141ebc 100644 --- a/platform/android/net_socket_android.h +++ b/platform/android/net_socket_android.h @@ -46,7 +46,7 @@ * the lock when broadcasting is enabled/disabled on a socket, or that socket * joins/leaves a multicast group. */ -class NetSocketAndroid : public NetSocketPosix { +class NetSocketAndroid : public NetSocketUnix { private: static jobject net_utils; static jclass cls; diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 85e7472c02..96bd56fb8e 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -279,16 +279,18 @@ def configure(env: "SConsEnvironment"): env.ParseConfig("pkg-config libwebp --cflags --libs") if not env["builtin_mbedtls"]: - # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228 - env.Append(LIBS=["mbedtls", "mbedcrypto", "mbedx509"]) + # mbedTLS only provides a pkgconfig file since 3.6.0, but we still support 2.28.x, + # so fallback to manually specifying LIBS if it fails. + if os.system("pkg-config --exists mbedtls") == 0: # 0 means found + env.ParseConfig("pkg-config mbedtls mbedcrypto mbedx509 --cflags --libs") + else: + env.Append(LIBS=["mbedtls", "mbedcrypto", "mbedx509"]) if not env["builtin_wslay"]: env.ParseConfig("pkg-config libwslay --cflags --libs") if not env["builtin_miniupnpc"]: - # No pkgconfig file so far, hardcode default paths. - env.Prepend(CPPPATH=["/usr/include/miniupnpc"]) - env.Append(LIBS=["miniupnpc"]) + env.ParseConfig("pkg-config miniupnpc --cflags --libs") # On Linux wchar_t should be 32-bits # 16-bit library shouldn't be required due to compiler optimizations diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 549634fc31..091870db1e 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -396,7 +396,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo } else { if (flt == "*.*") { filter_exts.push_back("*"); - filter_names.push_back(RTR("All Files")); + filter_names.push_back(RTR("All Files") + " (*)"); } else { filter_exts.push_back(flt); filter_names.push_back(flt); @@ -407,7 +407,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo } if (filter_names.is_empty()) { filter_exts.push_back("*"); - filter_names.push_back(RTR("All Files")); + filter_names.push_back(RTR("All Files") + " (*)"); } DBusError err; diff --git a/platform/macos/detect.py b/platform/macos/detect.py index cab91fd33c..3575e93c68 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -2,7 +2,7 @@ import os import sys from typing import TYPE_CHECKING -from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang, print_error, print_warning +from methods import detect_darwin_sdk_path, get_compiler_version, is_apple_clang, print_error, print_warning from platform_methods import detect_arch, detect_mvk, validate_arch if TYPE_CHECKING: @@ -101,10 +101,9 @@ def configure(env: "SConsEnvironment"): cc_version = get_compiler_version(env) cc_version_major = cc_version["apple_major"] cc_version_minor = cc_version["apple_minor"] - vanilla = is_vanilla_clang(env) # Workaround for Xcode 15 linker bug. - if not vanilla and cc_version_major == 1500 and cc_version_minor == 0: + if is_apple_clang(env) and cc_version_major == 1500 and cc_version_minor == 0: env.Prepend(LINKFLAGS=["-ld_classic"]) env.Append(CCFLAGS=["-fobjc-arc"]) diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm index 2365dc0818..c78ac639aa 100644 --- a/platform/macos/godot_open_save_delegate.mm +++ b/platform/macos/godot_open_save_delegate.mm @@ -132,7 +132,7 @@ } if ([type_filters count] > 0) { - NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1].strip_edges(), tokens[0].strip_edges())).utf8().get_data()]; + NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : tokens[1].strip_edges()).utf8().get_data()]; [new_allowed_types addObject:type_filters]; [popup addItemWithTitle:name_str]; } diff --git a/platform/web/detect.py b/platform/web/detect.py index f4a6073dbd..a3eb1a9de8 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -201,7 +201,7 @@ def configure(env: "SConsEnvironment"): sys.exit(255) env.Prepend(CPPPATH=["#platform/web"]) - env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"]) + env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED", "UNIX_SOCKET_UNAVAILABLE"]) if env["opengl3"]: env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"]) diff --git a/platform/web/package-lock.json b/platform/web/package-lock.json index a2e0fd3b27..7947fb96e4 100644 --- a/platform/web/package-lock.json +++ b/platform/web/package-lock.json @@ -9,14 +9,14 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "@eslint/js": "^9.3.0", - "@html-eslint/eslint-plugin": "^0.24.1", - "@html-eslint/parser": "^0.24.1", - "@stylistic/eslint-plugin": "^2.1.0", - "eslint": "^9.3.0", + "@eslint/js": "^9.12.0", + "@html-eslint/eslint-plugin": "^0.27.0", + "@html-eslint/parser": "^0.27.0", + "@stylistic/eslint-plugin": "^2.9.0", + "eslint": "^9.12.0", "eslint-plugin-html": "^8.1.1", "espree": "^10.0.1", - "globals": "^15.3.0", + "globals": "^15.9.0", "jsdoc": "^4.0.3" } }, @@ -60,14 +60,40 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", + "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", @@ -104,28 +130,54 @@ } }, "node_modules/@eslint/js": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.3.0.tgz", - "integrity": "sha512-niBqk8iwv96+yuTwjM6bWg8ovzAPF9qkICsGtcoa5/dmqcEMfdwNAX7+/OHcJHc7wj7XqPxH98oAHytFYlw6Sw==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz", + "integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@html-eslint/eslint-plugin": { - "version": "0.24.1", - "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.24.1.tgz", - "integrity": "sha512-JwNDQBrNIWEPcxgSpla/2jaUXyQCqL7Xp8CmON4Bk5qg8MwiDLXOgjylfVC+tN52i8JeHWMca34I9DqBGRj9Qg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.27.0.tgz", + "integrity": "sha512-aAF14sgDKidMCCQpJ4kIhe+fwyAaAbvDlgVTIgd99F+HOWxokTTXDt39a3gewMBo76IeEHDaoizUDJQ/Vc7Mdg==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@html-eslint/parser": { - "version": "0.24.1", - "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.24.1.tgz", - "integrity": "sha512-O13xX/+Ldh0P7VZMpDDYc3XtWiE1cYm5QhVJ0VB5i7D8Q69HrrGN+5BjS17vkCoLTz+3zWWIiJv4oFmyS5LReA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.27.0.tgz", + "integrity": "sha512-F/A1M0jnDAYoRvJiiSC7pIBD9DAsf4EhbndbvEi81aozD/wI8WWXON50xZPUaGHCI1C+2syTVifxDz8MvDKaQA==", "dev": true, + "license": "MIT", "dependencies": { "es-html-parser": "^0.0.9" }, @@ -133,18 +185,28 @@ "node": ">=8.10.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -160,17 +222,12 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true - }, "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -227,50 +284,15 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.1.0.tgz", - "integrity": "sha512-cBBowKP2u/+uE5CzgH5w8pE9VKqcM7BXdIDPIbGt2rmLJGnA6MJPr9vYGaqgMoJFs7R/FzsMQerMvvEP40g2uw==", - "dev": true, - "dependencies": { - "@stylistic/eslint-plugin-js": "2.1.0", - "@stylistic/eslint-plugin-jsx": "2.1.0", - "@stylistic/eslint-plugin-plus": "2.1.0", - "@stylistic/eslint-plugin-ts": "2.1.0", - "@types/eslint": "^8.56.10" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" - } - }, - "node_modules/@stylistic/eslint-plugin-js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.1.0.tgz", - "integrity": "sha512-gdXUjGNSsnY6nPyqxu6lmDTtVrwCOjun4x8PUn0x04d5ucLI74N3MT1Q0UhdcOR9No3bo5PGDyBgXK+KmD787A==", - "dev": true, - "dependencies": { - "@types/eslint": "^8.56.10", - "acorn": "^8.11.3", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" - } - }, - "node_modules/@stylistic/eslint-plugin-jsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-2.1.0.tgz", - "integrity": "sha512-mMD7S+IndZo2vxmwpHVTCwx2O1VdtE5tmpeNwgaEcXODzWV1WTWpnsc/PECQKIr/mkLPFWiSIqcuYNhQ/3l6AQ==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.9.0.tgz", + "integrity": "sha512-OrDyFAYjBT61122MIY1a3SfEgy3YCMgt2vL4eoPmvTwDBwyQhAXurxNQznlRD/jESNfYWfID8Ej+31LljvF7Xg==", "dev": true, + "license": "MIT", "dependencies": { - "@stylistic/eslint-plugin-js": "^2.1.0", - "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^8.8.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", "estraverse": "^5.3.0", "picomatch": "^4.0.2" }, @@ -281,101 +303,19 @@ "eslint": ">=8.40.0" } }, - "node_modules/@stylistic/eslint-plugin-plus": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-2.1.0.tgz", - "integrity": "sha512-S5QAlgYXESJaSBFhBSBLZy9o36gXrXQwWSt6QkO+F0SrT9vpV5JF/VKoh+ojO7tHzd8Ckmyouq02TT9Sv2B0zQ==", - "dev": true, - "dependencies": { - "@types/eslint": "^8.56.10", - "@typescript-eslint/utils": "^7.8.0" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/@typescript-eslint/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.10.0", - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/typescript-estree": "7.10.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@stylistic/eslint-plugin-ts": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-2.1.0.tgz", - "integrity": "sha512-2ioFibufHYBALx2TBrU4KXovCkN8qCqcb9yIHc0fyOfTaO5jw4d56WW7YRcF3Zgde6qFyXwAN6z/+w4pnmos1g==", - "dev": true, - "dependencies": { - "@stylistic/eslint-plugin-js": "2.1.0", - "@types/eslint": "^8.56.10", - "@typescript-eslint/utils": "^7.8.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" - } - }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/@typescript-eslint/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.10.0", - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/typescript-estree": "7.10.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/linkify-it": { "version": "5.0.0", @@ -400,16 +340,17 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz", - "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", + "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0" + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -417,12 +358,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz", - "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", + "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", "dev": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -430,22 +372,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz", - "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", + "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -462,15 +405,17 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -481,17 +426,41 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/utils": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz", + "integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/typescript-estree": "8.11.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz", - "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", + "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/types": "8.11.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -503,6 +472,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -511,10 +481,11 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -547,15 +518,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -577,15 +539,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -613,6 +566,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -718,18 +672,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -816,28 +758,33 @@ } }, "node_modules/eslint": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.3.0.tgz", - "integrity": "sha512-5Iv4CsZW030lpUqHBapdPo3MJetAPtejVW8B84GIcIIv8+ohFaddXsrn1Gn8uD9ijDb+kcYKFUVmC8qG8B2ORQ==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", + "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.6.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.3.0", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint/js": "9.12.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -846,14 +793,11 @@ "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { @@ -863,7 +807,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-plugin-html": { @@ -879,10 +831,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -895,10 +848,11 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -907,14 +861,15 @@ } }, "node_modules/espree": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", - "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.11.3", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "eslint-visitor-keys": "^4.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -940,6 +895,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -976,6 +932,7 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -992,6 +949,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -1037,6 +995,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1092,10 +1051,11 @@ } }, "node_modules/globals": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.3.0.tgz", - "integrity": "sha512-cCdyVjIUVTtX8ZsPkq1oCsOsLmGIswqnjZYMJJTGaNApj1yHtLSymKhwH51ttirREn75z3p4k051clwg7rvNKA==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -1103,26 +1063,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1217,19 +1157,11 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1430,15 +1362,17 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -1452,6 +1386,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -1572,20 +1507,12 @@ "node": ">=8" } }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1692,10 +1619,11 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1724,27 +1652,6 @@ "node": ">=8" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1780,6 +1687,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -1792,6 +1700,7 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -1812,10 +1721,11 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, + "license": "Apache-2.0", "peer": true, "bin": { "tsc": "bin/tsc", diff --git a/platform/web/package.json b/platform/web/package.json index 588af2ff3b..bf61eb184c 100644 --- a/platform/web/package.json +++ b/platform/web/package.json @@ -11,14 +11,14 @@ "format": "npm run lint -- --fix" }, "devDependencies": { - "@eslint/js": "^9.3.0", - "@html-eslint/eslint-plugin": "^0.24.1", - "@html-eslint/parser": "^0.24.1", - "@stylistic/eslint-plugin": "^2.1.0", - "eslint": "^9.3.0", + "@eslint/js": "^9.12.0", + "@html-eslint/eslint-plugin": "^0.27.0", + "@html-eslint/parser": "^0.27.0", + "@stylistic/eslint-plugin": "^2.9.0", + "eslint": "^9.12.0", "eslint-plugin-html": "^8.1.1", "espree": "^10.0.1", - "globals": "^15.3.0", + "globals": "^15.9.0", "jsdoc": "^4.0.3" } } diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 72ceeae201..886dee9d98 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -83,16 +83,6 @@ if env["windows_subsystem"] == "gui": env_wrap.Depends(prog_wrap, prog) sources += common_win_wrap + res_wrap_obj -# Microsoft Visual Studio Project Generation -if env["vsproj"]: - env.vs_srcs += ["platform/windows/" + res_file] - env.vs_srcs += ["platform/windows/godot.natvis"] - for x in common_win: - env.vs_srcs += ["platform/windows/" + str(x)] - if env["windows_subsystem"] == "gui": - for x in common_win_wrap: - env.vs_srcs += ["platform/windows/" + str(x)] - if env["d3d12"]: dxc_target_aliases = { "x86_32": "x86", diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 64f94edb71..a5969de971 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -507,7 +507,7 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) { } if (filter_names.is_empty()) { filter_exts.push_back(String("*.*").utf16()); - filter_names.push_back(RTR("All Files").utf16()); + filter_names.push_back((RTR("All Files") + " (*)").utf16()); } Vector<COMDLG_FILTERSPEC> filters; @@ -4794,9 +4794,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA break; } - DisplayServer::WindowID receiving_window_id = _get_focused_window_or_popup(); - if (receiving_window_id == INVALID_WINDOW_ID) { - receiving_window_id = window_id; + DisplayServer::WindowID receiving_window_id = window_id; + if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + receiving_window_id = _get_focused_window_or_popup(); + if (receiving_window_id == INVALID_WINDOW_ID) { + receiving_window_id = window_id; + } } const BitField<WinKeyModifierMask> &mods = _get_mods(); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 7176acb6c5..d7a97a3b2d 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -988,7 +988,7 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String pi.si.StartupInfo.hStdOutput = pipe_out[1]; pi.si.StartupInfo.hStdError = pipe_err[1]; - size_t attr_list_size = 0; + SIZE_T attr_list_size = 0; InitializeProcThreadAttributeList(nullptr, 1, 0, &attr_list_size); pi.si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(attr_list_size); if (!InitializeProcThreadAttributeList(pi.si.lpAttributeList, 1, 0, &attr_list_size)) { @@ -1084,7 +1084,7 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, pi.si.StartupInfo.hStdError = pipe[1]; } - size_t attr_list_size = 0; + SIZE_T attr_list_size = 0; InitializeProcThreadAttributeList(nullptr, 1, 0, &attr_list_size); pi.si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(attr_list_size); if (!InitializeProcThreadAttributeList(pi.si.lpAttributeList, 1, 0, &attr_list_size)) { diff --git a/platform_methods.py b/platform_methods.py index 2c4eb0d1dd..201df3c0b5 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -8,6 +8,13 @@ import methods # NOTE: The multiprocessing module is not compatible with SCons due to conflict on cPickle +compatibility_platform_aliases = { + "osx": "macos", + "iphone": "ios", + "x11": "linuxbsd", + "javascript": "web", +} + # CPU architecture options. architectures = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32"] architecture_aliases = { diff --git a/pyproject.toml b/pyproject.toml index 603b4ee694..0b6d668214 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,14 @@ [tool.mypy] -ignore_missing_imports = true disallow_any_generics = true +explicit_package_bases = true +ignore_missing_imports = true +namespace_packages = true no_implicit_optional = true pretty = true show_column_numbers = true warn_redundant_casts = true warn_return_any = true warn_unreachable = true -namespace_packages = true -explicit_package_bases = true exclude = ["thirdparty/"] python_version = "3.8" @@ -16,7 +16,7 @@ python_version = "3.8" extend-exclude = ["thirdparty"] extend-include = ["SConstruct", "SCsub"] line-length = 120 -target-version = "py37" +target-version = "py38" [tool.ruff.lint] extend-select = [ @@ -42,51 +42,51 @@ section-order = [ ] [tool.codespell] -enable-colors = "" -write-changes = "" -check-hidden = "" +enable-colors = true +write-changes = true +check-hidden = true quiet-level = 3 -builtin = "clear,rare,en-GB_to_en-US" -skip = """\ - .mailmap, - *.desktop, - *.gitignore, - *.po, - *.pot, - *.rc, - AUTHORS.md, - REDOT_AUTHORS.md, - COPYRIGHT.txt, - core/input/gamecontrollerdb.txt, - core/string/locales.h, - DONORS.md, - editor/project_converter_3_to_4.cpp, - platform/android/java/lib/src/com/*, - platform/web/package-lock.json -""" -ignore-words-list = """\ - breaked, - cancelled, - checkin, - colour, - curvelinear, - doubleclick, - expct, - findn, - gird, - hel, - inout, - labelin, - lod, - mis, - nd, - numer, - ot, - outin, - parm, - requestor, - te, - textin, - thirdparty, - vai -""" +builtin = ["clear", "rare", "en-GB_to_en-US"] +skip = [ + ".mailmap", + "*.desktop", + "*.gitignore", + "*.po", + "*.pot", + "*.rc", + "AUTHORS.md", + "REDOT_AUTHORS.md", + "COPYRIGHT.txt", + "core/input/gamecontrollerdb.txt", + "core/string/locales.h", + "DONORS.md", + "editor/project_converter_3_to_4.cpp", + "platform/android/java/lib/src/com/*", + "platform/web/package-lock.json", +] +ignore-words-list = [ + "breaked", + "cancelled", + "checkin", + "colour", + "curvelinear", + "doubleclick", + "expct", + "findn", + "gird", + "hel", + "inout", + "labelin", + "lod", + "mis", + "nd", + "numer", + "ot", + "outin", + "parm", + "requestor", + "te", + "textin", + "thirdparty", + "vai", +] diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 9e0973b239..c5174f5ec9 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -77,7 +77,7 @@ void TileMap::_set_tile_map_data_using_compatibility_format(int p_layer, TileMap for (int i = 0; i < c; i += offset) { const uint8_t *ptr = (const uint8_t *)&r[i]; uint8_t local[12]; - const int buffer_size = (format == TILE_MAP_DATA_FORMAT_2) ? 12 : 8; + const int buffer_size = (p_format >= TILE_MAP_DATA_FORMAT_2) ? 12 : 8; for (int j = 0; j < buffer_size; j++) { local[j] = ptr[j]; } diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 8456da3280..8cd6f0f547 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -335,9 +335,7 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m mf.node_path = get_path_to(mi); mf.subindex = -1; mf.mesh = mesh; - - static const int lightmap_scale[GeometryInstance3D::LIGHTMAP_SCALE_MAX] = { 1, 2, 4, 8 }; - mf.lightmap_scale = lightmap_scale[mi->get_lightmap_scale()]; + mf.lightmap_scale = mi->get_lightmap_texel_scale(); Ref<Material> all_override = mi->get_material_override(); for (int i = 0; i < mesh->get_surface_count(); i++) { @@ -371,7 +369,7 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m mf.xform = xf * mesh_xf; mf.node_path = get_path_to(s); mf.subindex = i / 2; - mf.lightmap_scale = 1; + mf.lightmap_scale = 1.0; mf.mesh = mesh; meshes.push_back(mf); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index 39fca0e415..6efdcf497f 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -196,7 +196,7 @@ private: NodePath node_path; int32_t subindex = 0; Ref<Mesh> mesh; - int32_t lightmap_scale = 0; + float lightmap_scale = 0.0; Vector<Ref<Material>> overrides; }; diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 0959c61f3a..68d412dbe2 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -456,14 +456,48 @@ AABB GeometryInstance3D::get_custom_aabb() const { return custom_aabb; } +void GeometryInstance3D::set_lightmap_texel_scale(float p_scale) { + lightmap_texel_scale = p_scale; +} + +float GeometryInstance3D::get_lightmap_texel_scale() const { + return lightmap_texel_scale; +} + +#ifndef DISABLE_DEPRECATED void GeometryInstance3D::set_lightmap_scale(LightmapScale p_scale) { ERR_FAIL_INDEX(p_scale, LIGHTMAP_SCALE_MAX); - lightmap_scale = p_scale; + switch (p_scale) { + case GeometryInstance3D::LIGHTMAP_SCALE_1X: + lightmap_texel_scale = 1.0f; + break; + case GeometryInstance3D::LIGHTMAP_SCALE_2X: + lightmap_texel_scale = 2.0f; + break; + case GeometryInstance3D::LIGHTMAP_SCALE_4X: + lightmap_texel_scale = 4.0f; + break; + case GeometryInstance3D::LIGHTMAP_SCALE_8X: + lightmap_texel_scale = 8.0f; + break; + case GeometryInstance3D::LIGHTMAP_SCALE_MAX: + break; // Can't happen, but silences warning. + } } GeometryInstance3D::LightmapScale GeometryInstance3D::get_lightmap_scale() const { - return lightmap_scale; + // Return closest approximation. + if (lightmap_texel_scale < 1.5f) { + return GeometryInstance3D::LIGHTMAP_SCALE_1X; + } else if (lightmap_texel_scale < 3.0f) { + return GeometryInstance3D::LIGHTMAP_SCALE_2X; + } else if (lightmap_texel_scale < 6.0f) { + return GeometryInstance3D::LIGHTMAP_SCALE_4X; + } + + return GeometryInstance3D::LIGHTMAP_SCALE_8X; } +#endif // DISABLE_DEPRECATED void GeometryInstance3D::set_gi_mode(GIMode p_mode) { switch (p_mode) { @@ -567,8 +601,13 @@ void GeometryInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_extra_cull_margin", "margin"), &GeometryInstance3D::set_extra_cull_margin); ClassDB::bind_method(D_METHOD("get_extra_cull_margin"), &GeometryInstance3D::get_extra_cull_margin); + ClassDB::bind_method(D_METHOD("set_lightmap_texel_scale", "scale"), &GeometryInstance3D::set_lightmap_texel_scale); + ClassDB::bind_method(D_METHOD("get_lightmap_texel_scale"), &GeometryInstance3D::get_lightmap_texel_scale); + +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_lightmap_scale", "scale"), &GeometryInstance3D::set_lightmap_scale); ClassDB::bind_method(D_METHOD("get_lightmap_scale"), &GeometryInstance3D::get_lightmap_scale); +#endif // DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_gi_mode", "mode"), &GeometryInstance3D::set_gi_mode); ClassDB::bind_method(D_METHOD("get_gi_mode"), &GeometryInstance3D::get_gi_mode); @@ -593,7 +632,10 @@ void GeometryInstance3D::_bind_methods() { ADD_GROUP("Global Illumination", "gi_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_gi_mode", "get_gi_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_lightmap_scale", PROPERTY_HINT_ENUM, String::utf8("1×,2×,4×,8×")), "set_lightmap_scale", "get_lightmap_scale"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gi_lightmap_texel_scale", PROPERTY_HINT_RANGE, "0.01,10,0.0001,or_greater"), "set_lightmap_texel_scale", "get_lightmap_texel_scale"); +#ifndef DISABLE_DEPRECATED + ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_lightmap_scale", PROPERTY_HINT_ENUM, String::utf8("1×,2×,4×,8×"), PROPERTY_USAGE_NONE), "set_lightmap_scale", "get_lightmap_scale"); +#endif // DISABLE_DEPRECATED ADD_GROUP("Visibility Range", "visibility_range_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_visibility_range_begin", "get_visibility_range_begin"); diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index 4c4e5de2e8..48237823a4 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -136,7 +136,7 @@ private: float extra_cull_margin = 0.0; AABB custom_aabb; - LightmapScale lightmap_scale = LIGHTMAP_SCALE_1X; + float lightmap_texel_scale = 1.0f; GIMode gi_mode = GI_MODE_STATIC; bool ignore_occlusion_culling = false; @@ -187,8 +187,13 @@ public: void set_gi_mode(GIMode p_mode); GIMode get_gi_mode() const; - void set_lightmap_scale(LightmapScale p_scale); + void set_lightmap_texel_scale(float p_scale); + float get_lightmap_texel_scale() const; + +#ifndef DISABLE_DEPRECATED + void set_lightmap_scale(GeometryInstance3D::LightmapScale p_scale); LightmapScale get_lightmap_scale() const; +#endif // DISABLE_DEPRECATED void set_instance_shader_parameter(const StringName &p_name, const Variant &p_value); Variant get_instance_shader_parameter(const StringName &p_name) const; @@ -205,8 +210,8 @@ public: }; VARIANT_ENUM_CAST(GeometryInstance3D::ShadowCastingSetting); -VARIANT_ENUM_CAST(GeometryInstance3D::LightmapScale); VARIANT_ENUM_CAST(GeometryInstance3D::GIMode); +VARIANT_ENUM_CAST(GeometryInstance3D::LightmapScale); VARIANT_ENUM_CAST(GeometryInstance3D::VisibilityRangeFadeMode); #endif // VISUAL_INSTANCE_3D_H diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index ec6b9f263d..35e974c92e 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -95,7 +95,9 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::process(const AnimationMixer AnimationMixer::PlaybackInfo pi = p_playback_info; if (p_playback_info.seeked) { - pi.delta = get_node_time_info().position - p_playback_info.time; + if (p_playback_info.is_external_seeking) { + pi.delta = get_node_time_info().position - p_playback_info.time; + } } else { pi.time = get_node_time_info().position + (backward ? -p_playback_info.delta : p_playback_info.delta); } @@ -142,6 +144,12 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe // 1. Progress for AnimationNode. bool will_end = Animation::is_greater_or_equal_approx(cur_time + cur_delta, cur_len); + bool is_started = p_seek && !p_is_external_seeking && Math::is_zero_approx(cur_time); + + // 1. Progress for AnimationNode. + if (is_started && advance_on_start) { + cur_time = cur_delta; + } if (cur_loop_mode != Animation::LOOP_NONE) { if (cur_loop_mode == Animation::LOOP_LINEAR) { if (!Math::is_zero_approx(cur_len)) { @@ -234,7 +242,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe // We should use call_deferred since the track keys are still being processed. if (process_state->tree && !p_test_only) { // AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection. - if (p_seek && !p_is_external_seeking && Math::is_zero_approx(cur_playback_time)) { + if (is_started) { process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_started), animation); } // Finished. @@ -284,6 +292,14 @@ bool AnimationNodeAnimation::is_backward() const { return backward; } +void AnimationNodeAnimation::set_advance_on_start(bool p_advance_on_start) { + advance_on_start = p_advance_on_start; +} + +bool AnimationNodeAnimation::is_advance_on_start() const { + return advance_on_start; +} + void AnimationNodeAnimation::set_use_custom_timeline(bool p_use_custom_timeline) { use_custom_timeline = p_use_custom_timeline; notify_property_list_changed(); @@ -333,6 +349,9 @@ void AnimationNodeAnimation::_bind_methods() { ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode); ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode); + ClassDB::bind_method(D_METHOD("set_advance_on_start", "advance_on_start"), &AnimationNodeAnimation::set_advance_on_start); + ClassDB::bind_method(D_METHOD("is_advance_on_start"), &AnimationNodeAnimation::is_advance_on_start); + ClassDB::bind_method(D_METHOD("set_use_custom_timeline", "use_custom_timeline"), &AnimationNodeAnimation::set_use_custom_timeline); ClassDB::bind_method(D_METHOD("is_using_custom_timeline"), &AnimationNodeAnimation::is_using_custom_timeline); @@ -350,6 +369,7 @@ void AnimationNodeAnimation::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation"); ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "advance_on_start"), "set_advance_on_start", "is_advance_on_start"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_timeline"), "set_use_custom_timeline", "is_using_custom_timeline"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeline_length", PROPERTY_HINT_RANGE, "0.001,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_timeline_length", "get_timeline_length"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch_time_scale"), "set_stretch_time_scale", "is_stretching_time_scale"); diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 9967a6f449..6cb208d710 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -40,6 +40,8 @@ class AnimationNodeAnimation : public AnimationRootNode { StringName animation; + bool advance_on_start = false; + bool use_custom_timeline = false; double timeline_length = 1.0; Animation::LoopMode loop_mode = Animation::LOOP_NONE; @@ -74,6 +76,9 @@ public: void set_backward(bool p_backward); bool is_backward() const; + void set_advance_on_start(bool p_advance_on_start); + bool is_advance_on_start() const; + void set_use_custom_timeline(bool p_use_custom_timeline); bool is_using_custom_timeline() const; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 00160f21a4..4566f34674 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -602,6 +602,22 @@ void AnimationMixer::_init_root_motion_cache() { root_motion_scale_accumulator = Vector3(1, 1, 1); } +void AnimationMixer::_create_track_num_to_track_cashe_for_animation(Ref<Animation> &p_animation) { + ERR_FAIL_COND(animation_track_num_to_track_cashe.has(p_animation)); + LocalVector<TrackCache *> &track_num_to_track_cashe = animation_track_num_to_track_cashe.insert_new(p_animation, LocalVector<TrackCache *>())->value; + const Vector<Animation::Track *> &tracks = p_animation->get_tracks(); + + track_num_to_track_cashe.resize(tracks.size()); + for (int i = 0; i < tracks.size(); i++) { + TrackCache **track_ptr = track_cache.getptr(tracks[i]->thash); + if (track_ptr == nullptr) { + track_num_to_track_cashe[i] = nullptr; + } else { + track_num_to_track_cashe[i] = *track_ptr; + } + } +} + bool AnimationMixer::_update_caches() { setup_pass++; @@ -930,20 +946,9 @@ bool AnimationMixer::_update_caches() { } animation_track_num_to_track_cashe.clear(); - LocalVector<TrackCache *> track_num_to_track_cashe; for (const StringName &E : sname_list) { Ref<Animation> anim = get_animation(E); - const Vector<Animation::Track *> tracks = anim->get_tracks(); - track_num_to_track_cashe.resize(tracks.size()); - for (int i = 0; i < tracks.size(); i++) { - TrackCache **track_ptr = track_cache.getptr(tracks[i]->thash); - if (track_ptr == nullptr) { - track_num_to_track_cashe[i] = nullptr; - } else { - track_num_to_track_cashe[i] = *track_ptr; - } - } - animation_track_num_to_track_cashe.insert(anim, track_num_to_track_cashe); + _create_track_num_to_track_cashe_for_animation(anim); } track_count = idx; @@ -1076,6 +1081,9 @@ void AnimationMixer::blend_capture(double p_delta) { capture_cache.remain -= p_delta * capture_cache.step; if (Animation::is_less_or_equal_approx(capture_cache.remain, 0)) { + if (capture_cache.animation.is_valid()) { + animation_track_num_to_track_cashe.erase(capture_cache.animation); + } capture_cache.clear(); return; } @@ -2207,6 +2215,9 @@ void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween: capture_cache.step = 1.0 / p_duration; capture_cache.trans_type = p_trans_type; capture_cache.ease_type = p_ease_type; + if (capture_cache.animation.is_valid()) { + animation_track_num_to_track_cashe.erase(capture_cache.animation); + } capture_cache.animation.instantiate(); bool is_valid = false; @@ -2230,6 +2241,8 @@ void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween: } if (!is_valid) { capture_cache.clear(); + } else { + _create_track_num_to_track_cashe_for_animation(capture_cache.animation); } } diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index b2468b3299..757ccb8ff6 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -323,6 +323,7 @@ protected: void _clear_playing_caches(); void _init_root_motion_cache(); bool _update_caches(); + void _create_track_num_to_track_cashe_for_animation(Ref<Animation> &p_animation); /* ---- Audio ---- */ AudioServer::PlaybackType playback_type; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index fc37a67c48..0b720d6710 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -878,6 +878,20 @@ Tween::EaseType AnimationPlayer::get_auto_capture_ease_type() const { return auto_capture_ease_type; } +#ifdef TOOLS_ENABLED +void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + const String pf = p_function; + if (p_idx == 0 && (pf == "play" || pf == "play_backwards" || pf == "has_animation" || pf == "queue")) { + List<StringName> al; + get_animation_list(&al); + for (const StringName &name : al) { + r_options->push_back(String(name).quote()); + } + } + AnimationMixer::get_argument_options(p_function, p_idx, r_options); +} +#endif + void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) { AnimationMixer::_animation_removed(p_name, p_library); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index db753f6bee..dd8ffd3286 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -180,6 +180,10 @@ public: void set_auto_capture_ease_type(Tween::EaseType p_auto_capture_ease_type); Tween::EaseType get_auto_capture_ease_type() const; +#ifdef TOOLS_ENABLED + void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; +#endif + void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); void play_section_with_markers(const StringName &p_name = StringName(), const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); void play_section(const StringName &p_name = StringName(), double p_start_time = -1, double p_end_time = -1, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 7c49aac2b2..a63cf78f1c 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -373,7 +373,9 @@ AnimationNode::NodeTimeInfo AnimationNode::process(const AnimationMixer::Playbac AnimationMixer::PlaybackInfo pi = p_playback_info; if (p_playback_info.seeked) { - pi.delta = get_node_time_info().position - p_playback_info.time; + if (p_playback_info.is_external_seeking) { + pi.delta = get_node_time_info().position - p_playback_info.time; + } } else { pi.time = get_node_time_info().position + p_playback_info.delta; } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 88532ab722..d50c88010c 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -93,7 +93,7 @@ public: if (Math::is_zero_approx(remain)) { return 0; } - return length - position; + return remain; } }; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index c8378f2b1d..b885565cdc 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -70,9 +70,9 @@ void FileDialog::_native_popup() { root = OS::get_singleton()->get_user_data_dir(); } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA)) { - DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb_with_options)); + DisplayServer::get_singleton()->file_dialog_with_options_show(get_translated_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb_with_options)); } else { - DisplayServer::get_singleton()->file_dialog_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb)); + DisplayServer::get_singleton()->file_dialog_show(get_translated_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), processed_filters, callable_mp(this, &FileDialog::_native_dialog_cb)); } } @@ -853,37 +853,54 @@ void FileDialog::_filename_filter_selected() { void FileDialog::update_filters() { filter->clear(); + processed_filters.clear(); if (filters.size() > 1) { String all_filters; + String all_filters_full; const int max_filters = 5; for (int i = 0; i < MIN(max_filters, filters.size()); i++) { - String flt = filters[i].get_slice(";", 0).strip_edges(); + String flt = filters[i].get_slicec(';', 0).strip_edges(); if (i > 0) { all_filters += ", "; } all_filters += flt; } + for (int i = 0; i < filters.size(); i++) { + String flt = filters[i].get_slicec(';', 0).strip_edges(); + if (i > 0) { + all_filters_full += ","; + } + all_filters_full += flt; + } if (max_filters < filters.size()) { all_filters += ", ..."; } - filter->add_item(atr(ETR("All Recognized")) + " (" + all_filters + ")"); + String f = atr(ETR("All Recognized")) + " (" + all_filters + ")"; + filter->add_item(f); + processed_filters.push_back(all_filters_full + ";" + f); } for (int i = 0; i < filters.size(); i++) { - String flt = filters[i].get_slice(";", 0).strip_edges(); + String flt = filters[i].get_slicec(';', 0).strip_edges(); String desc = filters[i].get_slice(";", 1).strip_edges(); if (desc.length()) { - filter->add_item(String(tr(desc)) + " (" + flt + ")"); + String f = atr(desc) + " (" + flt + ")"; + filter->add_item(f); + processed_filters.push_back(flt + ";" + f); } else { - filter->add_item("(" + flt + ")"); + String f = "(" + flt + ")"; + filter->add_item(f); + processed_filters.push_back(flt + ";" + f); } } - filter->add_item(atr(ETR("All Files")) + " (*)"); + String f = atr(ETR("All Files")) + " (*)"; + filter->add_item(f); + processed_filters.push_back("*.*;" + f); } void FileDialog::clear_filename_filter() { diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 84a3334503..5eeed5b220 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -103,6 +103,7 @@ private: Button *show_filename_filter_button = nullptr; Vector<String> filters; + Vector<String> processed_filters; String file_name_filter; bool show_filename_filter = false; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 6dcd944004..819d467da7 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -1454,13 +1454,12 @@ void LineEdit::undo() { return; } + if (!has_undo()) { + return; + } + if (undo_stack_pos == nullptr) { - if (undo_stack.size() <= 1) { - return; - } undo_stack_pos = undo_stack.back(); - } else if (undo_stack_pos == undo_stack.front()) { - return; } deselect(); @@ -1481,10 +1480,7 @@ void LineEdit::redo() { return; } - if (undo_stack_pos == nullptr) { - return; - } - if (undo_stack_pos == undo_stack.back()) { + if (!has_redo()) { return; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 526c2a9b36..d885b07d14 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -4177,7 +4177,7 @@ void TextEdit::redo() { } _push_current_op(); - if (undo_stack_pos == nullptr) { + if (!has_redo()) { return; // Nothing to do. } @@ -7312,6 +7312,10 @@ void TextEdit::_paste_internal(int p_caret) { } String clipboard = DisplayServer::get_singleton()->clipboard_get(); + if (clipboard.is_empty()) { + // Nothing to paste. + return; + } // Paste a full line. Ignore '\r' characters that may have been added to the clipboard by the OS. if (get_caret_count() == 1 && !has_selection(0) && !cut_copy_line.is_empty() && cut_copy_line == clipboard.replace("\r", "")) { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 4a808c4081..8a48b99040 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -332,7 +332,7 @@ void Viewport::_sub_window_update(Window *p_window) { int close_h_ofs = p_window->theme_cache.close_h_offset; int close_v_ofs = p_window->theme_cache.close_v_offset; - TextLine title_text = TextLine(p_window->atr(p_window->get_title()), title_font, font_size); + TextLine title_text = TextLine(p_window->get_translated_title(), title_font, font_size); title_text.set_width(r.size.width - panel->get_minimum_size().x - close_h_ofs); title_text.set_direction(p_window->is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); int x = (r.size.width - title_text.get_size().x) / 2; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index ab9c56d56c..3a2245c555 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -305,6 +305,11 @@ String Window::get_title() const { return title; } +String Window::get_translated_title() const { + ERR_READ_THREAD_GUARD_V(String()); + return tr_title; +} + void Window::_settings_changed() { if (visible && initial_position != WINDOW_INITIAL_POSITION_ABSOLUTE && is_in_edited_scene_root()) { Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); diff --git a/scene/main/window.h b/scene/main/window.h index d0f9cd11b9..00345e7b82 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -276,6 +276,7 @@ public: void set_title(const String &p_title); String get_title() const; + String get_translated_title() const; void set_initial_position(WindowInitialPosition p_initial_position); WindowInitialPosition get_initial_position() const; diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index 404f658fa6..da8ad3db15 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -434,12 +434,12 @@ void main() { vertex_interp = vertex; #ifdef NORMAL_USED - normal_interp = normal; + normal_interp = normalize(normal); #endif #if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED) - tangent_interp = tangent; - binormal_interp = binormal; + tangent_interp = normalize(tangent); + binormal_interp = normalize(binormal); #endif // VERTEX LIGHTING @@ -456,13 +456,13 @@ void main() { uvec2 omni_light_indices = instances.data[draw_call.instance_index].omni_lights; for (uint i = 0; i < sc_omni_lights(); i++) { uint light_index = (i > 3) ? ((omni_light_indices.y >> ((i - 4) * 8)) & 0xFF) : ((omni_light_indices.x >> (i * 8)) & 0xFF); - light_process_omni_vertex(light_index, vertex, view, normal, roughness, diffuse_light_interp.rgb, specular_light_interp.rgb); + light_process_omni_vertex(light_index, vertex, view, normal_interp, roughness, diffuse_light_interp.rgb, specular_light_interp.rgb); } uvec2 spot_light_indices = instances.data[draw_call.instance_index].spot_lights; for (uint i = 0; i < sc_spot_lights(); i++) { uint light_index = (i > 3) ? ((spot_light_indices.y >> ((i - 4) * 8)) & 0xFF) : ((spot_light_indices.x >> (i * 8)) & 0xFF); - light_process_spot_vertex(light_index, vertex, view, normal, roughness, diffuse_light_interp.rgb, specular_light_interp.rgb); + light_process_spot_vertex(light_index, vertex, view, normal_interp, roughness, diffuse_light_interp.rgb, specular_light_interp.rgb); } if (sc_directional_lights() > 0) { @@ -479,13 +479,13 @@ void main() { continue; // Statically baked light and object uses lightmap, skip. } if (i == 0) { - light_compute_vertex(normal, directional_lights.data[0].direction, view, + light_compute_vertex(normal_interp, directional_lights.data[0].direction, view, directional_lights.data[0].color * directional_lights.data[0].energy, true, roughness, directional_diffuse, directional_specular); } else { - light_compute_vertex(normal, directional_lights.data[i].direction, view, + light_compute_vertex(normal_interp, directional_lights.data[i].direction, view, directional_lights.data[i].color * directional_lights.data[i].energy, true, roughness, diffuse_light_interp.rgb, diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 8bf0e7f623..f564ca64a5 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -5274,14 +5274,13 @@ void RenderingDevice::_wait_for_transfer_worker(TransferWorker *p_transfer_worke p_transfer_worker->operations_processed = p_transfer_worker->operations_submitted; } - if (!p_transfer_worker->texture_barriers.is_empty()) { - MutexLock transfer_worker_lock(transfer_worker_pool_mutex); - _flush_barriers_for_transfer_worker(p_transfer_worker); - } + _flush_barriers_for_transfer_worker(p_transfer_worker); } void RenderingDevice::_flush_barriers_for_transfer_worker(TransferWorker *p_transfer_worker) { + // Caller must have already acquired the mutex for the worker. if (!p_transfer_worker->texture_barriers.is_empty()) { + MutexLock transfer_worker_lock(transfer_worker_pool_texture_barriers_mutex); for (uint32_t i = 0; i < p_transfer_worker->texture_barriers.size(); i++) { transfer_worker_pool_texture_barriers.push_back(p_transfer_worker->texture_barriers[i]); } @@ -5354,8 +5353,11 @@ void RenderingDevice::_submit_transfer_workers(RDD::CommandBufferID p_draw_comma } } } +} - if (p_draw_command_buffer && !transfer_worker_pool_texture_barriers.is_empty()) { +void RenderingDevice::_submit_transfer_barriers(RDD::CommandBufferID p_draw_command_buffer) { + MutexLock transfer_worker_lock(transfer_worker_pool_texture_barriers_mutex); + if (!transfer_worker_pool_texture_barriers.is_empty()) { driver->command_pipeline_barrier(p_draw_command_buffer, RDD::PIPELINE_STAGE_COPY_BIT, RDD::PIPELINE_STAGE_ALL_COMMANDS_BIT, {}, {}, transfer_worker_pool_texture_barriers); transfer_worker_pool_texture_barriers.clear(); } @@ -5955,6 +5957,7 @@ void RenderingDevice::_end_frame() { // The command buffer must be copied into a stack variable as the driver workarounds can change the command buffer in use. RDD::CommandBufferID command_buffer = frames[frame].command_buffer; _submit_transfer_workers(command_buffer); + _submit_transfer_barriers(command_buffer); draw_graph.end(RENDER_GRAPH_REORDER, RENDER_GRAPH_FULL_BARRIERS, command_buffer, frames[frame].command_buffer_pool); driver->command_buffer_end(command_buffer); diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index b39ff9b920..6e81291cb1 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -1287,6 +1287,7 @@ private: LocalVector<uint32_t> transfer_worker_pool_available_list; LocalVector<RDD::TextureBarrier> transfer_worker_pool_texture_barriers; BinaryMutex transfer_worker_pool_mutex; + BinaryMutex transfer_worker_pool_texture_barriers_mutex; ConditionVariable transfer_worker_pool_condition; TransferWorker *_acquire_transfer_worker(uint32_t p_transfer_size, uint32_t p_required_align, uint32_t &r_staging_offset); @@ -1301,6 +1302,7 @@ private: void _check_transfer_worker_vertex_array(VertexArray *p_vertex_array); void _check_transfer_worker_index_array(IndexArray *p_index_array); void _submit_transfer_workers(RDD::CommandBufferID p_draw_command_buffer = RDD::CommandBufferID()); + void _submit_transfer_barriers(RDD::CommandBufferID p_draw_command_buffer); void _wait_for_transfer_workers(); void _free_transfer_workers(); diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp index 1cf3ec46f0..bf0530b565 100644 --- a/servers/rendering/rendering_device_graph.cpp +++ b/servers/rendering/rendering_device_graph.cpp @@ -142,7 +142,7 @@ RDD::BarrierAccessBits RenderingDeviceGraph::_usage_to_access_bits(ResourceUsage #endif } -bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const { +bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index, bool &r_intersection_partial_coverage) const { if (p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_COLOR_READ_WRITE && p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE) { // We don't check possible intersections for usages that aren't consecutive color or depth writes. return true; @@ -157,6 +157,11 @@ bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resour return true; } + if (!r_intersection_partial_coverage) { + // Indicate if this draw list only partially covers the region of the previous draw list. + r_intersection_partial_coverage = !current_draw_list_command.region.encloses(previous_draw_list_command.region); + } + // We check if the region used by both draw lists have an intersection. return previous_draw_list_command.region.intersects(current_draw_list_command.region); } @@ -473,7 +478,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr resource_tracker->usage = new_resource_usage; } - bool command_intersection_failed = false; + bool intersection_partial_coverage = false; if (search_tracker->write_command_or_list_index >= 0) { if (search_tracker->write_command_list_enabled) { // Make this command adjacent to any commands that wrote to this resource and intersect with the slice if it applies. @@ -485,7 +490,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr if (!resource_has_parent || search_tracker_rect.intersects(write_list_node.subresources)) { if (write_list_node.command_index == p_command_index) { ERR_FAIL_COND_MSG(!resource_has_parent, "Command can't have itself as a dependency."); - } else if (_check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index)) { + } else if (_check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index, intersection_partial_coverage)) { // Command is dependent on this command. Add this command to the adjacency list of the write command. _add_adjacent_command(write_list_node.command_index, p_command_index, r_command); @@ -501,8 +506,6 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr write_list_index = write_list_node.next_list_index; continue; } - } else { - command_intersection_failed = true; } } @@ -513,16 +516,14 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr // The index is just the latest command index that wrote to the resource. if (search_tracker->write_command_or_list_index == p_command_index) { ERR_FAIL_MSG("Command can't have itself as a dependency."); - } else if (_check_command_intersection(resource_tracker, search_tracker->write_command_or_list_index, p_command_index)) { + } else if (_check_command_intersection(resource_tracker, search_tracker->write_command_or_list_index, p_command_index, intersection_partial_coverage)) { _add_adjacent_command(search_tracker->write_command_or_list_index, p_command_index, r_command); - } else { - command_intersection_failed = true; } } } if (write_usage) { - if (resource_has_parent || command_intersection_failed) { + if (resource_has_parent || intersection_partial_coverage) { if (!search_tracker->write_command_list_enabled && search_tracker->write_command_or_list_index >= 0) { // Write command list was not being used but there was a write command recorded. Add a new node with the entire parent resource's subresources and the recorded command index to the list. const RDD::TextureSubresourceRange &tracker_subresources = search_tracker->texture_subresources; diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h index 6c8aa23fe7..222bff8f95 100644 --- a/servers/rendering/rendering_device_graph.h +++ b/servers/rendering/rendering_device_graph.h @@ -651,7 +651,7 @@ private: static bool _is_write_usage(ResourceUsage p_usage); static RDD::TextureLayout _usage_to_image_layout(ResourceUsage p_usage); static RDD::BarrierAccessBits _usage_to_access_bits(ResourceUsage p_usage); - bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const; + bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index, bool &r_intersection_partial_coverage) const; int32_t _add_to_command_list(int32_t p_command_index, int32_t p_list_index); void _add_adjacent_command(int32_t p_previous_command_index, int32_t p_command_index, RecordedCommand *r_command); int32_t _add_to_slice_read_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); |